C 语言编程常见错误
1. 书写标识符时,忽略了大小写字母的区别。
int main( void )
{
int a = 5;
printf( "%d", A );
return 0;
}
编译器认为 a 和 A 是两个不同的变量名,而显示出错信息。C 语言规定大写字母和小写字母是不同的字符;而有些编程语言是不分大小写的。习惯上,符号常量名用大写,变量名用小写表示,以增加可读性。
2. 忽略了变量的类型,进行了不合法的运算。
int main( void )
{
float a, b;
printf( "%d", a % b );
return 0;
}
% 是求余运算符,a % b 的结果是 a 除以 b 的余数。只有 % 左右两边的操作数都是整型时,才可以进行求余运算。故而上面的程序是错误的,因为 a 和 b 是浮点型变量。
3. 将字符常量与字符串常量混淆。
char c;
c = "a";
这 里就混淆了字符常量与字符串常量,字符常量是由一对单引号括起来的单个字符,字符串常量是一对双引号括起来的字符序列。C 语言规定以 /0 作字符串常量的结束标志,它是由系统自动加上的,所以 "a" 实际上包含两个字符:'a' 和 '/0'。所以把 "a" 赋给字符变量是不行的。
4. 忽略了“=”与“==”的区别。
许多高级语言都使用“=”符号作为关系运算符的“等于”。如 BASIC 程序中可以写:
if ( a = 3 ) then …
但 C 语言中,“=”是赋值运算符,“==”才是关系运算符。如:
if (a == 3)
a = b;
a == 3 是比较 a 是否等于 3;而 a = b; 表示把 b 的值赋给 a。初学者往往会把 a == 3 错写成 a = 3,这样写是符合语法的,但是不符合我们的期望。如果错写成了 a = 3,有些编译器会给出警告。
5. 忘记加分号。
分号是 C 语句中不可缺少的一部分,语句末尾必须有分号。
a = 1
b = 2;
编译时,编译器发现“a = 1”后面没有分号,就会报错。改错时,如果在编译器所说的有错的一行中未发现错误,就需要看一下上一行是否漏掉了分号。
6. 多加分号。
{
z = x + y;
t = z / 100;
printf( "%f", t );
};
上面代码中,大括号 } 后面的分号(;)是毫无必要的!因为单独一个 ; 也是合法的 C 语言语句,所以 } 后面写了 ; 也是合法的,但是毫无必要!又如:
if ( a % 3 == 0 );
i++;
程序员的本意是如果 3 整除 a,则 i 加 1。但由于 if ( a % 3 == 0 ) 后加了分号,则 if 语句到此结束。不论 3 是否整除 a,程序都会执行 i++; 语句。再如:
for ( i = 0; i < 5; i++ ) ;
{
scanf( "%d", &x );
printf( "%d", x );
}
程序员的本意是先后输入 5 个数,每输入一个数后再将它输出。但由于for () 后加了一个分号,循环体变为空语句 ; 。空语句 ; 执行 5 次后,下面的程序块才开始执行,导致的结果是只能输入一个数并输出它。
7. 使用 scanf 函数时忘记加取址运算符“&”。
int a, b;
scanf( "%d %d", a, b );
这样写是错误的!应该改成 scanf( "%d %d", &a, &b );
8. 输入数据的方式与要求不符。
scanf( "%d %d", &a, &b );
输入时,不能用逗号作两个数据间的分隔符。例如:3, 4 。而要使用空格、回车或者 tab 键作为分隔符。例如:3 4 。如果我们想用逗号(,)来做分隔符,可以这样写:
scanf( "%d,%d", &a, &b );
此时不用逗号而用空格或其它字符是不对的。例如:3:4 。又如:
scanf("a=%d,b=%d",&a,&b);
输入应如以下形式:
a=3,b=4
9. 输出的数据类型与所用格式说明符不一致。
例如,a 定义为整型,b 定义为浮点型
int a = 3;
float b = 4.5;
printf( "%f%d/n", a, b );
编译时不给出出错信息,但运行结果将与原意不符。这种错误尤其需要注意。应该改成:
printf( "%d%f/n", a, b );
10. 输入数据时,企图规定精度。
scanf( "%7.2f", &a );
这样做是不合法的,输入数据时不能规定精度。
11. switch 语句中漏写 break 语句。
例如:根据考试成绩的等级打印出百分制数段。
switch ( grade ) {
case 'A':
printf( "85~100/n" );
case 'B':
printf( "70~84/n" );
case 'C':
printf( "60~69/n" );
case 'D':
printf( "<60/n" );
default:
printf( "error/n" );
由于漏写了 break 语句,当 grade 值为 A 时,printf 函数在执行完第一个语句后会接着执行第二、三、四、五个 printf 函数语句。正确写法应在每个分支后再加上“break;”。例如:
case 'A':
printf( "85~100/n" );
break;
12. 忽视了 while 和 do-while 语句在细节上的区别。
(1) int main( void )
{
int a = 0, i;
scanf( "%d", &i );
while ( i <= 10 ) {
a += i;
++i;
}
printf( "%d", a );
return 0;
}
(2) int main( void )
{
int a = 0, i;
scanf( "%d", &i );
do {
a += i;
++i;
} while ( i <= 10 );
printf( "%d", a );
return 0;
}
可以看到,当输入 i 的值小于或等于 10 时,二者得到的结果相同。而当 i>10 时,二者结果就不同了。因为 while 循环是先判断后执行,而 do-while 循环是先执行后判断。对于大于 10 的数 while 循环的循环体一次也不执行,而 do-while 语句则要执行一次。
13. 定义数组时误用变量。
int n = 6;
int a[n];
C99 以前,数组名后用方括号括起来必须是常量,包括常量表达式。但是在最新的 C99 标准中,上面的代码是正确无误的。如果您的编译器不支持 C99 ,那么上面的代码不能通过编译。
14. 在定义数组时,将定义的“元素个数”误认为是可使的最大下标值。
int main( void )
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printf( "%d", a[10] );
return 0;
}
C 语言规定:定义时如果用 a[10],则表示数组 a 有 10 个元素,其下标值范围为0到9。a[10]不属于上面定义的数组中。对 a[10] 进行赋值可能导致程序崩溃!
15. 在不应加地址运算符 & 的位置加了地址运算符。
char s[100];
scanf( "%99s", &s );
C 语言中,数组名代表该数组的起始地址。因此上面的代码中,s 本身就已经是一个地址了,我们不需要再对它进行取址运算。正确的写法应该是:scanf( "%99s", s );
C 语言编程常见错误 2
1. = 不等于 ==
从 Algol 派生出来的语言,如 Pascal 和 Ada,用 := 表示赋值而用 = 表示比较。而 C 语言则是用 = 表示赋值而用 == 表示比较。这是因为赋值的频率要高于比较,因此为其分配更短的符号。此外,C 可以多重赋值(如 a = b = c),并且可以将赋值嵌入到一个大的表达式或者语句中。这种便捷导致了一个潜在的问题:需要用比较的地方却写成了赋值。下面的语句看起来好像是要检查 x 是否等于 y :
if ( x = y )
foo();
而实际上是将 y 的值赋值给 x ,并检查结果是否非零。再看看下面的一个希望跳过空格、制表符和换行符的循环:
while ( c == ' ' || c = '/t' || c == '/n' )
c = getc(f);
在应该与 '/t' 进行比较的地方程序员错误地使用了 =,而不是==。这个“比较”实际上是将'/t' 赋给 c,然后判断 c 的(新的)值是否为零。因为 '/t' 不为零,所以这个“比较”一直为真,因此这是一个死循环。
一些编译器会对形如 e1 = e2 的条件给出一个警告以提醒用户。当你确实需要对一个变量进行赋值,然后再检查变量是否“非零”时,为了避免这种警告信息,应显式给出比较符。也就是将:
if ( x = y )
foo();
改写为:
if ( ( x = y ) != 0 )
foo();
2. 多字符符号
一些 C 符号,如 /、* 或 =,只有一个字符。还有些 C 符号,如 /* 、 == 或标识符,具有多个字符。当编译器遇到紧连在一起的 / 和 * 时,它必须决定是将这两个字符识别为两个符号还是一个单独的符号。C 语言标准规定:“如果一个字符被识别为符号,则应该包含下一个字符看看包含此字符后构成的字符串是否仍然可以构成符号,如果可以则继续包含下一个字符,一直到不能构成符号为止。”。因此,如果 / 是符号的第一个字符,并且 / 后面紧随着一个 *,则这两个字符构成注释符开始标记。下面的语句看起来像是将 y 的值设置为 x 的值除以 p 所指向的值:
y = x/*p /* p 指向除数 */;
实际上,因为 /* 是注释符开始标记,因此编译器会简单地“吞噬”程序文本,直到 */ 出现为止。换句话说,这条语句仅仅把 y 的值设置为 x 的值,而根本没有看到 p。我们应该将这条语句改为:
y = x / *p /* p 指向除数 */;
或者:
y = x / (*p) /* p指向除数 */;
3. else 问题
考虑下面的程序片断:
if ( x == 0 )
if ( y == 0 )
error();
else {
z = x + y;
f(&z);
}
写这段程序的程序员的目的明显是想将情况分为两种:x == 0 和x != 0。在第一种情况中,如果 y == 0,则调用 error()。第二种情况中,程序执行 z = x + y; 和 f(&z); 。
然而, 这段程序的实际效果却大为不同。其原因是 else 总是与离它最近的 if 相关联。上面那段代码其实等价于:
if ( x == 0 ) {
if ( y == 0 )
error();
else {
z = x + y;
f(&z);
}
}
也就是说,当 x != 0 发生时什么也不做。如果要达到我们想要的效果,应该改成:
if ( x == 0 ) {
if ( y == 0 )
error();
} else {
z = z + y;
f(&z);
}
4. 表达式求值顺序
一些运算符以一种已知的、特定的顺序对其操作数进行求值。但另一些则不是。例如下面的表达式:
a < b && c < d
C 标准规定 a < b 首先被求值。如果 a 确实小于 b,c < d 必须紧接着被求值以计算整个表达式的真假性。但如果 a 大于或等于 b,则 c < d 根本不会被求值。而对 a < b 求值时,到底是先取 a 的值,还是先取 b 的值,标准并没有定义。
C 中只有四个运算符(&&、||、?: 和 ,)指定了求值顺序。&& 和 || 最先对左边的操作数进行求值,而右边的操作数只有在需要的时候才进行求值。而 ?: 运算符中的三个操作数中,先对最左边的进行求值,然后根据它的值决定到底应该求中间的操作数的值,还是求最右边的操作数的值。逗号运算符(,)的求值顺序 为从左到右。
C 中所有其它运算符的操作数的求值顺序都是未定义的。特别要说的是,赋值运算符也没有对求值顺序做出任何保证。
出于这个原因,下面这种将数组 x 中的前 n 个元素复制到数组 y 中的方法是不可行的:
j = 0;
while ( j < n )
y[j] = x[j++];
因为标准没有保证 y[j] 在 j 增长之前被求值。到底 y[j] 先求值,还是 x[j++] 先求值是依赖编译器的!所以我们不应该这么写!另一种方案基于同样的原因也不可行:
j = 0;
while ( j < n )
y[j++] = x[j];
下面的代码才是正确的:
j = 0;
while ( j < n ) {
y[j] = x[j];
j++;
}
当然,也可以这么写:
for ( j = 0; j < n; j++ )
y[j] = x[j];
5. &&、|| 和 ! 运算符
C 规定 0 代表“假”,非零代表“真”。这些运算符返回 1 表示“真”而返回 0 表示“假”。&& 和 || 运算符如果可以通过左边的操作数确定整个表达式的真假性,就不会对右边的操作数进行求值。!10 返回 0,因为 10 非零;10 && 12 返回 1,因为 10 和 12 的值都不是 0;10 || 12 也是 1,因为 10 非零。这个表达式中的 12 不会被求值,因为左边的 10 就足够确定整个表达式为真。同理 :10 || f() 中的 f() 也不会被求值。
6. 下标从零开始
C 语言中,一个具有 n 个元素的数组中没有下标为 n 的元素,元素的下标是从 0 到n-1。下面的程序可能会崩溃:
int i, a[10];
for ( i = 1; i <= 10; i++ )
a = 0;
应该改成:
int i, a[10];
for ( i = 0; i < 10; i++ )
a = 0;
7. getchar 函数的返回值为整型(int)
请看以下程序:
#include <stdio.h>
int main( void )
{
char c;
while ( ( c = getchar() ) != EOF )
putchar(c);
return 0;
}
这段代码存在一个小小的,但已经足以致命的错误:c 被声明为字符型(char)而不是整型。这意味着 c 可能不能正确接收 EOF,从而导致程序不能退出!正确的写法是:将 char 改成int。