一起笨笨的学C——007小型数据库

目录

目录

前言

正文

ex17

#define定义常数

 errno与perror:

exit(1):

FILE相关函数:

strncpy:

atoi:

堆与栈:

 

附加题:   

1答答:

2答答:

其它答答:

        

  后语

        6、肝了两周后的反思:

 


 

前言

        那什么?如果让你手动录10页纸的程序,你害怕吗?ex17虽然没有这么多,但是给了我这种即视感!

        我怕,所以我又再次手动录了一遍程序!

        解决恐惧的办法,就是直面恐惧!我真的有了一些意外的收获。


 

正文

ex17

        代码太长,就不放进来了。下面记录了我的代码输出与一些关键字。

        代码输出:

c6ed4610ccf34adb9f9f192c0d90a387.png

“小僵”挺会鼓励人的吧,哈哈!

 

#define定义常数

        用cpp(C preprocessor)的另一部分功能创建了MAX_DATA与MAX_ROWS的常数设置。

 

 errno与perror:

       errno是一个全局变量,用于存储最近一次发生的错误代码。它在<errno.h>头文件中定义,并且在使用标准C库函数时,可以通过检查errno的值来判断函数调用是否失败。

        perror是一个库函数,用于将errno的值转换成对应的错误信息并输出到标准错误流。它在<stdio.h>头文件中定义,函数原型为:

        void perror(const char *str);

        str参数是一个字符串,它会在输出错误信息之前输出到标准错误流。接下来,perror会根据errno的值查找对应的错误信息,并将错误信息以"str: 错误信息"的形式输出到标准错误流。

        例如,如果在打开一个文件时发生了错误,可以通过以下代码来使用errno和perror来输出错误信息:

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

int main() {
    FILE *file = fopen("non_existent_file.txt", "r");
    if(file == NULL) {
        perror("文件打开失败");
        printf("错误代码:%d\n", errno);
    }
    return 0;
}

        上述代码会尝试打开一个不存在的文件,然后通过perror输出错误信息和错误代码。输出结果可能类似于:

文件打开失败: No such file or directory
错误代码:2

        这样的输出可以帮助开发者更好地了解程序中的错误和失败原因。

exit(1):

        一开始看到以为是很正常的一个退出函数,跳过了。后来反复看了几次,总感觉怪怪的,仔细一研究,厉害了我的哥!

        下面将它与return 1比较了下:

在C中,exit(1)return 1 都用于表示程序结束,并返回一个非零的状态码给操作系统。它们之间的区别在于:

  1. exit(1) 是一个函数调用,它会立即终止整个程序的执行,并返回状态码给操作系统。它不会执行后续的代码,也不会执行任何的清理操作。一旦调用了 exit(1),程序就会立即退出,不再执行其他的代码。

  2. return 1 是在函数内部使用的语句,用于结束函数的执行,并返回一个值给调用该函数的地方。它只会终止当前函数的执行,而并不会终止整个程序的执行。当函数执行到 return 1 后,会将控制权返回给调用函数的地方,后续的代码仍然会继续执行。

下面是一个示例,演示了 exit(1)return 1 的使用:

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

int myFunction();
int main() {
    // 使用 return 1 终止函数执行
   int errorCode = myFunction();
   printf("错误码:%d\n", errorCode);
   printf("这段代码会继续执行\n");

  // 使用 exit(1) 终止程序执行
   printf("开始执行\n");
   exit(1);  // 终止程序执行
   printf("这段代码不会执行\n");

   return 0;
}

int myFunction() {
   // 一些代码...

   return 1;  // 结束函数执行,返回错误码
}

在上面的示例中,使用 exit(1) 终止程序执行后,之后的代码(例如 "这段代码不会执行")不会被执行。而使用 return 1 终止函数执行后,控制权会返回到 main 函数,继续执行后续的代码。

 

FILE相关函数:

       在C语言中,可以使用 <stdio.h> 头文件中提供的 FILE 相关函数来进行文件的操作。以下是一些常见的 FILE 函数:

  1. fopen(filename, mode):打开一个文件并返回一个指向 FILE 结构的指针。filename 是要打开的文件名,mode 是打开文件的模式。常见的模式有:

    • "r":只读模式,文件指针将被放在文件的开头。
    • "w":写入模式,如果文件已存在,则覆盖原有内容;如果文件不存在,则创建新文件。
    • "a":追加模式,如果文件已存在,则在文件末尾追加内容;如果文件不存在,则创建新文件。
    • "x":创建模式,如果文件已存在,则无法打开文件。
    • "b":以二进制模式打开文件。
    • "t":以文本模式打开文件。
  2. fclose(file):关闭一个打开的文件。

  3. fgetc(file):从文件中读取一个字符,并返回读取的字符。

  4. fputc(character, file):将一个字符写入到文件中。

  5. fgets(buffer, size, file):从文件中读取一行内容,存储在缓冲区 buffer 中。

  6. fputs(string, file):将一个字符串写入到文件中。

  7. fread(ptr, size, count, file):从文件中读取数据块,并存储在指针 ptr 所指向的内存区域中。

  8. fwrite(ptr, size, count, file):将数据块写入到文件中。

  9. feof(file):检查文件结束标记。如果已到达文件末尾,则返回非零值,否则返回0。

  10. ferror(file):检查文件读写错误。如果发生错误,则返回非零值,否则返回0。

  11. fprintf(): 格式化输出数据到文件中。

  12. fscanf(): 从文件中按照指定格式读取数据。

  13. remove(): 删除指定文件。

  14. rewind(): 将文件指针重新定位到文件起始位置。

  15. ftell(): 获取文件指针的当前位置。

  16. fflush():刷新输出流,并将缓冲区的文件立即写入文件或终端上。使用 fflush 的时机很关键,错误的使用可能导致性能下降甚至出现错误。因此,在使用 fflush 函数之前,请确保真正需要立即刷新输出流的情况。

这些函数可以用来进行文件的读取和写入操作。使用时需要先通过 fopen() 函数打开文件,然后使用其他函数对文件进行操作,最后通过 fclose() 函数关闭文件。

 

strncpy:

        strncpy 是 C 语言中的一个库函数,用于从一个字符串中复制指定数量的字符到另一个字符串中。它的原型如下:

char *strncpy(char *dest, const char *src, size_t n)

        此函数接受三个参数:目标字符串 dest,源字符串 src,以及要复制的字符数量 n。它会将源字符串中指定数量的字符复制到目标字符串中,并返回目标字符串的指针。

       如果源字符串长度小于要复制的字符数量,则会使用空字符进行填充,直到达到指定数量。如果源字符串长度大于要复制的字符数量,则只会复制指定数量的字符。

        需要注意的是,strncpy 不会自动在目标字符串末尾添加空字符,因此在使用 strncpy 复制字符串时,确保目标字符串的最后一个字符为 null 字符('\0')是很重要的,以避免访问未初始化的内存。

 

atoi:

        atoi函数是C语言中的一个函数,用于将字符串转换为整数。(首字母不为整数就会被转为0)(它的原型如下:

        int atoi(const char *str);

        函数的参数是一个指向要转换的字符串的指针。

        atoi函数会从字符串中读取数字,直到遇到非数字字符为止。如果字符串是一个有效的整数表示,函数将返回对应的整数值。如果字符串无法转换为整数,函数将返回0。

下面是一个示例代码:

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

int main() {
    char str[] = "12345";
    int num = atoi(str);
    printf("The converted number is: %d\n", num);
    return 0;
}

输出结果为:

The converted number is: 12345

        需要注意的是,atoi函数只能将字符串转换为整数,无法处理小数或其他格式的数字。如果需要转换其他类型的数字,可以使用相关的函数,如atof函数用于将字符串转换为浮点数。

 

堆与栈:

        Zed的口诀:如果变量不是用malloc直接获取的,也不是在函数内通过malloc间接获取的,它就是在栈上。(草堆是买可乐送的?)   

关于使用堆和栈的3个注意点
1如果你通过“买可乐“获取了内存,却把指针放在栈上。那么函数退出后,指针也就没了。
2栈上不能放太多的数据(如大型结构体与数组),可能会导致栈溢出(stack overflow)、程序终止。所以就去”买可乐“吧……
3指向栈上的针针不要在函数中传递或者返回它,会造成断错误(segfault),原因同1。

附加题:   

创建任意大小的数据库,直到程序下跪
加强die函数,让它能传入conn变量,让die可以关闭链接清理程序。
2修改代码,让它接受MAX_DATA   MAX_ROWS的参数传入,将它们存储在Database结构体中,并且写到文件中去,这样就能创建任意大小结构体。
3添加更多的 数据库操作,如find
4了解C语言对结构体的封装,最终能根据结构体大小猜出文件大小
5在Address中加入新字段,让新成员支持搜索操作
6写命令行脚本,自动测试
7修改程序,让它用一个全局数据库链接。与之前有什么不同
8研究栈数据结构体,用C 语言写一个,或者其它你喜欢的语言(也就python七七八八,要秀吗?

1答答:

        把Database_close的函数复制过去,增加struct Connection *conn的参数,并把main函数中第一个die改成printf + exit(1)。(请问看过这本书的大大们,这对吗?)

2答答:

        好难,请了外援!

        首先,需要将MAX_DATA和MAX_ROWS从宏定义改为数据库结构体的字段:

struct Database {
    struct Address rows[MAX_ROWS];
    int max_data;
    int max_rows;
};

        然后,在Database_open函数中,需要将传入的max_data和max_rows赋值给conn->db->max_data和conn->db->max_rows字段。同时,需要将malloc分配内存的大小改为conn->db->max_rows * sizeof(struct Address)。


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");

    conn->db->max_data = max_data;
    conn->db->max_rows = max_rows;

   

        在Database_create函数中,需要将循环的次数从固定值MAX_ROWS改为conn->db->max_rows。


void Database_create(struct Connection *conn)
{
    int i = 0;

    for (i = 0; i < conn->db->max_rows; i++) {
        struct Address addr = {.id = i, .set = 0 };
        conn->db->rows[i] = addr;
    }
}

        这样,就修改了代码,使其能够接受MAX_DATA和MAX_ROWS参数传入,并根据传入的值动态创建任意大小的结构体。(ai也许也分等级,CSDN的ai给我的结果漏洞摆出,最终感觉没有传入任意大小的数据)

        

其它答答:

fd89a554fce64be0a68bac89350ea665.png

        除了4,其它答不出来,臣妾做不到!

        4答答:我发现的结果db是多大,文件就是多大,也就是我们获得多大的内存,创建的数据文件就是那么大。

        

 

       


  后语

           1、熟练掌握了写一点调试一点的办法!发生了很多段错误(那可是以前打破脑袋都解决不了的问题),都被我很快的解决了,哈哈!(数天后,我在结构体的段错误中跪了又跪)

           2、减少了长代码录入的恐惧,增加了阅读代码的自信心。(中间还有个小插曲,当我写了一半,想复制粘贴的时候,发现之前的代码也是录了一半,可能第一次刷的被删了,第二次我就稀里糊涂的跳过了吧!)

          3、写给与我一样的准新新们的话,这是一个完整的管理数据库的小程序,有一个难度的飞跃。它还是对内存分配的一个更正式的介绍。

        4、每一个附加都要花好几天,虽然可能结果还是不尽人意,但是有收获的。为什么花好几天,我查资料,打地基。那么问题来了,我为什么要花这么长时间呢?

        5、参数传入与指针赋值感悟杂谈

        万丈高楼平地起,C语言尤其需要多年的沉淀,前人诚不欺我也!以下是我静下心来与此篇对抗的最好收获之一(如果你对参数传入与赋值,,或者输入传到指针上有什么困惑,可以参考一下):

        以前一直不理解参数的哦作用,就是肤浅的觉得它就是一个桥梁,恩就是你看到桥梁,它就是桥梁的那种感觉,就是看山是山的那种感觉。

        今天经过反复探究附加任务2后,突然就悟了!(请见谅废话有点多,下面是正题)

        电脑端输入的字符就是菜,输入参数就是锅,经过它炒好,出锅入盘,盘子就是指针,一路送到客人那边。只要菜够,谁需要都可以点一盘。

        我们输入的是无根之萍,有了参数,才有了根,指引它到需要的地方。

        总感觉表述的还差点意思 -  -

        6、肝了两周后的反思:

        继006结束之后,过去了两周。中间有个劳动节,我花的时间比正常的上班更多。前面6篇的顺畅让我觉得,我可以一路丝滑到底,就算有个小坎儿,摔一下爬起来也就过去了。我很牛嗒!

        然而事实证明,生活就是生活,完美的结局永远只存在于YY的世界。尽管前面笔记里也说了,不会的可以先跳过去。可是我感觉我可能有点“入魔”,由于总是公开我的笔记,带入了一种奇怪的角色中。然而,学习要想进步,需要的是诚实。认真对待自己的问题,真诚反馈自己的收获。

        中途,我也用了创作助手,然而一些关键的感悟体会没有做到,那我就是不会。看懂了,写不出来,那肯定还是有些地方理解不透彻!

        然而根据“拉伸区”学习原则,我觉得我该跳过了。因为此系列的附加题,已经开始影响了我的情绪(几度崩溃于想不通的结构段错误),差点要让我放弃我的初心。

        学习是好的,方法更重要。该跳过就要跳过,我有信心回头能搞定它,总感觉就差那么一丝丝,那么一丝丝……

        

 

      

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值