📔 Lecture03 C Intro: Basics
更新日期:2023.11.02
1. 概述和背景¶
1.1 计算机结构的历史¶
-
ENIAC(1946年)
- 当时第一台电子通用计算机,也是为了早期弹道轨道的计算。
- 用于太多的真空管,重启新的程序需要2~3天。
-
EDSAC(1949年)
- 剑桥大学的edsack是第一台通用存储用途的计算机。
- 存储概念的计算机诞生。
1.2 C语言¶
- 书籍推荐:
- K & R 《C语言程序设计》
对于C语言,可以用来写操作系统,而不是直接使用汇编语言去进行。
对于UNIX是一个可移植操作系统。
对于C语言来说,可以编写C代码,然后将其移植到不同的体系结构。但是对于汇编语言来说,因为体系结构的更新,移植性就很差。
对于课程来说,使用的是C语言的原因:
- 可以使用C语言去探索更底层的机器特性。例如:
- 内存管理
- 特殊指令
- 并行性
对于语言来说,语言的抽象层次结构处在越高的位置,那么在编程中放弃的控制权就会越多。
其他C语言来说,也是现目前最流行的编程语言之一。但如果说需要追求安全性角度考虑,可以使用Rust或者Go
-
Rust
- 可以将解决指针和内存泄漏的问题。也可以使得代码的清晰度增加。
- 因为C是弱类型语言,编译器不会类型检查的操作。所以使用Rust(强类型语言)可以解决类型检查的问题。
-
Go
- 可以执行多个core和线程的轻量级的编程语言。
2. 编译器和解释器¶
计算机运行程序的两种方式:编译和解释
。
C编译器可以直接将C程序直接映射为特定体系结构的机器代码(0和1组成的字符串)
-
C和Java不同之处,Java是直接转换为独立于体系结构的字节码,然后通过JIT进行编译。
- 架构独立性。
- 通过可移植的字节码将要传递的信息传递到不同的机器上,然后执行它。
- Java同时具有解释器和编译器。
-
C和Python环境不同,Python环境在运行时转换为字节码。
- Python是解释型的语言。
- 本质区别:程序何时转换为低级机器指令(从“解释”级别角度分析)。
-
C通常是分为两个编译过程:
- .c文件 ----> .o文件
- .o文件 ----> 与库进行链接,生成可执行文件
- 也包括了汇编,但一般情况下,汇编步骤一般直接被默认隐藏。
2.1 C编译的简要视图¶
2.2 编译的优缺点¶
-
优点
- 合理的运行时间:
- Makefiles的编译过程是重新编译已修改的内容,适用于代码分段(大小文件较多)的情况下,只需要关注已修改的内容。
- 运行时性能
- 没有语言会比C更快。C编译为原始汇编语言,一般来说是最快的。
- 但是现目前来看,Python在科学计算的角度也是很好的性能:
- 在Python中可以使用更好的库来访问GPU特定的资源。
- Python也可以调用C代码来执行功能:Cpython
- 合理的运行时间:
-
缺点
- 编译文件(包括可执行文件)都是依赖于特定的计算机体系结构(例如:MPIS、x86、RISC-V)和操作系统(例如:Windows、Linux和MacOS)。
- 必须在每个新系统上重新构建可执行文件。(代码的移植性问题,需要适配新的架构和系统)
- 对于迭代周期来说很慢。原因:
更改 ----> 编译 ----> 运行
(这是重复周期)。- 对于make的编译方式来说,只编译已修改的方式。也可以同时并行编译:
make -j
- 链接器是连续的 ----- Amdahl’s Law
- 对于make的编译方式来说,只编译已修改的方式。也可以同时并行编译:
2.3 C 预处理器¶
- 仅处理C预处理器的命令。
- 任何以 "#" 开头的内容都是C预处理器的命令。
对于宏定义,编写快速代码很有帮助,但是也会使得代码调试带来困难。例如比较常见的C宏定义就是创建一个小型函数:
3. C VS Java¶
-
ANSI C也被叫做C99 或者 C9x 标准。
- 为了安全,可以使用 "
gcc -std=c99
" 编译
- 该版本的Hightlight - 类似Java中的for循环的声明 - 注释和Java一样,可以使用//
- 变长非全局数组 -<inttypes.h>
:显式整数类型 -<stdbool.h>
:用于布尔逻辑定义 - 为了安全,可以使用 "
-
C99升级为C11(C18修复一些Bugs)
- 编译使用:
gcc -std=c11
- 该版本的Hightlight
- 支持多线程
- Unicode字符串和常量
- 因为安全漏洞,移除
gets()
- 类型通用宏(根据类型调度)
- 支持复数值
- 支持静态断言、除了创建和打开。
- 编译使用:
4. C语法¶
4.1 main函数¶
- 如果要处理函数能输入参数
4.2 bool类型:True
和False
¶
在C语言中,不同的计算方式会有不同的结果。
4.3 Typed Variables in C¶
对于变量来说,必须要声明类型。所以在使用变量时,一定要声明其对应的类型,并且不能改变它。
目的:保证变量在程序的生命周期内处于被锁定,类型不能改变。
4.4 整型:Python vs Java vs C¶
不同程序语言对于int类型的大小定义不同。
C中int应该是目标处理器最能高效处理的整数类型。对于C来说,依赖于计算机(16位、32位或者64位)。
对于sizeof()
来说,就是计算数据类型的字节宽度
。
对于int类型,Python、Java和C对大小进行了比较:
对于int类型,也会遇到类似 intN_t
和 uintN_t
格式的。
4.5 consts and Enums in C¶
-
const
类型- 常量是在声明中被分配一次类型值。
-
在程序的整个执行过程中值都不会被改变。
-
enum
类型-
枚举常量为每个常量提供一个值。
-
4.6 C中的类型化函数¶
对于所有声明的函数都必须有返回类型。必须提前明确告知函数的预期返回类型是什么。
- 返回类型可以是任何C变量类型,并放在函数名称的左侧。
-
可以指定返回值类型为 void。
- void表示不会返回任何值。
-
对于函数的参数,也需要对其值进行类型声明。
-
对于变量和函数,需要在使用前进行声明。
4.7 Structs
in C¶
struct(结构体)
是创建抽象数据类型的一种方法。
-
Typedef 允许声明定义新类型。
-
结构体是结构化的变量组
4.8 C语法:控制流¶
-
在一个函数中,C语言中的控制流和Java非常接近。
-
if-else的两种语法格式:
- if格式
- if-else
-
while相关的语法格式:
-
两者的区别:
- 对于while
来说,直接判断表达式真假,如果为真则继续执行,否则直接结束。 - 对于do-while
语句来说,会先执行一次逻辑,然后再去判断表达式的真假。
-
-
for循环语句
-
switch语句
-
goto语法
- goto不推荐使用
- 表示可以直接随机跳转到某个标签中。