c++ ofstream 文件不存在_【C陷阱与缺陷】第5章 库函数

本文详细介绍了C++中的ofstream库函数,包括如何处理文件不存在的情况,getchar函数的使用,文件打开模式,缓冲输出与内存分配,以及错误检测中的errno使用。文章还提及了signal函数在捕获异步事件时的角色和注意事项。
摘要由CSDN通过智能技术生成

上一章:【C陷阱与缺陷】第4章 连接器

下一章:【C陷阱与缺陷】第6章 预处理器

​ c语言没有标准输入输出函数,所有的输入输出都需要靠库函数。ANSI C标准也说明了这一点。不过ANSI C标准没提供执行“底层”I/O操作的read/write函数,但是大多数c编译器的实现都包含了这些库函数。


1. 返回整数的"getchar"函数

getchar()函数一般返回标准输入文件的下一个字符int类型),当没有输入时返回EOF。其声明为:

int getchar();
//getc(stdin);//getchar();相当于这个

下个例子:

#include <stdio.h>

int main() {
    char c;
    while ((c = getchar()) != EOF)
    return 0;
}

可能运行不正常,因为变量c被声明为char类型,而不是int类型。getchar()返回的结果可能容不下,特别是EOF,情况如下:

  1. 某些合法的输入字符被截断后,恰好与EOF相同,造成提前结束循环。
  2. c可能根本取不到EOF,陷入死循环。
  3. 巧合下能够正常运行,不过是因为c = getchar()这语句的操作时使得getchat()返回值赋值给c后,丢弃该值,并把getchar()的返回值作为该语句的结果,这样就是检测getchar()的返回值与EOF的比较。这样就恰好能运行。

在很多实现中,getchar()并不是函数,而是宏实现,这样可以加速调用的速度,putchar()也一样。当真的使用这些函数时,速度可能会减慢。


2. 更新文件顺序

FILE *fopen(char *address, char *mode)的第二个参数的打开方式表如下:

| 打开方式 | 说明 | | :------: | :----------------------------------------------------------: | | r | “只读”方式打开,不允许写入。文件必须存在 | | w | “只写”方式打开,不读。如果文件不存在就创建新的文件;如果存在就清空原文件。 | | a | 以“追加”方式打开。若文件存在,则在文件末尾添加,保留原文件;若不存在,则新建一个。 | | r+ | 以“读写”方式打开,可写入也可读取,可以随意更新文件。文件必须存在 | | w+ | 以“写入/更新”方式打开,相当与"r+"与"w",随意更新或读取。如果文件不存在就创建新文件;若文件存在就清空原文件。 | | a+ | 以“追加/更新”打开,相当于"r+"和"a",可读可写,随意更新。若文件不存在就更新;若文件存在就在末尾追加。 | | t | 与打开方式1到6组合,如果没有,默认是t | | b | 与打开方式1到6组合,打开二进制文件 |

这是可读写方式打开一个文件的操作。

struct record rec;
//TODO
FILE *fp;
fp = fopen(file_addr, "r+");

while (fread((char *)&rec, sizeof(rec), 1, fp) == 1) {
    /*对rec操作*/
    if (/*rec需要重新写入*/) {
        fseek(fp, -(long)sizeof(rec), 1);
        fwrite((char *)&rec, sizeof(rec), 1, fp);
    }
}

​ 但别以为这样就可以自由地交错使用读写操作。这是为了兼容历史问题,在freadfwrite之间并不能自由地切换。在fread操作与fwrite操作之间必须使用fseek函数进行切换。当然,两个fwrite之间或者两个fread之间并不需要fseek。上述程序需要改成这样:

struct record rec;
//TODO
FILE *fp;
fp = fopen(file_addr, "r+");

while (fread((char *)&rec, sizeof(rec), 1, fp) == 1) {
    /*对rec操作*/
    if (/*rec需要重新写入*/) {
        fseek(fp, -(long)sizeof(rec), 1);
        fwrite((char *)&rec, sizeof(rec), 1, fp);
        fseek(fp, 0L, 1);
    }
}

fseek函数的用法如下:

int fseek(FILE *stream, long int offset, int origin);
//stream: FILE指针。
//offset: 一个long类型变量,为从origin位置移动offset字节。正就向前,负就向后,0就原地
//origin: 0表示文件头(SEEK_SET),1表示当前位置(SEEK_CUR),2表示文件末尾(SEEK_END)。

3. 缓冲输出与内存分配

程序输出有两种方式:1. 即时处理方式;2. 先暂存,然后大块写入方式。

方式2一般通过库函数setbuf实现,设buf为一个大小适当的数组,则通过语句

setbuf(stdout, buf);

通知输入/输出库,写入到stdout的输出使用buf作为输出的缓冲区,直到buf缓冲区已满或者调用fflush(对于写操作打开的文件,调用fflush会使得缓冲区内容被实际写入到文件中)。buf缓冲区的大小定义在<stdio.h>BUFSIZ中。下列错误例子:

#include <stdio.h>

int main(void) {
    int c;
    char buf[BUFSIZ];
    setbuf(stdout, buf);    //use buffer while output to stdout

    while ((c = getchar()) != EOF) {
        putchar(c);
    }
    return 0;
}

之所以错误是因为输出可能并不能放慢缓冲区,而且也没认为调用fflush,所以在退出main函数后,虽然会输出缓冲区,但此时buf数组已经被释放了,造成输出错误。解决方法:

  1. 声明静态缓冲区c static char buf[BUFSIZ];
  2. buf数组的声明放在main函数外。
  3. 动态分配buf内存。c char *malloc(); setbuf(stdout, malloc(BUFSIZ));
    这里并不需要考虑malloc是否分配成功,因为如果buf是个NULL指针,那么stdout就不需要缓冲直接输出。

4. 使用errno检测错误

与操作系统相关的一些库函数,当执行失败时,会通过一个errno的外部变量通知程序,函数调用失败。下面为错误例子:

/*调用库函数*/
if (errno) {
    /*错误处理*/
}

因为在库函数调用没有失败时,没有强制要求一定设置errno为0,这样可能会保留前一个失败库函数的errno导致检测错误。下面这样改也是错误的:

errno = 0;
/*调用库函数*/
if (errno) {
    /*错误处理*/
}

因为库函数成功调用,并没有强制使得errno清零,也没有禁止设置errno的值。因为i可能会有以下情况:当调用fopen函数新建一个文件时,fopen需要调用其他函数检测是否存在同名函数,这时就需要直到被调用的那个函数的errno的具体值,这样就修改了errno的值。就算fopen正常执行并返回,errno值也可能非0。


5. 库函数signal

所有c语言的实现都包含signal库函数,这是个捕获异步事件的方式,开头加上:

#include <signal.h>

//调用方法:
signal(signal type, handler function);

signal type是头文件signal.h的某些常量,表示signal函数要捕获的信号类型handler function是当指定事件发生时调用的事件处理函数

​ 因为c语言中,信号是真正的异步,所以信号可能出现在任何时候,比如在malloc的调用过程中。

1.  当`malloc`的执行过程被信号中断,则`malloc`用于跟踪**内存分配的数据结构**就可能只有部分被更新,此时`signal`函数再调用`malloc`的时候,就会导致该数据结构完全崩溃。
2.  `signal`函数使用long jmp退出时通常也是不安全的,因为信号可能在库函数没完成更新某些数据结构时发生,这就会导致更新混乱。

​ 所以signal函数最好只设置一些标志就返回,让主程序能够检测出标志的改变,发现信号的发生。

  1. 当程序发生算术运算时发生错误(除零/溢出)时,会产生相应的信号,某些机器在signal处理完这些信号时,返回到上一个状态,继续重新执行该算术运算,而且没有一个可移植的方法处理这些操作数,于是导致反复引发同一个信号。对于这个

​ 对于这个错误,signal函数唯一安全、可移植的操作就是打印出错信息,然后long jmp或者exit退出程序。

​ 处理信号时,很多操作都是不可移植的。所以最好的办法就是让signal尽量简单,并组织在一起。

上一章:

DeathWatch:【C陷阱与缺陷】第4章 连接器​zhuanlan.zhihu.com
8dcc4dbf5338cb3c97c9d42c6cbe4865.png

下一章:

DeathWatch:【C陷阱与缺陷】第6章 预处理器​zhuanlan.zhihu.com
02dbea4e5e4e70ad787610689e1b9058.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值