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

C语言数据库管理系统示例:文件操作、内存管理、错误处理与动态数据库设计 栈和堆的内存分配

C语言的管理数据库完整的小型系统示例:

#include <stdio.h>      // 引入标准输入输出库,提供printf等功能
#include <assert.h>      // 引入断言库,用于调试时检查条件
#include <stdlib.h>      // 引入标准库,提供malloc、free、exit等功能
#include <errno.h>       // 引入错误号库,用于获取系统调用的错误号
#include <string.h>      // 引入字符串处理库,提供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 <dbfile> <action> [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 获取错误信息。


2. freadfwrite 函数

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,则表示发生了错误。

3. rewind 函数

void rewind(FILE *stream);

功能:将文件指针 stream 移动到文件的开头。
参数:
        stream:指向要操作的文件流。
返回值:没有返回值。成功时,文件指针指向文件的起始位置。如果发生错误,ferror 会返回一个非零值。

4. fflush 函数

int fflush(FILE *stream);

功能:刷新输出缓冲区,将缓冲区中的数据强制写入到文件中。
参数:
        stream:指向要刷新的文件流。如果 stream 为 NULL,则刷新所有输出流的缓冲区。
返回值: 如果成功,返回 0。 如果发生错误,返回 EOF,并设置 errno 来指示错误原因。

 C 标准库中用于处理错误的相关机制

1、errno

        errno 是一个全局变量,定义在 <errno.h> 头文件中,它用于指示上一个系统调用或库函数发生的错误类型。
        作用:每当某个函数(特别是系统调用或库函数)失败时,它会将一个错误代码存储在 errno 中。该错误代码是一个整数,代表具体的错误类型。
         errno 的值:它的值会随着错误发生而改变。因此,在调用可能设置 errno 的函数后,需要检查其值并根据错误代码提供相应的处理。
常见的 errno 错误代码:
        EINVAL:无效的参数。
        ENOENT:没有该文件或目录。
        ENOMEM:内存不足。
        EACCES:权限拒绝。
        EIO:输入输出错误。
        EPERM:操作不允许。
        EIO:硬件错误。

示例:

#include <stdio.h>
#include <errno.h>
#include <string.h>

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;
}

2、perror

   perror 是 C 标准库中用于打印 errno 的错误信息的函数,它会根据 errno 中的错误代码输出相应的错误描述。它输出的信息通常包括用户自定义的前缀字符串(可选)和错误消息。
函数原型:

void perror(const char *s);

        参数 s:是用户提供的字符串,如果提供了,perror 会先输出该字符串,再输出对应的错误描述;如果不提供,直接输出错误描述。
        输出:perror 会输出一个标准的错误信息,格式为:<prefix>: <error_description>。

堆和栈的内存分配

        堆就是你电脑中的剩余内存,你可以通过malloc访问它来获取更多内存,OS会使用内部函数为你注册一块内存区域,并且返回指向它的指针。当你使用完这片区域时,你应该使用free把它交还给OS,使之能被其它程序复用。如果你不这样做就会导致程序“泄露”内存,但是Valgrind会帮你监测这些内存泄露。

        栈是一个特殊的内存区域,它储存了每个函数的创建的临时变量,它们对于该函数为局部变量。它的工作机制是,函数的每个函数都会“压入”栈中,并且可在函数内部使用。它是一个真正的栈数据结构,所以是后进先出的。这对于main中所有类似char sectionint 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_DATAMAX_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 <dbfile> <action> <max_data> <max_rows> [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;
    
  • 查询C如何打包结构体,并且试着弄清楚为什么你的文件是相应的大小。看看你是否可以计算出结构体添加一些字段之后的新大小。

    在 C 语言中,打包(packing)结构体是指调整结构体的内存布局,减少或消除由于字节对齐造成的内存填充(padding)。字节对齐的目的是提高处理器访问内存时的效率,尤其是在处理较大数据时。

    1. 结构体的内存布局(Padding)

    通常情况下,编译器会根据结构体中成员的类型对内存进行对齐。每个数据类型都有一个对齐要求(alignment requirement),例如:
    char 通常对齐到 1 字节边界(sizeof(char) = 1)
    int 通常对齐到 4 字节边界(sizeof(int) = 4)
    如果结构体成员没有按照其对齐要求对齐,编译器会自动插入填充字节(padding)来确保每个成员按照适当的对齐要求存储。
    例如:
    #include <stdio.h>
    
    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 <stdio.h>
    
    #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添加一些字段,使它们可被搜索。

    类似find功能
     
  • 编写一个shell脚本来通过以正确顺序运行命令执行自动化测试。提示:在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" "joe@example.com"
    
    # 测试 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!"
    
  • 尝试重构程序,使用单一的全局变量来储存数据库连接。这个新版本和旧版本比起来如何?

    使用全局变量,在重构后,struct Connection *conn 被定义为一个全局变量。所有与数据库相关的操作都直接访问这个全局变量,无需再通过函数参数传递连接。函数 Database_open、Database_close、Database_write、Database_create 等不再需要接受 conn 作为参数,因为全局变量 conn 可以直接访问。
    新版本与旧版本的对比:
    优点:
    简洁性: 代码中函数签名变得更简洁,不再需要通过参数传递 conn,而是直接使用全局变量。
    易于访问: 在多个函数中直接访问全局变量,避免了多次传递参数的麻烦。

    缺点:
    可维护性差: 使用全局变量会让程序的状态管理变得更复杂,特别是在多线程或多任务环境中。函数内部无法独立操作数据库连接,而是依赖于全局状态。
    难以测试: 单一的全局变量增加了测试的复杂性。测试时,可能需要手动初始化和清理全局变量,尤其是在多个测试用例之间共享状态时。
    灵活性降低: 使用全局变量意味着函数之间没有明确的数据流动关系,导致代码的解耦性降低,后期的扩展可能变得更加困难。

    如果程序的规模较小,且没有复杂的模块化需求,使用全局变量可以简化代码,减少函数参数的传递。 但在大型项目或需要高可维护性和高可测试性的项目中,尽量避免使用全局变量,或者将其限制在必要的范围内。
     
  • 搜索“栈数据结构”,并且在你最喜欢的语言中实现它,然后尝试在C中实现。

    使用python实现栈:
    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 <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    #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需要更多的手动内存管理和对边界条件的检查,代码相对繁琐。


http://www.kler.cn/a/446644.html

相关文章:

  • 如何缩放组件
  • 日本充电桩标准--CHAdeMO介绍
  • OpenGL ES 01 渲染一个四边形
  • 面试题整理4----lvs,nginx,haproxy区别和使用场景
  • 基于Python3编写的Golang程序多平台交叉编译自动化脚本
  • redis数据类型:list
  • [c++进阶(二)] 智能指针详细剖析--RAII思想
  • 在 .NET 5.0 运行 .NET 8.0 教程:使用 ASP.NET Core 创建 Web API
  • python 模拟法
  • 学技术学英文:SpringBoot的内置监控组件-Spring Boot Actuator
  • Android 10 Launcher3 删除谷歌搜索
  • c++中如何处理对象的创建与销毁的时机?
  • Python发送带key的kafka消息
  • TCP为什么需要三次握手和四次挥手?
  • 创新性融合丨卡尔曼滤波+目标检测 新突破!
  • C/C++语言基础--C++STL库之仿函数、函数对象、bind、function简介
  • 单元测试(C++)——gmock通用测试模版(个人总结)
  • Spring(三)-SpringWeb-概述、特点、搭建、运行流程、组件、接受请求、获取请求数据、特殊处理、拦截器
  • python实现word转html
  • AI大模型进一步推动了AI在处理图片、视频、音频、文本的等数据应用
  • 【新教程】非root用户给Ubuntu server设置开机自启服务-root用户给Ubuntu server设置开机自启服务
  • ArcGIS计算土地转移矩阵
  • 详细解释爬虫中的异常处理机制?
  • Rabbitmq实现延迟队列
  • Leetcode2545:根据第 K 场考试的分数排序
  • 26、基于SpringBoot的在线文档管理系统的设计与实现