Linux-C 文件操作
一、简述
二、系统I/O
三、标准I/O
四、标准输入/输出重定向
五、文件操作补充
六、上述文件操作函数代码示例
一、简述
**
基于Linux环境下C语言编程的文件操作。
两种操作文件的方式:
1、系统I/O:系统调用接口,`open(), read(), write(), lseek(), close()`。是操作系统直接提供的编程接口(API)。
2、标准/IO:标准库的I/O函数,fopen(), fread(), fwrite(), fseek(), fclose(),是对系统调用接口进一步封装。
系统I/O常用于硬件级别,可以设置读缓冲区,一般没有写缓冲区;
标准I/O常用于软件级别,自带读写缓冲区。
1、文件基本概念
C程序把文件分为ASCII文件和二进制文件,ASCII文件又称文本文件,二进制文件和文本文件(也称ASCII码文件)二进制文件中,数值型数据是以二进制形式存储的,
而在文本文件中,则是将数值型数据的每一位数字作为一个字符以其ASCII码的形式存储,因此,文本文件中的每一位数字都单独占用一个字节的存储空间,
而二进制文件则是把整个数字作为一个二进制数存储的,并非数值的每一位数字都占用单独的存储空间,无论一个C语言文件的内容是什么,
它一律把数据看成是字节构成的序列,即字节流,对文件的存取也是以字节为单位的,输入/输出的的数据流仅受程序控制而不受物理符号(如回车换行符)的控制,所以说C语言文件为流式文件
C语言的文件存取有两种方式:顺序存取和直接存取
(C语言有缓冲型和非缓冲型两种文件系统,缓冲型文件系统是指系统自动自动在内存中为每一个正在使用的文件开辟一个缓冲区,
作为程序与文件之间数据交换的中间媒介,也就是读文件时,数据先送到缓冲区,再传给C语言程序或则外存上,缓冲文件系统利用文件指针标识文件,
而非缓冲文件系统是不会自动设置文件缓冲区,缓冲区必须由程序员自己设定,缓冲型中的文件操作,也称高级文件操作,
高级文件操作函数大多是ANSIC定义的可移植的文件操作函数,具有跨平台和可移植能力,可解决大多数文件操作问题)
##二、系统I/O
**2.1 open()函数**
功能 打开一个指定的文件并获得文件描述符,或者创建一个新文件
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
***参数***
pathname 要打开的文件路径名称
**flags**
O_RDONLY 只读方式打开文件 这三个参数互斥
O_WRONLY 只写方式打开文件
O_RDWR 读写方式打开文件
O_CREAT 如果文件不存在,则创建该文件
O_TRUNC 如文件已经存在,则删除文件中原有数据
O_APPEND 以追加方式打开文件
mode 如果文件被新建,指定其权限为mode(八进制表示法)
返回值 成功 大于等于0 的整数(即文件描述符)
失败 -1,并且errno会被设置
**补充说明:**
1)flags 的各种取值可以用位或的方式叠加起来,例如创建文件的时候需要满足这样的
选项:读写方式打开,不存在要新建,如果存在了则清空。则flags 的取值应该是:O_RDWR | O_CREAT | O_TRUNC。
2)mode 是八进制权限,比如0644,或者0755 等。也可以是使用系统已定义的
S_IRWXU 00700 user (file owner) has read, write, and execute permission //用户可读写执行权限
S_IRUSR 00400 user has read permission//用户写权限
S_IWUSR 00200 user has write permission//用户写权限
S_IXUSR 00100 user has execute permission//用户执行权限 (更多选项可查询man手册:man 2 open)
比如新创建的文件权限只需要读写权限:S_IRUSR | S_IWUSR
**3)文件描述符**
其实是一个数组的下标值,在内核中打开的文件是用 file 结构体来表示的,每一个结构体都会有一个 指针来指向它们,这些指针被统一存放在一个叫做 fd_array 的数组当中,而这个数组被存 放在一个叫做 files_struct 的结构体中,该结构体是进程控制块 task_struct 的重要组成部分。
**2.2 close()函数**
功能:关闭文件并释放相应资源
头文件
#include <unistd.h>
原型
int close(int fd);
**参数**
fd 要关闭的文件的描述符
返回值
成功
失败
-1
备注
重复关闭一个已经关闭了的文件或者尚未打开的文件是安全的。
**2.3 read()函数**
功能
从指定文件中读取数据
头文件
#include <unistd.h>
原型
ssize_t read(int fd, void *buf, size_t count);
参数
fd
从文件 fd 中读数据,(fd是某个文件的描述符)
buf
指向存放读到的数据的缓冲区,(就是放数据的内存首地址)
count
想要从文件 fd 中读取的字节数
返回值
成功
实际读到的字节数
失败
-1
备注
实际读到的字节数小于等于 count
补充说明:
ssize_t :是类型重定义,为了跨平台兼容。比如说long在32位系统可能是4字节,64位系统可能是8字节,嵌入式开发有的只有16位,那么int只有2个字节。
但是在编程时往往需要根据类型大小来进行操作数据,例如在64位系统编程时8字节使用long类型,如果移植到32位系统时long只有4字节,那么就需要改动好多个地方,所以重定义一种类型,然后根据实际系统再指定,方便改动移植。例如:重定义ssize类型是8字节的,在32位系统,我可以将long long重定义为ssize类型,如果是64位系统可以定义为long类型,这样只需改动一处。
**2.4 write()函数**
功能
将数据写入指定的文件
头文件
#include <unistd.h>
**原型**
ssize_t write(int fd, const void *buf, size_t count);
**参数**
fd
将数据写入到文件fd 中 (fd是某个文件的描述符)
buf
指向即将要写入的数据,(要写的数据的内存首地址)
count
要写入的字节数
返回值
成功
实际写入的字节数
失败 : -1
备注
实际写入的字节数小于等于 count
**2.5 lseek()函数**
功能
调整文件位置偏移量
头文件
#include <sys/types.h>
#include <unistd.h>
原型
off_t lseek(int fd, off_t offset, int whence);
**参数**
fd
要调整位置偏移量的文件的描述符
offset
相对基准点的偏移大小
`
whence:基准点
SEEK_SET:文件开头处
SEEK_CUR:当前位置
SEEK_END:文件末尾处
返回值
成功
新文件位置偏移量(相对于文件开头的偏移)
失败
-1
备注
可用 file-size= lseek(fd,0,SEEK_END);来测量文件大小
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
int fd;
int wr_ret;
int rd_ret;
unsigned long file_size;
char wr_buf[100] = “hello world”;
char rd_buf[100];
fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IROTH);//等价于fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0x604);
if(fd == -1)
{
perror("open file error:");//只有上面的函数设置了error全局错误号,才可使用,会根据error输出对应的错误信息
return -1;
}
printf("fd = %d\n", fd);
wr_ret = write(fd, wr_buf, sizeof(wr_buf));
if(wr_ret == -1)
{
perror("write file error:");
return -1;
}
printf("wr_ret = %d\n", wr_ret);
lseek(fd, 0, SEEK_SET);//上面的写操作,文件位置偏移量也会相应的移动,此处将文件偏移到文件开始位置,然后才能读取刚刚输入的内容
rd_ret = read(fd, rd_buf, sizeof(rd_buf));
if(rd_ret == -1)
{
perror("read file error:");
return -1;
}
printf("rd_ret = %d\n",rd_ret);
printf("content=%s\n", rd_buf);
file_size = lseek(fd, 0, SEEK_END);
printf("file_size = %lu\n", file_size);
close(fd);//关闭文件
return 0;
}
**2.5 mmap()函数**
功能
将物理内存映射为虚拟内存,为了提高效率。(比如将一个文件映射到虚拟内存,以操作内存的方式进行数据的读写,但是不会改变文件的大小,一般不用于普通文件)
头文件
#include <sys/mman.h>
原型
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
**参数**
addr 从虚拟内存的哪个地址开始去映射这片内存
NULL代表由系统决定映射起始地址
length 映射的内存长度(字节为单位)
port
决定这块内存的操作权限,以下数值可以相或
PROT_EXEC Pages may be executed. 执行权限
PROT_READ Pages may be read. 读权限
PROT_WRITE Pages may be written. 写权限
PROT_NONE Pages may not be accessed. 无权限
flags 操作标志
MAP_SHARED:在多进程中把这块内存共享给其他进程
MAP_PRIVATE:不共享内存
fd
将数据写入到文件fd 中 (fd是某个文件的描述符)
offset
基于文件头偏移多少单位开始映射
返回值
成功
成功返回映射的虚拟地址
失败
失败返回MAP_FAILED,其实就是个-1, errno会被设置
备注
取消映射 int munmap(void *addr, size_t length);
addr:映射的起始地址
length:取消的长度
成功返回0,失败返回-1
更多请查看man手册:man 2 mmap
示例代码:
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> //mencpy()
int main(void)
{
int fd;
char *map_ptr;
int retval;
fd = open("a.txt",O_RDWR);
if(fd == -1)
{
perror("open failed:");
return -1;
}
map_ptr = mmap( NULL, 100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(map_ptr == MAP_FAILED)
{
perror("map memory failed:");
return -1;
}
printf("%s", map_ptr);
memcpy(map_ptr, "haha", 5);
retval = munmap(map_ptr, 100);
if(retval == -1)
{
perror("munmap failed:");
return -1;
}
close(fd);
return 0;
}
**三、标准I/O**
**3.1 fopen()函数**
功能
获取指定文件的文件指针
头文件
#include <stdio.h>
原型
FILE *fopen(const char *path, const char *mode);
**参数**
path
要打开的文件的路径名称
mode
“r” : 以只读方式打开文件,要求文件必须存在。
“r+” : 以读写方式打开文件,要求文件必须存在。
“w” : 以只写方式打开文件,文件如果不存在将会创建新文件,如果存
在将会将其内容清空。
“w+” : 以读写方式打开文件,文件如果不存在将会创建新文件,如果存在将会将其内容清空。
“a” : 以只写方式打开文件,文件如果不存在将会创建新文件,且文件位置偏移量被自动定位到文件末尾(即以追加方式写数据)。
“a+” : 以读写方式打开文件,文件如果不存在将会创建新文件,第一次用于写数据则文件位置偏移量被自动定位到文件末尾(即以追加方式写数据),如果第一次用于读数据,文件位置偏移位置会定位到文件开始。
返回值
成功
文件指针
失败 NULL
备注
返回的文件指针是一种指向结构体 FILE{}的指针,文件描述符就被封装在FILE结构体里面
程序一开始默认打开3个文件
设备
文件描述符(int)
文件指针(FILE *)
标准输入设备(键盘)
0
STDIN_FILENO
stdin
标准输出设备(屏幕)
1
STDOUT_FILENO
stdout
标准出错设备(屏幕)
2
STDERR_FILENO
stderr
**3.2 fclose()函数**
功能
关闭指定的文件并释放其资源
头文件
#include <stdio.h>
原型
int fclose(FILE *fp);
**参数**
fp
即将要关闭的文件
返回值
成功
0
失败
EOF
备注
fclose( )不能对一个文件重复关闭
**3.3 fread()函数**
功能
从指定文件读取若干个数据块
头文件
#include <stdio.h>
原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
**参数**
ptr
自定义缓冲区指针,(通俗的说就是要存放 读取出来的数据 的地方)
size
数据块大小
nmemb
数据块个数
stream
即将被读取数据的文件指针
返回值
成功
读取的数据块个数,等于 nmemb
失败
读取的数据块个数,小于 nmemb 或等于 0
备注
当返回小与 nmemb 时,文件 stream 可能已达末尾,或者遇到错误
3.4 fwrite()函数
功能
将若干块数据写入指定的文件
头文件
#include <sys/ioctl.h>
原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
**参数**
ptr:自定义缓冲区指针(要写入的数据的首地址)
size:数据块大小
nmemb:数据块个数
stream:即将被写入数据的文件指针
返回值
成功
写入的数据块个数,等于 sinmembze
失败
写入的数据块个数,小于 nmemb 或等于 0
备注
无
3.5 fseek()函数
功能
设置指定文件的当前位置偏移量
头文件
#include <sys/ioctl.h>
原型
int fseek(FILE *stream, long offset, int whence);
**参数**
stream:需要设置位置偏移量的文件指针
offset:新位置偏移量相对基准点的偏移
whence:基准点
SEEK_SET:文件开头处
SEEK_CUR:当前位置
SEEK_END:文件末尾处
返回值
成功
0
失败
-1
备注
无
3.6 ftell()函数
功能
获取指定文件的当前位置偏移量
头文件
#include <sys/ioctl.h>
原型
long ftell(FILE *stream);
**参数**
stream:需要返回当前文件位置偏移量的文件指针
返回值
成功
当前文件位置偏移量
失败
-1
备注
无
3.7rewind()函数
功能
将指定文件的当前位置偏移量设置到文件开头处
头文件
#include <sys/ioctl.h>
原型
void rewind(FILE *stream);
**参数**
stream:需要设置位置偏移量的文件指针
返回值
无
备注
该函数的功能是将文件 strean 的位置偏移量置位到文件开头处。
简单例子:
#include <stdio.h>
int main(int argc,char* argv[])
{
int wr_ret;
int rd_ret;
FILE *fp;
unsigned long file_size;
char wr_buf[100] = “hello world”;
char rd_buf[100];
fp = fopen( "a.txt", "a+" );//文件追加,可读可写,文件不存在则创建
if(fp == NULL)
{
perror("open file error:");//只有上面的函数设置了error全局错误号,才可使用,会根据error输出对应的错误信息
return -1;
}
wr_ret = fwrite( wr_buf, sizeof(wr_buf), 1, fp);
printf("wr_ret = %d\n", wr_ret);
rewind(fp);//上面的写操作,文件位置偏移量也会相应的移动,此处将文件偏移到文件开始位置,然后才能读取刚刚输入的内容
rd_ret = fread(rd_buf, sizeof(rd_buf), 1, fp);
printf("rd_ret = %d\n",rd_ret);
printf("content=%s\n", rd_buf);
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
printf("file_size = %lu\n", file_size);
fclose(fp);//关闭文件
return 0;
}
———————————————
#**四、标准输入/输出重定向**
实际上,对于终端设备,系统会自动打开3个标准文件:标准输入、标准输出和标准错误输出,相应的,系统定义了3个特别的文件指针常数:stdin、stdout、stderr,
分别指向标准输入、标准输出和标准错误文件,这3个文件都以标准终端设备作为输入/输出对象,在默认情况下,标准输入设备时键盘,标注输出设备是屏幕
fprintf()是printf()的文件操作版,二者的差别在于fprintf()多了一个FILE *类型的参数fp,如果为其提供的第1个参数时stdout,那么它就和printf()完全一样,
同理可推广到fputc()和putchar()等其他函数,
例如:
putchar(c);和fputc(c,stdout);等价
getchar();和fgetc(stdin);等价
puts(str)和fputs(str,stdout);等价
但函数fgets()与gets()不同,从如下函数原型可知其区别在于fgets()还多了一个参数size
char *fgets(char *s,int size,FILE *stream);
char *gets(char *s)
;
fgets()用其第二个参数size来说明输入缓冲区的大小,使读入的字符数不能超过限定缓冲区的大小,从而达到防止缓冲区溢出攻击的目的,
假如已定义一个有32字节的缓冲区buffer[32],那么在下面两条读字符串的语句中,后者的安全性更高
gets(buffer);
fgets(buffer,sizeof(buffer),stdin);//安全性更高
虽然系统隐含的I/O是指终端设备,但其实标准输入和标准输出是可以重新定向的,操作系统可以重新定向它们到其他文件或具有文件属性的设备,只有标准错误输出不能进行一般的输出重定向,
例如,在没有显示器的主机上,把标准输出定向到打印机,各种程序不用做任何改变,输出内容就自动从打印机输出
这里用“<”表示输入重定向,用“>”表示输出重定向,例如:假设exefile时可执行程序文件名,执行该程序时,需要输入数据,现在如果要从文件file.in中读取数据,而非键盘输入,
于是exefile的标准输入就被“<”重定向到了file.in,c此时程序exefile只会专心致志地从文件file.in中读取数据,而不再理会你此后按下的任何一个按键,
于是,exefile的标准输出就被“>”重定向到了文件file.out中,此时程序exefile的所有输出内容都被输出到了文件file.out中,而屏幕上没有任何显示
例:
复制代码
1 #include <stdio.h>
2
3 main()
4 {
5 int c;
6
7 scanf_s("%d", &c);
8 printf("%d", c);
9
10 return 0;
11 }
将exe文件移到E盘,新建一个te.txt输入10然后保存,打开DOS命令行,转到E盘,输入test1.exe<te.txt回车,则te.txt文件中的10作为输入值,输出值为10
#**五、文件操作补充**
因为在Microsoft Visual C++ 2010 Express中使用fopen,fscanf等函数编译器会显示警告,
This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
所有在文件操作时将改用fopen_s、fscanf_s等函数
fopen_s()函数
函数原型:`errno_t fopen_s( FILE** pFile, const char *filename, const char *mode );`
pFile----文件指针将接收到打开文件指针指向的指针
infilename----文件名
mode----允许访问的类型
fopen_s()打开文件成功返回0值,否则返回非0值
须定义另外一个变量errno_t err
例:
1 FILE *fp;
2 errno_t err;
3 err = fopen(&fp,“E:\ww.txt”,“r”);
这里的errno_t是int的别名,在编译器crtdef.h头文件中有typedef int errno_t;
fopen()与fopen_s()的区别
fopen_s()函数比fopen()函数多了一个溢出检测,安全性上有所提升,在使用形式上fopen_s()比fopen()多使用了一个参数,
需要特别注意的是:
fopen的返回值是FILE *,返回的是指向结构体类型的指针
而fopen_s的返回值是errno_t,返回的是errno_t(int)类型的数值
例:**fopen()函数**
1 fp = fopen("E:\\ww.txt", "r");
fopen_s()函数
1 errno_t err;
2 err = fopen_s(&fp,"E:\\ww.txt","r");
**fscanf_s()函数**
函数原型:`fscanf_s(_Inout_ FILE * _File, _In_z_ _Scanf_s_format_string_ const char * _Format, ...);`
fscanf_s和fscanf的区别
在使用形式上fopen_s()比fopen()多使用了一个参数,第四个参数是字节数(注意长度(strlen)和字节数(sizeof)的区别)
例:fscanf()函数
1 fscanf(fp, "%c", &c);
fscanf_s()函数
1 fscanf_s(fp, "%c", &c,sizeof(char));
#**六、上述文件操作函数代码示例**
(前提条件:在E盘根目录下新建一个txt文档命名为ww.txt,内容输入about保存)
fopen()函数----fopen(文件路径, 文件使用方式);
1 FILE *fp;
2 fp = fopen(“E:\ww.txt”, “r”);
fopen_s()函数----fopen_s(指向该文件指针的指针, 文件路径, 文件使用方式);
1 errno_t err;
2 err = fopen_s(&fp, “E:\ww.txt”, “r”);
fread()函数----fread(内存首地址, 数据块大小, 数据块个数, 文件指针);
1 char ss[20];
2 fread(ss, sizeof(char), 4, fp);
fwrite()函数----fwrite(内存首地址, 数据块大小, 数据块个数, 文件指针);
1 char ss[20] = “aabb”;
2 fwrite(ss, sizeof(char), 4, fp);
fscanf_s()函数----fscanf_s(文件指针, 格式参数, 存入地址, 字节数)
1 fscanf_s(fp, "%c", &c,sizeof(char));
fprintf()函数----fprintf(文件指针, 格式参数, 输出列表)
1 char c = ‘a’;
2 fprintf(fp, “%c”, c);
fseek()函数----fseek(文件指针, 指针偏移量, 起始位置);
1 fseek(fp, sizeof(char), SEEK_SET);
fgets()函数----fgets(内存首地址, 字符数, 文件指针);
1 char ss[20];
2 fgets(ss, 20, fp);
fgetc()函数----fgetc(文件指针);
1 char ss[20];
2 ss[0] = fgetc(fp);
3 printf("%c", ss[0]);
fputc()函数----fputc(变量名, 文件指针)
1 int c = ‘a’;
2 fputc(c, fp);
也可写成
fputc(‘c’, fp);
fputs()函数----fputs(字符串, 文件指针)
1 char ss[20] = “aaaa”;
2 fputs(ss, fp);
也可写成
1 fputs(“aaaa”, fp);