目录
目录
前言
那什么?如果让你手动录10页纸的程序,你害怕吗?ex17虽然没有这么多,但是给了我这种即视感!
我怕,所以我又再次手动录了一遍程序!
解决恐惧的办法,就是直面恐惧!我真的有了一些意外的收获。
正文
ex17
代码太长,就不放进来了。下面记录了我的代码输出与一些关键字。
代码输出:
“小僵”挺会鼓励人的吧,哈哈!
#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
都用于表示程序结束,并返回一个非零的状态码给操作系统。它们之间的区别在于:
-
exit(1)
是一个函数调用,它会立即终止整个程序的执行,并返回状态码给操作系统。它不会执行后续的代码,也不会执行任何的清理操作。一旦调用了exit(1)
,程序就会立即退出,不再执行其他的代码。 -
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
函数:
-
fopen(filename, mode)
:打开一个文件并返回一个指向FILE
结构的指针。filename
是要打开的文件名,mode
是打开文件的模式。常见的模式有:"r"
:只读模式,文件指针将被放在文件的开头。"w"
:写入模式,如果文件已存在,则覆盖原有内容;如果文件不存在,则创建新文件。"a"
:追加模式,如果文件已存在,则在文件末尾追加内容;如果文件不存在,则创建新文件。"x"
:创建模式,如果文件已存在,则无法打开文件。"b"
:以二进制模式打开文件。"t"
:以文本模式打开文件。
-
fclose(file)
:关闭一个打开的文件。 -
fgetc(file)
:从文件中读取一个字符,并返回读取的字符。 -
fputc(character, file)
:将一个字符写入到文件中。 -
fgets(buffer, size, file)
:从文件中读取一行内容,存储在缓冲区buffer
中。 -
fputs(string, file)
:将一个字符串写入到文件中。 -
fread(ptr, size, count, file)
:从文件中读取数据块,并存储在指针ptr
所指向的内存区域中。 -
fwrite(ptr, size, count, file)
:将数据块写入到文件中。 -
feof(file)
:检查文件结束标记。如果已到达文件末尾,则返回非零值,否则返回0。 -
ferror(file)
:检查文件读写错误。如果发生错误,则返回非零值,否则返回0。 -
fprintf(): 格式化输出数据到文件中。
-
fscanf(): 从文件中按照指定格式读取数据。
-
remove(): 删除指定文件。
-
rewind(): 将文件指针重新定位到文件起始位置。
-
ftell(): 获取文件指针的当前位置。
-
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间接获取的,它就是在栈上。(草堆是买可乐送的?)
1 | 如果你通过“买可乐“获取了内存,却把指针放在栈上。那么函数退出后,指针也就没了。 |
2 | 栈上不能放太多的数据(如大型结构体与数组),可能会导致栈溢出(stack overflow)、程序终止。所以就去”买可乐“吧…… |
3 | 指向栈上的针针不要在函数中传递或者返回它,会造成断错误(segfault),原因同1。 |
附加题:
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给我的结果漏洞摆出,最终感觉没有传入任意大小的数据)
其它答答:
除了4,其它答不出来,臣妾做不到!
4答答:我发现的结果db是多大,文件就是多大,也就是我们获得多大的内存,创建的数据文件就是那么大。
后语
1、熟练掌握了写一点调试一点的办法!发生了很多段错误(那可是以前打破脑袋都解决不了的问题),都被我很快的解决了,哈哈!(数天后,我在结构体的段错误中跪了又跪)
2、减少了长代码录入的恐惧,增加了阅读代码的自信心。(中间还有个小插曲,当我写了一半,想复制粘贴的时候,发现之前的代码也是录了一半,可能第一次刷的被删了,第二次我就稀里糊涂的跳过了吧!)
3、写给与我一样的准新新们的话,这是一个完整的管理数据库的小程序,有一个难度的飞跃。它还是对内存分配的一个更正式的介绍。
4、每一个附加都要花好几天,虽然可能结果还是不尽人意,但是有收获的。为什么花好几天,我查资料,打地基。那么问题来了,我为什么要花这么长时间呢?
5、参数传入与指针赋值感悟杂谈
万丈高楼平地起,C语言尤其需要多年的沉淀,前人诚不欺我也!以下是我静下心来与此篇对抗的最好收获之一(如果你对参数传入与赋值,,或者输入传到指针上有什么困惑,可以参考一下):
以前一直不理解参数的哦作用,就是肤浅的觉得它就是一个桥梁,恩就是你看到桥梁,它就是桥梁的那种感觉,就是看山是山的那种感觉。
今天经过反复探究附加任务2后,突然就悟了!(请见谅废话有点多,下面是正题)
电脑端输入的字符就是菜,输入参数就是锅,经过它炒好,出锅入盘,盘子就是指针,一路送到客人那边。只要菜够,谁需要都可以点一盘。
我们输入的是无根之萍,有了参数,才有了根,指引它到需要的地方。
总感觉表述的还差点意思 - -
6、肝了两周后的反思:
继006结束之后,过去了两周。中间有个劳动节,我花的时间比正常的上班更多。前面6篇的顺畅让我觉得,我可以一路丝滑到底,就算有个小坎儿,摔一下爬起来也就过去了。我很牛嗒!
然而事实证明,生活就是生活,完美的结局永远只存在于YY的世界。尽管前面笔记里也说了,不会的可以先跳过去。可是我感觉我可能有点“入魔”,由于总是公开我的笔记,带入了一种奇怪的角色中。然而,学习要想进步,需要的是诚实。认真对待自己的问题,真诚反馈自己的收获。
中途,我也用了创作助手,然而一些关键的感悟体会没有做到,那我就是不会。看懂了,写不出来,那肯定还是有些地方理解不透彻!
然而根据“拉伸区”学习原则,我觉得我该跳过了。因为此系列的附加题,已经开始影响了我的情绪(几度崩溃于想不通的结构段错误),差点要让我放弃我的初心。
学习是好的,方法更重要。该跳过就要跳过,我有信心回头能搞定它,总感觉就差那么一丝丝,那么一丝丝……