1. 文件操作在C语言中的核心地位
文件操作是C语言从入门到精通的必经之路。很多初学者在掌握了基础语法后,往往会在文件操作这个环节遇到瓶颈。实际上,几乎所有实用的C语言程序都离不开文件操作——无论是简单的配置文件读取,还是复杂的数据持久化存储。
我在教学过程中发现,很多学员能够熟练使用printf和scanf,但一到文件读写就手足无措。这主要是因为文件操作涉及更多系统层面的概念,比如文件指针、缓冲区、流等。理解这些概念对后续学习网络编程、数据库操作等高级主题至关重要。
2. 文件操作基础回顾
2.1 文件指针与FILE结构体
在C语言中,文件操作的核心是FILE结构体和文件指针。FILE结构体包含了操作系统管理文件所需的所有信息,而文件指针(FILE *)则是我们操作文件的句柄。
c复制FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
perror("文件打开失败");
return -1;
}
这段代码展示了最基本的文件打开操作。值得注意的是,fopen的第二个参数决定了文件的打开模式。常见的模式有:
- "r":只读
- "w":只写(会清空文件)
- "a":追加
- "r+":读写(文件必须存在)
- "w+":读写(创建新文件或清空已有文件)
- "a+":读写(追加模式)
2.2 基本的文件读写函数
C标准库提供了一系列文件读写函数,最基础的有:
- fgetc/fputc:字符读写
- fgets/fputs:字符串读写
- fscanf/fprintf:格式化读写
- fread/fwrite:二进制读写
c复制// 写入示例
FILE *fp = fopen("data.txt", "w");
if (fp) {
fprintf(fp, "Hello, World!\n");
fclose(fp);
}
// 读取示例
char buffer[100];
fp = fopen("data.txt", "r");
if (fp) {
fgets(buffer, sizeof(buffer), fp);
printf("%s", buffer);
fclose(fp);
}
3. 文件操作的进阶技巧
3.1 二进制文件操作
二进制文件操作是很多实际项目中的必备技能。与文本文件不同,二进制文件可以保存任何类型的数据,包括结构体。
c复制typedef struct {
int id;
char name[50];
float score;
} Student;
// 写入二进制数据
Student s = {1, "张三", 89.5};
FILE *fp = fopen("students.dat", "wb");
if (fp) {
fwrite(&s, sizeof(Student), 1, fp);
fclose(fp);
}
// 读取二进制数据
Student read_s;
fp = fopen("students.dat", "rb");
if (fp) {
fread(&read_s, sizeof(Student), 1, fp);
printf("ID: %d, Name: %s, Score: %.1f\n",
read_s.id, read_s.name, read_s.score);
fclose(fp);
}
3.2 文件定位与随机访问
C语言提供了强大的文件定位功能,允许我们在文件中任意位置进行读写操作。
c复制// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 随机访问示例
fseek(fp, 100, SEEK_SET); // 移动到第100字节处
char data[50];
fread(data, 1, 50, fp); // 读取50字节
4. 文件操作中的常见问题与解决方案
4.1 文件打开失败的处理
文件操作中最常见的问题就是文件打开失败。正确处理这种情况对程序的健壮性至关重要。
c复制FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("错误信息");
// 或者使用strerror(errno)
printf("错误代码: %d\n", errno);
// 根据错误类型采取不同措施
if (errno == ENOENT) {
printf("文件不存在\n");
} else if (errno == EACCES) {
printf("权限不足\n");
}
return -1;
}
4.2 缓冲区与同步问题
文件操作中的另一个常见问题是缓冲区未及时刷新导致的数据丢失。
c复制// 不安全的写法
FILE *fp = fopen("data.txt", "w");
fprintf(fp, "重要数据");
// 如果程序在这里崩溃,数据可能不会写入文件
// 安全的写法
FILE *fp = fopen("data.txt", "w");
if (fp) {
fprintf(fp, "重要数据");
fflush(fp); // 立即刷新缓冲区
// 或者直接关闭文件
fclose(fp);
}
5. 高级文件操作技巧
5.1 内存映射文件
对于大文件操作,内存映射(MMAP)是一种高效的方式。它允许我们将文件直接映射到内存空间。
c复制#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int fd = open("largefile.dat", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
void *addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap失败");
close(fd);
return -1;
}
// 现在可以直接通过addr指针访问文件内容
char *data = (char *)addr;
printf("文件前10字节: %.10s\n", data);
munmap(addr, sb.st_size);
close(fd);
5.2 临时文件处理
很多程序需要创建临时文件,C标准库提供了安全创建临时文件的函数。
c复制// 创建临时文件
FILE *tmp = tmpfile();
if (tmp) {
fprintf(tmp, "临时数据");
rewind(tmp);
// 使用临时文件...
fclose(tmp); // 自动删除
}
// 创建临时文件名
char tmpname[L_tmpnam];
tmpnam(tmpname);
printf("临时文件名: %s\n", tmpname);
6. 文件操作的最佳实践
6.1 错误处理的统一模式
良好的错误处理是文件操作的关键。我推荐使用统一的错误处理模式:
c复制#define CHECK_FILE(fp, action) \
do { \
if ((fp) == NULL) { \
perror("文件操作失败"); \
action; \
} \
} while(0)
void process_file(const char *filename) {
FILE *fp = fopen(filename, "r");
CHECK_FILE(fp, return);
// 文件操作代码...
if (fclose(fp) == EOF) {
perror("文件关闭失败");
}
}
6.2 资源管理的RAII模式
虽然C语言没有内置的RAII机制,但我们可以模拟这种模式:
c复制typedef struct {
FILE *fp;
} FileHandle;
FileHandle file_open(const char *filename, const char *mode) {
FileHandle fh = {fopen(filename, mode)};
if (fh.fp == NULL) {
perror("文件打开失败");
}
return fh;
}
void file_close(FileHandle *fh) {
if (fh->fp) {
fclose(fh->fp);
fh->fp = NULL;
}
}
void process_with_file() {
FileHandle fh = file_open("data.txt", "r");
if (fh.fp == NULL) return;
// 使用fh.fp进行文件操作
file_close(&fh); // 确保资源释放
}
7. 实际项目中的文件操作案例
7.1 配置文件解析
配置文件解析是文件操作的典型应用场景。下面是一个简单的INI文件解析器实现:
c复制#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_LINE 256
typedef struct {
char key[50];
char value[100];
} ConfigItem;
ConfigItem *parse_config(const char *filename, int *count) {
FILE *fp = fopen(filename, "r");
if (!fp) return NULL;
ConfigItem *items = NULL;
int capacity = 10;
int size = 0;
items = malloc(capacity * sizeof(ConfigItem));
if (!items) {
fclose(fp);
return NULL;
}
char line[MAX_LINE];
while (fgets(line, sizeof(line), fp)) {
// 跳过注释和空行
if (line[0] == '#' || line[0] == '\n') continue;
// 解析key=value格式
char *eq = strchr(line, '=');
if (eq) {
*eq = '\0';
char *key = line;
char *value = eq + 1;
// 去除value的换行符
value[strcspn(value, "\n")] = '\0';
if (size >= capacity) {
capacity *= 2;
ConfigItem *new_items = realloc(items, capacity * sizeof(ConfigItem));
if (!new_items) break;
items = new_items;
}
strncpy(items[size].key, key, sizeof(items[size].key) - 1);
strncpy(items[size].value, value, sizeof(items[size].value) - 1);
size++;
}
}
fclose(fp);
*count = size;
return items;
}
7.2 日志系统实现
日志系统是另一个常见的文件操作应用。下面是一个简单的日志系统实现:
c复制#include <stdio.h>
#include <time.h>
#include <stdarg.h>
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
} LogLevel;
void log_message(LogLevel level, const char *format, ...) {
static const char *level_names[] = {
"DEBUG", "INFO", "WARNING", "ERROR"
};
static FILE *log_file = NULL;
if (log_file == NULL) {
log_file = fopen("app.log", "a");
if (log_file == NULL) {
return; // 日志文件无法打开
}
setvbuf(log_file, NULL, _IOLBF, 0); // 行缓冲
}
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(log_file, "[%s] [%s] ", time_str, level_names[level]);
va_list args;
va_start(args, format);
vfprintf(log_file, format, args);
va_end(args);
fprintf(log_file, "\n");
}
// 使用示例
int main() {
log_message(LOG_INFO, "应用程序启动");
log_message(LOG_WARNING, "内存使用量接近上限: %dMB", 950);
log_message(LOG_ERROR, "无法连接到数据库");
return 0;
}
8. 性能优化与安全考量
8.1 文件操作性能优化
文件操作往往是程序性能的瓶颈之一。以下是一些优化技巧:
- 缓冲区大小调整:默认的缓冲区大小可能不适合你的应用场景。
c复制FILE *fp = fopen("largefile.dat", "rb");
if (fp) {
char buffer[8192]; // 8KB缓冲区
setvbuf(fp, buffer, _IOFBF, sizeof(buffer));
// 后续文件操作...
fclose(fp);
}
- 批量读写:减少系统调用次数可以显著提高性能。
c复制// 低效的方式
for (int i = 0; i < 1000; i++) {
fputc('a', fp);
}
// 高效的方式
char data[1000];
memset(data, 'a', sizeof(data));
fwrite(data, 1, sizeof(data), fp);
8.2 文件操作安全注意事项
文件操作中的安全问题不容忽视:
- 路径遍历攻击防护:
c复制// 不安全的做法
char filename[100];
sprintf(filename, "/data/%s", user_input); // 用户可能输入"../../etc/passwd"
// 安全的做法
#include <libgen.h>
char *safe_basename(const char *path) {
char *copy = strdup(path);
if (!copy) return NULL;
char *base = basename(copy);
char *result = strdup(base);
free(copy);
return result;
}
- 竞争条件防护:
c复制// 不安全的文件创建
FILE *fp = fopen("data.tmp", "wx"); // C11标准新增的独占模式
if (fp == NULL && errno == EEXIST) {
// 文件已存在
}
// 或者使用更可靠的方式
int fd = open("data.tmp", O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd == -1) {
perror("文件已存在或无法创建");
} else {
FILE *fp = fdopen(fd, "w");
if (fp) {
// 安全的文件操作...
fclose(fp);
}
}
9. 跨平台文件操作注意事项
不同操作系统对文件系统的实现有差异,编写跨平台代码时需要注意:
- 路径分隔符:
c复制#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
void join_path(char *dest, const char *dir, const char *file) {
size_t dir_len = strlen(dir);
strcpy(dest, dir);
if (dir_len > 0 && dest[dir_len-1] != PATH_SEPARATOR) {
dest[dir_len] = PATH_SEPARATOR;
dest[dir_len+1] = '\0';
}
strcat(dest, file);
}
- 文件权限:
c复制int set_file_permissions(const char *filename, int mode) {
#ifdef _WIN32
return _chmod(filename, mode);
#else
return chmod(filename, mode);
#endif
}
- 文件锁定:
c复制int lock_file(FILE *fp) {
#ifdef _WIN32
return _locking(fileno(fp), LK_LOCK, 0);
#else
struct flock fl = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0 // 锁定整个文件
};
return fcntl(fileno(fp), F_SETLKW, &fl);
#endif
}
10. 现代C语言中的文件操作新特性
C11标准引入了一些新的文件操作特性:
- 安全版本的文件操作函数:
c复制#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
void safe_file_ops() {
FILE *fp = NULL;
errno_t err = fopen_s(&fp, "data.txt", "r");
if (err != 0) {
printf("文件打开失败,错误码: %d\n", err);
return;
}
char buffer[100];
if (fgets_s(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
}
- 二进制流的Unicode支持:
c复制void write_unicode() {
FILE *fp = fopen("unicode.txt", "w, ccs=UTF-8");
if (fp) {
fwprintf(fp, L"这是一个Unicode字符串: 你好世界\n");
fclose(fp);
}
}
- 临时文件的安全创建:
c复制void safe_temp_file() {
char tmp_name[L_tmpnam_s];
if (tmpnam_s(tmp_name, sizeof(tmp_name)) == 0) {
FILE *fp = fopen(tmp_name, "w");
if (fp) {
fprintf(fp, "临时文件内容\n");
fclose(fp);
remove(tmp_name); // 使用后删除
}
}
}
11. 文件系统监控与事件处理
在实际应用中,经常需要监控文件系统的变化。虽然标准C库没有直接提供这种功能,但可以通过平台特定的API实现:
11.1 Linux/inotify示例
c复制#ifdef __linux__
#include <sys/inotify.h>
void monitor_directory(const char *path) {
int fd = inotify_init();
if (fd == -1) {
perror("inotify_init");
return;
}
int wd = inotify_add_watch(fd, path,
IN_MODIFY | IN_CREATE | IN_DELETE);
if (wd == -1) {
perror("inotify_add_watch");
close(fd);
return;
}
char buffer[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
while (1) {
ssize_t len = read(fd, buffer, sizeof(buffer));
if (len == -1) {
perror("read");
break;
}
const struct inotify_event *event;
for (char *ptr = buffer; ptr < buffer + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *) ptr;
if (event->mask & IN_CREATE) {
printf("文件创建: %s\n", event->name);
} else if (event->mask & IN_DELETE) {
printf("文件删除: %s\n", event->name);
} else if (event->mask & IN_MODIFY) {
printf("文件修改: %s\n", event->name);
}
}
}
close(fd);
}
#endif
11.2 Windows/ReadDirectoryChangesW示例
c复制#ifdef _WIN32
#include <windows.h>
void monitor_directory_win(const char *path) {
HANDLE hDir = CreateFileA(
path,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if (hDir == INVALID_HANDLE_VALUE) {
printf("CreateFile failed (%d)\n", GetLastError());
return;
}
char buffer[4096];
DWORD bytesReturned;
while (ReadDirectoryChangesW(
hDir,
buffer,
sizeof(buffer),
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
&bytesReturned,
NULL,
NULL
)) {
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *)buffer;
while (1) {
wchar_t filename[MAX_PATH];
memcpy(filename, info->FileName, info->FileNameLength);
filename[info->FileNameLength / sizeof(wchar_t)] = L'\0';
switch (info->Action) {
case FILE_ACTION_ADDED:
wprintf(L"文件添加: %s\n", filename);
break;
case FILE_ACTION_REMOVED:
wprintf(L"文件删除: %s\n", filename);
break;
case FILE_ACTION_MODIFIED:
wprintf(L"文件修改: %s\n", filename);
break;
case FILE_ACTION_RENAMED_OLD_NAME:
wprintf(L"文件重命名(旧): %s\n", filename);
break;
case FILE_ACTION_RENAMED_NEW_NAME:
wprintf(L"文件重命名(新): %s\n", filename);
break;
}
if (info->NextEntryOffset == 0) break;
info = (FILE_NOTIFY_INFORMATION *)((char *)info + info->NextEntryOffset);
}
}
CloseHandle(hDir);
}
#endif
12. 文件压缩与归档处理
在实际项目中,经常需要处理压缩文件。虽然标准C库不直接支持压缩,但可以通过第三方库实现:
12.1 使用zlib进行gzip压缩/解压
c复制#include <zlib.h>
int compress_file(const char *source, const char *dest) {
FILE *src = fopen(source, "rb");
if (!src) return -1;
gzFile dst = gzopen(dest, "wb");
if (!dst) {
fclose(src);
return -1;
}
char buffer[8192];
int bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src)) > 0) {
if (gzwrite(dst, buffer, bytes_read) != bytes_read) {
fclose(src);
gzclose(dst);
return -1;
}
}
fclose(src);
gzclose(dst);
return 0;
}
int decompress_file(const char *source, const char *dest) {
gzFile src = gzopen(source, "rb");
if (!src) return -1;
FILE *dst = fopen(dest, "wb");
if (!dst) {
gzclose(src);
return -1;
}
char buffer[8192];
int bytes_read;
while ((bytes_read = gzread(src, buffer, sizeof(buffer))) > 0) {
if (fwrite(buffer, 1, bytes_read, dst) != bytes_read) {
gzclose(src);
fclose(dst);
return -1;
}
}
gzclose(src);
fclose(dst);
return 0;
}
12.2 简单的tar格式处理
c复制#pragma pack(push, 1)
typedef struct {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char checksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12];
} TarHeader;
#pragma pack(pop)
void extract_tar(const char *filename) {
FILE *fp = fopen(filename, "rb");
if (!fp) return;
TarHeader header;
while (fread(&header, sizeof(header), 1, fp) == 1) {
if (header.name[0] == '\0') break;
long size = strtol(header.size, NULL, 8);
if (size <= 0) continue;
printf("提取文件: %s (%ld 字节)\n", header.name, size);
FILE *out = fopen(header.name, "wb");
if (!out) {
printf("无法创建文件: %s\n", header.name);
fseek(fp, (size + 511) & ~511, SEEK_CUR);
continue;
}
char buffer[512];
long remaining = size;
while (remaining > 0) {
int chunk = remaining > sizeof(buffer) ? sizeof(buffer) : remaining;
if (fread(buffer, 1, chunk, fp) != chunk) {
printf("读取错误\n");
break;
}
fwrite(buffer, 1, chunk, out);
remaining -= chunk;
}
fclose(out);
// 跳过填充字节
if (size % 512 != 0) {
fseek(fp, 512 - (size % 512), SEEK_CUR);
}
}
fclose(fp);
}
13. 文件操作中的多线程处理
在多线程环境下操作文件需要特别注意同步问题:
13.1 线程安全的日志系统
c复制#include <pthread.h>
typedef struct {
FILE *fp;
pthread_mutex_t lock;
} ThreadSafeFile;
ThreadSafeFile *tsf_open(const char *filename, const char *mode) {
ThreadSafeFile *tsf = malloc(sizeof(ThreadSafeFile));
if (!tsf) return NULL;
tsf->fp = fopen(filename, mode);
if (!tsf->fp) {
free(tsf);
return NULL;
}
pthread_mutex_init(&tsf->lock, NULL);
return tsf;
}
void tsf_write(ThreadSafeFile *tsf, const char *data) {
pthread_mutex_lock(&tsf->lock);
fputs(data, tsf->fp);
pthread_mutex_unlock(&tsf->lock);
}
void tsf_close(ThreadSafeFile *tsf) {
if (tsf) {
pthread_mutex_lock(&tsf->lock);
if (tsf->fp) fclose(tsf->fp);
pthread_mutex_unlock(&tsf->lock);
pthread_mutex_destroy(&tsf->lock);
free(tsf);
}
}
13.2 多线程文件处理示例
c复制typedef struct {
const char *filename;
int start_offset;
int chunk_size;
pthread_mutex_t *output_mutex;
FILE *output;
} ThreadData;
void *process_file_chunk(void *arg) {
ThreadData *data = (ThreadData *)arg;
FILE *input = fopen(data->filename, "rb");
if (!input) return NULL;
fseek(input, data->start_offset, SEEK_SET);
char *buffer = malloc(data->chunk_size);
if (!buffer) {
fclose(input);
return NULL;
}
size_t read = fread(buffer, 1, data->chunk_size, input);
fclose(input);
// 处理数据...
pthread_mutex_lock(data->output_mutex);
fwrite(buffer, 1, read, data->output);
pthread_mutex_unlock(data->output_mutex);
free(buffer);
return NULL;
}
void parallel_file_process(const char *input_file, const char *output_file, int thread_count) {
FILE *input = fopen(input_file, "rb");
if (!input) return;
fseek(input, 0, SEEK_END);
long file_size = ftell(input);
fclose(input);
long chunk_size = file_size / thread_count;
pthread_mutex_t output_mutex;
pthread_mutex_init(&output_mutex, NULL);
FILE *output = fopen(output_file, "wb");
if (!output) {
pthread_mutex_destroy(&output_mutex);
return;
}
pthread_t threads[thread_count];
ThreadData thread_data[thread_count];
for (int i = 0; i < thread_count; i++) {
thread_data[i].filename = input_file;
thread_data[i].start_offset = i * chunk_size;
thread_data[i].chunk_size = (i == thread_count - 1) ?
(file_size - i * chunk_size) : chunk_size;
thread_data[i].output_mutex = &output_mutex;
thread_data[i].output = output;
pthread_create(&threads[i], NULL, process_file_chunk, &thread_data[i]);
}
for (int i = 0; i < thread_count; i++) {
pthread_join(threads[i], NULL);
}
fclose(output);
pthread_mutex_destroy(&output_mutex);
}
14. 文件操作与数据库交互
在实际应用中,文件操作经常与数据库交互结合使用:
14.1 将CSV文件导入数据库
c复制#include <sqlite3.h>
int import_csv_to_sqlite(const char *csv_file, const char *db_file, const char *table_name) {
sqlite3 *db;
if (sqlite3_open(db_file, &db) != SQLITE_OK) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
return -1;
}
FILE *fp = fopen(csv_file, "r");
if (!fp) {
sqlite3_close(db);
return -1;
}
char line[1024];
int first_line = 1;
char *columns = NULL;
while (fgets(line, sizeof(line), fp)) {
// 去除换行符
line[strcspn(line, "\n")] = '\0';
if (first_line) {
// 第一行是列名
columns = strdup(line);
first_line = 0;
// 创建表
char create_sql[2048];
snprintf(create_sql, sizeof(create_sql),
"CREATE TABLE IF NOT EXISTS %s (%s);", table_name, columns);
if (sqlite3_exec(db, create_sql, NULL, NULL, NULL) != SQLITE_OK) {
fprintf(stderr, "创建表失败: %s\n", sqlite3_errmsg(db));
free(columns);
fclose(fp);
sqlite3_close(db);
return -1;
}
} else {
// 插入数据
char insert_sql[4096];
snprintf(insert_sql, sizeof(insert_sql),
"INSERT INTO %s (%s) VALUES (%s);", table_name, columns, line);
if (sqlite3_exec(db, insert_sql, NULL, NULL, NULL) != SQLITE_OK) {
fprintf(stderr, "插入数据失败: %s\n", sqlite3_errmsg(db));
}
}
}
free(columns);
fclose(fp);
sqlite3_close(db);
return 0;
}
14.2 将数据库查询结果导出到文件
c复制int export_sqlite_to_csv(const char *db_file, const char *table_name, const char *csv_file) {
sqlite3 *db;
if (sqlite3_open(db_file, &db) != SQLITE_OK) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
return -1;
}
FILE *fp = fopen(csv_file, "w");
if (!fp) {
sqlite3_close(db);
return -1;
}
// 获取列名
sqlite3_stmt *stmt;
char query[256];
snprintf(query, sizeof(query), "SELECT * FROM %s LIMIT 0;", table_name);
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "查询失败: %s\n", sqlite3_errmsg(db));
fclose(fp);
sqlite3_close(db);
return -1;
}
int column_count = sqlite3_column_count(stmt);
// 写入列名
for (int i = 0; i < column_count; i++) {
if (i > 0) fputc(',', fp);
fputs(sqlite3_column_name(stmt, i), fp);
}
fputc('\n', fp);
sqlite3_finalize(stmt);
// 查询数据
snprintf(query, sizeof(query), "SELECT * FROM %s;", table_name);
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "查询失败: %s\n", sqlite3_errmsg(db));
fclose(fp);
sqlite3_close(db);
return -1;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
for (int i = 0; i < column_count; i++) {
if (i > 0) fputc(',', fp);
const char *text = (const char *)sqlite3_column_text(stmt, i);
if (text) {
fputs(text, fp);
}
}
fputc('\n', fp);
}
sqlite3_finalize(stmt);
fclose(fp);
sqlite3_close(db);
return 0;
}
15. 文件操作的高级应用:内存文件系统
在某些场景下,我们需要在内存中模拟文件系统:
15.1 使用内存缓冲区模拟文件
c复制#include <stdio.h>
typedef struct {
char *buffer;
size_t size;
size_t pos;
} MemoryFile;
static int mem_read(void *cookie, char *buf, int size) {
MemoryFile *mf = (MemoryFile *)cookie;
int remaining = mf->size - mf->pos;
if (size > remaining) size = remaining;
memcpy(buf, mf->buffer + mf->pos, size);
mf->pos += size;
return size;
}
static int mem_write(void *cookie, const char *buf, int size) {
MemoryFile *mf = (MemoryFile *)cookie;
if (mf->pos + size > mf->size) {
// 扩展缓冲区
size_t new_size = mf->pos + size;
char *new_buffer = realloc(mf->buffer, new_size);
if (!new_buffer) return -1;
mf->buffer = new_buffer;
mf->size = new_size;
}
memcpy(mf->buffer + mf->pos, buf, size);
mf->pos += size;
return size;
}
static fpos_t mem_seek(void *cookie, fpos_t offset, int whence) {
MemoryFile *mf = (MemoryFile *)cookie;
switch (whence) {
case SEEK_SET: mf->pos = offset; break;
case SEEK_CUR: mf->pos += offset; break;
case SEEK_END: mf->pos = mf->size + offset; break;
default: return -1;
}
return mf->pos;
}
static int mem_close(void *cookie) {
MemoryFile *mf = (MemoryFile *)cookie;
free(mf->buffer);
free(mf);
return 0;
}
FILE *fmemopen_wrapper(const char *initial_data, size_t initial_size) {
MemoryFile *mf = malloc(sizeof(MemoryFile));
if (!mf) return NULL;
mf->buffer = malloc(initial_size);
if (!mf->buffer) {
free(mf);
return NULL;
}
if (initial_data) {
memcpy(mf->buffer, initial_data, initial_size);
}
mf->size = initial_size;
mf->pos = 0;
// 创建自定义文件流
FILE *fp = funopen(mf, mem_read, mem_write, mem_seek, mem_close);
if (!fp) {
free(mf->buffer);
free(mf);
return NULL;
}
return fp;
}
15.2 使用内存文件系统进行测试
c复制void test_memory_file() {
// 创建内存文件并写入数据
FILE *fp = fmemopen_wrapper(NULL, 0);
if (!fp) {
printf("无法创建内存文件\n");
return;
}
fprintf(fp, "这是一段测试数据\n");
fprintf(fp, "第二行内容\n");
// 读取内存文件内容