C语言-深度理解
经过几天的整理,我将C语言中一些难度比较高的语法和需要的注意点列出来,还有很多没有整理出来,比如最基本的数据类型、选择分支循环的语法,因为那些在C语言中使用得比较多也比较熟,所以就在此省略。
一、指针 (*p)
1、 数据类型的本质是固定内存块大小的别名。
指针也是一种数据类型,占4个字节,用来保存地址,指针指向谁就把谁的地址传给指针。
2、指针变量和他所指向的内存空间变量是两个不同的概念。
3、(*)就像一把钥匙,通过一个地址(&p),去修改p变量标识的内存空间,不断给指针赋值,相当于不停的改变指针的指向。
4、指针步长(p++)根据所指内存空间的数据类型来确定。
二、指针、数组、字符串
1、char *变量名 = "字符串内容"; //"字符串内容"是常量
C语言的字符串是以0(什么都不处理)作为结束的,内存可以分配在堆、栈、全局区。
2、char *str = "abc"//变量str存放的是字符串的首地址a的地址。
字符串指针:用来保存字符串首地址。
char *str = "aazazazaz";
3、指针数组来保存字符串,是保存的字符串常量地址,常量区是只读的,所以不能对他进行修改。
4、str = "i love you";
*(str+2)='x';//报错 因为字符串常量保存在常量区,不能被修改。
5、指针变量定义后没有赋初值会出现野指针。
6、键盘输入不能存放字符串,因为没有分配空间,字符串指针只是存放首地址。
7、二维字符数组类似于二维常量数组,第一维存的是每个字符串的首地址。
char a[2][3]={"ac","sd"};
a+1:表示一行的空间,跳到数组的下一行,多维数组名相当于一个数组指针,指向一个数组
*(a+i)代表第i行的首元素地址
*(a+i)+j ====== &a[i][j]
&(二维数组名)+1:代表二维数组最后一个元素的元素的下一个元素
8、多维数组做函数参数退化成一个数组指针a[3][5]退化成(*a)[5],a[i]退化成*a
9、数组首元素的地址和数组地址是两个不同的概念,数组名是个常量指针,为了能保 证局部变量内存能被安全释放,不能直接更改它的指向。
10、字符数组什么时候会自动添加'\0'?什么时候需要手动添加?
1)系统对字符串常量自动加一个'\0'作为结束符。
2)用字符串常量对字符数组进行初始化(自动加’\0’)。该方法只能给字符数组初始化,不能给字符数组赋值,字符数组的赋值只能对其元素一一赋值。
char str[30];
char *P=NULL;
P=str;
P="I am happy";
printf("%s\n",P);//数组长度为有效字符加1('\0')
3)char str[ ]={'I',' ','a','m',' ','h','a','p','p','y'};//系统不自动加’\0’
4)上面两种初始化不是等价的。因为用字符串常量初始化需要11个字节,单个赋值只有10个字节。
11、变长数组 int fun(int,int,arr[*][*]);如果省略维数变量,则方括号里要加*
三、指针数组(每个元素都是一个指针,int *p[5] )
1、typedef int (INT5)[5];构造新的数据类型,INT5 array;等价于int array[5];
2、定义:char *name[3] = {"aaaaaaa","sssss","ddddd"}存储字符串的首地址,对字符串长度没要求可以任意长,可用for输出。
3、数组名是常量不能赋值后重新赋值。
4、指针变量可以任意赋值。
四、数组指针(指向数组的指针,int (*p)[5] )
1、定义方法
第一种:用一个指针来指向数组
typedef int (INT5)[5];
INT5 *array
操作:(*array)[i]
第二种声明数组指针方法:
typedef int (*INT5)[5];
INT5 array;
操作:(*array)[i]
第三种方法:直接定义一个指向数组的指针变量
int (*array)[5];
char **p=NULL;
p = (char**)malloc(10*sizeof(char *));// char *array[10]
if(p == NULL)
return;
for(i=0;i<10;i++)
p[i] = (char *)malloc(30* sizeof(char));//相当于分配array[10][30]
//释放内存
for(i=0;i<10;i++)
{ free(p[i]);}
free(p);
2、使用数组指针赋值时一定要指向一个确定的地方,一般指向一个已定义的数组
五、指针与函数、数组
1、数组做函数参数的问题
int dimm(int array[])
{
return sizeof[array]/sizeof(*array);
}结果永远为1
数组做函数参数时会退化为一个指针,把数组的内存首地址和数组有效长度传给被调用函数。
函数的缺省认定:C语言会默认没有类型的函数参数为int
f(i,j)
{ return i+j;}
2、写在形参里的数组表面是一个数组,但实际上编译器会认定为是一个指针(首地址)处理,只会分配一个首地址,这是C语言的特点。
void fun(int (*p)[4]);等价于void fun(int p[][4]);空的方括号表示p是一个指针,多维数组中,首括号表示一个指针,其他的括号都要写数据
3、形参写在函数上和函数内本质是一样的,只是写在函数上有对外属性
4、不能对数组名进行直接复制与比较。下例中,若想把数组a 的内容复制给数组b, 不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy 进行复制。同理,比较b 和a 的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。例:
// 数组…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)
…
5、语句p = a 并不能把a 的内容复制指针p,而是把a 的地址赋给了p。要想复制a 的内容,可以先用库函数malloc 为p 申请一块容量为strlen(a)+1 个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。例:
// 指针…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)
6、指针做函数参数是研究重点
被调函数只能返回堆区、全局数据,主调函数可把堆区、全局数据、栈区的地址传递给被调函数。主调函数里分配的内存传入指针叫输入,被调函数里分配的内存返回指针叫输出(一定要分配内存,不能直接用一个指针变量去接收,因为指针只是一个地址不是一片内存)。指针既做输出又做输入时需要对它显式分配内存。
指针作函数参数的输入输出特性
1)、理解指针要把内存四区模型和函数调用模型相结合
前面已经讲述内存四区模型和函数调用模型、指针是为内存服务的,因此要深刻理解指针就要把指针和内存四区以及函数调用模型相结合理解。
2)、主调函数,被调函数
1、 主调函数可把堆区、栈区、全局数据内存地址传给被调用函数
2、被调用函数只能返回堆区、全局数据
3)、主调、被调函数内存分配方式----指针做函数参数输入输出特性
1、主调用函数分配内存,被调用函数使用内存---输入特性
2、被调用函数分配内存把结果输出给主调用(指针做函数参数和返回值做输出),
一般情况下都是分配堆区内存,然后由主调用函数析构该内存,这里注意避免野指针的产生
总结:
在项目开发中,深刻理解指针做函数参数,在主调和被调函数间的输入输出是关键点。
关键点1:主调和被调是哪个分配内存?
在C语言提供的库API函数中常见的字符串函数中通常能见到
char *strcpy( char *to, const char *from );
注意:const限定的函数参数一般是主调函数分配的内存,且该字符串做输入不能修改这也就是const在函数参数中出现的原因
一般情况有经验的程序员写函数接口的时候如果是指针做“输入”会加/*in*/注释更清晰易懂 char *strcpy( char *to/*in*/, const char *from/*in*/ );
关键点2:函数参数是输入还是输出,返回值是输出特性
六、间接赋值是指针存在的最大意义
1、 实参传到已初始化的形参不能修改实参的值想要修改实参变量(包括指针变量)的值只能将变量的地址传递给被调用函数,在以往传递实参本身时我们只是让它在被调函数里做一定的运算。
2、间接赋值的应用场景:定义一个实参和一个形参、两者建立联系、*p修改值。
3、 用1级指针去间接修改0级指针的值
用2级指针去间接修改1级指针的值
用3级指针去间接修改2级指针的值
七、野指针
1、野指针产生的原因:指针变量和指针变量指向的内存空间是两个概念。
释放了指针变量所指向的内存空间,但是指针变量本身并没有被重置成NULL,造成if(p1 != NULL)时出错,解决办法:释放free(p1);后将p1=NULL.
2、定义指针变量的同时最好初始化为null,用完指针后也将指针的值设置为null,也就是说除了在使用时,别的时间都把指针栓到0地址处,这样他就老实了。
3、int *p=(int*)malloc(100);//p为局部变量,保存在栈区,利用malloc函数又在堆区开辟空间,当把p的空间释放过后,在对p建立的空间未被释放形成野指针,需要进行一个释放指针的操作。
八、内存泄漏
1、在p被释放之前将malloc分配的空间释放(堆区中的),不然会出现内存泄漏
2、free(要释放空间首地址);//函数可以释放堆区空间
如:free(p);
3、在内存释放之后加上*p = NULL,相当将野指针拴在了NULL上.
九、const关键字
1、它是类型修饰符,类似unsigned short long
2、const修饰变量可以让变量的值不能改变
3、常类型是指使用函数修饰符const说明的类型,常类型的变量或对象的值是不能被修改的
4、const使用的地方:修饰变量,修饰指针,修饰数组
5、可以间接修改:
const int a=10;
int *p = &a;
*p = 100; //强制修改常量
printf("%d\n",a,*p);
输出结果为:10
10,100
6、const修饰的变量a赋给指针p,变量的指向地址可以改变但它的值不能改变。
例如:
int const*p1 = &a;
int const*p2 = &b;
p1 = p2;//该语句成立
p1 = 1000;//不能改变值(!!!)
7、const修饰的指针变量,指针变量的值可以变,地址不能变
例如:
int * const P2 = &a;
*p2 = 2000;//值可以改变
p1 = p2;//不可以改变地址(!!!!)
8、const修饰的指针变量值和地址都不能改变
const int * const p3 = &a;
p3 = &b;
*p3 = 100;
编译时上面两条语句都会报错,说明值和地址都不能改变
9、记忆的技巧:看const和*的位置
如果const在*的 左侧,此时表示指针变量指向变量的值不能变,地址能变
如果const在*的右侧,表示指针指向变量的值可以改变,指向的地址不能改变
如果const出现在*的两侧,表示指针变量的指向地址和值都不能变
十、指针函数
1、函数类型指的是返回值的类型,返回值是指针类型的函数就是指针函数
定义形式:
类型说明符 * 函数名(形参表)
{
函数体
return 地址;
}
2、举例:返回的是两个数中大数的地址
注意:(1)返回的是形参x和y的地址,而非实参,在程序入栈时实参传递的地址和形参分配的地址是不同的
(2)只有指针做函数参数时才能间接修改实参的值
传递出来的是形参的地址,跟实参没有任何关系,要实现可以将a、b的地址传给形参
证明了:调用函数的时候形参变量没有重新分配空间,实参和形参只是地址传递。
十一、函数指针(指向函数的指针)
1、 函数指针本质上是一个指针变量,指向一个同类型(返回值类型、参数个数、参数类型都相同)的函数。函数名也是指向函数第一条语句的常量指针。声明格式如下:
类型说明符 (*指针变量名)(参数列表);如: int (*fun) (int *p1 , int *p2);定义时形参名可以省略
2、 指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。
3、 一个指向函数的指针必须确保该函数被定义且分配了内存,否则它将指向一个空地址。
4、声明函数指针时,其返回值,参数个数,参数类型应该与需要它指向的函数保持一致;否则编译器无法通过。
5、利用函数指针指向某个函数的时候,我们只用,也只能给出该函数的函数名,不能把参数一并给出来
举例:
6、通过取别名重新构造类型
typedef double (*vp)(double &); vp p;等价于typedef double (*p)(double &);
7、 函数指针不能进行算术运算,普通指针变量加减是在数组中移动相应位置,而函数指针加减没任何意义。
十二、理解递归的两个重要节点
1、参数的入栈模型
2、函数嵌套调用返回流程
十三、结构体
1、结构体是一种数据类型,没有分配内存,只有在定义一个结构体变量才会分配内存
(.)操作符表示寻址操作,表示操作的对象对结构体的位置偏移量,在CPU中计算,没有操作内存,编译器允许直接将整个结构体赋值给另一个结构体(两个结构体类型一样)
2、构造的同时定义两个结构体变量
struct boy{
char name[30];
int age;
}boy1={"tianqingsong",21},boy2;
3、struct boy boy3; 先声明类型,后定义变量
4、结构体数组:
结构数组的没有一个元素都是具有相同结构类型下标结构变量
struct 结构名{
成员表列
}数组名[数组长度];
作用:用来存放大量结构相同的结构体变量
声明方式:定义结构体同时定义数组
先定义结构后定义数组 结构体初始化和遍历
初始化方法:定义同时初始化
5、 void *可以存储任何类型的数据
void指针意义:C语言规定只有相同类型的指针才可以相互赋值
void *作为左值用于接收任意类型的指针
作为右值赋值给其他类型时需要强制类型转换
char *p2 = NULL; p2 = (char *)malloc(100);//malloc返回值类型是void *类型
6、结构体指针:
定义一个结构体类型的指针,和结构体变量,通过赋值,该指针指向结构体,
用->表示成员。
程序中使用结构体类型指针引用结构体变量的成员,需要通过C提供的函数malloc()来为指针分配安全的地址。
7、结构体指针做函数参数和结构体变量做函数参数的区别,结构体变量不能修改实参的值,使用结构体指针时,传递的是地址,所以能修改对应地址的内容
8、字节对齐原则:
结构的数据成员,第一个放在offset为0的地方,以后每个数据成员存储的起始地址 要从该成员大小的整数倍开始
结构体的总内存大小必须是最大长度成员的整数倍,不足的要补齐。
如果一个结构里有其他结构的成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(如struct a里存有struct b,b里有char、 int、 double,那b应该从8的倍数倍开始存储)。
对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准,也就是说,结构体成员的偏移量应该取二者的最小值。
十四、关键字
1、extern和static
1)static与extern修饰局部变量
1、static和extern都是用来修饰变量,(局部的static实际也是全局的)
2、static修饰的变量只有你包含的那个变量定义的源代码文件可以访问(内部的变量)
3、extern定义的变量在所以源文件都可以访问 只要声明了的就可以(外部变量 )
4、static对局部变量的作用:
1)延长局部变量的生命周期 ,从程序启动到程序退出 ,但是它并没有改变变量的作用域
2)定义变量的代码在程序中仅仅会执行一次
5、extern不能修饰局部变量
6、static和extern修饰全局变量
2)全局变量(函数外部定义的变量):
内部变量:只能在本文件访问
外部变量:可以在其他文件中访问的变量,默认所有全局变量都是外部变量
static修饰的全局变量只能在当前文件中使用(内部变量),static可以在不同的文件中声明同名变量
extern修饰全局变量表示当前变量可以在本文件中使用也可在其他文件中使用
注意:eextern声明的全局变量在不同的文件中不能同名(两文件必须有 包含关系)在使用变量之前声明extern变量也可在函数体之前 加extern 变量名(此时extern可省略,省略后不是定义而是声明,extern函数也一 样)全局变量默认初始化为零
3)static和extern修饰函数
static修饰的函数:是一个内部函数,只能在本文件中使用(也可以通过在本文件中定义另一个非内部函数使用static函数,,然后在其他文件中使用新定义的非 内部函数)extern声明的函数可以在定义的文件中使用,也可在其他文件中使用
2、其他关键字
auto声明自动变量
union声明共用数据类型
enum声明枚举类型
typedef用以给数据类型取别名
register声明寄存器变量
volatile说明变量在程序执行中可被隐含地改变
if条件语句
else条件语句否定分支(与 if 连用)
switch用于开关语句
case开关语句分支
for一种循环语句
do循环语句的循环体
while循环语句的循环条件
goto无条件跳转语句
continue结束当前循环,开始下一轮循环
break跳出当前循环
default开关语句中的“其他”分支
sizeof计算数据类型长度
return子程序返回语句(可以带参数,也可不带参数)循环条件
十五、可变参数、函数与宏
1、可变参数分析
可变参数必须从头到尾按照顺序逐个访问参数列表中必须要存在一个确定的命名参数可变参数宏无法判断实际存在的参数的数量,可变参数宏无法判断参数的实际类型。
2、函数原型:float aver(int n,...);
va_list:用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明va_list类型的一个对象。
定义: typedef char * va_list;
va_start:访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和va_end使用;
va_arg:展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;
va_end:该宏使程序能够从变长参数列表用宏va_start引用的函数中正常返回。
va在这里是variable-argument(可变参数)的意思.
3、一个函数可以接受的参数可以不一定,参数可变函数在stdarg.h头文件
警告:在C语言中有些参数类型是不支持作为va_arg指定类型的如: char、
signed char、unsigned char、short、unsigned short、signed short、short int、signed short int、unsigned short int、float
4、函数和宏
宏的效率更高,因为是直接展开,无需再栈上创建活动记录多次使用宏会导致代码量增大(宏是直接展开)。
宏的参数可以是类型名:
#define MALLOC(type,n) (type*)malloc(n*sizeof(type))
如:int* p = MALLOC(int,5);
5、日志宏
宏表达式中不能出现递归定义
宏的作用域是在宏定义过后,要想限制宏的定义域则在某个地方进行#undef (xxx)
内置宏:_FILE_ 打印文件名
_LINE_ 打印当前行号
_DATE_ 打印日期
_TIME_ 打印编译时间_
打印日志只能用宏实现
十六、内存管理
1、内存区 :编译器为每个应用程序只建立一个内存区。
1)栈区(stack)保存程序局部变量,一般认为栈区开口向下。
栈区主要用于函数和调用的使用,栈空间最上面的是栈底,然后逐渐向下占用,最后是栈顶。局部变量不能返回一块地址。数组的地址永远是朝上和栈的生长方向不同。
测试栈区开口向上还是向下:
依次定义A B两个变量,确定入栈顺序。根据谁的地址大确定开口。
2)堆区(heap)主要用于内存的动态申请和释放,堆是程序中一块巨大的内存空间,堆空间通过申请才能获得,可由程序自由使用,堆中被程序申请使用的内存在程序主动 释放前将一直有效。
3)全局区(数据段)(global)常量和全局变量,操作系统管理。
程序的静态存储区主要用于保护程序中的全局变量和静态变量与堆、栈不同,静态存储区的信息最终会保存到可执行程序中,程序静态存储区随着程序的运行而分配空 间,直到程序运行结束。在程序的编译期静态存储区的大小已经被确定。
4)代码区(code)操作系统管理 程序执行的可编译代码的一块区域
5)BSS段 未初始化的全局变量和静态变量
2、 动态分配内存函数:malloc、calloc、realloc
1)malloc函数
void *malloc(unsigned size);
1、从内存的堆区分配size个连续内存空间,如果内存分配成功,返回内存首地址,失败返回NULL
2、Esp:从内存中申请一块内存空间,可以存储4个整数
int *p = (int *)malloc(4*sizeof(int));//赋值语句两侧类型应一样,malloc返回的是void*型,所以需要强制类型转换
if(p != NULL)
{程序段}
else
return;
3、使用函数给mallloc申请空间进行初始化:
memset(变量名,初始化内容,字节长度);
memset(p,'a',16);
4、 用malloc申请内存过后一定要立即检查指针值是否为NULL,杜绝操作0地址里的内容,因为一般它为操作系统所用。动态申请操作必须和释放操作相匹 配,防止内存泄漏和多次释放。
free指针之后必须立即赋值为NULL(习惯性,杜绝野指针)
2)calloc函数
calloc(块数,长度):分配了4块,每一块内存长度为四的连续内存空间
int *p = (int *)calloc(4,sizeof(int));
注意事项:calloc可以自动将申请的内存初始化为0
3)realloc函数
realloc函数用来给已存在的空间扩充大小,但是不太严谨,有可能从其他位置找空间给原来的空间
p = realloc(p,40*sizeof(int));
4)free函数
free(*p); 在使用完内存后要通过free函数释放掉动态申请的内存,p是要释放的内存块的首地址。
3、main函数可以在栈,堆,全局区上分配内存 可以被其他函数使用,
其他函数申请的内存能被main使用吗?
1)在栈上分配的的内存不能被其他函数和main使用
2)用malloc分配的内存(堆),可以被main和其他函数中使用
3)在全局区分配“abcdfeg”内存,可以被其他函数和main使用
十七、程序执行顺序点
1、函数参数的求值顺序依赖于编译器的实现
2、C语言中大多数运算符对其操作数求值的顺序都是依赖于编译器的实现
3、int i = f() * g(); 顺序不一定从左往右
4、程序中存在一定的顺序点,顺序点指的是执行过程中修改变量值的最晚时刻
在程序达到顺序点时候,之前所做的一切操作必须反映到后续的访问中
5、C语言中的顺序点:
1)每个完整表达式结束时(后置自增操作不会马上生效,而是记录着,当到达顺序点时将记录的操作返回,而前置操作马上就会完成)
2) && || ?: 以及逗号表达式的每个运算对象计算之后
3)函数调用中对所有实际参数的求值完成之后(进入函数体之前)例如: f(k,k++); i对应k j对应k++
调用此函数时,在它进入函数体之前有一个顺序点,所以i处的k就不会发生变化,j处的k不会马上生效并会记录一次后置自增操作,所以j处的k为1.当遇到顺序点时,k返回 k+1操作,让i处的k变成2
十八、优先级
.[]{}优先级高于*。
==和!=高于位操作符也高于赋值符。
算数运算符高于移位运算符。
逗号运算符优先级最低,逗号运算符的值就是最右边操作数的值。
在布尔操作、算术运算、位操作运算中应适当加括号使之思路更清晰。
所有的赋值符(包括复合赋值符)都具有右结合性,就是表达式最右边的操作数最先执行,从右到左依次执行。
位操作符(&和|)具有左结合性,从左到右依次执行。
所有优先级相同的操作符结合性也相同。
十九、条件编译概念和优点
1、按照不同的条件编译不同的代码,因而产生不同的目标文件。有利于程序的移植和调试
2、条件编译当然也可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译, 生成的目标代码很长,采用条件编译则根据条件 只编译其中的一段
3、发生在预处理阶段,生成目标文件(.o)大小不一样
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 用来判断某个宏是否定义,若已定义,执行随后的语句
#ifndef 用来判断某个宏是否定义,如果没定义就编译该段代码
#elif 若#if,#ifdef,#ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if,#ifdef,#define对应,若这些条件不满足,则执行#else之后的语句,相当于C中的else
#endif #if,#ifdef,#define这些命令条件的结束标志。
define 与#if,#elif配合使用,判断某个宏是否被定义
二十、编译器的工作
1、预编译
处理所有的注释,用空格代替。
将所有的#define删除,并且展开所有的宏定义。
处理条件编译指令#if,#ifdef,#elif,#else,#endif。
保留编译器需要使用的#pragma指令。
2、编译
对预处理文件进行一系列的词法分析,语法分析和语义分析。
词法分析主要分析关键字,标识符,立即数等是否合法。
语法分析主要分析表达式是否遵循语法规则。
语义分析在语法分析基础上进一步分析表达式是否合法。
分析结束后进行代码优化生成相应的汇编代码文件。
3、汇编
汇编器将汇编代码转变为机器可执行的二进制指令
每个汇编语句几乎都对应着一条指令。
二十一、文件操作
1、文件操作步骤和原理
1)引入头文件
2)定义文件指针
3)打开文件
if(fp != NULL)
操作文件
else
给用户提示
4)文件读写
5)关闭文件
2、文件操作函数:
1)字符读写函数 fgetc和foutc
2)字符串读写函数 fgets和fputs
3)数据块读写函数 fread和fwrite
4)格式化读写函数 fscanf和fprint
以上函数需包含头文件stdio.h
3、文件指针
文件指针:用一个指针变量指向一个文件的地址,文件指针指向文件的首地址。
一般设置一个指向FILE类型变量的指针变量,然后通过他来引用这些FILE类型变量通过文件指针就可对他所指的文件进行各种操作(读、写、追加信息等)
FILE *指针变量标识符;
FILE应为大写,是由系统定义的一个结构,包含文件名、文件状态、文件当前位置信息。
EOF和\0的区别:
\0表示字符结束,EOF是文件的东西,是一个标志,每个文件都有EOF,为一个宏定义—1 如:文件里的字符为:ABCD(-1)
4、文件使用方式及注意事项
使用方式:
r 打开已知文件 只读
w 创建一个新文件 只写 文件若存在则会将文件覆盖 先删后建
a 打开存在的文件进行追加 不能读取 文件不存在的话则创建这个文件准备写入数据
r+ 可读写
w+ 创建新文件读写
a+ 等价a可读
t 打开文本
b 打开二进制文件
注意事项:
带加号则可以读写。
把一个文本文件读入内存时,要将ASCLL码转换成二进制码,而把文件文件写入磁盘时,也要把二进制码转换成ASCLL码,因此文本文件的读写转换得花很多时间, 对二进制文件的读写则不存在这种转换。
4、文件操作函数
fgets();------>原型:char*fgets(char* ,int,FILE*);
fgets(数组名,数组长度,文件指针名);
从指定文件中读入一定数量的字符存到数组内,会在数组最后一个位置加\0,当文件内容读取完毕后会加结束符,遇到\n或EOF读取结束
与scanf、gets的比较:
1)char()scanf可以从键盘上接受一个字符串保存到数组中,不能接受空格字符
2)gets(str)可以接受空格。一个不安全的警告:比如数组长50,若存满 字符(50个),没有空间存放字符串结束符
3)fgets是一个安全的,char ch[5]使用它最多存放四个字符,会自动在数组中存放一个\0;当输入的字符串长度小于字符长度会接受回车相当于换行
fputs();------>原型: int fputs(const char * s,FILE * stream);
int fputs(要写入文件的字符串,文件指针名);
若写入成功则返回写入字符的个数,失败则返回EOF;
fputs不会自动换行,不能进行格式化的输出
fopen------->原型: FILE * fopen(const char * path,const char * mode);
文件指针变量名 = fopen("文件名","使用文件方式");双引号不能丢
使用文件方式:是指文件的类型和操作要求.
使用fopen函数后会进行判断,成功返回文件指针首地址,失败返回NULL
fclose-------->原型: int fclose(FILE * stream); 文件关闭函数
文件一旦使用完毕,应使用关闭函数把文件关闭,以避免文件的数据丢失等错误。如果文件写操作时,没有进行关闭,可能会导致写入文件失败。注意fopen 和fclose两个函数注意成对出现
fgetc------->原型:int fgetc(FILE * stream); 字符读函数,以字节为单位,读取一个字符到变量中
fputc------>原型: int fputc(int c,FILE * stream); (字符变量或字符常量,文件指针)函数的功能是把一个字符写入指定文件中
fseek----->(文件指针。位移量,起始点)文件的随机读写
实现随机读写的关键是要按要求移动位置指针,称为文件定位.位移量表示要移动的字节数,long类型数据要求后缀加L
起始点有三种: 文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2
feof------>原型:int feof(FILE * stream); 检测文件是否到达末尾。
若到文件末尾返回零值,未到返回非零值。