c语言中10语句是,再谈C语言中的语句

在《C语言编程魔法书》中的14章第6节已经详细讨论了C语言的六种语句:标签语句(labeled statement)、复合语句(compound statement)、表达式语句(expression statement)、选择语句(selection statement)、迭代语句(iteration statement)以及跳转语句(jump statement)。这里,我将对其中的表达式语句中的一种特殊形式的语句再做讨论——即空语句(null statement)。空语句在《C语言编程魔法书》中没怎么提到,这里正好做个补充。

空语句属于一种特殊的表达式语句,其表达式完全为空,即就一个分号( ; )所构成的一条表达式语句。这里我们需要注意,别把void表达式语句、空语句、空复合语句搞混了,以下先展示这三种不同的语句概念:

(void)0; // 这是一条void表达式语句

; // 这是一条空语句

{ } // 这是一条空的复合语句

空语句可以出现在所有语句所能出现的地方,一般来说它没有特殊语义。但是这里各位需要注意的是,C语言中分号可作为一条表达式语句、声明和定义的结束符,也能表示一条空语句,这是分号的两种不同的语义。此外,C语言标准中其实把声明/定义同语句是分开的,因此声明和定义不属于语句。如果我们把“声明”写作为“声明语句”就显得不够专业了~😄 另外,复合语句的 } 可作为结束符;而在声明和定义中,只有定义函数时,} 才能作为结束符,而其他场合则不行。

下面我们举个例子:

int a = 0; // 这里的分号表示声明的结束

a = 1; ; // 这里有两个分号,左边的表示表达式语句的结束,右边的则表示一条空语句。因此这里有两条语句。

C语言中除了表达式语句和跳转语句,其他类型的语句都需要跟一条其他语句来构成一条完整语句的。比如说:

int a = 0;

LABEL: a = 1; // 这是一条标签语句,在标签LABEL后面跟了一条表达式语句

if(a > 0) a = -a; // 这是一条选择语句,其后面伴随的是一条表达式语句作为一条完整的语句

for(;;); // 这是一条迭代语句,其后面伴随的是一条空语句构成一条完整的语句

while(a > 0) { --a; } // 这是一条迭代语句,其后面伴随一条复合语句构成一条完整的语句

if(a < 0) a++;

else { --a; } // 这里从if开始到最后 } 结束是一条完整的选择语句

以上这些概念请务必牢记,后面提到的容易触碰的“陷阱”其实就与语句的性质与条数有关。

我们上面已经提到了,标签语句、选择语句和迭代语句需要伴随一条这6种语句中的任何一条作为完整语句,而且无论是哪种情况均可跟任何一种语句。为了方便起见,我们可以把标签语句的LABEL:,选择语句的if(condition)和switch(selection),迭代语句的while(condition)和for(sub-clauses)看作为“头”(header),然后后面再跟一条6种语句中的任何一种。因此,我们现在可以放开脑洞,看看以下代码,这些代码都是合法有效的,完全能通过编译的:

static void ctest(void)

{

int a = 10;

// 这里的选择语句头后面跟着一条标签语句

if(a > 0)

LABEL:

++a;

// 这里的选择语句头后面跟着一条跳转语句

if(a < 0)

goto LABEL;

// 这里的标签语句头后面跟着一条选择语句

LABEL2:

if(a == 0);

// 这里的选择语句头后面跟着一条空语句

switch(a);

// 这里的选择语句头后面跟着一条标签语句

switch(a)

LABEL3:

++a; // 这里的 ++a; 将永远执行不到,除非显式使用goto跳转语句跳到LABEL3

// 这里的选择语句头后面跟着一条标签语句

switch (a)

LABEL4:

{

case 11:

puts("case 11 reached!!");

break;

case 12:

puts("case 12 reached!!");

break;

default:

puts("default case reached!!");

break;

}

if(a == 11)

{

++a;

goto LABEL4;

}

// 这里选择语句头后面跟着一条标签语句:case 12: --a;

switch(a)

case 12:

--a; // 这里的 --a; 将会被执行到。

// 这里后面不允许添加 break; 跳转语句,因为到 --a; 为止已经是一条完整的选择语句了。

}

当你执行ctest函数之后,控制台将会打印出两行“ case 11 reached!! ”。看到这里,各位不要被C语言的灵活性吓到😅~正因如此,C语言在语法体系的设计方面是相当完备的。这里要详细讨论的是“LABEL4”这块。“LABEL4”所引出的标签语句与选择语句头switch(a)一起组成了一条完整的选择语句。这里与上面“LABEL3”处的选择语句不同,由于LABEL4标签语句是由一条带有case标签组的复合语句构成,因此这里完全可以跟switch配对上。因而,当执行到“LABEL4”上面的switch(a)之后,下面的case语句将会被选择执行。

此外,由于“LABEL4”所引出的是一条标签语句,因此这里变量a的上下文将会被保持住,后续对a的修改不影响这里的case判断。也就是说,在LABEL4处的a的值已经被hold住不变了,我们可以认为这里的a被赋值给了一个隐藏的临时常量,而后面的case标签语句将会根据此隐藏的临时常量的值进行选择执行。因此,这个函数被执行后将会两次都输出“ case 11 reached!! ”。

了解了上述语句的特性之后,我们以后在写选择语句、迭代语句的时候就要务必小心了,不要很悠哉地乱加分号,以免引起很难发现的bug。对于一般的表达式语句,后面跟多少分号都没问题,比如:

int a = 0;

a++;;; // 这里其实有三条语句😄

但以下代码将是一个bug:

int a = 0;

for(int i = 0; i < 10; i++); // 这里来了个分号……😅

++a;

大家猜猜,上述代码执行后,a的值是几?😏

有了上述对C语言选择语句和迭代语句潜在的陷阱之后,我们下面就要聊聊一些更高级的主题了——当一个宏定义遇上选择语句或迭代语句之后,会发生神马化学反应~

我们有时为了封装一些代码块,会使用宏函数。不过在使用宏函数的时候也很有可能会引发一些意向不到的问题,尤其是该宏函数与选择语句、循环语句连用的场合,比如以下代码:

#define MY_MACRO(n) if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); }

这句代码初步看起来没啥问题吧?然而,我们再看看以下代码会发生神马:

int n = 100;

if(n >= 10)

MY_MACRO(10);

else // 发生编译错误:Expected expression

{

puts("n below ")

}

我们看到,这个看似朴素的宏函数“MY_MACRO”上面加了if语句之后,这里就出问题了!问题原因在哪儿?上面已经详细讨论过了。由于MY_MACRO在预处理时进行宏展开后,实际上是一条完整的选择语句,然而这里MY_MACRO(10)后面又跟了一个分号,导致了该分号作为一条空语句的形式出现,从而与else无法配上。这里,MY_MACROz(10)宏展开后如以下代码所示:

int n = 100;

if(n >= 10)

if((n) > 0)

{ printf("n = %d\n", n); puts("PASS!"); } ;

else // 发生编译错误:Expected expression

{

puts("n below 10!");

}

所以,我们如果写成以下形式则可避免宏展开后发生编译错误:

int n = 100;

if(n >= 10)

{

MY_MACRO(10);

}

else // 发生编译错误:Expected expression

{

puts("n below 10!");

}

当然,如果我们强迫第三方开发者一定要在选择、迭代语句后要用复合语句,那么似乎也属于要求比较苛刻了,那么我们自己定义的宏函数用哪些技巧可以使得调用起来更加灵活,不加如此约束呢?我们在市面上看到比较常用的是使用do-while迭代语句。do-while有何好处呢?while()在最后并不是以 } 符号结尾的,因此后面所跟的分号将作为整条迭代语句的结束符使用,而不会变成空语句。比如:

#define MY_MACRO(n) do { \

if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); } \

} while(0) // 注意,这里可不能加分号

int main(int argc, const char * argv[])

{

int x = 100;

if(x >= 10)

MY_MACRO(10);

else

{

puts("x below 10!");

}

}

这么一来,我们可以看到上述代码编译、运行都不会有任何问题。当然,除了do-while之外还可以用其他技巧,总之,我们的目标就是要把宏函数调用后面的分号作为语句的结束符,而不是空语句。比如,我这里“别出心裁”地使用if-else选择语句,当然不能说这种解决方案完全完美,但还是能解决问题😊

#define MY_MACRO(n) if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); } \

else

int main(int argc, const char * argv[])

{

int x = 100;

if(x >= 10)

MY_MACRO(10);

else

{

puts("x below 10!");

}

}

这段代码编译运行也不会有任何问题。而且宏定义部分也比之前的do-while版本更简洁清爽。

我这里也属于抛砖引玉,大家可以随之打开脑洞,设计出更有创造力的表达方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值