第
1
章
C
语言
本章主要描述 C 语言一些基本要素。当你开始编写 C 程序时,你可能对 C 语言的一些基本问题感到困惑,如 C 语言所使用的约定、关键字和术语等。本章将回答这方面你经常会遇到的一些问题。
例如, switch 语句是最常用的一种 C 语言构件,本章将回答与它有关的三个常见问题。本章还涉及其它几个问题,如循环、分支、运算符的优先级和程序块技术。在阅读本章时,请注意有关 switch 语句和运算符优先级的一些问题,这些问题常常会使 C 语言的初学者感到迷惑。
1 . 1 什么是局部程序块 (local block)?
局部程序块是指一对大括号 ({}) 之间的一段 C 语言程序。一个 C 函数包含一对大括号,这对大括号之间的所有内容都包含在一个局部程序块中。 if 语句和 swich 语句也可以包含一对大括号,每对大括号之间的代码也属于一个局部程序块。此外,你完全可以创建你自己的局部程序块,而不使用 C 函数或基本的 C 语句。你可以在局部程序块中说明一些变量,这种变量被称为局部变量,它们只能在局部程序块的开始部分说明,并且只在说明它的局部程序块中有效。如果局部变量与局部程序块以外的变量重名,则前者优先于后者。下面是一个使用局部程序块的例子:
#include <stdio.h>
void main(void);
void main()
{
/ * Begin local block for function main() * /
iht test_ var = 10;
printf("Test variable before the if statement: %d/n", test_var);
if (test_var>5)
{
/ * Begin local block for "if" statement * /
int test_ var = 5;
printf("Test variable within the if statement: %d/n",
test_var);
{
/ * Begin independent local block (not tied to
any function or keyword) * /
int test_var = 0;
printf (
"Test variable within the independent local block: %d/n",
test_var)
}
/ * End independent local block * /
printf ("Test variable after the if statement: %d/n", test_var);
}
/*End local block for function main () * /
上例产生如下输出结果:
Test variable before the if statement: 10
Test variable within the if statement: 5
Test variable within the independent local block:0
Test variable after the if statement: 10
注意,在这个例子中,每次 test_var 被定义时,它都要优先于前面所定义的 test_var 变量。此外还要注意,当 if 语句的局部程序块结束时,程序重新进入最初定义的 test_var 变量的作用范围,此时 test_var 的值为 10 。
请参见:
1 . 2 可以把变量保存在局部程序块中吗 ?
1 . 2 可以把变量保存在局部程序块中吗 ?
用局部程序块来保存变量是不常见的,你应该尽量避免这样做,但也有极少数的例外。例如,为了调试程序,你可能要说明一个全局变量的局部实例,以便在相应的函数体内部进行测试。为了使程序的某一部分变得更易读,你也可能要使用局部程序块,例如,在接近变量被使用的地方说明一个变量有时就会使程序变得更易读。然而,编写得较好的程序通常不采用这种方式来说明变量,你应该尽量避免使用局部程序块来保存变量。
请参见:
1 . 1 什么是局部程序块 ?
1 . 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
如果你有两个以上基于同一个数字 (numeric) 型变量的条件表达式,那么最好使用一条 switch 语句。例如,与其使用下述代码:
if (x ==l)
printf ("x is equal to one. /n");
else if (x ==2)
printf ("x is equal to two. /n");
else if (x = =3)
printf ("x is equal to three. /n");
else
printf ("x is not equal to one, two, or three. /n");
不如使用下述代码 , 它更易于阅读和维护:
switch (x)
{
case 1: printf ("x is equal to one. /n");
break;
case 2: printf ("x is equal to two. /n");
break
case 3: printf ('x is equal to three. /n");
break;
default: printf ("x is not equal to one, two, or three. /n");
break;
}
注意,使用 switch 语句的前提是条件表达式必须基于同一个数字型变量。例如,尽管下述 if 语句包含两个以上的条件,但该例不能使用 switch 语句,因为该例基于字符串比较,而不是数字比较:
char *name="Lupto";
if(!stricmp(name , "Isaac"))
printf("Your name means'Laughter' . /n");
else if(!stricmp(name , "Amy"))
printf("Your name means'Beloved' . /n");
else if(!stricmp(name , "Lloyd"))
printf("Your name means'Mysterious' . /n");
else
printf("I haven't a clue as to what your name means . /n");
请参见:
1 . 4 switch 语句必须包含 default 分支吗 7
1 . 5 switch 语句的最后一个分支可以不要 break 语句吗 ?
1. 4 switch 语句必须包含 default 分支吗 ?
不,但是为了进行错误检查或逻辑检查,还是应该在 switch 语句中加入 default 分支。例如,下述 switch 语句完全合法:
switch (char_code)
{
case tyt:
case 'y': printf ( " You answered YES ! /n" )
break
case 'N':
case 'n': printf ("You answered NO!/n");
break
}
但是,如果一个未知字符被传递给这条 switch 语句,会出现什么情况呢 ? 这时,程序将没有任何输出。因此,最好还是加入一个 default 分支,以处理这种情况:
......
default: printf ("Unknown response : %d/n", char_code);
break
......
此外, default 分支能给逻辑检查带来很多方便。例如,如果用 switch 语句来处理数目固定的条件,而且认为这些条件之外的值都属于逻辑错误,那么可以加入一个 default 分支来辨识逻辑错误。请看下列:
void move_cursor (int direction)
{
switch (direction)
{
case UP: cursor_up()
break
case DOWN: cursor_down()
break
case LEFT: cursor_left ()
break
case RIGHT: cursor_ right ( )
break
default: printf ("Logic error on line number %ld!!! /n",
__ LINE__ )
break
}
}
请参见:
1 . 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
1 . 5 Switch 语句的最后一个分支可以不要 break 语句吗 ?
1 . 5 switch 语句的最后一个分支可以不要 break 语句吗 ?
尽管 switch 语句的最后一个分支不一定需要 break 语句,但最好还是在 switch 语句的每个分支后面加上 break 语句,包括最后一个分支。这样做的主要原因是:你的程序很可能要让另一个人来维护,他可能要增加一些新的分支,但没有注意到最后一个分支没有 break 语句,结果使原来的最后一个分支受到其后新增分支的干扰而失效。在每个分支后面加上 break 语句将防止发生这种错误并增强程序的安全性。此外,目前大多数优化编译程序都会忽略最后一条 break 语句,所以加入这条语句不会影响程序的性能。
请参见:
1. 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
1. 4 switch 语句必须包含 default 分支吗 ?
1. 6 除了在 for 语句中之外,在哪些情况下还要使用逗号运算符 ?
逗号运算符通常用来分隔变量说明、函数参数、表达式以及 for 语句中的元素。下例给出了使用逗号的多种方式:
#include <stdio.h>
#include <stdlib.h>
void main(void);
void main ()
{
/ * Here, the comma operator is used to separate
three variable declarations. * /
int i, j, k;
/ * Notice how you can use the comma operator to perform
multiple initializations on the same line. * /
i=0, j=1, k=2;
printf("i= %d, j=%d, k= %d/n", i, j, k);
/ * Here, the comma operator is used to execute three expressions
in one line: assign k to i, increment j, and increment k.
The value that i receives is always the rigbtmost expression. * /
i= ( j++, k++ );
printf("i=%d, j=%d, k=%d/n", i, j, k);
/ * Here, the while statement uses the comma operator to
assign the value of i as well as test it. * /
while (i=(rand() % 100), i !=50)
printf("i is %d, trying again... /n", i)
printf ("/nGuess what? i is 50!/n" )
}
请注意下述语句:
i : (j++ , k++)
这条语句一次完成了三个动作,依次为:
(1) 把 k 值赋给 i 。这是因为左值 (lvaule) 总是等于最右边的参数,本例的左值等于 k 。注意,本例的左值不等于 k++ ,因为 k++ 是一个后缀自增表达式,在把 k 值赋给 j 之后 k 才会自增。如果所用的表达式是 ++k ,则 ++k 的值会被赋给 i ,因为 ++k 是一个前缀自增表达式, k 的自增发生在赋值操作之前。
(2)j 自增。
(3)k 自增。
此外,还要注意看上去有点奇怪的 while 语句:
while (i=(rand() % 100), i !=50)
printf("i is %d, trying again... /n");
这里,逗号运算符将两个表达式隔开, while 语句的每次循环都将计算这两个表达式的值。逗号左边是第一个表达式,它把 0 至 99 之间的一个随机数赋给 i ;第二个表达式在 while 语句中更常见,它是一个条件表达式,用来判断 i 是否不等于 50 。 while 语句每一次循环都要赋予 i 一个新的随机数,并且检查其值是否不等于 50 。最后, i 将被随机地赋值为 50 ,而 while 语句也将结束循环。
请参见:
1 . 12 运算符的优先级总能保证是 “ 自左至右 ” 或 “ 自右至左 ” 的顺序吗 ?
1 . 13 ++var 和 var++ 有什么区别 ?
1 . 7 怎样才能知道循环是否提前结束了 ?
循环通常依赖于一个或多个变量,你可以在循环外检查这些变量,以确保循环被正确执行。请看下例:
int x
char * cp[REQUESTED_BLOCKS]
/ * Attempt (in vain, I must add... )to
allocate 512 10KB blocks in memory. * /
for (x = 0; x<REQUESTED_ BLOCKS ; x++ )
{
cpi[x]= (char * ) malloc (10000,1)
if (cp[x]= = (char * ) NULL)
break
}
/ * If x is less than REQUESTED-BLOCKS,
the loop has ended prematurely. * /
if (x<REQUESTED_BLOCKS)
printf ("Bummer ! My loop ended prematurely ! /n" );
注意,如果上述循环执行成功,它一定会循环 512 次。紧接着循环的 if 语句用来测试循环次数,从而判断循环是否提前结束。如果变量 x 的值小于 512 ,就说明循环出错了。
1 . 8 goto , longjmp() 和 setjmp() 之间有什么区别 ?
goto 语句实现程序执行中的近程跳转 (local jump) , longjmp() 和 setjmp() 函数实现程序执行中的远程跳转 (nonlocaljump ,也叫 farjump) 。通常你应该避免任何形式的执行中跳转,因为在程序中使用 goto 语句或 longjmp() 函数不是一种好的编程习惯。
goto 语句会跳过程序中的一段代码并转到一个预先指定的位置。为了使用 goto 语句,你要预先指定一个有标号的位置作为跳转位置,这个位置必须与 goto 语句在同一个函数内。在不同的函数之间是无法实现 goto 跳转的。下面是一个使用 goto 语句的例子:
void bad_programmers_function(void)
{
int x
printf("Excuse me while I count to 5000... /n") ;
x----l~
while (1)
{
printf(" %d/n", x)
if (x ==5000)
goto all_done
else
x=x+1;
}
all_done:
prinft("Whew! That wasn't so bad, was it?/n");
}
如果不使用 goto 语句,是例可以编写得更好。下面就是一个改进了实现的例子:
void better_function (void)
{
int x
printf("Excuse me while I count to 5000... /n");
for (x=1; x<=5000, x++)
printf(" %d/n", x)
printf("Whew! That wasn't so bad, was it?/n") ;
}
前面已经提到, longjmp() 和 setjmp() 函数实现程序执行中的远程跳转。当你在程序中调用 setjmp() 时,程序当前状态将被保存到一个 jmp_buf 类型的结构中。此后,你可以通过调用 longjmp() 函数恢复到调用 setjmp() 时的程序状态。与 goto 语句不同, longjmp() 和 setjmp() 函数实现的跳转不一定在同一个函数内。然而,使用这两个函数有一个很大的缺陷,当程序恢复到它原来所保存的状态时,它将失去对所有在 longjmp() 和 setjmp() 之间动态分配的内存的控制,也就是说这将浪费所有在 longjmp() 和 setjmp() 之间用 malloc() 和 calloc() 分配所得的内存,从而使程序的效率大大降低。因此,你应该尽量避免使用 longjmp() 和 setjmp() 函数,它们和 goto 语句一样,都是不良编程习惯的表现。
下面是使用 longjmp() 函数和 setjmp() 函数的一个例子:
#include <stdio.h>
#include <setjmp.h>
jmp_buf saved_state;
void main(void);
void call_ longjmp (void);
void main(void)
{
int ret_code;
printf("The current state of the program is being saved... /n");
ret_code = setjmp (saved_state)
if (ret_code ==1)
{
printf("The longjmp function has been called. /n" )
printf("The program's previous state has been restored. /n");
exit(0)
}
printf("I am about to call longjmp and/n");
printf('return to the previous program state... /n" )
call_ longjmp ( )
}
void call_longjmp (void)
{
longjmp (saved_state, 1 )
}
1 . 9 什么是左值 (lvaule)?
左值是指可以被赋值的表达式。左值位于赋值语句的左侧,与其相对的右值 (rvaule ,见 1 . 11) 则位于赋值语句的右侧。每条赋值语句都必须有一个左值和一个右值。左值必须是内存中一个可存储的变量,而不能是一个常量。下面给出了一些左值的例子:
int x;
int *p_int;
x=1;
p_int=5;
变量 x 是一个整数,它对应于内存中的一个可存储位置,因此,在语句 “x = 1” 中, x 就是一个左值。注意,在第二个赋值语句 “*p_int = 5" 中,通过 “*” 修饰符访问 p_int 所指向的内存区域;因此, p_int 是一个左值。相反,下面的几个例子就不是左值:
#define CONST_VAL 10
int x
/* example 1 * /
l=x;
/ * example 2 * /
CONST_VAL = 5;
在上述两条语句中,语句的左侧都是一个常量,其值不能改变,因为常量不表示内存中可
存储的位置。因此,这两条赋值语句中没有左值,编译程序会指出它们是错误的。
请参见:
1. 10 数组 (array) 可以是左值吗 ? .
1. 11 什么是右值 (rvaule)?
1 . 10 数组 (array) 可以是左值吗 ?
在 1 . 9 中,左值被定义为可被赋值的表达式。那么,数组是可被赋值的表达式吗 ? 不是,因为数组是由若干独立的数组元素组成的,这些元素不能作为一个整体被赋值。下述语句是非法的:
int x[5] , y[5];
x=y ;
不过,你可以通过 for 循环来遍历数组中的每个元素,并分别对它们赋值,例如:
int i ;
int x[5];
int y[5];
......
for(i=0; i<5 , i++)
x = y ;
......
此外,你可能想一次拷贝整个数组,这可以通过象 memcpy() 这样的函数来实现,例如:
memcpy(x , y , sizeof(y)) ;
与数组不同,结构 (structure) 可以作为左值。你可以把一个结构变量赋给另一个同类型的结构变量,例如:
typedef struct t_name
{
charlast_name[25] ;
char first_name[15];
char middle-init [2];
} NAME
...
NAME my_name, your_name;
...
your_name = my_name;
...
在上例中,结构变量 my_name 的全部内容被拷贝到结构变量 your_name 中,其作用和下述语句是相同的:
memcpy(your_name , my_name , sizeof(your_name);
请参见:
1 . 9 什么是左值 (lvaule)?
1 . 11 什么是右值 (rvaule)?
1 . 11 什么是右值 (rvaule)?
在 1 . 9 中,左值被定义为可被赋值的表达式,你也可以认为左值是出现在赋值语句左边的表达式。这样,右值就可以被定义为能赋值的表达式,它出现在赋值语句的右边。与左值不同,右值可以是常量或表达式:例如:
int X , y;
x = 1; /* 1 iS an rvalue, x is an lvalue */
y = (x+1); /* (x+1)is an rvalue ; y is an lvalue */
在 1 . 9 中已经介绍过,一条赋值语句必须有一个左值和一个右值,因此,下述语句无法通过编译,因为它缺少一个右值:
int x;
x=void_function_call(); /* the{unction void—function—call()
returns nothing */
如果上例中的函数返回一个整数,那么它可以被看作一个右值,因为它的返回值可以存储
到左值 x 中。
请参见:
1 . 9 什么是左值 (lvaule)?
1 . 10 数组可以是左值吗 ?
1 . 12 运算符的优先级总能保证是 “ 自左至右 ” 或 “ 自右至左 ” 的顺序吗 ?
对这个问题的简单回答是:这两种顺序都无法保证。 C 语言并不总是自左至右或自右至左求值,一般说来,它首先求函数值,其次求复杂表达式的值,最后求简单表达式的值。此外,为了进一步优化代码,目前流行的大多数 C 编译程序常常会改变表达式的求值顺序。因此,你应该用括号明确地指定运算符的优先级。例如,请看下述表达式:
a=b+c/d/function—call() * 5
上述表达式的求值顺序非常模糊,你很可能得不到所要的结果,因此,你最好明确地指定运算符的优先级:
a = b+(((c/d)/function—call())* 5)
这样,就能确保表达式被正确求值,而且编译程序不会为了优化代码而重新安排运算符的优先级了。
1 . 13 ++var 和 var++ 有什么区别 ?
“++” 运算符被称为自增运算符。如果 “++” 运算符出现在变量的前面 (++var) ,那么在表达式使用变量之前,变量的值将增加 1 。如果 “++” 运算符出现在变量之后 (var++) ,那么先对表达式求值,然后变量的值才增加 1 。对自减运算符 (--) 来说,情况完全相同。如果运算符出现在变量的前面,则相应的运算被称为前缀运算;反之,则称为后缀运算。
例如,请看一个使用后缀自增运算符的例子:
int x, y;
x=1;
y=(x++* 5) ;
上例使用了后缀自增运算符,在求得表达式的值之后, x 的值才增加 1 ,因此, y 的值为 1 乘以 5 ,等于 5 。在求得表达式的值之后, x 自增为 2 。
现在看一个使用前缀自增运算符的例子:
int x, y;
x=1;
y=(++x*5);
这个例子和前一个相同,只不过使用了前缀自增运算符,而不是后缀自增运算符,因此, x 的值先增加 1 ,变为 2 ,然后才求得表达式的值。这样, y 的值为 2 乘以 5 ,等于 10 。
1 . 14 取模运算符 (modulus operator)“ % ” 的作用是什么 ?
取模运算符 “ % ” 的作用是求两个数相除的余数。例如,请看下面这段代码:
x=15/7;
如果 x 是一个整数, x 的值将为 2 。然而,如果用取模运算符代替除法运算符 "/" ,得到的结果就不同了:
X=15 % 7;
这个表达式的结果为 15 除以 7 的余数,等于 1 。这就是说, 15 除以 7 得 2 余 1 。
取模运算符通常用来判断一个数是否被另一个数整除。例如,如果你要打印字母表中序号为 3 的倍数的字母,你可以使用下面这段代码:
int x;
for(x = 1; x<=26; x++)
if((x%3) == 0)
printf("%c"; x+64);
上例将输出字符串 "cfilorux" ,即字母表中序号为 3 的倍数的所有字母。
本章主要描述 C 语言一些基本要素。当你开始编写 C 程序时,你可能对 C 语言的一些基本问题感到困惑,如 C 语言所使用的约定、关键字和术语等。本章将回答这方面你经常会遇到的一些问题。
例如, switch 语句是最常用的一种 C 语言构件,本章将回答与它有关的三个常见问题。本章还涉及其它几个问题,如循环、分支、运算符的优先级和程序块技术。在阅读本章时,请注意有关 switch 语句和运算符优先级的一些问题,这些问题常常会使 C 语言的初学者感到迷惑。
1 . 1 什么是局部程序块 (local block)?
局部程序块是指一对大括号 ({}) 之间的一段 C 语言程序。一个 C 函数包含一对大括号,这对大括号之间的所有内容都包含在一个局部程序块中。 if 语句和 swich 语句也可以包含一对大括号,每对大括号之间的代码也属于一个局部程序块。此外,你完全可以创建你自己的局部程序块,而不使用 C 函数或基本的 C 语句。你可以在局部程序块中说明一些变量,这种变量被称为局部变量,它们只能在局部程序块的开始部分说明,并且只在说明它的局部程序块中有效。如果局部变量与局部程序块以外的变量重名,则前者优先于后者。下面是一个使用局部程序块的例子:
#include <stdio.h>
void main(void);
void main()
{
/ * Begin local block for function main() * /
iht test_ var = 10;
printf("Test variable before the if statement: %d/n", test_var);
if (test_var>5)
{
/ * Begin local block for "if" statement * /
int test_ var = 5;
printf("Test variable within the if statement: %d/n",
test_var);
{
/ * Begin independent local block (not tied to
any function or keyword) * /
int test_var = 0;
printf (
"Test variable within the independent local block: %d/n",
test_var)
}
/ * End independent local block * /
printf ("Test variable after the if statement: %d/n", test_var);
}
/*End local block for function main () * /
上例产生如下输出结果:
Test variable before the if statement: 10
Test variable within the if statement: 5
Test variable within the independent local block:0
Test variable after the if statement: 10
注意,在这个例子中,每次 test_var 被定义时,它都要优先于前面所定义的 test_var 变量。此外还要注意,当 if 语句的局部程序块结束时,程序重新进入最初定义的 test_var 变量的作用范围,此时 test_var 的值为 10 。
请参见:
1 . 2 可以把变量保存在局部程序块中吗 ?
1 . 2 可以把变量保存在局部程序块中吗 ?
用局部程序块来保存变量是不常见的,你应该尽量避免这样做,但也有极少数的例外。例如,为了调试程序,你可能要说明一个全局变量的局部实例,以便在相应的函数体内部进行测试。为了使程序的某一部分变得更易读,你也可能要使用局部程序块,例如,在接近变量被使用的地方说明一个变量有时就会使程序变得更易读。然而,编写得较好的程序通常不采用这种方式来说明变量,你应该尽量避免使用局部程序块来保存变量。
请参见:
1 . 1 什么是局部程序块 ?
1 . 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
如果你有两个以上基于同一个数字 (numeric) 型变量的条件表达式,那么最好使用一条 switch 语句。例如,与其使用下述代码:
if (x ==l)
printf ("x is equal to one. /n");
else if (x ==2)
printf ("x is equal to two. /n");
else if (x = =3)
printf ("x is equal to three. /n");
else
printf ("x is not equal to one, two, or three. /n");
不如使用下述代码 , 它更易于阅读和维护:
switch (x)
{
case 1: printf ("x is equal to one. /n");
break;
case 2: printf ("x is equal to two. /n");
break
case 3: printf ('x is equal to three. /n");
break;
default: printf ("x is not equal to one, two, or three. /n");
break;
}
注意,使用 switch 语句的前提是条件表达式必须基于同一个数字型变量。例如,尽管下述 if 语句包含两个以上的条件,但该例不能使用 switch 语句,因为该例基于字符串比较,而不是数字比较:
char *name="Lupto";
if(!stricmp(name , "Isaac"))
printf("Your name means'Laughter' . /n");
else if(!stricmp(name , "Amy"))
printf("Your name means'Beloved' . /n");
else if(!stricmp(name , "Lloyd"))
printf("Your name means'Mysterious' . /n");
else
printf("I haven't a clue as to what your name means . /n");
请参见:
1 . 4 switch 语句必须包含 default 分支吗 7
1 . 5 switch 语句的最后一个分支可以不要 break 语句吗 ?
1. 4 switch 语句必须包含 default 分支吗 ?
不,但是为了进行错误检查或逻辑检查,还是应该在 switch 语句中加入 default 分支。例如,下述 switch 语句完全合法:
switch (char_code)
{
case tyt:
case 'y': printf ( " You answered YES ! /n" )
break
case 'N':
case 'n': printf ("You answered NO!/n");
break
}
但是,如果一个未知字符被传递给这条 switch 语句,会出现什么情况呢 ? 这时,程序将没有任何输出。因此,最好还是加入一个 default 分支,以处理这种情况:
......
default: printf ("Unknown response : %d/n", char_code);
break
......
此外, default 分支能给逻辑检查带来很多方便。例如,如果用 switch 语句来处理数目固定的条件,而且认为这些条件之外的值都属于逻辑错误,那么可以加入一个 default 分支来辨识逻辑错误。请看下列:
void move_cursor (int direction)
{
switch (direction)
{
case UP: cursor_up()
break
case DOWN: cursor_down()
break
case LEFT: cursor_left ()
break
case RIGHT: cursor_ right ( )
break
default: printf ("Logic error on line number %ld!!! /n",
__ LINE__ )
break
}
}
请参见:
1 . 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
1 . 5 Switch 语句的最后一个分支可以不要 break 语句吗 ?
1 . 5 switch 语句的最后一个分支可以不要 break 语句吗 ?
尽管 switch 语句的最后一个分支不一定需要 break 语句,但最好还是在 switch 语句的每个分支后面加上 break 语句,包括最后一个分支。这样做的主要原因是:你的程序很可能要让另一个人来维护,他可能要增加一些新的分支,但没有注意到最后一个分支没有 break 语句,结果使原来的最后一个分支受到其后新增分支的干扰而失效。在每个分支后面加上 break 语句将防止发生这种错误并增强程序的安全性。此外,目前大多数优化编译程序都会忽略最后一条 break 语句,所以加入这条语句不会影响程序的性能。
请参见:
1. 3 什么时候用一条 switch 语句比用多条 if 语句更好 ?
1. 4 switch 语句必须包含 default 分支吗 ?
1. 6 除了在 for 语句中之外,在哪些情况下还要使用逗号运算符 ?
逗号运算符通常用来分隔变量说明、函数参数、表达式以及 for 语句中的元素。下例给出了使用逗号的多种方式:
#include <stdio.h>
#include <stdlib.h>
void main(void);
void main ()
{
/ * Here, the comma operator is used to separate
three variable declarations. * /
int i, j, k;
/ * Notice how you can use the comma operator to perform
multiple initializations on the same line. * /
i=0, j=1, k=2;
printf("i= %d, j=%d, k= %d/n", i, j, k);
/ * Here, the comma operator is used to execute three expressions
in one line: assign k to i, increment j, and increment k.
The value that i receives is always the rigbtmost expression. * /
i= ( j++, k++ );
printf("i=%d, j=%d, k=%d/n", i, j, k);
/ * Here, the while statement uses the comma operator to
assign the value of i as well as test it. * /
while (i=(rand() % 100), i !=50)
printf("i is %d, trying again... /n", i)
printf ("/nGuess what? i is 50!/n" )
}
请注意下述语句:
i : (j++ , k++)
这条语句一次完成了三个动作,依次为:
(1) 把 k 值赋给 i 。这是因为左值 (lvaule) 总是等于最右边的参数,本例的左值等于 k 。注意,本例的左值不等于 k++ ,因为 k++ 是一个后缀自增表达式,在把 k 值赋给 j 之后 k 才会自增。如果所用的表达式是 ++k ,则 ++k 的值会被赋给 i ,因为 ++k 是一个前缀自增表达式, k 的自增发生在赋值操作之前。
(2)j 自增。
(3)k 自增。
此外,还要注意看上去有点奇怪的 while 语句:
while (i=(rand() % 100), i !=50)
printf("i is %d, trying again... /n");
这里,逗号运算符将两个表达式隔开, while 语句的每次循环都将计算这两个表达式的值。逗号左边是第一个表达式,它把 0 至 99 之间的一个随机数赋给 i ;第二个表达式在 while 语句中更常见,它是一个条件表达式,用来判断 i 是否不等于 50 。 while 语句每一次循环都要赋予 i 一个新的随机数,并且检查其值是否不等于 50 。最后, i 将被随机地赋值为 50 ,而 while 语句也将结束循环。
请参见:
1 . 12 运算符的优先级总能保证是 “ 自左至右 ” 或 “ 自右至左 ” 的顺序吗 ?
1 . 13 ++var 和 var++ 有什么区别 ?
1 . 7 怎样才能知道循环是否提前结束了 ?
循环通常依赖于一个或多个变量,你可以在循环外检查这些变量,以确保循环被正确执行。请看下例:
int x
char * cp[REQUESTED_BLOCKS]
/ * Attempt (in vain, I must add... )to
allocate 512 10KB blocks in memory. * /
for (x = 0; x<REQUESTED_ BLOCKS ; x++ )
{
cpi[x]= (char * ) malloc (10000,1)
if (cp[x]= = (char * ) NULL)
break
}
/ * If x is less than REQUESTED-BLOCKS,
the loop has ended prematurely. * /
if (x<REQUESTED_BLOCKS)
printf ("Bummer ! My loop ended prematurely ! /n" );
注意,如果上述循环执行成功,它一定会循环 512 次。紧接着循环的 if 语句用来测试循环次数,从而判断循环是否提前结束。如果变量 x 的值小于 512 ,就说明循环出错了。
1 . 8 goto , longjmp() 和 setjmp() 之间有什么区别 ?
goto 语句实现程序执行中的近程跳转 (local jump) , longjmp() 和 setjmp() 函数实现程序执行中的远程跳转 (nonlocaljump ,也叫 farjump) 。通常你应该避免任何形式的执行中跳转,因为在程序中使用 goto 语句或 longjmp() 函数不是一种好的编程习惯。
goto 语句会跳过程序中的一段代码并转到一个预先指定的位置。为了使用 goto 语句,你要预先指定一个有标号的位置作为跳转位置,这个位置必须与 goto 语句在同一个函数内。在不同的函数之间是无法实现 goto 跳转的。下面是一个使用 goto 语句的例子:
void bad_programmers_function(void)
{
int x
printf("Excuse me while I count to 5000... /n") ;
x----l~
while (1)
{
printf(" %d/n", x)
if (x ==5000)
goto all_done
else
x=x+1;
}
all_done:
prinft("Whew! That wasn't so bad, was it?/n");
}
如果不使用 goto 语句,是例可以编写得更好。下面就是一个改进了实现的例子:
void better_function (void)
{
int x
printf("Excuse me while I count to 5000... /n");
for (x=1; x<=5000, x++)
printf(" %d/n", x)
printf("Whew! That wasn't so bad, was it?/n") ;
}
前面已经提到, longjmp() 和 setjmp() 函数实现程序执行中的远程跳转。当你在程序中调用 setjmp() 时,程序当前状态将被保存到一个 jmp_buf 类型的结构中。此后,你可以通过调用 longjmp() 函数恢复到调用 setjmp() 时的程序状态。与 goto 语句不同, longjmp() 和 setjmp() 函数实现的跳转不一定在同一个函数内。然而,使用这两个函数有一个很大的缺陷,当程序恢复到它原来所保存的状态时,它将失去对所有在 longjmp() 和 setjmp() 之间动态分配的内存的控制,也就是说这将浪费所有在 longjmp() 和 setjmp() 之间用 malloc() 和 calloc() 分配所得的内存,从而使程序的效率大大降低。因此,你应该尽量避免使用 longjmp() 和 setjmp() 函数,它们和 goto 语句一样,都是不良编程习惯的表现。
下面是使用 longjmp() 函数和 setjmp() 函数的一个例子:
#include <stdio.h>
#include <setjmp.h>
jmp_buf saved_state;
void main(void);
void call_ longjmp (void);
void main(void)
{
int ret_code;
printf("The current state of the program is being saved... /n");
ret_code = setjmp (saved_state)
if (ret_code ==1)
{
printf("The longjmp function has been called. /n" )
printf("The program's previous state has been restored. /n");
exit(0)
}
printf("I am about to call longjmp and/n");
printf('return to the previous program state... /n" )
call_ longjmp ( )
}
void call_longjmp (void)
{
longjmp (saved_state, 1 )
}
1 . 9 什么是左值 (lvaule)?
左值是指可以被赋值的表达式。左值位于赋值语句的左侧,与其相对的右值 (rvaule ,见 1 . 11) 则位于赋值语句的右侧。每条赋值语句都必须有一个左值和一个右值。左值必须是内存中一个可存储的变量,而不能是一个常量。下面给出了一些左值的例子:
int x;
int *p_int;
x=1;
p_int=5;
变量 x 是一个整数,它对应于内存中的一个可存储位置,因此,在语句 “x = 1” 中, x 就是一个左值。注意,在第二个赋值语句 “*p_int = 5" 中,通过 “*” 修饰符访问 p_int 所指向的内存区域;因此, p_int 是一个左值。相反,下面的几个例子就不是左值:
#define CONST_VAL 10
int x
/* example 1 * /
l=x;
/ * example 2 * /
CONST_VAL = 5;
在上述两条语句中,语句的左侧都是一个常量,其值不能改变,因为常量不表示内存中可
存储的位置。因此,这两条赋值语句中没有左值,编译程序会指出它们是错误的。
请参见:
1. 10 数组 (array) 可以是左值吗 ? .
1. 11 什么是右值 (rvaule)?
1 . 10 数组 (array) 可以是左值吗 ?
在 1 . 9 中,左值被定义为可被赋值的表达式。那么,数组是可被赋值的表达式吗 ? 不是,因为数组是由若干独立的数组元素组成的,这些元素不能作为一个整体被赋值。下述语句是非法的:
int x[5] , y[5];
x=y ;
不过,你可以通过 for 循环来遍历数组中的每个元素,并分别对它们赋值,例如:
int i ;
int x[5];
int y[5];
......
for(i=0; i<5 , i++)
x = y ;
......
此外,你可能想一次拷贝整个数组,这可以通过象 memcpy() 这样的函数来实现,例如:
memcpy(x , y , sizeof(y)) ;
与数组不同,结构 (structure) 可以作为左值。你可以把一个结构变量赋给另一个同类型的结构变量,例如:
typedef struct t_name
{
charlast_name[25] ;
char first_name[15];
char middle-init [2];
} NAME
...
NAME my_name, your_name;
...
your_name = my_name;
...
在上例中,结构变量 my_name 的全部内容被拷贝到结构变量 your_name 中,其作用和下述语句是相同的:
memcpy(your_name , my_name , sizeof(your_name);
请参见:
1 . 9 什么是左值 (lvaule)?
1 . 11 什么是右值 (rvaule)?
1 . 11 什么是右值 (rvaule)?
在 1 . 9 中,左值被定义为可被赋值的表达式,你也可以认为左值是出现在赋值语句左边的表达式。这样,右值就可以被定义为能赋值的表达式,它出现在赋值语句的右边。与左值不同,右值可以是常量或表达式:例如:
int X , y;
x = 1; /* 1 iS an rvalue, x is an lvalue */
y = (x+1); /* (x+1)is an rvalue ; y is an lvalue */
在 1 . 9 中已经介绍过,一条赋值语句必须有一个左值和一个右值,因此,下述语句无法通过编译,因为它缺少一个右值:
int x;
x=void_function_call(); /* the{unction void—function—call()
returns nothing */
如果上例中的函数返回一个整数,那么它可以被看作一个右值,因为它的返回值可以存储
到左值 x 中。
请参见:
1 . 9 什么是左值 (lvaule)?
1 . 10 数组可以是左值吗 ?
1 . 12 运算符的优先级总能保证是 “ 自左至右 ” 或 “ 自右至左 ” 的顺序吗 ?
对这个问题的简单回答是:这两种顺序都无法保证。 C 语言并不总是自左至右或自右至左求值,一般说来,它首先求函数值,其次求复杂表达式的值,最后求简单表达式的值。此外,为了进一步优化代码,目前流行的大多数 C 编译程序常常会改变表达式的求值顺序。因此,你应该用括号明确地指定运算符的优先级。例如,请看下述表达式:
a=b+c/d/function—call() * 5
上述表达式的求值顺序非常模糊,你很可能得不到所要的结果,因此,你最好明确地指定运算符的优先级:
a = b+(((c/d)/function—call())* 5)
这样,就能确保表达式被正确求值,而且编译程序不会为了优化代码而重新安排运算符的优先级了。
1 . 13 ++var 和 var++ 有什么区别 ?
“++” 运算符被称为自增运算符。如果 “++” 运算符出现在变量的前面 (++var) ,那么在表达式使用变量之前,变量的值将增加 1 。如果 “++” 运算符出现在变量之后 (var++) ,那么先对表达式求值,然后变量的值才增加 1 。对自减运算符 (--) 来说,情况完全相同。如果运算符出现在变量的前面,则相应的运算被称为前缀运算;反之,则称为后缀运算。
例如,请看一个使用后缀自增运算符的例子:
int x, y;
x=1;
y=(x++* 5) ;
上例使用了后缀自增运算符,在求得表达式的值之后, x 的值才增加 1 ,因此, y 的值为 1 乘以 5 ,等于 5 。在求得表达式的值之后, x 自增为 2 。
现在看一个使用前缀自增运算符的例子:
int x, y;
x=1;
y=(++x*5);
这个例子和前一个相同,只不过使用了前缀自增运算符,而不是后缀自增运算符,因此, x 的值先增加 1 ,变为 2 ,然后才求得表达式的值。这样, y 的值为 2 乘以 5 ,等于 10 。
1 . 14 取模运算符 (modulus operator)“ % ” 的作用是什么 ?
取模运算符 “ % ” 的作用是求两个数相除的余数。例如,请看下面这段代码:
x=15/7;
如果 x 是一个整数, x 的值将为 2 。然而,如果用取模运算符代替除法运算符 "/" ,得到的结果就不同了:
X=15 % 7;
这个表达式的结果为 15 除以 7 的余数,等于 1 。这就是说, 15 除以 7 得 2 余 1 。
取模运算符通常用来判断一个数是否被另一个数整除。例如,如果你要打印字母表中序号为 3 的倍数的字母,你可以使用下面这段代码:
int x;
for(x = 1; x<=26; x++)
if((x%3) == 0)
printf("%c"; x+64);
上例将输出字符串 "cfilorux" ,即字母表中序号为 3 的倍数的所有字母。