当前位置: 首页 > news >正文

Linux系统C语言中与文件操作相关的核心库函数讲解

以下是 C 语言中文件操作相关库函数的表格总结,所有函数均来自 <stdio.h>头文件:

C 语言文件操作库函数一览表

函数声明功能描述头文件
文件打开与关闭
FILE *fopen(const char *filename, const char *mode);打开文件并返回文件指针<stdio.h>
FILE *freopen(const char *filename, const char *mode, FILE *stream);重新定向文件流<stdio.h>
int fclose(FILE *stream);关闭文件流<stdio.h>
FILE *tmpfile(void);创建临时文件(自动删除)<stdio.h>
文件读写操作
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);从文件读取数据块<stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);向文件写入数据块<stdio.h>
int fgetc(FILE *stream);从文件读取单个字符<stdio.h>
int getc(FILE *stream);从文件读取字符(通常为宏实现)<stdio.h>
int fputc(int char, FILE *stream);向文件写入单个字符<stdio.h>
int putc(int char, FILE *stream);向文件写入字符(通常为宏实现)<stdio.h>
char *fgets(char *str, int n, FILE *stream);从文件读取一行字符串<stdio.h>
int fputs(const char *str, FILE *stream);向文件写入字符串<stdio.h>
int fprintf(FILE *stream, const char *format, ...);格式化输出到文件<stdio.h>
int fscanf(FILE *stream, const char *format, ...);从文件格式化输入<stdio.h>
int ungetc(int char, FILE *stream);将字符推回流中<stdio.h>
文件定位与状态
int fseek(FILE *stream, long offset, int whence);设置文件位置<stdio.h>
long ftell(FILE *stream);获取当前文件位置<stdio.h>
void rewind(FILE *stream);重置文件位置到开头<stdio.h>
int fgetpos(FILE *stream, fpos_t *pos);获取文件位置(大文件支持)<stdio.h>
int fsetpos(FILE *stream, const fpos_t *pos);设置文件位置(大文件支持)<stdio.h>
int feof(FILE *stream);检查文件结束标志<stdio.h>
int ferror(FILE *stream);检查文件错误标志<stdio.h>
void clearerr(FILE *stream);清除错误和结束标志<stdio.h>
文件管理
int remove(const char *filename);删除文件<stdio.h>
int rename(const char *old_filename, const char *new_filename);重命名文件<stdio.h>
char *tmpnam(char *str);生成临时文件名(有安全风险)<stdio.h>
缓冲区控制
int fflush(FILE *stream);刷新输出缓冲区<stdio.h>
void setbuf(FILE *stream, char *buffer);设置文件缓冲<stdio.h>
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);设置文件缓冲模式<stdio.h>

fopen()函数

1. 函数声明

FILE *fopen(const char *pathname, const char *mode);
  • 作用:打开文件并创建文件流,用于后续读写操作
  • 头文件#include <stdio.h>

2. 参数详解

参数类型说明
pathnameconst char *文件路径(绝对路径或相对路径)
modeconst char *文件访问模式(见下方模式表)
返回值FILE *成功返回文件指针,失败返回 NULL

3. 打开模式详解(Linux 特注)

在 Linux 系统中,文本模式(无 “b”)和二进制模式(带 “b”)行为相同,因为 Linux 不转换换行符。

模式描述文件存在文件不存在指针位置写入行为
"r"只读必须存在失败开头禁止写入
"w"只写清空内容创建新文件开头覆盖写入
"a"追加保留内容创建新文件末尾尾部追加
"r+"读写必须存在失败开头覆盖写入
"w+"读写清空内容创建新文件开头覆盖写入
"a+"读写保留内容创建新文件读:开头 写:末尾尾部追加

📌 Linux 重要提示

  • 模式中的 b(如 "rb")在 Linux 中可忽略,但为跨平台兼容建议保留
  • 文件权限:新文件默认权限为 0666 & ~umask

4. 使用示例

#include <stdio.h>
#include <stdlib.h>int main() {// 示例1:安全打开文件(必须检查返回值!)FILE *fp = fopen("/home/user/data.txt", "r");if (fp == NULL) {perror("fopen failed");  // 输出具体错误exit(EXIT_FAILURE);}// 示例2:写入文件(自动创建新文件)FILE *log = fopen("app.log", "a");  // 追加模式if (log) {fprintf(log, "[INFO] Program started\n");fclose(log);}// 示例3:读写文件FILE *data = fopen("test.dat", "r+");if (data) {int value;fscanf(data, "%d", &value);  // 读取fseek(data, 0, SEEK_SET);    // 重置指针fprintf(data, "%d", value*2);// 写入fclose(data);}return 0;
}

5. 关键注意事项

  1. 必须检查返回值

    FILE *fp = fopen("file.txt", "r");
    if (fp == NULL) {// 处理错误:文件不存在/权限不足/路径错误perror("Error details");
    }
    
  2. 文件路径规范

    • 绝对路径:/home/user/docs/file.txt
    • 相对路径:../data/config.cfg
    • 当前目录:./output.logoutput.log
  3. 模式选择陷阱

    • "w""w+"会清空文件内容(误用会导致数据丢失!)
    • "a"模式写入时总是追加到末尾(无视文件指针位置)
  4. 读写位置管理

    // 在读写模式中切换操作时需重置指针
    fseek(fp, 0, SEEK_SET);  // 回到文件开头
    rewind(fp);              // 同上(但不清除错误标志)
    
  5. 资源释放

    fclose(fp);  // 必须关闭文件!否则导致:
    // - 资源泄漏
    // - 数据未写入磁盘
    // - 文件锁定问题
    

6. Linux 特有细节

  1. 文件锁机制

    • 使用 flock()fcntl()实现文件锁定
    • fopen()本身不提供锁,需额外处理并发访问
  2. 权限控制

    // 通过 umask 影响新文件权限
    umask(0022);  // 设置掩码
    FILE *fp = fopen("new.txt", "w"); // 实际权限 0644
    
  3. 符号链接处理

    • 默认跟随符号链接
    • 需防符号链接攻击:
    // 安全做法:先 lstat() 检查链接
    
  4. 错误代码

    • 通过 errno获取具体错误:
    #include <errno.h>
    if (fp == NULL) {if (errno == EACCES) {printf("Permission denied\n");}
    }
    

7. 最佳实践总结

  1. 始终检查返回值:避免 NULL 指针导致崩溃

  2. 明确路径处理:使用绝对路径或规范相对路径

  3. 模式选择原则

    • 只读 → "r"
    • 只写(覆盖)→ "w"
    • 追加 → "a"
    • 读写 → "r+"(需文件存在)或 "w+"(清空)
  4. 二进制文件处理:跨平台时显式使用 "b"模式

  5. 资源管理

    FILE *fp = NULL;
    if ((fp = fopen(...)) != NULL) {// 操作文件fclose(fp);  // 确保关闭
    }
    
  6. 错误处理:使用 perror()strerror(errno)输出详细信息

💡 性能提示

  • 频繁小量读写考虑使用 setvbuf()调整缓冲区
  • 大文件操作使用 fseek()替代多次 fopen()

fclose()函数

fclose()是 C 语言文件操作中至关重要的函数,负责安全关闭文件并释放资源。在 Linux 系统编程中,正确使用 fclose()是防止资源泄漏和确保数据完整性的关键。

一、函数声明与核心概念

1. 函数声明

int fclose(FILE *stream);

2. 作用

  • 关闭打开的文件流
  • 刷新缓冲区,确保所有数据写入磁盘
  • 释放与文件流相关的系统资源
  • 断开文件流与底层文件描述符的关联

3. 返回值

  • 成功:返回 0
  • 失败:返回 EOF(通常为 -1)

二、参数详解与使用示例

FILE *stream- 文件流指针

作用:指定要关闭的文件流

要求

  • 必须是通过 fopen(), freopen()tmpfile()打开的有效文件指针
  • 关闭后文件指针变为无效(称为"悬挂指针")
  • 多次关闭同一文件指针会导致未定义行为

示例

FILE *file = fopen("data.txt", "w");
if (file) {// 文件操作...if (fclose(file) == EOF) {perror("关闭文件失败");}// file 指针现在无效,不应再使用
}

三、完整使用示例

示例1:基本文件操作流程

#include <stdio.h>
#include <stdlib.h>int main() {FILE *file = fopen("example.txt", "w");if (!file) {perror("文件打开失败");return EXIT_FAILURE;}// 写入数据fprintf(file, "Hello, Linux File System!\n");// 关闭文件if (fclose(file) != 0) {perror("文件关闭失败");return EXIT_FAILURE;}printf("文件操作成功完成\n");return EXIT_SUCCESS;
}

示例2:批量处理多个文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_FILES 5int main() {FILE *files[MAX_FILES];const char *filenames[] = {"file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"};// 打开所有文件for (int i = 0; i < MAX_FILES; i++) {files[i] = fopen(filenames[i], "w");if (!files[i]) {fprintf(stderr, "无法打开文件: %s\n", filenames[i]);// 关闭之前已打开的文件for (int j = 0; j < i; j++) {fclose(files[j]);}return EXIT_FAILURE;}}// 写入数据到所有文件for (int i = 0; i < MAX_FILES; i++) {fprintf(files[i], "这是文件 %d 的内容\n", i+1);}// 关闭所有文件并检查错误int error_occurred = 0;for (int i = 0; i < MAX_FILES; i++) {if (fclose(files[i]) != 0) {perror(filenames[i]);error_occurred = 1;}}return error_occurred ? EXIT_FAILURE : EXIT_SUCCESS;
}

示例3:使用临时文件

#include <stdio.h>
#include <stdlib.h>int main() {// 创建临时文件(自动删除)FILE *tmp = tmpfile();if (!tmp) {perror("无法创建临时文件");return EXIT_FAILURE;}// 写入临时数据fprintf(tmp, "临时数据\n");// 关闭临时文件(自动删除)if (fclose(tmp) != 0) {perror("关闭临时文件失败");return EXIT_FAILURE;}printf("临时文件已安全处理\n");return EXIT_SUCCESS;
}

四、关键注意事项与使用细节

1. 资源泄漏风险

危险做法

void process_file() {FILE *file = fopen("data.txt", "r");// 处理文件...// 忘记 fclose(file)!// 函数返回后文件保持打开状态
}

正确做法

void safe_process() {FILE *file = fopen("data.txt", "r");if (!file) return;// 使用 goto 确保关闭if (error_condition) goto cleanup;// 处理文件...cleanup:if (fclose(file) != 0) {// 处理关闭错误}
}

2. 文件描述符管理

在 Linux 中,每个文件流对应一个文件描述符:

FILE *file = fopen("test.txt", "w");
int fd = fileno(file); // 获取底层文件描述符// 关闭文件流
fclose(file);// 现在 fd 已无效,不应再使用

3. 缓冲区刷新机制

fclose()会强制刷新缓冲区:

FILE *file = fopen("log.txt", "w");
fprintf(file, "重要数据...");// 此时数据可能在缓冲区,尚未写入磁盘
fclose(file); // 强制刷新缓冲区,确保数据写入

4. 错误处理重要性

FILE *file = fopen("critical.dat", "w");
// ...写入操作...
if (fclose(file) != 0) {// 关闭失败可能意味着数据未完全写入!// 需要紧急处理:尝试重新打开、备份等
}

5. 文件指针复用

FILE *file = fopen("first.txt", "w");
fclose(file);// 复用指针打开新文件
file = fopen("second.txt", "r");
if (file) {// 处理新文件...fclose(file);
}

五、高级应用场景

1. 使用 atexit()确保关闭

#include <stdlib.h>static FILE *global_file;void cleanup_file(void) {if (global_file && fclose(global_file) == 0) {global_file = NULL;}
}int main() {global_file = fopen("persistent.log", "a");if (!global_file) {perror("打开文件失败");return EXIT_FAILURE;}// 注册退出处理函数atexit(cleanup_file);// 程序运行期间持续使用文件...// 正常退出时自动关闭文件return EXIT_SUCCESS;
}

2. 文件描述符传递

#include <stdio.h>
#include <unistd.h>int main() {FILE *file = fopen("data.bin", "wb");int fd = fileno(file);// 写入数据...write(fd, "RAW DATA", 8);// 混合使用标准IO和系统调用时需要小心fflush(file); // 确保缓冲区刷新// 关闭文件fclose(file);return 0;
}

3. 自定义流关闭

#include <stdio.h>
#include <stdlib.h>int custom_close(void *cookie) {printf("关闭自定义流\n");free(cookie);return 0;
}FILE *create_custom_stream() {char *buffer = malloc(1024);// 创建自定义文件流FILE *stream = fopencookie(buffer, "w", (cookie_io_functions_t){.close = custom_close});return stream;
}int main() {FILE *custom = create_custom_stream();fprintf(custom, "测试自定义流");fclose(custom); // 触发 custom_closereturn 0;
}

六、常见错误与解决方案

错误1:忽略返回值

// 危险:可能忽略关闭错误
fclose(file);

解决方案

if (fclose(file) != 0) {perror("文件关闭失败");// 处理错误
}

错误2:重复关闭

fclose(file);
fclose(file); // 未定义行为!

解决方案

fclose(file);
file = NULL; // 设置为 NULL 防止误用

错误3:关闭标准流

fclose(stdin); // 危险!可能导致后续输入函数失败

解决方案

// 除非必要,否则不要关闭标准流
// 如需重定向,使用 freopen()
FILE *new_stdout = freopen("output.log", "w", stdout);

错误4:忘记关闭动态分配的文件指针数组

FILE **files = malloc(10 * sizeof(FILE*));
for (int i = 0; i < 10; i++) {files[i] = fopen(...);
}
// 使用后...
free(files); // 忘记关闭文件!

解决方案

for (int i = 0; i < 10; i++) {if (files[i]) fclose(files[i]);
}
free(files);

七、性能与资源管理

1. 文件描述符限制

#include <sys/resource.h>// 检查文件描述符限制
struct rlimit rlim;
getrlimit(RLIMIT_NOFILE, &rlim);
printf("软限制: %ld, 硬限制: %ld\n", rlim.rlim_cur, rlim.rlim_max);

2. 监控打开文件

// 查看进程打开的文件
system("ls -l /proc/$PPID/fd");

3. 提高文件描述符限制

// 临时提高限制
struct rlimit new_limit = {10240, 10240};
setrlimit(RLIMIT_NOFILE, &new_limit);

八、替代方案比较

1. vs close()系统调用

int fd = open("file.txt", O_WRONLY);
// ...操作...
close(fd); // 系统调用,不刷新缓冲区

2. vs fflush()

// fflush() 刷新但不关闭
fflush(file);
// 文件仍保持打开状态

3. 自动关闭技术(C++ RAII)

// C 中模拟 RAII
typedef struct {FILE *file;
} AutoFile;void auto_close(AutoFile *af) {if (af->file) fclose(af->file);
}int main() {AutoFile af = {fopen("auto.txt", "w")};// 使用 af.file...auto_close(&af); // 显式关闭return 0;
}

九、总结

fclose()是 Linux C 文件操作的最后关键一步,核心要点:

  1. 资源释放:关闭文件释放文件描述符和内存资源
  2. 数据完整性:确保缓冲数据写入磁盘
  3. 错误处理:必须检查返回值
  4. 安全实践:关闭后置指针为 NULL,避免重复关闭
  5. 系统限制:了解文件描述符限制
  6. 特殊文件:正确处理临时文件和标准流

fread()函数

一、函数声明与核心概念

1. 函数声明

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

2. 作用

fread()用于从文件流中读取数据块,特别适合处理二进制文件大数据块。它是 Linux 系统中高效文件操作的核心函数之一。

3. 返回值

  • 成功:返回实际读取的完整数据项数量(不是字节数)
  • 失败/EOF:返回值小于 nmemb参数
  • 0:当文件结束或发生错误时返回 0

二、参数详解与使用示例

1. void *ptr- 数据存储缓冲区

作用:指向存放读取数据的内存地址

类型:通用指针(可指向任何数据类型)

注意事项

  • 必须确保缓冲区足够大(size * nmemb字节)
  • 缓冲区内存必须已分配(静态数组或动态分配)

示例

// 读取整数数组
int numbers[100];
size_t items_read = fread(numbers, sizeof(int), 100, file_ptr);// 读取结构体
struct Employee {int id;char name[50];float salary;
};
struct Employee emp;
fread(&emp, sizeof(struct Employee), 1, file_ptr);

2. size_t size- 单个数据项大小

作用:指定每个数据项的字节大小

最佳实践

  • 使用 sizeof运算符确保准确性
  • 对于基本类型:sizeof(int), sizeof(double)
  • 对于结构体:sizeof(struct_name)

示例

// 正确使用 sizeof
double values[50];
fread(values, sizeof(double), 50, file_ptr);// 错误示例(硬编码大小)
float data[100];
fread(data, 4, 100, file_ptr); // 错误!float 大小可能不是4字节

3. size_t nmemb- 数据项数量

作用:指定要读取的数据项数量

注意事项

  • size参数共同决定总读取字节数(size * nmemb)
  • 实际读取量可能小于请求量(文件结束或错误)

示例

// 读取100个结构体
struct Point { float x, y; };
struct Point points[100];
size_t read_count = fread(points, sizeof(struct Point), 100, file_ptr);if (read_count < 100) {// 处理未完全读取的情况
}

4. FILE *stream- 文件流指针

作用:指定要读取的文件流

要求

  • 必须通过 fopen()打开的文件指针
  • 文件必须以可读模式打开(如 “r”, “rb”, “r+” 等)

示例

FILE *file = fopen("data.bin", "rb"); // 二进制读取模式
if (file == NULL) {perror("文件打开失败");exit(EXIT_FAILURE);
}char buffer[1024];
size_t read_items = fread(buffer, 1, 1024, file); // 读取1024字节

三、完整使用示例

示例1:读取二进制文件

#include <stdio.h>
#include <stdlib.h>int main() {FILE *file = fopen("image.jpg", "rb");if (!file) {perror("无法打开文件");return 1;}// 获取文件大小fseek(file, 0, SEEK_END);long file_size = ftell(file);rewind(file);// 分配内存unsigned char *image_data = malloc(file_size);if (!image_data) {perror("内存分配失败");fclose(file);return 1;}// 读取整个文件size_t items_read = fread(image_data, 1, file_size, file);if (items_read != file_size) {fprintf(stderr, "读取错误:请求 %ld 字节,实际读取 %zu 字节\n",file_size, items_read);}// 处理图像数据...free(image_data);fclose(file);return 0;
}

示例2:读取结构化数据文件

#include <stdio.h>
#include <stdlib.h>typedef struct {int id;char name[30];double balance;
} Account;int main() {FILE *file = fopen("accounts.dat", "rb");if (!file) {perror("无法打开账户文件");return 1;}// 读取文件头(记录数量)int record_count;if (fread(&record_count, sizeof(int), 1, file) != 1) {perror("读取记录数量失败");fclose(file);return 1;}// 分配内存读取所有账户Account *accounts = malloc(record_count * sizeof(Account));if (!accounts) {perror("内存分配失败");fclose(file);return 1;}// 批量读取账户数据size_t read_count = fread(accounts, sizeof(Account), record_count, file);if (read_count != record_count) {fprintf(stderr, "警告:请求 %d 条记录,实际读取 %zu 条\n",record_count, read_count);}// 处理账户数据...free(accounts);fclose(file);return 0;
}

四、关键注意事项与使用细节

1. 二进制模式 vs 文本模式

  • 二进制模式(“rb”):
    • 数据按原样读取,无任何转换
    • 适合图像、音频、压缩文件等
  • 文本模式(“r”):
    • Linux 中与二进制模式相同(无换行符转换)
    • Windows 中会转换换行符(\r\n\n

建议:处理非文本文件时始终使用二进制模式

2. 错误处理最佳实践

size_t items = fread(buffer, size, nmemb, file);if (items < nmemb) {if (feof(file)) {printf("到达文件末尾\n");}if (ferror(file)) {perror("读取错误");}
}

3. 缓冲区对齐优化

对于高性能应用,确保缓冲区对齐:

// 使用对齐分配
double *data = aligned_alloc(64, 1024 * sizeof(double)); // 64字节对齐
fread(data, sizeof(double), 1024, file);

4. 大文件处理技巧

分块读取大文件避免内存不足:

#define CHUNK_SIZE 65536 // 64KBunsigned char buffer[CHUNK_SIZE];
size_t total_read = 0;
size_t chunk_read;while ((chunk_read = fread(buffer, 1, CHUNK_SIZE, file)) > 0) {total_read += chunk_read;// 处理当前数据块...
}

5. 结构体读取的特殊考虑

struct Data {int a;double b;
};// 潜在问题:结构体可能有填充字节
fread(&data, sizeof(struct Data), 1, file);// 解决方案:逐字段读取
fread(&data.a, sizeof(int), 1, file);
fread(&data.b, sizeof(double), 1, file);

6. 与 fwrite()配对使用

写入和读取应使用相同的参数:

// 写入
int numbers[] = {1, 2, 3, 4, 5};
fwrite(numbers, sizeof(int), 5, file);// 读取(必须匹配)
int read_nums[5];
fread(read_nums, sizeof(int), 5, file);

五、性能优化技巧

1. 设置缓冲区大小

FILE *file = fopen("large.dat", "rb");
char custom_buffer[131072]; // 128KB// 设置自定义缓冲区
setvbuf(file, custom_buffer, _IOFBF, sizeof(custom_buffer));

2. 直接 I/O(绕过内核缓存)

file = fopen("fast.dat", "rb");
posix_fadvise(fileno(file), 0, 0, POSIX_FADV_SEQUENTIAL);

3. 内存映射文件

对于超大文件,考虑使用 mmap

int fd = open("huge.dat", O_RDONLY);
void *map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);// 直接访问文件内容,无需fread
memcpy(buffer, map, buffer_size);

六、常见错误与解决方案

错误1:忽略返回值

// 错误做法
fread(buffer, size, nmemb, file);// 正确做法
size_t read = fread(buffer, size, nmemb, file);
if (read < nmemb) {// 处理部分读取或错误
}

错误2:大小计算错误

// 错误:混淆大小和数量
int data[100];
fread(data, 100, sizeof(int), file); // 参数顺序错误!// 正确
fread(data, sizeof(int), 100, file);

错误3:未处理字节序

// 跨平台问题
uint32_t value;
fread(&value, sizeof(uint32_t), 1, file);// 需要转换字节序
value = ntohl(value); // 网络字节序转主机字节序

七、总结

fread()是 Linux C 程序中高效文件读取的核心函数,关键要点:

  1. 参数顺序ptr, size, nmemb, stream
  2. 返回值:实际读取的完整项数(非字节数)
  3. 错误处理:必须检查返回值并配合 feof()/ferror()
  4. 二进制模式:非文本文件必须使用 “rb”
  5. 缓冲区管理:确保足够空间和正确对齐
  6. 大文件处理:分块读取避免内存不足

fprintf()函数

fprintf()是 C 语言中用于格式化输出的核心函数,特别适合向文件流写入格式化的数据。在 Linux 系统编程中,它是日志记录、配置文件生成等任务的关键工具。

一、函数声明与核心概念

1. 函数声明

int fprintf(FILE *stream, const char *format, ...);

2. 作用

将格式化的数据写入指定的文件流中,支持多种数据类型和复杂的格式控制。

3. 返回值

  • 成功:返回写入的字符总数(不包括字符串结尾的 ‘\0’)
  • 失败:返回负数(通常表示错误)

二、参数详解与使用示例

1. FILE *stream- 文件流指针

作用:指定要写入的文件流

要求

  • 必须通过 fopen()打开的文件指针
  • 文件必须以可写模式打开(如 “w”, “a”, “r+” 等)
  • 可以是标准流:stdout(标准输出)、stderr(标准错误)

示例

// 写入普通文件
FILE *log = fopen("app.log", "a");
fprintf(log, "[INFO] Application started\n");// 写入标准输出
fprintf(stdout, "Hello, World!\n");// 写入标准错误
fprintf(stderr, "[ERROR] Invalid input detected\n");

2. const char *format- 格式字符串

作用:控制输出格式的字符串,包含:

  • 普通字符:直接输出
  • 格式说明符:以 %开头,指定后续参数的格式

格式说明符语法

%[标志][宽度][.精度][长度]类型
常用类型说明符:
说明符类型示例输出
%dint42
%uunsigned int42
%ffloat/double3.141593
%e科学计数法3.141593e+00
%g自动选择 %f/%e3.14159
%ccharA
%s字符串“Hello”
%p指针地址0x7ffd42
%%百分号字符%
标志、宽度、精度示例:
int num = 42;
double pi = 3.1415926535;// 标志示例
fprintf(stdout, "%-10d\n", num);   // 左对齐: "42        "
fprintf(stdout, "%+d\n", num);      // 显示符号: "+42"
fprintf(stdout, "%#x\n", num);      // 十六进制前缀: "0x2a"// 宽度和精度
fprintf(stdout, "%10d\n", num);     // 宽度10: "        42"
fprintf(stdout, "%.3f\n", pi);      // 精度3: "3.142"
fprintf(stdout, "%10.3f\n", pi);    // 宽度10精度3: "     3.142"

3. ...- 可变参数

作用:根据格式字符串提供要输出的数据

要求

  • 参数数量和类型必须与格式字符串中的说明符匹配
  • 支持任意数量和类型的参数

示例

// 多参数示例
char *name = "Alice";
int age = 30;
double score = 95.5;fprintf(stdout, "Name: %s, Age: %d, Score: %.1f\n", name, age, score);
// 输出: Name: Alice, Age: 30, Score: 95.5

三、完整使用示例

示例1:创建配置文件

#include <stdio.h>
#include <time.h>int main() {FILE *config = fopen("app.conf", "w");if (!config) {perror("无法创建配置文件");return 1;}time_t now = time(NULL);struct tm *t = localtime(&now);fprintf(config, "# Application Configuration\n");fprintf(config, "# Generated on: %04d-%02d-%02d %02d:%02d:%02d\n",t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,t->tm_hour, t->tm_min, t->tm_sec);fprintf(config, "\n[Settings]\n");fprintf(config, "log_level = INFO\n");fprintf(config, "max_connections = %d\n", 100);fprintf(config, "timeout = %.2f\n", 5.75);fclose(config);return 0;
}

示例2:带错误处理的日志系统

#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>void log_message(FILE *stream, const char *level, const char *format, ...) {time_t now = time(NULL);char timestamp[20];strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&now));// 打印时间戳和日志级别fprintf(stream, "[%s] [%s] ", timestamp, level);// 处理可变参数va_list args;va_start(args, format);vfprintf(stream, format, args);va_end(args);// 如果有系统错误,添加错误信息if (errno != 0) {fprintf(stream, " (%s)", strerror(errno));errno = 0; // 重置错误状态}fprintf(stream, "\n");fflush(stream); // 确保立即写入
}int main() {FILE *logfile = fopen("application.log", "a");if (!logfile) {perror("无法打开日志文件");return 1;}log_message(stdout, "INFO", "Application started");log_message(logfile, "DEBUG", "Initializing modules");// 模拟一个错误FILE *missing = fopen("nonexistent.txt", "r");if (!missing) {log_message(stderr, "ERROR", "Failed to open file");log_message(logfile, "ERROR", "File open failed");}log_message(logfile, "INFO", "Shutting down");fclose(logfile);return 0;
}

四、关键注意事项与使用细节

1. 格式字符串安全

危险做法

char user_input[100];
fgets(user_input, sizeof(user_input), stdin);
fprintf(stdout, user_input); // 格式字符串漏洞!

安全做法

fprintf(stdout, "%s", user_input); // 使用%s输出字符串

2. 缓冲区与刷新

// 默认情况下,文件流是缓冲的
fprintf(file, "This may not appear immediately");// 强制刷新缓冲区
fflush(file);// 设置无缓冲
setbuf(file, NULL);

3. 错误处理

int bytes_written = fprintf(file, "Important data");
if (bytes_written < 0) {perror("写入失败");if (ferror(file)) {// 处理具体错误}
}

4. 性能优化

对于频繁写入:

// 使用更大的缓冲区
char buffer[8192];
setvbuf(file, buffer, _IOFBF, sizeof(buffer));// 批量写入
for (int i = 0; i < 1000; i++) {fprintf(file, "Data %d\n", i);
}
// 而不是每次写入后刷新

5. 本地化支持

#include <locale.h>setlocale(LC_NUMERIC, "de_DE.UTF-8"); // 德国本地化
fprintf(stdout, "%.2f\n", 1234.56); // 输出 "1234,56"(使用逗号分隔)

6. 高级格式控制

// 动态宽度和精度
int width = 10;
int precision = 3;
double value = 3.1415926535;fprintf(stdout, "%*.*f\n", width, precision, value); // 输出 "     3.142"// 位置参数(GNU扩展)
fprintf(stdout, "%2$s %1$d\n", 42, "Answer"); // 输出 "Answer 42"

五、特殊文件处理

1. 写入 /dev/null

FILE *null = fopen("/dev/null", "w");
fprintf(null, "This will be discarded\n"); // 写入黑洞

2. 写入命名管道

mkfifo("my_pipe", 0666);
FILE *pipe = fopen("my_pipe", "w");
fprintf(pipe, "Data for another process\n");

3. 写入 syslog

#include <syslog.h>// 直接使用 syslog 函数更合适
syslog(LOG_INFO, "System event occurred");

六、常见错误与解决方案

错误1:格式说明符与参数不匹配

int num = 42;
fprintf(stdout, "Value: %f\n", num); // 错误!应为%d

解决方案

  • 使用编译器警告:gcc -Wall -Werror
  • 使用静态分析工具

错误2:忘记检查返回值

fprintf(file, "Critical data"); // 可能失败但未处理

解决方案

if (fprintf(file, "Critical data") < 0) {// 错误处理
}

错误3:文件未正确打开

FILE *file = fopen("data.txt", "r"); // 只读模式
fprintf(file, "Trying to write"); // 会失败

解决方案

FILE *file = fopen("data.txt", "a+"); // 读写/追加模式
if (!file) {perror("打开文件失败");return;
}

错误4:缓冲区溢出

char name[10];
fprintf(stdout, "Name: %s\n", name); // 如果name过长会溢出

解决方案

fprintf(stdout, "Name: %.9s\n", name); // 限制输出长度

七、性能对比与替代方案

1. 与 fwrite()对比

// 使用 fprintf 写入字符串
fprintf(file, "Hello, World!\n");// 使用 fwrite 效率更高
const char *msg = "Hello, World!\n";
fwrite(msg, 1, strlen(msg), file);

2. 与 snprintf()+ fwrite()组合

char buffer[256];
int len = snprintf(buffer, sizeof(buffer), "Value: %d", 42);
fwrite(buffer, 1, len, file);

3. 与 dprintf()对比(Linux 特有)

// 直接使用文件描述符
int fd = open("file.txt", O_WRONLY);
dprintf(fd, "Value: %d\n", 42);

八、总结

fprintf()是 Linux C 编程中格式化输出的核心工具,关键要点:

  1. 格式控制:使用 %格式说明符控制输出格式
  2. 文件流:支持文件、标准输出、标准错误等
  3. 错误处理:必须检查返回值和错误状态
  4. 安全:避免格式字符串漏洞
  5. 性能:批量写入和缓冲区管理优化性能
  6. 本地化:考虑本地化设置对输出的影响

fgets()函数

fgets()是 C 语言中用于安全读取文本行的核心函数,特别适合处理用户输入和文本文件。在 Linux 系统编程中,它是避免缓冲区溢出的关键工具。

一、函数声明与核心概念

1. 函数声明

char *fgets(char *str, int n, FILE *stream);

2. 作用

从指定的文件流中读取一行文本(直到遇到换行符或文件结束),并确保缓冲区不会溢出。

3. 返回值

  • 成功:返回 str指针(传入的缓冲区地址)
  • 失败/EOF:返回 NULL
  • 当读取到文件末尾且没有读取任何字符时返回 NULL

二、参数详解与使用示例

1. char *str- 数据存储缓冲区

作用:存放读取到的字符串

要求

  • 必须是指向字符数组的指针
  • 缓冲区大小必须足够容纳读取的数据
  • 函数会自动添加字符串终止符 \0

示例

// 静态缓冲区
char buffer[100];
fgets(buffer, sizeof(buffer), stdin);// 动态分配缓冲区
char *dyn_buffer = malloc(256);
if (dyn_buffer) {fgets(dyn_buffer, 256, stdin);
}

2. int n- 最大读取字符数

作用:指定最多读取的字符数(包括结尾的 \0

关键细节

  • 实际读取字符数 = n - 1(因为要预留空间给 \0
  • 如果遇到换行符,会包含在读取的字符串中
  • 如果遇到 EOF,会提前结束读取

示例

char buf[10];
fgets(buf, sizeof(buf), stdin);// 输入 "HelloWorld"(10字符)
// buf 内容: "HelloWorl" + \0(实际读取9字符)

3. FILE *stream- 文件流指针

作用:指定要读取的文件流

常用值

  • stdin:标准输入(键盘)
  • 文件指针:通过 fopen()打开的文件

示例

// 从标准输入读取
fgets(buffer, 100, stdin);// 从文件读取
FILE *file = fopen("data.txt", "r");
if (file) {fgets(buffer, 100, file);
}

三、完整使用示例

示例1:安全读取用户输入

#include <stdio.h>
#include <string.h>int main() {char name[50];printf("请输入您的姓名:");if (fgets(name, sizeof(name), stdin) == NULL) {fprintf(stderr, "读取输入失败\n");return 1;}// 去除可能的换行符name[strcspn(name, "\n")] = '\0';printf("您好, %s!\n", name);return 0;
}

示例2:逐行读取文件

#include <stdio.h>
#include <stdlib.h>int main() {FILE *file = fopen("config.txt", "r");if (!file) {perror("无法打开文件");return 1;}char line[256];int line_num = 0;while (fgets(line, sizeof(line), file) != NULL) {line_num++;// 去除换行符size_t len = strlen(line);if (len > 0 && line[len-1] == '\n') {line[len-1] = '\0';}printf("%4d: %s\n", line_num, line);}if (ferror(file)) {perror("读取文件时出错");}fclose(file);return 0;
}

示例3:处理长行(分块读取)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>int main() {FILE *file = fopen("long_lines.txt", "r");if (!file) {perror("无法打开文件");return 1;}char chunk[100];char *full_line = NULL;size_t full_size = 0;bool complete_line = false;while (fgets(chunk, sizeof(chunk), file) != NULL) {// 检查是否读取了完整行size_t chunk_len = strlen(chunk);complete_line = (chunk_len > 0 && chunk[chunk_len-1] == '\n');// 如果存在换行符,替换为 \0if (complete_line) {chunk[chunk_len-1] = '\0';}// 扩展缓冲区size_t new_size = full_size + chunk_len + (complete_line ? 0 : 1);char *new_buffer = realloc(full_line, new_size);if (!new_buffer) {free(full_line);perror("内存分配失败");fclose(file);return 1;}full_line = new_buffer;strcpy(full_line + full_size, chunk);full_size = new_size - (complete_line ? 0 : 1);// 如果是完整行,处理并重置if (complete_line) {printf("完整行: %s\n", full_line);free(full_line);full_line = NULL;full_size = 0;}}// 处理文件末尾的不完整行if (full_line) {printf("不完整行: %s\n", full_line);free(full_line);}fclose(file);return 0;
}

四、关键注意事项与使用细节

1. 换行符处理

fgets()会保留换行符:

// 输入 "Hello" + Enter
fgets(buf, 100, stdin);
// buf 内容: "Hello\n\0"// 安全去除换行符:
buf[strcspn(buf, "\n")] = '\0';

2. 缓冲区溢出保护

fgets()是安全函数:

// 安全
char small_buf[5];
fgets(small_buf, sizeof(small_buf), stdin);// 危险替代方案(已弃用)
gets(small_buf); // 可能导致缓冲区溢出

3. EOF 处理

正确处理文件结束:

while (fgets(line, sizeof(line), file)) {// 处理行
}if (feof(file)) {printf("已到达文件末尾\n");
} else if (ferror(file)) {perror("读取错误");
}

4. 输入流残留数据

当读取部分行时:

char buf[5];
fgets(buf, sizeof(buf), stdin); // 输入 "HelloWorld"// 输入流中残留 "oWorld\n"
// 需要清空输入流:
int c;
while ((c = getchar()) != '\n' && c != EOF);

5. 二进制文件注意事项

fgets()不适合二进制文件:

// 错误用法:读取二进制文件
FILE *bin = fopen("data.bin", "rb");
char buf[100];
fgets(buf, sizeof(buf), bin); // 可能提前停止(遇到0字节)// 正确做法:使用 fread()
fread(buf, 1, sizeof(buf), bin);

6. 性能优化

对于大文件:

// 设置更大的缓冲区
char io_buffer[8192];
setvbuf(file, io_buffer, _IOFBF, sizeof(io_buffer));while (fgets(line, sizeof(line), file)) {// 处理行
}

五、与 Linux 系统特性结合

1. 从管道读取

// Shell: echo "Hello" | ./program
fgets(buffer, 100, stdin);

2. 从设备文件读取

// 读取系统信息
FILE *meminfo = fopen("/proc/meminfo", "r");
if (meminfo) {fgets(buffer, 100, meminfo);fclose(meminfo);
}

3. 非阻塞 I/O

int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
FILE *file = fdopen(fd, "r");// 尝试读取
if (fgets(buffer, 100, file) == NULL) {if (errno == EAGAIN) {printf("数据暂不可用\n");}
}

六、常见错误与解决方案

错误1:忽略返回值

// 错误做法
fgets(buffer, size, file);// 正确做法
if (fgets(buffer, size, file) == NULL) {// 处理错误或EOF
}

错误2:缓冲区大小错误

char buf[10];
fgets(buf, 10, stdin); // 正确:最多读9字符+1个\0// 危险做法
fgets(buf, 11, stdin); // 缓冲区溢出风险!

错误3:未处理换行符

char name[50];
fgets(name, sizeof(name), stdin);
printf("你好, %s!", name); // 输出 "你好, Alice\n!"// 正确做法
name[strcspn(name, "\n")] = '\0';

错误4:错误处理 EOF

// 错误:使用feof()判断循环
while (!feof(file)) {fgets(line, size, file); // 最后一次读取会失败// ...
}// 正确做法
while (fgets(line, size, file)) {// ...
}

七、替代方案比较

1. vs scanf()

// scanf() 问题:
char buf[10];
scanf("%s", buf); // 可能溢出,不读取空格// fgets() 更安全:
fgets(buf, sizeof(buf), stdin);

2. vs getline()(POSIX 标准)

// getline() 自动分配内存
char *line = NULL;
size_t len = 0;
ssize_t read;while ((read = getline(&line, &len, file)) != -1) {// 处理行
}
free(line);

3. vs read()系统调用

// 低级读取
char buf[100];
ssize_t bytes = read(fd, buf, sizeof(buf)-1);
if (bytes > 0) {buf[bytes] = '\0'; // 需要手动添加终止符
}

八、总结

fgets()是 Linux C 编程中安全读取文本的核心工具,关键要点:

  1. 安全第一:防止缓冲区溢出
  2. 换行符处理:保留换行符,需要时手动移除
  3. 错误处理:必须检查返回值和错误状态
  4. EOF 处理:正确区分错误和文件结束
  5. 流处理:支持标准输入、文件和管道
  6. 文本专用:不适合二进制文件

fwrite()函数

fwrite()是 C 语言中用于高效写入二进制数据的核心函数,特别适合处理结构化数据和大型文件。在 Linux 系统编程中,它是高性能文件操作的关键工具。

一、函数声明与核心概念

1. 函数声明

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

2. 作用

将内存中的数据块直接写入文件流,特别适合处理:

  • 二进制文件(图像、音频、视频)
  • 结构化数据(数组、结构体)
  • 大数据集(数据库、日志文件)

3. 返回值

  • 成功:返回实际写入的数据项数量(不是字节数)
  • 失败:返回值小于 nmemb参数
  • 0:当写入失败时返回 0

二、参数详解与使用示例

1. const void *ptr- 数据源指针

作用:指向要写入数据的内存地址

要求

  • 可以是任何类型的数据指针
  • 数据在内存中必须是连续的
  • 对于非连续数据,需要先组合到缓冲区

示例

// 写入整数数组
int numbers[] = {1, 2, 3, 4, 5};
fwrite(numbers, sizeof(int), 5, file);// 写入结构体
struct Point { double x, y; };
struct Point p = {3.14, 2.71};
fwrite(&p, sizeof(struct Point), 1, file);

2. size_t size- 单个数据项大小

作用:指定每个数据项的字节大小

最佳实践

  • 使用 sizeof运算符确保准确性
  • 对于基本类型:sizeof(int), sizeof(double)
  • 对于结构体:sizeof(struct_name)

示例

// 正确使用 sizeof
float data[100];
fwrite(data, sizeof(float), 100, file);// 错误示例(硬编码大小)
double values[50];
fwrite(values, 8, 50, file); // 错误!double 大小可能不是8字节

3. size_t nmemb- 数据项数量

作用:指定要写入的数据项数量

注意事项

  • size参数共同决定总写入字节数(size * nmemb)
  • 实际写入量可能小于请求量(磁盘空间不足或错误)

示例

// 写入100个结构体
struct Record {int id;char name[50];float balance;
};
struct Record records[100];
size_t written = fwrite(records, sizeof(struct Record), 100, file);if (written < 100) {// 处理未完全写入的情况
}

4. FILE *stream- 文件流指针

作用:指定要写入的文件流

要求

  • 必须通过 fopen()打开的文件指针
  • 文件必须以可写模式打开(如 “w”, “wb”, “a”, “r+” 等)

示例

FILE *file = fopen("data.bin", "wb"); // 二进制写入模式
if (file == NULL) {perror("文件打开失败");exit(EXIT_FAILURE);
}char buffer[1024];
size_t written = fwrite(buffer, 1, 1024, file);

三、完整使用示例

示例1:创建二进制数据文件

#include <stdio.h>
#include <stdlib.h>int main() {FILE *file = fopen("dataset.bin", "wb");if (!file) {perror("无法创建文件");return 1;}// 生成并写入数据const int num_points = 1000;double *points = malloc(num_points * sizeof(double));if (!points) {perror("内存分配失败");fclose(file);return 1;}for (int i = 0; i < num_points; i++) {points[i] = i * 0.1;}size_t written = fwrite(points, sizeof(double), num_points, file);if (written != num_points) {fprintf(stderr, "写入错误:请求 %d 项,实际写入 %zu 项\n",num_points, written);}free(points);fclose(file);return 0;
}

示例2:写入结构化数据文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct {int id;char name[50];double salary;char department[30];
} Employee;int main() {Employee employees[] = {{101, "Alice Smith", 85000.0, "Engineering"},{102, "Bob Johnson", 75000.0, "Marketing"},{103, "Charlie Brown", 90000.0, "Engineering"}};FILE *file = fopen("employees.dat", "wb");if (!file) {perror("无法打开员工文件");return 1;}// 写入记录数量int count = sizeof(employees) / sizeof(Employee);if (fwrite(&count, sizeof(int), 1, file) != 1) {perror("写入记录数量失败");fclose(file);return 1;}// 写入所有员工记录size_t written = fwrite(employees, sizeof(Employee), count, file);if (written != count) {fprintf(stderr, "警告:请求 %d 条记录,实际写入 %zu 条\n",count, written);}fclose(file);printf("成功写入 %zu 条员工记录\n", written);return 0;
}

示例3:高效写入大文件

#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define CHUNK_SIZE (1024 * 1024) // 1MBint main() {FILE *file = fopen("largefile.bin", "wb");if (!file) {perror("无法创建大文件");return 1;}// 设置大缓冲区提高性能setvbuf(file, NULL, _IOFBF, 16 * 1024 * 1024); // 16MB缓冲区unsigned char *buffer = malloc(CHUNK_SIZE);if (!buffer) {perror("内存分配失败");fclose(file);return 1;}srand(time(NULL));size_t total_written = 0;const size_t target_size = 1024 * 1024 * 100; // 100MBwhile (total_written < target_size) {// 生成随机数据for (size_t i = 0; i < CHUNK_SIZE; i++) {buffer[i] = rand() % 256;}size_t to_write = (target_size - total_written < CHUNK_SIZE) ? (target_size - total_written) : CHUNK_SIZE;size_t written = fwrite(buffer, 1, to_write, file);total_written += written;if (written < to_write) {perror("写入不完整");break;}printf("已写入: %.2f MB\r", (double)total_written / (1024 * 1024));fflush(stdout);}free(buffer);fclose(file);printf("\n写入完成,总计 %.2f MB\n", (double)total_written / (1024 * 1024));return 0;
}

四、关键注意事项与使用细节

1. 二进制模式 vs 文本模式

  • 二进制模式(“wb”):
    • 数据按原样写入,无任何转换
    • 适合图像、音频、视频等二进制文件
  • 文本模式(“w”):
    • Linux 中与二进制模式相同(无换行符转换)
    • Windows 中会转换换行符(\n\r\n

建议:处理非文本文件时始终使用二进制模式

2. 错误处理最佳实践

size_t items = fwrite(data, size, nmemb, file);if (items < nmemb) {if (ferror(file)) {perror("写入错误");// 处理具体错误}if (feof(file)) {// 在写入模式下通常不会发生,但应检查}
}

3. 数据对齐优化

对于高性能应用,确保数据对齐:

// 使用对齐分配
double *data = aligned_alloc(64, 1024 * sizeof(double)); // 64字节对齐
fwrite(data, sizeof(double), 1024, file);

4. 结构体写入的特殊考虑

struct Data {int a;double b;
};// 潜在问题:结构体可能有填充字节
fwrite(&data, sizeof(struct Data), 1, file);// 解决方案:序列化每个字段
fwrite(&data.a, sizeof(int), 1, file);
fwrite(&data.b, sizeof(double), 1, file);

5. 与 fread()配对使用

写入和读取应使用相同的参数:

// 写入
int numbers[] = {1, 2, 3, 4, 5};
fwrite(numbers, sizeof(int), 5, file);// 读取(必须匹配)
int read_nums[5];
fread(read_nums, sizeof(int), 5, file);

6. 大文件处理技巧

使用 ftell()fseek()处理大文件:

fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);// 计算需要写入的块数
size_t chunks = file_size / CHUNK_SIZE;
size_t remainder = file_size % CHUNK_SIZE;

五、性能优化技巧

1. 设置缓冲区大小

FILE *file = fopen("data.bin", "wb");
char custom_buffer[16 * 1024 * 1024]; // 16MB// 设置自定义缓冲区
setvbuf(file, custom_buffer, _IOFBF, sizeof(custom_buffer));

2. 直接 I/O(绕过内核缓存)

file = fopen("fast.dat", "wb");
posix_fadvise(fileno(file), 0, 0, POSIX_FADV_SEQUENTIAL);

3. 内存映射文件

对于超大文件写入,考虑使用 mmap

int fd = open("huge.dat", O_RDWR | O_CREAT, 0644);
ftruncate(fd, file_size); // 设置文件大小
void *map = mmap(NULL, file_size, PROT_WRITE, MAP_SHARED, fd, 0);// 直接写入内存
memcpy(map, data, data_size);
msync(map, data_size, MS_SYNC); // 确保写入磁盘

六、常见错误与解决方案

错误1:忽略返回值

// 错误做法
fwrite(data, size, nmemb, file);// 正确做法
size_t written = fwrite(data, size, nmemb, file);
if (written < nmemb) {// 处理部分写入或错误
}

错误2:大小计算错误

// 错误:混淆大小和数量
int data[100];
fwrite(data, 100, sizeof(int), file); // 参数顺序错误!// 正确
fwrite(data, sizeof(int), 100, file);

错误3:字节序问题

// 跨平台问题
uint32_t value = 0x12345678;
fwrite(&value, sizeof(uint32_t), 1, file);// 需要转换字节序
value = htonl(value); // 主机字节序转网络字节序

错误4:未刷新缓冲区

fwrite(data, size, nmemb, file);
// 程序崩溃或断电可能导致数据丢失// 解决方案
fflush(file); // 强制刷新缓冲区
fsync(fileno(file)); // 强制写入磁盘(Linux)

七、高级应用场景

1. 写入管道

FILE *pipe = popen("gzip > data.gz", "w");
if (pipe) {fwrite(data, 1, data_size, pipe);pclose(pipe);
}

2. 写入共享内存

int shm_fd = shm_open("/myshm", O_CREAT | O_RDWR, 0644);
ftruncate(shm_fd, SHM_SIZE);
void *shm_ptr = mmap(NULL, SHM_SIZE, PROT_WRITE, MAP_SHARED, shm_fd, 0);// 使用fwrite写入共享内存
FILE *shm_file = fdopen(shm_fd, "wb");
fwrite(data, 1, data_size, shm_file);

3. 加密写入

FILE *file = fopen("encrypted.dat", "wb");
unsigned char buffer[4096];while (data_remaining) {// 加密数据块encrypt_chunk(buffer, current_data);if (fwrite(buffer, 1, sizeof(buffer), file) != sizeof(buffer)) {perror("加密写入失败");break;}
}

八、总结

fwrite()是 Linux C 程序中高效文件写入的核心函数,关键要点:

  1. 参数顺序ptr, size, nmemb, stream
  2. 返回值:实际写入的完整项数(非字节数)
  3. 错误处理:必须检查返回值并配合 ferror()
  4. 二进制模式:非文本文件必须使用 “wb”
  5. 缓冲区管理:使用大缓冲区提高性能
  6. 大文件处理:分块写入避免内存不足
  7. 数据对齐:优化对齐提高写入速度
  8. 字节序:跨平台时注意字节序转换

fputs()函数

fputs()是 C 语言中用于高效写入字符串的核心函数,特别适合处理文本数据和日志记录。在 Linux 系统编程中,它是比 fprintf()更轻量级的字符串输出工具。

一、函数声明与核心概念

1. 函数声明

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

2. 作用

  • 将字符串写入文件流
  • 不添加换行符(与 puts()不同)
  • fprintf()更高效(无格式解析开销)

3. 返回值

  • 成功:返回非负整数(通常为 0 或正数)
  • 失败:返回 EOF(-1)

二、参数详解与使用示例

1. const char *s- 字符串指针

作用:要写入的以 \0结尾的字符串

要求

  • 必须是有效的 C 字符串(以 null 结尾)
  • 可以是字符串字面量或字符数组
  • 内容中可包含换行符(需手动添加)

示例

// 字符串字面量
fputs("Hello, Linux!\n", stdout);// 字符数组
char message[] = "System message\n";
fputs(message, logfile);// 动态字符串
char *dynamic_str = malloc(100);
strcpy(dynamic_str, "Dynamic content\n");
fputs(dynamic_str, file);

2. FILE *stream- 文件流指针

作用:指定写入目标

常用值

  • stdout:标准输出
  • stderr:标准错误
  • 文件指针:通过 fopen()打开的文件

示例

// 写入标准输出
fputs("Console output\n", stdout);// 写入错误流
fputs("Critical error!\n", stderr);// 写入文件
FILE *log = fopen("app.log", "a");
fputs("Log entry\n", log);

三、完整使用示例

示例1:基本字符串写入

#include <stdio.h>int main() {// 写入标准输出fputs("=== System Information ===\n", stdout);// 写入文件FILE *info = fopen("system.txt", "w");if (!info) {fputs("无法创建系统信息文件\n", stderr);return 1;}fputs("Operating System: Linux\n", info);fputs("Kernel Version: 5.15.0\n", info);fclose(info);return 0;
}

示例2:高效日志系统

#include <stdio.h>
#include <time.h>
#include <string.h>void log_message(FILE *stream, const char *level, const char *msg) {time_t now = time(NULL);char timestamp[20];strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&now));// 高效拼接日志信息fputs("[", stream);fputs(timestamp, stream);fputs("] [", stream);fputs(level, stream);fputs("] ", stream);fputs(msg, stream);fputs("\n", stream);// 立即刷新确保日志实时写入fflush(stream);
}int main() {FILE *logfile = fopen("application.log", "a");if (!logfile) {fputs("无法打开日志文件\n", stderr);return 1;}log_message(stdout, "INFO", "应用程序启动");log_message(logfile, "DEBUG", "初始化模块");// 模拟错误if (1) { // 假设发生错误log_message(stderr, "ERROR", "配置文件损坏");log_message(logfile, "ERROR", "配置文件损坏");}log_message(logfile, "INFO", "应用程序关闭");fclose(logfile);return 0;
}

示例3:文件复制工具

#include <stdio.h>
#include <stdlib.h>#define BUFFER_SIZE 4096int main(int argc, char *argv[]) {if (argc != 3) {fputs("用法: fcopy <源文件> <目标文件>\n", stderr);return 1;}FILE *src = fopen(argv[1], "r");if (!src) {fputs("无法打开源文件\n", stderr);return 1;}FILE *dest = fopen(argv[2], "w");if (!dest) {fputs("无法创建目标文件\n", stderr);fclose(src);return 1;}char buffer[BUFFER_SIZE];while (fgets(buffer, sizeof(buffer), src)) {// 使用fputs写入读取的行if (fputs(buffer, dest) == EOF) {fputs("写入文件失败\n", stderr);break;}}if (ferror(src)) {fputs("读取源文件时出错\n", stderr);}fclose(src);fclose(dest);return 0;
}

四、关键注意事项与使用细节

1. 换行符处理

fputs()不会自动添加换行符:

// 错误:输出将连在一起
fputs("Line 1", file);
fputs("Line 2", file);
// 结果: "Line1Line2"// 正确:手动添加换行
fputs("Line 1\n", file);
fputs("Line 2\n", file);

2. 错误处理

必须检查返回值:

if (fputs("重要数据", file) == EOF) {if (ferror(file)) {perror("写入失败");// 处理具体错误}
}

3. 性能优化

对于大量字符串输出:

// 设置大缓冲区
setvbuf(file, NULL, _IOFBF, 16384); // 16KB缓冲区// 批量写入
const char *lines[] = {"Line1\n", "Line2\n", "Line3\n"};
for (int i = 0; i < 3; i++) {fputs(lines[i], file);
}
// 而不是多次调用fflush

4. 二进制模式注意事项

在 Linux 中,文本模式和二进制模式无区别:

// Linux下两者行为相同
fputs("Text\n", file);       // 文本模式
fputs("Binary\n", file);     // 二进制模式

5. 特殊字符处理

fputs()会原样输出所有字符:

fputs("包含\0空字符的文本", file);
// 文件内容: "包含" + 空字符 + "空字符的文本"
// 注意:空字符会截断字符串显示,但实际数据完整

五、与 Linux 系统特性结合

1. 写入系统日志

#include <syslog.h>// 替代方案:直接使用syslog
openlog("myapp", LOG_PID, LOG_USER);
syslog(LOG_INFO, "系统事件发生");
closelog();

2. 写入命名管道

mkfifo("my_pipe", 0666);
FILE *pipe = fopen("my_pipe", "w");
fputs("管道数据\n", pipe);
fclose(pipe);

3. 写入 /dev/null

FILE *null = fopen("/dev/null", "w");
fputs("这些内容会被丢弃\n", null);
fclose(null);

六、常见错误与解决方案

错误1:忘记换行符

fputs("没有换行", stdout);
fputs("的文字会连在一起", stdout);
// 输出: "没有换行的文字会连在一起"

解决方案

fputs("正确添加换行\n", stdout);

错误2:使用未初始化的指针

char *uninit_ptr; // 未初始化
fputs(uninit_ptr, stdout); // 未定义行为!

解决方案

char *safe_ptr = "有效字符串";
fputs(safe_ptr, stdout);

错误3:写入只读文件

FILE *file = fopen("/etc/passwd", "w"); // 权限不足
if (file) {fputs("尝试写入", file); // 会失败
}

解决方案

FILE *file = fopen("userfile.txt", "w"); // 用户可写位置
if (file) {if (fputs("内容", file) == EOF) {perror("写入失败");}
}

错误4:缓冲区未刷新

fputs("重要日志", logfile);
// 程序崩溃,日志可能丢失

解决方案

fputs("安全日志", logfile);
fflush(logfile); // 确保立即写入

七、与相关函数对比

1. vs fprintf()

// fputs更高效
fputs("简单字符串\n", file);// fprintf更灵活但开销大
fprintf(file, "格式化: %d %.2f\n", 42, 3.14);

2. vs puts()

// puts自动添加换行
puts("自动换行"); // 输出后加\n// fputs需手动控制
fputs("手动换行\n", stdout);

3. vs fwrite()

// fwrite处理二进制数据
fwrite(data, 1, size, file);// fputs处理文本字符串
fputs("文本内容", file);

八、高级应用场景

1. 构建简单 HTTP 响应

void send_http_response(FILE *client) {fputs("HTTP/1.1 200 OK\r\n", client);fputs("Content-Type: text/html\r\n", client);fputs("\r\n", client); // 空行分隔头部和主体fputs("<html><body>Hello World</body></html>", client);
}

2. 生成 CSV 文件

void write_csv_row(FILE *file, const char *col1, const char *col2, int col3) {fputs("\"", file);fputs(col1, file);fputs("\",\"", file);fputs(col2, file);fputs("\",", file);// 转换数字char num_buf[20];snprintf(num_buf, sizeof(num_buf), "%d", col3);fputs(num_buf, file);fputs("\n", file);
}

3. 实现简单命令行界面

void display_menu() {fputs("\n==== 系统菜单 ====\n", stdout);fputs("1. 查看状态\n", stdout);fputs("2. 配置设置\n", stdout);fputs("3. 退出系统\n", stdout);fputs("选择: ", stdout);fflush(stdout);
}

九、总结

fputs()是 Linux C 编程中高效字符串输出的核心工具,关键要点:

  1. 轻量高效:无格式解析开销,性能优于 fprintf
  2. 精确控制:不自动添加换行符,需手动管理
  3. 错误处理:必须检查返回值和错误状态
  4. 适用场景:纯文本输出、日志系统、协议实现
  5. 性能优化:配合缓冲区设置提高吞吐量
  6. 系统集成:支持标准流、文件和特殊设备

fscanf()函数

fscanf()是 C 语言中用于从文件流读取格式化数据的核心函数,特别适合处理结构化文本数据。在 Linux 系统编程中,它是解析配置文件、日志文件和用户输入的强大工具。

一、函数声明与核心概念

1. 函数声明

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

2. 作用

  • 从文件流中读取格式化输入
  • 解析文本数据为指定类型(整数、浮点数、字符串等)
  • 支持复杂格式匹配和跳过不需要的数据

3. 返回值

  • 成功:返回成功匹配和赋值的输入项数量
  • 失败:返回 EOF(-1)或匹配项数量小于预期
  • 0:当没有项匹配时返回 0

二、参数详解与使用示例

1. FILE *stream- 文件流指针

作用:指定要读取的文件流

要求

  • 必须通过 fopen()打开的文件指针
  • 文件必须以可读模式打开(如 “r”, “r+”, “rb” 等)
  • 可以是标准输入流 stdin

示例

// 从标准输入读取
int age;
fscanf(stdin, "%d", &age);// 从文件读取
FILE *config = fopen("settings.cfg", "r");
float value;
fscanf(config, "%f", &value);

2. const char *format- 格式字符串

作用:控制输入解析的格式,包含:

  • 普通字符:必须精确匹配输入中的字符
  • 格式说明符:以 %开头,指定数据类型
  • 空白字符:匹配任意空白(空格、制表符、换行)

格式说明符语法

%[*][宽度][长度]类型
常用类型说明符:
说明符类型示例输入
%dint“42” → 42
%iint(自动检测进制)“0x2A” → 42
%uunsigned int“42” → 42
%ffloat“3.14” → 3.14
%lfdouble“3.14” → 3.14
%cchar“A” → ‘A’
%s字符串“Hello”
%[...]字符集匹配“[A-Z]”
%n已读取字符数不消耗输入
%%百分号字符“%”
特殊修饰符:
  • *:赋值抑制(读取但不存储)
  • 宽度:指定最大读取字符数
  • 长度h(short)、l(long)、ll(long long)

三、完整使用示例

示例1:基本数据类型读取

#include <stdio.h>int main() {FILE *file = fopen("data.txt", "r");if (!file) {perror("无法打开文件");return 1;}int id;char name[50];float score;// 解析格式:ID 姓名 分数int result = fscanf(file, "%d %49s %f", &id, name, &score);if (result == 3) {printf("ID: %d, 姓名: %s, 分数: %.2f\n", id, name, score);} else if (result == EOF) {printf("文件结束\n");} else {printf("解析失败,只匹配了 %d 项\n", result);}fclose(file);return 0;
}

示例2:复杂格式解析(日志文件)

#include <stdio.h>
#include <string.h>int main() {FILE *log = fopen("access.log", "r");if (!log) {perror("无法打开日志文件");return 1;}char ip[16];char timestamp[20];char method[10];char url[100];int status;int size;while (1) {// 解析格式:IP [时间] "方法 URL" 状态 大小int result = fscanf(log, "%15s - - [%19[^]]] \"%9s %99[^\"]\" %d %d",ip, timestamp, method, url, &status, &size);if (result == EOF) break;if (result == 6) {printf("%s %s %s %s %d %d\n", ip, timestamp, method, url, status, size);} else {// 跳过无效行char buffer[256];fgets(buffer, sizeof(buffer), log);}}fclose(log);return 0;
}

示例3:高级格式控制

#include <stdio.h>int main() {// 输入示例: "Product: ABC-123, Price: $19.99, Stock: 50"char product[10];int code;float price;int stock;char currency;// 使用 * 跳过不需要的部分int result = scanf("Product: %9[^-]-%d, Price: %*c%f, Stock: %d",product, &code, &price, &stock);if (result == 4) {printf("产品: %s-%d, 价格: %.2f, 库存: %d\n", product, code, price, stock);}return 0;
}

四、关键注意事项与使用细节

1. 缓冲区溢出防护

// 危险:无长度限制
char name[20];
fscanf(file, "%s", name); // 可能溢出!// 安全:指定最大宽度
fscanf(file, "%19s", name); // 读取最多19字符

2. 空白字符处理

fscanf()会跳过前导空白字符:

// 输入 "   42" 也能正确解析
int num;
fscanf(stdin, "%d", &num);

3. 输入流残留问题

当部分匹配失败时:

// 输入 "abc123"
int num;
if (fscanf(stdin, "%d", &num) != 1) {// 清除残留输入int c;while ((c = getchar()) != '\n' && c != EOF);
}

4. 文件结束检测

while (1) {int value;int result = fscanf(file, "%d", &value);if (result == EOF) {if (feof(file)) {printf("文件结束\n");} else if (ferror(file)) {perror("读取错误");}break;} else if (result == 1) {// 处理值} else {// 跳过无效数据fgetc(file);}
}

5. 字符集匹配

// 只读取大写字母
char uppercase[20];
fscanf(file, "%19[A-Z]", uppercase);// 读取除逗号外的所有字符
char field[50];
fscanf(file, "%49[^,]", field);

6. 使用 %n跟踪位置

int pos1, pos2;
char str[100];fscanf(file, "%s%n %*d%n", str, &pos1, &pos2);
printf("字符串结束位置: %d, 数字结束位置: %d\n", pos1, pos2);

五、高级应用场景

1. 解析 CSV 文件

#include <stdio.h>
#include <string.h>#define MAX_LINE 1024int main() {FILE *csv = fopen("data.csv", "r");if (!csv) return 1;char line[MAX_LINE];while (fgets(line, sizeof(line), csv)) {// 替换换行符line[strcspn(line, "\n")] = '\0';char *ptr = line;char field[100];int field_count = 0;while (*ptr) {// 处理引号包围的字段if (*ptr == '"') {int len;ptr++; // 跳过开头的引号sscanf(ptr, "%99[^\"]%n", field, &len);ptr += len;if (*ptr == '"') ptr++; // 跳过结尾的引号} else {sscanf(ptr, "%99[^,\n]%n", field, &len);ptr += len;}printf("字段 %d: %s\n", ++field_count, field);if (*ptr == ',') ptr++;}}fclose(csv);return 0;
}

2. 读取配置文件

#include <stdio.h>
#include <string.h>
#include <ctype.h>int main() {FILE *config = fopen("app.conf", "r");if (!config) return 1;char line[256];while (fgets(line, sizeof(line), config)) {// 跳过注释和空行if (line[0] == '#' || line[0] == '\n') continue;char key[50], value[100];if (sscanf(line, "%49s = %99[^\n]", key, value) == 2) {printf("配置项: %s = %s\n", key, value);}}fclose(config);return 0;
}

3. 解析二进制文件头

#include <stdio.h>
#include <stdint.h>typedef struct {char magic[4];uint32_t version;uint64_t timestamp;uint16_t checksum;
} FileHeader;int main() {FILE *bin = fopen("data.bin", "rb");if (!bin) return 1;FileHeader header;if (fread(&header, sizeof(FileHeader), 1, bin) != 1) {perror("读取文件头失败");fclose(bin);return 1;}// 验证魔数if (memcmp(header.magic, "BIN", 3) != 0) {fprintf(stderr, "无效文件格式\n");fclose(bin);return 1;}printf("版本: %u\n", header.version);printf("时间戳: %lu\n", header.timestamp);printf("校验和: %u\n", header.checksum);fclose(bin);return 0;
}

六、常见错误与解决方案

错误1:忘记地址运算符 &

int num;
fscanf(file, "%d", num); // 错误!缺少 &

解决方案

fscanf(file, "%d", &num); // 正确

错误2:缓冲区溢出

char city[20];
fscanf(file, "%s", city); // 可能溢出

解决方案

fscanf(file, "%19s", city); // 安全

错误3:格式字符串不匹配

// 输入 "42.5"
int num;
fscanf(file, "%d", &num); // 匹配失败

解决方案

float value;
fscanf(file, "%f", &value); // 正确

错误4:未处理返回值

fscanf(file, "%d", &num); // 忽略返回值

解决方案

if (fscanf(file, "%d", &num) != 1) {// 处理错误
}

七、性能优化技巧

1. 批量读取

// 一次读取多个值
int values[100];
for (int i = 0; i < 100; i++) {if (fscanf(file, "%d", &values[i]) != 1) {break;}
}

2. 使用 fgets()+ sscanf()

char line[256];
while (fgets(line, sizeof(line), file)) {int a, b;if (sscanf(line, "%d %d", &a, &b) == 2) {// 处理值}
}

3. 内存映射文件

#include <sys/mman.h>
#include <fcntl.h>int fd = open("large.txt", O_RDONLY);
size_t size = lseek(fd, 0, SEEK_END);
char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);char *ptr = map;
int count = 0;
while (ptr < map + size) {int num, n;if (sscanf(ptr, "%d%n", &num, &n) == 1) {count++;ptr += n;} else {ptr++;}
}munmap(map, size);
close(fd);

八、替代方案比较

1. vs fgets()+ sscanf()

// fscanf 直接读取
int num;
fscanf(file, "%d", &num);// fgets + sscanf
char line[100];
fgets(line, sizeof(line), file);
sscanf(line, "%d", &num);

2. vs getline()+ sscanf()

// POSIX getline
char *line = NULL;
size_t len = 0;
ssize_t read;while ((read = getline(&line, &len, file)) != -1) {int a, b;sscanf(line, "%d %d", &a, &b);
}
free(line);

3. vs 正则表达式

// 使用正则表达式库(更强大但更重)
#include <regex.h>regex_t regex;
regcomp(&regex, "([0-9]+) ([A-Z]+)", REG_EXTENDED);
regmatch_t matches[3];if (regexec(&regex, line, 3, matches, 0) == 0) {char num_str[20];strncpy(num_str, line + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so);int num = atoi(num_str);
}

九、总结

fscanf()是 Linux C 编程中强大的格式化输入工具,关键要点:

  1. 格式控制:使用 %格式说明符精确控制输入解析
  2. 安全防护:始终指定字符串读取的最大宽度
  3. 错误处理:必须检查返回值并处理部分匹配
  4. 高级特性:利用 %[]%n*实现复杂解析
  5. 性能优化:对于大文件,考虑 fgets()+ sscanf()组合
  6. 二进制文件:使用 fread()替代二进制数据读取
http://www.dtcms.com/a/432048.html

相关文章:

  • 大数据成矿预测系列(二) | 证据权重法如何克服自身局限?
  • 基于springboot的家校合作管理系统
  • 湖南网站建设 搜搜磐石网络网站首页图片素材
  • STM32控制开发学习笔记【基于STM32 HAL库】
  • 滕州做网站比较好的网络公司网页设计十大品牌
  • 【项目】基于Spring全家桶的论坛系统 【上】
  • 北京移动端网站公司基于PHP的家教网站开发环境
  • 网站毕设怎么做网站开发合同技术目标
  • 成都市建设网站首页kol营销
  • MySQL学习笔记07:MySQL SQL优化与EXPLAIN分析实战指南(上):执行计划深度解析
  • 产品经理指南:Vibes与AI提示词驱动短视频创新与Instagram优化
  • 手机上怎样制作网站广州市做网站公司
  • 数据要素X_第三批“数据要素×”典型案例——现代农业领域【附全文阅读】
  • 华容网站企业软件管家
  • 汽车可以做哪些广告视频网站南宁建站服务公司
  • 【代码随想录day 31】 力扣 56. 合并区间
  • 成都网站快速优化排名做app需要什么条件
  • 网站怎样做全国地区推广网站seo方案
  • 建站用wordpress 起飞了如何创建网站的步骤
  • 网站建设标新立异类似织梦的建站cms
  • 企业建立网站步骤深圳市9号文
  • 建站推广免费公司个人博客免费模板
  • 什么是营销型网站?哪个wordpress编辑器
  • 域名购买后如何建设网站免费制作网站平台
  • 安全电子商务网站设计所见即所得的网页设计软件
  • 项目实战5:聊天室
  • 网站建设图片流程图我的家乡网页制作素材
  • **全息显示技术的发散创新与深度探索**一、引言随着科技的飞速发展,全息显示技术已成为显示领域的一大研究热点。本文将带你
  • 旅游网站推广方案植物染企业解决方案
  • 深度学习基础知识-Transformer基础