C/C++/OOP常见BUG

 

C语言是一种面向过程的计算机程序设计语言,它是目前众多计算机语言中举世公认的优秀的结构化程序设计语言之一。C++是在C语言的基础上发展而来的,它实现了由面向过程到面向对象的转变,是一种全面支持面向对象的程序设计方法。

目前C/C++程序设计方面的教程很多,但大多数是从语法、编程技巧、算法等角度进行组织和编写。在实际的软件系统开发过程中,许多刚涉足编程工作的程序员编写的代码往往质量不高,程序中往往隐藏着一些问题和错误,因程序员缺乏编程和调试经验而难以发现,给程序设计语言的学习和软件系统的开发造成了很大障碍。本文借鉴国内外的相关书籍、学术论文、网站论坛等文献资料,结合软件开发中经常遇到的实际问题和笔者长期从事软件系统开发的经验,从用户使用的角度出发,对C/C++编程中容易产生错误的知识点进行解释,对程序中常见的错误进行解析,以帮助他们尽快掌握C/C++编程技术,避免程序中的错误,提高代码质量,尽快成长为经验丰富的程序员。

本文内容按照CC++和面向对象程序设计的顺序进行组织,共包括三部分,分别为:

u  第一部分 C语言编程常见BUG

本部分主要包括初学者常见问题、基本数据类型、存储类、运算符、流程控制、函数、C语言预处理程序、指针和数组、结构和联合、输入和输出、文件操作等内容。

u  第二部分 C++编程常见BUG

本部分主要包括命名空间、C++语言的输入输出、动态内存的分配与释放、引用、const修饰符、字符串、C++语言中函数的新特性等内容。

u  第三部分 面向对象程序设计编程常见BUG

本部分主要包括类与对象、友元、继承机制、多态和虚函数等内容。

本文针对C/C++编程中的常见错误,列举大量实例,并进行解析,以提高实用性,使读者容易理解,快速掌握。每个例子都给出了题目要求、错误代码、编译结果、问题分析、正确代码及其运行结果。其中在编译结果中给出了编译器提供的错误和警告信息。读者可以根据这些信息判断问题所在。每章后都有练习题,以帮助读者进一步巩固知识,增强效果。本文所附光盘中包括书中所有例题的源代码,课后练习的源代码及其答案的源代码。本文采用Visual C++ 6.0作为编程和调试环境。

本文引用了大量书籍和文献资料,在此,向被引用文献的作者表示衷心的感谢,向为本文的编写和出版工作给予帮助的所有人士表示诚挚的敬意!

 


 

   

第一部分   C语言编程常见BUG.. 1

1 初学者常见问题... 2

问题1            字母大小写混淆... 2

问题2            容易混淆的字符... 3

问题3            在代码中使用了中文字符... 4

问题4            丢失或多余的分号... 6

问题5            丢失或多余的大括号... 7

问题6            混乱的缩进和对齐... 10

练习1    11

2 基本数据类型... 13

问题7            整型常量的第一个字符为0起的误解... 16

问题8            将字符常量与字符串常量混淆... 17

问题9            混淆‘/’‘\’ 17

问题10          缺少强制类型转换... 19

问题11          类型转换降低精度... 20

练习2    21

3 存储类    22

问题12          在使用变量前未定义,或未初始化... 24

问题13          错误理解静态变量的作用域... 24

问题14          全局变量的错误声明... 27

问题15          滥用全局变量... 28

练习3    28

4 运算符    30

问题16          分母为0. 32

问题17          对实型变量进行求余运算... 33

问题18          =”与“==混淆... 33

问题19          “&”与“&&”“|”与“||” 混淆... 34

问题20          ++、“--的前置和后置方式... 35

问题21          运算符优先级错误... 37

练习4    37

5 流程控制... 39

问题22          逻辑表达式或关系表达式错误... 41

问题23          if-else嵌套不配对... 43

问题24          分号导致的流程变化... 45

问题25          switch语句:case后面缺少break; 47

问题26          忽视了whiledo-while语句在细节上的区别... 49

问题27          滥用goto语句... 50

练习5    52

6 函数        54

问题28          使用库函数前没有包含头文件... 54

问题29          使用函数之前未声明... 55

问题30          函数实参格式不对... 56

问题31          错误的返回值... 57

问题32          递归时设置了错误的边界条件... 58

练习6    59

7 C语言预处理程序... 61

问题33          宏定义格式错误... 62

问题34          带参数的宏替换错误... 63

问题35          把宏调用混同于函数调用... 64

问题36          宏定义中多余的空格... 65

练习7    66

8 指针和数组... 67

问题37          数组越界... 69

问题38          定义数组时误用变量... 70

问题39          动态分配内存空间不够... 72

问题40          内存泄漏... 73

问题41          访问悬空指针... 75

练习8    76

9 结构和联合... 78

问题42          结构的嵌套... 79

问题43          结构与联合混淆... 80

练习9    82

10 输入和输出... 84

问题44          printfscanf的参数设置... 85

问题45          错误的文件打开和关闭方式... 87

练习10    88

第二部分   C++编程常见BUG.. 90

11 命名空间... 91

问题46          C++标准程序库的命名空间... 91

问题47          名字冲突... 92

练习11    94

12  C++语言的输入输出... 96

问题48          “<<”、“>>”混淆... 96

问题49          cout运算符优先级错误... 97

练习12    98

13 动态内存的分配与释放... 99

问题50          new/deletemalloc/free混用... 99

问题51          没有释放动态分配的内存空间... 100

问题52          一个指针被delete时,没有指向最初的地址... 102

问题53          重复释放已释放的空间... 104

问题54          重复delete同一指向的多个指针... 105

问题55          delete指向某一普通变量的指针... 106

练习13    107

14 引用    110

问题56          引用的声明和初始化错误... 110

问题57          引用作为参数的错误用法... 111

问题58          引用作为返回值的错误用法... 113

练习14    114

15 const修饰符... 117

问题59          const定义常量的错误... 117

问题60          const参数错误... 118

问题61          const成员函数错误... 120

练习15    121

16 字符串... 123

问题62          使用string类型未引用名字空间... 123

17 C++语言中函数的新特性... 125

问题63          内联函数定义错误... 126

问题64          函数的缺省参数定义错误... 126

问题65          函数重载错误... 127

问题66          函数的缺省参数造成的二义性... 129

练习17    130

第三部分  面向对象程序设计编程常见BUG.. 132

18 类与对象... 133

问题67          类的定义格式错误... 135

问题68          对象和类的访问错误... 136

问题69          在类体中进行数据成员的初始化... 137

问题70          成员函数实现时缺少作用域运算符... 139

问题71          缺少引用性说明... 141

问题72          构造函数/析构函数不应该有返回值... 143

问题73          多余的析构函数... 144

问题74          构造函数缺省参数导致的二义... 145

问题75          创建对象时缺少相应的构造函数... 146

问题76          默认的拷贝构造函数导致的内存问题... 148

问题77          静态数据成员初始化错误... 150

问题78          静态成员函数引用非静态数据成员... 151

问题79          局部类定义错误... 152

问题80          嵌套类定义错误... 154

练习18    158

19 友元    163

问题81          把友元函数当作类的成员... 163

问题82          在类外定义友元函数时多余的friend关键字... 165

问题83          友元关系不具有交换性... 166

问题84          友元关系不具有传递性... 168

练习19    170

20 继承机制... 173

问题85          基类成员在派生类中的访问权限... 174

问题86          派生类中由基类说明的数据成员应由基类的构造函数初始化... 177

问题87          多继承的二义性问题... 179

问题88          截切问题... 182

练习20    184

21 多态和虚函数... 188

问题89          运算符重载时不可臆造新的运算符... 190

问题90          运算符重载时不能改变运算符操作数的个数... 191

问题91          自增、自减运算符的重载:前缀运算和后缀运算的混淆... 193

问题92          重载了不允许重载的运算符... 194

问题93          运算符重载为友元函数时的参数个数... 195

问题94          不能重载为友元函数的运算符... 197

问题95          左操作数是常数时的运算符重载... 199

问题96          虚函数不能是静态成员函数... 201

问题97          虚函数必须是类的成员函数,不能是友元... 203

问题98          构造函数不能是虚函数... 204

问题99          将基类析构函数声明为非虚函数... 205

问题100        在构造函数和析构函数里调用虚函数... 206

问题101        重载虚函数导致的问题... 208

问题102        在构造函数和析构函数中调用纯虚函数... 210

问题103        实例化抽象类... 211

问题104        派生类必须实现所有纯虚函数才是具体类,否则仍是抽象类... 213

练习21    215

练习答案        221



 

第一部分 
C
语言编程常见BUG

 

 

本部分主要包括初学者常见问题、基本数据类型、存储类、运算符、流程控制、函数、C语言预处理程序、指针和数组、结构和联合、输入和输出、文件操作等内容。


1 初学者常见问题

初学编程的人往往会犯一些简单的错误。这些错误看上去不值一提,却是每个程序员成功之路上的拦路虎。这些常见的错误有:字母大小写混淆、容易混淆的字符(如逗号与分号;单引号与双引号;数字‘1’、大写字母‘I’与小写字母‘l’ 数字‘0’、大写字母‘O’与小写字母‘o’)、在代码中使用了中文字符、丢失或多余的分号、丢失或多余的大括号、混乱的缩进和对齐等。出现这些错误的根本原因在于编程和调试太少,对程序设计语言的基本语法掌握不够。多编写程序,多调试程序,自己改正程序中的错误,熟练之后就会避免再犯这些简单的错误。

问题1       字母大小写混淆

[1.1] 已知商品单价和数量,求总价。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     int count = 5;        //数量

5

     float price = 25;      //单价

6

     float total = price * Count;   //计算总价

7

     Printf("总价为%.2f\n",total);

8

}

 

[编译结果]

e:\code\1_1.cpp(6) : error C2065: 'Count' : undeclared identifier

e:\code\1_1.cpp(7) : error C2065: 'Printf' : undeclared identifier

 

[问题分析]

     上面的编译结果给出了错误信息。双击某一错误信息行,该错误信息行会加亮显示,并在程序出现错误的行前面用一个箭头加以指示。第12条错误信息分别指出‘Count’、‘Printf’未定义。仔细对照程序,发现程序中如下语句定义了变量‘count’:

     int count = 5;

首字母是小写;而

     float total = price * Count;

     Printf("总价为%.2f\n",total);

这几两句代码中用到的‘Count’和‘Printf’的首字母写成了大写。C语言中是严格区分大小写的,所以编译器认为‘Count’未定义。

     ‘printf’是产生格式化输出的函数(定义在 stdio.h )。程序中把‘printf’误写成了‘Printf’,由于相同的原因,编译器也认为‘Printf 未定义。其他关键字如‘if’、‘for’等如果拼写错误或大小写错误也会报同样的错误信息。

 

[正确代码]

代码修改如下:

6

     float total = price * count;

7

     printf("总价为%.2f\n",total);

 

C语言中是严格区分大小写的,只要定义变量的地方和使用这个变量的地方写法一致,就不会出现上面的问题。

 

[运行结果]

总价为125.00

 

[小提示]

如果程序编译不通过,编译器会给出错误信息。双击某一错误信息行,该错误信息行会加亮显示,并在程序出现错误的行前面用一个箭头加以指示。有时候,程序中的一个错误会导致多行错误信息,因此,常常修改一条错误后,再重新编译,直到没有错误为止。

问题2       容易混淆的字符

有一些字符的外形看起来很相似,初学C语言编程的人很容易混淆。如逗号与分号;单引号与双引号;数字‘1’、大写字母‘I’与小写字母‘l’;数字‘0’、大写字母‘O’与小写字母‘o’等等。

 

[1.2] 已知学生数学、语文、英语三门课的成绩,求总分和平均分。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     float math = 79;

5

     f1oat chinese = 80;

6

     float english = 8l;    

7

 

8

     float total = math+chinese+engIish;    //求总分

9

     fIoat average = total/3,               //求平均分

10

     printf('总分为%.2f,平均分为%.2f\n",total,average);

11

}

 

[编译结果]

E:\Code\1_2.cpp(5) : error C2065: 'f1oat' : undeclared identifier

E:\Code\1_2.cpp(5) : error C2146: syntax error : missing ';' before identifier 'chinese'

E:\Code\1_2.cpp(5) : error C2065: 'chinese' : undeclared identifier

E:\Code\1_2.cpp(8) : error C2065: 'engIish' : undeclared identifier

E:\Code\1_2.cpp(9) : error C2065: 'fIoat' : undeclared identifier

E:\Code\1_2.cpp(9) : error C2146: syntax error : missing ';' before identifier 'average'

E:\Code\1_2.cpp(9) : error C2065: 'average' : undeclared identifier

E:\Code\1_2.cpp(9) : warning C4244: '=' : conversion from 'float' to 'int', possible loss of data

E:\Code\1_2.cpp(10) : error C2001: newline in constant

E:\Code\1_2.cpp(10) : error C2015: too many characters in constant

E:\Code\1_2.cpp(11) : error C2143: syntax error : missing ')' before '}'

E:\Code\1_2.cpp(11) : error C2143: syntax error : missing ';' before '}'

 

[问题分析]

     让我们逐行分析这些错误提示。

     error C2065: 'f1oat' : undeclared identifier”:f1oat chinese = 80这一句里的“f1oat”中字母‘l’被写成了数字‘1

     syntax error : missing ';' before identifier 'chinese'”:同上

     error C2065: 'chinese' : undeclared identifier”:同上

“error C2065: 'engIish' : undeclared identifier”: 'engIish'中字母‘l’被写成了大写字母‘I’。

“error C2065: 'fIoat' : undeclared identifier”: fIoat average = total/3这一句里的“fIoat”中字母‘l’被写成了大写字母‘I’。

“error C2001: newline in constant”: fIoat average = total/3, 这一句最后的分号被写成逗号。

最后3条错误提示是由于printf('总分为%.2f,平均分为%.2f\n",total,average) 这一句里的“printf(”后面的双引号被写成了单引号所致。

以上错误都被更正之后,编译通过,运行结果如下:

总分为167.00,平均分为55.67

     奇怪,数学、语文、英语三门课的成绩分别为798081,总分应该为240.00,平均分应该为80.00,运行结果和预期不一致。到底哪里出错呢?原来是float english = 8l这一句里的81被写成了数字‘8和小写字母‘l’,而8L8l都表示值为8的长整型数,所以总分算出来为79+80+8=167

 

[正确代码]

代码修改如下:

5

     float chinese = 80;

6

     float english = 81;    

7

 

8

     float total = math+chinese+english;

9

     float average = total/3;

10

     printf("总分为%.2f,平均分为%.2f\n",total,average);

 

[运行结果]

正确的运行结果如下:

总分为240.00,平均分为80.00

 

问题3       在代码中使用了中文字符

在录入程序时,如果忘了把输入法切换到英文状态,就会不慎录入中文字符,如逗号、引号和分号,因为中文输入法与英文输入法下键入符号的编码不同,从而导致一系列编译错误。

 

[1.3] 已知矩形的长和宽,求该矩形的面积。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     int length = 5int width = 6;

5

     int area = length * width

6

     printf(“面积为%d\n",area);

7

}

 

[编译结果]

e:\code\1_3.cpp(4) : error C2018: unknown character '0xa3'

e:\code\1_3.cpp(4) : error C2018: unknown character '0xac'

e:\code\1_3.cpp(4) : error C2144: syntax error : missing ';' before type 'int'

e:\code\1_3.cpp(5) : error C2018: unknown character '0xa3'

e:\code\1_3.cpp(5) : error C2018: unknown character '0xbb'

e:\code\1_3.cpp(6) : error C2146: syntax error : missing ';' before identifier 'printf'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xa1'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xb0'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xc3'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xe6'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xbb'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xfd'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xce'

e:\code\1_3.cpp(6) : error C2018: unknown character '0xaa'

e:\code\1_3.cpp(6) : error C2143: syntax error : missing ')' before '%'

e:\code\1_3.cpp(6) : error C2660: 'printf' : function does not take 0 parameters

e:\code\1_3.cpp(6) : error C2017: illegal escape sequence

e:\code\1_3.cpp(6) : error C2065: 'd' : undeclared identifier

e:\code\1_3.cpp(6) : error C2001: newline in constant

e:\code\1_3.cpp(6) : error C2146: syntax error : missing ';' before identifier 'n'

e:\code\1_3.cpp(6) : error C2065: 'n' : undeclared identifier

e:\code\1_3.cpp(6) : error C2143: syntax error : missing ';' before 'string'

e:\code\1_3.cpp(7) : error C2143: syntax error : missing ';' before '}'

 

[问题分析]

     上面的编译结果给出了错误提示。令人吃惊的是短短的几行代码居然报了23个错误。不必担心,真正的错误只有3处:int length = 5之后的中文逗号;int area = length * width之后的中文分号;“printf(”与“面积”之间的中文引号。因为一个中文字符的内码占用两个字节,所以会报告类似的错误提示:

e:\code\1_3.cpp(4) : error C2018: unknown character '0xa3'

e:\code\1_3.cpp(4) : error C2018: unknown character '0xac'

凡是有类似错误提示的都是因为代码里用了中文符号。

[正确代码]

只要把中文符号改为对应的英文符号即可。

4

     int length = 5,  int width = 6;

5

     int area = length * width;

6

     printf("面积为%d\n",area);

 

[运行结果]

面积为30

 

问题4       丢失或多余的分号

     初学者还经常丢掉分号,或写了多余的分号。这将导致一系列编译错误。

[1.4] 计算从11010个整数之和。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     int sum = 0;

5

     for(int i=1;i<=10;i++);

6

          sum += i;

7

     printf("sum=%d\n",sum)

8

}

 

[编译结果]

E:\Code\1_4.cpp(8) : error C2143: syntax error : missing ';' before '}'

 

[问题分析]

     上面的编译结果给出了错误提示。“missing ';' before '}'”告诉我们printf("sum=%d\n",sum)后面少写了一个分号。在此处添加分号,则编译正常,运行结果为:

sum=11

11010个整数之和应改为55,而不是11。错误原因是for(int i=1;i<=10;i++);这一行最后多了一个分号,导致sum += i并不是在循环体里面循环执行,而是等循环结束之后才执行。因而实际结果为11

 

[正确代码]

代码修改如下:

5

     for(int i=1;i<=10;i++) //此处删除分号‘;

 

7

     printf("sum=%d\n",sum);     //添加分号‘;

 

[运行结果]

sum=55

问题5       丢失或多余的大括号

当程序代码中包括forifwhileswitch等语句时,程序中就会出现较多的‘{’和‘}’。它们是成对出现的。如果丢失了‘{’或‘}’,会导致程序出错。而且编译程序给出的错误信息往往不一定准确指明错误所在的位置,导致调试时遇到困难。

 

[1.5] 依次读入10个学生的成绩,判断成绩等级(优、良、中、及格、不及格),统计各等级的人数,并输出。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     int excellent=0, good=0, middle=0, pass=0, fail=0;

5

      for(int i=0;i<10;i++)

6

     {

7

          float score;

8

          scanf("%f",&score);  //输入分数

9

          if(score<=100 && score>=90)  //

10

          {

11

               excellent++;

12

               printf("\n");

13

          }

14

          else if(score<90 && score>=80)  //

15

               good++;

16

               printf("\n");

17

          }

18

          else if(score<80 && score>=70)  //

19

          {

20

               middle++;

21

               printf("\n");

22

          }

23

          else if(score<70 && score>=60)  //及格

24

          {

25

               pass++;

26

               printf("及格\n");

27

          }

28

          else if(score<60 && score>=0)  //不及格

29

          {

30

               fail++;

31

               printf("不及格\n");

32

          }

33

     printf("统计结果:\n优:%d\n", excellent);

34

     printf("良:%d\n", good);

35

     printf("中:%d\n", middle);

36

     printf("及格:%d\n", pass);

37

     printf("不及格:%d\n", fail);

38

}

 

[编译结果]

E:\Code\1_5.cpp(18) : error C2181: illegal else without matching if

E:\Code\1_5.cpp(18) : error C2065: 'score' : undeclared identifier

 

[问题分析]

1条错误信息是“legal else without matching if”,意思是else没有与它匹配的if。双击这条错误信息,编辑区焦点自动移到下面这一句代码:

else if(score<80 && score>=70)

但是前面明明有else if(score<90 && score>=80),为什么还报这个错呢?原来,由于else if(score<90 && score>=80)后面丢失了一个‘{’, 编译器认为printf("\n")这一句后面的‘}’不是与else if匹配的,而是与for匹配的,导致错误。改正这一句后再编译,仍有错误,错误信息为:

E:\Code\1_5.cpp(40) : fatal error C1004: unexpected end of file found

Error executing cl.exe.

双击这条错误信息行,该错误信息行会加亮显示,并在程序最后一行前面用一个箭头加以指示。这更加令人感到困惑,到底为什么呢?原来,因为printf("优:%d\n", excellent)前面丢失了一个‘}’, 编译器认为代码最后一行的‘}’是与for匹配的,那么main函数的结束‘}’就没有了,所以编译器会报告这样的错误信息。

 

[正确代码]

1

#include <stdio.h>

2

void main( )

3

{

4

     int excellent=0, good=0, middle=0, pass=0, fail=0;

5

      for(int i=0;i<10;i++)

6

     {

7

          float score;

8

          scanf("%f",&score);

9

          if(score<=100 && score>=90)  //

10

          {

11

               excellent++;

12

               printf("\n");

13

          }

14

          else if(score<90 && score>=80)  //

15

          { 

16

               good++;

17

               printf("\n");

18

          }

19

          else if(score<80 && score>=70)  //

20

          {

21

               middle++;

22

               printf("\n");

23

          }

24

          else if(score<70 && score>=60)  //及格

25

          {

26

               pass++;

27

               printf("及格\n");

28

          }

29

          else if(score<60 && score>=0)  //不及格

30

          {

31

               fail++;

32

               printf("不及格\n");

33

          }

34

     }

35

     printf("统计结果:\n优:%d\n", excellent);

36

     printf("良:%d\n", good);

37

     printf("中:%d\n", middle);

38

     printf("及格:%d\n", pass);

39

     printf("不及格:%d\n", fail);

40

}

 

[运行结果]

100

90

80

76

56

不及格

34

不及格

67

及格

78

89

91

统计结果:

优:3

良:2

中:2

及格:1

不及格:2

问题6       混乱的缩进和对齐

     一个程序员需要有良好的编程风格,例如代码行的缩进和对齐。初学者往往忽视这一点,或者认为这无关紧要。实际上混乱的缩进和对齐不仅影响代码的美观,还使代码变得难以理解,降低了工作效率,影响了团队合作。它还降低了代码的质量,增加了代码出错的概率,使程序员不容易找出代码中的错误。

 

[1.6] 要求在屏幕上输出如下图形:

#

**

###

****

#####

******

#######

********

#########

 

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      for(int i=1;i<10;i++)

4

{

5

 for(int j=0;j<i;j++)

6

if((i/2)*2==i)

7

     printf("*"); else

8

printf("#");

9

}

10

printf("\n");

11

}

12

}

 

[编译结果]

E:\Code\1_6.cpp(12) : error C2143: syntax error : missing ';' before '}'

E:\Code\1_6.cpp(12) : error C2143: syntax error : missing ';' before '}'

E:\Code\1_6.cpp(12) : error C2143: syntax error : missing ';' before '}'

 

[问题分析]

     这段代码混乱不堪,代码没有合理地缩进,成对的大括号也没有对齐。事实上很难看懂。编译产生的错误提示也没有准确指出错误所在。实际上如果使代码合理地缩进和对齐,就可以很容易看出,for(int j=0;j<i;j++)之后缺少了一个‘}’。

 

[正确代码]

     把混乱的代码进行整理,在for(int j=0;j<i;j++)之后加了一个‘}’。

1

#include <stdio.h>

2

void main( )

3

{      

4

     for(int i=1;i<10;i++)

5

     {

6

           for(int j=0;j<i;j++)

7

           {  

8

               if((i/2)*2==i)

9

                    printf("*");

10

               else

11

                    printf("#");

12

           }

13

           printf("\n");

14

     }

15

}

 

[运行结果]

同题目要求。

 

[小提示]

如何快速地规范代码缩进格式呢?必须一行一行地改吗?其实,只要选中需要规范的代码,按Alt+F8即可。

 

练习1

请改正如下程序中的错误。

1) #号输出字母E的图案。

 

1

#include <stdio.h>

2

void main( )

3

{

4

     Printf(" ####/n");

5

     printf(" #\n");

6

     printf(" ####\n");

7

     prlntf(" # \n");

8

     printf(" ####\n");

9

}

 

2) 一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

 

1

#include <stdio.h>

2

#include "math.h"

3

void main( )

4

{

5

     long int i,x,y;

6

     for(i=1;i<100000;i++);

7

     {

8

          x=sqrt(i+100);

9

          y=sqrt(i+168);

10

          if(x*x==i+100 && y*y==i+168);

11

               printf("%ld\n",i);

12

     }

13

}

 

3) 1234四个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?

 

1

#include <stdio.h>

2

void main( )

3

{

4

     int i,j,k;

5

     printf("\n");

6

     for(i=1;i<5;i++)

7

          for(j=1;j<5;j++)

8

               for (k=1;k<5;k++)

9

               {

10

                //确保ijk三位互不相同

11

                    if (i!=k && i!=j && j!=k)

12

                         printf("%d,%d,%d\n",i,j,k);

13

               }

14

          }

15

     }

16

}

 


2 基本数据类型

在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型三大类。如下:

 

基本数据类型最主要的特点是,其值不可以再分解为其它类型,包括整型、浮点型、字符型、空类型、枚举类型等。其中整型又分为短整型(short)、整型(int)、长整型(long),浮点型又分为单精度浮点型(float)和双精度浮点型(double)。

构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的,一个构造类型的值可以分解成若干个“成员”,每个“成员”都是一个基本数据类型或者一个构造类型。在C语言中,构造类型有数组类型、结构类型(struct)、联合类型(union)等几种。

指针类型用来表示某个量在内存中的地址。在计算机系统中每一个数据均需要占用一定的内存空间,而每段空间均有唯一的地址与之对应,因此在计算机系统中任意数据均有确定的地址与之对应。C语言中,为了描述数据存放的地址信息,引入指针类型。虽然指针的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。

C语言中,数据的基本表现形式是常量和变量。在程序执行过程中,其值不发生改变的量称为常量,它可以不经说明而直接引用。常量可分为整型常量、浮点常量、字符常量、枚举常量等。有时候,可以用一个符号名来代表一个常量,称为符号常量。例如在程序中经常使用圆周率3.14,可以用符号PI来表示,这样在每个用到它的地方只需用PI来代替3.14。如果要把3.14改为3.14159265,只需修改一个地方,而不是每个用到圆周率的地方。

在程序执行过程中,取值可变的量称为变量。在程序中,变量则必须先说明后使用。变量可以分为整型变量、浮点变量、字符变量、枚举变量等。使用变量时,需要注意区别变量名和变量值。变量代表内存中具有特定属性的一个和几个存储单元,它用来存储数据,也就是存储变量的量。变量应该有一个变量名,以便于被引用。变量名必须符合C语言对标识符的规定。

1. 整型数据

1)整型常量

整型常量即常整数。在C语言中,整型常量通常以十进制整数、八进制整数、十六进制整数来表示。在程序中根据前缀来区分各种不同进制的数。

十进制整数没有前缀,其数码为0925-90655351398都是合法的十进制整数,而029(不能有前导0)、23D(含有非十进制数码)则不是合法的十进制整数。

八进制整数必须以0开头,即以0作为八进制数的前缀。数码取值为07。八进制数通常是无符号数。015(十进制为13)0101(十进制为65)0177777(十进制为65535) 都是合法的八进制数。而26(无前缀0)、03A(包含了非八进制数码)、-017(出现了负号)则不是合法的八进制数。

十六进制整数的前缀为0X0x。其数码取值为0~9A~Fa~f0X2B(十进制为43)、0XA1(十进制为161)、0XFFFF(十进制为65535)是合法的十六进制整常数,而5A(无前缀0X)、0X3H(含有非十六进制数码)则不是合法的十六进制整常数。

在程序中,以上整数的三种表示形式都是合法、有效的。例如,160200x10都表示十进制数16

 

2)整型变量

C语言中有三种整型变量:整型(int),短整型(short intshort),长整型(long intlong)。ANSI C标准没有具体规定以上各类数据所占内存的字节数,只要求long型数据长度不短于int型,int型数据长度不短于short型。具体由各计算机系统自行决定。在Turbo C中,int型和short型数据都是2个字节,即16位二进制。long型数据是4个字节,即32位二进制。而Visual C++中,short型数据是2个字节,即16位二进制。而int型和long型数据是4个字节,即32位二进制。

一般情况下,整型变量是有符号的,即存储单元的最高位用来表示符号(0为正,1为负)。例如,一个short型数据被分配2个字节,即16位二进制,最高位存储符号,用来存储数据的是15位,存放的数据范围是-32768 ~ 32767。在实际应用中,变量的符号通常是正的,如分数、价格、年龄等。为充分利用变量的数值范围,可以将变量定义为“无符号”类型。只需要在shortintlong之前加上unsigned修饰符即可,如unsigned shortunsigned intunsigned long. 这样,对于unsigned short来说,用来存储数据的是16位,存放的数据范围是0 ~ 65535,使可以存放正数的范围扩大了一倍。修饰符signed表示有符号数,实际上可以省略。归纳起来,在C语言中可以定义六种整型变量,即:

Ÿ   有符号整型 [signed] int

Ÿ   无符号整型 unsigned int

Ÿ   有符号短整型 [signed] short [int]

Ÿ   无符号短整型 unsigned short [int]

Ÿ   有符号长整型 [signed] long [int]

Ÿ   无符号长整型 unsigned long [int]

 

2.1整数类型的数据取值范围

类型

Turbo C

Visual C++

字节数

数值范围

字节数

数值范围

[signed] int

2

-32768 ~ 32767

4

-2147483648~2147483647

unsigned int

2

0 ~ 65535

4

0 ~ 4294967295

[signed] short [int]

2

-32768 ~ 32767

2

-32768 ~ 32767

unsigned short [int]

2

0 ~ 65535

2

0 ~ 65535

[signed] long [int]

4

-2147483648~2147483647

4

-2147483648~2147483647

unsigned long [int]

4

0 ~ 4294967295

4

0 ~ 4294967295

 

变量说明的一般形式为: 类型说明符 变量名标识符,变量名标识符,...; 例如:

int a,b,c; (a,b,c为整型变量)

long m,n; (m,n为长整型变量)

unsigned int p,q; (p,q为无符号整型变量)

在说明变量时,应注意:

Ÿ   允许在一个类型说明符后,说明多个相同类型的变量。各变量名之间用逗号间隔。类型说明符与变量名之间至少用一个空格间隔。

Ÿ   最后一个变量名之后必须以“;”号结尾。

Ÿ   变量说明必须放在变量使用之前。

 

2. 浮点型数据

1)浮点型常量

在C语言中,浮点数就是实数。实数有二种形式:

Ÿ   十进制小数形式:由数码0~ 9和小数点组成。如3.20.35-321.897

Ÿ   指数形式:类似2.5×104的数称为指数形式。计算机中用字母eE代表以10为底的指数,如2.5E4代表2.5×104。一个浮点数在以指数形式输出时,通常采用“规范化的指数形式”,即在字母eE之前的小数部分中,小数点左边有且仅有一位非零的数字。

2)浮点型变量

在C语言中,浮点型变量分为两类:

Ÿ   单精度型:类型说明符为float,在Turbo C中占4个字节(32位)内存空间,其数值范围为3.4E-383.4E+38,可提供七位有效数字。例如: float x,y;

Ÿ   双精度型:类型说明符为double,在Turbo C中占8 个字节(64位)内存空间,其数值范围为1.7E-3081.7E+308,可提供16位有效数字。例如:double a,b,c;

 

3. 字符型数据

1)字符常量

     字符常量是用单引号括起来的一个字符。例如'a''b''$''?'都是合法的字符常量。

另外,C语言中允许使用转义字符。转义字符是一种特殊的字符常量,以反斜杠"\"开头,后跟一个或几个字符。“转义字符”的意思是把反斜杠"\"后面的字符转换成另外的含意。常用的转义字符见表2.2

2.2常用的转义字符及其作用

字符形式

含义

ASCII代码

\n

换行,把当前位置移到下一行开头

10

\t

水平制表(跳到下一制表位置)

9

\b

退格

8

\r

回车

13

\f

换页

12

\a

鸣铃

7

\\

反斜杠"\"

92

\'

单引号

39

\"

双引号

34

\ddd

13位八进制数所代表的字符

 

\xhh

12位十六进制数所代表的字符

 

 

广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。上表中的dddhh分别为八进制和十六进制的ASCII代码。

 

2)字符变量

字符变量的类型说明符是char,其取值是字符常量,即单个字符。每个字符变量被分配一个字节的内存空间,因此只能存放一个字符。字符值是以ASCII码的形式存放在变量的内存单元之中的。字符变量说明的格式和书写规则都与整型变量相同。例如:char a,b;

 

3)字符串常量

字符串常量是由一对双引号括起的字符序列。例如: "CHINA" "Hello""12.5" 等。在C语言中没有相应的字符串变量,可以用一个字符数组来存放一个字符串常量。字符串常量和字符常量是不同的量。二者有以下区别:

Ÿ   字符常量由单引号括起来,字符串常量由双引号括起来。

Ÿ   字符常量只能是单个字符,字符串常量则可以含一个或多个字符。

Ÿ   字符常量占一个字节的内存空间,而字符串常量占的内存字节数等于字符串中字符个数加1。这是因为需要在字符串的后面增加字符串结束标志,即字符'\0'(ASCII码为0)

 

问题7       整型常量的第一个字符为0引起的误解

[2.1] 在屏幕上输出两个整型常量(例如1017)的和。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      

4

     int a = 10;

5

     int b = 017;

6

     int c = a + b;

7

     printf("a+b=%d\n",c);

8

}

 

[编译结果]

编译通过。但运行结果是:

a+b=25

 

[问题分析]

     编译虽然通过了,但运行结果是a+b=25。按理说a+b=10+17=27,怎么会等于25呢?

     实际上,如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。例如17017的含义是不同的。前者只是十进制数字17,而后者是八进制数017,即十进制数字15。由于在int b = 017;里把17写成017,导致结果出错。

 

[正确代码]

     代码改动如下:

5

     int b = 17; //此处把017改成17

 

[运行结果]

a+b=27

 

问题8       将字符常量与字符串常量混淆

[2.2] 定义一个字符常量,赋值,并输出。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      

4

     char s;

5

     s = "a";

6

     printf("s=%c\n",s);

7

}

 

[编译结果]

E:\Code\2_2.cpp(5) : error C2440: '=' : cannot convert from 'char [2]' to 'char'

        This conversion requires a reinterpret_cast, a C-style cast or function-style cast

 

[问题分析]

char s;

s="a";

在这里就混淆了字符常量与字符串常量,字符常量是由一对单引号括起来的单个字符,字符串常量是一对双引号括起来的字符序列。C规定以'\0'作为字符串结束标志,它是由系统自动加上的,所以字符串“a”实际上包含两个字符:'a''\0',而把它赋给一个字符变量是不行的。

 

[正确代码]

     代码改动如下:

5

     s = 'a';

 

[运行结果]

s=a

问题9       混淆‘/’和‘\

[2.3] 输入两个数,求二者的商,输出结果。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      

4

     float a, b;

5

     scanf("%f %f",&a, &b);

6

     if(b==0)  \*判断分母是否为0*/

7

     {

8

          printf("分母为0/n");

9

     }

10

     else

11

     {

12

          float c = a\b;

13

          printf("a/b=%.2f/n",c);

14

     }

15

}

 

[编译结果]

E:\Code\2_3.cpp(6) : error C2017: illegal escape sequence

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xc5'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xd0'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xb6'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xcf'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xb7'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xd6'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xc4'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xb8'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xca'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xc7'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xb7'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xf1'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xce'

E:\Code\2_3.cpp(6) : error C2018: unknown character '0xaa'

E:\Code\2_3.cpp(6) : warning C4138: '*/' found outside of comment

E:\Code\2_3.cpp(6) : error C2100: illegal indirection

E:\Code\2_3.cpp(6) : error C2059: syntax error : '/'

E:\Code\2_3.cpp(7) : error C2143: syntax error : missing ';' before '{'

E:\Code\2_3.cpp(10) : error C2181: illegal else without matching if

E:\Code\2_3.cpp(12) : error C2017: illegal escape sequence

E:\Code\2_3.cpp(12) : error C2146: syntax error : missing ';' before identifier 'b'

 

[问题分析]

/’和‘\’这两个符号经常被混淆;注释对应的符号是‘/’,而转义字符是以‘\’开头,除号是‘/’。

在本例中,第6行代码居然报了18条错误信息!实际上这只是因为第6行代码中把表示注释的“/*”错误地写成了“\*”导致。因为把表示注释的“/*”错误地写成了“\*”,所以后面的“判断分母是否为0*/”就被认为是程序代码,而不是注释。因此才报了这么多错。第12行代码把除号“/”写成了“\”。重新编译则通过。运行,输入ab,看到的运行结果如下:

5.2 2.0

a/b=2.60/nPress any key to continue

从结果可以看出,第13行代码中把换行符“\n”错写为“/n”

 

[正确代码]

     代码改动如下:

6

     if(b==0) /*判断分母是否为0*/

以及

12

          float c = a/b;

13

          printf("a/b=%.2f\n",c);

 

[运行结果]

5.2 2.0

a/b=2.60

Press any key to continue

问题10   缺少强制类型转换

如果一个运算符两边的运算数类型不同,先要将其转换为相同的类型,即较低精度类型转换为较高精度类型,然后再参加运算。如两个float型数参加运算,虽然它们类型相同,但仍要先转成double型再进行运算,结果亦为double型。当运算符两边的运算数为不同类型时的转换,如一个long 型数据与一个int型数据一起运算,需要先将int型数据转换为long型,然后两者再进行运算,结果为long型。所有这些转换都是由系统自动进行的,但是,C语言也提供了以显式的形式强制转换类型的机制。

当较低精度类型的数据转换为较高精度类型时,一般只是形式上有所改变,而不影响数据的实质内容,而较高精度类型的数据转换为较低精度类型时则可能引起数据丢失。

当赋值运算符两边的运算对象类型不同时,将发生类型转换,转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。

整数相除会降低精度,丢失小数部分。可以在整数相除之前先做强制类型转换,以避免这种危险。

 

[2.4] 已知一个整数,求它的倒数并输出。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      

4

     int x=3;

5

     float y = 1/x;

6

     printf("x的倒数为:%.2f\n",y);

7

}

 

[编译结果]

编译通过。

 

[问题分析]

     虽然编译通过了,但运行结果为:

x的倒数为:0.00

实际上,3的倒数保留两位小数应该为0. 33。错误在哪里呢?因为x是整数31也是整数,则1/x是整数相除,结果为整数0,不会得到0. 33,进而会影响后边的计算结果。因此,可以把float y = 1/x;改成float y = 1/(float)x,或者改为float y = 1.0/x .当计算不同类型的数据时,一定要注意会不会出现引起错误的自动类型转换,建议最好加上强制转换。

 

[正确代码]

     代码修改如下:

6

     float y = 1/(float)x;  //或者改为float y = 1.0/x;

 

[运行结果]

x的倒数为:0.33

问题11   类型转换降低精度

当较低精度类型的数据转换为较高精度类型时,一般只是形式上有所改变,而不影响数据的实质内容,而较高精度类型的数据转换为较低精度类型时则可能有些数据丢失。如int型数值赋给char型变量时,只保留其最低8位,高位部分舍弃。 long型数据赋给int型变量时,将低16位值送给int型变量,而将高16 位截断舍弃。(这里假定int型占两个字节)

 

[2.5] 已知商品价格和折扣率,求折扣后的价格。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{      

4

     float price = 10.0f;        //原价

5

     float discount = 0.85f;     //折扣

6

     int newPrice = price*discount;  //计算折扣价

7

     printf("折扣后的价格为:%d\n",newPrice);

8

}

 

[编译结果]

e:\code\2_5.cpp(6) : warning C4244: 'initializing' : conversion from 'float' to 'int', possible loss of data

 

[问题分析]

     编译没有错误,只有一个警告:conversion from 'float' to 'int', possible loss of data.运行结果为:

折扣后的价格为:8

因为两个float值相乘,结果还是float型。将这个结果赋给一个整型变量,结果会舍弃高位部分。

 

[正确代码]

     代码修改如下:

6

     float newPrice = price*discount;  //计算折扣价

7

     printf("折扣后的价格为:%.2f\n",newPrice);

 

[运行结果]

折扣后的价格为:8.50

 

练习2

请改正如下程序中的错误。

1) 写一个函数strlen,返回给定字符串的长度。

1

#include <stdio.h>

2

int strlen(char s[])

3

{

4

     int i=0;

5

     while(s[i] != "\0")

6

          ++i;

7

     return i;

8

}

9

 

10

void main( )

11

{

12

     int len = strlen("Hello,world!");

13

     printf("len = %d/n", len);

14

}

 

2) 有一分数序列:2/13/25/38/513/821/13...求出这个数列的前20项之和。(结果应该为32.660259,但如下程序的运行结果为21.000000,请改正其中的错误)

1

#include <stdio.h>

2

void main( )

3

{

4

     int number=20;

5

     int a=2,b=1;

6

     float s=0;

7

     for(int n=1;n<=number;n++)

8

     {

9

          s=s+a/b;

10

          int t=a;a=a+b;b=t;

11

     }

12

     printf("sum is %.6f\n",s);

13

}


3 存储类

C语言中,每一个数据,不管是常量还是变量,都具有一定的数据类型。实际上,所有的变量不但具有一定的数据类型,还有一个存储类的问题。从编译程序的观点来看,一个变量名就等同于计算机中的某个物理存储单元,在该单元中,存储着用一串二进制数位代表的该变量的当前值。在计算机中,主要有两种单元:存储器单元和寄存器单元。变量存于存储器或寄存器就决定了它的存储类。变量的存储类确定了变量的作用域。

C语言中有四种存储类:自动变量、寄存器变量、静态变量、外部变量,分别由autoregisterstaticextern关键字来说明。

1. 自动变量

自动变量用关键字auto进行说明,其作用域被限制在该变量出现的那一块内。只要那一块或包含在那一块里的任何块被执行,该变量就存在而且可以被引用。如果程序离开了那一块,该变量就不存在。当不需要它们时,它们不占据任何存储空间。

每当一个变量在块内说明时,如果没有给予显式的存储类说明,则该变量就被看成是自动变量。大部分变量都属于这一种。所以说明自动变量的关键字auto是可以缺省的。自动变量可能通过以下方式加以说明:

auto int a;

auto int m = 33;

auto char ch;

……

 

2. 寄存器变量

CPU内部的寄存器中进行操作比在存储器上执行起来要快得多。当把一个变量说明为寄存器变量后,编译程序将尽可能地为该变量分配CPU内部的寄存器作为变量的存储单元,以加快运行速度。如果寄存器已经分配完毕,就把该寄存器变量当作自动变量来处理,即放在存储器中;此时,它的作用等价于auto,也只能用于局部变量和函数的参量说明。寄存器register关键字通过以下方式加以说明:

register int a;

register char ch;

……

实际上,根据具体的机器硬件,对寄存器变量存在一些限制。通常不建议使用它。

 

3. 静态变量

静态变量具有静态生存期,用static关键字通过以下方式加以说明:

static int a;

static int m = 33;

static char ch;

……

与自动变量一样,如果一个静态变量是在一个函数(或块)内加以说明,则该静态变量是“局部的”,即其作用域被限制在它出现的那一块内。但与自动变量不同的是,静态变量在函数退出时保持其数值。下次再调用这个函数时,静态变量仍包含上次函数退出时的值。

     静态变量可以是内部的(即局部于一个函数或块),也可以是外部的。如果静态变量的说明出现于任何函数之前,则这些静态变量对该文件中的所有函数均是已知的,是外部静态变量,但在其他文件中则是未知的。外部静态变量提供了隐藏数据对象的方法。

 

4. 外部变量

     与前三种不同,外部变量的作用域是全局的,而不是局部的。例如:

int x = 10;

void main( )

{

printf(“%d\n”,x);

}

虽然xmain函数之外定义,但在该函数内仍然是可以使用的。如果所用的局部变量和外部变量名字重复了,在发生冲突时,外部变量被屏蔽,局部变量起作用。

总的来说,外部变量必须在函数的外部说明一次,也应在想要使用它的任何函数中用关键字extern再次加以说明。但是只要在函数的外面说明了它,而说明又在同一个源程序文件的上面,则在函数内部就不需要再对外部变量加以说明。如果要在定义外部变量之前,或变量的定义和变量的使用不在同一个文件内,就必须使用extern加以说明。

 

5. 变量的作用域

     变量的作用域就是指变量的有效范围,或者起作用的范围。

     在一个函数内部定义的变量只在本函数范围内有效,即只有在本函数内才能使用它们,在此函数之外不能使用这些变量,称为“局部变量”。在函数之外定义的变量是外部变量,也称为“全局变量”。

局部变量可以与全局变量同名。在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,则这两个局部变量的作用域就在自己所在的循环体内。如果在函数内要使用全局变量,需要使用'::'extern 用于声明一个变量为全局变量。

静态变量的位置在静态存储区中(静态存储区在整个程序运行期间都存在)。未经初始化的全局静态变量会被程序自动初始化为0(自动变量对象的初值是任意的,除非被显示初始化)。

在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。局部静态变量的作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对它进行访问。

在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。全局静态变量的作用域与其他全局变量不同。非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。其他文件中可以使用相同名字的变量,不会发生冲突。

在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明它的文件当中可见,不能被其他文件所用。定义静态函数的好处是:其他文件中可以定义相同名字的函数,不会发生冲突;静态函数不能被其他文件所用。

 

问题12   在使用变量前未定义,或未初始化

[3.1] 对已知数组中的所有元素累加求和。

[错误代码]

1

#include <stdio.h>

2

void main( )

3

{           

4

     int a[]={1,2,3,4,5};

5

     int sum; 

6

     for(i=0;i<5;i++)

7

          sum+=a[i];

8

     printf("%d\n",sum);

9

}

 

[编译结果]

E:\Code\3_1.cpp(5) : error C2065: 'i' : undeclared identifier

 

[问题分析]

     上面的编译结果提示i没有定义。把第6句改为for(int i=0;i<5;i++)后,编译通过。但运行结果为:

-858993445

明显是错误的。原因是sum未初始化为0。把第5句改为int sum=0;则结果符合预期。

 

[正确代码]

     代码改动如下:

5

     int sum=0;

6

     for(int i=0;i<5;i++)

 

[运行结果]

15

问题13   错误理解静态变量的作用域

静态变量的位置在静态存储区中(静态存储区在整个程序运行期间都存在)。未经初始化的全局静态变量会被程序自动初始化为0(自动变量对象的值是任意的,除非被显示地初始化)。

在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。局部静态变量的作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对它进行访问。

在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。全局静态变量的作用域与其他全局变量不同:全局静态变量在声明它的文件之外是不可见的。准确地说是从定义之处开始到文件结尾。

 

[3.2] 静态变量的作用域演示。

[错误代码]

1

#include "stdafx.h"

2

void count( )

3

{

4

     static int num = 0;

5

     num++;

6

     printf("I have been called %d times\n",num);

7

}

8

void main( )

9

{

10

     for(int i=1; i<=3; i++)

11

          count( );

12

     printf("Total called %d times\n",num);

13

}

 

[编译结果]

E:\Code\3_2.cpp(14) : error C2065: 'num' : undeclared identifier

 

[问题分析]

     14行代码试图使用在count函数里定义的静态局部变量num。静态局部变量num是在count函数里定义的,它的作用域局限于count函数内部。可以改用全局变量。

 

[正确代码]

     代码改动如下:

12

//printf("Total called %d times\n",num);  删除此语句,或使用全局变量

 

[运行结果]

I have been called 1 times

I have been called 2 times

I have been called 3 times

 

[3.3] 全局静态变量的作用域演示。

[错误代码]

test1.cpp

1

#include "stdio.h"

2

void display( );

3

extern int n;

4

void main( )

5

{

6

     n = 20;

7

     printf("%d\n",n);

8

     display( );

9

}

 

test2.cpp

1

#include "stdio.h"

2

static int n;

3

void display( )

4

{

5

     n++;

6

     printf("%d\n",n);

7

}

 

[编译结果]

Linking...

test1.obj : error LNK2001: unresolved external symbol "int n" (?n@@3HA)

Debug/test1.exe : fatal error LNK1120: 1 unresolved externals

Error executing link.exe.

 

[问题分析]

     文件分别编译通过,但link的时候test1.cpp中的变量n找不到定义,产生错误。一种解决方法是把display函数放到test1.cpp中,另一种方法是把n定义成全局变量,而不是全局静态变量。但全局变量需要初始化,而未经初始化的全局静态变量会被程序自动初始化为0

 

[正确代码]

     把两个文件合成一个文件,并改动如下:

1

#include "stdio.h"

2

static int n;

3

void display( )

4

{

5

     n++;

6

     printf("%d\n",n);

7

}

8

void main( )

9

{

10

     n = 20;

11

     printf("%d\n",n);

12

     display( );

13

}

 

[运行结果]

20

21

 

问题14   全局变量的错误声明

全局变量一般这样定义:在某个cpp文件中定义全局变量,如int gCount; 然后在要调用的cpp文件里声明extern int gCount;即可。如果还是使用int gCount,那么就会产生LNK2005错误。

头文件的重复包含可能会导致全局变量的错误声明。需要包含的头文件中往往含有变量、函数、类的定义,在其它使用的地方不得不多次被包含,如果头文件中没有相关的宏等防止重复链接的措施,那么就会产生LNK2005错误。解决办法是在需要包含的头文件中做类似的处理:

#ifndef   MY_H_FILE              //如果没有定义这个宏,宏的名字任意

#define   MY_H_FILE          //定义这个宏  

int  gCount

  …….                           //头文件主体内容  

  ……  

#endif  

如果不使用宏来做,也可以使用预编译,在头文件中加入:  

#pragma   once  

…….                           //头文件主体  

 

[3.4] 使用全局变量,求已售商品的总营业额。

[错误代码]

test1.cpp

1

#include "stdio.h"

2

void Sale(int count,float price);

3

float gTotal;

4

void main( )

5

{

6

     gTotal = 0;

7

     Sale(5, 20);

8

     Sale(32, 3);

9

     printf("营业额为%.2f\n", gTotal);

10

}

 

test2.cpp

1

#include " stdio.h"

2

float gTotal; 

3

void Sale(int count, float price)

4

{

5

     gTotal += price*count;

6

}

 

[编译结果]

Linking...

test2.obj : error LNK2005: "float gTotal" (?gTotal@@3MA) already defined in test1.obj

Debug/test1.exe : fatal error LNK1169: one or more multiply defined symbols found

Error executing link.exe.

 

[问题分析]

     全局变量应该这样定义:在一个cpp文件中定义:“float gTotal;”,在其他cpp文件里声明“extern float gTotal; ”。而在本例中test1.cpptest2.cpp里都是用的“float gTotal;”应该把其中一个改为“extern float gTotal;”。

 

[正确代码]

     test1.cpp中的代码改动如下:

3

extern float gTotal;

 

[运行结果]

营业额为196.00

问题15   滥用全局变量

全局变量有很多优点,但是滥用全局变量会带来很多问题。全局变量不但阻碍了代码重用,而且使代码变得更难维护。它们阻碍代码重用是因为任何使用了全局变量的代码就立刻与之耦合,导致全局变量修改后它们也不得不跟着修改,从而使任何重用都不可能了。它们使代码变得更难维护的原因是很难甄别出哪些代码用了某个特定的全局变量,因为任何代码都有访问它们的权限。全局变量是不设防的。随便哪个维护代码的C++新手,都能使对全局变量有强烈依赖的软件随时崩溃。

有些程序员喜欢使用全局变量,认为它用起来很方便。但这样做很自私,因为软件的维护常常比它的初次开发要花费更多时间,而使用全局变量就意味着把麻烦扔给了维护工程师。在软件要交付之前,或者维护过程中,我们会发现,可能需要增加全局变量,这个变更需要大量时间,涉及到很多(甚至所有)源文件的更改。

所以,在没有必要的情况下,尽量不用全局变量。或者把对于值的访问加上了函数形式的包装,从而获得宝贵的可扩充性。

练习3

请改正如下程序中的错误。

1) 在函数里用静态变量为新学生自动按照先后顺序分配学号。

1

#include <stdio.h>

2

void NewStudent( )

3

{

4

     static int NO=0;

5

     NO++;

6

     printf("新生报到:学号为%d\n",NO);

7

}

8

void main( )

9

{

10

     NewStudent( );

11

     printf("刚才报到的新生学号为%d\n",NO);

12

    

13

     NewStudent( );

14

     printf("刚才报到的新生学号为%d\n",NO);

15

 

16

     NewStudent( );

17

     printf("刚才报到的新生学号为%d\n",NO);

18

}

 

2) 用全局变量保存学生总数,在函数AddStudent里增加新生数量,在函数Graduate里减去毕业学生数量。最后输出当前学生总数。

ex3_2.cpp

1

#include <stdio.h>

2

#include "func.h"

3

int TotalStudents;

4

void main( )

5

{

6

     TotalStudents = 0;

7

 

8

     AddStudent(5);

9

     Graduate(1);

10

     Graduate(2);

11

 

12

     printf("目前学校共有%d名学生。\n", TotalStudents);

13

}

 

func.h

1

#include <stdio.h>

2

int TotalStudents;

3

void AddStudent(int n)

4

{

5

     TotalStudents += n;

6

}

7

void Graduate(int n)

8

{

9

     TotalStudents -= n;

10

}

 


4 运算符

C语言中有丰富的运算符和表达式,使得C语言功能十分完善,这也是C语言的主要特点之一。

 

1. 运算符的种类

C语言中的运算符可分为以下几类:

1)算术运算符

包括加(+)、减()、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)等。

如果参加+、–、*/ 运算的两个数中有一个是float型或double型,则结果为double型。两个整数相除的结果为整数,舍去小数部分。但是如果除数或被除数中有一个为负值,则舍入的方向是不固定的。多数C编译系统采取“向零取整”的方法,即取整后向零的方向靠拢。

求余运算要求参与运算的量均为整型,其运算结果等于两数相除后的余数。

自增(++)、自减(--)的作用是使变量的值增1或减1,只能用于变量,不能用于常量或表达式。++--写在变量名的前面和后面具有不同含义。例如:

++i--i 的含义是在使用i之前,先使i的值加/1

i++i-- 的含义是在使用i之后,使i的值加/1

2)关系运算符

用于比较运算,包括大于(>)、小于(<)、等于(==) 大于等于(>=)、小于等于(<=)和不等于(!=)等。

3)逻辑运算符

用于逻辑运算。包括与(&&)、或(||)、非(!)

4)位操作运算符

按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)等。

5)赋值运算符

包括赋值运算符(=)及其扩展运算符,如复合算术赋值运算符(+=-=*=/=%=)和复合位运算赋值运算符(&=|=^=>>=<<=)等。

如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规定如下:

Ÿ   实型赋予整型,舍去小数部分。

Ÿ   整型赋予实型,数值不变,以浮点形式存放,即增加小数部分(小数部分的值为0)

Ÿ   字符型赋予整型,由于字符型为一个字节,而整型为二个字节,故将字符的ASCII码值放到整型量的低八位中,高八位为0

Ÿ   整型赋予字符型,只把低八位赋予字符量。

复合算术赋值运算符和复合位运算赋值运算符的写法,对初学者可能不习惯,但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。

6)条件运算符( :  )

这是一个三目运算符,用于条件求值。其一般形式是:

      <表达式1>?<表达式2>:<表达式3>;

该运算符的含义是:先求表达式1的值,如果为真,则求表达式2 的值并把它作为整个表达式的值;如果表达式1 的值为假,则求表达式3 的值并把它作为整个表达式的值。

7)逗号运算符( )

用于把若干表达式组合成一个表达式。程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值。并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。

8)指针运算符

包括取内容(*)和取地址(&)二种运算。

9)求字节数运算符

包括(sizeof),用于计算数据类型所占的字节数。

10)特殊运算符

包括括号运算符( ),下标运算符[ ],成员运算符(→和   )等。

 

2. 优先级

C语言中,运算符的运算优先级共分为15级,从1级到15级逐渐降低。在表达式中,优先级较高的先于优先级较低的进行运算。而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理。C语言中运算符的运算优先级见表4.1(从上到下逐渐降低)。

 

3. 结合性

在C语言的表达式中,各运算量参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定是自左向右进行运算还是自右向左进行运算。 这种结合性是其它高级语言的运算符所没有的,因此也增加了C语言的复杂性。

C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。双目运算具有左结合性。例如算术运算符的结合性是自左至右,即先左后右。如有表达式x-y+zy应先与“-”号结合,执行x-y运算,然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。 典型的右结合性运算符是赋值运算符。如x=y=z,由于“=”的右结合性,应先执行y=z再执行x=(y=z)运算。自增(++)、自减(--)也具有右结合性。C语言中运算符的结合性见表4.1

 

4.1 运算符的优先级及结合性

运算符

结合性

() [ ]  ->    

从左至右

 

!  ~  ++  --  +(正号)  -(负号)  * (指针所指内容)  &  sizeof

从右至左

 

*  /   % 

从左至右

 

+  -

从左至右

 

<<  >>

从左至右

 

<  <=  >  >=

从左至右

 

==  !=

从左至右

 

&(位与)

从左至右

 

^(位异或)

从左至右

 

| (位或)

从左至右

 

&&(逻辑与)

从左至右

 

||(逻辑或)

从左至右

 

?:

从右至左

 

=  +=  -=

从右至左

 

从左至右

 

 

问题16   分母为0

在进行除法运算或模运算时不能除以0,一定要做合法性检查。尤其是当运算复杂时,往往会忽视这一点。

[4.1] 输入x,求y=1/(1-x*x)

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     float x;

5

     printf("请输入x");

6

     scanf("%f",&x);

7

     float y = 1/(1-x*x);

8

     printf("y=%f\n",y);

9

}

 

[编译结果]

编译通过。

 

[问题分析]

编译通过。当x不为1时,结果是正确的,如:

 

请输入x2

y=-0.333333

 

但是当x1时,1-x*x等于0,即分母为0,输出结果如下:

 

请输入x1

y=1.#INF00

 

结果显然不对,应当进行合法性检验,除数为0时,应该输出警告信息。

 

[正确代码]

把第7~8行代码改为:

7

     if(x==1)    

8

         printf("除数不可以为0!\n");

9

     else

10

     {

11

          float y = 1/(1-x*x);

12

          printf("y=%f\n",y);

13

    }

 

[运行结果]

请输入x1

除数不可以为0!

问题17   对实型变量进行求余运算

%是求余运算,得到a/b的整余数。整型变量ab可以进行求余运算,而实型变量则不允许进行“求余”运算。

[4.2] 已知两个数,求二者相除的余数。

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     float a=5,b=2;

5

     printf("a%%b=%d\n",a%b);

6

}

 

[编译结果]

e:\code\4_2.cpp(5) : error C2296: '%' : illegal, left operand has type 'float'

e:\code\4_2.cpp(5) : error C2297: '%' : illegal, right operand has type 'float'

 

[问题分析]

实型变量则不允许进行“求余”运算。

 

[正确代码]

     代码改动如下:

4

     int a=5,b=2;

 

[运行结果]

a%b=1

问题18   =”与“==”混淆

  =表示赋值,而==才表示我们数学中的相等关系。在许多高级语言中,用“=”符号作为关系运算符等于。如在BASIC程序中可以写

if (a=3) then  a=b

C语言中,“=”是赋值运算符,==是关系运算符。由于习惯问题,初学者往往会把:

if (a==3) a=b;

写成:

if (a=3) a=b;

本来是要判断a是否和3相等,如果a3相等,就把b值赋给a;结果却直接把3赋给a,而且会导致程序逻辑错误。初学者往往会犯这样的错误。

[4.3] 555555的约数中最大的三位数是多少。

根据约数的定义,对于一个整数N,除去1和它自身,能整除N的数即为N的约数。因此,最简单的方法是用2N-1之间的所有数去除N,即可求出N的全部约数。本例中只需求出555555的约数中最大的三位数,则其取值范围可限制在100999之间。

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     long num = 555555;

5

     for(int i=999;i>=100;i--)

6

     {

7

          int j=num%i;

8

          if(j=0)

9

          {

10

               printf("555555的约数中最大的三位数是%d\n",i);

11

               break;

12

          }

13

     }

14

}

 

[编译结果]

编译通过。

 

[问题分析]

虽然编译通过,但运行结果不正确:没有任何屏幕输出。 但实际上555555的约数肯定有三位数。问题在哪里呢?printf根本就没有执行到,是不是判断条件的问题呢?仔细检查,发现if(j=0)这一句代码里把“==”错误地写成了赋值号“=”,导致变量j被赋值后其值为0,不满足判断条件,因此永远也无法执行printf这一句代码。

 

[正确代码]

     代码改动如下:

8

          if(j==0)

 

[运行结果]

555555的约数中最大的三位数是777

问题19   &”与“&&”,“|”与“|| 混淆

前面说过,“=”与“==”很容易被混淆;同样,将按位与运算符&与逻辑运算符&&,或者将按位或运算符|与逻辑运算符||混淆,也是很容易犯的错误。特别是C语言中按位与运算符&和按位或运算符|,与其他编程语言的按位与运算符和按位或运算符在表现形式上完全不同(如Pascal语言中分别是andor),更容易让程序员因为受其他编程语言的影响而犯错。按位与运算符&和按位或运算符|对操作数的处理方式是将其视作一个二进制的位序列,分别对每个位进行操作。而逻辑运算符&&||对操作数的处理方式是则是通常把0视作“假,把非0值视为“真”。而且,逻辑运算符&&||在左侧的操作数的值能够确定最终结果时根本不会对右侧操作数求值。

 

[4.4] 找出100以内既不能被2整除又不能被3整除的整数。

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     for(int i=0;i<100;i++)

5

     if(i%2 & i%3)

6

          printf("%d\t",i);

7

     printf("\n");

8

}

 

[编译结果]

编译通过。

 

[问题分析]

该程序的运行结果如下:

1       7       13      19      25      31      37      43      49      55

61      67      73      79      85      91      97

但实际上51117等数字也满足既不能被2整除又不能被3整除的条件。为什么这些数字被丢掉了呢?原来,第5if(i%2 & i%3)中,错把逻辑运算符&&写成按位与运算符&了。那么这一句代码在运行中时,5%2=1,即二进制数0001;而5%3=2,即二进制数0010;二者按位进行“与”的操作,结果为0,导致结果中漏掉了“5”。其他被漏掉的数字也使基于同样原因。

 

[正确代码]

     代码改动如下:

5

     if(i%2 && i%3)

 

[运行结果]

1       5       7       11      13      17      19      23      25      29

31      35      37      41      43      47      49      53      55      59

61      65      67      71      73      77      79      83      85      89

91      95      97

问题20   ++、“--的前置和后置方式

[4.5] 依次输入5学生的分数,如果及格,则输出及格人数。

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     int nSum = 0;

5

     int score;

6

     for(int i=0;i<5;i++)

7

     {

8

          scanf("%d",&score);

9

          if(score>=60)

10

               printf("及格人数:%d\t",nSum++);

11

          printf("\n");

12

     }

13

}

 

[编译结果]

编译通过。

 

[问题分析]

运行结果如下:

90

及格人数:0

80

及格人数:1

70

及格人数:2

60

及格人数:3

50

当输入分数90时,此时输出的及格人数居然是0。因为第10行代码

printf("sum:%d\t", ++nSum);

里面的++nSum被写成了nSum++。前者是在使用之前就先加1,后者是使用过后再加1。所以当输入分数90时,此时输出的及格人数是0

 

[正确代码]

代码改动如下:

10

               printf("及格人数:%d\t", ++nSum);

 

[运行结果]

90

及格人数:1

80

及格人数:2

70

及格人数:3

60

及格人数:4

50

问题21   运算符优先级错误

[4.6] 已知有两个小于16的二进制数hilow,要求得到r,其低4位与low一致,高4位与hi一致。

 

[错误代码]

1

#include "stdio.h"

2

void main( )

3

{

4

     int hi=0xc;

5

     int low=0x5;

6

     int r = hi<<4 + low;

7

     printf("r=%x\n",r);

8

}

 

[编译结果]

E:\CODE\4_6.cpp(6) : warning C4554: '<<' : check operator precedence for possible error; use parentheses to clarify precedence

 

[问题分析]

运行结果应该是:

r=c5

但实际上却是:

r=1800

结果出乎意料。问题出在第6行:

     int r = hi<<4 + low;

编译器给出警告:warning C4554: '<<' : check operator precedence for possible error; use parentheses to clarify precedence,建议加上括号。因为加法运算符的优先级比移位运算符的优先级高,所以实际上编译器认为:先算4+low,再把hi左移4+low位。

 

[正确代码]

代码改动如下:

6

int r = (hi<<4) + low;

 

[运行结果]

r=c5

练习4

请改正如下程序中的错误。

1) 编程求1000以内的所有阿姆斯特朗数(如果一个正整数等于其各个数字的立方和,则称该数为阿姆斯特朗数。如 153=13+53+33)。

1

#include<stdio.h>

2

 void main( )

3

 {

4

     int a[3];

5

     printf("1000以内的所有阿姆斯特朗数有:\n");

6

    

7

     for(int i=2;i<1000;i++) /*穷举要判定的数i的取值范围2~1000*/

8

     {

9

          for(int t=0, k=1000; k>=10; t++) /*截取整数i的各位(从高向低位)*/

10

          {

11

               a[t]=(i%k)/(k/10); /*分别赋于a[0]~a[2}*/

12

               k/=10;

13

          }

14

          if(a[0]*a[0]*a[0]+a[1]*a[1]*a[1]+a[2]*a[2]*a[2]=i)              

15

               /*判断i是否为阿姆斯特朗数*/              

16

               printf("%5d",i); /*若满足条件,则输出*/

17

     }

18

     printf("\n");

19

}

 

2) 1234四个数字,能组成哪些互不相同且无重复数字的三位数?

1

#include<stdio.h>

2

void main( )

3

{

4

     int i,j,k;

5

     printf("\n");

6

     for(i=1;i<5;i++)

7

          for(j=1;j<5;j++)

8

               for (k=1;k<5;k++)

9

               {

10

                    if (i!=k & i!=j & j!=k)    /*确保ijk三位互不相同*/

11

                         printf("%d%d%d\n",i,j,k);

12

               }

13

}

 


5 流程控制

C语言是一种结构化的程序设计语言,有很强的过程功能和数据结构功能,支持结构化的逻辑结构。从程序流程的角度来看,程序可以分为三种基本结构,即顺序结构、分支结构、循环结构。顺序结构是最简单的一种,依次执行程序中的各条语句。分支结构根据给定的条件进行判断,以决定执行某个分支程序段。循环结构的特点是:在给定条件成立时,反复执行某个程序段,直到条件不成立为止。 给定的条件称为循环条件,反复执行的程序段称为循环体。这三种基本结构可以组成所有的各种复杂程序。C语言提供了多种语句来实现这些程序结构。

控制语句用于控制程序的流程,以实现程序的各种结构方式。它们由特定的语句定义符组成。C语言的控制语句可分为条件判断语句、循环执行语句、转向语句三类。

 

1. 条件判断语句

1if语句

C语言的if语句有三种基本形式。

Ÿ   if
形式为:
    if(
表达式)
       
语句;
如果表达式的值为真,则执行其后的语句,否则不执行该语句。

Ÿ   if-else
形式为:
    if(
表达式)
       
语句1
    else
       
语句