UNIX环境编程-标准IO (7)

IO:input & output 一切实现的基础

IO分类:

  • stdio(标准IO)–优先使用 对于不同的系统均可以使用
  • sysio系统调用IO(文件IO)

标准IO优点:

  • 移植性性好、合并系统调用(可以为读写做一个加速的机制)
  • 在不同的系统下,标准IO所依赖的系统调用的函数不同

一、fopen()

1.fprintf

fprintf 是 C 语言标准库中的一个函数,用于将格式化的数据输出到文件。它是 printf 函数的文件版本,printf 输出到标准输出,而 fprintf 可以输出到任意文件

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int fprintf( FILE *stream, const char *format, ... );
// fprintf()函数根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件.因此fprintf()可以使得信息输出到指定的文件
// 如下:
/*
char name[20] = "Mary";
FILE *out;
out = fopen( "output.txt", "w" );
if( out != NULL )
fprintf( out, "Hello %s\n", name );
*/

参数的意义如下:

  • stream:这是一个指向 FILE 类型的指针,代表要写入的文件。可以是任何已打开的文件,包括 stdout
  • format:这是一个格式字符串,它定义了输出的格式。格式字符串可以包含普通字符和格式说明符。
  • ...:这是可变参数列表,它们的类型和数量由 format 字符串中的格式说明符决定。

函数返回写入的字符数,或者在出错时返回一个负数。

关于刷新缓冲区,C 语言标准库提供了 fflush 函数来刷新一个文件的缓冲区。如果你想立即将 fprintf 的输出发送到文件,而不等待缓冲区满或文件关闭,你可以使用 fflush 函数

fflush 函数的原型如下:

1
int fflush(FILE *stream);

stream 是你想刷新的文件的 FILE 指针。函数成功时返回0,出错时返回EOF。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("Failed to open file");
return 1;
}

fprintf(fp, "Hello, world!\n");
fflush(fp);

fclose(fp);
return 0;
}

这个例子首先打开一个文件用于写入,然后使用 fprintf 将一行文字写入文件,接着使用 fflush 将输出立即写入文件,最后关闭文件

在ubuntu终端可以使用man fopen查看fopen()函数相关信息

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode); // FILE是一个文件类型的结构体

// mode:
// r : Open text file for reading. The stream is positioned at the beginning of the file.
// r+: Open for reading and writing. The stream is positioned at the beginning of the file.
// w : Truncate file to zero length or create text file for writing(文件存在则清空,文件不存在则创建,只是写入).The stream is positioned at the beginning of the file.
// w+: Open for reading and writing. The file is created if it does not exist(相较w包含读写操作), otherwise it is truncated. The stream is positioned at the beginning of the file.
// a : Open for appending (writing at end of file)(在文件内容末尾进行内容追加). The file is created if it does not exist.The stream is positioned at the end of the file.
// a+: Open for reading and appending (writing at end of file)(读写) The file is created if it does not exist. Output is always appended to the end of the file. POSIX is silent on what the initial read position is when using this mode. For glibc, the initial file position for reading is at the beginning of the file, but for Android/BSD/MacOS, the initial file position for reading is at the end of the file.

Return

1
2
Upon  successful  completion  fopen(),  fdopen() and freopen() return a FILE pointer.  Otherwise, NULL is returned and errno is set to indicate the error. 
// 也会得到一个对应的errno

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
FILE *fp;

// 当前并为创建 tem.txt文件
fp = fopen("./tem.txt","r");

if(fp ==NULL)
{
fprintf(stderr,"fopen() failed! errno = %d\n",errno);
// 异常退出
exit(1);
}
puts("OK!");

exit(0);
}

运行可执行文件输出:

1
2
fopen() failed! errno = 2
// errno 可以进入 该目录查看 vim /usr/include/asm-generic/errno-base.h

image-20230716195033135

这样不太方便,因为对于报错的errno有很多,如果这样进行使用,后续还需要进入该目录下,查看该errno所对应的报错信息是什么



2.perror

这个函数可以对errno报错的信息进行自动关联(自动关联全局变量errno)

1
2
3
4
5
6
7
8
9
NAME
perror - print a system error message

SYNOPSIS
#include <stdio.h>

void perror(const char *s);

#include <errno.h>

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
FILE *fp = NULL ;

// 当前并为创建 tem.txt文件
fp = fopen("./tem.txt","r");

if(fp ==NULL)
{
// fprintf(stderr,"fopen() failed! errno = %d\n",errno);
perror("fopen()");
// 异常退出
exit(1);
}
puts("OK!");

exit(0);
}

运行可执行文件

1
bash: ./fopen: 没有那个文件或目录


3.strerror

1
2
3
4
5
6
7
8
NAME
strerror, strerror_r, strerror_l - return string describing error number

SYNOPSIS
#include <string.h>

// 需要的参数是一个errno
char *strerror(int errnum);

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
FILE *fp = NULL;

// 当前并为创建 tem.txt文件
fp = fopen("./tem.txt","r");

if(fp ==NULL)
{
// fprintf(stderr,"fopen() failed! errno = %d\n",errno);
// perror("fopen()");
fprintf(stderr, "fopen(): %s\n",strerror(errno));
// 异常退出
exit(1);
}
puts("OK!");

exit(0);
}

运行可执行文件

1
bash: ./fopen: 没有那个文件或目录


4.fopen相关问题

(1)fopen返回的FILE类型指针指向的是哪一个空间

栈 or 静态区 or 堆 答案: 堆上

  • 栈是局部变量存放的空间,不对 fopen返回的内容需要在整个程序(进程)运行期间均存在,所以不在栈上

  • 静态区 用static修饰该FILE变量.如下,但是需要注意的是,如果fopen函数被重复调用的时候,这个static FILE tmp;只会被声明一次,则如果多次调用fopen函数那么之前所返回的FILE指针则会被覆盖,不能用,有效的则是最后调用的那个fopen函数

1
2
3
4
5
6
FILE *fopen(const char *pathname, const char *mode)
{
static FILE tmp;

return &tmp;
}
  • 堆上,如下,需要对所使用的内存空间进行动态开辟malloc,那么fcolse函数则是对这块动态开辟的空间进行释放
1
2
3
4
5
6
7
FILE *fopen(const char *pathname, const char *mode)
{
FILE *tmp = NULL ;

tmp = malloc(sizeof(FILE));
return tmp;
}

问题:一个函数中定义一个静态变量,并且将静态变量地址作为函数的返回值,在程序中多次调用这个函数,会出现什么情况?

答:在一个函数中定义一个静态变量,并将该静态变量的地址作为函数的返回值,可以使得每次调用该函数时都访问到同一个变量。由于静态变量在程序的整个运行周期内都存在,并且仅在第一次调用时进行初始化,因此每次调用该函数时返回的都是同一个变量的地址。




二、字符输入与输出

菜鸟教程

1.模拟cp 指令将 src文件 复制到dest文件中

1
cp src dest
(1)fgetc
1
2
3
4
5
int fgetc(FILE *stream);
/*
fgetc()从流中读取下一个字符,并将其作为一个无符号字符返回,该字符转换为int,或文件末尾的EOF或error
从文件流中读取数据 ,并且返回
*/

(2)fputc
1
2
3
4
int fputc(int char, FILE *stream)
/*
C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流stream 中,并把位置标识符往前移动
*/

(3)实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
FILE *fps,*fpd;
int ch;

// 选择模式为 r表示源文件一定要存在 ,否则报错
fps = fopen(argv[1],"r");
if (fps == NULL)
{
perror("fopen()");
exit(1);
}

// 目标文件
fpd = fopen(argv[2],"w");
if (fpd == NULL)
{
perror("fopen()");
exit(1);
}

while(1)
{
// 从fps中读取数据
ch = fgetc(fps);
if(ch == EOF) // 读到文件末尾
{
break;
}
// 将原文件中读取的数据写入目标文件
fputc(ch,fpd);
}

fclose(fpd);
fclose(fps);
exit(0);
}

创建两个txt文件分别文src.txt以及dest.txt,在src.txt下写入123456,dest.txt为空白

运行可执行文件先在终端输入 (make mycpy)

1
./mycpy src.txt dest.txt

使用终端命令比较两个文件是否相同(Enter之后无任何输出表示两个文件是相同的)

1
$ diff src.txt dest.txt

image-20230717101947123



2.测试文件中存在有效字符数量

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
FILE *fp;
// 用于字符个数计数
int count = 0;

// 命令行参数数量出错
if(argc < 2)
{
fprintf(stderr, "Usage ...\n");
exit(1);
}

fp = fopen(argv[1],"r");

// 文件打开失败
if(fp == NULL)
{
perror("fopen()");
exit(1);
}


// 循环在fp文件中读取字符,直到文件的终止符
while(fgetc(fp) != EOF)
{
count++;
}

printf("count = %d \n",count);

// 关闭文件
fclose(fp);
exit(0);
}

运行可执行文件

image-20230717104133589



3.字符串相关

(1)fgets

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

1
2
3
4
SYNOPSIS
#include <stdio.h>

char *fgets(char *str, int n, FILE *stream) ;
  • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串
  • n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流

return

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

(2)fputs

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

1
2
#include <stdio.h>
int fputs(const char *str, FILE *stream)
  • str – 这是一个数组,包含了要写入的以空字符终止的字符序列
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流

return

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


4.fread 和 fwrite

(1)fread

注意:fread只能操作工整的数据

C 库函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中

1
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针
  • size – 这是要读取的每个元素(这个元素可以是结构体等)的大小,以字节为单位
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流

return

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

使用事项:

一般都是一个字节一个字节的读取

1
2
fread(buf,1,BUFSIZE,fps);
// BUFSIZE 为buf的字节数量,即大小

(2)fwrite

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

1
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr – 这是指向要被写入的元素数组的指针。
  • size – 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

return

  • 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误


5.printf与scanf函数族

image-20230717150540800

(1)sprintf

C 库函数 int sprintf(char *str, const char *format, …) 发送格式化输出到 str 所指向的字符串。

1
int sprintf(char *str, const char *format, ...);
  • str – 这是指向一个字符数组的指针,该数组存储了 C 字符串。
  • format – 这是字符串,包含了要被#写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。

(2)snprintf

snprintf() 是一个 C 语言标准库函数,用于格式化输出字符串,并将结果写入到指定的缓冲区,与 sprintf() 不同的是,snprintf() 会限制输出的字符数,避免缓冲区溢出

C 库函数 int snprintf(char *str, size_t size, const char *format, …) 设将可变参数**(…)按照 format 格式化成字符串,并将字符串复制到 str 中,size** 为要写入的字符的最大数目,超过 size 会被截断,最多写入 size-1 个字符

sprintf() 函数不同的是,snprintf() 函数提供了一个参数 size,可以防止缓冲区溢出。如果格式化后的字符串长度超过了 size-1,则 snprintf() 只会写入 size-1 个字符,并在字符串的末尾添加一个空字符(\0)以表示字符串的结束

1
int snprintf ( char * str, size_t size, const char * format, ... );
  • str – 目标字符串,用于存储格式化后的字符串的字符数组的指针
  • size – 字符数组的大小
  • format – 格式化字符串
  • – 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化

return

  • snprintf() 函数的返回值是输出到 str 缓冲区中的字符数,不包括字符串结尾的空字符 \0。如果 snprintf() 输出的字符数超过了 size 参数指定的缓冲区大小,则输出的结果会被截断,只有 size - 1 个字符被写入缓冲区,最后一个字符为字符串结尾的空字符 \0

  • 需要注意的是,snprintf() 函数返回的字符数并不包括字符串结尾的空字符 \0,因此如果需要将输出结果作为一个字符串使用,则需要在缓冲区的末尾添加一个空字符 \0


(3)fscanf

C 库函数 int fscanf(FILE *stream, const char *format, …) 从流 stream 读取格式化输入

1
int fscanf(FILE *stream, const char *format, ...)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
  • format – 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符format 说明符

(4)sscanf

C 库函数 int sscanf(const char *str, const char *format, …) 从字符串读取格式化输入

1
int sscanf(const char *str, const char *format, ...)
  • str – 这是 C 字符串,是函数检索数据的源
  • format – 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符format 说明符


6.文件位置函数和缓冲区刷新函数

在读取与写入的时候,存在文件位置指针

(1)fseek

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

1
int fseek(FILE *stream, long int offset, int whence)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
  • offset – 这是相对 whence 的偏移量,以字节为单位
  • whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:

image-20230717155519825

return

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

(2)ftell

C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置

1
long int ftell(FILE *stream)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流

return

  • 该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
// 判断命令行传递参数数量
if(argc < 2)
{
fprintf(stderr,"Usage ...\n");
exit(1);
}

FILE *fp;

fp = fopen(argv[1],"r");

if (fp ==NULL)
{
perror("fopen()");
exit(1);
}

// 文件位置指针值于文件末尾处
fseek(fp,0,SEEK_END);

printf("%ld\n",ftell(fp));

exit(1);
}

创建src.txt文件其中内容:123456\0 表示七个字节

image-20230717160628708

运行可执行文件

image-20230717160711855


(3)rewind

使用这个函数可以直接让文件位置指针达到文件内容的开头

C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头

1
void rewind(FILE *stream)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流

(4)fflush

C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区

1
int fflush(FILE *stream)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个缓冲流

return

如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
printf("Before while(1)..");

while(1);

printf("After while(1)..");

exit(0);
}

对上述代码进行make,然后运行可执行文件,发现终端无输出

原因: printf函数向标准终端进行输出时,是典型的行缓冲,是碰到换行符号或者一行满的时候以此来刷新缓冲区

修正

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
printf("Before while(1)..\n");

while(1);

printf("After while(1)..\n");

exit(0);
}

上述代码make之后运行会输出:Before while(1)..

使用fflush,强制刷新缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
printf("Before while(1)..");
// 将输出缓冲区进行刷新
fflush(stdout);

while(1);

printf("After while(1)..");
// 将会刷新所有打开的流
fflush(NULL);

exit(0);
}

make之后终端运行

image-20230717162950203


(5)缓冲区存在的作用

优点

  • 行缓冲:换行的时候刷新,满了的时候刷新,强制刷新(标准输出就是这样,因为是终端设备)
  • 全缓冲:满了的时候刷新,强制刷新(默认是,只要不是终端设备)
  • 无缓冲:如stderr,需要立即输出的内容

一个文件的缓冲模式默认为全缓冲模式,但是缓冲模式是可以进行修改的



7.getline

完整的获取文件一行的内容

视频教程

1
2
3
4
# define GNU_SOURCE
# include<stdio.h>

ssize_t getline(char **lineptr, size_t *n, FILE *stream);
  • lineptr:指向一个字符指针的指针,用于存储读取到的行。如果 *lineptr 是 NULL 或 n 是零,函数将为您分配一个新的缓冲区。当您完成使用后,需要使用 free 函数来释放这块内存
  • n:是指向已分配的内存大小的指针。它可能会被函数修改,表示新分配的大小
  • stream:是一个文件指针,指示从哪里读取数据,例如 stdin

返回值:

  • 成功:返回读取的字符数量(不包括结尾的 null 字符)。
  • 错误或读到文件结束:返回 -1

在编译程序时需要在CMakeLists.txt中添加该函数所需要的宏定义_D_GNU_SOURCE

(1)程序实例

读取文件内容

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char **argv)
{
FILE *fp;
char *linebuf;
size_t linesize;

if(argc < 2)
{
fprintf(stderr,"USage..\n");
exit(1);
}

fp = fopen(argv[1],"r");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}

/*getline函数至关重要的初始化*/
linebuf = NULL;
linesize = 0;


while(1)
{
if(getline(&linebuf,&linesize,fp) < 0)
{
break;
}
// 将当前行的字符数打印
printf("%ld\n",strlen(linebuf));
printf("%ld",linesize);
}
fclose(fp);
}

使用getline函数其参数的初始化是至关重要的,如果没有做好,会出现初始化

CMakeLists.txt

1
2
3
4
cmake_minimum_required(VERSION 3.16)
project(getline_test) # 工程名字可以与可执行程序名不一样
add_executable(main main.c)
target_compile_definitions(main PRIVATE -D_GNU_SOURCE)

创建src.txt

1
2
3
4
zhouxuezhi
comeno
wanglei
comeno

CMake之后运行程序,查看结果

1
2
3
4
5
6
7
8
12
120
7
120
8
120
7
120

需要注意的是,getline内部实际使用的malloc开辟动态的内存空间存储,读取到的内容(linebuf),并且当一行内容超过120字节时,则会使用realloc,在之前的malloc开辟的空间基础上继续增加120个字节的内容(类推)

内存泄漏,getline存在可控的内存泄漏,因为函数内部使用malloc开辟动态内存空间,但是最后并没有使用free函数将开辟的内存空间自动释放


(2)mygetline函数实现

原文链接

返回类型ssize_t为有符号整形,其值为获取的字符数,文件结束则返回-1,包括换行符’\n’,但不包括字符串结束符’\0’。linepter用来存储获得的字符串,size_t为无符号整形表示linepter的字节数。当*linepter为空时函数则动态为其分配空间,注意要将size_t值赋0。当linepter的空间不足时,函数会通过realloc,重新分配更大的空间。stream为文件指针,用于读取文件
mygetline.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef MYGETLINE_H_
#define MYGETLINE_H_

#include <stdio.h>
#include <stdlib.h>

ssize_t mygetline(char** line,size_t *n,FILE *fp);
void mygetline_free(char *buf);


#endif

mygetline.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <mygetline.h>


ssize_t mygetline(char** line,size_t *n,FILE *fp)
{
char *buf = *line;
// i to record string length, c to store characters
ssize_t c,i=0,init_bufsize=120;


// Opening up memory space
if(buf == NULL || *n = 0)
{
*line = malloc(120);
buf = *line;
*n = 120;
}

// read data
while((c=getc(fp))!='\n')
{
// Read to the end of the file
if(c==EOF)
{
return -1;
}
// Two spaces need to be left for characters '\n' and '\0'
if(i<(*n)-2)
{
*buf+i = c;
i++;
}
// The number of bytes in a row exceeds 120
else
{
*n = *n + init_bufsize;
// Insufficient initial allocated space, reallocating memory space with 120 bytes added each time
buf = realloc(buf,*n)
*buf+i = c;
i++;
}
}
// Fill in the characters '\0' and '\n' of the line in buf
*(buf+i++) = '\n';
*(buf+i++) = '\0';
return i;
}


void mygetline_free(char *buf)
{
free(buf);
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mygetline.h>

int main(int argc,char **argv)
{
FILE *fp;
char *linebuf;
size_t linesize;

if(argc < 2)
{
fprintf(stderr,"USage..\n");
exit(1);
}

fp = fopen(argv[1],"r");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}

/*getline函数至关重要的初始化*/
linebuf = NULL;
linesize = 0;


while(1)
{
if(mygetline(&linebuf,&linesize,fp) < 0)
{
break;
}
// 将当前行的字符数打印
printf("%ld\n",strlen(linebuf));
printf("%ld\n",linesize);
}
fclose(fp);
mygetline_free(linebuf);

exit(0);
}

工程目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
(base) zxz@ubuntu:~/Proj/C_C++/linux_c/sys/getline_test$ tree
.
├── bin
├── build
├── CMakeLists.txt
├── include
│   └── mygetline.h
├── lib
├── main.c
├── src
│   └── mygetline.c
└── src.txt

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cmake_minimum_required(VERSION 3.16)
project(getline_test)
# Output General Information
message("PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
# Define the output path for executable files
set(HOME ${PROJECT_SOURCE_DIR}/bin)
# Include header files
include_directories(${PROJECT_SOURCE_DIR}/include)
# search for file
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# Set the generation path for static libraries
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# Specify the output path of the executable file
set(EXECUTABLE_OUTPUT_PATH ${HOME})
# Create a static library
add_library(calc STATIC ${SRC_LIST})
# Generating Target Files
add_executable(main main.c)
# link static library
target_link_libraries(main calc)
target_compile_definitions(main PRIVATE -D_GNU_SOURCE)

运行结果

1
2
3
4
5
6
7
8
9
(base) zxz@ubuntu:~/Proj/C_C++/linux_c/sys/getline_test/bin$ ./main ../src.txt 
12
120
7
120
8
120
7
120

运行结果与getline函数相同



8.临时文件

(1)tmpfile

C 库函数 FILE *tmpfile(void) 以二进制更新模式(wb+)创建临时文件。被创建的临时文件会在流关闭的时候或者在程序终止的时候自动删除

1
FILE *tmpfile(void)

return

如果成功,该函数返回一个指向被创建的临时文件的流指针。如果文件未被创建,则返回 NULL

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main ()
{
FILE *fp;

fp = tmpfile();
printf("临时文件被创建\n");

/* 您可以在这里使用临时文件 */

fclose(fp);

return(0);
}

让我们编译并运行上面的程序,它将在 /tmp 文件夹中创建一个临时文件,但是一旦程序退出,临时文件会被自动删除