C库函数:stdio.h

stdio.h

C 标准库 – <stdio.h> | 菜鸟教程 (runoob.com)

下面是头文件 stdio.h 中定义的变量类型:

序号变量 & 描述
1size_t
这是无符号整数类型,它是 sizeof 关键字的结果。
2FILE
这是一个适合存储文件流信息的对象类型。
3fpos_t
这是一个适合存储文件中任何位置的对象类型。

其中的FILE比较常见,那么这个FILE到底是什么?

描述中声称FILE是一个适合存储文件流信息的对象类型。

在C语言中,用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。

定义文件指针的一般形式为:

FILE *fp;

这里的FILE,实际上是在stdio.h中定义的一个结构体,该结构体中含有文件名、文件状态和文件当前位置等信息,fopen 返回的就是FILE类型的指针。

注意:FILE是文件缓冲区的结构,fp也是指向文件缓冲区的指针。

不同编译器stdio.h 头文件中对 FILE 的定义略有差异,这里以标准C举例说明:

下面说一下如何控制缓冲区。

我们知道,当我们从键盘输入数据的时候,数据并不是直接被我们得到,而是放在了缓冲区中,然后我们从缓冲区中得到我们想要的数据 。如果我们通过setbuf()或setvbuf()函数将缓冲区设置10个字节的大小,而我们从键盘输入了20个字节大小的数据,这样我们输入的前10个数据会放在缓冲区中,因为我们设置的缓冲区的大小只能够装下10个字节大小的数据,装不下20个字节大小的数据。那么剩下的那10个字节大小的数据怎么办呢?暂时放在了输入流中。请看下图:

上面的箭头表示的区域就相当是一个输入流,红色的地方相当于一个开关,这个开关可以控制往深绿色区域(标注的是缓冲区)里放进去的数据,输入20个字节的数据只往缓冲区中放进去了10个字节,剩下的10个字节的数据就被停留在了输入流里!等待之后往缓冲区中放入!那么系统是如何来控制这个缓冲区呢?

再说一下 FILE 结构体中几个相关成员的含义:
cnt  // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
ptr  // 下一个要被读取的字符的地址
base  // 缓冲区基地址

在上面我们向缓冲区中放入了10个字节大小的数据,FILE结构体中的 cnt 变为了10 ,说明此时缓冲区中有10个字节大小的数据可以读,同时我们假设缓冲区的基地址也就是 base 是0x00428e60 ,它是不变的 ,而此时 ptr 的值也为0x00428e60 ,表示从0x00428e60这个位置开始读取数据,当我们从缓冲区中读取5个数据的时候,cnt 变为了5 ,表示缓冲区还有5个数据可以读,ptr 则变为了0x0042e865表示下次应该从这个位置开始读取缓冲区中的数据 ,如果接下来我们再读取5个数据的时候,cnt 则变为了0 ,表示缓冲区中已经没有任何数据了,ptr 变为了0x0042869表示下次应该从这个位置开始从缓冲区中读取数据,但是此时缓冲区中已经没有任何数据了,所以要将输入流中的剩下的那10个数据放进来,这样缓冲区中又有了10个数据,此时 cnt 变为了10 ,注意了刚才我们讲到 ptr 的值是0x00428e69 ,而当缓冲区中重新放进来数据的时候这个 ptr 的值变为了0x00428e60 ,这是因为当缓冲区中没有任何数据的时候要将 ptr 这个值进行一下刷新,使其指向缓冲区的基地址也就是0x0042e860这个值!因为下次要从这个位置开始读取数据!

在这里有点需要说明:当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符\n,这个换行符\n也会被存储在缓冲区中并且被当成一个字符来计算!比如我们在键盘上敲下了123456这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7 ,而不是6。


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

1int fclose(FILE *stream)
关闭流 stream。刷新所有的缓冲区。

C 库函数 int fclose(FILE *stream) 关闭流 stream。刷新所有的缓冲区。

直接传入一个文件类型指针即可。

如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。

EOF
这个宏是一个表示已经到达文件结束的负整数。
3int feof(FILE *stream)
测试给定流 stream 的文件结束标识符。

当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

注意,这句话的意思是,如果到了结束符,就返回一个非零值,要是还没读到结束符,就返回0。

5int fflush(FILE *stream)
刷新流 stream 的输出缓冲区。

7FILE *fopen(const char *filename, const char *mode)
使用给定的模式 mode 打开 filename 所指向的文件。
  • filename -- 字符串,表示要打开的文件名称(全路径)
  • mode -- 字符串,表示文件的访问模式,可以是以下表格中的值:
模式描述
"r"打开一个用于读取的文件。该文件必须存在。
"w"创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。
"a"追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。
"r+"打开一个用于更新的文件,可读取也可写入。该文件必须存在。
"w+"创建一个用于读写的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。
"a+"打开一个用于读取和追加的文件。

fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。

该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。

在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。

打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。

标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,其本质也是个FILE类型指针,可直接使用。我们通常自定义的是我们自己的文件位置,而这三个位置是系统已经定义好的,标准化的。

10stderr、stdin 和 stdout
这些宏是指向 FILE 类型的指针,分别对应于标准错误、标准输入和标准输出流。
8size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
从给定流 stream 读取数据到 ptr 所指向的数组中。

参数

  • ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size -- 这是要读取的每个元素的大小,以字节为单位。
  • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值

成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

使用示例:

#include <stdio.h>
#include <string.h>
 
int main()
{
   FILE *fp;
   char c[] = "This is runoo";
   char buffer[20];
 
   /* 打开文件用于读写 */
   fp = fopen("file.txt", "w+");
 
   /* 写入数据到文件 */
   fwrite(c, strlen(c) + 1, 1, fp);
 
   /* 查找文件的开头 */
   fseek(fp, 0, SEEK_SET);
 
   /* 读取并显示数据 */
   fread(buffer, strlen(c)+1, 1, fp);
   printf("%s\n", buffer);
   fclose(fp);
   
   return(0);
}

运行结果:

这个程序是菜鸟教程中给的示例程序,但是有个疑惑。

fread中,第二个参数为啥是strlen() + 1,这也不像是要读取的元素的大小呀,第三个参数为啥是1,而不是所有字符的总个数?

教程中给的例子应该不会有问题,那么就是我的理解问题了。

我理解的元素是字符,即一个字符一个字符地去读取,显然,这里不是这个意思。

那么,上述中提到的“元素”指的是什么?

这个函数,很容易理解错误。以为是一个字符一个字符地去读取。

count是要读取的字符个数,size是每个字符的字节数。

我觉得上述官方的描述很有问题。

下面查阅资料,给到真正的理解。

参数

  • ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size -- 这是每次要读取的字节数。
  • nmemb -- 这是要分多少次来读取目标数据。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

fread是一个分段读取文件的函数,count就是分多少段,举例来说,同样在文件中读100个字节:
int a = fread(buf, 1, 100, fp);   // a = 100
int b = fread(buf, 100, 1, fp);   // b = 1
其实count影响的是返回值,fread返回的是成功读取多少段,所以一般情况下,如果需要准确的知道到底读取了多少个字节,把size设为1,把count设为你需要读取的字节数,这样fread的返回值就是读取的字节数了。
性能方面不用担心,fread底层并不是一段一段调用系统调用去读的,是优化过的。

(1) 调用格式:fread(buf, sizeof(buf), 1, fp);
读取成功时:当读取的数据量正好是sizeof(buf)个Byte时,返回值为1(即count)
(2)调用格式:fread(buf, 1, sizeof(buf), fp);
读取成功返回值为实际读回的数据个数(单位为Byte)

这样一来,就理解了该函数,如果是这样,那么在上述案例中,如果将strlen(c)+1换成sizeof(c),应该是一样的效果。经过测试,确实如此。

10int fseek(FILE *stream, long int offset, int whence)
设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset -- 这是相对 whence 的偏移量,以字节为单位。
  • whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量描述
SEEK_SET文件的开头
SEEK_CUR文件指针的当前位置
SEEK_END文件的末尾

返回值

如果成功,则该函数返回零,否则返回非零值。

示例如下:

注意,偏移量是不算第一个字符的。

也就是说文件的开头指的是第一个字符。

13size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
把 ptr 所指向的数组中的数据写入到给定流 stream 中。

fwrite()函数常用于将一块内存区域中的数据写入到本地文本。

fwrite是fread的逆操作,参数是一样的。返回值也是一样的。只不过传输的方向不同。 

14int remove(const char *filename)
删除给定的文件名 filename,以便它不再被访问。

如果成功,则返回零。如果错误,则返回 -1,并设置 errno。 

15int rename(const char *old_filename, const char *new_filename)
把 old_filename 所指向的文件名改为 new_filename。

如果成功,则返回零。如果错误,则返回 -1,并设置 errno。 

16void rewind(FILE *stream)
设置文件位置为给定流 stream 的文件的开头。

17void setbuf(FILE *stream, char *buffer)
定义流 stream 应如何缓冲。

该函数应在与流 stream 相关的文件被打开时,且还未发生任何输入或输出操作之前被调用一次。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
  • buffer -- 这是分配给用户的缓冲,它的长度至少为 BUFSIZ 字节,BUFSIZ 是一个宏常量,表示数组的长度。
BUFSIZ
这个宏是一个整数,该整数代表了 setbuf 函数使用的缓冲区大小。

返回值

该函数不返回任何值。

示例如下:

18int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
另一个定义流 stream 应如何缓冲的函数。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
  • buffer -- 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
  • mode -- 这指定了文件缓冲的模式:
模式描述
_IOFBF全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
_IOLBF行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
_IONBF无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。
  • size --这是缓冲的大小,以字节为单位。

返回值

如果成功,则该函数返回 0,否则返回非零值。

实例演示:

和setbuf相比,主要多了可以选择缓冲模式。

21int fprintf(FILE *stream, const char *format, ...)
发送格式化输出到流 stream 中。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • format -- 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:
specifier(说明符)输出
c字符
d 或 i有符号十进制整数
e使用 e 字符的科学科学记数法(尾数和指数)
E使用 E 字符的科学科学记数法(尾数和指数)
f十进制浮点数
g自动选择 %e 或 %f 中合适的表示法
G自动选择 %E 或 %f 中合适的表示法
o有符号八进制
s字符的字符串
u无符号十进制整数
x无符号十六进制整数
X无符号十六进制整数(大写字母)
p指针地址
n无输出
%字符
 
flags(标识)描述
-在给定的字段宽度内左对齐,默认是右对齐(参见 width 子说明符)。
+强制在结果之前显示加号或减号(+ 或 -),即正数前面会显示 + 号。默认情况下,只有负数前面会显示一个 - 号。
(space)如果没有写入任何符号,则在该值前面插入一个空格。
#与 o、x 或 X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X。
与 e、E 和 f 一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示显示小数点。
与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除。
0在指定填充 padding 的数字左边放置零(0),而不是空格(参见 width 子说明符)。

思考:为什么我们在日常使用时,很多时候明明没有“-”号也是左对齐?


答:左对齐还是右对齐是在有对齐需要的时候才有意义的。由于你并没有指定每个int值输出的长度,因此int 值有多长就会输出多长,此时没有讨论左对齐或者右对齐的意义。
你只有加上%10d(10 只是我举的例子),这时候限定了int 值输出长度为10,而12345这个int值的长度为5,这时候就有左对齐还是右对齐的分别了。

参考此题:

width(宽度)描述
(number)要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断
*宽度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
注意,这里的number是指定的数字。
.precision(精度)描述
.number对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。
对于 e、E 和 f 说明符:要在小数点后输出的小数位数。
对于 g 和 G 说明符:要输出的最大有效位数。
对于 s: 要输出的最大字符数。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。
对于 c 类型:没有任何影响。
当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。
.*精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
 
length(长度)描述
h参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)。
l参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)。
L参数被解释为长双精度型(仅适用于浮点数说明符:e、E、f、g 和 G)。
  • 附加参数 -- 根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。

返回值

如果成功,则返回写入的字符总数,否则返回一个负数。

下面的实例演示了 fprintf() 函数的用法。

和printf类似,只是fprintf指定了一个输出的目标。

个人总结:宽度这个选项因为不会对更长的输出做截断,所以通常只在决定左对齐还是右对齐时有一定的作用。精度这个选项通常用在浮点数输出或者字符串输出时。

22int printf(const char *format, ...)
发送格式化输出到标准输出 stdout。
  • format -- 这是字符串,包含了要被写入到标准输出 stdout 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:

格式字符意义
a, A

以十六进制形式输出浮点数(C99 新增)。

实例 printf("pi=%a\n", 3.14); 输出 pi=0x1.91eb86p+1

d以十进制形式输出带符号整数(正数不输出符号)
o以八进制形式输出无符号整数(不输出前缀0)
x,X以十六进制形式输出无符号整数(不输出前缀Ox)
u以十进制形式输出无符号整数
f以小数形式输出单、双精度实数
e,E以指数形式输出单、双精度实数
g,G以%f或%e中较短的输出宽度输出单、双精度实数
c输出单个字符
s输出字符串
p输出指针地址
lu32位无符号整数
llu64位无符号整数

对于字符串来说,注意%ns和%.ns的区别:

#include <stdio.h>

int main()
{	
    printf("Hello, World! %5s\n","abcdefg");//Hello, World! abcdefg
	printf("Hello, World! %.5s\n","abcdefg");//Hello, World! abcde
   
    return 0;
}

%s:例如:printf("%s", "CHINA")输出"CHINA"字符串(不包括双引号)
%ms:输出的字符串占m列,如果字符串本身长度大于m,则突破m的限制,将字符串全部输出。若串长小于m,则左补空格。
%-ms:输出的字符串占m列,如果字符串本身长度大于m,则突破m的限制,将字符串全部输出。如果串长小于m,则在m列范围内,字符串向左靠,右补空格。
%m.ns:输出占m列,但只取字符串中左端n个字符。这n个字符输出在m列的右侧,左补空格。
%-m.ns:其中m、n含义同上,n个字符输出在m列范围的左侧,右补空格。如果n>m,则自动取n值,即保证n个字符正常输出。 

参考:

C语言中printf打印形式(%02X, %2X, %-2X, %.nf, %m.nf, %e, %m.ne, %2d, %-2d, %02d, %.2d)_printf %.2x-CSDN博客

当格式符个数小于后面的变量个数,则忽略后面多余的变量。

当需要输出%时,可以使用%%,此时,输出的就是%本身。

验证如下:

23int sprintf(char *str, const char *format, ...)
发送格式化输出到字符串。

如果想要把数字转成字符串,可以使用sprintf函数。

其实,还有个常见的itoa函数,int to array,但是itoa并不是一个标准的C函数,它是Windows特有的,如果要写跨平台的程序,请用sprintf。标准库中有sprintf,功能比itoa更强,但是其比itoa系列函数运行速度慢。嵌入式中一般使用sprintf。

注意:sprintf传入char *没用,只能传入char数组

更多用法参考:

sprintf的用法-腾讯云开发者社区-腾讯云

27int fscanf(FILE *stream, const char *format, ...)
从流 stream 读取格式化输入。

 

we are in 2014,输入时,遇到空格就视作是一个输入的结束。

比如,我将这里改成:

输出就变成了:

28int scanf(const char *format, ...)
从标准输入 stdin 读取格式化输入。

&a、&b、&c 中的 & 是地址运算符,分别获得这三个变量的内存地址。

所以说,输入时,实际是存入变量对应的内存地址的。

注意:

1、输入时,格式符之间是什么样的分隔,输入时就要是什么样的分隔。

2、在用 %c 输入时,空格和"转义字符"均作为有效字符。

3、接收字符串:

注:

*这是一个可选的星号,表示数据是从流 stream 中读取的,但是可以被忽视,即它不存储在对应的参数中。
width这指定了在当前读取操作中读取的最大字符数。

30int fgetc(FILE *stream)
从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

34int getc(FILE *stream)
从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

35int getchar(void)
从标准输入 stdin 获取一个字符(一个无符号字符)。

该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

 32int fputc(int char, FILE *stream)
把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

参数

  • char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

返回值

如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

实例演示:

37int putc(int char, FILE *stream)
把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。
38int putchar(int char)
把参数 char 指定的字符(一个无符号字符)写入到标准输出 stdout 中。

31char *fgets(char *str, int n, FILE *stream)
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

参数

  • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n -- 这是要读取的最大字符数(包括最后的空字符)。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

返回值

如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

如果发生错误,返回一个空指针。

也就是说,只要读取不成功,就会返回NULL。

33int fputs(const char *str, FILE *stream)
把字符串写入到指定的流 stream 中,但不包括空字符

该函数返回一个非负值,如果发生错误则返回 EOF。

36char *gets(char *str)
从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

如果成功,该函数返回 str。如果发生错误或者到达文件末尾时还未读取任何字符,则返回 NULL。

看一道题:

gets()函数从终端输入一个字符串到数组,直到按回车键为止,并把回车键保存为'\0‘

strcat会寻找第一个'\0'所以后面的数字才被清除了。

get(ss)之后,ss的内容变成了"ABC\03,4,5",之后strcat会寻找\0来接续,所以最后的结果是ABC6789

39int puts(const char *str)
把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。

41void perror(const char *str)
把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。

该函数不返回任何值。

补充

关于EOF

看这道题:

EOF是End Of File的意思,在C语言中定义的一个宏,用作文件结束标志。
该宏定义在stdio.h中,从数值角度看,就是-1
#define EOF (-1)

在C语言中,或更精确地说成C标准函数库中表示文件结束符 (endof file )。这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII 代码值的形式存放。我们知道,ASCII 代码值的范围是 0~255,不可能出现-1,因此可以用EOF作为文件结束标志。

文本文件和二进制文件到底啥区别?

文件分为文本文件和二进制文件两种。

文本文件和二进制文件的区别不在于两者的物理存储,而是在于两者对所存储二进制数的逻辑解释上。

所谓文本文件,也就是文件存储的是我们人类可读的一个个字符,比如"hello", "你好”等。当然,最后也是以二进制的方式存储在计算机上。为了把字符表示成二进制,事先得有一个所谓的码表,给出了每个字符与二进制数的对应关系,比如著名的ASCII码表。

除了用上面这种方式存储的文件,其余文件都应该被理解为二进制文件。有人说二进制文件其实就是存储的二进制数,这个不完全正确。因为本质上文件都是存储的二进制数。比如65表示成二进制数为01000001,如果是文本文件,那查ASCII码表,则是大写字母'A‘,但如果不是文本文件,它就是二进制的01000001,至于说到底代表什么含义,则是创建这个文件的用户自定义的。它可以是一个二进制的整数值,也可以是某个指令的编号,或者是某个内存地址的一部分。

那到底怎么区分文本文件和二进制文件呢?

有人说根据文件的扩展名区分,但是扩展名完全可以人为地修改,而且有些文件压根就没有扩展名,因此通过扩展名区分文件是文本文件还是二进制文件是不可靠的。

那有没有完全可靠的方法来区分文本文件和二进制文件呢?

很遗憾,严格意义上来说,不存在!哦,也不对,只能说是对文件创建者之外的人或机器来说,不存在。唯一知道能完全确定文件是文本文件还是二进制文件的就是文件创建者。

打印时,int的话%d不会有问题,64位使用%lld打印

浅谈%d, %ld, %lld 区别-CSDN博客

printf不是线程安全函数,在多线程打印时大概率会出问题,因为会插入其他的数据,会让人误以为是需要的数据,其实并不是。这一点千万注意。 C语言中的vsnprintf函数

C语言中的vsnprintf函数

参考:

C语言中的vsnprintf函数 - 禅元天道 - 博客园 (cnblogs.com)

函数原型:

int vsnprintf(char* sbuf, size_t n, const char* format, va_list arg)

函数说明:该函数用于向一个字符串缓存区格式化打印字符串,且可以限定打印字符串的最大长度。该函数需要C99以上版本支持。

函数参数:

  • sbuf:指向存储生成的C字符串的缓存区的指针,缓存区的大小应至少为n个字符;
  • n:缓存区中可以存储的最大字节数,生成的字符串长度最多为n-1(为额外的终止符预留空间);
  • format:C 字符串,其中包含一个格式字符串,该字符串遵循与printf中的格式相同的规范;
  • arg:标识使用 va_start 初始化的变量参数列表的值。

函数返回值:

如果足够大,则返回写入的字符数,不包括终止空字符。如果发生编码错误,则返回负数。请注意,仅当此返回值为非负值且小于n时,字符串才完全写入。

另外还有个函数snprintf

snprintf和sprintf的区别

snprintfsprintf 都是C语言中用于格式化字符串的函数,但它们有一些关键的区别。以下是对它们的详细比较:

函数原型

  • sprintf:

int sprintf(char *str, const char *format, ...);
  • snprintf:

int snprintf(char *str, size_t size, const char *format, ...);

功能

  • sprintf:

    • 将格式化的数据写入到字符串 str 中。

    • 不会检查目标字符串的大小,可能导致缓冲区溢出(如果目标字符串空间不足)。

    • 返回值是写入的字符数(不包括终止的空字符\0)。

  • snprintf:

    • 同样将格式化的数据写入到字符串 str 中,但它会检查目标字符串的大小,避免缓冲区溢出。

    • size 参数指定了目标字符串的最大大小(包括终止的空字符\0)。

    • 如果格式化后的数据长度超过 size,则只写入 size - 1 个字符,并确保字符串以 \0 结尾。

    • 返回值是格式化后的字符串长度(不包括终止的空字符\0),或者如果输出被截断,则返回值将是负数。

安全性

  • sprintf:

    • 不安全,容易导致缓冲区溢出,特别是在处理用户输入时。

  • snprintf:

    • 更安全,因为它限制了写入的字符数,防止缓冲区溢出。

适用场景

  • sprintf:

    • 适用于目标字符串大小已知且足够大的情况。

  • snprintf:

    • 更适用于需要严格控制输出大小的场景,特别是防止缓冲区溢出的情况下。

总结来说,snprintfsprintf 更安全,因为它可以防止缓冲区溢出,并且提供了更多的控制。在现代C编程中,推荐使用 snprintf 来替代 sprintf,以提高代码的安全性和稳定性。

 

格式字符串的直接换行问题

在C语言中,printf、sprintf、snprintf 等函数的格式字符串不能直接在程序里换行。

首先说明,格式字符串里能包含换行符

在 C 语言中,换行符用 \n 表示。当使用 snprintf 时,如果格式字符串中包含 \n,那么输出的字符串也会包含相应的换行符。

示例

以下是一个包含换行符的 snprintf 示例:

在这个例子中,snprintf 将格式化后的字符串写入到 buffer 中,其中包含了一个换行符 \n。输出结果如下:

注意事项

  • 缓冲区大小: 确保目标缓冲区足够大以容纳所有字符,包括换行符和终止的空字符 \0

  • 返回值: snprintf 返回的是格式化后的字符串长度(不包括终止的空字符 \0),如果返回值大于或等于指定的缓冲区大小,则表示输出被截断。

总结

通过在格式字符串中使用 \n,可以在 snprintf 生成的字符串中插入换行符,从而实现多行输出。这在需要格式化复杂文本输出时非常有用。

但是,在写格式字符串时,如果格式字符串太长,一行写不下,不能直接在代码里换行,否则会报错,比如:

提示缺少终止符。

在C语言中,printf、sprintf、snprintf 等函数的格式字符串不能直接换行,因为这些函数的格式字符串必须是一个单一的、连续的字符序列。

如果需要换行,但是又换不了,怎么办呢?

解决办法是:每一行都要用双引号

扩展一下,那就是C语言中的字符串都不能直接换行。

在C语言中,字符串是一个连续的字符序列,通常被包含在双引号内,并且必须写在同一行上。

有时候,如果一行代码太长而需要换行,可以通过使用反斜杠(\)作为续行符来实现。当编译器遇到反斜杠时,会将其后面的换行符忽略,并将下一行的内容视为当前行的一部分。但是这种方法只适用于一般的语句和宏定义,并不适用于字符串常量。

再扩展

C语言中哪些地方能直接换行?

在C语言中,可以在以下地方直接换行:

宏定义

在宏定义中,可以使用反斜杠(\)作为续行符,将一行代码分成多行显示。例如:

这种方式在编译时,反斜杠后面的换行符将被忽略,当做一行处理。

字符串常量

对于过长的字符串常量,可以将其拆分为多个较短的字符串,并用双引号引起来。例如:

这种方式会默认合并为一个常量字符串。

执行语句

当一个语句过长时,可以直接换行,并不会影响语句的编译。

具体以所用编译器为准。但是都遵循一点,那就是别把一个完整的字串打断,比如结构体指针访问a->element,别把这个完整的语句串给分成两行。一般都是在逗号后面换行。

fileno函数

fileno函数用于获取文件流对应的文件描述符。

fileno函数在C语言中是一个常用的标准库函数,它的主要功能是返回与给定文件流相关联的文件描述符。这个函数的定义在<stdio.h>头文件中,其函数原型为int fileno(FILE *stream);。其中,参数stream是一个指向FILE对象的指针,该对象代表了已经打开的文件流。函数的返回值是与该文件流相对应的文件描述符,如果操作失败则返回-1。

fileno函数的使用场景非常广泛,特别是在需要进行非标准I/O操作或者系统调用时。例如,当需要对文件进行底层操作如设置文件锁或调整文件大小等,可以通过fileno函数获取文件描述符,然后使用相应的系统调用来完成这些任务。此外,在某些情况下,程序可能需要同时使用标准I/O和系统级I/O操作,此时fileno函数就显得尤为重要,因为它提供了一种在不同级别的I/O操作之间转换的方法。

注意,FILE对象和文件描述符并不是同一个东西,要注意区分二者:

在计算机编程和操作系统中,FILE对象和文件描述符(File Descriptor)是两个重要但不同的概念。它们在定义、使用场景以及跨平台兼容性等方面存在区别,具体分析如下:

  1. 定义

    1. FILE对象:FILE对象是一个数据结构,用于表示一个打开的文件流。它在C语言标准库中定义,并包含了文件的相关信息,如文件名、文件状态、位置指针等。

    2. 文件描述符:文件描述符是一个非负整数,它是由操作系统内核分配给进程以引用已打开文件或设备的一种标识符。

  2. 使用场景

    1. FILE对象:通常用于高级I/O操作,例如通过fopenfclosefreadfwrite等函数进行文件操作。适用于需要更多控制和灵活性的场景,比如格式化输入输出、缓冲区管理等。

    2. 文件描述符:主要用于低级I/O操作,例如通过openclosereadwrite等系统调用进行文件操作。适用于性能要求高或需要直接与操作系统交互的场景,如网络编程、设备驱动开发等。

  3. 跨平台兼容性

    1. FILE对象:FILE对象是C标准库的一部分,具有良好的跨平台兼容性。无论是Windows还是Unix/Linux系统,都可以使用相同的API进行文件操作。

    2. 文件描述符:文件描述符的概念主要存在于Unix/Linux系统中,而在Windows系统中则被称为“文件句柄”(Handle)。虽然可以通过一些机制(如Windows API中的HANDLE类型)实现类似的功能,但直接使用文件描述符的代码在不同操作系统之间移植时可能需要修改。

  4. 资源管理

    1. FILE对象:FILE对象的创建和销毁由C标准库函数(如fopenfclose)管理。用户不需要直接处理底层的文件描述符。

    2. 文件描述符:文件描述符的生命周期由操作系统管理,当进程终止时,所有打开的文件描述符都会自动关闭。但是,如果需要提前关闭文件描述符,用户必须手动调用close函数。

总的来说,FILE对象和文件描述符各有其应用场景。FILE对象适用于跨平台的文件操作,提供了更多的灵活性和易用性;而文件描述符则适用于需要高性能和低级别控制的场合。在实际开发中,可以根据具体需求选择合适的工具和方法。

同步操作

fsync(fileno(fp))

fsync(fileno(fp))是一个用于确保文件数据被写入磁盘的函数调用组合。

在Linux操作系统中,当使用标准I/O函数(如fwrite)向文件写入数据时,数据通常会首先被缓存在内存中,而不是立即写入到物理存储设备上。这种机制提高了写入操作的效率,但也带来了数据丢失的风险,特别是在系统崩溃或突然断电的情况下。为了解决这个问题,可以使用fsync(fileno(fp))来强制将内存中的数据写入磁盘。

fileno(fp)函数用于获取与FILE对象fp相关联的文件描述符。文件描述符是一个整数值,用于在底层操作系统中标识已打开的文件。fsync()函数则接受一个文件描述符作为参数,并等待所有与该文件描述符关联的已修改数据被写入磁盘。这意味着,在fsync(fileno(fp))执行完毕后,可以确信之前通过标准I/O函数写入的所有数据都已经安全地存储到了物理介质上。

sync和fsync的区别

sync和fsync在定义、使用场景以及性能等方面存在区别。具体分析如下:

  1. 定义

    1. sync:sync是一个系统调用,用于将所有已修改的内存缓冲区数据排入写队列,然后立即返回,不等待实际写入磁盘的操作完成。

    2. fsync:fsync是另一个系统调用,用于将指定文件描述符对应的文件缓冲区数据写入磁盘,并等待写操作完成后才返回。

  2. 使用场景

    1. sync:适用于需要同步整个文件系统或大量文件的场景,如系统关机前确保所有数据都写入磁盘。

    2. fsync:适用于对单个文件的数据进行持久化存储,如日志文件或数据库文件的写入操作。

  3. 性能

    1. sync:由于sync只是将数据排入写队列,并不等待写入完成,因此执行速度较快,但无法保证数据的即时持久性。

    2. fsync:因为需要等待数据实际写入磁盘,所以执行时间较长,但可以确保数据的持久性和一致性。

  4. 错误处理

    1. sync:如果发生错误,sync可能会返回错误代码,但由于它不等待写入完成,错误可能不会立即显现。

    2. fsync:如果写入过程中发生错误,fsync会返回错误代码,并且由于它等待写入完成,错误更容易被检测到。

总的来说,sync和fsync都是用于数据同步的重要工具,但它们的使用场景和行为有所不同。sync更适合于需要快速同步大量数据的情况,而fsync则更适合于需要确保单个文件数据持久性的场合。在实际应用中,应根据具体需求选择合适的同步机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值