2.3 声明和初始化
1. 声明 (Declaration)
声明是一种引入一个或多个标识符 (Identifier) 到程序中,并指定其含义及属性的 C 语言构造。 最简单的声明包括两部分:类型说明符 (Type Specifier) 和标识符。
int a; // "int"是类型说明符,"a"是标识符相同类型的对象可以在一起声明:类型说明符 标识符 1, 标识符 2, 标识符 3, ... , 标识符 n;
但不推荐这种做法,尤其是类型较复杂时。
int a, b, c; // 声明三个 int 类型的对象2. 初始化 (Initialization)
对象声明可以通过名为初始化的步骤提供其初始值。
int a = 3;
int b = {3}; // 可选地用花括号环绕2
此后 a 和 b 的值都为 3。
3. 习题
下面的声明混用了本页讲过的几种写法。
#include <stdio.h>
int main(void) {
int a = 3;
int b = {3};
int c = (3);
int d = {3,};
int e = {3, 4};
int f = {};
printf("%d %d %d %d %d %d\n", a, b, c, d, e, f);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
- 先用
gcc -std=c17 -Wall -Wextra编译,记下警告;再加上-pedantic-errors编译,记下错误。哪些写法在 C17 下不合法? - 在能通过编译的前提下写出输出。
{3,}末尾多出的逗号带来了什么? - 把
int a = 3, b, c;与int a = 3, b = 4, c = 5;各写一遍,指出其中哪些对象得到了初始值。 - 初始化不是赋值:写
int x = {3};合法,而先声明再写x = {3};不合法。编译核对后者的报错,并说明这两种写法在语义上的区别(初始化发生在对象创建时,赋值发生在对象已经存在之后)。
验收:不加 -pedantic-errors 时只出现一条 excess elements in scalar initializer 警告,输出为 3 3 3 3 3 0;加上 -pedantic-errors 后,{3, 4} 与 {} 各报一条 error;x = {3}; 的报错是 expected expression before '{' token。
同一条声明里的多个声明符按从左到右的顺序处理,初始化式只能使用已经处理过的名字。编译下面的程序。
#include <stdio.h>
int main(void) {
int a = 3, b = a + 1;
int *p = &a, q = b * 2;
printf("%d %d %d %d\n", a, b, (int)sizeof *p, q);
return 0;
}2
3
4
5
6
7
8
9
写出输出。
p和q是同一类型吗?sizeof *p与sizeof q分别是多少?单独编译下面的函数,抄下 error 并解释:为什么初始化式里看不到同一行后面才声明的
base。cint main(void) { int doubled = base * 2, base = 10; return doubled + base; }1
2
3
4把
int a = b, b = 1;单独编译,抄下 error。再把它拆成两行、调整顺序,使程序合法并输出a = 1, b = 1。把
int *p = &a, q = b * 2;拆成两行分别声明。说明为什么本页不推荐int *p, q;这类写法:星号只修饰紧跟其后的那个声明符。下面的函数每次调用都返回 1:
cint next_id(void) { int id = 0; id = id + 1; return id; }1
2
3
4
5调用三次并打印,确认结果。把
int id = 0;改成static int id = 0;后重新运行,结果变为 1、2、3。用初始化的发生时机解释这一差别。这一问涉及存储期,属于第 15 章的内容,可以跳过。
验收:第 1 问 printf 输出 3 4 4 8,其中 8 是 q 的值,sizeof *p 与 sizeof q 都是 4;第 4 问拆开后 sizeof p 为 8、sizeof q 为 4;第 5 问前一种写法三次都打印 1,后一种打印 1、2、3。
写一个收款程序 receipt.c,全部金额用整数分表示,避免浮点误差。
程序里固定以下数据:单价 1250 分,数量 3 件,折扣率 10%,税率 6%(用千分之 60 表示,仍按整数计算)。依次计算小计、折扣、折后金额、税额和应付金额,每项各用一个对象保存,全部在声明处初始化,后续不再出现未初始化的对象。
要求:
- 每个对象单独一行声明并初始化;唯一的例外是折扣率与税率,要求用
int discount_rate_percent = {10};这种带花括号的写法声明,并在注释里说明花括号在这里起什么作用。 - 折扣与税额都用整数除法计算,结果四舍五入到分:
(amount * rate + half) / whole的形式,除数为 100 或 1000。 - 用
gcc -std=c17 -Wall -Wextra -Wconversion构建,必须没有任何诊断。 - 输出五行:
subtotal、discount、taxable、tax、payable各一行,数值以分为单位。 - 把数量和单价改成 5 与 725 再运行一次,记录新的结果,并说明换数据时哪些声明需要改、哪些不需要。
验收:第一组数据输出 subtotal = 3750、discount = 375、taxable = 3375、tax = 203、payable = 3578(税额按四舍五入);第二组输出 subtotal = 3625、discount = 363(四舍五入)、taxable = 3262、tax = 196、payable = 3458。
下面的程序在声明和初始化上有五处问题。
#include <stdio.h>
int main(void) {
int a = b, b = 1;
int x = {1, 2};
int y = {};
double price = 19.99;
int cents = price * 100;
int self = self;
printf("%d %d %d %d %d %d\n", a, b, x, y, cents, self);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
(1) [3.2] 用 gcc -std=c17 -Wall -Wextra 编译,抄下全部 error 与 warning,逐条对应到源代码中的声明。此时编译器还没有提示 self 的问题,为什么?
(2) [4.3] 加上 -pedantic-errors、-Wconversion、-Winit-self 重新编译,抄下新增的诊断。为什么 self = self 仍然没有出现在输出里?把前四行逐一改成合法写法、只留下 int self = self; 再编译,记录这次出现的警告,并解释这个警告为什么只有在前面的错误消失后才出现。五处问题各自违反了本页的哪条规则?
(3) [4.4] 逐个修正,要求 -std=c17 -pedantic-errors -Wall -Wextra -Wconversion -Winit-self 下没有任何诊断,运行输出为 1 1 1 0 1999 0。其中 cents 要改成四舍五入后再转换。
(4) [4.3*] 把 int y = {}; 保留下来,改用 -std=c2x 编译。它合法吗?y 的值是多少?说明这一写法要求的最低标准版本。
(5) [5.4] 把这段程序里的声明改写成每行一个、顺序合法、没有多余花括号的版本。改写后代码行数变多,但哪一类错误从此不会再出现?结合本页“不推荐多对象同声明”的说法给出你的判断。
验收:修正后的输出为 1 1 1 0 1999 0;int y = {}; 在 -std=c2x 下通过并得到 0,在 C17 下加 -pedantic-errors 报 ISO C forbids empty initializer braces before C2X。