C语言——程序解构说明

1. 程序结构说明

  1. 程序结构
#include<stdio.h>   //引入头文件

/*说明:
2. 这是一个 main 函数,是程序的执行入口,程序从 main 函数开始执行
3. void 表示 main 函数没有返回值
4. main(){
		//函数体,即函数语句
		语句 1;
		语句 2;}
*/
void main(){
	int a = 10; //定义一个整型变量
	printf("你好");		//printf 是一个函数,在头文件 <stdio.h>中,需要引入才能使用
}
  1. 注意事项
    (1)C 程序源文件以 “c” 为扩展名;
    (2)C程序的执行入口为 main() 函数;
    (3)C语言严格区分大小写;
    (4)C程序由一条条语句构成,每个语句以 “;” 结束;
    (5)大括号成对出现,缺一不可;
    (6)需注意中英文字符。

  2. C 常用转移字符
    在这里插入图片描述

注意:
\r 是回车,即回到该行开头重新输出,不是换行;如下:
printf("人非圣贤吗\r孰能无过");
则,输出结果为:孰能无过吗

C语言关键字

autobreakcasecharconstcontinue
defaultdodoubleelseenumextern
floatforgotoifintlong
registerreturnshortsignedsizeofstatic
structswitchtypedefunsignedunionvoid
volatilewhile

注意, C语言中,关键字都是小写的。

C语言的常量

在这里插入图片描述
其中,符号常量使用之前必须定义,形式为:

#define 标识符 常量

习惯上符号常量的标识符用大写字母,变量标识符用小写字母,以示区别;符号常量与变量不同,它的值在其作用域内不能改变,也不能再被赋值;使用符号常量的好处是:含义清楚,“一改全改”。

2. C 语言的变量

变量是程序的基本组成单位,变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房间,从而通过变量名可以访问到变量(值)

变量应该有名字,并在内存中占据一定的存储单元;变量名和变量值有不同的含义;变量名实为一个符号地址
在这里插入图片描述

  1. 变量的使用步骤:
//(1)声明变量:
int num;
//(2)赋值:
num = 60;
// (3)使用:
printf("num=%d", num);
// (4) 变量声明与赋值一步到位:
int num = 60;
  1. 变量的使用注意事项:
    (1)变量表示内存中的一个存储区域(不同的数据类型,占用的空间大小不一样);
    (2)该区域有自己的名称和类型;
    (3)变量必须先声明,后使用
    (4)该区域的数据可以在同一类型范围内不断变化;
    (5)变量在同一个作用域内不能重名;
    (6)变量三要素(变量名+值+数据类型)。

  2. 变量的数据类型
    每一种数据都定义了明确的数据类型,在内存中分配了不同大小的内存空间(使用字节多少表示)。数据类型决定了其取值范围,及可以进行的操作。
    在这里插入图片描述

注意

  1. C 语言没有字符串类型,用字符数组表示字符串;
  2. 在不同系统上,部分数据类型字节长度不一样。

字节和位

  1. 变量名实为一个符号地址
  2. 内存以字节为单元组成
  3. 每个字节有一个地址
  4. 一个字节一般由8个二进制位组成
  5. 每个二进位的值是0或1

整型:各种类型的存储大小与操作系统、系统位数和编译器有关。
整型的类型

类型存储大小值范围
char1字节 − 2 7 到 2 7 − 1 -2^7 到 2^7-1 27271
unsigned char1字节0 到 2 8 − 1 2^8-1 281
signed char1字节 − 2 7 到 2 7 − 1 -2^7 到 2^7-1 27271
int(signed int)2字节或4字节{ − 2 15 到 2 15 − 1 -2^{15}到2^{15}-1 2152151} 或 { − 2 31 到 2 31 − 1 -2^{31}到2^{31}-1 2312311}
unsigned int2字节或4字节{0到 2 16 − 1 2^{16}-1 2161} 或 {0到 2 32 − 1 2^{32}-1 2321}
short(signed short)2字节{ − 2 15 到 2 15 − 1 -2^{15}到2^{15}-1 2152151}
unsigned short2字节{0到 2 16 − 1 2^{16}-1 2161}
long(signed long)4 字节{ − 2 31 到 2 31 − 1 -2^{31}到2^{31}-1 2312311}
unsigned long4 字节{ 0 到 2 32 − 1 0到2^{32}-1 02321}

整数常量的表示方法:
在这里插入图片描述
注意:

  1. 整型数据的溢出
  2. 在整型常量后面加大写L或小写l,则告诉编译器,把该整型常量作为long类型处理。例:123L、0L;
  3. 在整型常量后面加u,则按无符号整型方式存放,负数转换成补码再按无符号整型方式存放。

浮点型

类型存储大小值范围有效数字
float单精度4字节(32位) 1.2 × 1 0 − 38 到 3.4 × 1 0 38 1.2\times10^{-38}到3.4\times10^{38} 1.2×10383.4×10386~7
double双精度8字节(64位) 2.3 × 1 0 − 308 到 1.7 × 1 0 308 2.3\times10^{-308}到1.7\times10^{308} 2.3×103081.7×1030815~16
long double 长双精度16字节(128位)18~19

浮点型注意事项:
3. 浮点型常量默认为double型,声明float型常量时,须后加‘f’或‘F’;
4. 浮点型常量有两种表示形式:十进制数形式 / 科学计数法形式
5. 通常情况下,应使用double型,更加精确。

数据类型按精度大小排序
在这里插入图片描述

数据类型的自动转换规则
在这里插入图片描述

  1. 有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度最大的那种数据类型,然后再进行计算(如int型和 short型运算时,先把short转成int型后再进行运算);
  2. 若两种类型的字节数不同,转换成字节数大的类型,若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型;
  3. 在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边的类型将转换为左边的类型,如果右边变量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。

强制类型转换

精度高的数据类型转换为精度小的数据类型。使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意。

强制类型转换的格式为:

(类型名)(表达式);
//例如:
double a = 1.13;
int num = (int)a; //需注意,不是进行四舍五入,而是直接截断小数后的部分
(double)(1+2);

(1)强制类型转换操作并不改变操作数本身;
(2)当进行数据的从精度高——>精度低,就需要使用到强制转换;
(3)强转符号只针对于最近的操作数有效,往往使用小括号提升优先级。

字符型

字符类型可以表示单个字符,字符类型是char,char是1个字节(可以存字母或者数字),多个字符称为字符串,在C语言中使用char数组表示,数组不是基本数据类型,而是构造类型。

(1)字符常量是用单引号 (") 括起来的单个字符。例如: char c= ‘a’;
(2)C中还允许使用转义字符 ‘’ 来将其后的字符转变为特殊字符型常量。例如: char c3=‘\n’; '\n’表示换行符;
(3)在C中,char的本质是一个整数,在输出时,是ASCII码对应的字符;
(4)可以直接给char赋一个整数,然后输出时,会按照对应的ASCII字符输出;
(5)char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码。

字符和码值的对应关系是通过字符编码表决定的。字符型变量存储到计算机中,需要将字符对应的**码值(整数)**找出来,例如:
存储:字符 ‘a’ ----> 码值(97)----> 二进制 (1100001)---->存储()
读取:二进制(1100001) ----> 码值(97) ----> 字符 ‘a’ ----> 读取(显示)

字符串常量: 用**双引号(“”)**括起来的字符序列;
其储存为在每个字符串尾自动加一个 ‘\0’ 作为字符串结束的标志,如字符串 “hello” 在内存中为:
在这里插入图片描述

基本数据类型及其对应的限定符
在这里插入图片描述
说明:

1)使用格式字符 %i  或  %d  以十进制显示整数值;
(2)使用格式字符 %o 以八进制显示整数值;使用格式字符 %#o 在八进制之前显示一个前导零;
(3)使用格式字符 %x 以十六进制显示整数值;使用格式字符 %#x 在十六进制之前显示一个前导 0x;
(4)使用格式字符 %f 显示浮点数;
(5)使用格式字符 %e 以科学计数法显示浮点数;
(6)使用格式字符 %g 显示浮点数,使得输出格式最美观,输出 %f 或 % e 中较短的一个;
(7)使用格式字符 %c 显示 char 变量的值,输出单个字符;
(8)使用格式字符 %s 输出字符串;

C 语言运算符

类别符号
算术运算符 + 、 − 、 ∗ 、 / 、 % 、 + + 、 − − +、 -、*、/、\%、++、-- +/%++
关系运算符 < 、 < = 、 = = 、 > 、 > = 、 ! = <、<=、 ==、 > 、>=、 != <<===>>=!=
逻辑运算符 或 ( ∣ ) 、 且 ( & & ) 、 非 ( ! ) 或(|)、且(\&\&)、非(!) ()(&&)(!)
位运算符 位 与 ( & ) 、 位 或 ( 位与(\&)、位或( (&)(I ) 、 位 非 ( ∼ ) 、 位 异 或 ( )、位非(\sim)、位异或( )()(^ ) 、 左 移 ( < < ) 、 右 移 ( > > ) )、左移(<<)、右移(>>) )(<<)(>>)
赋值运算符 简 单 赋 值 ( = ) 、 复 合 算 法 赋 值 ( + = 、 − = 、 ∗ = 、 / = 、 % = ) 、 复 合 位 运 算 赋 值 ( & = 、 简单赋值(=)、复合算法赋值(+=、-=、*=、/=、\%=)、复合位运算赋值(\&=、 (=)(+===/=%=)(&=I = 、 =、 =^ = 、 > > = 、 < < = ) =、>>=、<<=) =>>=<<=)
条件运算符 三 目 运 算 符 ( ? : ) 三目运算符 (?:) (?:)
逗号运算符 , ( 用 于 把 两 个 表 达 式 连 接 起 来 组 成 一 个 表 达 式 ) ,(用于把两个表达式连接起来组成一个表达式)
指针运算符 取 内 容 ( ∗ ) 、 取 地 址 ( & ) 取内容(*)、取地址(\&) ()(&)
求字节数运算符 s i z e o f \mathbb sizeof sizeof
特殊运算符 括 号 ( ) 、 下 标 [ ] 、 成 员 ( → 、 . ) 等 括号()、下标[]、成员(\to、.)等 ()[](.)

运算符的优先级:顶部最高,底部最底;
在这里插入图片描述

(1)关系运算符(其值为 “真” 或 “假”,以 “0”,“1”表示): “ < 、 < = 、 > 、 > = ” “<、<=、 > 、>=” <<=>>= 的优先级相同; “ = = 、 ! = ” “==、!=” ==!= 的优先级相同;前者优先级高于后者;
(2)逻辑运算符(其值为 “真” 或 “假”,以 “0”,“1”表示);

运算符的结合性
(1)左结合性:算术运算符;
(2)右结合性:赋值运算符

自增、自减运算符

1++i;  // i 自增 1 后再参与其他运算2--i;  // i 自减 1 后再参与其他运算3) i++;  // i 参与运算后,i 的值在自增 14) i--;  // i 参与运算后,i 的值在自减 1

C 语言控制语句

类别语句
条件判断语句 i f 语 句 、 s w i t c h 语 句 if语句、switch语句 ifswitch
循环执行语句 d o    w h i l e 语 句 、 w h i l e 语 句 、 f o r 语 句 do\;while语句、while语句、for语句 dowhilewhilefor
转向语句 b r e a k 语 句 、 r e t u r n 语 句 、 g o t o 语 句 、 c o n t i n u e 语 句 break语句、return语句、goto语句、continue语句 breakreturngotocontinue

数据输入输出

C 语言中,所有的数据输入/输出都是由库函数完成的,都为函数语句。

一、在使用 C 语言库函数时,要用预编译命令 #include 将有关头文件包括到源文件中;如,使用标准输入输出时,要用到 **“stdio.h”**文件:

#include<stdio.h>#include"stdio.h"

printf("格式控制字符串", 输出表列); 	//格式输出函数
scanf("格式控制字符串", 地址表列); 	//格式输入函数

putchar(字符变量); 	//字符输出函数,输出单个字符
getchar();					//键盘输入函数,只能接受单个字符

puts(字符数组名); 		字符串输出函数
gets(字符数组名);		字符串输入函数
printf("格式控制字符串", 输出表列); 	//格式输出函数
//其中,格式字符串的一般形式为:
[标志][输出最小宽度][.精度][长度]类型

格式字符串 说明如下:
(1)类型,见上述限定符或,如下:
在这里插入图片描述
(2)标志,有四种,如下:
在这里插入图片描述
(3)输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。
(4)精度:精度格式符以 “.” 开头,后跟十进制整数。本项的意义是:如果输出数字,则表示小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。
(5)长度:长度格式符为 h, l 两种,h 表示按短整型量输出,l表示按长整型量输出。

scanf("格式控制字符串", 地址表列); 	//格式输入函数
//其中,格式字符串同 printf 函数中作用相同,一般形式为:
%[*][输入数据宽度][长度]类型
//地址由地址运算符 “&” 后跟变量名组成,如:
&a, &b

格式字符串说明如下:
(1)类型,如下:
在这里插入图片描述
(2) " ∗ " "*" "" 符:用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值。如:

scanf("%d %*d %d", &a, &b);
// 当输入为:1 23时,把1赋予a,2被跳过,3赋予b。

(3)宽度:用十进制整数指定输入的宽度(即字符数)。如:

scanf("%5d", &a); //输入:12345678,即将把1234赋予a,其余部分被截去。
scanf("%4d %4d", &a); //输入:12345678,即将把1234赋予a,而把5678赋予b。

(4)长度:长度格式符为 h, l 两种,h 表示按短整型量输入,l表示按长整型量输入。

scanf 函数的注意点

  1. scanf 函数中没有精度控制,即 scanf(“%5.2f”,&a) 是非法的;
  2. scanf中要求给出变量地址,如给出变量名则会出错;
  3. 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。C编译在碰到空格,TAB,回车或非法数据(如对"%d"输入“12A"时,A即为非法数据)时即认为该数据结束;
  4. 在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符;
  5. 如果格式控制串中有非格式字符则输入时也要输入该非格式字符,如:
scanf("%d,%d,%d",&a,&b,&c); //其采用非格式字符 “,”作为间隔符,故输入应为:5,6,7
scanf("a=%d,b=%d,c=%d",&a,&b,&c); //则其输入应为:a=5,b=6,c=7
putchar(字符变量); 	//字符输出函数,输出单个字符

getchar();					//键盘输入函数,只能接受单个字符,
//1.输入数字也按字符处理;
//2.当输入多于一个字符时,只接受第一个字符;
//3.输入单个字符后,必须按一次回车,计算机才接受输入的字符

1. 分支结构语句:

  1. if 语句

形式:
(1)单分支:如果表达式的值为真,则执行其后的语句,否则不执行该语句。
在这里插入图片描述

if(表达式)
	{语句};

(2)二分支:如果表达式的值为真,则执行语句1,否则执行语句2。
在这里插入图片描述

if(表达式)
	{语句段1;}
else
	{语句段2;}

(3)多分支:
在这里插入图片描述

if(表达式1)
	{语句段1;}
else if(表达式2)
	{语句段2;}
else if(表达式3)
	{语句段3;}
	......
else
	{语句段n;}
  1. 条件运算符

条件运算符:是一个三目运算符,其求值规则为:如果表达式1的值为真,则以表达式2的值作为条件表达式的值,否则以表达式2的值作为整个条件表达式的值。

表达式1? 表达式2:表达式3;
max = (a>b)?a:b; //其语义为 max 为 a, b 中的最大值

注意点:
(1)条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符;
(2)条件运算符的结合方向是 自右至左

a>b?a:c>d?c:d 应理解为 a>b?a:(c>d?c:d)
  1. switch 语句
switch(表达式){
	case 常量表达式1: 语句1;
	case 常量表达式2: 语句2;
	......
	case 常量表达式n: 语句n;
	default : 语句n+1;
	}

2. 循环控制语句

  1. goto 语句和 if 语句构成循环;

goto 语句格式为:goto 语句标号; goto 语句常与 if 条件语句连用,当满足某一条件时,程序跳到标号处运行;标号必须与 goto 语句同处于一个函数中,但可以不在一个循环层中。如:

// 实现 0,1,2,....,100 的累加
main()
{
	int i, sum;
	i = 1, sum = 0;
loop: if (i<=100)
				{sum = sum + i;
				i++;
				goto loop;}
}

goto 语句通常不用,主要因为它将使程序层次不清,且不易读,但在多层嵌套退出时,用goto语句则比较合理。

  1. while 语句:while(表达式) {语句块;}

计算表达式的值,当值为真(非0)时,执行循环体语句。
在这里插入图片描述

// 实现 0,1,2,....,100 的累加
main()
{
	int i, sum;
	i = 1, sum = 0;
	while(i<=100)
		{sum = sum + i;
		i++;} 
}
  1. do-while 语句:
do
	{语句;}
while(表达式)

这个循环与 while循环的不同在于:它先执行循环中的语句,然后再判断表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此, do-while 循环至少要执行一次循环语句。
在这里插入图片描述

// 实现 0,1,2,....,100 的累加
main()
{
	int i, sum;
	i = 1, sum = 0;
	do
		{sum = sum + i;
		i++;}while(i<=100) 
}
  1. for 语句:for(表达式1; 表达式2; 表达式3) {语句;}
for(循环变量赋初值;循环条件;循环变量增量){语句;}

其执行过程为:
(1)先求解表达式1;
(2)求解表达式2,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面第(3)步;若其值为假(0),则结束循环,转到第(5)步;
(3)求解表达式3;
(4)转回上面第(2)步继续执行;
(5)循环结束,执行for语句下面的一个语句。

在这里插入图片描述

// 实现 0,1,2,....,100 的累加
main()
{
	int i, sum;
	for(i=1; i<=100; i++)
		{sum = sum + i;}
  1. break 语句break;
    (1)当break语句用于do-while、for、 while循环语句中时,可使程序终止循环而执行循环后面的语句,通常break语句总是与if语句联在一起。即满足条件时便跳出循环;
    (2)当break用于开关语句switch中时,可使程序跳出switch而执行switch以后的语句;如果没有break 语句,则将成为一个死循环而无法退出

注意:
(1)break 语句对 if-else 的条件语句不起作用;
(2)在多层循环中,一个break语句只向外跳一层。

  1. continue 语句continue;
    continue 语句的作用是跳过循环本中剩余的语句而强行执行下一次循环;continue语句只用在for、while、do-while等循环体中,常与 if 条件语句一起使用,用来加速循环。

continue 与 break 用于循环时,之间的差别如下:
在这里插入图片描述

数组

  1. 数组定义:类型说明符 数组名 [常量表达式];其中,类型说明符是任一种基本数据类型或构造数据类型;数组名是用户定义的数组标识符;方括号中的常量表达式表示数据元素的个数,也称为数组的长度。
int a[10];		//整型数组 a, 有 10 个元素
float b[20];
char ch[30];	//字符数组 c, 有 30 个元素

注意点:
(1)数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的;
(2)数组下标从 0 开始计算;
(3)方括号中不能用变量来表示元素个数;

  1. 一维数组:
    数组元素的引用数组名[下标],其中下标只能为整型常量或整型表达式;如为小数时,C编译将自动取整。C语言只能逐个地使用下标变量,而不能一次引用整个数组。
    数组初始化类型说明符 数组名 [常量表达式] = {值,值,...,值};其相关说明为:
    (1)可以只给部分元素赋初值;当 {} 中值的个数少于元素个数时,只给前面部分元素赋值;
    (2)只能给元素逐个赋值,不能给数组整体赋值;
    (3)如给全部元素赋值,则在数组说明中,可不给出数组元素的个数。
int a[5] = {1,1,1,1,1};   //正确
int a[5] = 1; 					 //错误
int a[] = {1,2,3,4,5};	 //正确
  1. 二维数组:类型说明符 数组名 [常量表达式1][常量表达式2];其中常量表达式1表示第一维下标的长度,常量表达式2表示第二维下标的长度。
    引用数组名[下标][下标]
    初始化:二维数组初始化也是在类型说明时给各下标变量赋以初值。二维数组可按行分段赋值,也可按行连续赋值。其相关说明为:
    (1)可以只对部分元素赋初值未赋初值的元素自动取0值;
    (2)如对全部元素赋初值,则第一维的长度可以不给出。
    (3)数组是一种构造类型的数据:二维数组可以看作是由一维数组的嵌套而构成的。设一维数组的每个元素都又是一个数组,就组成了二维数组。即一个二维数组也可以分解为多个一维数组。
int a[2][3] = {{1,2,3},{4,5,6}};	//分段赋值
int a[2][3] = {1,2,3,4,5,6};			//连续赋值
int a[][3] = {1,2,3,4,5,6};

函数

函数定义一般形式:
(1)无参函数

类型标识符 函数名(){
	声明部分;		//对函数体内部所用到的变量类型说明
	语句;
}

(2)有参函数

类型标识符 函数名(形式参数表列){
	声明部分;		//对函数体内部所用到的变量类型说明
	语句;
}

其中类型标识符和函数名称为函数头;类型标识符指明了本函数的类型,函数的类型实际上是函数返回值的类型;若无返回值,则类型标识符写为 void ;形式参数是各种类型的变量,因此需在形参表中给出形参的类型说明。

形参和实参
(1)形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用;
(2)实参出现在主调函数中,进入被调函数后,实参变量也不能使用;
(3)形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。
(4)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量;
(5)实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值;
(6)实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误;
(7)函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

函数返回值
(1)函数的值只能通过 return语句 返回主调函数;该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值

return 表达式;
return (表达式);

(2)函数值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换;
(3)如函数值为整型,在函数定义时可以省去类型说明;
(4)不返回函数值的函数,可以明确定义为“空类型”,类型说明符为“void”;

函数调用函数名(实际参数表);
(1)对无参函数调用时则无实际参数表;
(2)实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式;(3)各实参之间用逗号分隔。

函数调用的方式:
(1)函数表达式:eg. z=max(x,y); //把max 函数的返回值赋予变量 z
(2)函数语句;
(3)函数实参:eg. max(a,max(b,c));

被调用函数的声明:在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。其一般形式为:

类型说明符 被调函数名(类型 形参, 类型 形参,...);
或
类型说明符 被调函数名(类型, 类型,...);

对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部。

函数的嵌套调用:C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。其关系可表述为:
在这里插入图片描述
函数的递归调用
一个函数在它的函数体内调用它自身称为递归调用;为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段;常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。

// 使用递归计算 n!
long int f(int n)
{
	long int a;
	if(n<0){printf("n<0, input error");}
	else if(n==0 || n==1){a=1;}
	else {a = f(n-1) * n;}
	return (f);	
}

数组作为函数参数:数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式:一种是把数组元素(下标变量)作为实参使用;另一种是把数组名作为函数的形参和实参使用。
注意:
(1)用数组元素作实参时,对数组元素的处理是按普通变量对待的;
(2)用数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误;
(3)在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量;
(4)在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。
(5)数组名就是数组的首地址。
(6)因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。如下:
在这里插入图片描述

其中,设a为实参数组,类型为整型。a占有以2000为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送,把实参数组a的首地址传送给形参数组名b,于是b也取得该地址2000。于是a,b两数组共同占有以2000为首地址的一段连续内存单元。a和 b下标相同的元素实际上也占相同的两个内存单元(整型数组每个元素占二字节)。
(7)在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化,两者的终值是不同的,数组元素做实参时,也是如此。
(8)而当用数组名作函数参数时,由于实际上形参和实参为同一数组,因此当形参数组发生变化时,实参数组也随之变化。
(9)形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。
(10)在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。例如:

int f1(int a[]);int f1(int a[], int n);	
//其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n 的值由主调函数的实参进行传送。
int f1(int a[], int n)
{
	int i;
	printf("%d values of array a are :\n");
	for(i=0;i<n;i++)
		{
		if(a[i]<0){a[i]=0;}
		printf("%d", a[i]);
		}
}

int main()
{
	int b[5], i;
	b[5] = {1,2,3,4,-1};
	f1(b, 5); 	// f1 函数形参数组a没有给出长度,由n动态确定该长度。在main函数中,函数调用语句为 f1(b,5),其中实参5将赋予形参n作为形参数组的长度。
}

(11)多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。如:

int max(int a[3][2]);int max(int a[][]2);

局部变量与全局变量(从变量的作用域(空间)角度):
局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
局部变量的作用域:
(1)主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。
(2)主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。
(3)形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。
(4)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。
(5)在复合语句中也可定义变量,其作用域只在复合语句范围内(“{}”)。

全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。只有在函数内经过说明的全局变量才能使用。全局变量的说明符为 extern。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。

变量的存储类别:在c语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。
从变量值存在的作用时间(生存期)角度,可分为:

  1. 静态存储方式:是指在程序运行期间分配固定的存储空间的方式。
  2. 动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式。
    用户存储空间可分为:
    (1)程序区:
    (2)静态存储区:全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;
    (3)动态存储区:存放 函数形式参数、自动变量(未加 static 声明的局部变量)、函数调用时的现存保护和返回地址;对这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。

在这里插入图片描述
auto 变量
(1)函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中;
(2)函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明;

static 声明局部变量:当希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为**“静态局部变量”**,用关键字static进行声明。
(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。
(2)自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放;
(3)静态局部变量在编译时赋初值,即只赋初值一次;
(4)自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句;
(5)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量);
(6)而对自动变量来说,如果不赋初值则它的值是一个不确定的值。

register 变量:为了提高效率,C语言允许将局部变量得值放在CPU中的寄存器中,这种变量叫"寄存器变量”,用关键字 register 作声明。

register int i = 5;

(1)只有局部自动变量和形式参数可以作为寄存器变量;
(2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;
(3)局部静态变量不能定义为寄存器变量。

** extern 声明外部变量**:
(1)外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。
(2)如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。
(3)如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”。表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

int main()
{
	extern A, B;
	int a = A + B;
	int A = 1, B = 2;
}

预处理命令

C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。以"#"号开头的预处理命令:包含命令#include,宏定义命令#define等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为预处理部分。
所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

1. 宏定义
在C语言源程序中允许 用一个标识符来表示一个字符串,称为"宏"。被定义为"宏”的标识符称为 “宏名”。在编译预处理时,对程序中所有出现的"宏名",都用宏定义中的字符串去代换,这称为"宏代换"或”宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。

无参宏定义:#define 标识符 字符串
其中:
(1)其中的 " # " 表示这是一条预处理命令。凡是以 **" # "**开头的均为预处理命令。
(2)define 为宏定义命令。
(3)标识符 为所定义的宏名。
(4)“字符串” 可以是常数、表达式、格式串等。

#define M (3*a+b)
#define N 3*a+b
int main()
{
	int a, b, s, z;
	a = 3, b= 7;
	s = 6 * M;		//等价于 s = 6 * (3*a+b)
	z = 6 * N; 	//等价于 z = 6 * 3*a+b
}

需注意:
(1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现;
(2)宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换;
(3)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用 #undef命令

#define PI 3.14			// 定义 宏
int main()
{...}

#undef PI			//终止宏的作用域,即 PI 只在 main 函数中有效,在 f2 函数中无效;
f2()
{...}

(4)宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。

#define A 10
int main()
{
	printf("A"); 	// 输出结果 A,即把 "A"作为字符处理
	//宏名A表示10,但在printf语句中A被引号括起来,因此不作宏代换。
}

(5)宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。

#define PI 3.14
#define S PI*y*y			// PI 是已定义的宏名

(6)习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。(7)可用宏定义表示数据类型,使书写方便。

有参宏定义:
C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数;对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

带参宏定义:
#define 宏名(形参表) 字符串

带参宏调用:
宏名(实参表);
#define M(y) y*y+5*y		//宏定义
int main()
{
	int k = M(5);		//宏调用,等价于 k = 5*5+5*5
	int a = 3, s;
	s = M(a);			//等价于:s = a*a+5*a = 3*3+5*3
}

需注意:
(1)带参宏定义中,宏名和形参表之间不能有空格出现。
(2)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。
(3)而宏调用中的实参有具体的值。要用它们去代换形参,因此必须作类型说明。
(4)这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行"值传递”。而在带参宏中,只是符号代换,不存在值传递的问题
(5)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
(6)在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

#define A1(x) x*x
#define A2(x) (x)*(x)
int main()
{
int x = 2, s1, s2;
s2 = A2(x+1);	// 等价于 s2 = (x+1)*(x+1)
s1 = A1(x+1);	// 等价于 s1 = x+1*x+1 = 2*x +1
}

(7)带参的宏和带参函数很相似,但有本质上的不同,把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。

2. 文件包含
文件包含命令行的一般形式为:#include"文件名"
其功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。需注意:
(1)包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来;但是这两种形式是有区别的:
使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形式。

#include"stdio.h"
#include<math.h>

(2)一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。
(3)文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

3. 条件编译
预处理程序提供了条件编译的功能,可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件,这对于程序的移植和调试是很有用的。
(1)第一种:它的功能是,如果标识符已被 #define命令定义过,则对程序段1进行编译:否则对程序段2进行编译;

#ifdef 标识符
	程序段1
#else 
	程序段2
#endif

如果没有程序段2(它为空),本格式中的 #else可以没有,即

#ifdef 标识符
	程序段1
#endif

(2)第二种:与第一种相反,与第一种形式的区别是将 **“ifdef”**改为 “ifndef”。它的功能是,如果标识符未被 #define 命令定义过则对程序段1进行编译,否则对程序段⒉进行编译。

#ifndef 标识符
	程序段1
#else
	程序段2
#endif

(3)第三种:如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。

#if 常量表达式
	程序段1
#else
	程序段2
#endif

指针

基本概念:
(1)在计算机中,所有的数据都是存放在存储器中的。
(2)一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等。
(3)为了正确地访问这些内存单元,必须为每个内存单元编上号。
(4)根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址
(5)根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针
(6)内存单元的指针和内存单元的内容是两个不同的概念。
(7)对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。
(8)在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
(9)严格地说,一个指针是一个地址,是一个常量。
(10)一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。即 “指针” 是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。
(11)在一个指针变量中存放一个数组或一个函数的首地址时,因为数组或函数都是连续存放的,通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。
(12)凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可。
在这里插入图片描述
如上图所示,字符变量C,其内容为 “K” (ASCII 码为十进制数75),C占用了011A号单元(地址用十六进数表示)。指针变量P,内容为011A,即称为Р指向变量C,或说Р是指向变量C的指针。

变量的指针:即变量的地址;
指针变量:存放变量地址,指向变量。
在这里插入图片描述
如上图所示,“ * ” 表示“指向”;i_pointer 代表指针变量;*i_pointer 是 i_pointer 所指向的变量。

定义指针变量:类型说明符 *变量名;一个指针变量只能指向同类型的变量;
指针变量的引用:几点说明:
(1)指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值;
(2)未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机;
(3)指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误;
(4)在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。

1&:取地址运算符;如,&a 表示变量 a 的地址;
(2*:指针运算符;

其中,需注意:
(1)取地址运算符& 是单目运算符,其结合性为自右至左,其功能是取变量的地址。
(2)取内容运算符 ∗ * 是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在 ∗ * 运算符之后跟的变量必须是指针变量。

指针变量初始化:

int a, b;
int *p = &a;
p = &b;
*p = 100;		//使 p 指向的地址的值为100 ,即a =100
以下为错误:
p = 1000;	//不能把一个数赋予指针变量;指针变量的赋值只能是地址
*p = &b;		//被赋值的针变量前不能再加“*"说明符

指针变量的引用:
在这里插入图片描述

int i = 200, x;
int *ip;
ip = &i;
x = *ip;		//等价于 x = i;  *ip 访问的是地址为1800的存储区域

指针变量和一般变量一样,存放在它们之中的值是可以改变的,如:

在这里插入图片描述
通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向)。

指针变量作函数参数:
函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

swap_1(int x, int y)		//注:该函数不能实现对应实参的互换,互换只发生在函数域内
{
	int temp;
	temp = x;
	x = y;
	y = temp;
}
swap(int *p1, int *p2)
{
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
int main()
{
	int a, b;
	int *pointer_1, *pointer_2;
	a = 5, b = 9;
	pointer_1 = &a;
	pointer_2 = &b;
	if(a<b)
	{
		swap(pointer_1, pointer_2);	//可实现 a,b值的调换,即 a=9;b=5
		swap_1(a, b);	//不能调换a,b值,即 a=5;b=9
	}
}

上述程序图解如下:
在这里插入图片描述
指针变量的运算:
1 .赋值运算:
(1)指针变量初始化;
(2)把一个变量的地址赋予指向相同数据类型的指针变量;

int a, *p;
p = &a; 	//把整型变量a的地址赋予整型指针变量 p

(3)把一个指针变量的值赋予指向相同类型变量的另一个指针变量;

int a, *p = &a, *p1;
p1 = p;			//把 p 的地址赋予指针变量 p1

(4)把数组首地址赋予指向数组的指针变量;

int a[5], *p;
p = a;			//数组名表示数组首地址
或
p = &a[0];		//数组第一个元素的地址也是数组的首地址*p = a;		//赋值

(5)把字符串的首地址赋予指向字符类型的指针变量;

char *pc;
pc = "ABC"; //这里并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。

(6)把函数的入口地址赋予指向函数的指针变量。

int (*pf)();
pf = f;			// f 为函数名
  1. 加减算术运算:
    对于指向数组的指针变量,可以加上或减去一个整数n。注意:
    (1)指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。
    (2)数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。
    (3)如指针变量加1,即向后移动1个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。
    (4)指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。
int a[5], *p;
p = a;				//p指向数组a,也是指向a[0]
p = p + 2;		//p指向a[2],即 p的值为&pa[2]
  1. 两个指针变量之间的运算:
    只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
    (1)两指针变量相减:
    两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数);
    (2)两指针变量不能进行加法运算;
    (3)两指针变量进行关系运算:
    指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
    (4)指针变量可同 0 比较;
    (5)对指针变量赋 0 值和不赋值是不同的:
    指针变量未赋值时,可以是任意值,是不能使用的,否则将造成意外错误;
    而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已。
int a[5], *p1, *p2;
p1 = a;				//p指向数组a,也是指向a[0]
p1 == p2; 	//表示 p1 和 p2 指向同一数组元素
p1 > p2;		//表示 p1 处于高地址位置;
p1 == 0;		//表示 p 是空指针,不指向任何变量
p1 != 0; 	//表示 p 不是空指针
p1 = 0;		//空指针

数组指针和指向数组的指针变量
所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

1 .指向数组元素的指针
基本概念:
一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元,一个数组元素的首地址也是指它所占有的内存单元的首地址。C语言规定,数组名代表数组的首地址,也就是第0号元素的地址;

2 .通过指针引用数组元素
如果指针变量p 已指向数组中的一个元素,则 p+1指向同一数组中的下一个元素。若 p 的初值为 &a 或 &a[0],则
(1)p+i 和 a+i 就是 a[i] 的地址,或者说它们指向a数组的第i个元素;
(2) ∗ * (p+i) 或 ∗ * (a+i) 就是 p+i 或 a+i 所指向的数组元素,即a[i];
(3)指向数组的指针变量也可以带下标,即 p[i] 与 *(p+i) 等价。
在这里插入图片描述
需注意:
(1)指针变量可以实现本身的值的改变。如 p++ 是合法的;而 a++ 是错误的。因为 a 是数组名,它是数组的首地址,是常量。
(2)需注意指针变量的当前值;
(3)需数组的长度是固定的,但指针变量可以指到数组以后的内存单元,系统并不认为非法;
在这里插入图片描述
3 . 数组名做函数参数:

数组名可以作函数的实参和形参,数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同数组

如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有:
(1)形参和实参都是数组名;
(2)实参用数组名,形参用指针变量;
(3)实参、形参都用指针变量;
(4)实参为指针变量,形参为数组名;

4 .指向多维数组的指针和指针变量

多维数组的地址,以二维数组为例,a[3][4] ={{0,1,2,3},{4,5,6,7},{8,9,10,11}};,设其首地址为 1000,则各变量首地址及其值如下:
在这里插入图片描述
指向多维数组的指针变量(以二维为例):类型说明符 (*指针变量名)[长度];
其中,“长度”指将二维数组分解为多个一维数组时,一维数组的长度,即二维数组的列数。
应注意 “(*指针变量名)” 两边的括号不可少,如缺少括号则表示是指针数组,意义就完全不同了。

将二维数组a[3][4] ={{0,1,2,3},{4,5,6,7},{8,9,10,11}}
分解为一维数组:a[0], a[1], a[2]int (*p)[4];
p = a;		// 指针 p 指向二维数组 a;p+i 指向一维数组 a[i]
*(p+i)+j 为二维数组第 i 行,第 j 列的地址
*(*(p+i)+j) 为二维数组第 i 行,第 j 列的值

字符串的指针指向字符串的针指变量

C语言中,可用两种方法访问一个字符串:
(1)用字符数组存放字符串

char string[] = "ABC";		//string是数组名,它代表字符数组的首地址。

(2)用字符串指针指向一个字符串,
需注意字符串指针变量指向字符的指针变量的区别。

char *s = "ABC";	//表示s是一个指向字符串的指针变量。把字符串的首地址赋予s.
char c, *p =&c;		//表示p是一个指向字符变量c的指针变量。
//把一个字符串的内容复制到另一个字符中
void cpystr(char *pss, char *pds)
{
	//pps指向源字符串, pds指向目标字符串
	while((*pds=*pps)!='\0')
	{
		pds++;
		pps++;
	}
}

int main()
{
	char *p1 = "ABC", b[10], *p2;
	p2 = b;
	cpystr(p1, p2);
}

其中, cpystr 函数可简化为:

void cpystr(char *pss, char *pds)
{	while((*pds++ = *pss++)!='\0');}void cpystr(char *pss, char *pds)
{	while((*pds++ = *pss++));}			
// '\0'的 ASCII码值为0,while语句其表达式为0则结束循环

使用字符串指针变量与字符数组的区别:
(1)字符串指针变量本身是一个变量,用于存放字符串的首地址。
(2)字符串本身是存放在以该首地址为首的一块连续的内存空间中,以 ‘\0’ 作为串的结束。
(3)字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。

1)对字符串指针方式,可写为:
char *ps = "ABC";char *ps;
ps = "ABC";2)对数组方式,只能写作:
char st[] = {"ABCD"};
不能写为:
		char st[10];
		st = {"ABCD"};
其只能对字符数组的各个元素逐个赋值

函数指针变量
基本概念:
(1)在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
(2)可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。把这种指向函数的指针变量称为”函数指针变量”。
(3)函数指针变量定义的一般形式为:类型说明符 (*指针变量名)();
其中 “类型说明符” 表示被指函数的返回值的类型。“( ∗ * 指针变量名)“表示” ∗ * "后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。
(4)调用函数的一般形式为:(*指针变量名)(实参表);

int max(int a, int b);
{
	if(a>b)	return a;
	else	return b;
}

int main()
{
	int max(int a, int b);
	int x = 3, y = 4, z;
	int (*pf)();		//定义函数指针变量,表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
	pf = max;			//把被调函数的入口地址(函数名)赋子该函薮指针变量,
	z = (*pf)(x,y);		//用函数指针变量形式调用函数
}

需注意:
(1)函数指针变量不能进行算术运算,这是与数组指针变量不同的。
(2)数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
(3)函数调用中"( ∗ * 指针变量名)"的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处只是一种表示符号。

指针型函数
函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数,气一般形式为:

类型说明符 *函数名(形参表)
{
	...../函数体/
}

函数名之前加了 “*" 号表明这是一个指针型函数,即返回值是一个指针。

指针数组

(1)一个数组的元素值为指针则是指针数组。
(2)指针数组是一组有序的指针的集合。
(3)指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
(4)指针数组说明的一般形式为:类型说明符 *数组名[数组长度]

int *pa[3];	//表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。

(5)可用一个指针数组来指向一个二维数组。指针数组中的每个元素被赋予二维数组每一行的首地址,即指向一个一维数组。

int a[3][3] = {1,2,3,4,5,6,7,8,9};
int *pa[3] = {a[0],a[1],a[2]};
int *p = a[0];
//其中*a[i]表示i行0列元素值;
//*(*(a+i)+i)表示i行i列的元素值;
//*pa[i]表示i行0列元素值;
//由于p与a[0]相同,故p[i]表示0行i列的值:*(p+i)表示0行i列的值

(6)应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。
(7)二维数组指针变量是单个的变量,其一般形式中 (*指针变量名) 两边的括号不可少。
(8)指针数组类型表示的是多个指针(一组有序指针)在一般形式中 *指针数组名 两边不能有括号。

int (*p)[3];	//表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为

int *p[3];		//表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针变量。

(9)指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地址。指向字符串的指针数组的初始化更为简单。

char *string[] = {"ABC", "EFD"};

指向指针的指针
在这里插入图片描述

(1)如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
(2)通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。
(3)通过指向指针的指针变量来访问变量则构成“二级间址”。

//name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址;
char *name[] = {"Follow me", "BASIC", "Great Wa11", "FORTRAN", "Computer desighn"};
char **p;	//相当于 *(*p)
p = name;	//p是指向指针的指针变量

说明:
(1)p前面有两个 ∗ * 号,相当于 ∗ * ( ∗ * p)。
(2)如果没有最前面的 ∗ * ,那就是定义了一个指向字符数据的指针变量
(3)前面又加一个 ∗ * 号,表示指针变量 p 是指向一个字符指针型变量的。也就是 p 所指向的另一个指针变量。
(4)name+1是 name[i] 的地址,即指向指针型数据的指针(地址)
(5)设置一个指针变量 p,使它指向指针数组元素。p 就是指向指针型数据的指针变量。

在这里插入图片描述
main 函数的参数
(1)前面介绍的main函数都是不带参数的。因此main后的括号都是空括号。
(2)实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。
(3)C语言规定main 函数的参数只能有两个,习惯上这两个参数写为 argc和 argv。
(4)C语言还规定argc(第一个形参)必须是整型变量, argv(第二个形参)必须是指向字符串的指针数组。
(5)main函数的函数头可写为:main(int argc, char argv[])
(6)main函数不能被其它函数调用,因此不可能在程序内部取得实际值。
(7)main 函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在 DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。
(8)DOS提示符下命令行的一般形式为:C:\>可执行文件名 参数 参数...;
(9)特别注意,main 的两个形参和命令行中的参数在位置上不是一一对应的。因为, main的形参只有二个,而命令行中的参数个数原则上未加限制。(10)argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc 的值是在输入命令行时由系统按实际参数的个数自动赋予的

例,命令行:
C:\>E24 BASIC foxpro FORTRAN
//由于文件名E24本身也算一个参数,所以共有4个参数,因此argc取得的值为4。

//argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。
//指针数组的长度即为参数个数。
//数组元素初值由系统自动赋予。

其 argv 参数表示如下所示:
在这里插入图片描述
汇总

  1. 有关指针的数据类型:
    在这里插入图片描述2. 指针运算
int *p;

(1)指针变量加减一个整数:一个指针变量加(减)一个整数并不是简单地将原值加(减)一个整数,而是将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数加(减)。

p++;
p--;
p+i;
p-i;
p+=i;
p-=i;

(2)指针变量赋值:讲一个变量的地址赋给另一个指针变量

p = &a;			//将变量 a 的地址赋给 p
p = array; 		//将数组 array 的首地址赋给 p
p = &array[i];	//将数组 array 的第 i 个元素的地址赋给 p
p = max;		//max为已定义的函数,将 max 的入口地址赋给 p
p1 = p2 ;		// p1 和 p2 都是指针变量,将 p2 的值赋给 p1

注意,以下是错误的:
p = 1000;		//指针不能赋实值

(3)指针变量可以有空值,即该指针变量不指向任何变量;

p = NULL;

(4)两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数;但不能相加。
(5)两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比较。指向前面的元素的指针变量“小于”指向后面的元素的指针变量

结构体和共用体

在实际问题中,一组数据往往具有不同的数据类型。当然不能用一个数组来存放这种数据。因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。

C语言中给出了另一种构造数据类型——“结构(structure)“或叫"结构体”。"结构体”是一种构造类型,它是由若干"成员"组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。
定义一个结构体:

struct 结构体名
{
	成员表列;
};

例如,

struct stu
{
	int num;
	char name[20];
	char sex;
	float score;
};

1 .结构体变量的声明
(1)先定义结构体,再声明结构体变量。

struct stu
{
	int num;
	char name[20];
	char sex;
	float score;
};
struct stu boy1, boy2;	//声明了两个变量boy1和boy2为stu结构类型。

或使用宏定义使一个符号常量来表示一个结构体类型。

#define STU struct stu
STU
{
	int num;
	char name[20];
	char sex;
	float score;
};
STU boy1, boy2;	//声明了两个变量boy1和boy2为stu结构类型。

(2)在定义结构体类型的同时说明结构体变量。

struct 结构名
{
	成员表列;
}变量名表列;

如,

struct stu
{
	int num;
	char name[20];
	char sex;
	float score;
}boy1, boy2;

(3)直接说明结构体变量

struct 
{
	成员表列;
}变量名表列;

如,

struct
{
	int num;
	char name[20];
	char sex;
	float score;
}boy1, boy2;

2 . 结构体变量成员的表示方法:
在程序中使用结构体变量时,往往不把它作为一个整体来使用。一般对结构体变量的使用,包括赋值、输入、输出、运算等都是通过结构体变量的成员来实现的。
结构体变量成员的一般形式为:结构体变量名.成员名
(1)结构体变量的赋值:

boy1.num = 32;
boy1.name = "大白";

(2)结构体变量的初始化:结构体的初始化与数组的初始化类似——只要将元素列在一对大括号中,每个元素之间用逗号隔开。

struct stu boy1 = {32, "大白", "女", 12.5};
struct stu boy1 = {.num=32, .score=12.5}	//允许以任意顺序对成员进行初始化,也可以仅初始化指定成员。
boy2 = boy1;

复合字面量:在单个语句中为一个结构体指定一个或多个值。
struct date
{
	int year;
	int month;
	int day;	
};
today = (struct date) {2022, 12, 1};
或
today = (struct date) {.month=12, .year=2015, .day=1};

3 .结构体数组:
数组的元素也可以是结构体类型的,因此可以构成结构体型数组。结构体数组的每一个元素都是具有相同结构体类型的下标结构体变量。

struct date Date[5];	//定义了一个结构体数组,共 5 个元素

4 .结构体指针变量
(1)指向结构体变量的指针:
一个指针变量当用来指向一个结构体变量时,称之为结构体指针变量。结构体指针变量中的值是所指向的结构体变量的首地址。通过结构体指针即可访问该结构体变量。

结构体指针变量声明的一般形式为:struct 结构体名 *结构体指针变量

结构体变量的成员访问:

  1. 结构体变量.成员名
  2. (*结构体指针变量).成员名
  3. 结构体指针变量->成员名
struct date *pdate; 	//定义一个指向 date 的指针变量 pdate
pdate = &today;		//赋值是把结构变量的首地址赋予该指针变量;

不能把结构名赋予该指针变量:
pdate = &date;		//错误

访问结构体变量的成员:
today.year;
(*pdate).year;		// 因为成员符”.”的优先级高于“*",所以不能去掉括号。
pdate->year;

(2)指向结构体数组的指针:
指针变量可以指向一个结构体数组,这时结构体指针变量的值是整个结构数组的首地址。结构体指针变量也可指向结构数组的一个元素,这时结构体指针变量的值是该结构体数组元素的首地址。

struct date Date[3] = {
	{2011, 11, 2},
	{2012, 12, 13},
	{2022, 10, 11}
};
struct date *pdate;
pdate = Date;
pdate = &Date[0];

pdate 为指向结构体数组的指针变量,则 pdate 也指向该结构体数组的 0 号元素,pdate+1指向1号元素,pdate+i 则指向 i 号元素。这与普通数组的情况是一致的。
注意:
一个结构体指针变量虽然可以用来访问结构体变量或结构体数组元素的成员,但是,结构体指针变量不能指向一个成员。也就是说不允许取一个成员的地址来赋予它。

错误:
pdate = &Date[1].year;		//错误的

(3)结构体指针变量作函数参数:
虽然允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。
因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传向形参的只是地址,从而减少了时间和空间的开销。采用指针变量作运算和处理,故速度更快,程序效率更高。

//定义求平均日期的函数;

struct date Date[3] = {
	{2011, 11, 2},
	{2012, 12, 13},
	{2022, 10, 11}
};

void avg(struct date *pdate);	

int main()
{
	struct date *pdate;
	void avg(struct date *pdate);	
	pdate = Date;
	avg(pdate);
}

动态内存分配
C语言中,数组的长度是预先定义好的,在整个程序中固定不变,即不允许动态数组类型。但是在实际的编程中,所需的内存空间取决于实际输入的数据,而无法预先确定。因此,以内内存管理函数,常用的有:
(1)内存空间分配函数 malloc
调用形式:(类型说明符*)malloc(size)
其功能是在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
其中,“类型说明符”表示把该区域用于何种数据类型; ( 类 型 说 明 符 ∗ ) (类型说明符*) () 表示把返回值强制转换为该类型指针;“size"是一个无符号数。

pc = (char *)malloc(100);
//表示分配100个字节的内存空间,并强制转换为字符数组类型,
//函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc.

(2)内存空间分配函数 calloc
调用形式:(类型说明符*)calloc(n, size)
其功能为在内存动态存储区中分配 n 块长度为"size"字节的连续区域。函数的返回值为该区域的首地址;calloc函数与malloc函数的区别仅在于一次可以分配n块区域。

ps = (struct date *)calloc(2, sizeof(struct date));
//其中的sizeof(struct date)是求date的结构长度。
//该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps.

(3)内存空间释放函数 free
调用形式 free(指针名);
其功能为释放指针所指向的一块内存空间,该指针指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域

枚举类型
在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。
为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。注意,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
(1)枚举类型的定义:enum 枚举名{枚举值表};在枚举值表中应罗列出所有可用值,这些值被称为枚举元素。
(2)枚举变量的声明:

enum weekday{sun, mou, tue, wed, thu, fri, sat};		//定义枚举类型
enum weekday a, b, c;			//先定义,后声明
enum weekday{sun, mou, tue, wed, thu, fri, sat}a, b, c;		//同时定义和声明
enum {sun, mou, tue, wed, thu, fri, sat}a, b, c;		//直接声明

(3)枚举变量的赋值:
a. 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值
b. 枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…,如在weekday中,sun值为0,mon 值为1,…, sat 值为6。
c. 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量
d. 如一定要把数值赋予枚举变量,则必须用强制类型转换。
e. 枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。

a = sum;	b = mon;		//正确的
a = 0;	b = 1;		//错误的
a = (enum weekday)2;		//强制类型转换,把数值赋予枚举变量,相当于 a = tue

类型定义符 typedef
C语言还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。

其一般形式为:typedef 原类型名 新类型名,其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。

typedef int INTEGER 		//用INTEGER来代替int 作整型变量的类型说明了。
INTEGER a, b;		  		//等价于 int a, b

用 typedef 定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。

typedef char NAME[20];		//表示NAME是字符数组类型,数组长度为 20
NAME a1, a2, a3;		//等效于 char a1[20], a2[20], a3[20]

typedef struct stu
{
	char name[20];
	int age;
	char sex;
}STU;
STU boy1, boy2;		//定义STU表示stu的结构类型,然后可用STU来说明结构变量

有时也可用宏定义来代替 typedef 的功能,但是宏定义是由预处理完成的,而 typedef 则是在编译时完成的,后者更为灵活方便。

位运算

位运算符

符号作用
&按位与
I按位或
^按位异或
~取反
<<左移
>>右移

按位与运算
按位与运算符 “&” 是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1,否则为0。参与运算的数以 补码 方式出现。eg. 9&5=1
在这里插入图片描述

按位或运算
按位或运算符 “|” 是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。eg. 9|5=13
在这里插入图片描述

按位异或运算
按位异或运算符 “^” 是双目运算符。其功能是参与运算的两数各对应的二进位相异
或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现。eg.9^5=12
在这里插入图片描述
求反运算
求反运算符 “~” 为单目运算符,具有右结合性。其功能是对参与运算的数的各二进位按位求反。eg. ~9
在这里插入图片描述
左移运算
左移运算符 “<<” 是双目运算符。其功能把 “<<” 左边的运算数的各二进位全部左移若干位,由 “<<” 右边的数指定移动的位数,高位丢弃,低位补0。

a<<4	//若a=00000011(十进制3),左移 4 位后为 00110000(十进制48)
等价于 a*(24次方)

右移运算
右移运算符 “>>” 是双目运算符。其功能是把 “>>” 左边的运算数的各二进位全部右移若干位, “>>” 右边的数指定移动的位数。低位丢弃,高位补0。

a=15;		//二进制 00001111
a>>2;		//右移2位,00000011(十进制3)

应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补 0 或是补 1 取决于编译系统的规定。TurboC和很多系统规定为补1。

位域(位段)

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为 “位域” 或 "位段” 。

所谓 “位域” 是 把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

位域定义的形式:

struct 位域结构名
{
	位域列表;
};

其中,位域列表的形式为:

类型说明符 位域名:位域长度

位域的使用(位域的使用和结构成员的使用相同):位域变量名.位域名

struct bs
{
	int a:8;
	int b:2;		
	int c:6;		
}data, *pb;
//说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。
data.a = 12;
data.b = 3;
data.c = 24;

pb = &data;
pb->a = 0;

位域变量的说明与结构变量说明的方式相同。可采用先定义后说明,同时定义说明或者直接说明这三种方式。

对于位域定义的说明:
(1)一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。

struct bs
{
	unsigned a:4;		//取值范围为 0~15
	unsigned :0;			// 空域
	unsigned b:2;		//取值范围为 0~3
	unsigned c:6;		//取值范围为 0~63
};
//在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用2位,c占用6位。

(2)由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。

(3)位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。位域在本质上就是一种结构类型,不过其成员是按二进位分配的。

struct k
{
	int a:1;
	int :2;		//该 2 位不能使用
	int b:3;
	int c:2;
};

文件

C 文件概述:所谓“文件”是指一组相关数据的有序集合。这个数据集有一个名称,叫做文件名。文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来。

从不同的角度可对文件作不同的分类。从用户的角度看,文件可分为普通文件和设备文件两种。普通文件 是指驻留在磁盘或其它外部介质上的一个有序数据集,可以是源文件、目标文件、可执行程序;也可以是一组待输入处理的原始数据,或者是一组输出的结果。对于源文件、目标文件、可执行程序可以称作程序文件,对输入输出数据可称作数据文件。设备文件 是指与主机相联的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。

通常把显示器定义为 标准输出文件,一般情况下在屏幕上显示有关信息就是向标准输出文件输出。如前面经常使用的printf, putchar函数就是这类输出。键盘通常被指定为 标准输入文件,从键盘上输入就意味着从标准输入文件上输入数据。scanf, getchar函数就属于这类输入。

从文件编码的方式来看,文件可分为 ASCII 码文件二进制码文件 两种。ASCII 文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII 码;二进制文件是按二进制的编码方式来存放文件的。
在这里插入图片描述
ASCII 码文件可在屏幕上按字符显示,例如源程序文件就是ASCII 文件,用DOS命令TYPE可显示文件的内容。由于是按字符显示,因此能读懂文件内容。二进制文件虽然也可在屏幕上显示,但其内容无法读懂。

C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。因此也把这种文件称作 “流式文件”。

文件指针

在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。

其定义的一般形式为:FILE *指针变量标识符;

其中 FILE 为大写,它实际上是由系统定义的一个结构,该结构中含有文件名、文件状态和文件当前位置等信息,在编写源程序时不必关心FILE结构的细节。

FILE *fp;
//表示fp是指向FILE结构的指针变量,通过fp即可找存放某个文件信息的结构变量,然后按结构变量提供的信息找到该文件,实施对文件的操作。
//习惯上也笼统地把fp称为指向一个文件的指针。

文件的打开与关闭
文件在进行读写操作之前要先打开,使用完毕要关闭。所谓打开文件,实际上是建立文件的各种有关信息,并使文件指针指向该文件,以便进行其它操作。关闭文件则断开指针与文件之间的联系,也就禁止再对该文件进行操作。在C语言中,文件操作都是由库函数来完成的。

文件打开( fopen 函数)
其使用的一般形式为:文件指针名 = fopen(文件名,使用文件方式);

其中,文件指针名必须是被说明为FILE 类型的指针变量;文件名是被打开文件的文件名;使用文件方式是指文件的类型和操作要求;文件名是字符串常量或字符串数组。

FILE *fp;
fp = fopen("c:\\abc2", "rb"); 	//表示打开 C 盘下的 abc2 (二进制)文件,只允许进行读操作;
//两个反斜线 “//” 中的第一个表示转义字符,第二个表示根目录。

使用文件的 12 种方式
在这里插入图片描述
对于文件使用方式的几点说明:
(1)共由 6 个字符 r, w, a, t, b, + 拼接而成,各个字符的含义为:

字符含义
r(read)
w(write)
a(append)追加
t(text)文本文件(可省略)
b(banary)二进制文件
+读、写

(2)凡用 “r” 打开一个文件时,该文件必须已经存在,且只能从该文件读出;
(3)用 “w” 打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件;
(4)若要向一个已存在的文件追加新的信息,只能用 “a” 方式打开文件。但此时该文件必须是存在的,否则将会出错。
(5)在打开一个文件时,如果出错,fopen 将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:

FILE *fp;
if(fp = fopen("c:\\abc2", "rb")==NULL)
{
	printf("\nerror open!");
	getch();		//从键盘输入一个字符,但不在屏幕上显示;
	//(其作用为等待,只有当用户从键盘任意敲一个键时,程序再继续执行)
	exit(1);		//敲键后执行 exit(1) 退出程序
}

(6)把一个文本文件读入内存时,要将ASCII 码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII 码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
(7)标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用。

文件关闭( fclose 函数)fclose(文件指针);
正常完成关闭文件操作时,fclose 函数返回值为0。如返回非零值则表示有错误发生。

文件读写:

字符读写函数:以字符(字节)为单位的读写函数。每次可从文件读出或向文件写入一个字符。
1 . 读字符函数 fgetc,从指定文件中读一个字符,形式为:字符变量 = fgetc(文件指针);

ch = fgetc(fp);		//从打开的文件fp中读取一个字符并送入ch 中。

说明:
(1)在 fgetc 函数调用中,读取的文件必须是以读或读写方式打开的;
(2)读取字符的结果也可以不向字符变量赋值,但这样读出的字符不能保存;
(3)在文件内部有一个位置指针。用来指向文件的当前读写字节。在文件打开时,该指针总是指向文件的第一个字节。使用fgetc函数后,该位置指针将向后移动一个字节。因此可连续多次使用fgetc函数,读取多个字符。应注意文件指针和文件内部的位置指针不是一回事。文件指针是指向整个文件的,须在程序中定义说明,只要不重新赋值,文件指针的值是不变的。文件内部的位置指针用以指示文件内部的当前读写位置,每读写一次,该指针均向后移动,它不需在程序中定义说明,而是由系统自动设置的。

2 . 写字符函数 fputc,把一个字符写入指定的文件中,形式为:fputc(字符量, 文件指针);
其中,待写入的字符量可以是字符常量或变量。

fputc('a', fp);		//把字符 a 写入 fp 所指向的文件中

说明:
(1)被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,写入字符从文件首开始。如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件。被写入的文件若不存在,则创建该文件。
(2)每写入一个字符,文件内部位置指针向后移动一个字节。
(3)fputc 函数有一个返回值,如写入成功则返回写入的字符,否则返回一个EOF。可用此来判断写入是否成功。

字符串读写函数
1 . 读字符串函数 fgets,从指定的文件中读一个字符串到字符数组中,形式为:fgets(字符数组名, n, 文件指针);
其中的n是一个正整数。表示从文件中读出的字符串不超过n-1个字符。在读入的最后一个字符后加上串结束标志 ’\0’ 。

fgets(str, n, fp);		//从 fp 所指的文件中读出 n-1 个字符送入字符数组 str 中

说明:
(1)在读出n-1个字符之前,如遇到了换行符或EOF,则读出结束。
(2)fgets 函数也有返回值,其返回值是字符数组的首地址。
2 . 写字符串函数 fputs,向指定的文件写入一个字符串,其形式为:fputs(字符串, 文件指针);
其中字符串可以是字符串常量,也可以是字符数组名,或指针变量。

fputs("abcd", fp);		//将字符串 abcd 写入 fp 所指的文件中

数据块读写函数
读数据块函数:fread(buffer, size, count, fp);
写数据块函数:fwrite(buffer, size, count, fp);
其中,buffer 是一个指针,在fread函数中,它表示存放输入数据的首地址。在fwrite函数中,它表示存放输出数据的首地址;
size表示数据块的字节数;
count表示要读写的数据块块数;
fp表示文件指针。

fread(fa, 4, 5, fp);
//从fp所指的文件中,每次读4个字节(一个实数)送入实数组fa中,连续读5次,即读5个实数到fa中。

格式化读写函数
fscanf函数,fprintf 函数与前面使用的scanf和 printf函数的功能相似,都是格式化读写函数。两者的区别在于fscanf 函数和 fprintf函数的读写对象不是键盘和显示器,而是磁盘文件

格式化读函数:fscanf(文件指针,格式字符串, 输入表列);
格式化写函数:fprintf(文件指针,格式字符串, 输出表列);

文件的随机读写

顺序读写,即读写文件只能从头开始,顺序读写各个数据。但在实际问题中常要求只读写文件中某一指定的部分。为了解决这个问题可移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写称为随机读写。实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。

文件定位
1 . rewind 函数,将文件内部的位置指针移动到文件首。形式为:rewind(文件指针);

2 . fseek 函数,移动文件内部位置指针,形式为:fseek(文件指针, 位移量, 起始点);
注意, fseek 函数一般用于二进制文件。
其中,“文件指针” 指向被移动的文件;
“位移量"表示移动的字节数,要求位移量是long型数据,以便在文件长度大于64KB时不会出错。当用常量表示位移量时,要求加后缀”L”。
"起始点"表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾,其表示方法为:
在这里插入图片描述

fseek(fp, 100L, 0);		//把位置指针移动到离文件首 100 个字节出

文件检测函数
1 . 文件结束检测函数(feof 函数),功能为判断文件是否处于文件结束位置,如文件结束,则返回值为1,否则为0。
格式为:feof(文件指针);
2 . 读写文件出错检测函数(ferror 函数),功能为检查文件在用各种输入输出函数进行读写时是否出错。如 ferror 返回值为 0 表示未出错,否则表示有错。
格式为:ferror(文件指针);
3 . 文件出错标志和文件结束标志置 0 函数(clearerr 函数),用于清除出错标志和文件结束标志,使它们为 0 值。
格式为:clearerr(文件指针);

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值