C语言的管理数据库完整的小型系统示例:
#include // 引入标准输入输出库,提供printf等功能
#include // 引入断言库,用于调试时检查条件
#include // 引入标准库,提供malloc、free、exit等功能
#include // 引入错误号库,用于获取系统调用的错误号
#include // 引入字符串处理库,提供strncpy等字符串操作函数
#define MAX_DATA 512 // 定义常量MAX_DATA为512,用于指定名字和邮件的最大长度
#define MAX_ROWS 100 // 定义常量MAX_ROWS为100,表示数据库最多可以有100条记录
// 地址结构体,表示每条记录
struct Address {
int id; // 记录的唯一标识符
int set; // 标记记录是否已被设置,0表示未设置,1表示已设置
char name[MAX_DATA]; // 存储联系人姓名的数组,最大长度为MAX_DATA
char email[MAX_DATA];// 存储联系人电子邮件的数组,最大长度为MAX_DATA
};
// 数据库结构体,表示整个数据库
struct Database {
struct Address rows[MAX_ROWS]; // 数组存储每一条记录,最多MAX_ROWS条记录
};
// 连接结构体,用于表示数据库连接,包括文件和数据库内存数据
struct Connection {
FILE *file; // 文件指针,指向数据库文件
struct Database *db; // 指向内存中数据库数据的指针
};
// 错误处理函数,打印错误信息并终止程序
void die(const char *message)
{
if (errno) { // 如果有错误号
perror(message); // 打印系统错误信息
} else {
printf("ERROR: %s\n", message); // 否则打印自定义的错误消息
}
exit(1); // 退出程序
}
// 打印地址(联系人的)信息
void Address_print(struct Address *addr)
{
printf("%d %s %s\n", addr->id, addr->name, addr->email); // 打印id、name和email
}
// 从文件中加载数据库
void Database_load(struct Connection *conn)
{
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file); // 从文件读取数据库
if (rc != 1) die("Failed to load database."); // 如果读取失败,调用die函数输出错误信息并退出
}
// 打开数据库文件,返回连接对象
struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection)); // 为Connection结构体分配内存
if (!conn) die("Memory error"); // 如果内存分配失败,调用die函数
conn->db = malloc(sizeof(struct Database)); // 为Database结构体分配内存
if (!conn->db) die("Memory error"); // 如果内存分配失败,调用die函数
if (mode == 'c') { // 如果是创建模式,打开文件进行写操作
conn->file = fopen(filename, "w");
} else { // 如果是读取模式,打开文件进行读写操作
conn->file = fopen(filename, "r+");
if (conn->file) { // 如果文件打开成功
Database_load(conn); // 从文件加载数据库
}
}
if (!conn->file) die("Failed to open the file"); // 如果文件无法打开,调用die函数退出
return conn; // 返回连接对象
}
// 关闭数据库连接并释放相关资源
void Database_close(struct Connection *conn)
{
if (conn) { // 如果连接对象不为空
if (conn->file) fclose(conn->file); // 关闭文件
if (conn->db) free(conn->db); // 释放数据库内存
free(conn); // 释放连接对象内存
}
}
// 将数据库内容写入文件
void Database_write(struct Connection *conn)
{
rewind(conn->file); // 将文件指针回到文件开头
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file); // 将数据库内容写入文件
if (rc != 1) die("Failed to write database."); // 如果写入失败,调用die函数
rc = fflush(conn->file); // 刷新文件流,确保所有数据都写入文件
if (rc == -1) die("Cannot flush database."); // 如果刷新失败,调用die函数
}
// 创建数据库,初始化每条记录
void Database_create(struct Connection *conn)
{
int i = 0;
for (i = 0; i < MAX_ROWS; i++) {
struct Address addr = {.id = i, .set = 0}; // 初始化每条记录,id为i,set为0
conn->db->rows[i] = addr; // 将初始化的记录赋值给数据库的相应位置
}
}
// 设置数据库某条记录的信息
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
struct Address *addr = &conn->db->rows[id]; // 获取指定id的记录
if (addr->set) die("Already set, delete it first"); // 如果记录已设置,则报错
addr->set = 1; // 设置记录标记为已设置
// 复制名字到记录
char *res = strncpy(addr->name, name, MAX_DATA);
if (!res) die("Name copy failed"); // 如果复制失败,调用die函数
// 复制电子邮件到记录
res = strncpy(addr->email, email, MAX_DATA);
if (!res) die("Email copy failed"); // 如果复制失败,调用die函数
}
// 获取并打印指定id的记录
void Database_get(struct Connection *conn, int id)
{
struct Address *addr = &conn->db->rows[id]; // 获取指定id的记录
if (addr->set) { // 如果记录已设置
Address_print(addr); // 打印记录
} else {
die("ID is not set"); // 如果记录未设置,则报错
}
}
// 删除指定id的记录
void Database_delete(struct Connection *conn, int id)
{
struct Address addr = {.id = id, .set = 0}; // 初始化一个删除的记录,id为id,set为0
conn->db->rows[id] = addr; // 将该记录写入数据库
}
// 列出所有已设置的记录
void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;
for (i = 0; i < MAX_ROWS; i++) {
struct Address *cur = &db->rows[i]; // 获取当前记录
if (cur->set) { // 如果记录已设置
Address_print(cur); // 打印记录
}
}
}
// 主函数:根据命令行参数执行相应的数据库操作
int main(int argc, char *argv[])
{
if (argc < 3) die("USAGE: ex17 [action params]"); // 如果参数不足,报错并退出
char *filename = argv[1]; // 获取数据库文件名
char action = argv[2][0]; // 获取操作类型(c=create, g=get, s=set, d=del, l=list)
struct Connection *conn = Database_open(filename, action); // 打开数据库文件并返回连接对象
int id = 0;
if (argc > 3) id = atoi(argv[3]); // 如果有id参数,转换为整数
if (id >= MAX_ROWS) die("There's not that many records."); // 如果id超过最大记录数,报错并退出
switch (action) {
case 'c': // 如果操作类型是创建数据库
Database_create(conn); // 创建数据库
Database_write(conn); // 写入文件
break;
case 'g': // 如果操作类型是获取记录
if (argc != 4) die("Need an id to get"); // 如果缺少id参数,报错
Database_get(conn, id); // 获取并打印指定id的记录
break;
case 's': // 如果操作类型是设置记录
if (argc != 6) die("Need id, name, email to set"); // 如果缺少参数,报错
Database_set(conn, id, argv[4], argv[5]); // 设置指定id的记录
Database_write(conn); // 写入文件
break;
case 'd': // 如果操作类型是删除记录
if (argc != 4) die("Need id to delete"); // 如果缺少id参数,报错
Database_delete(conn, id); // 删除指定id的记录
Database_write(conn); // 写入文件
break;
case 'l': // 如果操作类型是列出记录
Database_list(conn); // 列出所有已设置的记录
break;
default: // 如果操作类型无效,报错
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
}
Database_close(conn); // 关闭数据库连接并释放资源
return 0; // 返回0表示程序正常结束
}
1.fopen
函数FILE *fopen(const char *filename, const char *mode);
功能:fopen
是 C 标准库中用于打开文件的函数,用于指定文件的打开模式,并返回一个指向文件的指针,允许程序读写文件
参数:
filename:要打开的文件名(包括路径,如果文件不在当前目录)。
mode:打开文件的模式,指定如何访问文件。
返回值: 如果文件成功打开,返回指向文件的 FILE 指针,后续的文件操作都通过这个指针进行。 如果文件无法打开(例如文件不存在,权限不足等),返回 NULL,此时可以通过 errno 或 perror 获取错误信息。
fread
和 fwrite
函数int fread(void *ptr, size_t size, size_t count, FILE *stream);
功能:从指定的文件流 stream 中读取 count 个对象,每个对象大小为 size 字节,并将它们存储在 ptr 指向的内存区域中。
参数:
ptr:指向存储读取数据的缓冲区的指针。
size:每个对象的大小(以字节为单位)。
count:要读取的对象数量。
stream:指向 FILE 类型的文件指针,指定要读取的文件。
返回值:返回实际读取的对象数量。如果返回值小于 count,则表示发生了错误或文件结束。
int fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
功能:向指定的文件流 stream 写入数据。写入 count 个对象,每个对象大小为 size 字节,数据来自 ptr 指向的内存区域。
参数:
ptr:指向要写入数据的缓冲区的指针。
size:每个对象的大小(以字节为单位)。
count:要写入的对象数量。
stream:指向 FILE 类型的文件指针,指定要写入的文件。
返回值:返回实际写入的对象数量。如果返回值小于 count,则表示发生了错误。
rewind
函数void rewind(FILE *stream);
功能:将文件指针 stream 移动到文件的开头。
参数:
stream:指向要操作的文件流。
返回值:没有返回值。成功时,文件指针指向文件的起始位置。如果发生错误,ferror 会返回一个非零值。
fflush
函数int fflush(FILE *stream);
功能:刷新输出缓冲区,将缓冲区中的数据强制写入到文件中。
参数:
stream:指向要刷新的文件流。如果 stream 为 NULL,则刷新所有输出流的缓冲区。
返回值: 如果成功,返回 0。 如果发生错误,返回 EOF,并设置 errno 来指示错误原因。
errno 是一个全局变量,定义在
作用:每当某个函数(特别是系统调用或库函数)失败时,它会将一个错误代码存储在 errno 中。该错误代码是一个整数,代表具体的错误类型。
errno 的值:它的值会随着错误发生而改变。因此,在调用可能设置 errno 的函数后,需要检查其值并根据错误代码提供相应的处理。
常见的 errno 错误代码:
EINVAL:无效的参数。
ENOENT:没有该文件或目录。
ENOMEM:内存不足。
EACCES:权限拒绝。
EIO:输入输出错误。
EPERM:操作不允许。
EIO:硬件错误。
示例:
#include
#include
#include
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
// 打印错误代码
printf("Error code: %d\n", errno);
// 打印错误描述
printf("Error description: %s\n", strerror(errno));
}
return 0;
}
perror
是 C 标准库中用于打印 errno
的错误信息的函数,它会根据 errno
中的错误代码输出相应的错误描述。它输出的信息通常包括用户自定义的前缀字符串(可选)和错误消息。
函数原型:
void perror(const char *s);
参数 s:是用户提供的字符串,如果提供了,perror 会先输出该字符串,再输出对应的错误描述;如果不提供,直接输出错误描述。
输出:perror 会输出一个标准的错误信息,格式为:
堆就是你电脑中的剩余内存,你可以通过malloc
访问它来获取更多内存,OS会使用内部函数为你注册一块内存区域,并且返回指向它的指针。当你使用完这片区域时,你应该使用free
把它交还给OS,使之能被其它程序复用。如果你不这样做就会导致程序“泄露”内存,但是Valgrind
会帮你监测这些内存泄露。
栈是一个特殊的内存区域,它储存了每个函数的创建的临时变量,它们对于该函数为局部变量。它的工作机制是,函数的每个函数都会“压入”栈中,并且可在函数内部使用。它是一个真正的栈数据结构,所以是后进先出的。这对于main
中所有类似char section
和int id
的局部变量也是相同的。使用栈的优点是,当函数退出时C编译器会从栈中“弹出”所有变量来清理。这非常简单,也防止了栈上变量的内存泄露。
理清内存的最简单的方式是遵守这条原则:如果你的变量并不是从malloc
中获取的,也不是从一个从malloc
获取的函数中获取的,那么它在栈上。
而除了堆和栈之外,还有数据段(Data Segment),用于存储程序中的全局变量、静态变量和常量数据,分为:已初始化数据段(存储已初始化的全局变量和静态变量)未初始化数据段(BSS段)存储未初始化的全局变量和静态变量,程序运行时会将它们初始化为 0。特点:数据段在程序加载时会被加载到内存中,并且生命周期与程序相同
die
函数需要接收conn
变量作为参数,以便执行清理并关闭它。 void die(struct Connection *conn,const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}
Database_close(conn);
exit(1);
}
MAX_DATA
和MAX_ROWS
,将它们储存在Database
结构体中,并且将它们写到文件。这样就可以创建任意大小的数据库。// 修改struct Database
struct Database {
int max_data; // 动态存储最大数据大小
int max_rows; // 动态存储最大行数
struct Address *rows; // 动态分配存储的地址
};
// 修改struct Connection *Database_open函数
struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows)
{
struct Connection *conn = malloc(sizeof(struct Connection));
if (!conn) die(conn, "Memory error");
conn->db = malloc(sizeof(struct Database));
if (!conn->db) die(conn, "Memory error");
// 存储MAX_DATA和MAX_ROWS
conn->db->max_data = max_data;
conn->db->max_rows = max_rows;
// 动态分配数据库条目
conn->db->rows = malloc(max_rows * sizeof(struct Address));
if (!conn->db->rows) die(conn, "Memory error for rows");
if (mode == 'c') {
conn->file = fopen(filename, "w");
} else {
conn->file = fopen(filename, "r+");
if (conn->file) {
Database_load(conn);
}
}
if (!conn->file) die(conn, "Failed to open the file");
return conn;
}
// 修改主函数中输入参数格式
if (argc < 5) die(NULL, "USAGE: ex17 [action params]");
char *filename = argv[1];
char action = argv[2][0];
int max_data = atoi(argv[3]);
int max_rows = atoi(argv[4]);
struct Connection *conn = Database_open(filename, action, max_data, max_rows);
int id = 0;
if (argc > 5) id = atoi(argv[5]);
if (id >= conn->db->max_rows) die(conn, "There's not that many records.");
switch (action) {
case 'c':
Database_create(conn);
Database_write(conn);
break;
case 'g':
if (argc != 6) die(conn, "Need an id to get");
Database_get(conn, id);
break;
case 's':
if (argc != 7) die(conn, "Need id, name, email to set");
Database_set(conn, id, argv[6], argv[7]);
Database_write(conn);
break;
case 'd':
if (argc != 6) die(conn, "Need id to delete");
Database_delete(conn, id);
Database_write(conn);
break;
case 'l':
Database_list(conn);
break;
default:
die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list");
}
find
。// 添加相应内容
void Database_find(struct Connection *conn,const char *name)
{
struct Database *db = conn->db;
for (int i = 0; i < db->max_rows; i++) {
struct Address *cur = &db->rows[i];
if (cur->set && strcmp(cur->name,name) == 0) {
Address_print(db,i);
}
}
}
case 'f':
if (argc != 7) die(conn, "Need an id to get");
Database_find(conn,argv[6]);
break;
#include
struct MyStruct {
char a;
int b;
short c;
};
int main() {
printf("Size of MyStruct: %lu\n", sizeof(struct MyStruct));
return 0;
}
// 输出结果为: Size of MyStruct: 12
布局如下: | a(char) | padding(3 bytes) | b(int) | c(short) | padding(2 bytes) |
2. 结构体打包(packing)#pragma pack
指令(具体依赖于编译器)或 __attribute__((packed))
。#include
#pragma pack(1) // 设置结构体按 1 字节对齐
struct MyStruct {
char a;
int b;
short c;
};
int main() {
printf("Size of MyStruct: %lu\n", sizeof(struct MyStruct));
return 0;
}
// 输出结果:Size of MyStruct: 7
Address
添加一些字段,使它们可被搜索。bash
顶端使用使用set -e
,使之在任何命令发生错误时退出。#!/bin/bash
# 启用错误退出模式
#set -e
# 定义文件和路径
C_SOURCE_FILE="ex17.c"
C_EXEC_FILE="ex17"
TEST_DB_FILE="testdb.dat"
# 编译 C 代码
echo "Compiling the C code..."
gcc -Wall -g $C_SOURCE_FILE -o $C_EXEC_FILE
# 测试 1: 创建数据库
echo "Running test 1: Creating a new database..."
./$C_EXEC_FILE $TEST_DB_FILE c
# 测试 2: 设置数据
echo "Running test 2: Setting data for ID 0..."
./$C_EXEC_FILE $TEST_DB_FILE s 0 "Joe Alex" "[email protected]"
# 测试 3: 获取数据
echo "Running test 3: Getting data for ID 0..."
./$C_EXEC_FILE $TEST_DB_FILE g 0
# 测试 4: 列出所有数据
echo "Running test 4: Listing all entries..."
./$C_EXEC_FILE $TEST_DB_FILE l
# 测试 5: 删除数据
echo "Running test 5: Deleting data for ID 0..."
./$C_EXEC_FILE $TEST_DB_FILE d 0
# 测试 6: 确认删除
echo "Running test 6: Verifying data for ID 0..."
./$C_EXEC_FILE $TEST_DB_FILE g 0
# 清理测试文件
echo "Cleaning up after tests..."
rm $TEST_DB_FILE
echo "All tests passed successfully!"
class Stack:
def __init__(self):
# 使用列表来存储栈中的元素
self.items = []
def is_empty(self):
# 判断栈是否为空
return len(self.items) == 0
def push(self, item):
# 将元素推入栈中
self.items.append(item)
def pop(self):
# 从栈中弹出一个元素,并返回该元素
if not self.is_empty():
return self.items.pop()
else:
raise IndexError("pop from empty stack")
def peek(self):
# 查看栈顶的元素,不移除它
if not self.is_empty():
return self.items[-1]
else:
raise IndexError("peek from empty stack")
def size(self):
# 返回栈中元素的个数
return len(self.items)
# 示例用法
if __name__ == "__main__":
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(f"栈顶元素: {stack.peek()}") # 输出 3
print(f"栈的大小: {stack.size()}") # 输出 3
print(f"弹出的元素: {stack.pop()}") # 输出 3
print(f"栈的大小: {stack.size()}") # 输出 2
使用C语言实现栈:
#include
#include
#include
#define MAX_SIZE 100 // 栈的最大容量
// 定义栈结构体
typedef struct {
int items[MAX_SIZE]; // 存储栈元素的数组
int top; // 栈顶指针
} Stack;
// 初始化栈
void init_stack(Stack *s) {
s->top = -1; // 初始化时栈为空
}
// 判断栈是否为空
bool is_empty(Stack *s) {
return s->top == -1;
}
// 判断栈是否已满
bool is_full(Stack *s) {
return s->top == MAX_SIZE - 1;
}
// 将元素推入栈中
void push(Stack *s, int item) {
if (is_full(s)) {
printf("栈满,无法推入元素\n");
return;
}
s->items[++(s->top)] = item;
}
// 从栈中弹出一个元素
int pop(Stack *s) {
if (is_empty(s)) {
printf("栈空,无法弹出元素\n");
exit(1); // 异常退出
}
return s->items[(s->top)--];
}
// 查看栈顶元素
int peek(Stack *s) {
if (is_empty(s)) {
printf("栈空,无法查看栈顶元素\n");
exit(1); // 异常退出
}
return s->items[s->top];
}
// 返回栈的大小
int size(Stack *s) {
return s->top + 1;
}
int main() {
Stack stack;
init_stack(&stack);
push(&stack, 1);
push(&stack, 2);
push(&stack, 3);
printf("栈顶元素: %d\n", peek(&stack)); // 输出 3
printf("栈的大小: %d\n", size(&stack)); // 输出 3
printf("弹出的元素: %d\n", pop(&stack)); // 输出 3
printf("栈的大小: %d\n", size(&stack)); // 输出 2
return 0;
}
Python和C实现对比
在Python中,栈实现依赖于列表(list),Python自动处理内存管理。栈的大小可以动态变化。 在C中,我们必须手动管理内存(栈大小),并且栈的最大容量是预定义的(MAX_SIZE)。如果需要动态调整大小,需要重新分配内存。 Python的代码更加简洁和灵活,因为它具有自动内存管理和内建的动态数据结构(如list)。 C需要更多的手动内存管理和对边界条件的检查,代码相对繁琐。