C 语言拥有悠长的发展历史,尽管 C 语言一直在努力实现向后兼容,但仍有一些功能是我们应该避免使用的。
atoi与atol:这两个函数在出错时会返回 0,但这也是一个有效的返回值。个人更推荐 strtoi 等。
gets :不安全,因为这些函数无法给出目标缓冲区的界限。个人更喜欢 fgets。
在学习 C 语言的过程中,我们必须认识到,C 语言作为一种语言,只处理大小已知的数据块。你可以认为 C 语言是一种“复制已知大小值的语言”。
我们可以向程序传递整数或结构,通过函数返回它们,并将它们视为相应的对象,因为 C 知道它们的大小,因此 C 可以编译代码,并复制完整的数据。
然而,数组却完全不同。对于 C 语言来说,数组的大小是未知的。假设我在一个函数中声明了一个变量 int[5],实际上我得到的并不是类型 int[5] 的值,而是一个 int* 值,它指向的位置分配了 5 个整数。由于这只是一个指针,因此程序员必须代替语言来负责复制真正的数据并保证数据有效。
但是,结构内的数组与值一样,可以与结构一起复制。
(严格来讲,指定了大小的数组是真正的类型,而不仅仅是指针,例如你可以通过 sizeof 得知整个数组的大小。只不过,你不能将它们视为独立的值。)
编译器的各种选项C 语言的编译器有很多选项,而且默认值不是很好用。下面是一些你可能需要的选项。
-O2:在发布代码时,对代码进行优化。
-g -Og:用于调试代码,可以让调试器输出额外的信息,并根据调试进行优化。
-Wall:启用更多警告(有点像 linter),你可以通过-Wno禁用特定警告。
-Werror:警告变成错误。我建议启用 -Werror=implicit,这样可以确保调用未声明的函数会报错。
-DNAME 和 -DNAME=value:用于定义宏。
-std=...:选择一个标准。在大多数情况下,你可以省略这个选项,使用编译器的默认值(通常是最新标准)。如果你想使用“经典”C,可以指定 -std=c89。
自动存储:用于保存局部变量。当函数被调用时,就会创建一个新的自动存储区域,并在函数返回结果时删除。只有返回值会被保留,并被复制到调用它的函数的自动存储中。这意味着,返回一个指向局部变量的指针是不安全的,因为底层数据会被默默删除。自动存储通常被称为“栈”。
分配的存储:运行malloc 会返回的内存类型,这种内存会一直保留,直到被 free 函数释放,所以可以被传递到任何地方,包括返回给上级调用函数。通常被称为“堆”。
静态存储:在程序的整个生命周期内有效。在进程启动时分配,全局变量都存储在这里。
如果想通过一个函数“返回”内存,不必通过调用 malloc,可以直接将一个指向本地数据的指针传递给函数:
void getData(int *data) {
data[0] = 1;
data[1] = 4;
data[2] = 9;
}
void main {
int data[3];
getData(data);
printf("%d\n", data[1]);
}