Tips1:

函数:fopen()、getc()、putc()、exit()、fclose() fprintf()、fscanf()、fgets()、fputs() rewind()、fseek()、ftell()、fflush() fgetpos()、fsetpos()、feof()、ferror() ungetc()、setvbuf()、fread()、fwrite()

如何使用C标准I/O系列的函数处理文件文件模式和二进制模式、文本和二进制格式、缓冲和无缓冲I/O

使用既可以顺序访问文件也可以随机访问文件的函数

Tips2:

文件操作

文件是当今计算机系统不可或缺的部分。文件用于储存程序、文档、数 据、书信、表格、图形、照片、视频和许多其他种类的信息。作为程序员, 必须会编写创建文件和从文件读写数据的程序。本章将介绍相关的内容。

13.1 与文件进行通信

有时,需要程序从文件中读取信息或把信息写入文件。这种程序与文件交互的形式就是文件重定向(第8章介绍过)。这种方法很简单,但是有一定限制。例如,假设要编写一个交互程序,询问用户书名并把完整的书名列表保存在文件中。如果使用重定向,应该类似于:

books > bklist

用户的输入被重定向到 bklist 中。这样做不仅会把不符合要求的文本写入 bklist,而且用户也看不到要回答什么问题。 C提供了更强大的文件通信方法,可以在程序中打开文件,然后使用特殊的I/O函数读取文件中的信息或把信息写入文件。在研究这些方法之前, 先简要介绍一下文件的性质。

13.1.1 文件是什么

文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。然而,对操作系统而言,文件更复杂一些。例如,大型文件会被分开储存,或者包含一些额外的数据,方便操作系统确定文件的种类。然而,这都是操作 系统所关心的,程序员关心的是C程序如何处理文件(除非你正在编写操作系统)。

C把文件看作是一系列连续的字节,每个字节都能被单独读取。这与UNIX环境中(C的发源地)的文件结构相对应。由于其他环境中可能无法完全对应这个模型,C提供两种文件模式:文本模式和二进制模式

说明:之前的数据是单个的,文件可看作已被命名的一段连续数据的集合,每个字节可以被读取

13.1.2 文本模式和二进制模式

首先,要区分文本内容和二进制内容、文本文件格式和二进制文件格式,以及文件的文本模式和二进制模式

所有文件的内容都以二进制形式(0或1)储存。但是,如果文件最初使用二进制编码的字符(例如, ASCII或Unicode)表示文本(就像C字符串那 样),该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代表机器语言代码或数值数据(使用相同的内部表示,假设,用于long或 double类型的值)或图片或音乐编码,该文件就是二进制文件,其中包含二进制内容。

说明:文件都是以二进制形式储存的,如0x2f47386a(十六进制表示的32位数),如果以字符表示文本,就是文本文件。如用字符表示刚才的32位数,查找ASCII码,‘2f’=‘\',’47‘=’G‘,’38‘=’8‘(字符8),’6a‘=’j‘。所以字符表示为’\G8j'。内容是一样的,只是表示方法不同,十六进制的2位表示一个字符。

UNIX用同一种文件格式处理文本文件和二进制文件的内容。不奇怪, 鉴于C是作为开发UNIX的工具而创建的,C和UNIX在文本中都使用\n(换行 符)表示换行。UNIX目录中有一个统计文件大小的计数,程序可使用该计数确定是否读到文件结尾。然而,其他系统在此之前已经有其他方法处理文件,专门用于保存文本。也就是说,其他系统已经有一种与UNIX模型不同 的格式处理文本文件。例如,以前的OS X Macintosh文件用\r (回车符)表示新的一行。早期的MS-DOS文件用\r\n组合表示新的一行,用嵌入的Ctrl+Z字符表示文件结尾,即使实际文件用添加空字符的方法使其总大小是256的倍数(在Windows中,Notepad仍然生成MS-DOS格式的文本文件,但是新的编辑器可能使用类UNIX格式居多)。其他系统可能保持文本文件中的每一行长度相同,如有必要,用空字符填充每一行,使其长度保持一致。或者, 系统可能在每行的开始标出每行的长度。

为了规范文本文件的处理,C提供两种访问文件的途径:二进制模式和文本模式。在二进制模式中,程序可以访问文件的每个字节。而在文本模式中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时, 把本地环境表示的行末尾或文件结尾映射为C模式。例如,C程序在旧式 Macintosh中以文本模式读取文件时,把文件中的\r转换成\n;以文本模式写入文件时,把\n转换成\r。或者,C文本模式程序在MS-DOS平台读取文件时,把\r\n转换成\n;写入文件时,把\n转换成\r\n。在其他环境中编写的文本模式程序也会做类似的转换。

说明:以二进制模式和文本模式读写文件时,内容不同。两种模式下对换行符的表示不同,但这些是定义好的,程序员选择模式即可,写程序仍然按C语言风格,读则会自动转换

除了以文本模式读写文本文件,还能以二进制模式读写文本文件。如果读写一个旧式MS-DOS文本文件,程序会看到文件中的\r 和\n 字符,不会发生映射(图 13.1 演示了一些文本)。如果要编写旧式 Mac格式、MS-DOS格式或UNIX/Linux格式的文件模式程序,应该使用二进制模式,这样程序才能确定实际的文件内容并执行相应的动作。

虽然C提供了二进制模式和文本模式,但是这两种模式的实现可以相 同。前面提到过,因为UNIX使用一种文件格式,这两种模式对于UNIX实现 而言完全相同。Linux也是如此。

13.1.3 I/O的级别

除了选择文件的模式,大多数情况下,还可以选择I/O的两个级别(即处理文件访问的两个级别)。底层I/O(low-level I/O)使用操作系统提供的基本I/O服务。标准高级I/O(standard high-level I/O)使用C库的标准包和stdio.h头文件定义。因为无法保证所有的操作系统都使用相同的底层I/O模型,C标准只支持标准I/O包。有些实现会提供底层库,但是C标准建立了可移植的I/O模型,我们主要讨论这些I/O。   //只讨论标准I/O

13.1.4 标准文件

C程序会自动打开3个文件,它们被称为标准输入(standard input)、标准输出(standard output)和标准错误输出(standard error output)。在默认情况下,标准输入是系统的普通输入设备,通常为键盘标准输出和标准错误输出是系统的普通输出设备,通常为显示屏。 通常,标准输入为程序提供输入,它是getchar()和 scanf()使用的文件。 程序通常输出到标准输出,它是putchar()、puts()和printf()使用的文件。第8 章提到的重定向把其他文件视为标准输入或标准输出。标准错误输出提供了一个逻辑上不同的地方来发送错误消息。例如,如果使用重定向把输出发送给文件而不是屏幕,那么发送至标准错误输出的内容仍然会被发送到屏幕 上。这样很好,因为如果把错误消息发送至文件,就只能打开文件才能看到。

13.2 标准I/O

与底层I/O相比,标准I/O包除了可移植以外还有两个好处。第一,标准 I/O有许多专门的函数简化了处理不同I/O的问题。例如,printf()把不同形式 的数据转换成与终端相适应的字符串输出。第二,输入和输出都是缓冲的。 也就是说,一次转移一大块信息而不是一字节信息(通常至少512字节)。 例如,当程序读取文件时,一块数据被拷贝到缓冲区(一块中介存储区域)。这种缓冲极大地提高了数据传输速率。程序可以检查缓冲区中的字节。缓冲在后台处理,所以让人有逐字符访问的错觉(如果使用底层I/O, 要自己完成大部分工作)。程序清单13.1演示了如何用标准I/O读取文件和统计文件中的字符数。我们将在后面几节讨论程序清单 13.1 中的一些特性。该程序使用命令行参数,如果你是Windows用户,在编译后必须在命令提示窗口运行该程序;如果你是Macintosh用户,最简单的方法是使用Terminal在命令行形式中编译并运行该程序。或者,如第11章所述,如果在IDE中运行该程序,可以使用Xcode的Product菜单提供命令行参数。或者也可以用puts()和fgets()函数替换命令行参数来获得文件名

程序清单13.1 count.c程序
/* count.c -- 使用标准 I/O */
#include <stdio.h>
#include <stdlib.h> // 提供 exit()的原型int main(int argc, char *argv [])
{int ch; // 读取文件时,储存每个字符的地方FILE *fp; // “文件指针”unsigned long count = 0;if (argc != 2){printf("Usage: %s filename\n", argv[0]);exit(EXIT_FAILURE);}if ((fp = fopen(argv[1], "r")) == NULL){printf("Can't open %s\n", argv[1]);exit(EXIT_FAILURE);}while ((ch = getc(fp)) != EOF){putc(ch, stdout); // 与 putchar(ch); 相同count++;}fclose(fp);printf("File %s has %lu characters\n", argv[1], count);return 0;
}

程序输出如下(和书中有出入)

Usage: H:\C_Project\Test\Project1\Debug\Project1.exe filename

13.2.1 检查命令行参数

首先,程序清单13.1中的程序检查argc的值,查看是否有命令行参数。 如果没有,程序将打印一条消息并退出程序。字符串 argv[0]是该程序的名 称。显式使用 argv[0]而不是程序名,错误消息的描述会随可执行文件名的改变而自动改变。这一特性在像 UNIX这种允许单个文件具有多个文件名的环境中也很方便。但是,一些操作系统可能不识别argv[0],所以这种用法并非完全可移植。

exit()函数关闭所有打开的文件并结束程序。exit()的参数被传递给一些操作系统,包括 UNIX、Linux、Windows和MS-DOS,以供其他程序使用。 通常的惯例是:正常结束的程序传递0,异常结束的程序传递非零值。不同 的退出值可用于区分程序失败的不同原因,这也是UNIX和DOS编程的通常 做法。但是,并不是所有的操作系统都能识别相同范围内的返回值。因此, C 标准规定了一个最小的限制范围。尤其是,标准要求0或宏 EXIT_SUCCESS用于表明成功结束程序,宏EXIT_FAILURE用于表明结束程序失败。这些宏和exit()原型都位于stdlib.h头文件中。

根据ANSI C的规定,在最初调用的main()中使用return与调用exit()的效果相同。因此,在main(),下面的语句:

return 0;

和下面这条语句的作用相同:

exit(0);

但是要注意,我们说的是“最初的调用”。如果main()在一个递归程序 中,exit()仍然会终止程序,但是return只会把控制权交给上一级递归,直至 最初的一级。然后return结束程序。return和exit()的另一个区别是,即使在其他函数中(除main()以外)调用exit()也能结束整个程序。

说明:两条语句的对比:

exit()函数直接结束程序;

return 0结束当前调用函数;如果在main()中使用return 0;表示程序结束

13.2.2 fopen()函数

继续分析程序清单13.1,该程序使用fopen()函数打开文件。该函数声明在stdio.h中。它的第1个参数是待打开文件的名称,更确切地说是一个包含该文件名的字符串地址。第 2 个参数是一个字符串,指定待打开文件的模式。表13.1列出了C库提供的一些模式。

说明:r:只读; w:清空后写入或新建; a:文件末尾添加写入或新建;

r+:更新    w+:同w;                    a+:同a     //写模式和更新模式的区别没看出来

后面加b:以二进制模式打开;

后面加x(限w使用):1.文件已存在则打开失败,保护已有文件不被清空;

2.文件用w+x的形式写入时,禁止其他程序访问该文件。

WX方式写入的好处:一是保护已存在文件不会被覆盖;二是写入过程中禁止别的文件访问。

像UNIX和Linux这样只有一种文件类型的系统,带b字母的模式和不带b字母的模式相同

新的C11新增了带x字母的写模式,与以前的写模式相比具有更多特 性。第一,如果以传统的一种写模式打开一个现有文件,fopen()会把该文件 的长度截为0,这样就丢失了该文件的内容。但是使用带 x字母的写模式, 即使fopen()操作失败,原文件的内容也不会被删除。第二,如果环境允许, x模式的独占特性使得其他程序或线程无法访问正在被打开的文件

警告

如果使用任何一种"w"模式(不带x字母)打开一个现有文件,该文件的内容会被删除,以便程序在一个空白文件中开始操作。然而,如果使用带x字母的任何一种模式,将无法打开一个现有文件。

程序成功打开文件后,fopen()将返回文件指针(file pointer),其他I/O函数可以使用这个指针指定该文件。文件指针(该例中是fp)的类型是指向 FILE的指针,FILE是一个定义在stdio.h中的派生类型。文件指针fp并不指向实际的文件,它指向一个包含文件信息的数据对象,其中包含操作文件的 I/O函数所用的缓冲区信息。因为标准库中的I/O函数使用缓冲区,所以它们不仅要知道缓冲区的位置,还要知道缓冲区被填充的程度以及操作哪一个文 件。标准I/O函数根据这些信息在必要时决定再次填充或清空缓冲区。fp指向的数据对象包含了这些信息(该数据对象是一个 C结构,将在第 14章中介绍)。

说明:fopen()函数并不单指打开文件,还可以新建文件,和在原文件末尾添加数据

13.2.3 getc()和putc()函数

getc()和putc()函数与getchar()和putchar()函数类似。所不同的是,要告诉 getc()和putc()函数使用哪一个文件。下面这条语句的意思是“从标准输入中获取一个字符”:

ch = getchar();          //相当于ch=getc(stdin);

然而,下面这条语句的意思是“从fp指定的文件中获取一个字符”

ch = getc(fp);            //getc()函数文件格式

与此类似,下面语句的意思是“把字符ch放入FILE指针fpout指定的文件中”

putc(ch, fpout);        //putc()函数文件格式

在putc()函数的参数列表中,第1个参数是待写入的字符,第2个参数是 文件指针。

程序清单13.1把stdout作为putc()的第2个参数。stdout作为与标准输出相 关联的文件指针,定义在stdio.h中,所以putc(ch, stdout)与putchar(ch)的作用相同。实际上,putchar()函数一般通过putc()来定义。与此类似,getchar()也 通过使用标准输入的getc()来定义。

定义代码如下:

char getchar(void)
{char ch;ch=getc(stdin);return ch;
}

为何该示例不用 putchar()而要用 putc()?原因之一是为了介绍 putc()函 数;原因之二是,把stdout替换成别的参数,很容易将这段程序改写成文件输出。

13.2.4 文件结尾

从文件中读取数据的程序在读到文件结尾时要停止。如何告诉程序已经读到文件结尾?如果 getc()函数在读取一个字符时发现是文件结尾,它将返回一个特殊值EOF。所以C程序只有在读到超过文件末尾时才会发现文件的结尾(一些其他语言用一个特殊的函数在读取之前测试文件结尾,C语言不同)。 为了避免读到空文件,应该使用入口条件循环(不是do while循环)进行文件输入。鉴于getc() (和其他C输入函数)的设计,程序应该在进入循环体之前先尝试读取。如下面设计所示:

// 设计范例 #1
int ch; // 用int类型的变量储存EOF
FILE * fp;
fp = fopen("wacky.txt", "r");
ch = getc(fp); // 获取初始输入
while (ch != EOF)
{putchar(ch); // 处理输入ch = getc(fp); // 获取下一个输入
}

以上代码可简化为:


// 设计范例 #2
int ch;
FILE * fp;
fp = fopen("wacky.txt", "r");
while (( ch = getc(fp)) != EOF)
{putchar(ch); //处理输入
}

由于ch = getc(fp)是while测试条件的一部分,所以程序在进入循环体之前就读取了文件。

//或者可以再简化一点:FILE * fp= fopen("wacky.txt", "r");

其他输入函数也会用到这种处理方案,它们在读到文件结尾时也会返回一个错误信号(EOF 或 NULL指针)。

说明:文件末尾的产生,当标准输入时,用键盘“Ctrl+z”,其他文件末尾有空指针或者EOF,这些是人为定义的。文件末尾的判断:当读到的字符为EOF或NULL指针

13.2.5 fclose()函数

fclose(fp)函数关闭fp指定的文件,必要时刷新缓冲区。对于较正式的程序,应该检查是否成功关闭文件。如果成功关闭,fclose()函数返回0,否则 返回EOF:

if (fclose(fp) != 0)

printf("Error in closing file %s\n", argv[1]);

如果磁盘已满、移动硬盘被移除或出现I/O错误,都会导致调用fclose() 函数失败。

说明:红色两条语句可以放在文件末尾做标准语句。

13.2.6 指向标准文件的指针

stdio.h头文件把3个文件指针与3个标准文件相关联,C程序会自动打开这3个标准文件。如表13.2所示:

这些文件指针都是指向FILE的指针,所以它们可用作标准I/O函数的参数,如fclose(fp)中的fp。接下来,我们用一个程序示例创建一个新文件,并写入内容。

说明:当通过键盘读取字符时相当于调用函数ch = getchar();   也就是相当于ch=getc(stdin);同样当使用显示器输出字符时,相当于putchar();也就是putc(ch,stdout);-----当实现定义好‘ch’的时候

13.3 一个简单的文件压缩程序

下面的程序示例把一个文件中选定的数据拷贝到另一个文件中。该程序 同时打开了两个文件,以"r"模式打开一个,以"w"模式打开另一个。该程序 (程序清单13.2)以保留每3个字符中的第1个字符的方式压缩第1个文件的内容。最后,把压缩后的文本存入第2个文件。第2个文件的名称是第1个文 件名加上.red后缀(此处的red代表reduced)。使用命令行参数,同时打开多 个文件,以及在原文件名后面加上后缀,都是相当有用的技巧。这种压缩方 式有限,但是也有它的用途(很容易把该程序改成用标准 I/O 而不是命令行参数提供文件名)。

程序清单13.2 reducto.c程序
// reducto.c –把文件压缩成原来的1/3!
#include <stdio.h>
#include <stdlib.h> // 提供 exit()的原型
#include <string.h> // 提供 strcpy()、strcat()的原型
#define LEN 40int main(int argc, char *argv [])
{FILE *in, *out; // 声明两个指向 FILE 的指针int ch;char name[LEN]; // 储存输出文件名int count = 0;// 检查命令行参数if (argc < 2){fprintf(stderr, "Usage: %s filename\n", argv[0]);exit(EXIT_FAILURE);}// 设置输入if ((in = fopen(argv[1], "r")) == NULL){fprintf(stderr, "I couldn't open the file \"%s\"\n",argv[1]);exit(EXIT_FAILURE);}// 设置输出strncpy(name, argv[1], LEN - 5); // 拷贝文件名name[LEN - 5] = '\0';strcat(name, ".red"); // 在文件名后添加.redif ((out = fopen(name, "w")) == NULL){ // 以写模式打开文件fprintf(stderr, "Can't create output file.\n");exit(3);}// 拷贝数据while ((ch = getc(in)) != EOF)if (count++ % 3 == 0)putc(ch, out);// 打印3个字符中的第1个字符// 收尾工作if (fclose(in) != 0 || fclose(out) != 0)fprintf(stderr, "Error in closing files\n");return 0;
}

该程序示例演示了几个编程技巧。我们来仔细研究一下。

fprintf()和 printf()类似,但是 fprintf()的第 1 个参数必须是一个文件指针。程序中使用stderr指针把错误消息发送至标准错误,C标准通常都这么做。

//fprintf()函数的格式:fprintf(stderr, "I couldn't open the file \"%s\"\n",argv[1]); 打印内容到文件指针指向的文件,当前面是stdout和stderr,则表示打印到屏幕

为了构造新的输出文件名,该程序使用strncpy()把名称eddy拷贝到数组name中。参数LEN-5为.red后缀和末尾的空字符预留了空间。如果argv[2]字符串比LEN-5长,就拷贝不了空字符。出现这种情况时,程序会添加空字符。调用strncpy()后,name中的第1个空字符在调用strcat()函数时,被.red的. 覆盖,生成了eddy.red。程序中还检查了是否成功打开名为eddy.red的文件。 这个步骤在一些环境中相当重要,因为像strange.c.red这样的文件名可能是无效的。例如,在传统的DOS环境中,不能在后缀名后面添加后缀名(MSDOS使用的方法是用.red替换现有后缀名,所以strange.c将变成strange.red。 例如,可以用strchr()函数定位(如果有的话),然后只拷贝点前面的部分即可)。

//strncpy复制字符串个数的限制:当数组长度为LEN时,最多LEN-1,留一个给空字符,这里需要给.red留出空间,所以最多复制个数为LEN-1-4=LEN-5个字符。然后以这个数字为索引给空字符

该程序同时打开了两个文件,所以我们要声明两个FIFL指针。注意, 程序都是单独打开和关闭每个文件。同时打开的文件数量是有限的,这个限制取决于系统和实现,范围一般是10~20。相同的文件指针可以处理不同的 文件,前提是这些文件不需要同时打开。

13.4 文件I/Ofprintf()、fscanf()、fgets()和fputs()

前面章节介绍的I/O函数都类似于文件I/O函数。它们的主要区别是,文件I/O函数要用FILE指针指定待处理的文件。与 getc()、putc()类似,这些函数都要求用指向 FILE 的指针(如,stdout)指定一个文件,或者使用fopen() 的返回值。

13.4.1 fprintf()和fscanf()函数

文件I/O函数fprintf()和fscanf()函数的工作方式与printf()和scanf()类似, 区别在于前者需要用第1个参数指定待处理的文件。我们在前面用过 fprintf()。程序清单13.3演示了这两个文件I/O函数和rewind()函数的用法。

程序清单13.3 addaword.c程序
/* addaword.c -- 使用 fprintf()、fscanf() 和 rewind() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 41int main(void)
{FILE *fp;char words[MAX];if ((fp = fopen("wordy", "a+")) == NULL){fprintf(stdout, "Can't open \"wordy\" file.\n");exit(EXIT_FAILURE);}puts("Enter words to add to the file; press the #");puts("key at the beginning of a line to terminate.");while ((fscanf(stdin, "%40s", words) == 1) && (words[0] != '#'))fprintf(fp, "%s\n", words);puts("File contents:");rewind(fp); /* 返回到文件开始处 */while (fscanf(fp, "%s", words) == 1)puts(words);puts("Done!");if (fclose(fp) != 0)fprintf(stderr, "Error closing file\n");return 0;
}

该程序可以在文件中添加单词。使用"a+"模式,程序可以对文件进行读写操作。首次使用该程序,它将创建wordy文件,以便把单词存入其中。随后再使用该程序,可以在wordy文件后面添加单词。虽然"a+"模式只允许在文件末尾添加内容,但是该模式下可以读整个文件。rewind()函数让程序回到文件开始处,方便while循环打印整个文件的内容。注意,rewind()接受一 个文件指针作为参数。

如你所见,fprintf()和 fscanf()的工作方式与 printf()和 scanf()类似。但是,与 putc()不同的是fprintf()和fscanf()函数都把FILE指针作为第1个参数,而不是最后一个参数

说明:文件有二进制模式和文本模式,都是ASCII码形式,所以fprintf()和fscanf()只处理字符串

13.4.2 fgets()和fputs()函数

第11章时介绍过fgets()函数。它的第1个参数和gets()函数一样,也是表示储存输入位置的地址(char * 类型);第2个参数是一个整数,表示待输入字符串的大小 [1];最后一个参数是文件指针,指定待读取的文件。下面是一个调用该函数的例子:

fgets(buf, STLEN, fp); 这里,buf是char类型数组的名称,STLEN是字符串的大小,fp是指向 FILE的指针。   //fgets()函数的格式

fgets()函数读取输入直到第 1 个换行符的后面,或读到文件结尾,或者读取STLEN-1 个字符(以上面的 fgets()为例)。然后,fgets()在末尾添加一个空字符使之成为一个字符串。字符串的大小是其字符数加上一个空字符。 如果fgets()在读到字符上限之前已读完一整行,它会把表示行结尾的换行符 放在空字符前面。fgets()函数在遇到EOF时将返回NULL值,可以利用这一机制检查是否到达文件结尾;如果未遇到EOF则之前返回传给它的地址。

//fgets()函数的返回值是它接收的字符串,在s_gets()函数中,定义了char *ret_val接收其返回值

说明:fgets()函数有了迭代版本s_gets(),从屏幕输入字符串,直接调用即可。这里主要是看从文件读入而不是从屏幕输入,当然把s_gets()函数稍作修改也能满足要求。

如何理清fgets()函数:它只规定了在指定长度后的字符后面加了一个空字符,s_gets()函数的处理了末尾之前换行符的情况,以及超过LEN后,吸收多余字符。

fputs()函数接受两个参数:第1个是字符串的地址;第2个是文件指针。 该函数根据传入地址找到的字符串写入指定的文件中。和 puts()函数不同, fputs()在打印字符串时不会在其末尾添加换行符。下面是一个调用该函数的例子:

fputs(buf, fp);        //fputs(buf,stdout);相当于不加换行符的puts()

这里,buf是字符串的地址,fp用于指定目标文件。

由于fgets()保留了换行符,fputs()就不会再添加换行符,它们配合得非常好。如第11章的程序清单11.8所示,即使输入行比STLEN长,这两个函数依然处理得很好。

小结:getc()和putc()函数:处理字符输入输出,指针在后;fprintf()和fscanf()函数:格式化输入和输出,指针在前;fgets()和fputs()函数,处理字符串输入输出,指针在后。

13.5 随机访问:fseek()和ftell()

有了 fseek()函数,便可把文件看作是数组,在 fopen()打开的文件中直接移动到任意字节处。我们创建一个程序(程序清单13.4)演示fseek()和 ftell()的用法。注意,fseek()有3个参数,返回int类型的值;ftell()函数返回一 个long类型的值,表示文件中的当前位置。

说明:fopen()函数获取到的文件指针默认在文件开始处,如果是‘a’开头的模式('a+','ab'等)默认在文件结尾处,有了fseek()函数,可把指针移动到文件中的任意地址中,概念很重要。前面有个rewind()函数也可以用fseek()函数替代。

程序清单13.4 reverse.c程序
/* reverse.c -- 倒序显示文件的内容 */
#include <stdio.h>
#include <stdlib.h>
#define CNTL_Z '\032' /* DOS文本文件中的文件结尾标记 */
#define SLEN 81int main(void)
{char file[SLEN];char ch;FILE *fp;long count, last;puts("Enter the name of the file to be processed:");scanf("%80s", file);if ((fp = fopen(file, "rb")) == NULL){ /* 只读模式 */printf("reverse can't open %s\n", file);exit(EXIT_FAILURE);}fseek(fp, 0L, SEEK_END); /* 定位到文件末尾 */last = ftell(fp);for (count = 1L; count <= last; count++){fseek(fp, -count, SEEK_END); /* 回退 */ch = getc(fp);if (ch != CNTL_Z && ch != '\r') /* MS-DOS 文件 */putchar(ch);}putchar('\n');fclose(fp);return 0;
}

//说明:需要建立一个文件,用文件名替换‘if ((fp = fopen(file, "rb")) == NULL)’中的file,在vs2019下尚未完善。

该程序使用二进制模式,以便处理MS-DOS文本和UNIX文件。但是, 在使用其他格式文本文件的环境中可能无法正常工作。

注意 如果通过命令行环境运行该程序,待处理文件要和可执行文件在同一个目录(或文件夹)中。如果在IDE中运行该程序,具体查找方案序因实现而 异。例如,默认情况下,Microsoft Visual Studio 2012在源代码所在的目录中查找,而Xcode 4.6则在可执行文件所在的目录中查找。

接下来,我们要讨论3个问题:fseek()和ftell()函数的工作原理、如何使用二进制流、如何让程序可移植。

13.5.1 fseek()和ftell()的工作原理

fseek()的第1个参数是FILE指针,指向待查找的文件,fopen()应该已打 开该文件。

fseek()的第2个参数是偏移量(offset)。该参数表示从起始点开始要移动的距离(参见表13.3列出的起始点模式)。该参数必须是一个long类型的值,可以为正(前移)、负(后移)或0(保持不动)。

fseek()的第3个参数是模式,该参数确定起始点。根据ANSI标准,在 stdio.h头文件中规定了几个表示模式的明示常量(manifest constant),如表 13.3所示。

旧的实现可能缺少这些定义,可以使用数值0L、1L、2L分别表示这3种 模式。L后缀表明其值是long类型。或者,实现可能把这些明示常量定义在 别的头文件中。如果不确定,请查阅实现的使用手册或在线帮助。

下面是调用fseek()函数的一些示例,fp是一个文件指针:

fseek(fp, 0L, SEEK_SET); // 定位至文件开始处

fseek(fp, 10L, SEEK_SET); // 定位至文件中的第10个字节

fseek(fp, 2L, SEEK_CUR); // 从文件当前位置前移2个字节

fseek(fp, 0L, SEEK_END); // 定位至文件结尾

fseek(fp, -10L, SEEK_END); // 从文件结尾处回退10个字节

对于这些调用还有一些限制,我们稍后再讨论。

如果一切正常,fseek()的返回值为0;如果出现错误(如试图移动的距 离超出文件的范围),其返回值为-1。       //注意fseek()函数的返回值

ftell()函数的返回类型是long,它返回的是当前的位置。ANSI C把它定 义在stdio.h中。在最初实现的UNIX中,ftell()通过返回距文件开始处的字节 数来确定文件的位置。文件的第1个字节到文件开始处的距离是0,以此类 推。ANSI C规定,该定义适用于以二进制模式打开的文件,以文件模式打开文件的情况不同。这也是程序清单13.4以二进制模式打开文件的原因。

下面,我们来分析程序清单13.4中的基本要素。

首先,下面的语句: fseek(fp, 0L, SEEK_END);

把当前位置设置为距文件末尾 0 字节偏移量。也就是说,该语句把当前位置设置在文件结尾。下一条语句:

last = ftell(fp);

把从文件开始处到文件结尾的字节数赋给last。

然后是一个for循环:

for (count = 1L; count <= last; count++)

{

fseek(fp, -count, SEEK_END); /* go backward */

ch = getc(fp);

}

第1轮迭代,把程序定位到文件结尾的第1个字符(即,文件的最后一个 字符)。然后,程序打印该字符。下一轮迭代把程序定位到前一个字符,并打印该字符。重复这一过程直至到达文件的第1个字符,并打印。

13.5.2 二进制模式和文本模式

我们设计的程序清单13.4在UNIX和MS-DOS环境下都可以运行。UNIX 只有一种文件格式,所以不需要进行特殊的转换。然而MS-DOS要格外注 意。许多MS-DOS编辑器都用Ctrl+Z标记文本文件的结尾。以文本模式打开这样的文件时,C 能识别这个作为文件结尾标记的字符。但是,以二进制模 式打开相同的文件时,Ctrl+Z字符被看作是文件中的一个字符,而实际的文件结尾符在该字符的后面。文件结尾符可能紧跟在Ctrl+Z字符后面,或者文 件中可能用空字符填充,使该文件的大小是256的倍数。在DOS环境下不会打印空字符,程序清单13.4中就包含了防止打印Ctrl+Z字符的代码

二进制模式和文本模式的另一个不同之处是:MS-DOS用\r\n组合表示文本文件换行。以文本模式打开相同的文件时,C程序把\r\n“看成”\n。但是, 以二进制模式打开该文件时,程序能看见这两个字符。因此,程序清单13.4 中还包含了不打印\r的代码。通常,UNIX文本文件既没有Ctrl+Z,也没有 \r,所以这部分代码不会影响大部分UNIX文本文件。

说明:二进制模式和文本模式对文件末尾和换行符的表示不同,有的C能识别,有的不能识别,所以在编写代码时候有些区别。在程序清单13.4中均有体现。

ftell()函数在文本模式和二进制模式中的工作方式不同。许多系统的文 本文件格式与UNIX的模型有很大不同,导致从文件开始处统计的字节数成 为一个毫无意义的值。ANSI C规定,对于文本模式,ftell()返回的值可以作 为fseek()的第2个参数。对于MS-DOS,ftell()返回的值把\r\n当作一个字节计数。

13.5.3 可移植性

理论上,fseek()和ftell()应该符合UNIX模型。但是,不同系统存在着差异,有时确实无法做到与UNIX模型一致。因此,ANSI对这些函数降低了要求。下面是一些限制。

在二进制模式中,实现不必支持SEEK_END模式。因此无法保证程序清单13.4的可移植性。移植性更高的方法是逐字节读取整个文件直到文件末尾。C 预处理器的条件编译指令(第 16 章介绍)提供了一种系统方法来处理这种情况。

在文本模式中,只有以下调用能保证其相应的行为。

不过,许多常见的环境都支持更多的行为。

13.5.4 fgetpos()和fsetpos()函数

fseek()和 ftell()潜在的问题是,它们都把文件大小限制在 long 类型能表示的范围内。也许 20亿字节看起来相当大,但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI C新增了两个处理较大文件的新定位函数:fgetpos()和 fsetpos()。这两个函数不使用 long 类型的值表示位置, 它们使用一种新类型:fpos_t(代表file position type,文件定位类型)。 fpos_t类型不是基本类型,它根据其他类型来定义fpos_t 类型的变量或数据对象可以在文件中指定一个位置,它不能是数组类型,除此之外,没有其他限制。实现可以提供一个满足特殊平台要求的类型,例如,fpos_t可以实现为结构。

//fpos_t类型可以通过typedef来定义,比如long long unsigned int;类似于size_t可以找到定义如果头文件有的话,如果没有可以自己定义

ANSI C定义了如何使用fpos_t类型。

fgetpos()函数的原型如下:

int fgetpos(FILE * restrict stream, fpos_t * restrict pos);

调用该函数时,它把fpos_t类型的值放在pos指向的位置上,该值描述了 文件中的一个位置。如果成功,fgetpos()函数返回0;如果失败,返回非0。

fsetpos()函数的原型如下:

int fsetpos(FILE *stream, const fpos_t *pos);

调用该函数时,使用pos指向位置上的fpos_t类型值来设置文件指针指向该值指定的位置。如果成功,fsetpos()函数返回0;如果失败,则返回非0。 fpos_t类型的值应通过之前调用fgetpos()获得。

//如何使用:

FILE*fp = fopen(file, "rb")                           //打开一个文件

typedef long long unsigned int fpos_t;

fpos_t My_pos=0x23243dda0212da28;      //文件中的位置

fgetpos(fp, &My_pos);

fsetpos(fp, &My_pos);                   //执行完毕后fp指向0x23243dda0212da28这个位置

说明:上述代码未验证

13.6 标准I/O的机理

我们在前面学习了标准I/O包的特性,本节研究一个典型的概念模型, 分析标准I/O的工作原理。 通常,使用标准I/O的第1步是调用fopen()打开文件(前面介绍过,C程序会自动打开3种标准文件)。fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。假设把该指针赋给一个指针变量fp,我们说fopen()函数“打开一个流”。如果以文本模式打开该文件,就获得一个文本流;如果以二进制模式打开该文件,就获得一个二进制流。

这个结构通常包含一个指定流中当前位置的文件位置指示器。除此之 外,它还包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一 个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。

我们主要考虑文件输入。通常,使用标准I/O的第2步是调用一个定义在 stdio.h中的输入函数,如fscanf()、getc()或 fgets()。一调用这些函数,文件中的数据块就被拷贝到缓冲区中。缓冲区的大小因实现而异,一般是512字节或是它的倍数,如4096或16384(随着计算机硬盘容量越来越大,缓冲区的大小也越来越大)。最初调用函数,除了填充缓冲区外,还要设置fp所指向的结构中的值。尤其要设置流中的当前位置和拷贝进缓冲区的字节数。通常,当前位置从字节0开始。

在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据。在它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于 stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。

当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。以这种方式,输入函数可以读取文件中的所有内容,直到文件结尾。函数在读取缓冲区中的最后一个字符后,把结尾指示器设置为真。于是,下一次被调用的输入函数将返回EOF。

输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝至文件中。

13.7 其他标准I/O函数

ANSI标准库的标准I/O系列有几十个函数。虽然在这里无法一一列举, 但是我们会简要地介绍一些,让读者对它们有一个大概的了解。这里列出函 数的原型,表明函数的参数和返回类型。我们要讨论的这些函数,除了 setvbuf(),其他函数均可在ANSI之前的实现中使用。参考资料V的“新增C99 和C11的标准ANSI C库”中列出了全部的ANSI C标准I/O包。

13.7.1 int ungetc(int c, FILE *fp)

函数 int ungetc()函数把c指定的字符放回输入流中。如果把一个字符放回输入 流,下次调用标准输入函数时将读取该字符(见图13.2)。例如,假设要读 取下一个冒号之前的所有字符,但是不包括冒号本身,可以使用 getchar()或 getc()函数读取字符到冒号,然后使用 ungetc()函数把冒号放回输入流中。 ANSI C标准保证每次只会放回一个字符。如果实现允许把一行中的多个字 符放回输入流,那么下一次输入函数读入的字符顺序与放回时的顺序相反。

说明:复习输入队列是怎么来的,初始输入放在缓冲区,当按下enter键后刷新缓冲区进入输入序列,然后getchar()从输入序列里取出一个字符交给变量‘ch’。上图所示输入序列从w开始,第二行使用语句ch=getchar(); 则此时ch=‘w’,输入序列来到‘h’;第三行ungetc(ch,stdin);将w放回输入序列,此时输入序列又从w开始,如果再执行语句ch=getchar();此时‘ch’又等于‘w’。

延伸:由此想到是否可以用ungetc()函数去修改比如一个字符串的某一个字符前的内容,后来想了想因为不确定是否可以,但用指针一定可以。

13.7.2 int fflush()函数

fflush()函数的原型如下:

int fflush(FILE *fp);

调用fflush()函数引起输出缓冲区中所有的未写入数据被发送到fp指定的输出文件。 这个过程称为刷新缓冲区。如果 fp是空指针,所有输出缓冲区都被刷新。在输入流中使用fflush()函数的效果是未定义的。只要最近一次操作不是输入操作,就可以用该函数来更新流(任何读写模式)。

说明:只能在输出流中使用,如前一次操作是fputs(),putc()等,写入文件指针*FILE所指定文件

13.7.3 int setvbuf()函数

setvbuf()函数的原型是:

int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);

setvbuf()函数创建了一个供标准I/O函数替换使用的缓冲区在打开文件后且未对流进行其他操作之前,调用该函数。指针fp识别待处理的流,buf 指向待使用的存储区。如果buf的值不是NULL,则必须创建一个缓冲区。例如,声明一个内含1024个字符的数组,并传递该数组的地址。然而,如果把 NULL作为buf的值,该函数会为自己分配一个缓冲区。变量size告诉setvbuf() 数组的大小(size_t是一种派生的整数类型,第5章介绍过)。mode的选择如下:_IOFBF表示完全缓冲(在缓冲区满时刷新);_IOLBF表示行缓冲(在 缓冲区满时或写入一个换行符时);_IONBF表示无缓冲。如果操作成功, 函数返回0,否则返回一个非零值。

假设一个程序要储存一种数据对象,每个数据对象的大小是3000字节。 可以使用setvbuf()函数创建一个缓冲区,其大小是该数据对象大小的倍数。

说明:setvbuf()函数自己创建了一个缓冲区,替换了fopen()建立的那个。

13.7.4 二进制I/O:fread()和fwrite()

介绍fread()和fwrite()函数之前,先要了解一些背景知识。之前用到的标准I/O函数都是面向文本的,用于处理字符和字符串。如何要在文件中保存数值数据?用fprintf()函数和%f转换说明只是把数值保存为字符串。例如, 下面的代码:

double num = 1./3.;

fprintf(fp,"%f", num);

把num储存为8个字符:0.333333。使用%.2f转换说明将其储存为4个字符:0.33,用%.12f转换说明则将其储存为14 个字符:0.333333333333。改变转换说明将改变储存该值所需的空间数量,也会导致储存不同的值。把 num 储存为 0.33 后,读取文件时就无法将其恢复为更高的精度。一般而 言, fprintf()把数值转换为字符数据,这种转换可能会改变值。

说明:和之前考虑的一样,fprintf()函数即使使用%f,%d等转换符,也是转换成字符

为保证数值在储存前后一致,最精确的做法是使用与计算机相同的位组合来储存。因此,double 类型的值应该储存在一个 double 大小的单元中。 如果以程序所用的表示法把数据储存在文件中,则称以二进制形式储存数 据。不存在从数值形式到字符串的转换过程。对于标准 I/O,fread()和 fwrite 函数用于以二进制形式处理数据(见图13.3)。

实际上,所有的数据都是以二进制形式储存的,甚至连字符都以字符码的二进制表示来储存。如果文件中的所有数据都被解释成字符码,则称该文件包含文本数据。如果部分或所有的数据都被解释成二进制形式的数值数据,则称该文件包含二进制数据(另外,用数据表示机器语言指令的文件都是二进制文件)。//下面几张图展示了二进制存储数据和字符串存储数据的区别

以二进制形式存储的数据(上图)

 以fprintf()函数将数据写入文件的过程,虽然是%d的格式,仍然以字符串形式存储(上图)

以fwrite()函数将数据写入文件的过程(上图)

二进制和文本的用法很容易混淆。ANSI C和许多操作系统都识别两种文件格式:二进制和文本。能以二进制数据或文本数据形式存储或读取信息。可以用二进制模式打开文本格式的文件,可以把文本储存在二进制形式的文件中。可以调用 getc()拷贝包含二进制数据的文件。然而,一般而言, 用二进制模式在二进制格式文件中储存二进制数据。类似地,最常用的还是以文本格式打开文本文件中的文本数据(通常文字处理器生成的文件都是二进制文件,因为这些文件中包含了大量非文本信息,如字体和格式等)。

说明:fread()和fwrite()函数的出现使得文件可以精确存储除字符型数据外的其他数据,注意存储时要以二进制模式打开。

13.7.5 size_t fwrite()函数

fwrite()函数的原型如下:

size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb,FILE * restrict fp);

fwrite()函数把二进制数据写入文件。size_t是根据标准C类型定义的类型,它是sizeof运算符返回的类型,通常是unsigned int,但是实现可以选择使用其他类型。指针ptr是待写入数据块的地址。size表示待写入数据块的大小(以字节为单位),nmemb表示待写入数据块的数量。和其他函数一样, fp指定待写入的文件。例如,要保存一个大小为256字节的数据对象(如数 组),可以这样做:

char buffer[256];

fwrite(buffer, 256, 1, fp);

以上调用把一块256字节的数据从buffer写入文件。    //调用函数的格式

另举一例,要保存一 个内含10个double类型值的数组,可以这样做:

double earnings[10];

fwrite(earnings, sizeof(double), 10, fp);

以上调用把earnings数组中的数据写入文件,数据被分成10块,每块都是double的大小。

注意fwrite()原型中的const void * restrict ptr声明。fwrite()的一个问题是,它的第1个参数不是固定的类型。例如,第1个例子中使用buffer,其类型是指向char的指针;而第2个例子中使用earnings,其类型是指向double的指针。在ANSI C函数原型中,这些实际参数都被转换成指向void的指针类型,这种指针可作为一种通用类型指针(在ANSI C之前,这些参数使用char *类型,需要把实参强制转换成char *类型)。

fwrite()函数返回成功写入项的数量。正常情况下,该返回值就是 nmemb,但如果出现写入错误,返回值会比nmemb小。

13.7.6 size_t fread()函数

size_t fread()函数的原型如下:

size_t fread(void * restrict ptr, size_t size, size_t nmemb,FILE * restrict fp);

fread()函数接受的参数和fwrite()函数相同。在fread()函数中,ptr是待读 取文件数据在内存中的地址,fp指定待读取的文件。该函数用于读取被 fwrite()写入文件的数据。例如,要恢复上例中保存的内含10个double类型值的数组,可以这样做:

double earnings[10];

fread(earnings, sizeof (double), 10, fp);

该调用把10个double大小的值拷贝进earnings数组中。

fread()函数返回成功读取项的数量。正常情况下,该返回值就是nmemb,但如果出现读取错误或读到文件结尾,该返回值就会比nmemb小。

说明:fread()和fwrite()函数可以理解为是成对出现的

13.7.7 int feof(FILE *fp)和int ferror(FILE *fp)函数

如果标准输入函数返回 EOF,则通常表明函数已到达文件结尾。然而,出现读取错误时,函数也会返回EOF。feof()和ferror()函数用于区分这两种情况。当上一次输入调用检测到文件结尾时,feof()函数返回一个非零值,否则返回0。当读或写出现错误,ferror()函数返回一个非零值,否则返 回0。

说明:feof()函数可以判断文件末尾,在写入数据的时候可以用。ferror()函数事后判断。

if(feof(fp))

{

fgets(str,LEN,fp)

};                                       //将文件内所有内容放进str[]数组里

if(ferror(fp))

printf("error!\n")                //读写错误后,打印提示错误信息

13.7.8 一个程序示例

接下来,我们用一个程序示例说明这些函数的用法。该程序把一系列文件中的内容附加在另一个文件的末尾。该程序存在一个问题:如何给文件传 递信息。可以通过交互或使用命令行参数来完成,我们先采用交互式的方法。下面列出了程序的设计方案。

小结:对文件的操作,有打开或者新建文件,或者末尾添加几种模式。

当获取到文件指针后,可以读取和写入字符,字符串。如果要读写其他格式数据,需要用二进制模式打开。所有操作均是以文件指针为前提开始,文件指针不会自己移动,需要调用fseek()函数。文件操作可以看成文件指针的移动加上文件读写函数两部分组成。正是因为这个原因,对文件的处理以字符的方式更好

二进制模式和文本模式下,某些文件(dos,Unix)在C语言里表示有些不同,表示的换行符和文件末尾不一样,需要作不同的处理,C语言默认处理文本模式。

感觉文件指针的操作有点笨拙,只能通过for循环加fseek()函数,还需要事先知道个数配合ftell()函数才行。没有类似p++那种指针形式。

C语言基础之13:文件输入/输出相关推荐

  1. c语言字符型数据怎么输入输出,C语言基础之数据类型与输入/输出

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 常用的进位制 R进制数只采用R个基本数字符号,R称为数制的基数,数制中每一固定位置对应的单位值称为权,进位原则是逢R进1 ...

  2. C基础学习之C 输入 输出

    目录 标准文件 getchar() & putchar() 函数 gets() & puts() 函数 scanf() 和 printf() 函数 当我们提到输入时,这意味着要向程序填 ...

  3. C语言基础:使字符串倒序输出

    C语言基础:使字符串倒序输出 1. 用字符指针作形参 例1: void reverse1(char *s) {char* p=s+strlen(s)-1;char t;while(s<p){t= ...

  4. C/C++-标准输入/输出重定向为文件输入/输出

    /*Time: 2017-02-22 11:11:15Describe: C++程序将标准输入/输出重定向为文件输入/输出. */#include <iostream> #include ...

  5. C语言基础专题 - 头文件引用

    C语言基础专题 - 头文件引用 本文介绍了C语言中头文件的引用方法 阅读本文前推荐阅读C语言预处理 1.

  6. Scipy文件输入/输出

    Scipy文件输入/输出 随机生成数组,使用scipy中的io.savemat()保存 文件格式是.mat,标准的二进制文件 导入 import scipy.io io存储 # moon是读入的图片, ...

  7. Scipy文件输入/输出mat,wav,mp3

    Scipy文件输入/输出mat,wav,mp3 moon (df) 降噪后的moon_cleaned (df) 导入: import scipy.io as spio 读写.mat 文件 写入 .sa ...

  8. python文件输入符_python基础入门详解(文件输入/输出 内建类型 字典操作使用方法)...

    一.变量和表达式 >>> 1 + 1 2 >>> print 'hello world' hello world >>> x = 1 >&g ...

  9. c语言程序设计实验13文件,第13章_文件---《C语言程序设计》实验指导.ppt

    第13章_文件---<C语言程序设计>实验指导 第十三章 主要内容 13.1 C文件概述 13.2 文件类型指针 13.3 文件的打开与关闭 13.4 文件的读写 13.5 文件的定位 1 ...

最新文章

  1. 直播预告丨告别后知后觉的指标波动,神策智能预警分析直播来袭
  2. python知识图谱可视化_知识图谱可视化
  3. vb 垂直滚动条定位
  4. ps人像精修照片步骤_15天零基础自学PS!送你整套PS教程297集+视频+素材+源文件模板6...
  5. 【SpringBoot 框架】- SpringBoot 配置文件,深入浅出mysql第三版pdf百度云
  6. java short int 转换_基本类型'short'-Java中的强制转换
  7. 【论文速览】PV-RCNN: Point-Voxel Feature Set Abstraction for 3D Object Detection
  8. C++11绑定器bind及function机制
  9. O2OA:移动办公市场中的另一股清流
  10. Tomcat8安装后tomcat8w.exe点击出现“指定的服务未安装”解决方案
  11. Android蓝牙调试助手
  12. 百度云服务器Ubuntu18.04部署web项目
  13. C#中五个序列聚合运算(Sum, Average, Min, Max,Aggregate)
  14. 开源OA协同办公平台使用教程:O2OA如何集成yozo
  15. 国科大计算机学院推免,电子科技大学2020届保研率17.8%,主要保研本校、国科大、北大、中科大...
  16. Java之父都说他老了找工作受阻,我们怎么办?
  17. 微信小程序中,图片的位置设置
  18. 使用CSS3中的column-rules属性实现仿报纸新闻排版
  19. 什么是Tendermint?(1)
  20. 最新基于协同过滤的毕业设计题目

热门文章

  1. Python海龟画图 画一个爱心 赶快给女朋友来一个
  2. 手机的模拟,有耗电和充电方法, 有电量的属性
  3. 分数换算小数补0法_小学数学概念+知识点顺口溜汇总+常用单位换算汇总
  4. canvas 将两张图片叠加
  5. U盘文件突然不见却占内存 解决方案
  6. 因为不允许对公司代码 XXXX科目 XXXXXXXXXX进行销项/进项税相关操作,所以税码XX无效
  7. 文件管理(操作系统)
  8. 让女人爱上你 打动女人的5大法门
  9. Turbo C 2.0的下载地址
  10. 解决QT接受串口数据时数据更新不及时,串口数据太多导致程序界面崩溃,串口接收数据过快等问题