Linux C常见数I/O函数比较: printf, sprintf, fprintf, write...

1. 输出

1.1 输出格式化字符串

1.1.1 printf

输出格式化字符串到标准输出设备stdout

#include <stdio.h>
int printf(const char *format, ...);
  • 参数
    format 格式化字符串以null终结符结尾
    ... 对应format中转移字符, 支持char, short, int, long, float, double, 有符号/无符号等类型, char* (字符串), 不过, 需要配套转义字符.

  • 返回值
    成功返回写入的字符总数(不包括null终结符); 失败, 返回负数.

如果要向屏幕/控制台打印一个文本字符串, 可以直接用printf.
示例:

int age = 20;
printf("my age is %d\n", age);

注意: printf有自己的库缓存, "\n" 会将内容冲刷到内核IO缓存, 然后输出. 如果不使用 \n, 默认不会立即打印, 需要等待IO库缓存满.
可以调用fflush(stdout); 将内容主动冲刷.


1.1.2 fprintf

将格式化串输出到流文件中, 除了第一个参数需要调用者指定流, 其他同printf.

注意第一个参数是流文件, 如stdout, stderr, etc.

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);

向标准错误输出stderr打印一条错误消息

fprintf(stderr, "post a error message: %s\n", strerror(errno));

1.1.3 sprintf

将格式化串输出到缓存中, 除了第一个参数需要调用者指定流, 其他同printf.

#include <stdio.h>
int sprintf(char *str, const char *format, ...);

将一个字符串写到指定缓存中:

char buf[250];
sprintf(buf, "post a error message: %s\n", strerror(errno));

这里存在一个潜在的问题: 如果要写到缓存中的数据, 超过了缓存空间, 会导致内存溢出, 从而导致未定义行为.
使用sprintf就要求调用者得确保不会溢出. 另外的解决办法是, 使用更安全的snprintf.


1.1.4 snprintf

将指定长度的格式化串输出到缓存中, 其他同sprintf. 比sprintf更安全.

#include <stdio.h>
int snprintf(char *str, size_t size, const char *format, ...);

将一个字符串写到指定缓存中:

char buf[250];
snprintf(buf, sizeof(buf), "post a error message: %s\n", strerror(errno));

通常, 将第二个参数size设置为缓存大小, 但不一定非得这样做, 可根据实际情况决定.


1.1.5 dprintf

将格式化串输出到已打开文件描述符, 其他同printf.

#include <stdio.h>
int sprintf(char *str, const char *format, ...);

示例: 向标准输出(默认已打开, 文件描述符 = STDOUT_FILENO)输出

int age = 20;
dprintf(STDOUT_FILENO, "my age is %d\n", age);

1.1.6 v版本输出格式化字符串

带v版本的函数vprintf, vfprintf, vdprintf(), vsprintf(), vsnprintf() 等同于函数printf(), fprintf(), dprintf(), sprintf(), snprintf(), 除了v版本输出函数使用va_list表示可变参数, 而不带v版本初始函数用"..."表示可变参数.

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

printf可以用vprintf来实现:

int printf(const char *format, ...)
{
    va_list ap;
    int ret;    

    va_start(ap, format);
    ret = vprintf(format, ap);
    va_end(ap);

    return ret;
}

1.2 输出字符&字符串

1.2.1 putchar

向标准输出设备写一个字符c

#include <stdio.h>
int putchar(int c);
  • 返回值
    成功, 返回写入字符对应ASCII码; 失败, 返回EOF.

示例: 打印一个字符

char c = 'a';
int ret = putchar(c);
if (ret == EOF) {
    // 发生错误
}

1.2.2 putc, fputc

向指定文件流stream写一个字符c. fputc <=> putc, 区别在于putc可能是用宏定义实现的.
putchar(c) <=> putc(c, stdout)

#include <stdio.h>
int putc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);

示例: 向stdout写一个字符

char c = 'a';
int ret = putc(c, stdout); // <=> ret = fputc(c, stdout);
if (ret == EOF) {
    // 发生错误
}

1.2.3 fputc

fputc <=> putc, 区别在于putc可能是用宏定义实现的.

#include <stdio.h>
int fputc(int c, FILE *stream);

1.2.4 puts, fputs

puts向stdout写一行字符串;
fputs向指定文件流写一行字符串;

注意: 字符串末尾会自动换行

#include <stdio.h>

int fputs(const char *s, FILE *stream);
int puts(const char *s);

示例: 写一行字符串

puts("hello, this is a test string");
fputs("hello, this is a test string", stdout);

1.3 write

向已打开文件描述符. 将缓存buf内容写count个字节到fd指向的文件. buf不必以null终结符结尾.

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

示例: 向stdout写入所有缓存字符

char buf[2] = {'a', 'b'};
int n = write(STDOUT_FILENO, buf, sizeof(buf));
if (n < 0) {
    perror("write error");
    exit(1);
}

2. 输入

2.1 输入格式化字符串

2.1.1 scanf, fscanf, sscanf

scanf 从标准输入设备stdin读取格式化字符串
fscanf 从指定文件流读取格式化字符串, 其他同scanf
sscanf 从指定缓存str中读取格式化字符串

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

2.1.2 vscanf, vfscanf, vsscanf

vscanf, vfscanf, vsscanf分别等同scanf, fscanf, sscanf, 除了可变参数不一样: 带v的函数, 可变参数是va_list类型; 不带v的函数, 可变参数是用"..."表示

#include <stdarg.h>

int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);

同样的, scanf可以用fscanf来实现

int scanf(const char *format, ...)
{
    va_list(ap, format);
    int ret;

    va_start(ap);
    ret = vscanf(format, ap);
    va_end(ap);
    return ret;
}

2.2 输入一行字符串

2.2.1 gets

gets从stdin读取一行字符串, 直到新行EOF, 并用null终结符替代(换行符, 或EOF). 已弃用, gets没有检查缓冲区边界, 很可能会产生缓冲区溢出的问题. 替代方案是fgets.

#include <stdio.h>

char *gets(char *s);

2.2.2 fgets

fgets从指定文件流读取指定字节数的字符, 当遇到EOF或新行时, 停止读取; 或者缓冲区满时, 也会停止读取. 末尾元素会确保是null终结符.

#include <stdio.h>

char *fgets(char *s, int size, FILE *stream);

2.2.3 getchar, getc, fgetc

getchar 从stdin读取一个字符
getc 从指定文件流读取一个字符
fgetc <=> getc, 区别在于getc可能用宏定义实现, fgetc是函数实现

getchar() <=> getc(stdin) <=> fgetc(stdin)

#include <stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

2.2.4 ungetc

ungetc 将一个字符c推入指定文件流中, 使其下一个读取到的字符就是推入的字符c

#include <stdio.h>

int ungetc(int c, FILE *stream);

2.3 getline, getdelim

getline 从指定文件流读取一行数据, 读取的存入lineptr 指向的地址, 长度存入 结果参数n. 当读取到换行符(\n)时, getline会认为这是一行结束.
getline会用malloc分配缓冲区
lineptr, 用于存储该行字符串, 当*lineptr指向的缓冲区大小不够时, getline会用realloc重新调整其大小

getdelim 工作类似于getline, 除了由参数delim指定行分隔符, 而非默认的'\n'.
getdelim(&line, &len, stream, '\n') <=> getline(&line, &len, stream);

getline 和fgets区别
都是从指定文件流读取一行字符串, 区别在于:

  1. getline会申请分配内存, 调用者只需读取分配的缓存首地址、大小, 调用完毕后, 需要主动释放(free), 而fgets需要调用者提供缓存.
  2. getline返回成功读取字符串长度, 而fgets返回字符串(缓存)地址;
  3. getline是POSIX.1内容, fgets C89和POSIX.1内容;
#include <stdio.h>

ssize_t getline(char **lineptr, size_t *n, FILE *stream);

ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);
  • 参数
    lineptr 由调用者提供, *lineptr指向由getline分配用于存放字符串的缓冲区. 不能为NULL
    n n为缓冲区lineptr大小, 由getline设置. 不能为NULL
    stream 文件流, 由调用者指定

  • 返回值
    成功时, 返回读取到的字符数(包含分隔符, 不包含null终结符); 失败时, 返回-1, errno被设置.

示例: 从指定文件按行读取内容, 并打印到stdout

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

int main()
{
    char *line = NULL;
    ssize_t nread;
    size_t len;
    FILE *stream;

    stream = fopen("./testfile", "r");
    if (stream == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    while ((nread = getline(&line, &len, stream )) != -1) {
        printf("Retrieved line of length %zu:\n", nread);
        fwrite(line, nread, 1, stdout);
    }
    
    free(line);
    fclose(stream);

    return 0;
}

运行结果如下, 可以看到fwrite缓冲区line内容时有换行, 可知getline并没有把换行符替换掉.

$ cat testfile 
hello, this is a test file
test data:
abc 123;
yuiop @09876_54qwertyuio!;

$ ./a.out 
Retrieved line of length 27:
hello, this is a test file
Retrieved line of length 11:
test data:
Retrieved line of length 9:
abc 123;
Retrieved line of length 27:
yuiop @09876_54qwertyuio!;
Retrieved line of length 1:

2.4 read

read 从已打开文件描述符读取指定字节数的内容, 存放到缓存buf中
缓存buf由调用者提供, 读取碰到EOF或者数量已达count为止. 读取的字节数不会超过count.

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

示例: 从已打开文件, 读取并打印所有数据

#define FILE_NAME     "./testfile"

char buf[2];
int n;
int fd;

fd = open(FILE_NAME, O_RDONLY);

while ((n = read(fd, buf, sizeof(buf))) > 0) {
    write(STDOUT_FILENO, buf, n);  /* 输出到stdout */
}

close(fd);

2.5 fread

fread从指定文件流读取指定数量的内容到指定缓冲区ptr

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 参数
    ptr 缓冲区首地址
    size 要读取的元素个数
    nmemb 要读取的每个元素大小(byte)

  • 返回值
    成功, 返回读取的元素个数; 失败, 读取的总数≠nmemb(到达EOF或者出错), 此时需要用用feof, ferror来区分到达文件末尾, 还是出错.

示例: 从已打开文件, 读取并打印所有数据

#define FILE_NAME     "./testfile"

char buf[2];
int n;
FILE *fp;

fp = fopen(FILE_NAME, "r");
while ((n = fread(buf, sizeof(buf[0]), sizeof(buf), fp)) != sizeof(buf)) {
    write(STDOUT_FILENO, buf, n);
}

if (feof(fp)) {
    write(STDOUT_FILENO, buf, n);
}

if (ferror(fp)) {
    fprintf(stderr, "fread error\n");
}

fclose(fp);
posted @ 2021-06-11 15:04  明明1109  阅读(1286)  评论(0编辑  收藏  举报