1.标准IO
1.1标准IO介绍
课程目标
文件的概念和类型(了解)
如何理解标准IO(了解)
流(FILE)的含义(了解)
流的缓冲类型(熟练)
文件基础
- 文件:一组有关数据的有序集合
- 文件类型
- 常规文件r
- 目录文件d
- 字符设备文件c
- 块设备文件b
- 管道文件p
- 套接字文件s
- 符号链接文件l
- IO的概念
I input 输入设备 比如键盘鼠标都是Input设备
O output 输出设备 比如显示器
优盘,网口,既是输入也是输出
UNIX基础知识-系统调用和库函数
系统调用和库函数
1. 系统调用:系统调用就是操作系统提供的接口函数
2. 库函数: 如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性。Printf就是库函数然后调用了系统调用才在显示器上显示字符。
介于应用代码和和系统调用之间,实现了解耦,相同库函数可以有不同的系统调用实现。
标准I/O – 介绍
标准I/O由ANSI C标准定义
主流操作系统上都实现了C库
标准I/O通过缓冲机制减少系统调用,实现更高的效率
标准I/O – 流
-
FILE
-
标准IO用一个结构体类型来存放打开的文件的相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
-
FILE又被称为流(stream),就是数据的流,在程序中就是一个结构体。
文本流/二进制流
命令
-
man 2 name 查系统调用
man 3 name 查库文件调用
标准I/O – 文本流和二进制流
-
Windows
-
二进制流: 换行符 ←→ ‘\n’
文本流: 换行符 ←→ ‘\r’ ‘\n’
Linux
- 换行符 ←→ ‘\n’
标准I/O – 流的缓冲类型
缓冲区的概念
为了减少操作IO设备的次数,提高运行效率,在内存里面设置的缓冲区
-
全缓冲
- 当流的缓冲区无数据或无空间时才执行实际I/O操作,缓冲区写满后会自动刷新 行缓冲
-
当在输入和输出中遇到换行符(‘\n’)时,进行I/O操作
当流和一个终端关联时,典型的行缓冲,行缓冲区大小1024B
无缓冲
- 数据直接写入文件,流不进行缓冲
缓冲区实验:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char*argv[]){
int i=0;
for(i=0;i<1025;i++){
printf("a");
}
// printf("hello world\n");
while(1){
sleep(1);
}
}
Sleep函数:是释放cpu给其他应用程序使用的库函数。使用的头文件是#include <unistd.h>
标准I/O –stdin,stdout,stderr
标准I/O预定义3个流,程序运行时自动打开
标准I/O – 小结
标准IO
流的缓冲类型
stdin / stdout / stderr
stdin/stdout 默认是行缓冲
stderr没有缓冲
写代码显示缓冲区大小
1.2文件打开和关闭的概念
课程目标
文件的打开(熟练)
文件的关闭(熟练)
小结
标准I/O – 打开文件
-
文件的打开:占用资源
-
文件的关闭:释放资源
-
函数,打开一个标准IO流。
FILE *fopen(const char *path,const char *mode); path 默认是当前文件,mode 是模式 成功返回流指针;出错返回NULL fopen() 创建的文件访问权限是0666(rw-rw-rw-) Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & ~umask),此外真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks
标准I/O – fopen – mode参数
标准I/O – fopen – 示例
- 文件打开代码
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
printf(“fopen error\n”);
return -1;
}
……
return 0;
}
标准I/O – fopen – 新建文件权限
fopen() 创建的文件访问权限是0666(rw-rw-rw-)
Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & ~umask)
Root用户是 022 普通用户是002
用户可以通过umask函数或者命令修改相关设定
如果希望umask不影响文件访问权限,该如何设定?
linux目录权限与文件权限的区别
在Linux系统中,目录最大的权限是777,文件最大的权限是666,因为基于安全原因,新建的文件不允许有执行权限,所以从文件的权限位来看,文件比目录少了执行(x)权限。
Linux系统文件的默认权限和特殊权限
标准I/O – 处理错误信息
extern int errno;
void perror(const char *s);
char *strerror(int errno);
errno 存放错误号,由系统生成
perror先输出字符串s,再输出错误号对应的错误信息
strerror根据错误号返回对应的错误信息
-
void perror(const char*s);
-
打印错误错误信息
成功则打印成功
一般加在错误的分支
perror先输出字符串s,再输出错误号对应的错误信息
char *strerror(int errno);
-
头文件<string.h>
errno 参数可以直接写系统变量
strerror(errno);
-
error 需要引入头文件<errno.h>
errno 存放错误号,由系统生成
strerror根据错误号返回对应的错误信息
标准I/O – 错误信息处理 – 示例1
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
perror(“fopen”);
return -1;
}
// ……
//fopen: No such file or directory
标准I/O – 错误信息处理 – 示例2
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
printf(“fopen: %s\n”, strerror(errno));
return -1;
}
// ……
// fopen: No such file or directory
标准I/O – 关闭文件
-
int fclose(FILE *stream);
-
fclose()调用成功返回0,失败返回EOF(-1),并设置errno
流关闭时自动刷新缓冲中的数据并释放缓冲区,比如:常规文件把缓冲区内容写入磁盘
当一个程序正常终止时,所有打开的流都会被关闭
fclose()函数的入参stream必须保证为非空,否则出现段错误。
标准I/O – 小结
fopen
fclose
标准I/O – 思考和练习
程序中能够打开的文件或流的个数有限制,如何测试?
思路:循环打开流,成功则计数器累加,直到出错为止
答案:1021 + stdin + stdout + stderr = 1024
代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[]){
FILE *fp;
int fpret;
fp = fopen("1.txt","r");
if(fp==NULL){
//printf("Open file Failed\n");
perror("fopen");
printf("fopen:%s\n",strerror(errno));
}else{
printf("Open file success\n");
// perror("open");
fpret = fclose(fp);
if(fpret==0){
printf("file close sucess\n");
}else{
perror("fclose");
}
}
}
2.标准IO的读写
2.1读写单个字符
课程目标
按字符输入(熟练)
按字符输出(熟练)
小结
标准I/O – 读写流
流支持不同的读写方式:
读写一个字符:fgetc()/fputc()一次读/写一个字符
读写一行:fgets()和fputs()一次读/写一行
读写若干个对象:fread()/fwrite() 每次读/写若干个对象,而每个对象具有相同的长度
标准I/O – 按字符输入
下列函数用来输入一个字符:
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);执行时会阻塞,等待键盘输入
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1),
getchar()等同于fgetc(stdin)
getc和fgetc区别是一个是宏一个是函数
标准I/O – fgetc – 示例
int ch;
ch = fgetc(stdin);//stdin 是一个系统定义变量,代表标准输入,函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
printf(“%c\n”, ch);
FILE *fp;
int ch, count = 0;
if ((fp = fopen(argv[1], “r”)) == NULL) { //打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
perror(“fopen”); return -1;
}
while ((ch = fgetc(fp)) != EOF) {
count++;
}
printf(“total %d bytes\n”, count);
标准I/O – 按字符输出
下列函数用来输出一个字符:
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
成功时返回写入的字符;出错时返回EOF
putchar©等同于fputc(c, stdout)
注意事项:
1返回和输入参数都是int类型
2遇到这种错误:Bad file descriptor, 很可能是文件打开的模式错误(只读模式去写,只写模式去读)
标准I/O – fputc – 示例
fputc(‘a’, stdout);
putchar(‘\n’);
FILE *fp;
int ch;
if ((fp = fopen(argv[1], “w”)) == NULL) {
perror(“fopen”); return -1;
}
for(ch = ‘a’; ch <=‘z’; ch++) {
fputc(ch, fp);
}
标准I/O – 小结
fopen
fgetc
fputc
标准I/O – 思考和练习
如何利用fgetc / fputc实现文件的复制?
1 通过命令行参数传递源文件和目标文件名
2 通过fgetc返回值判断是否读到文件末尾
2.2按行读写文件
课程目标
按行输入(熟练)
按行输出(熟练)
小结
标准I/O – 按行输入
下列函数用来输入一行:
#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
最多只能读size-1 个有效字符,末位填充’\0’,不满则自动添加换行符。
成功时返回s,到文件末尾或出错时返回NULL
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’
注意事项:
1 gets函数已经被淘汰,因为会导致缓冲区溢出
2 fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。
标准I/O – fgets – 示例
#define N 6
char buf[N];
fgets(buf, N, stdin);
printf(“%s”, buf);
标准I/O – 按行输出
下列函数用来输出字符串:
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s, FILE *stream);
成功时返回非负整数;出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加’\n’
fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’
标准I/O – fputs – 示例
puts(“hello world”);
FILE *fp;
char buf[] = “hello world”;
if ((fp = fopen(argv[1], “a”)) == NULL) {
perror(“fopen”);
return -1;
}
fputs(buf, fp);
注意:输出的字符串中可以包含’\n’,也可以不包含
标准I/O – 思考和练习
如何统计一个文本文件包含多少行?
1 fgetc ?
2 fgets ?
效率低
如何判断读取了一行?
2.3 对象输入输出(二进制读写)
文本文件和二进制的区别:
存储的格式不同:文本文件只能存储文本。
计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字0101001…,所以所有符号都要编码)
标准I/O – 按对象读写
下列函数用来从流中读写若干个对象:
#include <stdio.h>
-
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
ptr指向要读取的数组中首个对象的指针
size每个对象的大小(单位是字节)
n要读取的对象个数
fp输入流 -
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
成功返回读写的对象个数;出错时返回EOF既可以读写文本文件,也可以读写数据文件效率高
注意事项:
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:移动指针(后面讲解)到文件头;关闭文件,重新打开
标准I/O – fread/fwrite – 示例
fwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student{
char name[16];
int age;
char sex[8];
};
int main(int argc,char *argv[]){
FILE *fp;
size_t ret;
struct student stu;
struct student stu2;
fp=fopen("1.bin","w");
if(fp==NULL){
perror("fopen");
return 0;
}
strcpy(stu.name,"zhangsan");
stu.age = 49;
strcpy(stu.sex,"male");
ret = fwrite(&stu,sizeof(stu),1,fp);
if(ret ==-1){
perror("fwrite");
goto end;
}else{
printf("write struct student success!\n");
}
fclose(fp);
fp=fopen("1.bin","r");
if(fp==NULL){
perror("fopen");
return 0;
}
ret = fread(&stu2,sizeof(stu),1,fp);
if(ret ==-1){
perror("fread");
goto end;
}
printf("name=%s,age=%d,sex=%s\n",stu2.name,stu2.age,stu2.sex);
end:
fclose(fp);
}
fread.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
FILE *fp;
char *buff;
size_t ret;
fp=fopen("1.txt","r");
if(fp==NULL){
perror("fopen");
return 0;
}
buff=(char*)malloc(100);
if(buff==NULL){
return 0;
}
ret = fread(buff,10,1,fp);
if(ret==-1){
perror("fread");
goto end;
}
printf("buf=%s\n",buff);
end:
free(buff);
fclose(fp);
}
fwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student{
char name[16];
int age;
char sex[8];
};
int main(int argc,char *argv[]){
FILE *fp;
size_t ret;
struct student stu;
struct student stu2;
fp=fopen("1.bin","w");
if(fp==NULL){
perror("fopen");
return 0;
}
strcpy(stu.name,"zhangsan");
stu.age = 49;
strcpy(stu.sex,"male");
ret = fwrite(&stu,sizeof(stu),1,fp);
if(ret ==-1){
perror("fwrite");
goto end;
}else{
printf("write struct student success!\n");
}
fclose(fp);
fp=fopen("1.bin","r");
if(fp==NULL){
perror("fopen");
return 0;
}
ret = fread(&stu2,sizeof(stu),1,fp);
if(ret ==-1){
perror("fread");
goto end;
}
printf("name=%s,age=%d,sex=%s\n",stu2.name,stu2.age,stu2.sex);
end:
fclose(fp);
}
标准I/O – 小结
fgetc / fputc
fgets / fputs
fread / fwrite
标准I/O – 思考和练习
-
如何利用fread / fwrite实现文件的复制?
-
通过命令行参数传递源文件和目标文件名
通过fread返回值判断是否读到文件末尾
- 格式化输入输出
- int fprintf(FILE *stream, const char *format, …);
- int sprintf(char *str, const char *format, …);
- int fscanf(FILE *stream, const char *format, …);
- int sscanf(const char *str, const char *format, …);
3.流刷新定位、格式化输入输出
3.1文件流的刷新和定位
课程目标
流的刷新(熟练)
流的定位(熟练)
检测流结束和出错(了解)
标准I/O – 刷新流
#include <stdio.h>
int fflush(FILE *fp);
成功时返回0;出错时返回EOF
将流缓冲区中的数据写入实际的文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
如果是输出到屏幕使用fflush(stdout)
定位流 – ftell/fseek/rewind
#include <stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
ftell() 成功时返回流的当前读写位置,出错时返回EOF
fseek()定位一个流,成功时返回0,出错时返回EOF
whence参数:SEEK_SET/SEEK_CUR/SEEK_END
SEEK_SET 从距文件开头 offset 位移量为新的读写位置
- SEEK_SET 从距文件开头 offset 位移量为新的读写位置
- SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
- SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量
offset参数:偏移量,可正可负
** 打开a模式 fseek无效**
rewind()将流定位到文件开始位置
读写流时,当前读写位置自动后移
示例一(在文件末尾追加字符’t’)
FILE *fp = fopen(“test.txt”, “r+”);
fseek(fp, 0, SEEK_END);
fputc(‘t’, fp);
FILE *fp = fopen(“test.txt”, “r+”);
fseek(fp, 0, SEEK_END);
fputc(‘t’, fp);
示例二(获取文件长度,最大小于2G)
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
perror(“fopen”);
return -1;
}
fseek(fp, 0, SEEK_END);
printf(“length is %d\n”, ftell(fp));
标准I/O – 判断流是否出错和结束
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
ferror()返回1表示流出错;否则返回0
feof()返回1表示文件已到末尾;否则返回0
标准I/O – 小结
fflush
ftell
fseek
3.2格式化输出和输入
标准I/O – 格式化输出
#include <stdio.h>
int printf(const char *fmt, …);
int fprintf(FILE *stream, const char *fmt, …);
int sprintf(char *s, const char *fmt, …);
成功时返回输出的字符个数;出错时返回EOF
使用起来很方便,强烈推荐!
标准I/O – 格式化输出 – 示例
//以指定格式 “年-月-日” 分别写入文件和缓冲区
int year, month, date;
FILE *fp;
char buf[64];
year = 2014; month = 10; date = 26;
fp = fopen(“test.txt”, “a+”);
fprintf(fp, “%d-%d-%d\n”, year, month, date);
sprintf(buf, “%d-%d-%d\n”, year, month, date);
格式化输出,用于解析日志文件及解析字符串
标准I/O – 格式化输入
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
重点掌握sprintf 和sscanf
标准I/O – 小结
fprintf
sprintf
fscanf
sscanf
标准I/O – 思考和练习
每隔1秒向文件test.txt中写入当前系统时间,格式如下:
1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
该程序无限循环,直到按Ctrl-C中断程序
每次执行程序时,系统时间追加到文件末尾,序号递增
1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
3, 2014-10-16 11:35:07
4, 2014-10-16 11:35:08
标准I/O – 思考和练习– 提示
time()用来获取系统时间(秒数)
time_t time(time_t *seconds) 1970.1.1 0:0:0
localtime()将系统时间转换成本地时间
struct tm *localtime(const time_t *timer)
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
注意:
int tm_mon; 获取的值要加1是正确的月份
int tm_year; 获取的值加1900是正确的年份
获取文件内的所有行数量:
while(fgets(buf,32,fp)!=NULL){
if(buf[strlen(buf)-1] =='\n'){ //注意判断是否是一行结束
linecount++;
}
}
写完文件记得fflush ,写到磁盘里面去。
标准IO磁盘文件的缓冲区一般为4096
注意和标准输出的全缓冲区别,标准输出是1024
sleep()实现程序睡眠
以何种方式打开流?
流的缓冲类型对文件写入的影响
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
time_t tm;
struct tm *tmm;
int lc =1;
char buf[32];
FILE *fp = fopen("time.txt","a+");
if(fp ==NULL){
perror("fopen");
return 0;
}
//get line number
while((fgets(buf,32,fp))!= NULL){
if(buf[strlen(buf)-1]=='\n'){
lc++;
}
}
//printf time to FILE
while(1){
tm = time(NULL);
printf("time:%d\n",(int)tm);
tmm = localtime(&tm);
fprintf(fp,"%03d %d-%02d-%02d %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
fflush(fp);
lc++;
sleep(1);
}
fclose(fp);
return 0;
}
时间类型长度32位二进制
4.文件IO
4.1 文件IO 的打开和关闭
课程目标
如何理解文件IO(了解)
文件描述符的含义(了解)
文件I/O-介绍(系统IO、系统调用)
没有缓冲机制
什么是文件I/O?
posix(Portable Operating System Interface ,缩写为 POSIX,可移植操作系统接口)定义的一组函数
不提供缓冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
访问各种类型文件
Linux下, 标准IO基于文件IO实现
文件io和标准io的区别
1.使用标准不一样,文件IO遵循POSIX(可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ))标准,只能在遵循POSIX标准的类UNIX环境下使用。标准IO遵循ANSI标准(美国国家标准学会(American National Standard Institute,ANSI)),只要重新编译就可以在不同的环境下运行。
2.文件IO属于系统文件只能在linux下进行系统调用,可移植性差。标准IO属于C库(意思是C语言环境),可以在不同的操作系统下移植使用, 主流操作系统上都实现了C库。
3.文件IO使用文件描述符(fd:起的编号),标准IO使用文件流指针(结构体:包含文件的各种数据)。
在Linux系统中,一切设备都看作文件。而每打开一个文件,就有一个代表该打开文件的文件描述符。程序启动时默认打开三个I/O设备文件:标准输入文件stdin,标准输出文件stdout,标准错误输出文件stderr,分别得到文件描述符 0, 1, 2。
4.文件IO不带缓冲,执行效率低;标准IO带缓冲,执行效率高。
缓冲区是标准IO在文件IO基础上封装出来的一片用于存放数据的地址,一般存放不着急的数据。等缓冲区满或程序员手动刷新这片地址时,系统会调用一次文件IO读走缓冲区中的数据。
缓冲区分为三类:全缓冲,行缓冲,无缓冲。
全缓冲一般是对文件的操作,缓冲区大小为4096个字节。行缓冲有两个:标准输入和标准输出。缓冲区大小为1024个字节。无缓冲一般用于标准错误输出,用于比较着急的数据,实际不会进入缓冲区,直接调用文件IO执行。
手动刷新缓冲区的函数:fflush
头文件:#include <stdio.h>
函数: int fflush(FILE *stream);
功能: 刷新缓冲区,将保存在缓冲区的数据显示到终端上
参数: 目标文件流指针
返回值:成功返回 0 ;失败返回 EOF(-1)
刷新缓冲区的条件:
全缓冲:缓冲区满;程序结束;手动刷新。
行缓冲:缓冲区满;程序结束;手动刷新;遇到 ‘\n’ ;当标准输入和标准输出一方要使用缓冲区时,正在使用的一方需要让出缓冲区,给需要用的一方使用。
5.文件IO属于系统调用,可以访问不同类型的文件,如普通文件,设备文件(open时不能create),管道文件,套接字文件等;标准IO属于C库只能访问普通文件。
文件I/O-文件描述符
每个打开的文件都对应一个文件描述符。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。
文件描述符从0开始分配,依次递增。
文件IO操作通过文件描述符来完成。
英文:缩写fd(file descriptor)
是0-1023的数字,表示文件。
0, 1, 2 的含义 标准输入,标准输出,错误
文件I/O-open
open函数用来创建或打开一个文件:
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
成功时返回文件描述符;出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限,(只有在建立新文件时有效)此外真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks
可以打开设备文件,但是不能创建设备文件(创建设备mknode 驱动部分会讲)
O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非搁置模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会搁置程序动作,直到有数据或写入完成。
它们的差别在于设立O_NDELAY会使I/O函式马上回传0,但是又衍生出一个问题,因为读取到档案结尾时所回传的也是0,这样无法得知是哪中情况;因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。
不过需要注意的是,在GNU C中O_NDELAY只是为了与BSD的程序兼容,实际上是使用O_NONBLOCK作为宏定义,而且O_NONBLOCK除了在ioctl中使用,还可以在open时设定。
文件IO和标准的模式对应关系
umask :用来设定文件或目录的初始权限,文件和目录的真正初始权限
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限
文件I/O – open – 示例1
以只写方式打开文件1.txt。如果文件不存在则创建,如果文件存在则清空:
int fd;
if ((fd = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
perror("open");
return -1;
}
文件I/O – open – 示例2
以读写方式打开文件1.txt。如果文件不存在则创建,如果文件存在则报错:
int fd;
if ((fd = open(“1.txt”, O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) {
if (errno == EEXIST) {
perror(“exist error”);
} else {
perror(“other error”);
}
}
文件I/O – close
close函数用来关闭一个打开的文件:
#include <unistd.h>
int close(int fd);
成功时返回0;出错时返回EOF
程序结束时自动关闭所有打开的文件
文件关闭后,文件描述符不再代表文件
文件I/O – 小结
文件描述符
open
close
容易出错点:
求字符串长度使用sizeof,对二进制数据使用strlen
printf 的字符最后没有’\0’
4.2文件IO的读写定位
文件I/O – read
read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
成功时返回实际读取的字节数;出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区
count不应超过buf大小(实际字符串练习中发现,buf写满的情况下,实际能读到buf-1个字节的数据,然后会换行,count应比buf大小小一个,留出一个字符串结束字符)
文件I/O – read – 示例
- 从指定的文件(文本文件)中读取内容并统计大小
int main(int argc, char *argv[]) {
{
int fd, n, total = 0;
char buf[64];
if (argc < 2) {
printf(“Usage : %s <file>\n”, argv[0]); return -1;
}
if ((fd = open(argv[1], O_RDONLY)) < 0) {
perror(“open”); return -1;
}
while ((n = read(fd, buf, 64)) > 0) {
total += n;
}
}
文件I/O – write
write函数用来向文件写入数据:
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
成功时返回实际写入的字节数;出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小(count 和 buffer 的实际大小必须一致,不够也得填满,否则出现乱码)
文件I/O – write – 示例
- 将键盘输入的内容写入文件,直到输入quit
int fd;
char buf[20];
if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
perror(“open”); return -1;
}
while (fgets(buf, 20, stdin) != NULL) {
if (strcmp(buf, “quit\n”) == 0) break;
write(fd, buf, strlen(buf));
}
文件IO – lseek( 定位)
- lseek函数用来定位文件:
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
成功时返回当前的文件读写位置;出错时返回EOF
参数offset和参数whence同fseek完全一样
- offset参数:偏移量,可正可负
- 打开a模式 fseek无效
- whence参数:SEEK_SET/SEEK_CUR/SEEK_END
- SEEK_SET 从距文件开头 offset 位移量为新的读写位置
- SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
- SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量
文件I/O – 小结
read
write
lseek
作业
使用文件IO实现“每隔1秒向文件1.txt写入当前系统时间,行号递增”
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#define LEN 27
int main(int argc, char *argv[])
{
time_t tm;
struct tm *tmm;
int lc =1;
char buf[LEN];
char buf2[LEN]={0};
int fd = open("time.txt",O_RDWR|O_CREAT|O_APPEND, 0666);
if(fd < 0){
perror("fopen");
return -1;
}
//get line number
//每次读LEN个,读够则行数++
while((read(fd,buf2,LEN))> 0){
lc++;
}
//printf time to FILE
while(1){
tm = time(NULL);
//输出到控制台
tmm = localtime(&tm);
printf("%03d %d-%02d-%02d %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
//输出到缓冲
sprintf(buf,"%03d %d-%02d-%02d %02d:%02d:%02d\n",lc,tmm->tm_year+1900,tmm->tm_mon+1,tmm->tm_mday,tmm->tm_hour,tmm->tm_min,tmm->tm_sec);
//缓冲输出到文件
write(fd,buf,27);
lc++;
sleep(1);
}
close(fd);
return 0;
}
5.目录操作和库的使用
5.1目录的读取
课程目标
读取目录(熟练)
修改文件访问权限(熟练)
获取文件属性(熟练)
打开目录 – opendir
opendir函数用来打开一个目录文件:
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd); 使用文件描述符,要配合open函数使用
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针;出错时返回NULL
读取目录 – readdir
readdir函数用来读取目录流中的内容:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256] 参考帮助文档
成功时返回目录流dirp中下一个目录项;
出错或到末尾时时返回NULL
关闭目录 – closedir
closedir函数用来关闭一个目录文件:
#include <dirent.h>
int closedir(DIR *dirp);
成功时返回0;出错时返回EOF
打印指定的目录下所有文件名称
int main(int argc, char *argv[]) {
{
DIR *dirp;
struct dirent *dp;
if (argc < 2) {
printf(“Usage : %s <directory>\n”, argv[0]); return -1;
}
if ((dirp = opendir(argv[1])) == NULL) {
perror(“opendir”); return -1;
}
while ((dp = readdir(dirp)) != NULL) {
printf(“%s\n”, dp->d_name);
}
closedir(dirp);
}
修改文件访问权限 – chmod/fchmod
chmod/fchmod函数用来修改文件的访问权限:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
成功时返回0;出错时返回EOF
root和文件所有者能修改文件的访问权限
示例: chmod(“test.txt”, 0666);
成功时返回0;出错时返回EOF
注意:在vmware和windows共享的文件夹下,有些权限不能改变。
5.2文件属性获取
获取文件属性 – stat/lstat/fstat
stat/lstat/fstat函数用来获取文件属性:
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
成功时返回0;出错时返回EOF
如果path是符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性
文件属性 – struct stat
struct stat是存放文件属性的结构体类型:
mode_t st_mode; 类型和访问权限
uid_t st_uid; 所有者id
uid_t st_gid; 用户组id
off_t st_size; 文件大小
time_t st_mtime; 最后修改时间
Stat 结构体
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
文件类型 – st_mode
通过系统提供的宏来判断文件类型:
宏 | 表示 | 描述 |
---|---|---|
S_IFMT | 0170000 | 文件类型的位遮罩 |
S_ISREG(st_mode) | 0100000 | 是否常规文件 |
S_ISDIR(st_mode) | 0040000 | 是否目录 |
S_ISCHR(st_mode) | 0020000 | 是否字符设备 |
S_ISBLK(st_mode) | 0060000 | 是否块设备 |
S_ISFIFO(st_mode) | 0010000 | 是否FIFO文件 |
S_ISLNK(st_mode) | 0120000 | 是否链接文件 |
S_ISSOCK(st_mode) | 0140000 | 是否SOCKET文件 |
文件访问权限 – st_mode
通过系统提供的宏来获取文件访问权限:
宏 | 表示 | 描述 | 权限 |
---|---|---|---|
S_IRUSR | 00400 | bit:8 | 所有者有读权限 |
S_IWUSR | 00200 | 7 | 所有者拥有写权限 |
S_IXUSR | 00100 | 6 | 所有者拥有执行权限S_IRGRP |
S_IWGRP | 00020 | 4 | 群组拥有写权限 |
S_IXGRP | 00010 | 3 | 群组拥有执行权限 |
S_IROTH | 00004 | 2 | 其他用户拥有读权限 |
S_IWOTH | 00002 | 1 | 其他用户拥有写权限 |
S_IXOTH | 00001 | 0 | 其他用户拥有执行权限 |
练习 – 获取并显示文件属性1
以下面格式打印指定文件的主要信息:
$ ./a.out test.c
-rw-r–r-- 317 2014-11-08 test.c
调用lstat函数获取文件的属性
从stat结构体中获取相应信息并输出
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
int main (int argc,char **argv){
struct stat buf;
int ret;
ret = stat("chmod_t.c",&buf);
if(ret<0){
perror("stat");
return 0;
}
if(S_ISREG(buf.st_mode)){
printf("-");
}
if(S_ISDIR(buf.st_mode)){
printf("d");
}
if(S_ISCHR(buf.st_mode)){
printf("c");
}
if(S_ISBLK(buf.st_mode)){
printf("b");
}
if(S_ISFIFO(buf.st_mode)){
printf("p");
}
if(S_ISSOCK(buf.st_mode)){
printf("s");
}
// printf(" ");
int i;
for(i=8;i>=0;i--){
if(buf.st_mode & (1<<i)){
switch(i%3){
case 2:
printf("r");
break;
case 1:
printf("w");
break;
case 0:
printf("x");
break;
}
}else{
printf("-");
}
}
printf(" %d",(int)buf.st_size);
struct tm *t;
t = localtime(&buf.st_ctime);
printf(" %d-%d-%d %d:%d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min);
printf(" chmod_t.c\n");
}
练习 – 获取并显示文件属性2
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char *argv[]){
int fd;
int ret;
char buf[32] = "hello world";
char buf2[32]={0};
fd = open("test.txt",O_RDWR | O_CREAT|O_APPEND, 0666);
if(fd<0){
printf("open file err\n");
return 0;
}
printf("sucess,fd=%d\n",fd);
ret=write(fd,buf,strlen(buf));
if(ret<0){
perror("write");
goto END;
}
printf("write count=%d\n",ret);
lseek(fd,0,SEEK_SET);
ret = read(fd,buf2,32);
if(ret<0){
perror("read");
goto END;
}
buf2[31]=0;
printf("read buf2=%s\n",buf2);
END:
close(fd);
}
小结
opendir
readdir
closedir
lstat/stat
5.3静态库的使用
课程目标:
库的概念(了解)
静态库(了解)
静态库创建(熟练)
链接静态库(熟练)
库的概念
库是一个二进制文件,包含的代码可被程序调用
标准C库、数学库、线程库……
库有源码,可下载后编译;也可以直接安装二进制包
Linux 库位置:/lib /usr/lib
库是事先编译好的,可以复用的代码。
在OS上运行的程序基本上都要使用库。使用库可以提高开发效率。
Windows和Linux下库文件的格式不兼容
Linux下包含静态库和共享库
静态库的特点
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已包含代码,运行时不再需要静态库
程序运行时无需加载库,运行速度更快
占用更多磁盘和内存空间
静态库升级后,程序需要重新编译链接
静态库的创建
库名lib开头
缀名是.a
-
步骤
-
1.编写库文件代码 vi xxxx.c
2.编译成.o目标文件 gcc -c xxxx.c -Wall
3.创建静态库 ar -rsv libxxxx.a xxxx.o
4.查看库中符号信息 nm libxxxx.a
5.链接静态库 gcc -o test xxxx.c -L. -lxxxx
6.运行:./test
确定库中函数的功能、接口
编写库源码hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
编译生成目标文件
$ gcc -c hello.c -Wall
创建静态库 hello
$ ar -rsv libhello.a hello.o
ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,
里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。
原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。
同时:这个也是很好的可以学习的Makefile文件,阅读源码应该慢慢的养成一种习惯。
-
ar 参数:
-
c 禁止在创建库时产生的正常消息
r 如果指定的文件已经存在于库中,则替换它
s 无论 ar 命令是否修改了库内容都强制重新生成库符号表
v 将建立新库的详细的逐个文件的描述写至标准输出
q 将指定的文件添加到库的末尾
t 将库的目录写至标准输出
注意:
1 静态库名字要以lib开头,后缀名为.a
2 没有main函数的.c 文件不能生成可执行文件。
查看库中符号信息
$nm libhello.a
hello.o:
0000000 T hello
U puts
nm:显示指定文件中的符号信息
-a 显示所有符号
链接静态库
编写应用程序test.c
#include <stdio.h>
void hello(void);
int main() {
hello();
return 0;
}
编译test.c 并链接静态库libhello.a
$ gcc -o test test.c -L. -lhello
$ ./test
hello world
小结
库-library
静态库
5.4动态库的使用
课程目标
共享库(了解)
共享库创建(熟练)
链接共享库(熟练)
加载共享库(熟练)
-
命令
-
ldd 可执行文件名 查看使用动态库名称(
ldd:list dynamic dependencies, 列出动态库依赖关系;dll:dynamic link library,动态链接库
)
nm 文件名.o 查看目标文件符号表
创建使用步骤
-
1.编写库文件代码 vi xxx.c ;vi xxxx.c; vi test.c
2.生成位置无关代码的目标文件 gcc -c -fPIC xxx.c xxxx.c
3.生成动态库 gcc -shared -o libcommon.so.1 xxx.o xxxx.o
4.为共享库文件创建链接文件 ln -s libcommon.so.1 libcommon.so
5.编译test.c 并链接共享库 gcc -o test test.c -L. -lcommon
6.将当前路径添加到共享库的加载路径 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
7…/test
共享库的特点
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库升级方便,无需重新编译程序
使用更加广泛
共享库的创建
确定库中函数的功能、接口
编写库源码
编写库源码 hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}
编写库源码 bye.c
#include <stdio.h>
void bye(void) {
printf(“bye\n”);
return;
}
编译生成目标文件
$ gcc -c -fPIC hello.c bye.c -Wall
创建共享库 common
$ gcc -shared -o libcommon.so.1 hello.o bye.o
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号
为共享库文件创建链接文件
$ ln -s libcommon.so.1 libcommon.so
符号链接文件命名规则
lib<库名>.so
链接共享库
编写应用程序test.c
#include <stdio.h>
#include “common.h”
int main() {
hello();
bye();
return 0;
}
编译test.c 并链接共享库libcommon.so
$ gcc -o test test.c -L. -lcommon
加载共享库
加载共享库
执行程序
$ ./test
./test: error while loading shared libraries: libcommon.so
出现报错:cannot open shared object file : No such file or directory
添加共享库的加载路径
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
$ ./test
hello world
bye!
如何找到共享库
为了让系统能找到要加载的共享库,有下边几种方法 :
-
把库拷贝到/usr/lib和/lib目录下
-
在LD_LIBRARY_PATH环境变量中添加库所在路径
-
export LD_LIBRARY_PATH= (可以填写当前路径或库的绝对路径,只在当前窗口内有效,新开窗口失效)
-
添加在~/.bashrc 文件里面,使用source ~/.bashrc 生效。
-
添加/etc/ld.so.conf.d/*.conf文件(需要root用户权限),执行ldconfig刷新
查看可执行文件使用的动态库:
ldd 命令 : ldd 你的可执行文件