c语言

【CSDN编者注】C语言对于很多人来说是入门的编程语言。这篇文章的作者惊呼C语言太难了。要是我能早点知道这些就好了!于是,他在学习C语言的过程中,记录了一些好的项目,CSDN组织翻译人员整理出来,与大家分享。

原文链接:https://tmewett.com/c-tips/#good-projects-to-learn-from

作者:汤姆·M

译者:弯月

对我来说,学习C语言很难。这门语言本身的基础知识并不是很难,但是《C语言编程》需要各种知识,不是那么容易掌握的:

c语言在不同的平台和操作系统上有不同的行为,所以你需要了解平台。

c语言有很多编译器选项和构建工具,即使运行一个简单的程序也需要做出很多决策。

c语言涉及到很多与CPU、操作系统、编译代码相关的概念。

c语言的使用方式多种多样,远没有像其他语言那样有一个集中的社区和统一的风格。

在这篇文章中,我想总结一下学习C语言的要点和建议,希望对你有所帮助。

学习资源

值得借鉴的项目

编译器、链接、标题和符号

不推荐的功能

数组不是一个值。

编译程序任选项

三种类型的记忆以及何时使用它们。

命名约定

静电

结构方法模式

常数

平台和标准APII

整数

大小

算术运算和整数提升

字符类型符号

宏和常量变量

宏和内嵌函数

c语言

学习资源

教程要点C:基础知识介绍

Awesome-c:库和工具列表

CP preference:C语言和标准库的技术参考

值得借鉴的项目

在学习的过程中,看一些C语言代码会有帮助。

Bloopsaphone:一个用于声音合成的Ruby库,其核心是一个小的C模块。概念少,结构好;

简单动态字符串(sds):有一个. c和。h文件,这是学习C语言的一个很好的例子,展示了如何管理更复杂的资源。

Brogue CE:一款类似rogue的视频游戏。这个库比较大,大概有3万行代码。我在维护这个代码库,我们的许多贡献者都是C语言专家。

Stb单文件库:包含很多中小型C模块,主要用于嵌入式设备和游戏机。

编译、链接、标题和符号

下面是一些关于如何编译C语言的基础知识。

c语言代码是用源文件写的。每个源文件将被编译成一个目标文件。o,它就像一个容器,装载了。c文件。但是这些功能是不可执行的。目标文件中有一个符号表。这些符号是文件中定义的全局函数和变量的名称。

#编译成对象cc-c thing . c-othing . OCC-cs tuff . c-ostuff . o源文件是完全独立的,可以并行编译成对象。

如果要跨文件调用函数和变量,必须使用头文件(。h)。这些文件也是C源文件,但是它们有特殊的用法。回想起来,目标文件只包含全局函数和变量的名称,没有类型、宏甚至函数参数。如果要在文件间使用这些符号,需要指定附加信息。我们将这些“声明”放在。h文件,然后是其他。c文件通过#include包含它们。

通常是为了避免重复。c文件不定义自己的类型/宏,只包含自己的模块或组件的头文件。

你可以把头文件看作API的规范,但是实现可以放在多个源文件中。您甚至可以在同一个头文件中实现不同的平台或目的。

如果遇到只声明了(例如,通过头文件)而没有在编译时定义的符号引用,编译后的目标文件会将其标记为缺失,需要填充。

工作的最后部分由编译器的链接器组件完成,它负责将一个或多个对象连接在一起,匹配所有的符号引用,然后输出一个完整的可执行文件或共享库。

# linkobjects到可执行的ccthing。ostuff.o-ogizmo综上所述,C语言的源文件不能包含其他源文件,只能包含声明,然后由链接器完成匹配。

不推荐使用的功能

c语言有着悠久的发展历史。虽然C语言一直在努力实现向后兼容,但还是有一些函数是我们应该避免使用的。

Atoi()和atol():这两个函数在出现错误时会返回0,但这也是一个有效的返回值。个人推荐strtoi()之类的。

Gets():这是不安全的,因为这些函数不能给出目标缓冲区的边界。个人比较喜欢fgets()。

数组不是值

在学习C语言的过程中,我们必须认识到,C语言作为一种语言,只处理已知大小的数据块。你可以认为C语言是一种“复制已知大小值的语言”。

我们可以把整数或者结构传递给程序,通过函数返回,把它们当作对应的对象,因为C知道它们的大小,所以C可以编译代码,复制完整的数据。

但是,数组完全不同。对于C语言,数组的大小是未知的。假设我在一个函数中声明了一个变量int[5]。其实我得到的不是int[5]类型的值,而是一个int*值,五个整数赋给它所指向的位置。因为这只是一个指针,程序员必须代替语言复制真实的数据,并保证数据有效。

但是,结构中的数组与值一样,可以随结构一起复制。

严格来说,指定大小的数组是实型,而不仅仅是指针。例如,通过sizeof可以知道整个数组的大小。但是,你不能把它们当成独立的价值观。)

编译器的各种选项

C语言的编译器选项很多,默认值不是很好用。这里有一些你可能需要的选项。

-O2:发布时优化代码。

-g -Og:用于调试代码,允许调试器输出附加信息,并根据调试情况进行优化。

-Wall:启用更多警告(有点像linter),可以通过-Wno禁用特定的警告。

-Werror:警告变成了错误。我建议启用-Werror=implicit,这样可以保证调用未声明的函数会报错。

-DNAME和-DNAME=value:用于定义宏。

-标准=…:选择一个标准。在大多数情况下,可以省略这个选项,使用编译器的默认值(通常是最新的标准)。如果想用“经典”的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]);}

c语言不支持命名空房间。如果你想写一个公共库或者命名一个“模块”,你需要在所有公共API的名字前加一个前缀。这些名称包括:

功能

类型

枚举值

宏伟的

此外,每个枚举还应该加上不同的前缀,以便区分一个值属于哪个枚举类型:

枚举颜色{颜色_红色,颜色_蓝色,…}关于命名没有太多真正的约定。你可以随意选择snake_case或者camelCase,但是请记得保持一致!因为很多标准C类型采用ptrdiff_t和int32_t的形式,所以有人把这种类型命名为My _ type _ t..

static

函数或文件级的静态变量仅限于文件内部访问。这些函数或变量不作为符号导出,因此不能在其他源文件中使用。

Static也可以用在局部变量上,它可以在多次函数调用之间保持变量的值不变。你可以把它想象成一个只被这个函数使用的全局变量。您可以使用static来计算和存储数据,以便在后续调用中重用。但是要记住,这种使用方法和全局状态或者共享状态有一样的问题,比如线程安全,递归冲突等等。

结构方法模式

如果你在学习C语言之前学习过比较有特色的语言,你可能会发现很难将这些知识运用到C语言学习中。例如,面向对象编程的一个常见概念:结构化方法,即函数接受一个指向结构的指针,并通过该指针修改该结构或获取属性:

typedef结构{ int x;int y;} vec2void vec_add(vec2 *u,const vec 2 * v){ u-& gt;x+= v-& gt;x;u-& gt;y+= v-& gt;y;}int vec_dot(const vec2 *u,const ve C2 * v){ return u-& gt;x * v-& gt;x+u-& gt;y * v-& gt;y;你不能扩展结构或者实现类似面向对象的功能,但是这样想还是有用的。

const

以const T的形式声明T类型的变量或参数意味着这个变量或参数不能被修改。这意味着它不能被赋值,如果t是指针或数组类型,它就不能被修改。

你可以把T转换成const T,但不能反过来。

将函数的指针参数默认设置为const是一个很好的习惯,只有在这些变量确实需要修改的时候才省略const。

平台和标准 API

我们很难根据# include做出决定

标准c库(缩写为“stdlib”)。例如:stdio.h,stdlib.h,error.h

这是语言规范的一部分,应该由所有兼容的平台和编译器来实现。非常安全,可以放心使用。

https://en.cppreference.com/w/c/header

POSIX:操作系统API的标准。例如:unistd.h,sys/time.h

一般由Linux,macOS,BSD实现。

默认情况下,它不能在Windows中使用。如果你使用MinGW,你可以使用POSIX API。如果想要更完整的支持,可以使用Cygwin库。

可以通过官方的OpenGroup页面或者帮助手册查看POSIX头文件(包括C stdlib)的所有细节。

非标准操作系统接口。

特定于Linux的API

Windows Win32(以及C++/WinRT——这是一个更现代的c++接口)。

(Mac的OS API是Objective C(现在的Swift),不是C。)

安装在标准位置的第三方库。

您可以通过独立于平台的头文件与更多特定于平台的代码进行交互,这些头文件可以通过不同的方式实现。许多流行的C库本质上只是一个统一的、设计良好的特定于平台的函数的抽象。

整数

C语言中的整数是一个非常大的坑。写代码的时候要小心。

大小

所有整数类型都有一定的最小位数。在一些常见的平台中,整数的大小大于最小位数。比如int在Windows、macOS、Linux上是32位,但它的最小位数是16位。编写可移植代码时,必须注意不要让整数大小超过最小位数。

如果想精确控制整数大小,可以使用stdint.h中的标准类型,如int32_t、uint64_t等。还有_least_t和_fast_t类型。

算术运算与整数提升

C语言中的算术运算有很多奇怪的规则,会产生意想不到的或者不可移植的结果。

另外,整数提升请格外小心。

char 类型的符号

默认情况下,所有其他整数类型都是有符号的,但char可以是有符号的,也可以是无符号的,这取决于平台。因此,这种类型只有在用作字符时才是可移植的。如果要指定一个很小的数字,比如只有8位数,还应该指定一个符号。

宏与 const 变量

如果您想定义一个非常简单的常数值,您有两种选择:

static const int my _ constant = 5;//或者#define MY_CONSTANT 5的区别在于前者是实变量,后者是复制粘贴的内联表达式。

宏:与变量不同,您可以在需要“常量表达式”的上下文中使用宏,例如数组长度或switch语句。

变量:与宏不同,你可以得到指向变量的指针。

“常量表达式”其实很实用,所以经常被定义为宏。变量更适合于更大或更复杂的值,例如结构实例。

宏与内联函数

宏可以有参数,可以扩展到C代码。

与函数相比,宏有以下优点:

宏生成的代码相当于直接粘贴到周围的代码中,不像函数需要调用指令。这使得代码运行得更快,因为函数调用需要额外的开销;

宏不需要指定类型。例如,任何数字类型都可以执行x+y运算。如果你写一个函数,必须声明参数,指定类型,比如类型的大小,是否有符号,所以它的用途是有限的。

缺点:

参数需要反复计算。假设我们有一个宏MY_MACRO(x)。如果在定义中多次使用X,表达式X会因为简单的复制粘贴而重复计算。相比之下,函数的参数表达式只需要计算一次,然后将结果传递给函数。

宏更容易出错,因为它们在源代码级别。尽量用括号,把宏的整个定义和每个参数都放在括号里,这样表达式就不会意外合并。

//不建议这样写:#define MY_MACRO(x) x+x//应该写成#define MY_MACRO(x) ((x)+(x))除非需要多个泛型,否则可以直接定义一个静态的内联函数,这样可以两者兼得。内联意味着函数中的代码应该直接编译到它被使用的地方,而不是被调用。您可以将静态内联函数放在头文件中,就像宏一样。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论