标准io
一.文件指针及文件操作函数
1.1 fopen 函数
FILE* fopen(const char “文件名”,“操作字母”)
操作任何文件之前都要向打开文件
打开文件函数
打开失败返回NULL
打开成功返回指向存储该文件的文件信息区的FILE变量的 指针
文件名之前可以加上该文件的绝对路径
要注意\要有两个才是单纯的\字符,不然是转义字符。
例:
FILE* pf = fopen("D:\\2024.1\\1.19\\1.19", "r");
参数 pathname:准备被FILE* 指针指向的文件的路径名
参数 mode:使用一下提供几种形式之一来打开文件
r:文件以只读的形式打开,打开后文件流指针指向文件的开头
文件开头指的是文件中第一个数据的地址
文件的末尾指的是文件中结束符的地址
注意:如果文件不存在,则打开失败。FILE* 则会指向NULL
w:以只写的形式打开文件,如果文件存在,则清空文件内容后打开,如果文件不存在,则创建文件后打开,文件打开后,文件流指针指向文件的开头
注意:以w打开文件,大概率总是成功
只有当系统中文件的打开数量达到了上限,w打开文件才会失败:1024,可以使用指令 ulimit -a 查看
r+:以读写的形式打开文件,其他和r一样(不会清空文件,不会创建文件)
注意:以读写的形式打开文件,读光标和写光标独立管理
注意:针对文件的读、写操作,都会让文件流指针自动的向后偏移一个字节,保证下一次读、写的是新的数据、位置
w+:以读写的形式打开文件,其他和w是一样的
注意:以读写的形式打开文件,读光标和写光标独立管理。
注意:针对文件的读、写操作,都会让文件流指针自动的向后偏移一个字节,保证下一次读、写的是新的数据、位置
a:以追加写的形式打开文件(从文件的末尾开始写),如果文件不存在则创建文件,存在则直接打开。文件开后,文件指针指向文件的末尾
a+:以读和追加写的形式打开文件,如果文件不存在则创建文件,如果文件存在则直接打开
文件打开后,读指针指向文件的开头,写指针总是指向文件的末尾
注意:标准IO只提供写入或者读取操作,不提供删除文件中内容的操作,想要删除文件中的内容需要自己写逻辑来实现
返回值:返回成功打开的文件的指针,文件打开失败,返回空指针NULL
注意,一旦使用fprintf/fscanf,访问一个空的文件指针,立刻段错误
所以,在文件打开之后,我们需要做一个判空的操作,确保文件是成功打开的
注意:打开一个文件之后,不再使用的话,注意关闭文件
操作字母
r:读取文件(用该方式打开文件时:如果打开的文件不存在(所以读打开的时候,必须打开一个已经存在的文件),否则就无法打开文件)
w:编写文件(用该方式打开文件时:如果打开的文件不存在,就创建一个新文件如果打开的文件存在且该文件中有内容,内容会被清空)
a:追加文件(用该方式打开文件时:如果打开的文件不存在,就创建一个新文件
如果打开的文件存在且该文件中有内容,内容不会被清空,但只能在该文件之前有的数据之后修改/增加数据,不能改变以该方式打开文件之前的该文件已有的数据)
注意:针对文件的读或写操作,都会让文件流指针自动的向后偏移一个字节,保证下一次读或写的是新的数据、位置
例如以w形式打开,fprintf其中已经写好了,所以在fprintf(写内容到文件中去)后,光标会自动向后偏移
就是意思是读或写时候,光标都会偏移到下一个字节的脸上,再读一个字节是脸上那个字节
文件成功打开后,操作文件的光标,定位在文件的最开头(所谓光标在文件最开头,指的是在文件中第一个数据的脸上,而不是第一个数据的前面。如果文件中没有数据,则第一个数据就是文件结束符)
操作字母加+
r+:表示可读可写的形式打开文件。其他属性和r一样。读写光标独立管理。
w+:表示既可以读又可以写,读、写光标独立管理。可从FILE类型结构体中看到,读光标和写光标是由2个指针独立管理的。
a+:表示既可以读又可以写,写光标位于文件末尾,读光标位于文件开头,分别独立管理。
操作字母加b
表示读写二进制内容
返回值
判空一定写好!!
1.2 fclose 函数
int fclose(FILE *stream)
使用完文件后都要关闭文件
关闭文件函数
关闭成功返回0,失败返回EOF
功能描述:关闭一个打开的文件。只需要传入一个文件流指针,即可关闭该文件
1.3 特殊的文件流指针
不需要我们手动打开,每一个程序天生就有。
都是FILE* 类型的
1.3.1stdin:指向终端的输入流(就是scanf使用的流指针)
标准输入流
1.3.2stdout:指向终端的输出流(就是printf使用的流指针)
标准输出流
1.3.3stderr:指向终端的错误流
效果
printf本质上,默认使用的是stdout,scanf默认使用的是stdin
其实:printf(“hello world\n”) 效果等同于 fprintf(stdout,“hello world\n”);
同理,scanf(“%d”,&a); 效果等同于 fscanf(stdin,“%d”,&a)
标准错误流
默认的错误流,还是输出到终端
错误流的存在意义:仅仅为了区分标准输出流,标准输出流输出到终端,标准错误流通过一系列的手段,输出到"错误日志",将来查看错误信息的话,直接查看错误日志就行了
int main(int argc,char* argv[])
{
fprintf(stdout,"标准输出流\n");//若在终端输入./a.out 1>>err.txt,将标准输出流的内容都输出到err.txt中
fprintf(stderr,"标准错误流\n");
return 0;
}
其中>>表示追加,>表示覆盖
若在终端输入./a.out 1>>err.txt,将标准输出流的内容都输出到err.txt中
更改标准错误流的流向,则 ./a.out 2>>err.txt.终端只有标准输出流这个内容,标准错误流输出在文件中,可以理解为错误日志
1.直接调用函数 perror
void perror(const char *s);
功能描述:在终端上输出 "s:errno对应的错误信息"
例如:当前errno == 2 :No such file or directory
perror("错误信息")
终端上输出的内容为 “错误信息:No such file or directory”
如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <errno.h>//用strerror需要添加这个头文件
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc,char* argv[])
{
FILE* fp=fopen("./1.txt","r");
if(fp==NULL)
{
//perror("fopen");
//strerror(errno);
//fprintf(stderr,"fopen:%s\n",strerror(errno));//可参考,自己写的
char* err = strerror(errno);//返回指向字符串的指针
fprintf(stderr,"fopen:%s\n",err);
return 1;
}
return 0;
}
2.通过函数 strerror 将errno转换成字符串,之后再输出
char *strerror(int errnum);
功能描述:传入errno,返回errno对应的错误信息
1.4 fputc 和 fgetc
fputc
字符写入函数
- 功能:写入一个字符
int fputc(int c, FILE *stream);
功能描述:将c看做ASCII码,他所代表的字符,写入stream所指向的文件中去
返回值:成功,返回成功写入的字符,注意是以unsigned char形式返回,失败返回 EOF
用法:fputc(想要写入文件中的1个字符,文件指针)
fgetc
读取一个字符
读取成功返回读取到的字符的ASCII码值
读取失败返回EOF
每次使用fgetc函数文件读取指针会自动向后走一个字节。
文件中有abcdef
如果使用3次fgetc函数,就会读取出abc
而不是aaa
int fgetc(FILE *stream)
功能描述:从stream指向的文件中,读取1个字符,并返回
返回值:成功,返回成功读取的字符,注意是以unsigned char形式返回,失败返回 EOF
用法:char ch = fgetc(文件指针)
提问:unsigned char retval = fgetc(fp);
接受这个返回值的时候,绝对不能以 unsigned char的形式去接受,因为读取文件失败的时候,会返回-1,unsigned char 没有 -1.
这里用的话,就在while循环中retval==255在break退出循环//这里255是根据-1得到的
int main(int argc,char* argv[])
{
FILE* fp=fopen("./fputc.txt","r");
while(1)
{
unsigned char retval = fgetc(fp);
if(retval == 255){break;}
putchar(retval);
}
fclose(fp);
return 0;
}
案例1 关于变量最大值,最小值的计算以及隐式类型转换
int main(int argc, const char *argv[])
{
// 要求,使用循环,计算char数据类型的最大值和最小值
char a = 0;
// char b = a + 1;
while(a<(char)(a+1)){
a++;
// b++;
}
printf("最大值为:%d,最小值为%d\n",a,(char)(a+1));
return 0;
}
案例2 使用 fgetc 来计算一个文件有多少行
可与下面fgets作比较
int main(int argc,char* argv[])
{
FILE* fp=fopen(argv[1],"r");
if(fp==NULL)
{
perror("fopen");
}
int count=0;
while(1)
{
char ch=fgetc(fp);
if(EOF==ch)
{
break;
}
if(10==ch)
{
count++;
}
}
printf("%d\n",count);
fclose(fp);
return 0;
}
案例3 使用 fgetc 和 fputc 实现文件的拷贝功能
int main(int argc,char* argv[])
{
FILE *fp1=fopen("./1.c","r");
FILE *fp2=fopen("./2.c","w");
while(1)
{
int ch=fgetc(fp1);
if(ch==EOF)
{
break;
}
else
fputc(ch,fp2);
}
return 0;
}
1.5 fprintf 和 fscanf
fprintf
格式写入文件函数
(使用方式除了多了一个FILE的指针参数以外与printf没什么区别)
写入成功返回写入的数据的个数,失败返回负数
int printf(const char *format, ...);
printf("%d %d %d",1,2,3)
功能:将format字符串中的内容,输出到终端
int fprintf(FILE *stream, const char *format, ...);
fprintf(fp,"%d %d %d",1,2,3)
功能:将format字符串中的内容,输出到stream文件流指针指向的文件中去
想要将数据以何种形式输出到文件中,
只需要在format中写上对应的格式占位符即可
sprintf
int sprintf(char *str, const char *format, …);
功能描述:将format字符串中的内容,输出到str所指向的字符数组中去
实际上实现的效果为:将任意类型的数据转换成字符串类型
char buf[32] = {0};
double a = 3.1415926;
%lf 输出3.14 =》 3.140000
%g 输出3.14 =》 3.14
sprintf(buf,"%g",a)
fscanf
int fscanf(FILE *stream, const char *format, ...);
功能:从stream指向的文件中,读取内容,写入format中格式占位符代表的变量中去
fscanf如果想要读取文件全部内容,流程是:
死循环 -> 循环内部使用fscanf -> 每次循环判断一下fscanf的返回值是否为EOF -> 如果fscanf的返回值是EOF说明文件读取结束了,可以跳出循环了
while(1)
{
int a;
char b[32];
double c;
int retval=fscanf(fp,"%d %s %lf",&a,b,&c);
if(EOF == retval){break;}
接下来写正确读取到的数据之后的逻辑
}
sscanf
int sscanf(const char *str, const char *format, ...)
功能:读取str所指向的字符串/字符数组中的数据,写入到format中格式占位符所代表的变量中去(写入到format所表示的地址中去)
实现效果:将字符串类型的数据转换成任意类型的数据
注意:scanf和fscanf的唯二区别
1:fscanf需要传入文件指针
2:scanf调用之后,会挂起,等待用户键盘输入数据
fscanf调用后,不会挂起,即使文件中没有可读数据也不会挂起
3:scanf 使用 %c 的时候,不吸收空格,不吸收回车
fscanf 使用%c 的时候,吸收空格,吸收回车
其他地方,fscanf的使用方法和scanf保持一致
返回值:成功返回成功吸收的数据的项数(格式占位符的数量),失败返回EOF(-1)
1.6 fputs 和 fgets
1.6.1fputs
int fputs(const char *s, FILE *stream);
功能描述:将字符串s,输出到stream所指向的文件中
参数s:想要写入的字符串或字符数组
参数stream:指向写入的字符串或字符数组
用法:
const char* str = "hello world";
FILE* fp = fopen("filename","w");
fputs(str,fp);
fclose(fp);
1.6.2 fgets(字符串读取函数)
char *fgets(char *s, int size, FILE *stream);
功能描述:最多从stream指向的文件中,读取最多 size - 1 个字节的数据,然后将这些数据存储到s所指向的字符数组中去。并且在读取到EOF或者’\n’时也会读取结束
注意:读取到’\n’也会存入s指向的字符数组中去
注意:所谓的最多读取size - 1是因为,读取到换行符的时候,也会读取结束,并且在末尾自动补全一个结束符。所以我们传入的size一般是存储数据的数组的最大容量
char* fgets(char* str,int num,FILE *p)
读取成功返回 第一个参数str读取失败返回 NULL
str为读取的字符串存储的位置的首地址
num为最多读取的字符个数+1(因为它读取的时候会自动读取一个’\0’)
若在读到num-1个字符之前遇到换行符/文件结束标志EOF,则停止读取,系统自动添加’\0’,若有换行符则将换行符保留(换行符在’\0’之前),EOF不保留
返回值:成功读取数据,返回第一个参数s,失败或者读取到文件结束符时,返回NULL
总结:fgets结束读取有三种
①.读取的数据量达到size- 1
②.读取到了回车
③.读取到了EOF
隐藏关键点:fgets能吸收空格
用法:
如果想要使用 fgets 读取文件中的所有内容(所有行的内容)
一样的套路:死循环 -> 循环中调用 fgets -> 判断fgets的返回值
FILE* fp = fopen("filename","r");
char buf[64] = {0};
fgets(buf,64,fp);
fclose(fp);
总结:fgets的第2个参数,只要不是1,准备的字符数组大小只要不是1,其他任意大小都是可以的
我们会用fgets的返回值和NULL来判断是否相等,从而判断是否读取到文件结束符EOF
scanf不让用时,可以使用fgets
fgets是吸收一整行数据,吸收到’\n’(回车)为止。可看如下案例来封装函数实现吸收空格
- 一般fgets用来封装函数,实现能够吸收终端输入的空格的功能:
部分函数代码:
char* getLine(char* buf, int size){
fgets(buf,size,stdin);
int len = strlen(buf);
if(buf[len-1] == '\n'){
buf[len-1] = 0;
}
}
int main()
{
char buf[16]={0};
fgets(buf,16,stdin);
getline(buf,16);
}
案例1 封装实现能够吸收终端输入的空格的功能
i.错误实现代码
#include <stdio.h>
#include <string.h>
char* fun(char* buf,int size)
{
fgets(buf,16,stdin);//终端输入回车的时候会带'\n'
int len=strlen(buf);
buf[len-1]='\0';
}
int main()
{ char buf[16]={0}; // 确保缓冲区有足够的空间
/*//
* //scanf("%16s", buf); // 读取最多 16 个字符的字符串
* //fgets(buf,16,stdin);
*/
fun(buf,16);
printf("%s",buf);
return 0;
}
运行结果:
分析:
输入 helloworld word 包含 16 个字符(包括空格),fgets 读取前 15 个字符和 null 终止符,
z最后打印结果会导致缓冲区存储 helloworld wor。字符 d 被截断,最后的部分无法存储。(就是%s遇到’\0’打印结束)
所以要修改则需要在fun函数中作判断,必须保
ii.正确实现
#include <stdio.h>
#include <string.h>
char* fun(char* buf,int size)
{
fgets(buf,16,stdin);//终端输入回车的时候会带'\n'
int len=strlen(buf);
if(buf[len-1]=='\n')
{
buf[len-1]='\0';
}
}
int main()
{ char buf[16]={0}; // 确保缓冲区有足够的空间
fun(buf,16);
printf("%s",buf);//由于缓冲区大小为 16 字节,fgets 读取最多 15 个字符加上一个 null 终止符。
//输入 helloworld word 包含 16 个字符(包括空格),fgets 读取前 15 个字符和 null 终止符,
return 0;
}
运行结果
案例1 使用fgets计算行数
i.方法一
int main(int argc,char* argv[])
{
FILE* fp=fopen(argv[1],"r");
//①.char buf[4096]={0};
char buf[2]={0};
int count=0;
while(1)
{
char* retval=fgets(buf,4096,fp);
if(retval==NULL){break;}
count++;
}
fclose(fp);
printf("%d\n",count);
return 0;
}
保证这个数组绝对能放进一行数据,但这种不稳妥,万一这一行超过4096字节,会有问题。。
ii.方法二
int main(int argc,char* argv[])
{
FILE* fp=fopen(argv[1],"r");
//①.char buf[4096]={0};
char buf[2]={0};
int count=0;
while(1)
{
char* retval=fgets(buf,2,fp);
if(retval==NULL){break;}
if(buf[0]=='\n')
{
count++;
}
}
fclose(fp);
printf("%d\n",count);
return 0;
}
这里也可以在while中使用fgetc一个字节一个字节读。
案例2 使用 fgets + fputs 实现文件的拷贝功能
int main(int argc,char* argv[])
{
FILE* rfp=fopen(argv[1],"r");
FILE* wfp=fopen(argv[2],"w");
while(1)
{//
char buf[2]={0};//char buf[4096]={0};建议还是一个字节一个字节读选char buf[2]={0}
char* retval=fgets(buf,2,rfp);//char* retval=fgets(buf,4096,rfp);
if(retval==NULL){break;}
}
fputs(buf,wfp);
fclose(rfp);
fclose(wfp);
return 0;
}
1.7fwrite 和 fread
1.7.1 fwrite函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
- 参数 ptr:指向想要写入文件中的数据
- 参数 size:每一个数据的字节大小
- 参数 nmemb:总共多少个数据
- 参数 stream;文件流指针
调用形式
fwrite(想要写入文件中的数据的地址,单个数据的大小,多少个数据,fp)
功能描述:将ptr指向的内存上面, 总共nmemb项数据,每一项数据size大小,也就是总共nmemb*size个字节的数据,写入 stream指向的文件中去
save.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
typedef struct Student{
char name[20];
int chinese;//语文成绩
int math;
int english;
int physical;
int chemical;
int biological;
}stu_t;
int main(int argc,char* argv[])
{
stu_t s[3] = {{"张三",2,4,5,1,55,6},{"李四",45,12,56,66,22},{"王五",155,66,123,55,63,21}};
FILE *p1=fopen("./f1.txt","w");
/*for(int i=0;i<3;i++)
{
fprintf(p1,"%s %d %d %d %d %d %d\n",s[i].name,s[i].chinese,s[i].math,s[i].english,s[i].physical,s[i].chemical,s[i].biological);
}
*/
//方法1:fwrite(&s[0],sizeof(stu_t),3,p1);
//方法2:fwrite(s,sizeof(stu_t),3,p1);写入三组数据,每组数据学生结构体那么大
//方法3:fwrite(s,sizeof(s),1,fp);写入一组数据,每组数据一个数组那么大
/*方法1和2差不多。三种方式都可将s写入文件中,但是下面介绍的方法最安全
for(int i=0;i<3;i++)
{
fwrite(arr+i,sizeof(stu_t),1,fp);
}
fclose(p1);
return 0;
}
介绍最后方法最安全原因如下:
当写入文件中的数据非常非常大的时候,前两种方式属于一次性写入,一次性写入的数据有上限存在,所以注定丢失数据。
注意是以数据流的形式去写的
1.7.2 fread函数原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
调用形式
int res = fread(存放读取到的数据的地址,读取到的每一个数据的大小,读取多少个数据,fp)
- 参数 ptr:指向用来存放读取到的数据的地址
- 参数 size:每一个读取到的数据的大小
- 参数 nmemb:读取多少个数据
- 参数 stream:文件流指针
- 返回值:成功读取,返回读取到的数据的项数,也就是第三个参数 nmemb,失败或者读取到文件的末尾返回0
功能描述:
读取stream指向的文件中nmemb个数据,每个数据size大小,总共读取 nmemb * size个字节的数据,然后将读取到的数据写入ptr指向的内存中
cp.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc,char* argv[])
{
FILE *p=fopen(argv[1],"r");
FILE *p1=fopen(argv[2],"w");
while(1)
{
char buf[1]= {0};
int res = fread(buf,1,1,p);
if(res==0)
{break;}
//fwrite(buf,200,1,p1);有点问题
fwrite(buf,1,1,p1);//一个字节一个字节读
}
fclose(p);
fclose(p1);
return 0;
}
fwrite(buf,200,1,p1);
分析:这条代码会导致拷贝不完整,因为最后一次fread读取数据的时候,只要数据量不足200个字节,最后一次读取就会连带结束符一起读取
fread只要读取到结束符就会返回0
只要返回0,就会被break跳出循环
所以导致最后一次fread读取到的数据没有被fwrite输出,从而导致拷贝不完整
处理方式:一个字节1个字节读,保证每次的fread不会出现连带结束符一起读取的情况
代码段:
fwrite(buf,1,1,p1);
1.8 fseek:文件流指针偏移函数
用来实现文件的光标定位问题
1.8.1函数原型
int fseek(FILE *stream, long offset, int whence);
调用形式
fseek(fp,1,SEEK_SET)
fseek(fp,-1,SEEK_END)
fseek(fp,5,SEEK_CUR)
功能描述:定位stream指向的文件的光标,从whence处开始偏移,偏移offset个字节
- 参数 stream:文件流指针
- 参数 offset:光标的偏移量,+表示从左向右偏移,-表示从右向左偏移
- 参数 whence: 光标偏移的起点
SEEK_SET:从文件开头位置开始偏移
SEEK_CUR:从文件当前光标位置开始偏移
SEEK_END:从文件结束符开始偏移
参考:
下面自己写的:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc,char* argv[])
{
FILE* p=fopen("./fseek.txt","r");
fseek(p,1,SEEK_CUR);
char ch=fgetc(p);
printf("ch= %c\n",ch);
fseek(p,5,SEEK_SET);
ch=fgetc(p);
printf("ch= %c\n",ch);
fseek(p,1,SEEK_CUR);
ch=fgetc(p);
printf("ch= %c\n",ch);
fseek(p,-1,SEEK_END);
ch=fgetc(p);
printf("ch= %d\n",ch);
fclose(p);
return 0;
}
1.9 读取bmp文件的信息
总结
通过文件指针,向文件写数据:
fwrite\fputs\fputc\fprintf
通过文件指针,从文件读数据:
fread\fgets\fgetc\fscanf
返回值
0.fopen/fclose
fopen
返回值:返回成功打开的文件的指针,文件打开失败,返回空指针NULL
fclose
返回值:关闭成功返回0,失败返回EOF
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Error opening file");
return 1;
}
// Perform file operations here
if (fclose(file) != 0) {
perror("Error closing file");
return 1;
}
return 0;
}
分析:
在这个例子中:
如果 fopen 返回 NULL,使用 perror 打印错误信息并返回 1。
如果 fclose 返回 EOF,也使用 perror 打印错误信息并返回 1。
这些返回值和错误检查机制帮助确保文件操作的正确性和可靠性。
1.fscanf
返回值:成功返回成功吸收的数据的项数(格式占位符的数量),失败返回EOF(-1)
int retval=fscanf(fp,"%d %s %lf",&a,b,&c);
if(EOF == retval){break;}
接下来写正确读取到的数据之后的逻辑
2.fgetc
返回值:成功,返回成功读取的字符,注意是以unsigned char形式返回,失败返回 EOF
char ch=fgetc(fp);
if(EOF==ch){break;}
3.fputs
fputs(想要写入文件中的字符串,fp)
返回值:写入失败返回EOF,成功返回 非0(大概率是成功写入的数据的字节数量)
4.fgets
返回值:成功读取数据,返回第一个参数s,失败或者读取到文件结束符时,返回NULL
char* res = fgets(用来存放吸收数据的字符数组,最多从终端吸收数据的数量,fp)
例:
char* res=fgets(buf,16,fp)//读取文件中最多16个字节的数据,写入buf
知识点补充
scanf(“%16s”,buf)//读取最多 16 个字符的字符串
1.1feof
int feof(FILE *stream)
功能描述:用来测试 stream文件指针指向的文件,是否被读取到的了末尾,如果读取到了末尾的话,则返回非0(其实是1),如果没有读取到末尾,则返回0
但是注意:虽然feof很好用, 但是依旧推荐使用函数的返回值去判断文件是否读取完毕
1.2数组指针、指针数组
数组指针:本质是指针,指向数组地址的指针
指针数组:本质是数组,存放数据是指针的数组
int arr[5]={0};
int brr[5][5]={0};
int* p1=arr;
int (*p2)[5]=&arr;
p2=brr;
p2 = brr;之所以有效,是因为brr 衰减为指向其第一行的指针,该指针的类型为 int (*)[5]。因此,p2 现在指向 brr 的第一行,它是一个 5 个元素整数数组。
函数指针 int (fun1)(int a,int b)
比如:void func(int a,double b) 数据类型为void(int,double)
演示类型而来:
void(int,double)*pf = func;//void(int,double)
因为我们说指针,\*pf要写在前面
所以变成:
void (*pf)(int,double)//其中指针*和pf天然绑定,所以加括号
memcpy函数
1.3指针类型转换及大小端存储
i.大小端存储(链接见左)
大型网络 大端存储:高地址存数据低位,地址低位存数据高位;
pc(计算机) 小端存储:高地址存数据高位,低地址存数据低位。
int main(int argc,char* argv[])
{
char arr[5]={1,2,3,4,5};
short*p;
p=(short*)arr;
printf("*p=%d,%d,%x\n",*p,*(p+1),*(p+1));
return 0;
}
分析:
- short *p; 声明了一个指向 short 类型的指针。
- p = (short*)arr; 将 arr 强制转换为 short* 类型,并赋值给 p。这里的 arr 其实是 char 数组的首地址,转换成 short* 后,p 将按照 short 类型来解释内存中的数据。
- *p 解释 arr 的前两个字节(1 和 2)作为一个 short 值。由于 short 通常是 2 字节(在大多数平台上),*p 将结合这两个字节来显示 short 类型的整数。实际值取决于字节序(大端或小端):
在小端系统(如 x86),低字节在前,高字节在后,所以 *p 将显示 0x0201,即 513(十进制)。
在大端系统,字节顺序相反,结果会是 0x0102,即 258(十进制)。
*(p + 1) 访问 p 指向的下一个 short 值,即 arr 中的 3 和 4。同样地,这个值也取决于字节序:
小端系统中,*(p + 1) 显示 0x0403,即 1027(十进制)。
大端系统中,*(p + 1) 显示 0x0304,即 772(十进制)。
ii.指针类型转换
参考学习
2.1案例
案例1:字符串和字符数组区别
#include <stdio.h>
int main()
{
char* str = "hello world";//字符串
char ptr[32]={hello world};//字符数组
return 0;
}
相同点
①字符数组ptr可以退化成一个char*类型的指针。str和ptr作为指针,都指向了各自写的"hello world"中,'h’的地址。
②str和ptr等号右侧的数据都是常量字符串
不同点
①str只占8个字节,ptr占32个字节(ptr本质是数组,只不过可以退化为指针),所以其就是占据空间大小不同
②str是指针,指向了常量字符串的首地址;ptr是数组,它将等号右侧的常量字符串保存到ptr自己所占据的32个字节的栈空间里面(导致不可以通过str修改字符串的值,但是可以修改字符数组的值)
所以*str不能修改,但是*ptr可以被修改
str本质存放的是’h’的地址,ptr本质上存放的是数据本身–>“hello world”(只不过这个’h’所在的地址能够理解为ptr可以退化成一个指针去表示)
案例2 :使用循环计算char数据类型的max和min
#include <stdio.h>
int main(int argc,char* argv[])
{
char a=0;
//char b=a+1;
while(1)
{
if(a<(char)(a+1)){
a++;
//b++;
}
if(a>(char)(a+1)){break;}
}
printf("char max=%d char min=%d\n",a,(char)(a+1));
return 0;
}
注意有无符号类型转换
问题1
下面代码段有什么问题
int arr[5]={0};
int brr[5][5]={0};
int* p1=&arr;
int (*p2)[5]=&arr;
p2=brr;
p1 声明为 int*,但 &arr 的类型为 int (*)[5],它是指向 5 个整数数组的指针。这些类型不兼容,因为 p1 需要 int*,而不是 int (*)[5] 。
应该改用 int* p1 = arr;