概述
C源程序的结构特点
- 一个C语言源程序可以由一个或多个源文件组成。
- 每个源文件可由一个或多个函数组成。
- 一个源程序不论由多少个文件组成,都有一个且只能有一个 main 函数,即主函数。
- 源程序中可以有预处理命令(include 命令仅为其中的一种),预处理命令通常应放在源文件或源程序的
最前面。 - 每一个说明,每一个语句都必须以分号结尾。但预处理命令,函数头和花括号“}”之后不能加分号。
- 标识符,关键字之间必须至少加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。
C语言词汇
在C语言中使用的词汇分为六类:标识符,关键字,运算符,分隔符,常量,注释符等。
- 标识符 在程序中使用的变量名、函数名、标号等统称为标识符。除库函数的函数名由系统定义外,其余都由用户自定义。C 规定,标识符只能是字母(A
Z,az)、数字(0~9)、下划线(_)组成的字符串,并且其第一个 字符必须是字母或下划线。
以下标识符是合法的:
a, x, x3, BOOK_1, sum5
以下标识符是非法的:
3s 以数字开头
s*T 出现非法字符*
-3x 以减号开头
bowy-1 出现非法字符-(减号)
在使用标识符时还必须注意以下几点:
- 标准 C 不限制标识符的长度,但它受各种版本的 C 语言编译系统限制,同时也受到具体机器的限制。
例如在某版本 C 中规定标识符前八位有效,当两个标识符前八位相同时,则被认为是同一个标识符。 - 在标识符中,大小写是有区别的。例如 BOOK 和 book 是两个不同的标识符。
- 标识符虽然可由程序员随意定义,但标识符是用于标识某个量的符号。因此,命名应尽量有相应的意 义,以便于阅读理解,作到“顾名思义”。
- 关键字
关键字是由C语言规定的具有特定意义的字符串,通常也称为保留字。用户定义的标识符不应与关键字 相同。C语言的关键字分为以下几类:- 类型说明符
用于定义、说明变量、函数或其它数据结构的类型。如前面例题中用到的 int,double 等 - 语句定义符
用于表示一个语句的功能。如例 1.3 中用到的 if else 就是条件语句的语句定义符。 - 预处理命令字
用于表示一个预处理命令。如前面各例中用到的 include。
- 类型说明符
- 运算符 C语言中含有相当丰富的运算符。运算符与变量,函数一起组成表达式,表示各种运算功能。运算符由一个或多个字符组成。
- 分隔符
在C语言中采用的分隔符有逗号和空格两种。逗号主要用在类型说明和函数参数表中,分隔各个变量。 空格多用于语句各单词之间,作间隔符。在关键字,标识符之间必须要有一个以上的空格符作间隔,否则将 会出现语法错误,例如把 int a;写成 inta;C 编译器会把 inta
当成一个标识符处理,其结果必然出错。 - 常量
C 语言中使用的常量可分为数字常量、字符常量、字符串常量、符号常量、转义字符等多种。在后面章 节中将专门给予介绍。 - 注释符
C 语言的注释符是以“/*”开头并以“*/”结尾的串。在“/*”和“*/”之间的即为注释。程序编译时, 不对注释作任何处理。注释可出现在程序中的任何位置。注释用来向用户提示或解释程序的意义。在调试程 序中对暂不使用的语句也可用注释符括起来,使翻译跳过不作处理,待调试结束后再去掉注释符。
程序的灵魂-算法
一个程序应包括:
- 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构(data structure)。
- 对操作的描述。即操作步骤,也就是算法(algorithm)。
数据结构 + 算法 = 程序
程序 = 算法 + 数据结构 + 程序设计方法 + 语言工具和环境
算法的概念
为解决一个问题而采取的方法和步骤,就称为算法。
计算机算法:
- 计算机能够执行的算法。
- 计算机算法可分为两大类:
- 数值运算算法:求解数值;
- 非数值运算算法:事务管理领域。
算法的特性
- 有穷性:一个算法应包含有限的操作步骤而不能是无限的。
- 确定性:算法中每一个步骤应当是确定的,而不能应当是含糊的、模棱两可的。
- 有零个或多个输入。
- 有一个或多个输出。
- 有效性:算法中每一个步骤应当能有效地执行,并得到确定的结果。
结构化程序设计方法
- 自顶向下;
- 逐步细化;
- 模块化设计;
- 结构化编码。
数据类型、运算符与表达式
C语言的数据类型
对变量的定义可以包括三个方面:
- 数据类型
- 存储类型
- 作用域
数据类型可分为:
- 基本数据类型
- 整形
- 字符型
- 实型(浮点型)
- 单精度型
- 双精度型
- 枚举类型
- 构造数据类型
- 数组类型
- 结构体类型
- 共用体类型
- 指针类型
- 空类型
- 基本数据类型
基本数据类型最主要的特点是,其值不可以再分解为其它类型。也就是说,基本数据类 型是自我说明的。 - 构造数据类型
构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说, 一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一 个构造类型。在 C 语言中,构造类型有以下几种:- 数组类型
- 结构体类型
- 共用体(联合)类型
- 指针类型:指针是一种特殊的,同时又是具有重要作用的数据类型。其值用来表示某个变量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。
- 空类型:在调用函数值时,通常应向调用者返回一个函数值。这个返回的函数值是具有一定的数据类型的,应在函数定义及函数说明中给以说明,例如在例题中给出的 max 函数定义中,函数头为:int max(int a,int b);其中“int ”类型说明符即表示该函数的返回值为整型量。又如在例题中,使用了库函数 sin,由于系统规定其函数返回值为双精度浮点型,因此在赋值语句 s=sin (x);中,s 也必须是双精度浮点型,以便与 sin 函数的返回值一致。所以在说明部分,把 s 说明为双精度浮点型。但是,也有一类函数,调用后并不需要向调用者返回函数值,这种函数可以定义为“空类型”。其类型说明符为 void。在后面函数中还要详细介绍。
常量与变量
对于基本数据类型量,按其取值是否可改变又分为常量和变量两种。在程序执行过程中,其值不发生改变的量称为常量,其值可变的量称为变量。它们可与数据类型结合起来分类。例如,可分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。在程序中,常量是可以不经说明而直接引用的,而变量则必须先定义后使用。
常量和符号常量
- 直接常量(字面常量):
- 整型常量:12、0、-3;*
- 实型常量:4.6、-1.23;
- 字符常量:‘a’、‘b’。
- 标识符:用来标识变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列。
- 符号常量:用标示符代表一个常量。在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。
符号常量在使用之前必须先定义,其一般形式为:其中1
#define
也是一条预处理命令(预处理命令都以”#”开头),称为宏定义命令(在后面预处理程序
中将进一步介绍),其功能是把该标识符定义为其后的常量值。一经定义,以后在程序中所有出现该标识符的地方均代之以该常量值。 - 习惯上符号常量的标识符用大写字母,变量标识符用小写字母,以示区别。
指针
地址指针的基本概念
内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通 常也把这个地址称为指针。在C语言中,允许用一个变量来存 放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
变量的指针和指向变量的指针变量
变量的指针就是变量的地址。存放变量地址的变量是指针变量。即在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。
在程序中用“”符号表示“指向”,例如,i_pointer 代表指针变量,而i_pointer 是 i_pointer 所指向的变量。
因此,下面两个语句作用相同:
1 | i=3; |
第二个语句的含义是将 3 赋给指针变量 i_pointer 所指向的变量。
定义一个指针变量
对指针变量的定义包括三个内容:
- 指针类型说明,即定义变量为一个指针变量;
- 指针变量名;
- 变量值(指针)所指向的变量的数据类型。
其一般形式为:
类型说明符 *变量名;
1 | int *p2; // 指向整型变量的指针变量 |
应该注意的是,一个指针变量只能指向同类型的变量,如 P3 只能指向浮点变量,不能时而指向一个浮
点变量,时而又指向一个字符变量。
指针变量的引用
指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。
在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。
两个有关的运算符:
- &:取地址运算符。
- *:指针运算符(或称“间接访问”运算符)。
C语言中提供了地址运算符&来表示变量的地址。其一般形式为:&变量名;
如&a 表示变量 a 的地址,&b 表示变量 b 的地址。变量本身必须预先说明。
设有指向整型变量的指针变量 p,如要把整型变量 a 的地址赋予 p 可以有以下两种方式:
- 指针变量初始化的方法
1
2int a;
int *p=&a; - 赋值语句的方法
1
2
3int a;
int *p;
p=&a;
不允许把一个数赋予指针变量,故下面的赋值是错误的:
1 | int *p; |
被赋值的指针变量前不能再加*
说明符,如写为*p=&a
也是错误的。
假设:
1 | int i=200, x; |
此后我们就可以通过ip
间接访问变量i,例如:x=*ip
指针的指向是可以改变的,如:
1 | int i,j,*p1,*p2; |
此时执行p2=p1
就会使p2与p1一同指向i
如果执行*p2=*p1
则表示把p1指向的内容赋给p2所指的区域,相当于j=i
1 |
|
&代表地址
*代表指定地址的值
&a是个地址
*&a是值
指针变量作为函数参数
1 | swap(int *p1, int *p2) |
执行swap会使
*p1
和·*p2
的值互换
指针变量几个问题的进一步说明
指针变量可以进行某些运算,但其运算的种类是有限的。它只能进行赋值运算和部分算术运算及关系运算。
指针运算符
- 取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。
- 取内容运算符*:取内容运算符是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在运算符之后跟的变量必须是指针变量。
需要注意的是指针运算符和指针变量说明中的指针说明符不是一回事。在指针变量说明中,“”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“”则是一个运算符用以表示指针变量所指的变量。
指针变量的运算
赋值:
- 把一个变量的地址赋予指向相同数据类型的指针变量。 例如:
1
2int a,*pa;
pa=&a; /*把整型变量 a 的地址赋予整型指针变量 pa*/ - 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。
如:由于 pa,pb 均为指向整型变量的指针变量,因此可以相互赋值。1
2int a,*pa=&a,*pb;
pb=pa; /*把 a 的地址赋予指针变量 pb*/ - 把数组的首地址赋予指向数组的指针变量。
例如:(数组名表示数组的首地址,故可赋予指向数组的指针变量 pa) 也可写为:1
2int a[5],*pa;
pa=a;当然也可采取初始化赋值的方法:1
pa=&a[0]; /* 数组第一个元素的地址也是整个数组的首地址,也可赋予pa */
1
int a[5],*pa=a;
- 把字符串的首地址赋予指向字符类型的指针变量。
例如:或用初始化赋值的方法写为:1
2char *pc;
pc="C Language";这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入 指针变量。在后面还将详细介绍。1
char *pc="C Language";
- 把函数的入口地址赋予指向函数的指针变量。
例如:1
2int (*pf)();
pf=f; /*f 为函数名*/加减算数运算
对于指向数组的指针变量,可以加上或减去一个整数 n。设 pa 是指向数组 a 的指针变量,则 pa+n,pa-n,pa++,++pa,pa–,–pa 运算都是合法的。指针变量加或减一个整数 n 的意义是把指针指向的当前 位置(指向某数组元素)向前或向后移动 n 个位置。应该注意,数组指针变量向前或向后移动一个位置和地址 加 1 或减 1 在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。 如指针变量加 1,即向后移动 1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上 加 1。
例如:指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。1
2
3int a[5],*pa;
pa=a; /*pa 指向数组 a,也是指向 a[0]*/
pa=pa+2; /*pa 指向 a[2],即 pa 的值为&pa[2]*/两个指针变量之间的运算:
只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。 - 两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。例如 pf1 和 pf2 是指向同一浮点 数组的两个指针变量,设 pf1 的值为 2010H,pf2 的值为 2000H,而浮点数组每个元素占 4 个字节, 所以 pf1-pf2 的结果为(2000H-2010H)/4=4,表示 pf1 和 pf2 之间相差 4 个元素。两个指针变量不能 进行加法运算。 例如,pf1+pf2 是什么意思呢?毫无实际意义。
- 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间 的关系。
例如:
pf1==pf2 表示 pf1 和 pf2 指向同一数组元素;
pf1>pf2 表示 pf1 处于高地址位置;
pf1<pf2 表示 pf2 处于低地址位置。
指针变量还可以与 0 比较。
设 p 为指针变量,则 p==0 表明 p 是空指针,它不指向任何变量; p!=0 表示 p 不是空指针。
空指针是由对指针变量赋予 0 值而得到的。
例如:对指针变量赋 0 值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否1
2
int *p=NULL;
则将造成意外错误。而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已。
数组指针和指向数组的指针变量
数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
指向数组的指针
1 | int a[10]; /*定义 a 为包含 10 个整型数据的数组*/ |
通过指针引用数组元素
如果指针变量 p 已指向数组中的一个元素,则 p+1 指向同一数组中的下一个元素。 引入指针变量后,就可以用两种方法来访问数组元素了。
如果 p 的初值为&a[0],则:
- p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。
*(p+i)
或*(a+i)
就是p+i或a+i所指向的数组元素,即a[i]
。例如,*(p+5)
或*(a+5)
就是a[5]。- 指向数组的指针变量也可以带下标,如
p[i]
与*(p+i)
等价。
根据以上叙述,引用一个数组元素可以用:
- 下标法,即用
a[i]
形式访问数组元素。在前面介绍数组时都是采用这种方法。1
2
3
4
5
6
7
8
9
10
11#include <stdio.h>
int main(void)
{
int a[10], i;
for (i = 0; i < 10; i++)
a[i] = i;
for (i = 0; i < 10; i++)
printf("a[%d]=%d\n", i, a[i]);
return 0;
} - 指针法,即采用
*(a+i)
或*(p+i)
形式,用间接访问的方法来访问数组元素,其中a是数组名,p是 指向数组的指针变量,其处值 p=a。1
2
3
4
5
6
7
8
9
10
11
int main(void)
{
int a[10], i;
for (i = 0; i < 10; i++)
*(a + i) = i;
for (i = 0; i < 10; i++)
printf("a[%d]=%d\n", i, *(a + i));
return 0;
}
结构体与共用体
定义一个结构的一般形式
1 | struct stu { |
定义结构体类型便令
- 先声明结构体类型,再定义改类型的变量
1
2
3
4
5
6
7
8struct stu
{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1, boy2; - 在声明类型的同时定义变量
1
2
3
4
5
6
7
8
9struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1, student2; - 不指定类型名而直接定义结构体类型变量
1
2
3
4struct
{
成员表列
} 变量名表列
结构体变量初始化和引用
1 |
|
在定义结构变量时可以对它的成员初始化。初始化列表是用花括号括起来的一些常量,这些常量依次赋给结构体变量中的各成员。
C99标准允许对某一成员初始化,如:strct Student b ={.name="Zhang Fang}
使用结构体数组
struct Student
{
int num;
char name[20];
float score;
};
struct Stuent stu[5]
结构体指针
指向结构体变量的指针
struct Student * pt; // pt 可以指向 struct Student 类型的变量或数组
1 |
|
(*p).num
可以使用p->num
来替代