C++ I/O管理(代码层面)

C++ I/O管理(代码层面)

一、系统调用

系统调用可以直接操作页缓存和调用设备驱动程序。

1.1 打开关闭文件

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
// __path 代表文件路径名, __oflag 代表打开方式, mode 代表创建文件的权限
// 返回值为文件描述符序号,若打开文件出错,则返回-1,具体出错原因可以通过全局变量errno确认。
int open (const char *__path, int __oflag, .../*mode_t mode*/);
int close(int fd);
// 以读写方式打开或创建“myfile”文件,打开时会清空文件内容。
// 若创建文件,则文件权限为文件拥有者可以读写。
int fd = open("myfile",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWSR);
// 关闭myfile 文件描述符
if(close(fd)==-1) errExit("close");

打开方式 __oflag 包括了如下三组标志:

  • 文件访问模式标志
  • 文件创建标志
  • 已打开文件的状态标志

mode 指定了当需要创建文件时,文件的访问权限:

1.2 读写文件

#include <unistd.h>
// fd 代表文件描述符序号,buffer 代表用户空间缓冲空间,count代表读或写的字节数。
// 返回值为实际读取的字节,如果出现错误则返回-1
ssize_t read(int fd, void *buffer, size_t count);
ssize_t write(int fd, void *buffer, size_t count);
// 从标准输入设备读入字符,写入“myfile”中
#define BUF_SIZE = 256;
char buffer[BUF_SIZE];
if(read(STDIN_FILENO,buffer,MAX_READ)==-1) 
   errExit("read failed"); // tlpi 库函数
int outfd = open("myfile",O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR);
if(write(outfd,buffer,numRead)==-1){errExit("write failed");}

1.3 文件偏移量

#include <unistd.h>
// 每个打开的普通文件都有一个内核级的文件偏移量,代表执行下一个读写操作的起始位置。
// fd 代表指定文件,offset 代表偏移量,whence 偏移的参照基点
// 基点可以取以下三个值:SEEK_SET 文件头部,SEEK_CUR 当前文件偏移量,SEEK_END 文件尾部。
off_t lseek(int fd, off_t offset, int whence);
// 文件头部
lseek(fd,0,SEEK_SET);
// 最后一个字节
lseek(fd,-1,SEEK_END);
// 当前位置向前数第十个字节
lseek(fd,-10,SEEK_CUR);

1.4 其他设备I/O操作

一些硬件的设置或控制操作可能无法集成到read/write等通用文件接口,可以通过在硬件驱动程序中定义 device_ioctl 函数来通过一个与每个I/O操作对应的整型变量指定相应的I/O操作。可以通过 man ioct_list 命令查看 Linux内核自带的 ioctl 列表。

#include <sys/ioctl.h>
// fd 代表设备文件描述符号,request对应device_ioctl中定义的不同操作,argp 为相应操作的参数
// 返回值由device_ioctl函数具体的实现决定。
int ioctl(int fd, int request, .../*argp*/);

二、C 标准 I/O

C++ 标准库 cstdio 兼容了 C 标准库 stdio.h 的 I/O 操作与数据类型。库函数简化了用户空间缓存与系统调用的操作,提供了格式、字符编码、本地化 (locale)等功能。

2.1 文件的删除与重命名

#include <cstdio>
// 删除filename指定的文件,成功返回0,失败返回非零值
int remove(const char *filename);
// 将 old 对应的文件重命名为 new。成功返回0,失败返回非零值
int rename(const char*old,const char *new);

2.2 文件的基本操作

2.2.1 打开关闭文件

// 打开名为filename 的文件,mode指定了打开模式。
// 成功返回指向该文件内核数据结构的FILE对象指针,失败返回 NULL。
FILE *fopen(const char *filename, const char *mode);
// 刷新流缓冲区缓存,关闭 FILE 关联的文件,释放流缓冲区。
// 成功返回0,失败返回非零值。
int fclose(FILE *stream);
// 打开文件与更改文件打开模式
// 若filename 为null,则更改 stream 关联文件的打开方式为 mode。
// 否则打开filename 指定文件,将其关联到 stream。
FILE *freopen(const char *filename, const char *mode, FILE *stream);
fopen 打开模式

2.2.2 流缓冲操作

// 设置流缓冲大小与缓冲模式
// buf 代表自定义动态分配缓冲,mode 代表缓冲模式,size代表缓冲大小。
// 缓冲模式有:_IOFBF 全缓冲,即缓冲区满自动刷新;_IOLBF 行缓冲,遇到换行符刷新;
//           _IONBF 无缓冲,没有缓冲区。
// 如果 buf 为 NULL,则stream自动分配缓存,可以通过size 指定缓存大小。
// 成功返回0,不成功返回非零值。
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

2.2.3 格式化 I/O

// 按照 format 指定的格式,将参数 argps 写入stream缓冲。
// format 设置了参数类型,字段的宽度,精度,科学计数法,字段宽度内的对齐模式等等
// 返回写入缓冲的字符数,若出错则返回一个负值。
int fprintf(FILE *restrict stream, cosnt char *format,.../*argps*/);
// 按照 format 指定的格式,从stream缓冲中提取相应的数据到argps里。
int fscanf(FILE *stream, const char *format, .../*argps*/);
// 设置本地化支持语言符号转化
setlocale(LC_CTYPE, "");
wchar_t wc[] = L"中文ΔΣα ГШ≌ち"; //多字节编码字符串
char c[] = "Hello World!"; //ASCII编码字符串
double d = 314.15926535; 
int i = 2566;
FILE* fp = fopen("test.dat","w");
int nbytes = fprintf(fp,"多字节字符串: %ls \   //多字节编码字符串用%ls指代
                       \r普通字符串 %s    \  //ASCII编码字符串用%s指代
                       \r浮点数 %+10.5e  \ //+表示显示正负号,10表示对应数字所占字段宽度
                                         //.5表示小数点后的位数,e表示科学计数法                                        
                       \r整数%Xd",wc,c,d,i); //X代表16进制,d代表整数
printf("Write %d bytes\n", nbytes);
// 读文件内容到用户空间
wchar_t wc[50]={0}; 
char c[50]={0};
double d = 0;
unsigned int i = 0;
FILE* fp = fopen("test.dat","r");
// 依次将format内容与流内容对比:
// 对于format中的普通字符,将其与当前位置的流内容对比,相等则继续,否则返回。
// 对于format中的空格字符,默认将文件指针指向下一个非空格字符,不返回。
// 对于format中的%标识的字符,从文件当前位置开始读取满足标识符类型的最长的字符串(遇到空格停止),
// 将其转化成对应类型的数据赋值给相应的变量。
fscanf(fp,"多字节字符串: %l[^\r]\r",wc);//多字节字符用ls或l[]标识。对于后者,括号内容为
//提取内容的选择范围,^后的内容是读到后必须返回的范围。[abc^\n]意味着从文件读取a、b、c内容,
//遇到\n或非a、b、c内容返回。
fscanf(fp,"普通字符串: %[^\r]\r",c);//ASCII编码字符串用s或[]标识。
fscanf(fp,"浮点数: %lf\r",&d);     //double 类型用lf标识,参数类型为double*
fscanf(fp,"整数:%xd\r",&i);        //16进制内容用xd标识,参数类型为unsigned int*
printf("%ls \n%s \n%lf \n%d \n",wc,c,d,i);

2.2.4 字节 I/O

// 将c对应的ASCII字符写入stream。
// 成功返回c,若发生错误则返回EOF。
int fputc(int c, FILE *stream);
// 将s对应的字符串写入stream。
// 成功返回非零值,失败返回EOF。
int fputs(const char *s, FILE *stream);
// 从stream提取一个字节转化为int作为返回值。
int fgetc(FILE *stream);
// 从stream提取至少n-1个字节到s,s的第n个字节添加'\0'空字节。
// 成功则返回s,失败返回 NULL。
char *fgets(char *s, int n, FILE *stream);
// fgetc的逆向操作,将字符c写回输入流
// 成功返回c,失败返回EOF。
int ungetc(int c, FILE *stream);
// 以ptr为起始地址,向stream写入大小为size字节的元素,写入元素的数目为nmemb。
// 返回写入的元素数,
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE*stream);
// 以ptr为起始地址,从stream读入大小为size字节的元素,读入元素的数目为nmemb。
// 返回读入的元素数。
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
// 写结构体
struct STU{
    char name[12]={0};
    int age=0;
};
struct STU student[5] = {
        {.name={'T','o','m'},.age=10},
        {.name={'A','l','i','c','e'},.age=8},
        {.name={'G','e','o','r','g','e'},.age=11},
        {.name={'J','e','f','f'},.age=9},
        {.name={'A','a','r','o','n'},.age=12}
    };
FILE *fp = fopen("student.txt","w");
// STU对象长度为16字节,fwrite向fp写入5个STU对象,如下图所示。
fwrite(student,sizeof(struct STU),5,fp);
// 读入结构体
FILE *fp = fopen("student.txt","r");
int i = fread(student,sizeof(struct STU),5,fp);
printf("%d elements read.\n",i);
fwrite 写入文件

2.3 文件定位操作

// 获得当前文件位置指针, 将其保存在pos中
// 成功返回0,失败返回非零值。
int fgetpos(FILE *stream, fpos_t *pos);
// 设置当前文件指针为pos。
// 成功返回0,失败返回非零值。
int fsetpos(FILE *stream, const fpos_t *pos);
// 返回当前相对于文件开头的偏移量
long int ftell(FILE *stream);
// 按照偏移量offset和基点whence,设置文件位置指针。
// whence可以取:SEEK_SET文件开头;SEEK_CUR当前位置;SEEK_END;文件结尾。
// 成功返回0,失败返回非零值。
int fseek(FILE *stream, long int offset, int whence);
// 回到文件开头,相当于fseek(stream,0L,SEEK_SET);
void rewind(FILE *stream);

2.4 错误处理函数

// 清除stream的错误标志和EOF标志
void clearerr(FILE *stream);
// stream的EOF标志处于设置位则返回非零值,处于清除位则返回零。
int feof(FILE *stream);
// stream的error标志处于设置为则返回非零值,处于清除位则返回零。
int ferror(FILE *stream);
// 将stream的errno对应的错误信息写入stderr
// 写入格式为:s对应字符串: 错误信息
void perror(const char *s);

三、<iostream> 标准库

iostream 库利用面向对象范式的重载和继承特性实现了 C++ I/O 的类型安全与可拓展性。

iostream 实现了从用户区堆栈到用户区缓存再通过系统调用到外部设备过程中的数据格式、本地化与编码的功能。

从用户堆栈到用户缓存的过程称为格式化层,具体功能有数字的精度、进制、字段宽度、是否忽略空格、填充字符、数字或日期等具体类型数据的本地化等。

从用户区缓存到系统调用的过程称为编码转换层,具体功能有宽字符与多字节编码的相互转化以及本地化的转码功能等。

iostream 框架

在iostream库中,用basic_streambuf 的子类代表用户缓冲区,用basic_ios的子类管理streambuf对象的格式化与编码过程,提供相关属性设置的接口。

3.1 文件基本操作

#include <fstream>
#include <locale>
// 创建 stream 对象,读文件用ifstream ,写文件用ofstream,读写文件用fstream
fstream fp;
// 设置 fp 管理的缓冲区
const unsigned int bufsize = 4096;
char *buf = new char[bufsize];
fp.rdbuf()->pubsetbuf(buf,bufsize);
// 本地化设置,可以通过/var/lib/locales/supported.d 文件夹查看可用locale
fp.imbue(locale("en_HK.UTF-8"));
// 打开关联文件,如果没有手动设置缓冲区,则会启动分配BUFSIZ=8192大小的缓冲区。
fp.open("myfile",ios::in|ios::out|ios::trunc); // 设置打开方式为截断
if (!fp.is_open()) // 判断文件是否成功打开
{
    cerr << "Error open file\n";
    return EXIT_FAILURE;
}
char str[10];
fp >> str; //读文件内容到字符串。  
fp << "Hello World!"; // 写入文件
// 清空用户区缓存
fp.flush();
// 关闭文件,重置 filebuf 对象
fp.close();
fstream 打开模式

3.2 流状态标志与抛出异常

每个流对象保存了一个ios_base::iostate对象,用来保存流的状态标志。

流状态标志
// 判断状态标志
fstream fp("myfile",ios::in|ios::out);
ios_base::iostate fstate = fp.rdstate();//获取状态标志
fp.bad();//return if(fstate==ios::badbit);
fp.good();//return if(fstate==ios::goodbit);
fp.fail();//return if(fstate==ios::failbbit);
fp.eof();//return if(fstate==ios::eofbit);
// 重置状态标志位
fp.clear();//等价于fp.clear(ios::goodbit);
// 当fp状态标志变为badbit或failbit时,抛出异常
try{
    fp.exceptions(ios_base::badbit|ios_base::failbit);
    fp<<"Hello World\n"<<flush;
    ...
}
catch(ios_base::failure& exc){// 处理异常
    cerr<<exc.what()<<endl;
    throw;//继续抛出异常
}

3.2 格式化I/O

3.2.1 stream 的格式设置函数

#include <fstream>
fstream f("myfile");
f.width(10); // 设置字段宽度,每次I/O操作之后会被重置为0。
f.precision(8);// 设置浮点数有效位数
f.fill(*);// 设置空闲字段的填充字符
// 设置字段对齐方式
f.unsetf(ios_base::adjustfield);
f.setf(ios_base::left);
// 设置数字进制表示
f.unsetf(ios_base::basefield);
f.setf(ios_base::hex);
// 设置数字表示方式
f.unsetf(ios_base::floatfield);
f.setf(ios_base::scientific);//科学计数法,ios_base::fixed表示固定点法
// 将bool变量用true和false表示
f.setf(ios_base::boolalpha);
// 用+显示正数
f.setf(ios_base::showpos);
// 显示浮点数小数点
f.setf(ios_base::showpoint);
// 显示进制前缀
f.setf(ios_base::showbase);
// 字母大写
f.setf(ios_base::uppercase);
// 每次格式化I/O操作都清空缓冲区
f.setf(ios_base::unitbuf);
// 读文件时不将空格视为分隔符
f.setf(ios_base::skipws);

3.2.2 stream 操作符

可以利用插入/提取算符设置stream 格式。

#include <iomanip>
#inlcude <iostream>
cout << setw(10) << boolalpha << dec<<left<<setfill('_')<<endl;

3.2.3 自定义类型I/O

struct STU{
  string name=“”;
  unsigned int id=0;
  friend istream& operator>>(istream& is, STU& stu);
  friend ostream& operator<<(ostream& os, const STU& stu);
}
ostream& operator<<(ostream& os, const STU& stu){
    return os <<"|"<<setw(10)<<left<< stu.name+": "<<"|"
              <<setw(2)<<right<< stu.id<<"|";
}
istream& operator>>(istream& is, STU& stu){
    is.ignore(1,'|'); //忽略第一个‘|’字符
    return is >>setw(10)>>stu.name >> stu.id;
}
STU tom{"tom",10};
STU jerry{"jerry",8};
fstream fp("myfile",ios::in|ios::out|ios::app);
// 写STU对象
fp << tom<<"\n"<<jerry;
// 读STU对象
fp.seekg(ios::beg);// 读指针移动到文件开头
STU stu;
fp >> stu;

3.3 字节 I/O

3.3.1 字节操作

// 二进制模式打开文件,遇到换行符不会进行内部转换
// 如果不用binary模式,则'\n'在输出时会转化成系统决定的换行符(输入时相反)
// 对于Linux系统来说是'\n'(0x0A),对于Windows系统来说是'\r''\n'(0x0D 0x0A)。
fstream f("myfile.b",ios::in|ios::out|ios::binary);
// 写一个字节
fp.put(0x65);
fp.put('&');
// 读一个字节
char c;
fp.get(c);
// 写连续几个字节
char buf[1024]="aoeiuv";
fp.write(buf,3); // 从buf开始写入3个字节
fp.write(buf+2,2); // 从 buf第三个元素开始写入两个字节 
char read_array[10];
fp.read(read_array,4);//从当前读指针位置开始读4个字节到read_array
// 行读取
char arr[1024];
// 读入最多10个字节内容(实际读了9个字节加上结尾添加的'\0'),遇到换行符或'a'时停止,读指针指向'a'或换行符后面的第一个字节。
fp.getline(arr,10,'a');

3.3.2 文件定位

fstream f("myfile",ios::in|ios::out|ios::app);
int gp = f.tellg();// 获取读指针相对于文件开头的偏移量
int pp = f.tellp();// 获取写指针相对于文件开头的偏移量
f.seekg(-5);// 将读指针后退五个字节
f.seekg(ios::beg);// 将读指针移动到文件开头
f.seekg(-10,ios::end);// 将读指针移动到EOF前第10个字节
// f.seep()同理
// 返回当前读指针所在字节,读指针不发生变化
char cur = f.peek();

链接

编辑于 2021-09-21 19:41