c/c++程序设计及其应用 - pudn.comread.pudn.com/downloads71/ebook/258469/c.doc · web...

278
C/C++语语语语语语语语语 伍伍伍 伍伍 伍伍 伍伍伍伍伍伍伍

Upload: others

Post on 23-Apr-2020

31 views

Category:

Documents


0 download

TRANSCRIPT

C/C++程序设计及其应用

C/C++语言与研究开发实践

伍晓宇 辛勇 编著

航空工业出版社

内容提要

本书的前三部分重点放在C/C++语言本身的掌握和使用上,尽量避免较复杂的算法,可使读者顺利向面向对象技术及可视化程序设计技术进行平稳的过渡。

第四部分作为研究专题,深入分析了C/C++程序与其相应汇编代码之间的关系,使读者能从更深层次上理解和掌握C/C++的精髓。作者提出的应用C/C++程序自动生成汇编代码这一技术可提高开发工作的效率和程序的稳定性。最后以浮动许可证设计为题,详细讨论了TCP/IP网络的Socket编程技术和以Windows操作系统为平台的网络加密系统的设计技术。

前言

近年来,在国内各高校的许多专业,尤其是理工科类专业广泛设置了C语言课程的教学。这是因为在实际工作中C语言已不仅为计算机专业工作者所使用,而且开始为广大非计算机专业出身的工程技术人员所喜爱和使用,成为理工科各专业必备的工具。学习C语言已成为广大计算机应用人员和初学者的迫切要求。

但是,随着计算机软件技术的发展,特别是可视化程序设计技术的广泛应用,原有的C语言教学体系已不能适应需要,应该对其教学内容进行修改、调整和充实,并加入C++和C/C++可视化程序技术等内容。

目前,高等教育改革要求压缩课堂授课学时,增加授课学时以外的动手机会,使学生不仅能够完整全面地掌握C/C++的基本知识,同时能够初步应用这些基本知识尝试去分析和解决各种实际工程问题。

针对以上问题,本书包括以下几个部分:

第一部分:C程序设计。

C程序设计部分是全书的基础,作者把介绍的重点放在语言本身的掌握和使用上,尽量避免较复杂的算法,因为算法本身不属于语言学习的范畴。该部分几乎所有的实例都是短小和简炼的,力求在初步掌握基本知识的基础上,使学生上机时能尽快将实例调试通过,以增强学习的信心和兴趣。

本书对目前国内某些C语言教材的一些观点进行了修正,例如,在PC机上关于short、int型数据是否有区别,register变量是否能起实际作用等。另外,对于过去教学中没有提及的一些问题进行了探讨,如PC机上指针通常的实际长度等。本书还指出了Turbo C和Visual C++在处理个别问题上的不同点。

第二部分:C++程序设计。

以往我们在C++程序设计学习过程中,往往强调C++面向对象技术内容的一面,而忽略它的非面向对象技术部分。作者认为C++非面向对象技术的内容是学习其面向对象技术内容的基础。对于C++程序设计学习过程中的困难,常常是因为对某些非面向对象特性不能较好理解造成的。本书强调C++非面向对象内容的学习和正确理解,例如强调结构体是类的子集。应在正确理解C++结构体的基础上,顺利向类的概念进行平稳的过渡,这样可以收到较好的学习效果。

第三部分:Visual C++可视化程序设计。

可视化程序设计是近年来非常流行的程序开发技术,由于它大大提高了设计效率和程序的稳定性,因此得到了广泛应用,但目前在各高校的C/C++语言教学中,它一般不属于课堂教学的范畴,这就造成了课堂学习与学生将来实际应用之间的脱节。因此,在本书中我们将该部分的基本内容加上。

可视化程序设计与传统的程序设计在基本概念上有很大的区别,本部分的目的在于帮助读者在学习标准的C/C++基本知识的基础上,完成从传统设计技术向可视化设计技术的过渡。

第四部分:研究开发专题。

该部分收集了作者近年来在科研工作中的一些研究开发实例,经过简化后总结于本书中,可供科研开发人员参考,也可作为学生在完成C/C++的课程学习之后,进一步学习提高之用。

第二十四章“C/C++程序深入剖析”从编译系统的角度,对C/C++执行程序的内存映象结构进行了分析,并以Turbo C和Borland C++编译器为实例深入分析了C/C++程序与其相应汇编代码之间的关系,使读者能从更深层次上理解和掌握C/C++语言的精髓。在该章中,作者提出了应用C/C++程序自动生成汇编代码这一技术可大大提高开发工作的效率和程序的稳定性。

第二十五章“PC机存储器型卡程序设计”充分利用第二十四章的成果,将其应用于电路板卡的程序设计工作中。

第二十六章浮动许可证加密系统(Floating License)设计,以作者为新加坡国立大学所属某软件公司开发的网络浮动加密系统为原型,详细讨论了TCP/IP网络的Socket编程技术,计算机ID号的构造,浮动许可证工作原型和设计的基本技术等。读者在学习基本原理的基础上,可以开发自已的系统。

本书第一、二、四部分由伍晓宇编写、第三部分由辛勇编写,全书由伍晓宇统编和修改,由南昌大学杨国泰教授审稿,作者十分感谢华中理工大学同窗学友李建军博士为本书所提出的许多建设性的意见。另外,深圳大学学生刘斌、胡勇、冯俊秋、储昕、奚荣斌和张顺娣等在参加作者指导的科研实践和毕业设计中做了不少与本书有关的工作,在此向他们表示感谢。

由于作者水平有限,肯定会有不少缺点或错误,希望得到专家和读者批评指正。

伍晓宇 辛勇

1999年9月

E-mail:[email protected]

1 第一部分 C程序设计

第一章 C语言概述2

1.1 C语言来源与特点2

1.2 C语言的结构3

第二章 常量、变量、运算符与表达式5

2.1 C语言的基本数据类型5

2.2 常量7

2.3 变量9

2.4 各类数值型数据之间的混合运算14

2.5 运算符与表达式15

第三章 程序控制语句21

3.1 条件分支语句21

3.2 循环控制语句22

3.3 开关语句与跳转语句24

第四章 数组28

4.1 一维数组28

4.2 二维数组30

4.3 字符数组31

第五章 指针33

5.1 定义与引用33

5.2 一维数组与指针的关系34

5.3 字符串指针35

5.4 指针内存分配36

5.5 多维数组与指针的关系38

5.6 指针数组和指向指针的指针40

第六章 函数43

6.1 函数的定义43

6.2 函数的说明44

6.3 函数的调用45

6.4 递归调用49

6.5 命令行参数51

6.6 函数指针52

6.7 函数变参54

第七章 结构体56

7.1 结构体的定义56

7.2 结构体的引用57

7.3 结构体的初始化58

7.4 指向结构体的指针59

7.5 共用体(联合体)61

7.6 枚举类型63

7.7 自定义类型64

7.8 位段66

第八章 编译预处理68

8.1 宏定义68

8.2 文件包含处理70

8.3 条件编译70

第九章 文件I/O72

9.1 文件的操作模式72

9.2 文件与设备的读写73

第二部分 C++程序设计81

第十章 C++的非面向对象特性(C的扩展)82

10.1 C++的输入和输出82

10.2 new和delete82

10.3 const83

10.4 注解84

10.5 缺省的函数形式参数84

10.6 强制类型转换84

10.7 引用(reference)85

10.8 函数原型(说明)87

10.9 结构体(struct)与共用体(union)87

第十一章 类94

11.1 类与对象(变量)94

11.2 静态成员100

第十二章 派生类103

12.1 一般派生103

12.2 虚函数109

12.3 虚基类112

第十三章 友元114

13.1 友元函数114

13.2 友元类114

第十四章 运算符重载116

14.1 一元运算符116

14.2 二元运算符117

第十五章 流库122

15.1 流类的结构122

15.2 设备操作符123

15.3 ostream和istream成员函数的应用124

15.4 文件管理126

第十六章 模板132

16.1 函数模板132

16.2 类模板133

第三部分 Visual C++可视化程序设计136

第十七章 Visual C++可视化编程的基本思想137

17.1 MFC编程特点137

17.2 VC工程文件的构成137

17.3 AppWizard工具138

17.4 ClassWizard工具139

17.5 Resource Editor工具141

第十八章 MFC程序结构分析143

18.1 Windows程序工作原理143

18.2建立应用程序143

18.3程序结构剖析144

18.4 显示文本147

18.5 显示图形148

18.6 显示位图149

18.7 文档的串行化150

18.8 直接显示数据151

18.9 保持显示数据152

第十九章 消息处理过程155

19.1 消息处理机制155

19.2 鼠标消息155

19.3 键盘消息156

19.4 窗口消息156

19.5 其它消息158

19.6 自定义消息160

第二十章 菜单163

20.1 菜单的处理机制163

20.2 菜单项的增加163

20.3 菜单项的更新控制165

第二十一章 对话框167

21.1 生成对话框资源167

21.2 对话框的数据交换167

第二十二章 常用控键170

22.1 进度条170

22.2 滑动条171

22.3 Up-down控制173

22.4 工具条174

22.5 状态条175

22.6 部件库177

22.7 ActiveX控件简介183

第二十三章 多任务编程初步187

23.1 多进程的启动187

23.2 多线程的启动187

23.3 优先级的设定188

23.4 任务间通信与同步190

第四部分 专题应用研究195

第二十四章 C/C++程序深入剖析196

24.1 C程序对应汇编代码分析196

24.2 C++程序对应汇编代码分析208

24.3 C/C++语言辅助汇编程序设计217

第二十五章 PC机存储器型卡程序设计230

25.1 存储型卡驻留程序的基本格式230

25.2 测试电路及其写入程序231

25.3 ROM文件的准备232

第二十六章 浮动许可证加密系统235

26.1 计算机ID号的构造235

26.2 Floating License核心技术238

26.3 License状态的管理255

26.4 网络防火墙与远程监控272

附录:ASCII字符代码274

参考文献278

ú

û

ù

ê

ë

é

6

5

4

3

2

1

ú

ú

ú

û

ù

ê

ê

ê

ë

é

6

3

5

2

4

1

第一部分

C程序设计

第一章 C语言概述

第二章 常量、变量、运算符与表达式

第三章 程序控制语句

第四章 数组

第五章 指针

第六章 函数

第七章 结构体

第八章 编译预处理

第九章 文件I/O

第一章 C语言概述

1.1 C语言来源与特点

一、来源

C语言是UNIX操作系统的主程序设计语言,最初UNIX是用汇编语言写的, 存在:1)移植性差;2)开发效率低;3)难读等问题。因此, 人们希望找到一种既象汇编语言一样能对硬件进行直接操作,同时又具有高级语言的优点(包括可读性好,移植性强等)。这类语言介于高级语言和低级语言之间,称为中级语言。

C语言是一种中级语言。它的发展过程大体如下:

1970年贝尔实验室的K.Thompson设计了B语言,并用它来改写UNIX系统。

1972年贝尔实验室的D.M.Rithie针对B语言过于简单、功能不强和生成的解释代码运算速度慢的弱点,在B语言的基础上设计了C语言。

1973年两人合作用C语言重写了UNIX操作系统,原来的UNIX系统是它们1969 年用汇编语言合写的。

现在UNIX系统90%的代码是C,广泛用于各种工作站和小型机,如HP、 Sun、RISC/6000等,并产生了很多变种,如Aegis、AIX等。

70年代后期微型机迅速发展,C语言开始移植到非UNIX系统上,已独立于UNIX。

1983年, 美国国家标准协会制定了C语言标准 ── ANSI C。

目前在PC机上比较流行的C编译系统有:

MSC, Visual C++

Turbo C 2.0, 3.0. Turbo C++, Borland C++

High C++

Watcom C

二、特点

优点:

1.表达能力强,设计自由度大。既能完成普通高级语言的功能,又能对计算机硬件进行直接访问,而且能直接对物理地址进行操作或进行字位级操作, 几乎没有限制。

2.代码紧凑、高效、书写简洁。

源代码中,仅有四种基本数据类型,32个关键字。

目标代码中,执行效率仅比汇编程序低10~20%,编译代码小。

3.良好的移植性。

缺点:

安全性差,表现在数据和指针没有屏障;类型转换比较随便;数组越界及变量类型一致性不作语法上的严格检查。

但要辩证地看待上述问题,应强调程序员自己去保证程序的正确性。以下是几种常用计算机语言的应用范围。

Basic ── 初学者

Fortran ── 科学计算

Pascal ── 学生

COBOL ── 银行、金融部门

C ── 程序员

1.2 C语言的结构

/* [例1-1] 最简单的C程序之一,求两整数和*/

/* samp1_1.c */

void main()

{ int a, b, c;

a=1; b=2;

c=a+b;

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

}

运行结果:

c=3

/* [例1-2] 从键盘输入两整数,并且显示较大者*/

/* samp1_2.c */

#include /* 头文件stdio.h,它包含标准输入输出库函数的等信息的说明 */

int max(int x, int y); /* 函数max()的说明(申明) */

/* 主函数定义 */

void main()

{ int a,b,c; /* {}内为函数体, 本行为其中变量定义部分,以下为执行部分*/

/* 调用ANSI标准库函数scanf,键盘输入变量a、b值*/

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

c=max(a,b); /* 调用自编函数max ,其中a、b为实参*/

printf("max=%d",c); /* 调用ANSI标准库函数printf,显示变量c的值*/

}

/* 自编函数max的定义 */

int max(int x, int y) /* 函数说明部分:函数类型int, 函数名max, 函数形参x,y 及其类型*/

{ int z; /* {}范围内为函数体, 本行为其中的变量定义部分 */

if(x>y) z=x; /* 以下为执行部分 */

else z=y;

return z; /* 返回函数值 */

}

运行结果:

3,4 ( (从键盘上输入两整数)

max=4

通过以上例子可以看出:

1.C程序是由函数构成的。

至少包含一个主函数(main函数),也可以包含一个main函数和若干个子函数。

函数包括两种:

1)库函数。它包括ANSI标准函数和非ANSI标准函数。为了保证程序的良好移植性,应尽可能使用标准函数。

2)自编函数。它包括一个main函数和若个子函数。

2.一个函数的定义由函数的说明部分和函数体两部分组成。

1)函数的说明部分包括函数类型,函数名,函数参数(形参)名和形式参数类型等。

2)函数体是函数说明部分后的大括号{......}之间的部分。它一般包括:a)变量定义;b)执行部分;c)返回函数值。

此外,在程序的开头还常常有自编函数的(说明)申明,以及说明库函数的头文件等。

3.关于书写格式

1)main函数位置任意,但习惯上放在其它函数前面。

2)每条语句的最后必须有一个分号。

3)一行内可以写几个语句,一个语句也可以分写在多行上。

4)程序的注释部分应括在/* ...... /之间,/和*之间不允许留有空格;注释部分允许出现在程序中的各个位置上,但在双引号“”内无效。

5)C语言标识符的大小写是不同的(或称大小写敏感)。

第2章 常量、变量、运算符与表达式

2.1 C语言的基本数据类型

C语言的基本类型可以分为:

1.字符型。它以char表示。

表2.1

数据类型

Windows 表示

字节数

位数

取值范围

char

short

int

long

unsigned char

unsigned short

unsigned int

unsigned long

float

double

CHAR

SHORT

INT

LONG

UCHAR

USHORT

UINT

ULONG

FLOAT

DOUBLE

1

2

4

4

1

2

4

4

4

8

8

16

32

32

8

16

32

32

32

64

-128~+127

-32768~+32767

-2147483648~2147483647

-2147483648~2147483647

0~256

0~65535

0~4294967295

0~4294967295

-1.0E38~+1.0E38

(8位有效数字)

-1.0E308~+1.0E308

(16位有效数字)

2.整型。它又可分为:

短整型,以short或short int表示;

普通整型,以int表示;

长整型,以long或long int表示。

在PC机上的DOS操作系统中,一般无int与short型的区别,两者是一致的,但对于PC机上的Windows系统,或者其它机种上的各种操作系统,两者一般是有区别的。

对于字符型和整型,可以将其存储单元中全部二进位(bit)用作存放数本身,而不包括符号,构成无符号字符型和无符号整型。包括:

unsigned char

unsigned short

unsigned int

unsigned long

3.浮点型(实型)。它又可分为:

单精度型。它以float表示;

双精度型。它以double表示。

4.无值型(void)。

ANSI标准没有具体规定以上各类数据类型所占内存字节数,各种机器或操作系统处理上有所不同。表2.1是PC机上Windows系统(Win 3.x, Win95/97/98)下,各种数据类型的有关信息列表。可以用标准库函数sizeof计算数据类型的长度进行验证,参见以下实例。

/* [例2-1] 计算各种基本数据类型的字节长度*/

/* samp2_1.c */

#include

#include

void main()

{ printf( "Size of char : %d\n", sizeof(char) );

printf( "Size of short : %d\n", sizeof(short) );

printf( "Size of int : %d\n", sizeof(int) );

printf( "Size of long : %d\n", sizeof(long) );

printf( "Size of float : %d\n", sizeof(float) );

printf( "Size of double: %d\n\n", sizeof(double) );

/* 以下求Windows表示法的数据类型长度,若在Turbo C等DOS操作系统上的编译器中编译时,需将下列部分删除或注释 */

printf( "Size of CHAR : %d\n", sizeof(CHAR) );

printf( "Size of SHORT : %d\n", sizeof(SHORT) );

printf( "Size of INT : %d\n", sizeof(INT) );

printf( "Size of LONG : %d\n", sizeof(LONG) );

printf( "Size of FLOAT : %d\n", sizeof(FLOAT) );

printf( "Size of DOUBLE: %d\n\n", sizeof(DOUBLE) );

}

以上程序应用Visual C++ 编译后,运行可得以下结果:

Size of char : 1

Size of short : 2

Size of int : 4

Size of long : 4

Size of float : 4

Size of double: 8

Size of CHAR : 1

Size of SHORT : 2

Size of INT : 4

Size of LONG : 4

Size of FLOAT : 4

Size of DOUBLE: 8

上述程序去除后半部分,可直接在Turbo C2.0(DOS操作系统平台)等编译器中编译,其运行结果有些差别,int的长度与short的长度都为2个字节。

2.2 常量

一、直接常量

1.整型(int)常量。它包括:

1)10进制。例如10,20等。

2)16进制。例如0xA,0X10等。

2.长整型(long)常量。它包括:

1)10进制。例如-28l。

2)16进制。例如0x33dl。

表2.2

字符形式

功能

\n

换行

\t

横向跳格(即跳到下一个输出区)

\b

退格

\r

回车

\a

响铃

\\

反斜杠字符“\”

\’

单引号字符

\”

双引号字符

\ddd

1到3位8进制数所代表的字符

\xhh

1到2位16进制数所代表的字符

3.浮点常量。它只有10进制表达方式。

1)小数表示法。例如 -1.25,0.452

2)指数表示法。例如 6E4 (60000),3.5E-5 (0.000035)

注意,上述 x、l、e使用时大小写都可以。

4.字符常量

例如:'A', 'a'

字符常量可以用字符对应的ASCII码数字表示,即两者是等价的。如'A'等价于0X41,而'a'等价于0X61。因此语句行:

printf(“%c”,’A’)和printf(“%c”,0x41)都将在屏幕上显示字符A。

除以上形式的字符常量外,还有以“\”开头的特殊字符序列,见表2.2。

/* [例2-2] 应用特殊字符编程 */

/* samp2_2.c */

#include

#include

void main()

{ printf("\t"); /* 跳格 */

printf("\b\b\b"); /* 退三格 */

printf("\a"); /* 响铃 */

printf("\'"); /* 显示' */

printf("\""); /* 显示" */

printf("\n\n"); /* 连续换2行 */

getch(); /* 等待用户按一键(暂停)*/

/* 以下用相应的ASCII码数字字符完成相同的动作 */

printf("%c", '\x09'); /* 跳格 */

printf("%c%c%c", '\x08', '\x08', '\x08'); /* 退三格 */

printf("%c", '\x07'); /* 响铃 */

printf("%c", '\x27'); /* 显示' */

printf("%c", '\x22'); /* 显示" */

printf("%c%c", '\x0a', '\xa'); /* 连续换2行 */

getch(); /* 等待用户按一键(暂停)*/

/* 以下用相应的ASCII码数字直接完成相同的动作 */

printf("%c", 0x09); /* 跳格 */

printf("%c%c%c", 0x08, 0x08, 0x08); /* 退三格 */

printf("%c", 0x07); /* 响铃 */

printf("%c", 0x27); /* 显示' */

printf("%c", 0x22); /* 显示" */

printf("%c%c", 0x0a, 0xa); /* 连续换2行 */

getch(); /* 等待用户按一键(暂停)*/

}

5.字符串常量

"A","CHINA"

对于字符串,编译系统自动加空字符 '\0'作为字符串结尾,所以N 个字符组成的字符串常量总是分配N+1个字节的存储空间。

二、符号常量

符号常量通过#define进行定义,例如:

#define PI 3.1415926

/* [例2-3] */

/* 程序名: samp2_3.c */

#include

#define PRICE 30

void main()

{ int num, total;

num=10; total=num*PRICE;

printf("total=%d",total);

}

运行结果:

total=300

符号常量不同于变量,它的值在其作用域内不能赋值,例如:PRICE=40是不合法的。习惯上,符号常量用大写字符表达。

2.3 变量

C语言使用的标识符包括变量名、符号名、函数名、数组名、类型名等。标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为下划线或字母。标识符大小写不同,但许多编译系统有大小写敏感开关,设置其为关闭状态后,将不区分大小写。

C语言的标识符可分为以下三类:

1.关键字

标准C语言共有32个关键字,每个关键字在C程序中都代表着某一固定含意,且这些关键字都不允许作为用户标识符使用。

2.预定义标识符

这些标识符在C语言中都具有特定含意,如C语言提供的库函数名和预编译处理命令。

3.用户标识符

用户可以根据需要对C程序中用到的变量、符号常量、自己定义的函数进行命名,形成用户标识符。以下是变量定义的格式:

<存储类型> 数据类型 变量<=初值>; (“<>”内的项目有时可以省略)

例如:

static int a=1;

unsigned long d;

int x,y,sum;

char ch,key;

一、局部变量

局部变量是在函数内部定义的变量,它只在本函数范围内有效。例如:

void main()

{ int a,b;

……

}

void func();

{ int a,b;

……

}

特点:

1)程序运行过程中,局部变量并不始终占有内存空间,只有在用到它们之前才为其分配一定的存储空间。

2)局部变量仅在定义它的函数内部有效,对主函数main()也不例外,这是和PASCAL不同的。

3)在不同函数中使用相同的局部变量名是允许的,不会互相冲突。例如上面的主函数main()和子函数func()都定义了变量a和b,它们在内存中实际占用不同的单元,互不干扰。

4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也可称为“分程序”或“程序块”。例如:

void main()

{ int a,b;

……

{ int c; /* 分程序开始 */

c=a+b;

……

} /* 分程序结束 */

}

变量c只在复合语句(分程序)内有效,离开该复合语句该变量无效,释放内存单元。

局部变量的存储类型有auto(自动)、static(静态)和register(寄存器)三种。

1.自动变量(auto)

当局部变量未指明存储类型时,被自动说明成auto变量,即auto可以省略不写。自动变量是普通的局部变量。

2.静态局部变量(static)

作用域限制于定义它的函数内,静态局部变量在编绎时就分配了固定的存储单元,当程序运行离开所定义的函数时,静态变量的值得到保留,当再次进入该函数运行时,还可继续使用上一次运行保留的数值。

/* [例2-4] */

/* samp2_4.c */

#include

void add();

void main()

{ add(); add(); add();

}

void add()

{ static int x=2; /* x为静态局部变量 */

printf("%d\n",x); x=x+2;

}

运行结果:

C>samp2_4 ( 如在上述程序中去掉static,结果为:

2 2

4 2

6 2

3.寄存器变量(register)

为提高执行效率,C语言允许将局部变量的值放在运算器中的寄存器里,需要时直接从寄存器中取出参加运算,不必再到内存中去存取,这种变量叫做寄存器变量,用关键词register作说明。

/* 例[2-5] */

/* samp2_5.c */

#include

void main()

{ register int i, f = 1; /* 定义寄存器变量 */

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

f = f * i; printf( "%d ", f );

}

}

寄存器变量只能用做局部变量,它不能太多,因寄存器有限。寄存器变量太多时,编译系统将其自动转为自动变量。

有些C语言教材认为PC机上的C编译系统中,寄存器变量无作用,这一说法是错误的,有兴趣的读者可以使用第24章的方法进行分析。

二、全局变量

全局变量在函数外部定义,可为本文件中的各个函数所共用,它的作用域为定义这个变量的位置开始到本源程序文件结束处。例如:

int p=1, q=5;

void func()

{ int b,c;

……

}

char c1, c2;

void main()

{ int a,b,c;

……

}

p、q、c1、c2都是全局变量,但它们的作用域不同,在main()中函数可以使用全局变量p、q、c1、c2 ,但在func()函数中只能使用全局变量p、q而不能使用c1、c2。

如果在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用域内,全局变量不起作用。

全局变量在程序加载运行过程中,始终占用内存空间。设置全局变量的作用是增加函数间数据联系的渠道,在一个函数中改变全局变量的值,就能影响到其它函数,但全局变量不能过多使用,否则维护不方便。

必要时,可以对全局变量使用extern(外部)和static(静态)两种存储类型。

1.外部全局变量(extern)

如果全局变量在文件开头定义,则整个该源程序文件范围内都可以使用该全局变量,如果不在文件开头定义,其作用域只限于定义点到文件终点。如果在定义点之前的函数想引用该全局变量,则应该在该函数中或者在引用函数所在源文件的开头用关键词extern对该全局变量作“外部引用说明”。参见以下实例:

/* [例2-6] 判断输入整数a,b的最大值 */

/* 程序名: samp2_6.c */

#include

int max(int a, int b); /* 函数max()的说明 */

void main()

{ extern int z; /* 在全局变量z定义域外引用它 */

int x,y;

scanf("%d:%d",&x,&y);

z=max(x,y); printf("max:%d",z);

}

int z; /* 定义全局变量z, 其作用域从本行到本源程序最后一行 */

int max(int a, int b)

{ if(a>b) return a; else return b;

}

运行结果:

4:5 (

max:5

当用多个C源程序共同编译链接生成一个统一的执行文件时,若一个源程序里需要引用另一个源程序中已经定义过的全局变量(两者是同一个全局变量),则应该在引用的函数中或者在引用函数所在源文件的开头用关键词extern对该全局变量作“外部引用说明”,表示该变量在另一个源程序中定义。参见以下实例:

/* [例2-7] */

/* 源程序1:samp2_7.c */

#include

int temp(); /* 函数temp()的说明 */

extern int z; /* 引用另一个源程序temp.c中的全局变量z */

void main()

{ temp(); /* 引用另一个源程序temp.c中的子函数temp() */

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

}

/* 源程序2:temp.c */

int z;

int temp()

{ z=3; return 1;

}

为编译上述程序,在Visual C++的工程中应包括下列源程序名:

samp2_7.c

temp.c

2.静态全局变量

限制作用域于定义它的源程序中,其它源程序不能引用,即使加上extern也不行。例如,若将[例2-7]中的全局变量z在源程序2中的定义改为静态全局变量,即

static int z;

则源程序1无法对该变量进行引用,因为z的作用域仅限于源程序2中。

三、变量的初始化

变量的初始化就是在定义变量的同时,给变量赋初值。例如:

int a=3; 相当于int a; a=3;

也可以连续定义和初始化几个变量:

int i=3, k=10, a;

对于全局变量、静态变量,其初始化在整个程序编译时完成;而对于局部变量,其初始化在所在的函数执行过程中完成。

2.4 各类数值型数据之间的混合运算

实际应用中,常存在各种类型数据的混合运算,例如:

10+'a'+1.5-8265.1234*'b'

规则:不同类型的数据要先转换成同一类型,然后进行运算。转换过程是自动的,规律如图2.1所示。

double ( float

(

unsigned long ( long

(

unsigned int ( int ( char, short

图2.1

1.图中横向向左的箭头表示必定要进行的转换,如char,short型数据运算前必先转换为int型数据,float型数据在运算时一律先转换成double型。即使是两个float 型数据相加,也先转换成double型然后再相加,以提高运算精度。

2.纵向和横向向右的箭头表示运算对象为不同类型时的转换方向,注意箭头方向只表示数据类型级别的高低,由低向高转换,而不表示转换的过程。

例如:10+’a’+i*f-d/e

其中 i ─ int, f ─ float, d ─ double, e ── long

分析如下:

i*f → double

d/e → double

‘a’ → int (转换为’a’的ASCII数值0x61)

因此,最后结果为 double型数据。

/* [例2-8] 数据类型转换 */

/* samp2_8.c */

void main()

{ long k;

k=32766*10;

}

上述十分简单的程序用Turbo C2.0编译后运行结果不正确!即不等于327660。原因在于,虽然整型(int)数据32766、10和定义的长整型(long)变量k均没有越界溢出,但是32766*10=327660结果在其赋值于k之前仍然应该是整型(int)数据,显然超出了Turbo C2.0编译系统整型数据的范围。程序中的求值部分,应修改为 k=32766*10l 或者k=32766l*10l。

另一方面,上述程序在Visual C++上运行正确,为什么?

在C语言中可以使用强制类型转换以强迫表达式的值转换为某一特定类型,其一般形式是:

(类型)表达式

其中类型(type)是一个单目运算符。例如为确保表达式x/2成为double型,可以写成如下形式:

(double)x/2

2.5 运算符与表达式

一、算术表达式

+ (加),- (减),* (乘),/ (除) 。例如:a+b,2+3,x*y和a/b。

% 为取模运算符。它取得整数除法后的余数,不能用于float和double型数据。

例如:5%2,运算结果为1。

++,-- 使变量完成增1或减1的功能

++x,--y ─┐ 相同点:都使x加1, y减1。

x++,y-- ─┘ 不同点:++x,--y是先加1或减1,再使用x,y的值。

x++,y--是先使用x,y的值,再加1或减1。

例如:若i=3

1) j=++i; // i先变为4,然后j的值变为4。

2) j=i++; // j先变为3,然后i的值变为4。

3) printf("%d",++i); // i先变为4,然后在文本屏幕输出4,。

4) printf("%d",i++); // 在文本屏幕输出3,然后i的值变为4。

注意:

1.++,--只能用于变量,而不能用于常量和表达式。例如5++或(a+b)++是不合法的。

2.在表达式中包含++,--运算时,很容易出错。例如,若 i=3 ,则

(i++)+(i++)+(i++) ≠ 3+4+5=12 错误!

= 3+3+3=9,然后i变为6。 正确!

(++i)+(++i)+(++i) ≠ 4+5+6=15 错误!

= 6+6+6=18,i先变为6。 正确!但Visual C++的结果不同。

3.i+++j等价于(i++)+j。C编译系统在处理时尽可能多地自左而右将若干字符组成一个运算符。

4.printf("%d,%d,%d,%d,%d\n",i,i,++i,i++,++i);

在C语言编译系统中对函数参数的求值顺序是从右向左(与Pascal语言相反),每处理一个参数的计算就立即将结果传入函数体内部,上述语句的处理过程如下:

++i (i=4输入传入printf()内部,屏幕上显示4)

i++ (i=4输入传入printf()内部,然后i=5。屏幕上显示4,)

++i (i=6输入传入printf()内部,屏幕上显示6)

i (i=6输入传入printf()内部,屏幕上显示6)

i (i=6输入传入printf()内部,屏幕上显示6)

所以上述表达式最终输出结果应为 6,6,6,4,4;但是也有个别系统的输出有所不同,例如Visual C++ 。

5.在复合表达式或复合语句中++,--运算符均以子表达式或子句为判断操作顺序的对象,但对于下面将要介绍的复合型赋值表达式例外,例如:

int a,b,i=5,c;

a=(b=5+i++); /* 在复合型赋值表达式中, 先a=b=10, 然后i=6 */

if( ++a==b++ ) /* 先a=11, 再判断a是否等于b(显然条件不满足), 最后b=11 */

c=b++;

else c=++a; /* 先a=12, 然后c=12 */

二、赋值表达式

例如:a=3; b=(a+3)*c;

若有连续多个赋值表达式,它们之间也可以用逗号相连,但最后仍必须用分号结束,例如,上述两个赋值语句可改写为:a=3, b=(a+3)*c;

赋值号‘=’左边只能是变量,所以 3+m=6是不合法的。

赋值运算符两侧的运算对象的数据类型不同时,系统自动进行类型转换,即

char → int → long → float → double

上述右边类型的数据赋给比较靠左边类型的变量时,可能会发生截断现象,例如,浮点数赋给一个整型变量时,小数点后面的部分将被舍去(不进行四舍五入运算)。

在C语言中大量使用复合型赋值表达式,它体现了C语言表达的灵活性。例如:

a+=3; 等价于 a=a+3;

x*=y+8; 等价于 x=x*(y+8);

x%=3; 等价于 x=x%3;

a=(b=5); 等价于 a=b=5;

a=b=c=5;

a=(b=4)+(c=6); 等价于 b=4; c=6; a=b+c; 结果:a=10

a=(b=10)/(c=2); 等价于 b=10; c=2; a=b/c; 结果:a=5

c=(a=2)+(a=5); 等价于 a=2; a=5; c=a+a; 结果:a=10

a=12; a+=a-=a*a; 等价于 a=12; a-=a*a; a+=a; 结果:a=-264

三、逗号表达式

b = ( a = 3*5, a*4 ); 结果:a=15, b=60

c = ( a = 3*5, b = a*4, a ); 结果:a=15, b=60, c=15

逗号表达式的值等于其中最后一项表达式的值。例如,如果a=1,b=2,c=3,则

printf("%d,%d,%d",(a,b,c),b,c);

将在文本屏幕上输出:3,2,3

四、关系、逻辑表达式

> 大于

< 小于

>= 大于等于

<= 小于等于

== 等于

!= 不等于

z=(x==y?10:5);

上式含义:当x=y时,z=10;否则 z=5。故相当于语句:

if(x==y) then z=10; else z=5;

或者相当于语句:

if(x!=y) then z=5; else z=10;

&& 逻辑与

|| 逻辑或

! 逻辑非

例如:if( (a>b) && (i<1) ) z=10;

上式含义:当a大于b并且i<1时,令z=10。

又例如:if( ( a == 1 ) || ( b > 0 ) ) z = 10;

上式含义:当a等于1或者b大于0时,令z=10。

又例如:if( ! ( x > y ) ) z = 10;

上式含义:当x大于y不成立(也就是y大于或等于x)时,令z = 10。

五、求类型长度运算符sizeof

sizeof用于计算一种数据类型所占的字节数,例如:sizeof(double)。参见[例2-1]。

六、按位运算符

只能用于计算字符型和整型(char、short、int和long)数据。

& 按位与:运算符&对于参加运算的两个运算数进行按位与运算,如果两个相应的位都为1,则该位的运算结果为1,否则为0。注意,不可把&运算符和逻辑与&&运算符混淆。对于&&运算符,只要两边运算数为非0,运算结果为1;而对于按位与运算结果并非如此。

| 按位或:运算符 | 对于参加运算的两个运算数进行按位或运算,即,只要两个相应的位中有一个为1,则该位的运算结果为1;只有当两个相应的位都为0时,该位运算结果才为0。

例如,如果想使short型变量a的低字节全置1,高字节保持原样,可采用表达式a=a|0xff。如果想使a的高字节全置1,低字节保持原样,可采用表达式a=a|0xff00。

^ 按位异或:如果参加运算的两个运算数,相应位上的数相同,则该位的运算结果为0;不同,则运算结果为1。

~ 按位取反:它是单目运算符,运算对象出现在运算符的右边,其运算功能是把对象的内容按位取反。注意,~x不是求x的负数。例如,~1的运算结果不是-1而是-2,~8的运算结果是-9,学习过补码知识的读者很容易理解这一问题。

<<按位左移:运算对象出现在运算符的左边,右边是整型表达式,表示左移的位数,左移时,低位(右边)补0。

>>按位右移:运算对象出现在运算符的左边,右边是整型表达式,表示右移的位数,右移时,对于正数,高位(左边)补0;对于负数,高位(左边)补1。

/* [例2-9] */

/* samp2_9.c */

#include

void main()

{ unsigned char a;

/* & 按位与 */

a=0x41 & 0xC0; /* 相当于二进制 01000001 */

printf( "0x41 & 0xC0=%x\n",a ); /* AND 11000000 */

/* 01000000 = 0x40 */

/* | 按位或 */

a=0x41 | 0xC0; /* 相当于二进制 01000001 */

printf( "0x41 | 0xC0=%x\n", a ); /* OR 11000000 */

/* 11000001 = 0xC1 */

/* ^ 按位异或 */

a=0x41 ^ 0xC0; /* 相当于二进制 01000001 */

printf( "0x41 ^ 0xC0=%x\n", a ); /* XOR 11000000 */

/* 10000001 = 0x81 */

/* ^ 按位异或 */

a=0x81 ^ 0xC0; /* 相当于二进制 10000001 */

printf("0x81 ^ 0xC0%x\n",a); /* XOR 11000000 */

/* 01000001 = 0x41 */

/* ~ 按位取反 */

a= ~0x41; /* 相当于二进制 */

printf("~0x41=%x\n",a); /* NOT 01000001 */

/* 10111110 = 0xBE */

/* >> 按位右移 */

a=a>>2; /* 10111110 右移2位为 00101111=0x2F */

printf("0xBE>>2=%x\n",a );

/* >> 按位左移 */

a=a<<2;

printf("0x2F<<2=%x\n", a ); /* 00101111左移2位为 10111100=0xBC */

/* 对有符号整型数按位取反。1=00000001按位取反为11111110=-2 */

/* 8=00001000按位取反为11110111=-9 */

printf("%d,%d\n", ~1, ~8 );

}

运行结果:

0x41 & 0xC0=40

0x41 | 0xC0=c1

0x41 ^ 0xC0=81

0x81 ^ 0xC041

~0x41=be

0xBE>>2=2f

0x2F<<2=bc

-2,-9

七、运算符优先级

表2.3列出了C语言运算符的优先次序。注意,所有的运算符,除单目运算符和“?”以外,都是从左至右关联的。而单目运算符“*”、“&”、“-”和“?”是从右向左关联的。另外,从表中可以看出圆括号的运算级别是最高的,它的用处是提高它所包含运算符的优先级别。

表2.3 C语言运算符的优先次序

最高 () [ ] -> .

! ( ++ -- (type) * & sizeof

* / %

+ -

<< >>

< <= > >=

== !=

&

^

|

&&

||

?

= += -+ *= /=

最低 ,

第三章 程序控制语句

C语言中的语句共分为五类,它们是:表达式语句、函数调用语句、空语句、复合语句和控制语句。

1.表达式语句

最典型的表达式语句是由一个赋值表达式加一个分号构成的赋值语句,即:

变量=表达式;

2.函数调用语句

函数调用语句的一般形式为:

函数名(实参数);

3.空语句

空语句的一般形式为:

;

4.复合语句

复合语句的一般形式为:

{ 语句1; 语句2; ……; }

即用花括弧{}把若干条语句括起来构成一条复合语句。

上述四种语句形式都比较简单,可以通过各种实例进行学习,因此下面重点讨论控制语句的使用。控制语句有可分为条件分支语句、循环控制语句、开关语句和跳转语句等。

3.1 条件分支语句

格式: if(表达式1) 语句1

else if(表达式2) 语句2

else if(表达式3) 语句3

......

else 语句n

/* [例3-1] 根据从键盘输入的整数a,b的值, 计算a+b, a-b, a*b及 a/b */

/* samp3_1.c */

#include

void main()

{ int a,b,c;

printf( "a=" ); scanf( "%d", &a ); /* 从键盘输入整型数a */

printf( "b=" ); scanf( "%d", &b ); /* 从键盘输入整形数b */

printf( "[+,-,*,/]:" ); c=getch(); /* 从键盘输入运算符 */

if( c=='+' ) printf( "a+b=%d\n", a+b ); /* 计算a+b, 并显示结果 */

else if( c == '-' ) printf( "a-b=%d\n", a-b ); /* 计算a-b, 并显示结果 */

else if( c == '*' ) printf( "a*b=%d\n", a*b ); /* 计算a*b, 并显示结果 */

else if( c == '/' ) printf( "a/b=%d\n", a/b ); /* 计算a/b, 并显示结果 */

else printf( "error!\n" ); /* 其它情况, 报错 */

}

3.2 循环控制语句

一、for语句

格式:for(表达式1;表达式2;表达式3) 循环体

其中:

表达式1:初值,赋值表达式。

表达式2:控制条件,条件表达式,其值为TRUE(非0值)和FALSE(0值)两种。

表达式3:增量,赋值表达式。

它的执行步骤如下:

1.计算表达式1。

2.计算表达式2,若值为非0,执行3);若值为0,执行6)。

3.执行循环体语句。

4.计算表达式3。

5.转向2)执行。

6.结束循环,执行for循环后的语句。

/* 例[3-2] 用ASCII码数值显示A到Z的26个字母*/

/* samp3_2.c */

#include

void main()

{ char i;

for(i=65;i<=90;i++) /* 以A到Z字母的ASCII码进行循环 */

printf("%c",i); /* 显示大写字母, 如果将%c改变为%d将显示数字 */

}

for语句还可同时对多个变量进行操作,参见以下实例。

/* 例[3-3] 显示A到Z等26个字母和它们相应的ASCII码数值以及顺序号 */

/* samp3_3.c */

#include

void main()

{ char i,j;

for(j=1,i=65;i<=90;j++,i++)

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

}

在for语句中, 除分号不能省略外, 任何一个表达式都可省略。

/* 例[3-4] 计算1,2,3,...100之和 */

/* samp3_4.c */

#include

void main()

{ int i=1,j=0;

for(;i<=100;)

{ j+=i; /* 相当于j=j+i; */

i++; /* 循环变量加1 */

}

printf( "%d", j ); /* 显示1~100之和 */

}

特别地,用for(;;)可以表示无限循环。

二、while语句 ── 前置条件循环语句

格式: while(条件表达式) 循环体

while语句执行时,首先对测试条件进行检查,若循环条件不具备,它可以一次循环都不进行,这是就是所谓前置条件循环语句。前述的for语句也是前置条件循环语句的一种。

/* 例[3-5] 不断要求从键盘输入字符并回显, 直到键入字符'.' */

/* samp3_5.c */

#include

void main()

{ char c='\0'; /* 赋c的初值 */

while(c!='.')

c=getche(); /* 读入字符'.'后, 才退出循环 */

}

与for(;;)语句相似,while(1)也是一种无限循环表达,其中1可以用其它非零常量代替。

三、do ... while语句 ── 后置条件循环语句

格式:do 循环体 while (条件表达式)

首先执行循环体操作,然后再进行循环控制条件判断,所以它至少执行一次循环体。

/* 例[3-6] 计算10! */

/* samp3_6.c */

#include

void main()

{ int a=1; long b=1;

do { b *= a; /* 相当于b=b*a; */

a++;

} while( a <= 10 ); /* 若a<=10, 继续循环 */

printf( "10!=%ld\n", b ); /* 显示长整型数结果b */

}

3.3 开关语句与跳转语句

一、switch(开关)语句

格式: switch (表达式)

{ case 判断值1:

语句组1;

break;

case 判断值2:

语句组2;

break;

......

default:

语句组;

break;

}

/* [例3-7] 分别计算数字、空白符和其它字符的数目 */

/* samp3_7.c */

#include

void main()

{ int c, i, nwhite, nother, ndigit[10];

/* 清保存0到9共10种数字的数组元素为0 */

for( i = 0; i < 10; i++ ) ndigit[i] = 0;

/* 分别清保存空白字符和其它字符的变量nwhite和nother为0 */

nwhite = nother = 0;

while(( c = getchar() ) != '\n') {

switch(c) {

case '0':

case '1':

case '2':

case '3':

case '4':

case '5':

case '6':

case '7':

case '8':

case '9':

ndigit[c-'0']++; /* 数字字符计数加1 */

break;

case ' ':

case '\t':

nwhite++; /* 空白字符计数加1 */

break;

default:

nother++; /* 其它字符计数加1 */

break;

}

}

printf("digits = ");

for( i = 0; i < 10; i++ ) printf(" %d", ndigit[i] );

printf(", white space = %d, other = %d\n", nwhite, nother );

}

运行结果:

183758275 jkaqit123(

digits = 0 2 2 2 0 2 0 2 2 0, white space = 3, other = 6

二、goto(无条件转移)语句

程序设计方法学认为程序中的goto语句是一种有害的结构,应尽可能少用,一个使用goto语句较为合理的方法是从多层循环中退出,如

for(...) {

for(...) {

while(...) {

if(...) goto stop; /* 当条件具备时, 跳到stop处执行 */

......

}

}

}

stop: /* stop为跳转位置标号 */

printf("error in program!\n");

注意:

1.跳转位置标号可以和变量相同,但与函数名不能重复,但某些系统无这一限制。

2.goto语句的目的地只能在同一函数内,也就是goto语句的使用范围局限于某一函数内。

3.不允许从循环的外部跳转到循环语句的内部。

三、break(中断)语句

/* 例3-8 计算1,2,3,...100之和 */

/* samp3_8.c */

#include

void main()

{ int i,j=0;

for(i=1;;i++)

{ if(i>100) break; /* 当i大于100时退出循环 */

j+=i; /* j=j+i */

}

printf("%d",j); /* 显示结果j */

}

运行结果:

5050

四、continue(继续)语句

跳过continue下面的语句, 强行执行下一次循环.

/* 例3-9 显示键入字符的下一个字符(如键入A, 则显示B), 当键入'$'时退出. */

/* samp3_9.c */

#include

void main()

{ char done,ch;

done=0; /* 赋done的初值 */

while(!done) { /* 当done=0(伪)时, 继续循环 */

ch=getch();

if(ch=='$') {

done=1; /* 令done=1(真) */

continue; /* 强行执行下一次循环 */

}

putchar(ch+1); /* 显示字符ch的下一个字符 */

}

}

五、return(返回)语句

函数经常要通过return语句返回到它的调用处,并将函数值传回给调用它的程序,参见[例1-1]中的max函数,max函数的运行结果z值正是通过return语句返回给主函数中的变量c。

如果一个不返回任何值的return语句处在函数的最低位置,则return本身也可省略。本章中的实例基本上是属于这种情况。

第四章 数组

数组是有序数据的集合,它的每一个元素都属于同种数据类型。用一个统一的数组名和下标来唯一地确定数组中的元素。

4.1 一维数组

一、定义

定义格式:类型说明符 数组名[常量表达式];

例如:int a[10];

其中数组名为a,有a[0]~a[9]10个int型元素。又例如:

static double s[10];

其中数组名为s,有s[0]~s[9]10个静态double型元素。

数组大小用常量表达式定义,不能包含变量,即不能动态定义。例如:

int n; n=10; int a[n]; ...

是错误的!

二、引用

数组必须先定义,然后使用。C语言规定只能逐个引用数组元素而不能一次引用整个数组元素。

/* [例4-1] 一维数组元素的引用。*/

/* samp4_1.c */

#include

void main()

{ int i, a[10]; /* 定义整型变量i和数组a */

for(i=0;i<=9;i++) a[i]=i; /* 使a[0]( a[9]的值为0(9 */

for(i=9;i>=0;i--) printf("%d ",a[i]); /* 逆序输出数组元素之值 */

}

运行结果:

9 8 7 6 5 4 3 2 1 0

三、初始化

可以用赋值语句或输入语句使数组中的元素得到值,但占用运行时间。而所谓数组的初始化是使数组在编译阶段得到初值。

标准C语言规定只有静态变量和全局变量的数组才能初始化。

例如:static int a[11]={0,1,2,3,4,5,6,7,8,9,10};

注意:

1.可以只给一部分元素赋值,如static int a[10]={0,1,2,3}; 给出前4 个元素的值,后6个元素值为0。

2.如不进行初始化,系统会对所有元素赋以0值,但也有些系统是赋随机数。

3.在对全部数组元素赋初值时,可以不指定数组长度,而由编译系统自动确定。

例如:static int a[]={0,1,2,3,4,5,6,7,8,9,10}; /* 显示数组长度为11 */

4.很多C编译系统,如Turbo C、Boland C++、Visual C++等,在初始化数组时,可以省略static。

5.在函数内部的局部数组变量,也可以采用上述初始化写法,但其数组元素值实际上是在运行阶段得到的。

/* 例[4-2] 用数组来处理Fibonacci数列问题

Fibonacci数列: 1,1,2,3,5,8,... 即

F1=1 (n=1)

F2=1 (n=2)

Fn=Fn-1+Fn-2 (n>=3)

打印该数列的前20个数.

*/

/* samp4_2.c */

#include

void main()

{ int i;

static int f[20]={1,1};

for(i=2;i<20;i++) f[i]=f[i-2]+f[i-1];

for(i=0;i<20;i++) {

if(i%5==0) printf("\n"); /* 每行输出5个数据后换行 */

printf("%12d",f[i]);

}

}

运行结果:

1 1 2 3 5

8 13 21 34 55

89 144 233 377 610

987 1597 2584 4181 6765

4.2 二维数组

一、定义

格式:类型说明符 数组名[常量表达式] [常量表达式];

例如:float a[3][4];

定义a为3(4(3行4列)的单精度实型数组。可以把a视为一个一维数组,它有3个元素:a[0],a[1]和a[2],每个元素又是一个包含4个元素的一组数组。即:

a[0]: a[0][0] a[0][1] a[0][2] a[0][3]

a[1]: a[1][0] a[1][1] a[1][2] a[1][3]

a[2]: a[2][0] a[2][1] a[2][2] a[2][3]

它们在内存中的存储顺序为:

a[0][0],a[0][1],a[0][2],a[0][3],a[1][0],a[1][1],a[1][2],a[1][3],a[2][0],a[2][1],a[2][2],a[2][3]。故其最后一个元素为 a[2][3]。

多维数组与二维数组的处理相似。

二、初始化

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

{5,6,7,8},

{9,10,11,12}};

或 static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};

上述两种方法中,第1种表达更清楚。也可以只对部分元素赋初值,如

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

等价于:static int a[3][4]={{1,0,0,0},{5,0,0,0},{9,0,0,0}};

static int a[3][4]={{1},{0,6},{0,0,11}};

等价于:static int a[3][4]={{1,0,0,0},{0,6,0,0},{0,0,11,0}};

static int a[3][4]={{1},{},{9}};

等价于:static int a[3][4]={{1,0,0,0},{0,0,0,0},{9,0,0,0}};

下列情况下定义数组时对第1维的长度可以不指定,但第2维的长度不能省。

1.如果对全部元素都赋初值。

static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};

编译系统会根据总个数分配空间,由于一共12个数据,每行4个元素,故可确定共有3行。

2.用{ {},{},{} }时。

static int a[][4]={{0,0,3},{},{0,10}};

以上的写法能通知编译系统,数组共有3行。

ú

û

ù

ê

ë

é

6

5

4

3

2

1

/* 例[4-3] 将一个二维数组行和列元素互换,存到另一个二维数组中。

*/

/* samp4_3.c */

#include

void main()

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

static int b[3][2], i,j;

printf(“array a:\n”);

for(i=0;i<=1;i++) {

for(j=0;j<=2;j++) {

printf(“%5d”,a[i][j]); b[j][i]=a[i][j];

}

printf(“\n”);

}

printf(“array b:\n”);

for(i=0;i<=2;i++) {

for(j=0;j<=1;j++) printf(“%5d”,b[i][j]);

printf(“\n”);

}

}

运行结果如下:

array a:

1 2 3

4 5 6

array b:

1 4

2 5

3 6

4.3 字符数组

一、引用

用来存放字符数据的数组是字符数组,字符数组中的每一个元素存放一个字符。字符数组实际上仍属于一维数组、二维数组或多维数组的范畴,只是它们用于存放字符数据。

/* 例[4-4] */

/* samp4_4.c */

#include

void main()

{ static char c[10]={'I',' ','a','m',' ','a',' ','b','o','y'};

int i;

for(i=0;i<10;i++) printf("%c",c[i]);

printf("\n");

}

运行结果:

I am a boy

二、字符串

在C语言中,将字符串作为字符数组来处理。C语言规定了一个“字符串结束标记”即以字符’\0’代表字符串的结尾,但它不包括在字符串有效长度内。以下是字符串”China”表达的三种方法。该字符串有效长度为5,但占6个字节的内存空间。

static char a[]={'C','h','i','n','a','\0'};

static char a[6]="China";

static char a[]="China";

第五章 指针

指针是C语言中的一个重要概念,也是C语言的重要特色和精华。它可以表达复杂的数据结构,能够动态分配内存,能方便地使用字符串和数组。

5.1 定义与引用

指针变量:若把记录数据的内存地址保存在一个变量中,那么这个变量被称为指针变量。指针变量占用存储单元,保存数据地址,而不是数据本身。在PC机上基于Windows操作系统的C编译系统(例如Visual C++)中,通常指针占4个字节,实质上,它相当于int数据。基于DOS操作系统的C编译系统(例如Turbo C2.0)中,通常指针占2个字节,特殊地,远程指针占4个字节(段地址和偏移地址各占2个字节)。

与指针相关的运算符有两个:

1.& 取地址运算符。例如,&i为取变量i的地址。

2.* 指针运算符。它有两种应用方式:

1) 指针变量定义。例如,int *ptr1将定义指向int对象的指针变量ptr1。

2) 用指针变量值做地址间接访问它所指引的对象。例如,*ptr1为用指针变量ptr1做地址取它所指引的对象之值。

short i,j;

double k;

short *ptr1; /* 定义变量ptr1, 用来保存指向short对象的地址 */

double *ptr2; /* 定义变量ptr2, 用来保存指向double对象的地址 */

*ptr1 = 3; /* 非法, ptr1现在指向随机地址,没有指向确定空间, 此语句必须删除 */

ptr1 = &i; /* ptr1等于变量i的地址*/

ptr2 = &k; /* ptr2等于变量k的地址*/

*ptr1 = 3; /* 合法, ptr1已指向确定空间i */

*ptr2 = 9.0

j = 6;

若上述变量i、j、k、ptr1、ptr2 的内存地址分别为0x0064fde4至0x0064fdf4,则执行上述语句后的内存映象如图5.1所示。

上例中的第一条*ptr1 = 3语句执行时,ptr1指针变量刚定义,是一个随时数(代表一个随机地址),并没有指向确定的自由空间。另一方面,C编译系统中并不对指针的指引位置做安全性检查,所以这类程序语句将能成功编译,但一般会有警告信息提示。执行时若随机地址恰好是未分配的自由内存区,程序将能正确执行;反之,程序将出现错误,甚至造成系统崩溃。这种问题只能靠程序员自已来解决。

/* 例[5-1] */

/* samp5_1.c */

#include

void main()

{ int a, b, *ptr1, *ptr2;

a=100; b=10;

ptr1=&a; ptr2=&b; /* 指针指向a,b变量, 以便下面能正确引用 */

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

printf("%d,%d\n", *ptr1, *ptr2);

}

总之,在使用指针时,一定注意安全性问题,要确保指针变量值指向的是正确的空间。否则,将发生错误,甚至引起系统崩溃。

5.2 一维数组与指针的关系

指针与数组间有极密切的关系,实质上,数组在编译系统内部也是作为指针处理的。

C语言规定数组名是指向第0号元素的常量指针。

p=a(&a[0] a[0] int a[10],*p;

a[1] p=&a[0]; (或写成 p=a;)

... ...

a[8]

a[9]

图5.2

这里,a是数组名,它是指向数组a第0号元素的常量指针,常量指针由于其地址固定不变,故不能移动,因此a++是不合法的;而指针变量则可以移动,因此p++是合法的。p++的结果使得p将指向数组的下一个元素。需要指出的是:

1.*p++、*(p++)和*p--、*(p--)的作用是先得到p指向的变量的值(即*p),然后再使p=p+1或p=p-1。

2.*++p、*(++p)和*--p、*(--p)的作用是先使p=p+1或p=p-1,然后再得到p指向的变量的值(即*p)。

3.(*p)++ 的意义与* p++ 不同,它表示p所指向的元素加1,即(*p)加1,请注意括号的位置。

/* 例[5-2] */

/* samp5_2.c */

#include

#define SIZE 50 /* 定义符号常量SIZE代表50 */

void copy(char *a, char *b); /* 函数copy()的申明 */

void main()

{ char s1[SIZE], s2[SIZE]; /* 定义字符数组s1[0]~s2[49],s2[0]~s2[49] */

printf("s1:"); scanf("%s",s1); /* 从键盘上输入一字符串, 将其赋给s1指向的空间 */

copy(s1,s2); /* 调用子函数copy将s1指向的字符串拷贝给s2指向的空间*/

printf("s2:%s\n",s2); /* 屏幕打印s2指向的字符串 */

}

void copy(char *a, char *b)

{ while(( *b++=*a++ ) != '\0' );

}

上面程序中 while((*b++=*a++)!='\0'); 等价于

while(1) {

*b=*a;

if(*b=='\0') break;

b++;a++;

}

对于数组s1[SIZE]和s2[SIZE],其名字s1和s2为指针常量,所以以下表达都是错误的。

s1++; s2++; s1=s2;

5.3 字符串指针

在C语言中,可用两种方法实现一个字符串。

1.用字符数组实现。例如:

static char string[]={"I love China"}; /* 数组初始化 */

printf("%s\n",string);

2.用字符指针实现。例如:

static char *string="I love China"; /* 指针初始化 */

上述两种方法在用法上有区别:

1.写法不一样,请仔细比较。

2.赋值方式不同。例如对于数组:

char str[14];

str="I love China"; /* 非法,指针常量不能改变 */

strcpy(str, "I love China"); /* 合法,将字符串拷贝到数组中 */

将内存中存储的字符串"I love China"共13个字节(包括结尾标记’\0’) 拷贝到字符数组str中。

但是对于指针:

char *a;

a="I love China"; /* 合法,因为a为变量 */

实质上是将内存中存储的字符串"I love China"的地址赋给a(4字节)。

3.初始化问题。例如:

char str[14];

scanf("%s",str); /* 合法,str有确定空间,可保存13个字符(不包含’\0’) */

但是:

char *a; /* 定义变量a用来存储字符数据的地址, 即字符型指针 */

scanf("%s",a); /* 非法,a没有指向确定的自由空间 */

这里,指针变量a刚定义,是一个随时数(代表一个随机地址),因此需要在语句scanf(“%s”,a)之前使指针a指向确定的自由空间。

/* [例5-3] 将字符串数字转换成整型数据 */

/* samp5_3.c */

#include

#include

void main()

{char *s="123456"; /* 初始化数字字符串 */

int a = atoi( s ); /* 将数字字符串转换成整型数据 */

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

}

/* atoi: 转换字符串s成为整型数 */

int atoi(char *s)

{int n, sign; /* n记录数据,sign记录其符号 */

for(; isspace(*s);s++);

sign = (*s == '-' ) ? -1 : 1;

if( *s == '+' || *s == '-' ) s++; /* 若s为符号字符,则跳过它 */

for( n = 0; isdigit(*s); s++) /* 若*s是数字,则进行for循环 */

n = 10 * n + *s - '0'; /* 将ASCII码数字字符转化为数字 */

return sign * n; /* 在数字前加上符号位后返回 */

}

5.4 指针内存分配

在指针使用方面,初学者最常犯的错误之一就是将指针“拿来就用”。

在定义指针变量(例如int *p)后,指针是指向一个不确定(随机)的内存空间,这个不确定空间很可能是被操作系统等资源所占用的区域。因此若对该区域施以写操作,将可能导致严重的后果。例如:

short a, *k; /* 定义short型变量a和指针k */

若直接应用:

*k=99; /* 不合法, 将可能导致严重后果 */

必须先令:

k=&a; /* 使k指向short型变量a */

然后才可令:

*k=99; /* a=99 */

在对任何指针变量所指引区域进行读写操作之前,必须确保它指向正确的空间位置。参见图5.3,假设变量a的地址在0x0064fde4处,语句k=&a使k等于变量a的地址,即0x0064fde4,这样k已指向程序定义的自由空间──变量a,接下来语句*k=99将以指针变量k的值做地址,将99写到该地址对应的内存区,相当于使变量a=99。

以上先使指针变量k指向已占据自由内存空间的变量a,然后再对指针所指引的空间进行操作,实现对变量a的间接访问。此外,在C语言中还有另一种有效的方法,这就是动态分配内存技术,它常使用以下标准C库函数:

void *malloc(unsigned int size);

功能:分配并返回大小为size字节的内存区域的首字节地址;如果分配不成功,返回0。

void free(void *ptr);

功能:释放以前用malloc函数动态分配给指针ptr所指向的内存区域。

/* 例[5-4] */

/* samp5_4.c */

#include

#include

#include

#define SIZE 6

void main()

{ char *s;

s=(char *)malloc(SIZE); /* 动态分配SIZE个字节内存给指针s */

strcpy(s,"world"); /* 将字符串"world"拷贝到指针s指向的内存区 */

printf("%s",s); /* 在屏幕上显示指针s 指向的字符串 */

free(s); /* 释放用malloc函数为指针s分配的内存空间*/

}

运行结果:

world

5.5 多维数组与指针的关系

一、多维数组的理解

在多维数组与指针的关系中有些概念比较难懂,要反复思考,以二维数组为例。例如:

static short a[3][4]={{1,3,5,7},

{9,11,13,15},

{17,19,21,23}};

可以这样理解:a数组包含三个元素:a[0]、a[1]、a[2]。而每个元素又是一个一维数组,分别包含4个元素,例如,a[0]所代表的一维数组包含4个元素:a[0][0]、a[0][1]、a[0][2]、a[0][3]。

数组名a代表整个二维数组的首地址,也就是第0行的首地址。a+1代表第1行的首地址,a+2代表第2行的首地址。在本例中,假若首地址a为0x00410a30,则a+1和a+2分别为0x00410a38和0x00410a40,如图5.4所示。

a[0]、a[1]、a[2]既然是一维数组名,而C语言又规定数组名代表数组的首地址,因此a[0]代表第0行中第0列元素的地址,即&a[0][0],同理a[1]是&a[1][0],a[2]是&a[2][0]。

另一方面,既然a[0]、a[1]、a[2]分别是二维数组的第0、1、2行,则它们的地址&a[0]、&a[1]、&a[2]分别等价于a、a+1、a+2;并可由此推理出*a、*(a+1)、*(a+2)等价于a[0]、a[1]、a[2]。

表5.1

a

首地址

0x00410a30

a+0

a+1

a+2

0行地址

1行地址

2行地址

0x00410a30

0x00410a38

0x00410a40

&a[0]

&a[1]

&a[2]

a[0]

a[1]

a[2]

0行0列地址

1行0列地址

2行0列地址

0x00410a30

0x00410a38

0x00410a40

&a[0][0] *(a+0)

&a[1][0] *(a+1)

&a[2][0] *(a+2)

/* 例[5-5] */

/* samp5_5.c */

#include

#define FORMAT "%x, %x\n"

void main()

{ static short a[3][4]={1,3,5,7, 9,11,13,15, 17,19,21,23};

printf(FORMAT,a,*a);

printf(FORMAT,a[0],*(a+0));

printf(FORMAT,&a[0],&a[0][0]);

printf(FORMAT,a[1],a+1);

printf(FORMAT,&a[1][0],*(a+1)+0);

printf(FORMAT,a[2],*(a+2));

printf(FORMAT,&a[2],a+2);

printf(FORMAT,a[1][0],*(*(a+1)+0));

}

参考运行结果:

410a30, 410a30 (地址值)

410a30, 410a30 (地址值)

410a30, 410a30 (地址值)

410a38, 410a38 (地址值)

410a38, 410a38 (地址值)

410a40, 410a40 (地址值)

410a40, 410a40 (地址值)

9, 9, (第1行第0列元素)

对于上述地址值,不同的机器可能得到不同的结果,但其相对值应当不变。另外,对于以上实例,下列结果成立。

*(*(a+1)+7)=23

*(a[1]+1)=11

*(&a[1]+4)=指向第5行第0列的地址值(已越界)

二、指向数组元素的指针变量

/* 例[5-6] */

/* samp5_6.c */

#include

void main()

{ static int a[3][4]={1,3,5,7, 9,11,13,15, 17,19,21,23};

int *p; /* 定义指向整型数据的指针, 以下用它来索引数组元素 */

for(p=a[0];p

{ if((p-a[0])%4 == 0) printf("\n");

printf("%4d",*p); /* 在屏幕上显示指针p指引的内容 */

}

}

运行结果:

1 3 5 7

9 11 13 15

17 19 21 23

三、指向一维数组的指针变量

当普通的指针变量p指向数组时,p加1将指向原来p指向的元素的下一元素;而指向长度为m的一维数组的指针变量p,p的增值以一维数组的长度为单位,参见图5.5。指向一维数组的指针变量与二维数组关系十分紧密,请观察以下实例,其中int (*p)[4]是指向长度为4的一维整型数组的指针变量,p、p+1、p+2分别指向二维数组的0、1、2行。

/* 例5-7 */

/* samp5_7.c */

#include

void main()

{ static int a[3][4]={ {1,3,5,7},

{9,11,13,15},

{17,19,21,23}};

int (*p)[4]; /*p为指向长度为4的一维数组指针 */ 9

int i,j; p=a;

scanf("i=%d,j=%d",&i,&j);

printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));

}

运行结果:

i=1,j=2 (

a[1,2]=13

上例中,*(p+2)是数组a第2行0列的地址,*(*(p+2)+3)是数组a第2行3列地址所指引的内容, 即a[2][3],而*((p+2)+3)则是数组a第5行0列的地址,对本例它已超界。

5.6 指针数组和指向指针的指针

一、指针数组

例如:char *p[10];

上述指针数组定义有10个元素,每个元素都是指向char型数据的指针,与普通指针相同,每个指针占4字节内存,共占40字节。char *p[10]与char (*p)[10]不同,后者是指向长度为10的一维数组的指针,只是一个指针,占4字节内存。

/*例5-8*/

/* samp5_8.c */

#include

#define SIZE 6

#define LINE 30

void main()

{ char *a[SIZE]; /* 定义指针数组, 每一个元素都是指针 */

int i;

for(i=0;i

{ a[i]=(char *)malloc(LINE); /* 为a[i]动态分配内存, 在此之前,a[i]指向的是不确定空间 */

scanf("%s",a[i]); /* 从键盘输入字符串给a[i]所指向的空间 */

}

for(i=0;i

printf("%d %s\n",i,a[i]); /* 屏幕打印i,a[i] */

}

上述处理与二维数组方法相似,说明指针数组与多维数组密切相关,但两者是有区别的,例如考虑以下数组:

二维字符数组 指针数组

char city[3][9]={ char *city[]={

"Beijing", /* city[0]占9字节 */ "Beijing", /* city[0]占8字节*/

"Shanghai", /* city[1]占9字节 */ "Shanghai", /* city[1]占9字节*/

"Hefei" /* city[2]占9字节 */ "Hefei" /* city[2]占6字节*/

}; };

对于二维数组city[3][9],不管它的存储内容如何,它总是占用3*9=27字节空间;而指针数组*city[]则可以针对具体情况自动进行调节,它共占用8+9+6=23字节空间。显然在本例中使用指针数组更为合理。

/* [例5-9] */

/* samp5_9.c */

#include

void main()

{ char *city[]={

"Beijing", /* city[0] */

"Shanghai", /* city[1] */

"Hefei" /* city[2] */

};

int i; char *p;

for(i=0;i<3;i++) { p=*(city+i); printf("%s\n",p); }

}

运行结果:

Beijing

Shanghai

Hefei

二、多级指针

多级指针就是指向指针(地址)的指针(地址)。在一般情况下,指针的值是这个变量的地址,地址的内容是变量的值,在指向指针的指针中,第一个指针的值是第二个指针的地址,第二个指针是这个变量的地址,而这个地址的内容是这个变量的值。前面介绍过的指针数组、二维以上的数组实际上都是指向指针的指针。

多级指针是一种多重间接访问数据的形式,多重间接访问数据的方式根据需要还可以进一步延伸,但很少使用超过两级指针以上的情况。

/* [例5-10] 两级指针的应用 */

/* samp5_10.c */

#include

void main()

{ int x, *p, **q; /* q为二级指针变量 */

x=10; p=&x; q=&p;

printf("%d \n", **q ); /* 显示x的值 */

printf("%x\n", p ); /* 显示x的地址p */

printf("%x\n", q ); /* 显示x的地址的地址q */

}

运行结果:

10

64fdec

64fdf0

第六章 函数

函数是指按一定的格式排列并能独立执行某种任务的代码块,它是C 语言的基本构件。从使用角度分类,函数包括:

1.库函数

2.用户自定义函数:

1)main函数

2)用户定义子函数

C程序从main()函数开始,调用各种函数,最后流程回到main()函数,在main()函数中结束整个程序的运行。

6.1 函数的定义

格式1:

<存储类型> <数据类型> 函数名(<形式参数说明>)

{ 函数体 1)说明部分

2)执行部分

}

例如:

int max(int x, int y) /* x,y为形式参数 */

{ int z;

z=x>y?x:y;

return (z);

}

格式2:

<存储类型> <数据类型> 函数名(<形式参数>)

<形式参数说明>

{ 函数体 1)说明部分

2)执行部分

}

例如:

int max(x,y) /* x,y为形式参数 */

int x,y; /* 形式参数类型说明,无顺序要求 */

{ int z;

z=x>y?x:y;

return (z);

}

说明:

1.存储类型:

1)外部类型extern。它是缺省类型,即函数的隐含类型为外部类型,外部函数可以为属于同一执行文件的其它源程序所调用。

2)静态类型static。只能为同一源程序文件的其它函数所调用。

2.数据类型:定义通过return语句所返回的函数数据类型。若为int型时,可以省略。当函数只完成特定操作而不需返回数据值时,可用类型名void。

3.函数名:函数名的定义与变量一样,必须是一个合法的标识符,但函数名本身不是变量,不能将其看成存放结果的变量名。

4.函数的定义在程序中都是平行的,不允许在一个函数内部再定义另一个函数。

5.形式参数。在定义函数时函数名后面括弧中的变量名称为“形式参数”,简称“形参”。与它对应的有“实参”,在调用函数时,函数名后面括弧中的表达式称为“实参”。

关于形参与实参的说明:

1)形参只有在发生函数调用时才被分配内存单元, 在调用结束后, 形参所占的内存单元也被释放,因此它在函数内部实际是局部变量。

2)实参可以是常量、变量或表达式,如max(3,a+b),但要求有确定值。在调用时将实参值传给形参。

3)在定义函数过程中,必须指定形参的类型。

4)新的ANSI标准推荐使用上述格式1的方法对形式参数作说明,即在列出形参列表时,同时说明形参类型,例如int max(int x, int y)。

5)实参与形参的类型应一致,否则编译时会发生类型不匹配错误,但字符型与整型可以互相通用。

6)C语言规定,实参对形参的数据传递是“单向传递”。

6.2 函数的说明

函数除定义代码外,还必须在调用前方进行说明(申明),其格式为:

<存储类型> 数据类型 函数名();

/* 例[6-1] */

/* samp6_1.c */

#include

float add(float x, float y);

void main()

{ float a,b,c;

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

c=add(a,b);

printf("sum is %f",c);

}

float add(float x, float y)

{ float z;

z=x+y;

return (z);

}

运行结果:

3.6,5.5 (

sum is 9.100000

主函数上方的float add()语句是对被调用函数add做说明。“定义”与“说明”不是一回事。

1.“定义”是指对函数功能的确立,包括指定函数名,函数返回值类型、 形参及其类型、函数体等,它是一个完整的、独立的函数单位。

  2.“说明”是在主调函数中对已定义的函数返回值进行类型说明(或称"申明"),它可以只包括函数名、函数类型以及一个空的括弧,也可在括弧中包括形参及其类型。

省略说明的情况:

  1.若函数返回是普通整型(有符号)时,可以不必进行说明,系统对它自动按普通整型处理。

2.如果被调用函数的定义出现在调用之前,可以不必加以说明。因为编译系统已经先知道了已定义的函数类型,会自动进行处理。

函数的说明对库函数也不例外,若用了内存操作函数void *memccpy(),必须在文件头部加上:#include,因它内部包括函数void *memccpy()的说明。而应用了库函数int printf()或int scanf()后,可在文件头部加上:#include;但也可以省略,因为函数printf()和scanf()是int型的。

6.3 函数的调用

C语言规定,实参对形参的数据传递是“单向传递”。它又可进一步分为传值调用和传址调用。

1、 传值调用(把数据的本身作为实际参数传递给形式参数)

特点:由于数据在传递方和被传递方占用不同的内存空间,所以被传递的数据在被调用的函数中无论如何变化,都不会影响在主调函数中的实参值,数据本身保持不变。

/* 例[6-2] */

/* samp6_2.c */

#include

long jc(); /* 函数long jc()说明 */

void main()

{ int k; long i; k=10 实参

k=10;

i=jc(k); /* 实参值为10 */ ( 单向传递x(10

printf("%d!=%ld",k,i);

} x 形参

long jc(int x)

{ long z=1; x的最终值0并不传回给k

while(x>0) { z=z*x; x--; }

/* 函数返回时,形参x的值为0 */ 图6.1

return z;

}

运行结果:10!=3628800

显然单向传值的结果,k值保持不变,始终为10。

二、传址调用

特点:由于使用地址作为传递参数,因此数据无论是在主调函数中,还是在被调用函数中都使用同一内存空间,所以被调用函数中在该内存空间做出某种变动后,必然会影响到使用该空间的调用函数中的变量值。应该指出的是,传址调用与传值调用从本质上讲是相同的,即所传递的参数都保持不变,对于传址调用是地址保持不变;而对于传值调用是数据本身保持不变。

/* 例[6-3] 交换x=10,y=20的值*/

/* samp6_3.c */ 0x0064fdf4 10 x

#include

void swap(int *x1, int *y1); 0x0064fdf0 20 y

void main()

{ int x,y ; ( 单向传递x1(0x0064fdf4 (&x)

x=10; y2(0x0064fdf0 (&y)

y=20; &x,&y与x1,y1指向同一内存区

swap(&x,&y); /* 实参值为x,y的地址 */

/* 显示变量结果值*/ 0x0064fde8 0x0064fdf4 x1

printf("x=%d, y=%d\n", x, y) ;

} 0x0064fdec 0x0064fdf0 y1

void swap(int *x1, int *y1)

{ int temp; 图6.6

temp = *x1;

*x1=*y1;

*y1=temp;

}

运行结果:

x=20, y=10 交换结果

由于单向传递的是地址值x1=&x,y1=&y,所以可通过改变它们共同指向的内存区中的数据,达到交换变量x,y值的目的。例[6-3]在不同机器上运行时,结果中的地址值一般是不相同的,但形参与实参值肯定是相等的。

由于数组名是常量指针,所以可以用数组名作函数实参或形参。

/* 例[6-4] 输入3个浮点数值,并求其平均值 */

/* samp6_4.c */

#include

float average(float array[3])

/* 形参数组长度无意义,可改为float array[];等;也可改用指针float *array; */

{ int i; float aver, sum=0;

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

sum=sum+array[i]; /* 可改为指针用法 sum=sum+*(array+i); */

aver=sum/3;

return (aver);

}

void main()

{ float score[3],aver;

int i;

printf("input 3 scores: ");

for(i=0;i<3;i++) scanf("%f",&score[i]);

aver=average(score);

printf("average score is %5.2f",aver);

}

运行结果如下:

input 3 scores: 100 76 98.5 (

average score is 91.50

说明:

1.用数组元素作为函数参数,与普通变量做函数参数的用法相同。

2.用数组名作函数参数,应该在主调函数和被调用函数中分别定义数组, 例中array是形参数组名,score是实参数组名,分别在其所在函数中定义,不能只在一方定义。

3.实参与形参数组类型应一致,否则将出错。

4.实参与形参数组大小可以不一致,甚至可以不指定它的大小。C编译器不检查数组大小,只是将实参