九、文件操作;
数据I/O流
#include <stdio.h>
1 fopen()函数:打开文件 函数原型:FILE *fopen(char restrict *filename, char restrict *mode); // restrict C99标准才引进的,属于类型修饰符,表示修饰的这块内存空间只能被这个指针引用和修改,除此之外别无他法。
参数: filename: 需要打开的文件 mode: 文件打开方式
r 以只读的方式打开文件,前提是这个文件必须存在(只写 r 默认是文本文件) r+ 以可读可写的方式打开文件,前提是这个文件必须存在(默认是文本文件)。 rb 以只读的方式打开一个二进制文件,前提是这个文件必须存在。 rb+ 以可读可写的方式打开一个二进制文件,前提是这个文件必须存在。
w 以只写的方式打开文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则清空内容。 w+ 以可读可写的方式打开文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则清空内容。 wb 以只写的方式打开一个二进制文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则清空内容。 wb+ 以可读可写的方式打开一个二进制文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则清空内容。
a 以追加的方式打开只写文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则在文件尾部追加内容。 a+ 以追加的方式打开一个可读可写的文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则在文件尾部追加内容。 ab 以追加的方式打开一个二进制只写文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则在文件尾部追加内容。 ab+ 以追加的方式打开一个二进制可读可写文件,如果这个文件不存在,就创建这个文件;如果这个文件存在,则在文件尾部追加内容。
r(read): 读; w(write):写; a(append):追加; +(plus):读或写,主要是配合r、w、a使用; t(text):文本文件; b(binary):二进制文件
返回值:如果文件顺利打开,则返回值是指向这个文件流的文件指针, 如果文件打开失败,返回 NULL (void*)0
一般来说,文件打开失败会做一个文件指针错误判断 FILE *fp = fopen("c:\\code\\text.c","w+"); if(NULL == fp) { //code //exit(-1); }
2 fgetc(); 和 fputc(); 1) fgetc() 文件字符读取函数 原型: int fgetc(FILE * stream); 参数: stream:文件流 返回值:成功返回获取的字符ASCII码,失败返回 EOF(-1); 举例: char ch = fgetc(fp); // 从fp指向的文件流里接收文件流里的第一个字符
2) fputc() 文件字符写入函数 原型: int fputc(int ch, FILE * stream); 参数: ch :就是写入的字符,函数在执行的时候,会自动把 ch ASCII码转换成一个 unsigned char 类型。 stream: 文件流 返回值:成功返回输出的字符,失败返回 EOF(-1); 举例: fputc(ch, fp); //把字符ch写入到 fp 所指向的文件流里。
3 fgets(); 和 fputs(); 1) fgets() 读取文件字符串函数 原型:char *fgets(char *str, int size, FILE* fp); 参数: str : 保存从fp指向的文件流里读取的一行字符串。 size: 从文件流里读取的字符串不超过 size 个字符。( 一般会使用size - 1,留一个字符位置给 '\0') fp :文件指针 返回值:成功返回读取的字符串所在的内存首地址,失败返回 NULL(0); 举例: char str[20] = { 0 }; fgets(str, 20 - 1, fp); // 从fp指向的文件流的第一行里读取 19个字符,然后放到字符数组 str里。
2) fputs() 写入文件字符串函数 原型:int fputs(char *str, FILE* fp); 参数: str: 要写入到文件里字符串(不包括'\0') fp: 文件指针,成功写入一个字符串后,文件指针会自动后移 返回值:成功为写入的字符个数,失败则返回 EOF(-1)。 举例: char str[20] = "Hello Kitty!"; fputs(str, fp); //向 fp 指向的文件流里写入一个字符串 str,具体怎么写,看 mode 属性。
4 fprintf(); 和 fscanf(); 1) fprintf() 将格式化后的数据写入到文件流里 原型: int fprintf(FILE *stream, char *format, argument...); 举例: int i = 10; float f = 3.14; char ch = 'C'; char str[10] = "haha"; fprintf(fp, "%d %f %c %s\n", i, f, ch,str); // 将各个数据按格式写入到文件流里
2) fscanf() 从文件流里获取数据格式化写入输入流里 原型: int fscanf(FILE *stream, char *format, argument... ); 举例: int i; float f; char ch; char str[10]; fscanf(fp, "%d,%f", &i, &f); // 如果不需要从文件里面写入字符串,那么就可以用逗号或者其他符号来分隔
fscanf(fp, "%s%c", str, &ch); // 如果文件里需要写入字符串,那么字符串与其他数据之间只能用空格和回车来分隔
5 fread(); 和 fwrite(); 二进制文件读写函数 函数原型: size_t fread(void *ptr, size_t size,size_t count, FILE* fp); size_t fwrite(void *ptr, size_t size,size_t count, FILE* fp); 参数: ptr: 是一个指针,对应 fread()来说,是从文件里读入的数据存放的地址; 对应 fwrite()来说,是写入到文件里的数据存放的地址。 size: 每次要读写的字节数 count : 读写的次数 fp: 文件指针
返回值:成功读取/写入的字节数
举例: char str[] = { 0 }; fread(str, sizeof(char) *10, 1, fp); // 每次从fp指向的文件中读取10个字节大小,放入字符数组 str中,总共读1次 fwrite(str, sizeof(char) *10, 1, fp); // 每次从str里获取 10个字节大小,写入到 fp 指向的文件中,总共写1次
6 fseek(); 文件指针操作函数 函数原型: size_t fseek(FILE* fp, long offset, int whence); 参数: fp : 文件指针 offset: 偏移量,基于起始点偏移了 offset 个字节 whence : 起始点(三个): SEEK_SET 0 文件开头位置 SEEK_CUR 1 当前位置 SEEK_END 2 文件结尾位置
举例 fseek(fp, 0, SEEK_END); // 将文件指针指向文件结尾,并偏移了 0 个字节,也就是直接将文件指针指向文件结尾 fseek(fp, -10, SEEK_CUR); // 将文件指针指向当前位置,并偏移了 -10 个字节,也就是将文件指针往前移动10个字节
7 ftell(); 文件指针操作函数 函数原型: long ftell(FILE* fp); 参数: fp 文件指针 返回值:返回文件指针当前位置,基于文件开头的偏移字节数,
举例: long len = ftell(fp); // 返回文件指针当前位置,基于文件开头的偏移字节数,保存到 len 里。
8 rewind(); 文件指针操作函数 函数原型: void rewind(FILE* stream); 参数: fp 文件指针
举例: rewind(fp); // 将文件指针重新指向I/O流(文件流)的开头。
stream > istream / ostream -> fstream -> sstream
6\7\8 大例子
FILE *fp = fopen("C:\\code\\a.txt","r+"); fseek(fp, 0, SEEK_END); //将文件指针指向文件结尾 long len = ftell(fp); //获取文件指针位置,得到文件的大小(Byte) rewind(fp); //将文件指针重新指向文件开头
9 fflush(); 清空数据流里的数据
函数原型: void fflush(FILE* stream); 参数: stream 数据流
举例: fflush(fp); // 清空文件流 fflush(stdin); // 清空输入流 fflush(stdout); // 清空输出流
10 int stat(const char*path, struct stat *buf); // 自行补充
11 rename(); 和 remove();
rename(FILE* filename1, FILE* filename2); rename("old_name.txt","new_name.txt"); // 把old_name.txt 重命名为 new_name.txt
remove(FILE* filename); remove("C:\\code\\a.txt"); // 将绝对路径下的 a.txt 文件删除
12 feof(); 原型: int feof(FILE* fp); 参数: fp 文件指针 返回值:一旦文件指针指向文件结尾,就返回一个真值;否则返回非真值(0)
1. 这个函数达到文件结尾的时候,返回的是一个真值,所以在做判断的时候要注意 !feof(fp) 2. 这个函数必须对文件进行过一次读写操作才会生效,也就是说哪怕这个文件是空的,也必须读写一次,feof()才会返回真值。 文件结束是一个标识符,每次对文件读写都会修改这个标识符的位置,对文件读写一次,文件标识符才会被找到,feof()做出返回操作。
13 fclose(); 原型: int flcose(FILE* fp); 参数: fp 文件指针 返回值:如果成功释放,返回 0,否则返回 EOF(-1);
fclose(fp); 表示释放文件指针和相关的文件缓冲区,文件指针不再合法指向那块区域,但是不代表清空对应的区域。
UTF-8 编码格式下 一个汉字 3个字节 GBK 编码格式下 一个汉字 2个字节
// UTF-8 下 汉字逆置原理 #include <stdio.h> #include <string.h>
int main(void) { char str[] = "阿基米德"; int len = strlen(str);
// printf("%c%c\n", str[0], str[1]); // 打印"阿"
for (int i = len - 1; i > 0; i -= 3) // UTF-8 编码下是3个字节,GBK下是2个字节 { printf("%c%c\n", str[i - 1], str); }
return 0; }
不同操作系统的行尾标志: CR LF \ CR \ LF
CR 是 '\r' 回车 LF 是 '\n' 换行
在DOS和NT内核的Windows下,采用的是 回车+换行(CR LF '\r''\n') 来表示下一行的开始 在Unix/Linux下,采用的是 换行(LF '\n') 来表示下一行的开始 在Macintosh下(OS X) ,采用的是 回车(CR '\r') 来表示下一行的开始
十、结构体
1. 结构体的字节对齐: 在C语言里,结构体所占的内存是连续的,但是各个成员之间的地址不一定是连续的。所以就出现了"字节对齐".
结构体变量的大小,一定是其最大的数据类型的大小的整数倍,如果某个数据类型大小不够,就填充字节。 结构体变量的地址,一定和其第一个成员的地址是相同的。
1) 结构体字节对齐 #include <stdio.h> #include <string.h>
struct Box { // 首先检查结构体成员里最大的数据类型是 double, 占8个字节,则 int height; // 系统判断 int 可以和相邻的 char name[10] 共同填充字节 char name[10]; // char name[10] 需要填充 到double 的倍数,但是可以和相邻的 int 一起累加,再填充4个字节,对齐 double 的2倍 double width; // double 是所有成员里最大数据类型,满足double 的1倍 char type; // char 会填充7个字节,对齐double 的1倍 };
int main(void) { struct Box box; box.height = 4; //高度 strcpy(box.name, "Dropbox"); // 名称 box.width = 5.5; //宽度 box.type = 'C'; //类型
printf("box = %p\n", &box); printf("box.height = %p\n", &box.height); printf("box.name = %p\n", box.name); printf("box.width = %p\n", &box.width); printf("box.type = %p\n", &box.type);
printf("box = %d\n", sizeof(box)); // 16 + 8 + 8 = 24 return 0; }
2) 初识链表 #include <stdio.h> #include <string.h> #include <stdlib.h>
struct Student { char *name; //姓名 int age; //年龄 struct Student *next; // next 是结构体成员,但是类型是 struct Student * 类型,用来指向某个 struct Student 的结构体变量的。 // 结构体可以看做是一个自定义的数据类型,而且结构体可以嵌套,但是嵌套有条件: // 结构体只可以嵌套自身类型的结构体指针,但是绝对不能嵌套自身类型的结构体变量 // 比如,不能嵌套 struct Student next; 这种 };
int main(void) { struct Student stu, *stup; //定义了一个结构体变量 stu 和一个结构体指针变量 stup
stu.name = (char *)malloc(10 * sizeof(char)); // 给姓名申请了一个10个字节的堆空间 strcpy(stu.name, "damao"); // 拷贝字符串"damao" 给 stu.name (注意,不能直接赋值,要用拷贝) stu.age = 18; //今年 18岁了
stup = (struct Student *)malloc(1 * sizeof(structStudent)); // 给 stup 申请一个堆空间,用来保存两个指针(name,next)和一个int stup->name = (char *)malloc(10 * sizeof(char)); // 给 stup->name 申请一个堆空间,保存字符串 strcpy(stup->name, "ermao"); // 拷贝字符串 stup->age = 16; //今年 16岁了
stu.next = stup; //stu的成员next 指向了结构体变量 stup 的首地址,链表诞生 stup->next = NULL; //stup的成员 next 指向 NULL,保证安全。
free(stup->name); //最后申请的堆 最先释放 free(stup); //继续释放 free(stu.name); //最先申请的堆 最后释放
return 0; //程序正常结束 }
End...
|