IO课程学习

标准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;
}
运行结果:

code

分析:

  输入 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;
}
运行结果

code

案例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:从文件结束符开始偏移
    参考:
    参考案例

下面自己写的:
fseek.txt

#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;

                                      链接:
                                         参考博主:liuyunluoxiao

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值