系统编程笔记01——标准IO

1 标准IO

1.1 基本概念

1.1.1 系统调用IO与标准IO的区别
  • (1) 系统调用IO:内核提供的一系列接口,不同内核的接口不同。

  • (2) 标准IO:合并系统调用并将内核接口进行封装后的库函数,可移植性高。

  • (3) 标准IO与系统IO的对比:① 可移植性:标准IO>文件IO;② 效率:多文件时标准IO的效率更好。

  • (4) 合并系统调用:标准IO并没有马上将缓冲区的内容输出到文件,减少了对外设的使用,效率更高。

  • (5) 原则:能用标准IO就用标准IO。

1.1.2 数据流
  • (1) 流:数据在内存和文件之间的路径。

  • (2) 输入流:数据从文件输入到内存的路径。

  • (3) 输出流:数据从内存输出到文件的路径。

  • (4) 文本流和二进制流:

    • ① 文本文件以ASCII码、Unicode、GBK等编码格式存储,windows下’\r’ '\n’为换行。

    • ② 二进制文件以二进制格式存储,大部分文件都是二进制文件。

    • ③ 默认打开的流为:stdout(标准输出流)、stdin(标准输入流)、stderr(标准错误流)。

  • (5) 在Linux下打开流就是打开了一个FILE*。

1.1.3 文件类型
  • (1) 文件:数据,Linux下一切皆文件。

  • (2) linux下查看文件类型:ls -la。

  • (3) 输入输出设备:① 输入设备:鼠标、键盘;②输出设备:显示器;③串口即是输入也是输出设备。

文件类型 标识符 描述
常规文件 _ 普通的文件
目录文件 d 一个目录
字符文件 c 鼠标、键盘等输入设备
块设备文件 b 硬盘、磁盘、U盘等存储设备
管道文件 p
套接字文件 s 用在网络编程
符号链接文件 i 类似于windows快捷方式
1.1.4 缓冲机制
  • (1) 全缓冲:当缓冲区满了才会执行实际IO操作。

  • (2) 行缓冲:当缓冲区中遇到了换行符才会进行时间IO操作,最大长度为1024。

  • (3) 无缓冲:直接输出到外设。

  • (4) 标准IO的缓冲类型:stdout和stdin默认行缓冲,stderr默认无缓冲。

  • (5) 缓冲的原因:外设的传输速度低于CPU,加缓冲可以减少外设传输的次数。

1.1.5 man手册
手册位置 内容
man 1 常用linux指令
man 2 系统调用函数
man 3 库函数
man 7 机制
1.1.6 FILE关键字
  • FILE:文件结构体,保存了一系列文件操作的属性。

  • FILE*的存放位置:需要打开和释放,故存放在堆区,若在全局区只能打开一个,栈区则不能返回局部变量地址。除了固定字段比如ID信息之类的,其余的有逆反操作的都存放在堆中。

  • 缓冲区的刷新就是将指针 ptr 变为缓冲区的基地址 ,同时 cnt 的值变为0 ,因为缓冲区刷新后里面是没有数据的。

typedef struct _iobuf {
    int cnt;  // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
    char *ptr;  // 下一个要被读取的字符的地址
    char *base;  // 缓冲区基地址
    int flag;  // 读写状态标志位
    int fd;  // 文件描述符
    // 其他成员
} FILE;

1.2 打开和关闭

1.2.1 打开函数
#include 
/*
	功能:打开成功,但会文件流指针,打开失败返回NULL,并将设置对应errno。
	路径:绝对路径和相对路径。
	备注:每次打开都会创建一个流。
*/
FILE *fopen(const char *pathname, const char *mode); 
/*打开新的流并关闭之前的流*/
FILE *freopen(const char *pathname, const char *mode, FILE *stream);

1.2.2 读写模式
读写权限 描述
r 只读,流位于文件开头。
r+ 读写,流位于文件开头。
w 只写。如果文件不存在,则会创建该文件,否则会截断该文件。流位于文件的开头。
w+ 读写。如果文件不存在,则会创建该文件,否则会截断该文件。流位于文件的开头。
a 只写。如果文件不存在,则会创建该文件。流位于文件的末尾后一个字节。
a+ 读写。如果文件不存在,则会创建该文件。读取的初始文件位置在文件的开头,但输出在文件的末尾后一个字节。
  • 文件不存在用:w、w+、a、a+。
  • 可读写:后面加加好。
  • 文件指针在末尾的下一个位置:a、a+。
  • windows下二进制读写加b,linux下不区分二进制和文本读写。
1.2.3 打开数量
  • linux下使用ulimit -a 命令可以看到open file的数量1024。

  • 但默认打开了stdin、stdout、stderr,所以还能打开1021个文件。

  • 使用ulimit -a -n 数量来修改open file的数量。

1.2.4 新建文件权限
  • 新建的文件的权限公式:0666 &~ umask,其中umask越大则权限越下。

  • linux下可以使用umask来查询umask大小。

1.2.5 打印错误信息
/*errno是定义好的宏*/ 
#include    /*errno 是一个宏定义的错误号*/
/*perror函数打印错误信息*/
#include 
void perror(const char *s); /*将错误信息拼接在s后面*/
/*获取errno对应的错误字符串*/
 #include 
 char *strerror(int errnum);
1.2.6 关闭函数
#include 
int fclose(FILE *stream);
/*
	功能:关闭FILE指针,关闭成功返回0,否则返回EOF,并设置errno。
	注意:不能同时关闭一个指针两次。
	备注:关闭的时候会将缓冲区全部刷新到文件中。
*/
1.2.7 打开关闭模版
/********打开关闭模版*******
* @preif	打开和关闭文件,打印错误信息
*/

#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	/*注意文件不存在时,需要切换到root才能创建文件*/
	FILE *fp = fopen ("/home/src_test.txt","w");
	int flag = 0;
	if(!fp){ /*fp为NULL,则打开文件失败*/
		fprintf(stdout,"%d",__LINE__);
		perror("fopen: "); /*打印错误信息*/
		exit(1);
	}
	fprintf(stdout,"%d fopen is success!\n",__LINE__);
	
	/*中间执行逻辑*/
	
	flag = fclose(fp); /*关闭文件*/
	if(flag != 0){
		fprintf(stderr,"%d fclose: %s\n",__LINE__,strerror(errno));
		exit(1);
	}
	fprintf(stdout,"%d fclose is success!\n",__LINE__);
	return 0;
}

1.3 字符读写

1.3.1 字符读取函数
#include 
/*从标准流中获取字符,成功时将该字符转为int返回;若到文件末尾或出错时返回EOF*/
int fgetc(FILE *stream);   /*返回值为负数是为了容纳EOF*/
int getc(FILE *stream);  /*fgetc的宏实现*/
int getchar(void);   /*从标准输入流中获取一个字符*/
1.3.2 判断错误、末尾
/*判断是否到了文件尾*/
int feof(FILE *stream);		/*判断文件尾部指示符,如果如果文件指示符指向文件尾部,返回非0*/
void clearerr(FILE *stream); 	/*为流所指向的流清除文件结尾和错误指示符,文件尾部指示符只能通过该函数清除*/

/*判断是否错误*/
int ferror(FILE *stream); /*测试流所指向的流的错误指示符,如果设置了,则返回非零。错误指示器只能通过clearerr()重置*/
1.3.3 字符写入函数
#include 
/*将字符输入到输出流,成功返回c转为的int,否则返回EOF*/
int fputc(int c, FILE *stream); 
int putc(int c, FILE *stream);   /*fputs的宏实现*/
int putchar(int c);  /*输入到stdin*/
1.3.4 读写模版
/********单字节读写模版*******
* @preif	将test_src拷贝到test_src中
*/

#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	FILE *fp_src = NULL;
	FILE *fp_dest = NULL;
	int ch = 0;
	/*判断参数个数*/
	if(argc < 3){
		fprintf(stderr,"%d 缺少参数\n",__LINE__);
		exit(1);
	}
	
	/*打开文件fp_src*/
	fp_src = fopen(argv[1],"r");
	if(!fp_src){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		exit(1);
	}
	
	/*打开文件fp_dest*/
	fp_dest = fopen(argv[2],"w");
	if(!fp_dest){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		fclose(fp_src);
		exit(1);
	}
	
	/*字符读写*/
	while((ch = fgetc(fp_src)) >= 0){
		if(fputc(ch,fp_dest)!=ch){
			fprintf(stderr,"%d fputc: %s",__LINE__,strerror(errno));
			fclose(fp_dest);
			fclose(fp_src);
			exit(1);
		}
	}
	
	/*判断是否出错*/
	if(ferror(fp_src)){
		fprintf(stderr,"%d fgetc: %s\n",__LINE__,strerror(errno));
	}
	
	fclose(fp_dest);
	fclose(fp_src);
	return 0;
}

1.4 行读写

1.4.1 行读取函数
#include 
/*从流中获取size个大小的字节到缓冲区,读取成功返回字符串,否则返回NULL*/
char *fgets(char *s, int size, FILE *stream);
/*fgets的结束形式,读到size - 1或者遇到'\n'时停止,并在后面加'\0'*/
/*读取一行,刚好size-1个字符,但文件指针在尾部,则需要读取两次*/
char *gets(char *s);   /*从stdin中读取,不检查缓冲区溢出,不会记录换行/
1.4.2 行写入函数
#include 
/*将字符串输入到流中区,但不包括'\0',成功返回非负数,否则返货EOF或者ERROR*/
int fputs(const char *s, FILE *stream);
int puts(const char *s); /*输入到标准流stdout*/
1.4.3 读写模版
/********行读写模版*******
* @preif	将test_src拷贝到test_src中
*/

#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	FILE *fp_src = NULL;
	FILE *fp_dest = NULL;
	char buf[1024];
	/*判断参数个数*/
	if(argc < 3){
		fprintf(stderr,"%d 缺少参数\n",__LINE__);
		exit(1);
	}
	
	/*打开文件fp_src*/
	fp_src = fopen(argv[1],"r");
	if(!fp_src){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		exit(1);
	}
	
	/*打开文件fp_dest*/
	fp_dest = fopen(argv[2],"w");
	if(!fp_dest){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		fclose(fp_src);
		exit(1);
	}
	
	/*字符串读写*/
	while(fgets(buf,1024,fp_src)!= NULL){
		if(fputs(buf,fp_dest) < 0){
			fprintf(stderr,"%d fputs: %s",__LINE__, strerror(errno));
			exit(0);
		}
	}
	
	/*判断是否出错*/
	if(ferror(fp_src)){
		fprintf(stderr,"%d fgets: %s\n",__LINE__,strerror(errno));
	}
	
	fclose(fp_dest);
	fclose(fp_src);
	return 0;
}

1.5 对象读写

1.5.1 对象读取函数
#include 
/*从流中获取nmemb个对象,每个对象的长度为size,将其保存到ptr中,返回无符号的数:读到的对象个数*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
/*如果读取到的字节不满足一个对象则不会计数,所以为了直到读到了多少个字节,可以每个对象一个字节大小*/
1.5.2 对象写入函数
#include 
/*从ptr中获取n个size长度大小的对象,写入到流中,成功返回写入的对象的数量*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

/*
	备注:
		fread和fwrite既可以读取文本也可以读取二进制文件。
		返回值:返回成功读写的块数,也即  nmemb。
		如果返回值小于 nmemb:
		对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
		对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
*/
1.5.3 读写模版
/********对象读写模版*******
* @preif	将test_src拷贝到test_src中
*/

#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	FILE *fp_src = NULL;
	FILE *fp_dest = NULL;
	char buf[1024];
	/*判断参数个数*/
	if(argc < 3){
		fprintf(stderr,"%d 缺少参数\n",__LINE__);
		exit(1);
	}
	
	/*打开文件fp_src*/
	fp_src = fopen(argv[1],"r");
	if(!fp_src){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		exit(1);
	}
	
	/*打开文件fp_dest*/
	fp_dest = fopen(argv[2],"w");
	if(!fp_dest){
		fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
		fclose(fp_src);
		exit(1);
	}
	
	/*字符串读写*/
	while(fread(buf,1,1,fp_src) == 1){
		if(fwrite(buf,1,1,fp_dest) != 1){
			fprintf(stderr,"%d fwrite: %s",__LINE__, strerror(errno));
			exit(0);
		}
	}
	
	
	/*判断是否出错*/
	if(ferror(fp_src)){
		fprintf(stderr,"%d fread: %s\n",__LINE__,strerror(errno));
	}
	
	fclose(fp_dest);
	fclose(fp_src);
	return 0;
}

1.6 格式化读写

1.6.1 格式化输出
#include   /*返回输出的字符个数,错误返回负数*/
int printf(const char *format, ...);  /*输出到标准输出*/
int fprintf(FILE *stream, const char *format, ...);	 /*输出到某个标准流*/
int sprintf(char *str, const char *format, ...);	/*输出到字符串,不检查字符串是否溢出*/
int snprintf(char *str, size_t size, const char *format, ...);	/*输出多少个到字符串,包含'\0'一共size个*/

 #include     /*字符串转整型*/
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);

#include 	/*字符串转浮点型*/
double atof(const char *nptr);
1.6.2 格式化输入
#include 	/*返回读取的字符个数,如果遇到文末或者读取错误返回EOF*/
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);   /*从字符串中输入*/
1.6.3 字符串转换
/********将数据转为字符串输出到文件*****/

#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	FILE *fp = NULL;
	char str[1024] = " ";
	char name[10] = "simon";
	int age = 18;
	char sex = 'b';
	if(argc < 2){
		fprintf(stderr,"%d main参数个数不足",__LINE__);
		exit(1);
	}
	sprintf(str,"%s\n%d\n%c\n",name,age,sex);
	fp = fopen(argv[1],"w");
	if(!fp){
		fprintf(stderr,"%d fopen: %s",__LINE__, strerror(errno));
		exit(1);
	}
	fprintf(fp,"%s",str);
	
	fclose(fp);
	return 0;
}

1.7 刷新与定位

1.7.1 刷新函数
#include 
/*成功返回0,否则返回EOF或者设置errno*/
int fflush(FILE *stream);
/*传参NULL刷新所有*/
/*只能刷新输出,输入丢弃*/


#include 
#include 
#include 
#include 
#include 

int main(void){
	printf("hello");
	fflush(NULL);
	sleep(5); /*延时5us*/
	printf("world");
	return 0;
}
1.7.2 定位函数
#include 
/*
	从wherece便宜offset个位置,正向后,负向前
	返回值:偏移量
	whence:SEEK_SET起始, SEEK_END文末, SEEK_CUR当前
	备注:文件的打开使用a模式 fseek无效
*/
int fseek(FILE *stream, long offset, int whence);
/*
	返回值:当前位置
	long局限:负数用不了,只能使用2G大小
*/
long ftell(FILE *stream);
/*
	定位到起始位置
*/
void rewind(FILE *stream);

/*空洞文件:直接占位,后面再写入*/

/*
补充方言
用off_t替换long,可以定位更大的文件
需要define _FILE_OFFSET_BITS,64位
不支持C89和C99
*/
#include 
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);

#include 
#include 
#include 
#include 

int main(void){
	FILE *fp = NULL;
	long index = 0;
	char str[1024];
	fp = fopen("/home/test_src","r");
	if(!fp){
		fprintf(stderr, "%d fopen: %s", __LINE__, strerror(errno));
		exit(1);
	}
	
	if(fgets(str,7,fp) == NULL){
		fprintf(stderr,"%d fgets: %s", __LINE__, strerror(errno));
		exit(1);
	}
	fprintf(stdout,"%s\n",str);
	
	index = ftell(fp);
	fprintf(stdout,"index = %ld\n",index);
	fseek(fp,0,SEEK_END);
	index = ftell(fp);
	fprintf(stdout,"index = %ld\n",index);
	rewind(fp);
	index = ftell(fp);
	fprintf(stdout,"index = %ld\n",index);
	fclose(fp);
	
	return 0;
}
17.3 查询文件字数
#include 
#include 
#include 
#include 

int main(int argc, char **argv){
	/*
		查询字节数
	*/
	
	FILE *fp = NULL;
	long ret = 0;
	if(argc < 2){
		fprintf(stderr,"%d 参数不够\n",__LINE__);
		exit(1);
	}
	
	fp = fopen(argv[1],"r");
	if(!fp){
		fprintf(stderr,"%d fclose: %s\n",__LINE__,strerror(errno));
		exit(1);
	}
	fseek(fp,0,SEEK_END);
	ret = ftell(fp);
	fprintf(stdout,"total: %ld\n",ret);
	
	fclose(fp);
	
	return 0;
}

1.8 补充

1.8.1 getline函数
#include 
/*需要再Makefile中加 -D_GNU_SOURCE*/
/*
	传入一个字符串指针的地址,一个整型的地址
	传入之前需要将lineptr = NULL,n = 0,方便识别第一次malloc
	lineptr保存获取的行的首地址
	n保存整个行的元素个数
	读取成功返回读取的个数,失败返回-1,且设置errno
*/
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
/*内存分配方式:120,再两倍扩容*/
/*没有销毁方式,存在可控的内存泄漏,线程结束后会回收*/


#include 
#include 
#include 
#include 
#define _GNU_SOURCE
int main(void){
	FILE *fp = NULL;
	char *Line = NULL;
	size_t LineSize = 0;
	fp = fopen("/etc/brltty.conf","r");
	if(!fp){
		fprintf(stderr,"%d fopen: %s",__LINE__,strerror(errno));
		exit(1);
	}
	while(getline(&Line,&LineSize,fp) >= 0){
		fprintf(stdout,"%s",Line); 
	}
	fclose(fp);
	return 0;
}
1.8.2 临时文件
/*临时文件:不冲突,及时销毁*/

/*直接获取一个可用文件名,但两步完成,可能多线程下会冲突*/
#include    
char *tmpnam(char *s);  /*s最好为NULL*/

/*直接获取一个匿名文件,不会与其他人冲突,并且fclose时会自行销毁*/
#include 
FILE *tmpfile(void);

你可能感兴趣的:(嵌入式开发笔记,笔记,c语言)