学c语言之前应该学的知识,学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
整数
尺寸
算术运算和整数提升
char类型符号
宏和常量变量
宏和内联函数
学习资源
教程C点:基础知识介绍
Awesome-c:库和工具列表
cppreference:C语言和标准库技术参考
一个值得学习的项目
在学习时,阅读一些C 代码可能会有所帮助。
Bloopsaphone:一个以小型C 模块为中心的声音合成Ruby 库。概念很少,结构非常好。
简单动态字符串(sds):学习C 语言的一个很好的示例,其中的.c 和.h 文件展示了如何管理更复杂的资源。
Brogue CE:Roguelike 视频游戏。这个库比较大,大约有3万行代码。我维护这个代码库,有很多贡献者都是C 语言专家。
stb单文件库:包含大量中小型C模块,主要用于嵌入式设备和游戏机。
编译、链接、标头和符号
这里我们将介绍如何编译C语言的一些基础知识。
C语言代码写在源文件.c中。每个源文件都被编译成目标文件.o。它就像一个在.c 文件中加载编译函数的容器。然而,这些函数是不可执行的。目标文件内有一个符号表,这些符号是文件内定义的全局函数和变量的名称。
# 编译为对象cc -c thing.c -o thing.occ -c stuff.c -o stuff.o
源文件是完全独立的,可以并行编译成对象。
如果要在文件之间调用函数或变量,则必须使用头文件(.h)。这些文件也是C源文件,但它们的使用方式特殊。回想一下,目标文件仅包含全局函数和变量的名称,而不包含类型、宏,甚至函数参数。如果在整个文件中使用这些符号,则必须指定附加信息。将这些“声明”分别放在.h 文件中,并#include 它们到其他.c 文件中。
为了避免重复,c 文件通常不定义自己的类型/malos 等,而仅包含其自身或其所属模块或组件的头文件。
头文件可以被认为是API 的规范,只不过实现可以放置在多个源文件中。您还可以在同一个头文件中实现不同的平台和用途。
如果在编译过程中遇到仅声明但未定义的符号引用(例如在头文件中),则编译后的目标文件会将其标记为丢失并且必须嵌入。
这项工作的最后一部分是由编译器的“链接器”组件执行的。该组件负责链接一个或多个对象,匹配所有符号引用,并输出完整的可执行文件或共享库。
# 将对象链接到可执行文件cc thing.o thing.o -o gizmo
综上所述,C语言源文件不能包含其他源文件。它只能包含声明,链接器将完成匹配。
过时的功能
尽管C语言有着悠久的发展历史,并且一直努力向后兼容,但仍然有一些特性是应该避免的。
atoi() 和atol():这两个函数出错时返回0,这也是有效的返回值。我个人推荐strtoi()。
gets() :这些函数无法提供目标缓冲区的边界,因此它们是不安全的。就我个人而言,我更喜欢fgets()。
数组不是一个值
在学习C语言的过程中,你需要认识到C语言只处理已知大小的数据块。您可以将C 视为“用于复制已知大小的值的语言”。
您可以将整数和结构传递给程序,通过函数返回它们,并将它们视为相应的对象。由于C 知道它们的大小,因此它可以编译代码并复制完整的数据。
然而,数组却完全不同。在C语言中,数组的大小是未知的。当你在函数中声明一个变量int[5] 时,你实际得到的并不是一个int[5] 类型的值,而是一个指向5 个整数被赋值位置的int* 值。由于这只是一个指针,因此程序员必须复制实际数据,而不是语言,并负责确保数据有效。
但是,结构内的数组可以与结构一起复制,值也可以。
(严格来说,给定大小的数组是真实类型,而不仅仅是指针。例如,您可以使用sizeof 找出整个数组的大小。但是,将它们视为独立值是可以的' t。)
编译器选项
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\', 数据[1]);}
命名约定
C语言不支持命名空间。如果您创建公共库或想要命名您的“模块”,则必须为所有公共API 的名称添加前缀。这些名称包括:
功能
类型
枚举值
大的
此外,每个枚举类型应该有不同的前缀,以便您可以区分特定值属于哪个枚举类型。
枚举颜色{COLOR_RED,COLOR_BLUE,}
命名方面并没有太多规则。随意使用snake_case或CamelCase,但记住要保持一致。许多标准C 类型采用ptrdiff_t、int32_t 等形式,因此有人将类型命名为my_type_t。
静态函数或文件级静态变量只能在文件内访问。这些函数或变量不会导出为符号,并且不能在其他源文件中使用。
static 也可用于局部变量,允许变量的值在多个函数调用之间保持不变。您可以将其视为仅限于该函数的全局变量。您可以使用static 来计算数据并存储它以便在后续调用中重用。但是,请注意,这种用法会遇到与全局或共享状态相同的问题,例如线程安全和递归争用。
结构方法模式
如果您在学习C 之前学习了更具体的语言,您可能会发现很难将该知识应用到学习C 中。例如,面向对象编程中的一个常见概念:结构体方法或函数接受指向结构体的指针,并通过该指针修改该结构体或检索属性。
typedef struct {int x;int y;} vec2;void vec_add(vec2 *u, const vec2 *v) {u-x +=v-x;u-y +=v-y;}int vec_dot(const vec2 *u, const vec2 *v) {返回u-x * v-x + u-y * v-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++ 接口C++/WinRT——)。
(Mac OS API 是Objective C(现在是Swift),而不是C。)
第三方库安装在标准位置。
您可以通过独立于平台的头文件来使用更多特定于平台的代码,这些头文件可以通过多种方式实现。许多流行的C 库本质上都是精心设计的抽象,集成了特定于平台的功能。
整数
C 中的整数是一个巨大的陷阱。编写代码时要小心。
大小所有整数类型都有一定的最小位数。在某些常见平台上,整数的大小超出了最小位数。例如,在Windows、macOS 和Linux 上,int 为32 位,但最小位数为16 位。编写可移植代码时,必须注意确保整数不超过最小位数。
如果要精确控制整数的大小,可以使用stdint.h 中的标准类型(int32_t、uint64_t 等)。还有_least_t 和_fast_t 类型。
算术运算和整数提升C 算术运算有许多奇怪的规则,会产生意外且不可移植的结果。
另外,要特别小心整数提升。
对char 类型进行签名默认情况下,所有其他整数类型都是有符号的,但char 可以有符号或无符号,具体取决于平台。因此,该类型仅在用作角色时才可移植。如果您指定一个非常小的数字(例如仅8 位数字),请同时指定符号。
宏和常量变量
如果您想定义一个非常简单的常量值,您有两种选择。
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))
除非您需要多个泛型,否则您可以直接定义静态内联函数并获得两全其美的效果。内联意味着函数内的代码必须在使用的地方直接编译,而不是被调用。与宏类似,静态内联函数可以放置在头文件中。
天地劫幽城再临归真4-5攻略:第四章归真4-5八回合图文通关教学[多图],天地劫幽城再临归真4-5怎么样八回合内通
2024-04-03