我要是在学习 C 语言之前知道这些就好了

news2024/12/23 22:56:35

学习 C 语言好难啊。这门语言本身的基础知识并不是很难,但是“用 C 语言编程”需要用到各种知识,这些知识可没有那么容易掌握:

  • C 语言在各个平台和操作系统上的行为有所差异,因此你需要了解平台;

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

  • C 语言涉及很多与 CPU、操作系统、编译代码有关的概念;

  • C 语言的使用方式多种多样,远不像其他语言那样有中心化的社区和统一的风格。

C 语言的学习要点和建议

  • 学习资源

  • 值得借鉴的项目

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

  • 不推荐使用的功能

  • 数组不是值

  • 编译器的各种选项

  • 三种类型的内存,以及何时使用它们

  • 命名约定

  • static

  • 结构方法模式

  • const

  • 平台和标准 APII

  • 整数

  • 大小

  • 算术运算与整数提升

  • char 类型的符号

  • 宏与 const 变量

  • 宏与内联函数

1、学习资源

  • TutorialsPoint C:基本知识介绍

  • awesome-c:库和工具列表

  • cppreference:C 语言和标准库的技术参考

2、值得借鉴的项目

学习过程中,阅读 C 语言代码会很有帮助。

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

  • Simple Dynamic Strings(sds):有一个 .c 和 .h 文件,是一个很好的学习C语言的例子,说明了如何管理更复杂的资源;

  • Brogue CE:一款类 Roguelike 视频游戏。这个库相对较大,大约有3万行代码。我正在维护这个代码库,而且我们还有许多贡献者都是C语言高手;

  • stb 单文件库:其中包含许多中小型 C 模块,主要面向嵌入式设备和游戏机。

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

下面关于如何编译 C 语言的基础知识:

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

# compile to objects
cc -c thing.c -o thing.o
cc -c stuff.c -o stuff.o

源文件之间是完全独立的,可并行编译成对象。

如果想跨文件调用函数和变量,则必须使用头文件(.h)。这些文件也是 C 源文件,只不过使用方式比较特殊。回顾一下,目标文件只包含全局函数和变量的名称,没有类型、宏,甚至没有函数参数。如果想跨文件使用这些符号,就需要指定额外的信息。我们将这些“声明”单独放在 .h 文件中,然后由其他 .c 文件通过 #include 包含进来。

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

你可以将头文件视为 API 的规范,只不过实现可以放在多个源文件中。你甚至可以在同一个头文件中实现不同的平台或目的。

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

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

# link objects to executable
cc thing.o stuff.o -o gizmo

概括起来,C语言的源文件中不能包含其他源文件,只能包括声明,然后由链接器完成匹配。

4、不推荐使用的功能

C 语言拥有悠长的发展历史,尽管 C 语言一直在努力实现向后兼容,但仍有一些功能是我们应该避免使用的。

  • atoi()与atol():这两个函数在出错时会返回 0,但这也是一个有效的返回值。个人更推荐 strtoi() 等。

  • gets() :不安全,因为这些函数无法给出目标缓冲区的界限。个人更喜欢 fgets()。

5、数组不是值

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

我们可以向程序传递整数或结构,通过函数返回它们,并将它们视为相应的对象,因为 C 知道它们的大小,因此 C 可以编译代码,并复制完整的数据。

然而,数组却完全不同。对于 C 语言来说,数组的大小是未知的。假设我在一个函数中声明了一个变量 int[5],实际上我得到的并不是类型 int[5] 的值,而是一个 int* 值,它指向的位置分配了 5 个整数。由于这只是一个指针,因此程序员必须代替语言来负责复制真正的数据并保证数据有效。

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

(严格来讲,指定了大小的数组是真正的类型,而不仅仅是指针,例如你可以通过 sizeof 得知整个数组的大小。只不过,你不能将它们视为独立的值。)

6、编译器的各种选项

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

  • -O2:在发布代码时,对代码进行优化。

  • -g -Og:用于调试代码,可以让调试器输出额外的信息,并根据调试进行优化。

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

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

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

  • -std=...:选择一个标准。在大多数情况下,你可以省略这个选项,使用编译器的默认值(通常是最新标准)。如果你想使用“经典”C,可以指定 -std=c89。

7、三种类型的内存,以及何时使用它们

自动存储:用于保存局部变量。当函数被调用时,就会创建一个新的自动存储区域,并在函数返回结果时删除。只有返回值会被保留,并被复制到调用它的函数的自动存储中。这意味着,返回一个指向局部变量的指针是不安全的,因为底层数据会被默默删除。自动存储通常被称为“栈”。

分配的存储:运行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]);
}

8、命名约定

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

  • 函数

  • 类型

  • 枚举值

另外,每个枚举也应该加上不同的前缀,这样才能分辨某个值属于哪种枚举类型:

enum color {
COLOR_RED,
COLOR_BLUE,
...
}

关于命名,并没有太多真正的约定,你可以随意选择蛇形命名法(snake_case)或驼峰式命名法(camelCase),但请记住保持一致!由于许多标准 C 类型都采用了 ptrdiff_t、int32_t  等形式,所以有人将类型命名为 my_type_t。

static

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

static 也可以用在局部变量上,可以让变量在多次函数调用之间保持值不变。你可以将其视为一个仅限于该函数使用的全局变量。你可以利用 static 计算和存储数据,以供后续调用重用。但请记住,这种使用方法与全局状态或共享状态有同样的问题,例如线程安全、递归冲突等。

9、结构方法模式

如果你在学习 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) {
return u->x * v->x + u->y * v->y;
}

你无法扩展结构或实现类似于面向对象的功能,但采用这种思路来思考问题很有用。

const

以 const T 的形式声明类型 T 的变量或参数,则表示这个变量或参数不能被修改。这意味着,不能赋值,而且如果 T 是指针或数组类型,也不能被修改。

你可以将 T 转换为 const T,但反之不行。

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

10、结构方法模式

我们很难根据 #include <some_header.h> 来判断依赖项究竟是什么,它有可能来自:

  • 标准 C 库(缩写为“stdlib”)。比如:stdio.h、stdlib.h、error.h。

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

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

  • POSIX:操作系统 API 的标准。比如:unistd.h、sys/time.h。

  • 一般由 Linux、macOS、BSDs 实现。

  • 默认情况下,不可在Windows使用。如果使用 MinGW,则可以使用 POSIX API。如果想获得更完整的支持,可以使用 Cygwin 库。

  • 你可以通过官方的OpenGroup页面或帮助手册,查看POSIX头文件的所有详细信息(包括 C stdlib)。

  • 非标准操作系统接口。

  • 特定于 Linux 的 API。

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

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

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

你可以通过不依赖于平台的头文件与更多平台特定的代码进行交互,这样就可以通过不同的方式实现。许多流行的 C 库本质上只是对特定于平台的功能进行了统一的、精心设计的抽象。

11、整数

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

大小

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

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

算术运算与整数提升

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

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

char 类型的符号

所有其他整数类型默认都有符号,但char可以有符号,也可以没有符号,具体取决于平台。因此,只有在作为字符时,这种类型才可移植。如果你想指定一个很小的数字,比如只有8位,也要指定符号。

12、宏与 const 变量

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

static const int my_constant = 5;
// 或者
#define MY_CONSTANT 5

二者的不同之处在于,前者是一个真正的变量,而后者是一个复制粘贴的行内表达式。

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

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

“常量表达式”实际上非常实用,因此常常被定义为宏。而变量则更适合更大或更复杂的值,如结构实例。

13、宏与内联函数

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

相较于函数,宏的优势在于:

  • 宏产生的代码相当于直接粘贴到周围的代码中,而不像函数需要一个调用指令。这样代码的运行速度更快,因为函数调用需要额外的开销;

  • 宏不需要规定类型。例如,任何数字类型都可以执行 x + y 运算。如果写成函数,就必须声明参数,并指定类型,比如类型大小、是否有符号,因此使用很有限。

缺点:

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

  • 宏更容易出错,因为它们是源代码级别。尽可能多使用括号,将宏的整个定义和每个参数都放到括号内,这样表达式就不会不小心被合并。

// 不推荐这种写法:
#define MY_MACRO(x) x+x
// 应该写成:
#define MY_MACRO(x) ((x)+(x))

除非你需要多种泛型,否则可以直接定义静态内联函数(static inline function),这样就可以兼具二者的优点。内联表示,函数中的代码应该直接编译到使用的地方,而不是被调用。你可以将静态内联函数放在头文件中,就像宏一样。

c语言基础学习的个人空间-c语言基础学习个人主页-哔哩哔哩视频哔哩哔哩c语言基础学习的个人空间,提供c语言基础学习分享的视频、音频、文章、动态、收藏等内容,关注c语言基础学习账号,第一时间了解UP注动态。每天分享一个编程技术C/C++游戏源码素材及各种安装包:724050348 私信不常看!https://space.bilibili.com/2061978075?spm_id_from=333.788.0.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud+SpringCloud Alibaba(尚硅谷2020版)

文章目录1.传统单体架构和微服务架构的对比1.1传统单体架构1.1.1传统单体架构概述1.1.2单体架构的特点1.1.3单体架构的优点1.1.4单体架构的缺点1.2微服务架构1.2.1微服务架构概述1.2.2微服务架构的特点1.2.3微服务架构的优点1.2.4微服务架构的缺点2.SpringCloud简介3.SpringClo…

Service入门

Service入门 1.什么是Service 1.1手机中的Service案例 1.2为什么会有service&#xff1f; 由于手机的原因一个时刻只允许一个激活状态的Activity&#xff0c;其余的处于未激活 后台服务机制应运而生 1.3Service简介 四大组件之一没有UI界面&#xff0c;后台服务&#xff0c;长时…

Word处理控件Aspose.Words功能演示:在 Python 中将 Markdown 转换为 PDF

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

人物百度百科创建:建立人物百度百科都需要什么资料?

人物百度百科词条就像一张网络名片&#xff0c;词条上面的内容是非常具有权威性和公信力的。创建一个百度百科词条&#xff0c;可以帮助你很好地打造个人ip&#xff0c;尤其是在当今互联网时代&#xff0c;ip的打造对于个人的重要性是不言而喻的。 如果你想要提升自己的知名度…

2023年全国管理类联考综合试卷真题及解析

1.油价上涨5%后&#xff0c;加一箱油比原来多花20元&#xff0c;一个月后油价下降了4%&#xff0c;则加一箱油需要花( )钱 A.384元 B.401元 C.402.8元 D.403.2元 E.404元 2.已知甲、乙两公司的利润之比为3&#xff1a;4&#xff0c;甲、丙两公司的利润之比为1&am…

二硬脂酰磷脂酰乙醇胺-聚乙二醇-巯基吡啶 DSPE-PEG-OPSS;常用于脂质体的合成

DSPE-PEG-OPSS 、OPSS-PEG-DSPE 巯基吡啶聚乙二醇磷脂、磷脂聚乙二醇巯基吡啶 中文名称 &#xff1a;磷脂聚乙二醇巯基吡啶、巯基吡啶聚乙二醇磷脂 简称 &#xff1a;OPSS O-PEG-DSPE、DSPE-PEG-OPSS 外观 &#xff1a;白色液体、半固体或固体&#xff0c;取决于分子量。 …

Spring之底层架构核心概念-Environment

文章目录1.Environment作用2.用法2.1 systemEnvironment 和 systemProperties2.2 MutablePropertySources3.实际用法4.总结1.Environment作用 Environment&#xff1a;获取环境变量 2.用法 2.1 systemEnvironment 和 systemProperties public static void main(String[] ar…

【UE4 第一人称射击游戏】13-瞄准开火

上一篇&#xff1a; 【UE4 第一人称射击游戏】12-全自动步枪并显示剩余弹药量 本篇效果&#xff1a; 步骤&#xff1a; 1.打开“ThirdPersonCharacter”&#xff0c;添加一个骨架网格体组件 将其作为Mesh&#xff08;继承&#xff09;的子级 父项套接字选为“Weapon_Attach”…

2022需求最大的 8 种编程语言:Python第二,ta居榜首

一直以来&#xff0c;编程语言都是程序员非常关注的话题。虽然有超过200编程语言&#xff0c;但其中只有少数用于现实世界的行业。 2022年也快接近尾声&#xff0c;今年最受欢迎使用最多的语言有哪些呢&#xff1f; 从2021年10月到2022年11月&#xff0c;DevJobsScanner分析了…

国内葡萄酒行业数据浅析

大家好&#xff0c;这里是小安说网控。 葡萄酒是最为常见的果酒&#xff0c;在国内酒品市场上占据着一席之地。近年来&#xff0c;受整体经济环境影响&#xff0c;葡萄酒行业的各项数据都不甚理想。 今年&#xff0c;1-10月份&#xff0c;国内葡萄酒产值&#xff0c;无论是当期…

[ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

skywalking 快速入门

一、skywalking简介 1、skywalking介绍 Skywalking是一个国产的开源框架&#xff0c;2015年有吴晟个人开源&#xff0c;2017年加入Apache孵化器&#xff0c;国人开源的产品&#xff0c;主要开发人员来自于华为&#xff0c;2019年4月17日Apache董事会批准SkyWalking成为顶级项…

村政府小型网站

小型网站架构 编程工具&#xff0c;编程语言&#xff0c;编程细节 编程工具 idea最好的java项目编程工具&#xff0c;集合springboot框架。 编程语言 java,jsp,数据库表编程 编程细节 配置springboot&#xff0c;pom.xml相关配置文件&#xff0c;熟练使用idea以及相关目…

再学C语言15:其他运算符

C大约有40个运算符 一、sizeof运算符和size_t运算符 sizeof运算符&#xff1a;以字节为单位返回其操作数大小&#xff1b;操作数可以是一个具体的数据对象&#xff08;如一个变量名&#xff09;或者一个类型&#xff08;如float&#xff09;&#xff1b;如果数据对象是一个类…

unity前端通过java后端实现将图片上传到阿里云OSS并在前端显示

一、开通对象存储OSS服务 阿里云-搜索推荐 &#xff08;1&#xff09;申请阿里云账号 &#xff08;2&#xff09;实名认证 &#xff08;3&#xff09;开通对象存储oss服务 &#xff08;4&#xff09;进入管理控制台 &#xff08;5&#xff09;创建bucket &#xff08;6&a…

基于注解的自动装配之@Autowired的原理及注意事项

基于注解的自动装配之Autowired的原理及注意事项 1. Autowired的原理 首先根据所需要的组件类型到IOC容器中查找 能够找到唯一的bean&#xff1a;直接执行装配 如果完全找不到匹配这个类型的bean&#xff1a;装配失败 和所需类型匹配的bean不止一个 没有Qualifier注解&#…

YGG 与 Mike Ovecka 对 Web3 电子竞技的愿景

&#xff08;从左到右&#xff09;YGG 内容创作者 Een Mercado、Ampverse Web3 负责人 Sascha Jochum、Axie Infinity 菲律宾负责人 Nix Eniego、MetaSports 联合创始人兼 CEO Joe Josue 和 YGG 电子竞技负责人 Mike Ovecka 在 Philippine Web3 Festival 的“区块链游戏电子竞…

MybatisPlus代码生成器配置(处理blob等类型)

一&#xff1a;新建springboot项目 二&#xff1a;导包 <dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.3.2</version></dependency><depe…

ATJ2158界面绘制LVGL---定时器

LVGL定时器 在lv_timer.c中有相关的操作函数 Create a timer&#xff08;创建定时器&#xff09; LVGL 有一个内置的定时器系统。可以注册一个函数以定期调用它。定时器在lv_timer_handler()中被处理和调用&#xff0c;它需要每隔几毫秒调用一次 Ready and Reset&#xff0…

fio磁盘压测工具

文章目录背景安装测试如何模拟现实中的IOfio参数解释【1】随机读测试【2】随机写测试【3】 顺序读测试【4】 顺序写测试numjobs5 测试随机读随机写顺序读顺序写blktracefio 模拟真实IO场景查看 blktrace 记录的结果将结果转化为二进制文件使用 fio 重放日志结论背景 因为是虚拟…