目录
内部函数
外部函数
第1章 什么是C语言
C语言的先祖是BCPL语言。它是由美国贝尔实验室的D.M.Ritchie在1972-1973年间设计出来的计算机编程语言。C语言还是目前使用最为广泛的计算机编程语言之一,由于它学习起来简单、使用方便、功能强大和容易上手等特点,普遍被广大程序设计者使用。
第2章 算法
算法需要有以下特性:(1)有穷性:有限的操作步骤;(2)确定性:每个步骤都是确定的,不能含糊;(3)有零个或多个输入:从外界获取必要输入信息;(4)有一个或多个输出:一个没输出的算法没有意义;(5)有效性:每个步骤都能有效执行;
第3章 最简单的C程序设计
3.1 数据类型
3.1.1 常量
常量包括以下几种:整形常量、实型常量、字符常量、字符串常量和符号常量。整形常量如100等;实型常量有两种形式,一种是十进制小数形式如10.21,另一种是指数形式如12.34e3(代表12.34×10*3);字符常量包括普通字符如‘a’,和转义字符‘\n’。字符串常量是使用双撇号把若干个字符括起来,如“boy123”。符号常量是指使用#define指令,指定一个符号代表一个常量,如#define P1 3.12,这时P1代表3.12。
3.1.2 变量
变量代表一个有名字的、具有特定属性的一个存储单元,存储单元里的值是可以改变的,所以变量的值可以改变。在使用变量时必须先定义,后使用。在有些情况下我们不希望变量的值被改变,这时可以将变量定义为常变量。使用const可以将变量定为常变量,如const int P1=12,之后P1的值将不会被改变。
3.1.3 数据类型
在定义变量和函数时,需要指定相应的数据类型,数据类型包括:整型类型、浮点类型、枚举类型(enum)、空类型(void)和派生类型。
整型类型:注意整型中,区分有无符号的整型,无符号需要加上unsigned,如unsigned int a;
类型 | 字节 |
基本整形(int) | 2或4 |
短整型(short) | 2 |
长整型(long int) | 4 |
双长整型(long long int) | 8 |
字符型(char) | 1 |
布尔型(bool)值true 和 false | 1 |
浮点类型:
类型 | 字节 |
单精度浮点型(float) (有效6位) | 4 |
双精度浮点型(double) (有效8位) | 8 |
长双精度浮点型(long double) 有效(16位) | 16 |
复数浮点型 (不常用) |
派生类型:
指针类型(*) | 数组类型([ ]) | 结构体类型(struct) | 共用体类型(union) | 函数类型 |
数据以补码的形式存储再存储空间中。
补码求法:正·数的补码就是其二进制码如5的补码是0000 0000 0000 0101,负数的补码是其正数的二进制码取反,再加1,如-5,它的正数是5,5的二进制码是0000 0000 0000 0101,取反为1111 1111 1111 1010,再加1,得-5的补码是1111 1111 1111 1011。
3.2 运算符和表达式
经常混肴的运算符有:/(除法运算符)、%(求余运算符)、++和--(自增和自减运算符),例如++i(--i),它们在使用i之前,先使i的值加(减)1;i++(i--),它们在使用i之后,使i的值加(减)1。
强制类型转换符:使用强制类型转换符可以使一个表达式转换成所需的类型,一般形式如:(类型名)(表达式),例如(double)a,(float)(5%3)将5%3的值转换为float类型。
需要注意的运算符:= 赋值运算符、== 等于运算符、?:条件运算符、(sizeof)求字节数运算符、,->成员运算符、<<左移运算符、复合的赋值运算符,例如j×=y+8,等价于j=j×(y+8)等。
左移运算符(<<)
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
例:a = a << 2 将a的二进制位左移2位,右补0,
左移1位后a = a * 2;
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
右移运算符(>>)
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
操作数每右移一位,相当于该数除以2。
例如:a = a >> 2 将a的二进制位右移2位,
左补0 or 补1 得看被移数是正还是负。
注意: 当一个占多字节的整型数据,给一个占少字节的整型变量或字符变量赋值时,只能将它的低字节原封不动的送到被赋值变量,例如:a占4个字节,b占2个字节,当b=a时,只能把a的2位低字节赋给b。
3.3 数据的输入输出
3.3.1数据的输入
通过标准的输入函数完成,系统函数库中标准的输入函数有:scanf(格式输入)、gets(输入字符串)、getchar(输入字符)。
scanf函数的一般形式:scanf(格式控制,地址列表),如scanf(“a=%f,b=%f”,&a,&b);
gets函数的一般形式:gets(地址),如int a[10],gets(a),从键盘输入字符串,送到数组a中。
getchar函数的一般形式:a=getchar(),从键盘输入一个字符,送到字符变量a。
3.3.2数据的输出
标准输出函数有:printf(格式输出)、puts(输出字符串)、putchar(输出字符)
printf函数的一般格式:printf(格式控制,输出列表),如printf(“a=%d,ch[]=%s”,a,ch);a是变量名,ch是数组ch[]的数组名。
puts函数的一般格式:puts(地址),如int a[10],puts(a),这地址是指要输出数组的地址。
putchar函数的一般格式:putchar(字符),如char c=‘a’,putchar(c),输出字符变量c的值。
printf 的格式字符(scanf的格式基本一样)
d,i | o | x,X | u | c | s | f | e,E | g,G |
十进制输出整数 | 八进制 | 十六进制 | 无符号十进制输出整数 | 字符输出 | 字符串输出 | 小数形式输出单、双精度数 | 指数形式输出实数 | 选用%f或%e中输出宽度较短的一种格式 |
printf 和scanf 的格式附加字符
printf | printf | printf | scanf | scanf |
l | m(代表一个正整数) | n(代表一个正整数) | l | h |
用于长整型整数,可加于d,0,x,u前面 | 数据最小宽度 | 对实数时,表示输出n个小数,字符串时,表示截取字符个数 | 用于输入长整型数据,如%ld,%lx,%lo | 用于输入短整型数据,如%hd,%ho,%hx |
第4章 选择结构程序设计
4.1 if条件选择
if语句的一般形式是:if(表达式)语句1,如if(a+b>10) printf("yes");表达的意思是,如果a+b的值大于10,则执行printf函数输出yes。通过判断if表达式的真假,来判断是否执行语句1。
if语句一般与else语句一起使用,以实现更多功能,例如
if(表达式) 语句1;
else 语句;
例如 if(a>b) a=b;
else b=a;
这里表示如果a>b,则a=b,否则b=a;
if和else还可以嵌套使用,例如
if(number>50) a=1; else if(number>40) a=2; else if(number>30) a=3; else if(number>20) a=4; else a=5;
注意 : 关系运算符的优先级低于算术运算符
关系运算符的优先级高于赋值运算符
! | && | || |
逻辑非 | 逻辑与 | 逻辑或 |
!(逻辑非),如果a为真,则!a为假,如果a为假,则!a为真。
优先级:!(非)>&&(与)>||(或)
优先级:!(非)>算术运算符>关系运算符>&&(与)>||(或)>赋值运算符
4.2 switch多分枝选择
switch语句是多分枝选择语句,switch的一般形式如下:
switch(表达式) { case 常量1 :语句1 case 常量2 :语句2 case 常量n :语句n default :语句n+1 }
注意:switch后面括号内的“表达式”,其值的类型应为整数类型(字符型)
在执行switch语句时,根据switch表达式的值找到相应的入口标号,然后从相应的case中执行语句,在执行完入口标号的case语句后,程序会继续往下执行下面的case标号的语句,直到switch程序结束,所以一般情况下需要在case子句后面,应用break语句,使程序跳出switch结构。如果没有与switch表达式相匹配的case常量,流程会转去执行default标号后面的语句。
switch语句使用例子:
#include<stdio.h> int mian() { char ch; int a=0; ch=getchar(); switch(ch) { case 'a' : printf("a");break; case 'b' : printf("b");break; case 'c' : printf("c");break; default : printf("d"); } return 0; }
第5章 循环结构程序设计
5.1 for循环结构
for循环的一般形式: for(表达式1;表达式2;表达式3) 语句,例如
for(i=1;i<10;i++)
sum=sum+i;
其中表达式1可以省略,如for(;i<10;i++),但分号不能省。但是当表达式2省略时,表示无限循环。
for循环实现冒泡算法:
void xunhuan(int r[],int n) { int i,j,temp=0; for(i=1;i<n;i++) for(j=1;j<=n-i;j++) if(r[j]>r[j+1]) { temp=r[j]; r[j]=r[j+1]; r[j+1]=temp; } }
5.2 while循环结构
while实现循环有两种方式,一种是使用do.....while结构,另一种是使用while结构。
do.....while语句的一般形式为:
do
{语句}
while(表达式);
例如:
#include<stdio.h> int main() { int i=1,sum=0; do { sum=sum+i; i++; }while(i<=100); printf("sum=%d\n",sum); return 0; }
while语句的一般形式为:
while(表达式)语句;
例如,使用while和for语句实现的冒泡算法
#include<stdio.h> int main() { int i=0,j=0,flag=1,temp=0; int a[5]={10,2,57,7,98}; while((i<4)&&flag) { flag=0 for(j=0;j<4-i;j++) if(a[j]>a[j+1]) { temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; flag=1; } i++; } for{i=0;i<4;i++} printf("%d ",a[i]); return 0; }
注意:break语句和continue语句的区别是,在执行for或while循环语句时,通过break语句可以提前终止循环,跳出循环体。而continue语句只是提前结束本次循环,而不是终止整个循环,然后继续执行下一次循环。
第6章 数组
6.1 一维数组
数组是一组有序数据的集合,数组中的每个元素都是同一种类型。通过数组可以存储一组数据或多组数据。定义一维数组的一般形式为:类型符 数组名 [常量表达式];例如int a[10],定义了一个名为a的数组,它包含10个元素,它第一个元素是a[0]。当数组常量表达式为空时,数组的长度由初始化列表中元素的个数决定,例如int a[ ]={1,2},这时a数组的长度为2。
数组的常量表达式可以是常量和符号常量(#define P1 3.12,其中P1代表符号常量),但不能是变量。如果数组是在被调用的函数里面(不包括主函数)定义的数组,其长度可以是变量或非常量表达式。例如:
void fun(int n) { int a[2*n];//合法,n的值从实参传来 . . . }
这种情况称为“可变长数组”,允许每次调用fun函数时,n有不同的值,但在执行fun函数时,n的值是不变的。如果指定fun函数中的a数组为静态(static)存储方式,则不能用“可变长数组”,例如:static int a[2*n];这是不合法的,即可变长数组不能定义为静态存储方式。
一维数组的数组名代表数组的起始地址。一维数组的初始化可以用“初始化列表”或给数组元素逐个赋值,其中“初始化列表”的方式,如:int a[10]={0,1,2,3,4};,把初始化列表的数值,逐个赋给a数组的前5个元素,系统会自动给后面5个元素赋初值为0。给数组元素赋值,如a[0]=1;
数组中的元素引用,可以通过数组名加下标的方式,一般形式为:数组名[下标],下标可以是整型常量、整型的符号常量、整型表达式或者是整型变量,如int i=1; b=a[i]。
数组实现的冒泡算法
#include<stdio.h> int main() { int a[10]; int i,j,t; printf("input 10 numbers :\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(i=0;i<9:i++) for(j=0;j<9-i;j++) if(a[j]>a[j+1]) {t=a[j];a[j]=a[j+1];a[j+1]=t;} printf("the sorted numbers :\n"); for(j=0;j<10;j++) { printf("%d",a[j]); } printf("\n"); return 0; }
6.2 二维数组
二维数组定义的一般形式:类型符 数组名[常量表达式1][常量表达式2];
例如:float a[3][4],定义一个3×4(3行4列)的数组a,常量表达式1表示数组的行数,常量表达式2表示数组的列数。
二维数组可以看成一个特殊的一维数组,它的元素又是一个一维数组,例如,把a看为一维数组,它有3个元素:a[0],a[1],a[2],每个元素又是一个包含4个元素的一维数组,如a[0]包含:a[0][0],a[0][1],a[0][2],a[0][3]。a[0]、a[1]、a[2]可以看作三个一维数组名。二维数组元素的存放是按行存放的,先放第一行,再放第二行。在内存中二维数组的各元素是连续存放的,不是二维的,是线性的。
二维数组初始化和引用数组元素
二维数组的初始化形式:
1、给二维数组部分元素赋初值:如
int a[3][4]={ {1,2},[5],{9}}; 表示给第一行的1、2列分别赋值1和2,给第二、三行的第1列,分别赋值5和9,其余元素值自动为0;
2、把所有数据写在一个花括号中,按数组元素在内存中的排列顺序对各元素赋初值。
如int a[2][2]={1,2,3,4};
3、分行给二维数组赋初值。
例如:int a[2][2]={ {1,2},{3,4}};
4、如果给数组全部元素赋初值(即提供全部初始化数据),定义二维数组时,第1维(行)的长度可以不指定,但第2维(列)的长度不能省略。
如:a[ ][2]={1,2,3,4}; 定义一个2×2的数组a
二维数组元素的引用:二维数组元素表示形式,数组名[下标1][下标2],下标应是整型表达式,如 a[0][1]=1;b=a[0][1]; 把数组a的第0行,第1列的元素赋值给b,b=1。
6.3 字符数组
字符数组的一般形式与一维数组和二维数组的一样的,只是数据类型不同,例如,char a[2]={'I','k'};char a[ ]={I','k'};,这两个数组的长度都是一样的。char a[2][2]={ {'b','a'},{'c','d'}}; char a[][2]={ {'b','a'},{'d'}};,当字符数组初值的个数小于数组长度时,只将这些字符赋给数组中前面的元素,其余的元素自动定为空字符(即‘\0’)。
在C系统中,使用字符数组存储字符串常量时会自动加一个'\0'作为结束符,C语言规定了'\0'作为字符串结束标志,'\0'代表ASCII码为0的字符。例如char c[ ]={"C program"},字符串是存放在一维数组中的,在数组中它占10个字节,最后一个字节'\0'是由系统自动加上的。
注意:char c[ ]={"Cab"};不等于char c[3]={‘C’,'a','b'};前式长度为4,后式长度为3。
字符串的输入输出
字符串输入 char c[10]; 方式1 使用格式输入函数scanf() scanf("%s",c) ; //c是数组名,代表数组c的地址 方式2 使用gets()输入字符串函数 gets(c); //c是数组名,代表数组c的地址 字符串输出 char c[10]; 方式1 使用格式输出函数printf() printf("%s",c) ; //c是数组名,代表数组c的地址 方式2 使用puts()输出字符串函数 puts(c); //c是数组名,代表数组c的地址
字符串处理函数,使用时需要加头文件#include<string.h>
1、gets(字符数组) 输入字符串函数 gets(c); //c是数组名,代表数组c的地址
2、puts(字符数组) 输出字符串函数 puts(c); //c是数组名,代表数组c的地址
3、strcat函数——字符串链接函数 strcat(字符数组1,字符数组2)
char str1[30]={"ABC"}; char str2[]={"DE"}; printf("%s",strcat(str1,str2)); 输出:ABCDE (1)字符数组1必须足够大,以便容纳连接后的新字符串。如果字符数组1不够大,会出现问题。 (2)连接前两个字符串的后面都有'\0',连接时将字符串1后面的'\0'取消,只在新字符串最后保留'\0'。 (3)连接的新串放在字符数组1中,函数最后返回的是字符数组1的地址。
4、strcpy和strncpy函数——字符串复制函数 strcpy(字符数组1,字符串2)
char str1[10],str2[ ]={"china"}; strcpy(str1,str2);
执行后str1的状态如下:
c | h | i | n | a | \0 | \0 | \0 | \0 | \0 |
说明:
(1)、字符数组1的长度不应小于字符串2。
(2)、字符数组1必须写成数组形式,字符串2可以是字符数组名,也可以是字符串常量。
(3)、如果字符数组1没被赋初值,它各字节中的内容是未知的,复制时将str2中的字符串和其后的‘\0’一起复制到字符数组1中,取代其前6个字符,str1的最后4个字符并不一定为'\0',而是其原有的最后4个字节的内容。
(4)、不能使用赋值语句直接把字符串常量或字符数组给一个字符数组。
(5)、可以使用strncpy函数将字符串2中的前n个字符复制到字符数组1中,但n不应多于str1中原有的字符(不包括'\0')。
5、strcmp函数——字符串比较函数 strcmp(字符串1,字符串2)
strcmp(str1,str2); strcmp(str1,"Beijing"); strcmp("China","Korea");
说明
(1)比较规则,两个字符串自左至右逐个字符相比较(按ASCII码值大小比较),直至出现不同字符或遇到'\0'为止。
(2)全部字符相同,则两个字符串相等;
(3)若出现不同的字符,则以第1对不相同的字符的比较结果为准。
(4)字符串1=字符串2,则函数值为0;字符串1>字符串2,则函数值为一个正整数;字符串1<字符串2,则函数值为一个负整数。
6、 strlen函数——测字符串长度的函数 strlen(字符数组)
函数的值为字符串中的实际长度(不包括'\0'在内),例如
char str[10]={"china"}; printf("%d",strlen(str)); 输出:5
7、strlwr函数——转换为小写函数 strlwr(字符串)
8、strupr函数——转换为大写函数 strupr(字符串)
第7章 函数的使用
7.1 为什么使用函数
在编写一个程序时,往往不是只实现一个或两个功能,当程序功能多且复杂时,如果还是把功能的实现放在主函数里,会使得主程序变得复杂,使思路变得复杂,不利于查看和检查,而且有时一个程序需要多人一起完成,如果把系统功能实现放在主程序中,这样会不利于团队的合作。通过函数可以把程序的各功能分别在子函数中实现,然后通过主函数调用各子函数就可以实现系统的功能,在团队合作中,每个人只需把他需要完成的功能写在子函数中,最后通过主函数调用就能实现系统功能。使用函数可以大大简化程序功能的实现,有助理清思路,各功能可以互不干预。
7.2 函数的定义
定义无参函数:
类型名 函数名() 或 类型名 函数名(void)
{ {
函数体 函数体
} }
定义有参函数:
类型名 函数名(形式参数列表)
{
函数体
}
int max(int x,int y) { int z; //声明部分 z=x>y?x:y; //执行语句部分 return(z); }
函数体包括声明部分和语句部分。定义函数时的类型名是指定函数返回值的类型。
定义空函数:类型名 函数名() { } 例如void fun() { }
7.3 函数的调用方式
如果函数在主函数后定义,在函数调用前,需要对函数进行声明,声明方式是:类型名 函数名();例如:int max(int x,int y);,根据函数在程序中的位置和形式,函数调用分三种形式:
(1)函数调用语句 例如:fun();
(2)函数表达式 例如:c=2*max(a,b);
(3)函数参数 例如:m=max(a,max(b,c));
函数调用时,用到的实参和形参是直传递的,即形参的改变不会影响到实参。当形参类型是指针、数组、或变量的&引用时,形参的改变才会影响到实参。
函数的嵌套调用:
函数的嵌套调用是指,在主函数中调用a函数(子函数),然后在a函数中调用b函数(别的子函数),函数的执行流程是:在主程序中遇到a函数调用,则跳到a函数执行,在a函数遇到b函数调用,下一步跳到b函数执行,在b函数执行完后,程序返回a函数继续执行,a函数执行完后,程序返回主函数继续执行,直到整个主函数执行完毕。
函数的递归调用:
在调用一个函数的过程中又出现直接或间接地调用本函数本身,称为函数的递归调用。
int age(int n) { int c; if(n==1) c=10; else c=age(n-1)+2; return(c); }
7.4 数组作为函数参数
函数的实参可以是常量、变量或表达式,数组元素当实参与变量相当,向形参传递数组元素的值,从实参传到形参,单向传递(值传递)。此外,数组名也可以当实参和形参,传递的是数组第一个元素的地址(首地址),即实参和形参数组的地址相同,它们操作同一片存储空间,所以当形参改变时,实参也会跟着改变。
7.5 内部函数和外部函数
7.5.1 局部变量
局部变量包括:在函数内部定义的变量(包括主函数)、在复合语句内定义的变量、形参也是局部变量。它的作用域是定义函数内部、定义复合语句内部,例如在fun函数内定义的变量,它的作用域是fun函数。在for循环里定义的变量,它的作用域是for循环里面。
7.5.2 全局变量
在函数之外定义的变量称为外部变量,外部变量是全局变量,它的作用域是从定义变量的位置开始到本源文件结束,全局变量可以为本文件中其他函数所共用。
当定义的全局变量和局部变量同名时,在局部变量的作用域内,同名的全局变量会被同名的局部变量所屏蔽。例如在全局变量a=10,局部变量a=5,在局部变量a的作用域内,b=a,则b的值为5。
变量的存储方式:动态存储方式与静态存储方式
静态存储方式:指在程序运行期间由系统分配固定的存储空间的方式。
动态存储方式:指在程序运行期间根据需要进行动态的分配存储空间的方式。
供用户使用的存储空间分3部分:(1)程序区;(2)静态存储区;(3)动态存储区。数据分别存储在静态存储区和动态存储区中,全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。
在动态存储区中存放以下数据:
(1)函数形式参数。在调用函数时给形参分配存储空间。
(2)函数中定义的没有用关键字static声明的变量,即自动变量(auto变量)。
(3)函数调用时的现场保护和返回地址。
以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的。
在c语言中,每个变量和函数都有两个属性:数据类型和数据的存储类别。在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(系统自动指定为某一种存储类别)。数据类别指的是:数据在内存中的存储方式(静态存储和动态存储)。
C的存储类别包括4种,自动的(auto),静态的(statis)、寄存器的(register)、外部的(extern)。
1、自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存放在动态存储区中。函数中的形参和函数中定义的局部变量(包括复合语句中定义的局部变量)都是自动变量(auto变量)。
int fun(int a) { int b=3; //省略auto时,系统隐含指定为“自动存储类别(auto)”,b为自动变量 auto int c=2; //定义c为自动变量 . . }
当定义的自动变量不赋初值时,它的值是未知的。
2、静态局部变量(static)
静态局部变量属于静态存储类别,存放在静态存储区内分配的单元。对静态局部变量赋初值是在编译时完成的(即只赋初值一次),在以后函数调用时,不再重新赋初值,而是保留上一次调用函数结束时的值。例如
int a() { static int i=1; printf{"%d",i}; i=i+1; }
第一次调用a函数时,i的初值为1,输出i为1;第二次调用a函数输出的i为2,保留上一次函数结束时的值。
当定义的静态局部变量不赋初值时,编译时自动赋初值为0(对数值型变量)或'\0'(对字符变量)
3、寄存器变量(register变量)
寄存器变量存放在CPU中的寄存器中,它不是存放在内存中的。由于寄存器变量存放在cpu中的寄存器中,所以数据的存取速度远高于存放在内存中的数据的存取。使用寄存器变量可以提高执行效率。定义寄存器变量的一般形式:register int f;
4、全局变量(外部变量)
全局变量都是存放在静态存储区中的。它的作用域一般是从定义全局变量处开始,到本程序文件的末尾。当定义外部变量的位置,不是位于文件开头时,在外部变量定义处之前的程序是不能引用外部变量的,如果要引用外部变量,需要在引用之前使用关键字extern对该变量进行“外部变量声明”
把外部变量的作用域扩展到其他文件的方式,可以在其他文件中使用extern对外部变量进行声明。例如
文件file1.c #include<stdio.h> int a; //定义外部变量 int b; //定义外部变量 int main() { . . . } 文件file2.c extern int a; //把文件file1.c中的外部变量a的作用域扩展到file2.c中 extern b; //把文件file1.c中的外部变量b的作用域扩展到file2.c中 int fun() { . . . }
把外部变量的作用域限定在本文件中,只能被本文件引用,而不能被其他文件引用,可以通过,在定义外部变量时加一个statis声明来实现。通过statis定义的静态外部变量只能作用于本文件,当其他文件也定义了相同名字的外部变量时,它们之间的互不干预的。例如
文件file1.c #include<stdio.h> static int a; //定义静态外部变量 int main() { . . . }
5、小结
(1)从作用域角度分,有局部变量和全局变量。它们的存储类别如下:
局部变量 | 自动变量,即动态局部变量(离开函数,值就消失) |
静态局部变量(离开函数,值仍然保留) | |
寄存器变量(离开函数,值就消失) | |
形式参数可以定义为自动变量或寄存器变量 | |
全局变量 | 静态外部变量(只限本文件引用) |
外部变量(即非静态的外部变量,允许其他文件引用) |
(2)从变量生存时间(生存期)来区分,有动态存储和静态存储两种类型。
动态存储 | 自动变量(本函数内有效) |
寄存器变量(本函数内有效) | |
形式参数(本函数内有效) | |
静态存储 | 静态局部变量(函数内有效) |
静态外部变量(本文件内有效) | |
外部变量(用extern声明后,其他文件可引用) |
(3)从变量值存放的位置来区分。
内存中静态存储区 | 静态局部变量 |
静态外部变量(函数外部静态变量) | |
外部变量(可为其他文件引用) | |
内存中动态存储区 | 自动变量和形式参数 |
CPU中寄存器 | 寄存器变量 |
内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数(又称静态函数,因为它使用static声明的),它的作用域只局限于所在文件。这样,在不同文件中即使有同名的内部函数,也互不干扰。一般形式:static 类型名 函数名(形参列表),例如static int a(int a){...}
外部函数
如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。函数首部可以为:extern int fun(int a,int b)。C语言规定,如果在定义函数时省略extern,则默认为外部函数。
第8章 指针
8.1 什么是指针
指针指的是地址,一个变量的地址称为该变量的指针。一个变量专门用来存放另一个变量的地址,称为指针变量。指针变量的值是地址。
8.2 指针变量
存放地址的变量是指针变量,它用来指向另一个对象(如变量、数组、函数等)。定义指针变量的一般形式:类型名 *指针变量名;如int *a;char *b;定义了指向整型变量的指针变量a和定义了指向字符变量的指针变量b;
指针变量的初始化,可以在定义的同时初始化,也可以在定义后初始化,例如:
int a=0; int *b=a; //定义指针变量时初始化 int *c; c=&a; //定义指针变量后初始化,意思是把a的地址赋值给指针变量c,指针变量c指向变量a 错误赋值方式:int *d=&a; //错误,*d不是指针变量,d才是指针变量, 所以应该改为int *d=a;
一个变量的指针的含义包括两方面:一是以存储单元编号表示的地址,一是它指向的存储单元的数据类型。
8.3 指针的使用方式
8.3.1指针变量的使用
当指针变量指向一个变量时,那么指针变量就存储着所指向变量的地址,通过对指针变量的使用可以实现它所指向变量的功能。例如
int a=12; int *put=a; //定义指向变量a的指针变量put printf("a=%d ",a); printf("a=%d",*put); 输出指针变量put所指向的变量的值 结果:a=12 a=12
指针变量的使用例子:
输入两个数,按先大后小输出 #include<stdio.h> int main() { int *p1,*p2,*p,a,b; printf("please enter two integer numbers:"); scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(a<b) {p=p1;p1=p2;p2=p;} printf("a=%d,b=%d\n",a,b); printf("max=%d,min=%d\n",*p1,*p2); return 0; } 结果:please enter two integer numbers:5,9 a=9,b=5 max=9,min=5
8.3.2 指针变量作为函数参数
指针变量作为函数参数,例如:
#include<stdio.h> int mian() { int fun(int *a); //声明函数fun int a=8; int *b=a; fun(&a); fun(b); return 0; } int fun(int *a); { printf("%d ",*a); } 运行结果:8 8
当指针变量作为形参时,因为形参和实参所操作的存储单元是同一个的(它们指向同一个存储单元),所以形参值的修改也会导致实参值的修改。
8.3.3 通过指针引用数组
所谓数组元素的指针就是数组元素的地址,可以用一个指针变量指向一个数组元素,例如:
int a[5]={1,2,3,4,5}; int *p; p=&a[0]; 使指针变量p指向a数组的第0号元素。
在引用数组元素时指针的运算。因为指针的值是数组元素的地址,当指针p指向数组中的一个元素时,则p+1指向同一数组中下一个元素,p-1指向同一数组中的上一个元素。注意:执行p+1时,并不是将p的值(地址)简单的加1,而是加上一个数组元素所占用的字节数。例如,数组元素是float型,每个元素占4个字节,则p+1意味着是p的值(地址)加4个字节。
通过指针引用数组元素
指针法:如 *(a+i)或*(p+i),其中a是数组名,p是指向数组元素的指针变量,其初值p=a。
通过数组名计算数组元素地址,找出元素的值。例如:
#include<stdio.h> int main() { int a[10]; int i; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) printf("%d ",*(a+i)); //通过数组名和元素序号计算元素地址,再找到该元素 printf("\n"); return 0; } 第10行中(a+i)是a数组中序号为i的元素的地址,*(a+i)是该元素的值。
用指针变量指向数组元素
#include<stdio.h> int main() { int a[10]; int i,*p; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<(a+10);p++) printf("%d ",*p); //通过数组名和元素序号计算元素地址,再找到该元素 printf("\n"); return 0; }
注意:*p++,由于++和*同优先级,结合方向为自右向左,因此它等价于*(p++),先引用p的值,实现*p的运算,然后再使p自增1(p+1).
数组名作为函数参数:
int main() { void fun(int arr[],int n); //声明fun函数 int array[10]; . . . fun(array,10); return 0; } void fun(int arr[],int n) { . . . }
变量名和数组名作为函数参数比较:
实参类型 | 变量名 | 数组名 |
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量值 | 实参数组首元素的地址 |
通过函数调用能否改变实参的值 | 不能改变实参变量的值 | 能改变实参数组的值 |
通过指针引用多维数组,以int a[3][4]为例,a代表首行的首地址,a+1代表序号为1的行的首地址,a+1指向a[1](代表a数组的元素,但它又是一维数组),或者说a+1的值是a[1]的首地址。a[0],a[1],a[2]代表一维数组名,因此a[0]代表一维数组a[0]的第0列的地址(即&a[0][0])。
a[0]+3代表a[0][3]的地址(&a[0][3]),“a[0]+3中的a[0]代表数组名(地址)”。
a[0](代表一维数组a的元素,但它又是一维数组)和*(a+0)等价,a[ i ]和*(a+i)等价。因此a[0]+1和*(a+1)+1等价,都是&a[0][1]。进一步分析,a[0][1]的值,使用地址法为:*(*(a+0)+1)或*(*a+1)和*(a[0]+1)。
二维数组名(如a)指向行的。在指向行的指针前加一个*,就转换为指向列的指针,例如a+1(行指针),第一行的指针;*(a+1)为列指针,第一行第0列的指针。反之在列的指针前加&,就成为指向行的指针,例如&a[0],由于a[0]和*(a+0)等价,因此&a[0]和&*a等价,它是指向第0行的指针。
指向多维数组元素的指针变量
#include<stdio.h> int main() { int a[3][4]={....}; int *p; for(p=a[0];p<a[0]+12;p++) //a[0]是a[0][0]的地址,指针p指向a[0][0]元素 printf("%4d",*p); //输出p指的数组元素的指 return 0; }
定义指向“由n个元素组成的一维数组”的指针变量
#include<stdio.h> int main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int (*p)[4],j,i; p=a; printf("please enter row and colum :"); scanf("%d,%d",&i,&j); printf("[%d,%d]=%d\n",i,j,*(*(p+i)+j)); return 0; } 结果:please enter row and colum:1,2 a[1,2]=13
用指向数组的指针作函数参数
#include<stdio.h> int main() { void a(int *a); void b(int (*b)[4]); int c[3][4]={...}; a(*c); //*c是数组c的第0行第0列的地址 b(c); //c是数组c的第0行的地址 return 0 }
8.3.4 通过指针引用字符串
例子:
#include<stdio.h> int main() { char *string="I love china!"; printf("%s",string); return 0; } 输出:I love china!
注意:char *string="I love china!"; 等价于char *string; string="I love china!",把字符串第一个元素的地址赋值给字符指针变量string。
字符指针作函数参数,例如: void fun(char *p),调用时,实参可以是字符指针变量、字符数组名。
void fun(char a[ ]),调用时,实参可以是字符指针变量、字符数组名。
用字符指针作函数参数时,实参和形参的类型对应关系如下
实参 | 形参 | 实参 | 形参 |
字符数组名 | 字符数组名 | 字符指针变量 | 字符指针变量 |
字符数组名 | 字符指针变量 | 字符指针变量 | 字符数组名 |
用字符指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串,例如:
char *format; format="a=%d,b=%f\n"; printf(format,a,b); 它相当于 printf("a=%d,b=%f\n",a,b); 因此只要改变指针变量format所指的字符串,就可以改变输入输出的格式。 这种printf函数称为“可变格式输出函数”
8.3.5 指向函数的指针(函数指针变量)
函数代码也有存储空间,这段存储空间的起始地址称为这个函数的指针。定义一个指针变量指向函数,存放某一函数的起始地址,因为此指针变量指向该函数。这就是指向函数的指针(函数指针变量)。函数指针变量定义方式:类型名 (*指针变量名)(函数参数列表);例如:int (*p)(int ,int);,定义一个指向函数的指针变量p,它可以指向函数的类型为整型且有两个整型参数的函数。
可以通过指向函数的指针(函数指针变量)调用函数,例如
int max(int a,int b) {...} int (*p)(int ,int); p=max; 这时调用函数:max(1,2);等价于(*p)(1,2);
指向函数的指针变量可以作为函数参数。例如
void fun(int (*a)(int)) {...} int flie1(int a) {...} int (*b)(int); b=flie1; //指向flie1函数 fun(b); //指向函数的指针变量作实参 fun(flie1); //函数名作实参,函数名代表该函数存储空间的起始地址
8.3.6返回指针值的函数
定义返回指针值的函数的一般形式:类型名 *函数名(参数列表),
例如 int *fun(int n){...},
int *fun(int n) { int *p; n=n+1; p=n; return(p); //返回指针变量p所指的地址 }
8.3.7 指针数组
定义一维指针数组的一般形式:类型名 *数组名[数组长度],例如int *p[5],char *name[ ]={"abc","bcd"};
指向指针数据的指针变量的定义为 :类型名 **指针名,如char **p,p=name,其中name是指针数组的数组名,*p代表指针数组中存储的指针,**p代表指针数组中存储的指针所指的单元的值。
指针数组作main函数的形参:int main(int argc,char *argv[ ]),其中argc,argv,它们是程序的命令行参数(这是在系统操作命令下完成),argc是参数的个数。命令行的一般形式:命令名 参数1 参数2 ....参数n,其中命令名是可执行文件名,如,可执行文件flie1.exe,现想将两个字符串“china”,“beijing”作为传送给mian函数的参数,命令行可写为 flie1 china beijing,这时argc的值为3,参数的个数为3,argv[0]指向flie1字符串。
8.3.8 动态内存分配与指向它的指针变量
内存中的动态存储区也称之为"栈区"。在C语言中还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,可以随时开辟和关闭,这些数据是临时存放在一个特别的自由存储区,称之为“堆(heap)区”
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数,在使用这些函数时,需要添加头文件stdlib.h。
malloc函数,其函数原型为:void *malloc(unsigned int size),其作用是在内存的动态存储区中分配一个长度为size的连续空间,形参size是无符号整型,不允许有负数,该函数的返回值是所分配的第一个字节的地址。注意,指针的基类型为void,即不指向任何类型的数据,只提供地址。如果该函数未能成功执行(例如内存空间不足),则返回空指针(NULL)。
calloc函数,其函数原型为:void *calloc(unsigned n,unsigned size),其作用是在内存的动态存储区中分配n个长度为size的连续空间,这空间一般比较大,足可以保存一个数组。可以使用calloc为一维数组开辟动态存储空间,n代表数组元素个数,每个元素的长度为size。这是动态数组,该函数返回的是所分配域的起始位置的指针,如果分配不成功,则返回NULL。
free函数(无返回值),其函数原型为:void free(void *p),其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc和malloc函数时得到的函数返回值。例如free(p)。
realloc函数,其函数原型为:void *realloc(void *p,unsigned int size);其作用是改变通过malloc和calloc函数获得的动态空间的大小,通过realloc函数重新分配。用法说明:用realloc函数将p所指向的动态空间的大小改变为size,p的值不变。如果重新分配不成功,返回NULL。例如:realloc(p,50);
void 指针类型,定义一个void类型的指针变量,它不指向任何类型的数据,它是空类型或不指向确定类型,例如void *p; p=&a, printf("%d",*p),错误,因为p是无指向,不能指向a。
指针变量可以有空值,即不指向任何变量,例如p=NULL。
#include<stdio.h> #include<stdlib.h> int main() { void check(int *p); int *p1,i; p1=(int *)malloc(5*sizeof(int)); for(i=0;i<5;i++) scanf("%d",p1+i); check(p1); free(p1); return 0 } void check(int *p) { int i; printf("They are fail:"); for(i=0;i<5;i++) if(p[i]<60) printf("%d",p[i]); printf("\n"); } 结果: 67 98 59 78 57 They are fail:59 57
有关指针变量的归纳:
变量定义 | 类型表示 | 含义 |
int i; | int | 定义整型变量 |
int *p; | int * | 定义p为指向整型数据的指针变量 |
int a[5 ]; | int [5] | 定义整型数组a,它有5个元素 |
int *p[4]; | int *[4] | 定义指针数组p,它由4个指向整型数据的指针元素组成 |
int (*p)[4]; | int (*)[4] | p为指向包含4个元素的的一维数组的指针变量 |
int f(); | int () | f为返回整型函数值的函数 |
int *p(); | int *() | p为返回一个指针的函数,该指针指向整型数据 |
int (*p)(); | int (*) | p为指向函数的的指针变量,该函数返回一个整型值 |
int **p; | int ** | p是一个指针变量,它指向一个指向整型数据的指针变量 |
void *p | void * | p是一个指针变量,基类型为void(空类型),不指向具体的对象 |
第9章 自定义数据类型
9.1 结构体类型
结构体类型,是由用户根据自己的需要建立的数据类型。声明一个结构体类型的一般形式:struct 结构体名 {成员列表};例如:
struct student { int num; char name[20]; char sex; int ahe; float score; char addr[30]; struct Date birthday; //该成员的数据类型为Date结构类型 };
花括号内是该结构体的成员,成员列表也称为“域表”,每个成员是结构体中的一个域。成员名命名规则与变量名相同。它的成员可以是另一个结构体类型。
定义结构体类型变量
在建立结构体类型时,它只相当于一个模型,并没有定义变量,其中并无具体数据,系统不会对其分配存储单元。定义结构体类型变量的三种方式:
1、先声明结构体类型,再定义该类型的变量 例如:struct student student1;
2、在声明类型的同时定义变量:例如
struct student { 成员列表 }student1,student2; //定义结构体变量student1和student2
3、不指定类型名而直接定义结构体类型变量
其一般形式为 struct { 成员列表 }变量名列表;
初始化结构体变量
#include<stdi.h> int main() { struct student { long int num; char name[20]; char sex; }a={10101,"xiaoming",'m'}; //定义结构体变量a并初始化 return 0; } struct student a={10101,"xiaoming",'m'}; //定义结构体变量a并初始化 struct student a={.name="xiaoming"}; //定义结构体变量a,并对其成员a初始化,其他未被 指定初始化的数值型成员被系统初始化为0,字符型成员被系统初始化为'\0',指针型成员被系统初始化 为NULL。
引用结构体变量的成员:结构体变量名.成员名 例如studen1.num=10101;
如果成员本身又是结构体类型,则要使用若干个成员运算符,一级一级地找到最低的一级的成员。例如,student1.birthday.month(结构体变量student1中的成员birthday中的成员month)
同类的结构体变量可以互相赋值,如student1=student2;
可以引用结构体变量成员的地址,也可以引用结构体变量的地址。例如
scanf("%d",&student1.num); printf("%o",&student1); (输出结构体变量student1的首地址)
结构体数组
定义结构体数组和定义一般数组的方式类似,它的一般形式为:结构体类型 数组名[数组长度],或struct 结构体名 { 成员列表 } 数组名[数组长度];,它的每个成员都是结构体类型。
对结构体数组初始化的形式是在定义数组后面加上:={ 初值列表 };
例如:struct person leader[3]={ {“Li”,0},{"Zhang",0},{"Sun",0}};
结构体指针
指向结构体变量的指针,称为结构体指针,一个结构体变量的起始地址就是该结构体变量的指针。指向结构体变量的指针变量既可指向结构体变量,也可指向结构体数组中的元素。
如果p指向一个结构体变量stu,以下3种用法等价:
(1) stu.成员名(如stu.num)
(2) (*p).成员名(如(*p).num) //*p表示p指向的结构体变量
(3) p->成员名(如p->num)
(++p)->num //先使p自加1,然后得到p指向的数组元素中的num成员的值。
结构体变量和结构体变量的指针作函数参数
(1) 结构体变量的成员作实参,用法和普通的变量作实参一样。(值传递)
(2)结构体变量作实参。(值传递)
(3)用指向结构体变量(或数组元素)的指针作实参,将结构体变量的地址传给形参。
用指针处理链表
链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。链表可以根据需要开辟内存单元。链表有一个“头指针”变量head,它存放一个地址,该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包含两个部分:(1)用户需要用的实际数据;(2)下一个结点的地址。
链表中各元素在内存中的地址可以不连续,要找某一元素,可以通过它的上一个元素中的地址寻找。如果不提供头指针(head),则整个链表都无法访问。通过结构体变量建立链表最合适。例如:
struct student
{int num;
float score;
struct student *next; //next是指针变量,指向结构体变量,链表中用来指向下一个结点
};
建立简单的静态链表:
#include<stdio.h> struct student { int num; struct student *next; }; int main() {struct student a,b,c,*head,*p; a.num=1; b.num=2; c.num=3; head=&a; a.next=&b; b.next=&c; c.next=NULL; p=head; do { printf("%d\n",p->num) p=p->next; }while(p!=NULL) } 结果: 1 2 3
所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”。
建立动态链表
动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。
#include<stdio.h> #include<stdlib.h> #define LEN sizeof(struct student) //设置LEN的长度 struct student { long num; float score; struct student *next; }; int n; struct student *creat(void) //定义函数,此函数返回一个指向链表头的指针 { struct student *head,p1,p2; n=0; p1=p2=(struct student *)malloc(LEN); //开辟动态存储区,把起始地址赋给p1 scanf("%ld,%f",&p1->num,&p1->score); head=NULL; while(p1->num!=0) { n=n+1; if(n==1) head=p1; else p2->next=p1; p2=p1; p1=(struct student *)malloc(LEN); //开辟动态存储区,把起始地址赋给p1 scanf("%ld,%f",&p1->num,&p1->score); } p2-next=NULL; return(head); } int main() { struct student *pt; pt=creat(); //函数返回链表第一个结点的地址 printf("\nnum:%ld\nscore:%5.f\n",pt->num,pt->score); //输出第一个结点的成员值 return 0; } 结果: 1 65 2 50 3 34 0 0 num:1 score:65
9.2 共用体类型
共用体结构,用同一段内存单元存放不同类型的变量(存放共用体的成员,但同时只能放一个,保留最一个存放的变量)。
定义共用体类型变量的一般形式:
union 共用体名 例如 union Data
{成员表列 { int i;
}变量表列 ; char ch;
float f;
}a,b,c;
也可以类型声明和变量定义分开:例如:
union Data {int i; char ch; float f; }; union Data a,b,c;
共用体类型数据的特点,同一段内存可以几种不同的类型成员,但每一瞬间只能存放其中一个成员,而不是同时存放几个成员。
union Data {int i; char ch; float f; }; union Data a; a.ch='a'; a.f=100.2; a.i=97; //内存中最终存储97 printf("%d",a.i); //输出97 printf("%c",a.ch); //输出字符'a',ASCII码是97 printf("%f",a.f); //输出实数0.0000
9.3 枚举类型
声明枚举类型的一般形式为:enum[枚举名] {枚举元素列表};
定义枚举类型变量:例如:(1)先声明枚举类型enum Weekdat: enum Weekdat {sun,mon,tue,wed,thu,fir,sat};(2)在定义变量:enum Weekdat workday; workday变量的值只能是枚举元素列表中的枚举元素(也称枚举常量)。枚举元素列表中的枚举元素按顺序系统默认它们的值是0,1,2,3....。即workday=mon;相当于workday=1;
用typedef声明新类型名
简单地用一个新的类型名代替原有的类型名
例如: typedef int Integer;指定用Integer代表int类型。这样 int i;等价于 Integer i;
通过typedef可以命名一个简单的类型名代替复杂的类型表示方法,例如
sypedef struct { long int num; char name[20]; char sex; }student1; 声明了一个新类型名student1,代表上面的结构体类型。 定义结构体变量: student1 xisi;
命名一个新的类型名代表数组类型,例如typedef int Num[100];//声明Num为整型数组类型名
Num a;定义a为整型数组名,它有100个元素。
命名一个新的类型名代表指针类型,例如typedef char *String;//声明String 为字符指针类型
String p,s[10];//定义p为字符指针变量,s为字符指针数组。一般typedef声明新的类型名的开头用大写。
命名一个新的类型名代表指向函数的指针类型,typedef int (*Pointer)(); //声明Pointer为指向函数的指针类型,该函数的返回整型。Pointer p1,p2; //p1,p2为Pointer类型的指针变量。
第10章 对文件的输入输出(主要讨论数据文件)
10.1 什么是文件
文件有不同的类型,在程序设计中,主要用到两种文件:程序文件和数据文件。程序文件:包括源程序文件(后缀.c)、目标文件(后缀.obj)、可执行文件(后缀.exe)等,这些文件的内容是程序代码。数据文件,文件的内容不是程序,而是程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他设备)的数据,或在程序运行过程中供读入的数据,如:一批学生的成绩数据。
为了简化用户对输入输出设备的操作,使用户不必去区分各种输入输出设备之间的区别,操作系统把各种设备都统一作为文件处理。(例如键盘是输入文件、显示屏和打印机是输出文件)。所谓”文件“一般指存储在外部介质上数据的集合(一批数据是以文件的形式存放在外部介质(如磁盘)上的)。
流,输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流,即数据流。
10.2 文件分类
一个文件要有一个唯一的文件标识:包括(1)文件路径;(2)文件名主干;(3)文件后缀。如:D: \CC \temp \file1.dat
文件的分类:根据数据的组织形式,数据文件可分为:ASCII文件和二进制文件。数据在内存中以二进制的形式存储的,如果不加转换地输出到外存,就是二进制文件,可以认为它就是存储在内存的数据的映像,所以也称为映像文件。ASCII文件又称为文本文件(text file),每一个字节放一个字符的ASCII代码。
在磁盘上,字符一律以ASCII形式存储,数值型数据既可以用ASCII也可以用二进制存储。
10.3 文件的使用
ANSI C标准采用”文件缓冲系统“处理数据文件。所谓文件缓冲系统是指系统自动地在内存区为程序中每个正在使用的文件开辟一个文件缓冲区。文件缓冲区作用:程序数据区——输入(或输出)文件缓冲区——磁盘。
文件类型指针:FILE *文件名,如FILE *f1。它是指向一个文件的文件信息区(存放这文件的有关信息的地方)。
打开文件,所谓的”打开“,是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
fopen函数,打开文件。函数调用方法:fopen(文件名,使用文件方式),例如fopen("a1","r"),fopen(a2,“r”),FILE *fp=fopen(a3,“w”),a2、a3是存放文件名的数组名。
文件使用方式 | 含义 | 如果指定的文件不存在 |
”r“(只读) | 为了输入数据,打开一个也存在的文本文件 | 出错 |
"w"(只写) | 为了输出数据,打开一个文本文件 | 建立新文件 |
"a"(追加) | 向文本文件尾添加数据 | 出错 |
"rb"(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
"wb"(只写) | 为了输出数据,打开一个二进制文件 | 建立新文件 |
"ab"(追加) | 向二进制文件尾添加数据 | 出错 |
"r+"(读写) | 为了读和写,打开一个文本文件 | 出错 |
"w+"(读写) | 为了读和写,建立一个新的文本文件 | 建立新文件 |
"a+"(读写) | 为了读和写,打开一个文本文件 | 出错 |
"rb+"(读写) | 为了读和写,打开一个二进制文件 | 出错 |
"wb+"(读写) | 为了读和写,建立一个新的二进制文件 | 建立新文件 |
"ab+"(读写) | 为了读和写,打开一个二进制文件 | 出错 |
fclose函数,关闭文件。fclose函数调用的一般形式:fclose(文件指针),例如:fclose(fp);
对文本文件读写一个字符的函数
函数名 | 调用形式 | 功能 | 返回值 |
fgetc | fgetc(fp) | 从fp指向的文件读入一个字符 | 成功(返回读取的字符),失败(返回-1,即文件结束标志EOF) |
fputc | fputc(ch,fp) | 把字符ch写到文件指针变量fp所指向的文件中 | 成功(返回输出的字符),失败(返回-1,即文件结束标志EOF) |
读写一个字符串的函数
函数名 | 调用形式 | 功能 | 返回值 |
fgets | fgets(str,n,fp) | 从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中。 | 读成功(返回地址str),失败(返回NULL) |
fputs | fputs(str,fp) | 把str所指向的字符串写到文件指针变量fp所指向的文件中。 | 输出成功(返回0),失败(返回非0值) |
用格式化的方式读取文件:
fprintf(文件指针,格式字符串,输出列表);例如:fprintf(fp,"%d,%f",i,f);
fsanf(文件指针,格式字符串,输入列表);scanf(fp,"%d,%f",&i,&f);
用二进制方式向文件读取一组数据:它们一般调用形式:读入:fread(buffer,size,count,fp);输出:fwrite(buffer,size,count,fp);其中buffer是地址,size:要读写的字节数,count:要读写多少个数据项(每个数据项的长度为size)。注意,打开文件时要使用二进制形式,如:fopen(”file1“,”wb“)。
文件位置标记
(1)用rewind函数使文件位置标记指向文件开头。
(2)用fseek函数改变文件位置标记,fseek函数的调用形式为:fseek(文件指针,位移量,起始点),起始点可以用0、1、2代替,其中0代表文件开始位置,1代表当前位置,2代表文件末尾位置。起始点名字如表:
起始点 | 名字 | 用数字代表 |
文件开始位置 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件末尾位置 | SEEK_END | 2 |
位移量应是long型数据(在数字的末尾加一个字母L,就表示long型)
例如:fseek(fp,100L,0)将文件位置标记向前移到离文件开头100个字节处。
文件读写的出错检测
ferror函数,检查输入输出函数出错的情况,一般调用形式:ferror(fp);出错时返回非0值,未出错返回0值。注意,对同一个文件每一次调用输入输出函数,都会产生一个新的ferror函数值。
clearerr函数,作用是使文件错误标志和文件结束标志为0。一般调用形式:clearerr(fp)。