C 语言是什么
计算机只能识别由0和1组成的机器语言,但使用机器语言来进行编程非常的不方便,程序员需要记住一系列0和1组成的命令,学习成本非常高,因此在早期,编写计算机的程序员往往都是高等院校的研究员。
后来在机器语言的基础上出现了汇编语言,汇编语言使用一些英文单词来代替机器语言由0和1组成的命令,这使得汇编语言在学习成本和编写程序的效率上远高于机器语言。但是汇编语言编写的程序需要先转换成机器语言,再由计算机执行,这使得机器语言编写的程序效率高于汇编语言。
随着计算机软件、硬件的发展,又出现了一系列的高级编程语言,高级编程语言相比汇编语言,高级编程语言对程序员更友好,程序的执行效率会比汇编语言稍低,不过由于现在的计算机执行速度很快,因此这点执行效率并不会让用户感到程序执行慢。
而 C 语言就是一种高级编程语言。
C 语言能干什么
C语言主要应用于底层开发,和硬件打交道,如操作系统,嵌入式开发,或者要求效率,高可移植性的地方。比较常见的 C 程序就是操作系统、驱动程序。
C 语言如何编程
为了让读者对编程有大概的了解,我们把编写 C 程序的过程分解为 7 个步骤,如下图所示。需要注意的是,这是理想状态,在实际的使用过程中,尤其是在较大型项目中,可能要做一些重复的工作,根据下一个步骤的情况来调整或改进上一个步骤。—— 《C Primer Plus》
- 确定程序目标。想要做什么事、需要什么信息(输入信息)、需要进行那些计算、获得什么结果(输出信息)
- 设计程序。如何表示数据、用什么方法处理数据、怎样组织程序、用户界面是什么样的、…
- 编写代码。此时才是真正开始书写代码。
编译源代码。编译的作用:检查C语言程序的编写是否有错误;将源代码编译为计算机能够识别的指令集(即目标代码文件);将源代码编译后的文件和C库代码、启动代码合并为可执行文件。
PS:通过编译的代码叫做目标代码文件,目标代码文件不能直接运行,还缺少启动代码和库函数,需要通过链接将其合并为一个文件。
运行程序。编译之后会生成一个可执行文件(exe),可通过双击的形式直接在操作系统中运行。
- 测试和调试程序。两个名词:bug —— 程序中的一些错误;调试 —— 查找并修复程序错误的过程。
- 维护和修改代码。创建完程序之后,发现程序有错或者想要扩展程序用途,就需要修改程序。
C 语言的编译
Q:为什么要编译?
C语言只是我们人能读懂的语言,在计算机眼中C语言就是外星文,根本看不懂,计算机只能识别机器语言,所以我们需要将C语言“翻译”为机器语言。
这里就有两点:1. 为什么不直接用机器语言编程,而是使用C语言?2. 如何将C语言翻译为机器语言?
1.为什么不直接用机器语言变成而是使用C语言?因为机器语言是一串0、1码,非常难以记忆和阅读,是人难以理解的一种语言,但是对于计算机而言就是非常容易理解的;而C语言则相反,C语言使用人比较容易理解的方式,而计算机是无法理解的。
2.如何将C语言翻译为机器语言?C语言是由编译器来将我们写的C语言程序翻译为计算机可以理解的指令集 —— 这就是编译的过程。
Q:编译有什么好处?
1.使用人容易理解的语言来进行编程,提高编程效率。
使用C语言编程的过程可以理解为写文件的时候使用中文写,然后找专业的翻译人员将其翻译为英文,然后将这个英文文件交由处理人员,处理人员就可以根据你写的这份英文文件的步骤进行操作。而使用机器语言编程的过程就需要你查找你陌生的英文将其翻译为英文文件,交由处理人员。可以想象,对于一个对英文不熟悉的人来说,这效率可以说非常低了。
2.可移植性。
之前讲过,编译就是将C语言编译为计算机可以识别的指令集,说到指令集,我们要知道不同 CPU 的指令集是不同的,比如 Intel 和 AMD 的 CPU 指令集就完全不同,这代表着在 Intel 的 CPU 中可以运行的指令集放在 AMD 的 CPU 中可能就完全无法理解需要做什么。
程序的可移植性的意思就是我们写好的源代码移动到另一台计算机中依旧可以执行。
C语言的可移植性是如何保证的?不同的CPU使用的指令集不同,通过不同的编译器,可以将同一个C语言代码转换成不同CPU可以识别的机器语言指令集程序,以适应不同的CPU环境。这样我们将源代码移动到另一台计算机中,在该计算机中会将其编译为这台计算机可以理解的指令集。
编译型语言和解释型语言
计算机不能直接理解高级编程语言,必须把高级编程语言翻译为机器语言。这个翻译的方式有两种:编译和解释。之前我们讲的就是编译,编译和解释只是在翻译的时机不同 。
编译:编译型语言是使用编译器将源代码编译链接为可执行文件,点击可执行文件即可运行这个文件。翻译的过程是在程序运行之前。
解释:解释型语言是使用解释器在运行时逐条将其翻译为机器语言。翻译的过程是在程序运行过程中。
PS:不管是编译型语言还是解释型语言都可以进行编译和解释,前提是有这样的编译器和解释器。因此各种编译语言本质没有不同,所谓的编译型和解释型只是执行机制的不同。
Q:编译型语言和解释型语言有什么特点?
解释型语言由于是在运行时进行“翻译”,因此程序运行效率较低(边运行边翻译),但是每次修改源代码不需要重新编译;而编译型语言在运行之前进行“翻译”,因此程序运行效率较高(只运行),但每次修改源代码之后需要进行重新编译。
编译型语言和解释型语言举例:编译型语言举例:C、C++ 等;解释型语言举例:Python、JavaScript 等。
学习编程的过程中,一定会听到另一个大名 —— Java,Java 有点特殊,Java 先将源代码编译为 .class 文件,然后再由 JVM 在程序运行时来将 .class 文件解释为机器语言。因此 Java 是编译型语言还是解释型语言是有争议的。我比较倾向于 Java 是编译型语言。
入门代码 —— Hello, World
Hello, World 代码
学习编程的第一个程序一般都是经典的控制台输出 Hello, World! 的程序,这表示我们开启了一个编程的大门,进入到编程世界。
#include <stdio.h>
int main(void)
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
表示后面的是预处理语句
include 表示包含另一个文件,这里包含的是 stdio.h 文件
int main(void)
,int 表示返回值是 int 类型,() 中的 void 表示没有参数。这条语句还可以写成int main()
或者void main(void)
或者void main()
如果返回值类型为 void ,程序{ }中最后的 return 语句需要去掉,但是需要注意的是后面两种写法可能会碰到某些编译器不识别的问题。所以推荐写第一种,第二种其实也可以,int main() 是 C99 标准之前的写法,而 int main(void) 是 C99 标准之后的写法。
注释
C语言提供两种注释 —— 单行注释和多行注释
- 单行注释:以 // 开头,直到本行最后都是注释
- 多行注释:以 / 开头,/ 结尾的都是注释。
Java 中除了当行注释、多行注释之外,还提供了一个文档注释 /* /。
程序可读性
刚才的 Hello, World 程序很简短,但是对于复杂的程序,代码可能有成千上万行,如果程序的可读性很差,那么在维护代码的时候效率会非常低。
提升程序的可读性:
- 选择有意义的变量名或者函数名
- 写注释
- 在函数中用空行分割概念上的多个部分
- 每个语句占一行
环境
C 和指针》第2章的笔记。
在 ANSI C 的任何一种实现中,存在两种不同的环境。第一种是翻译环境,在这个环境里,源代码被转换为可执行的机器指令。第二种是执行环境,它用于实际执行代码。
标准明确说明,这两种环境不必位于同一台机器上。例如,交叉编译器就是在一台机器上运行,但它产生的可执行代码运行于不同类型的机器上。操作系统也是如此。
翻译环境
翻译阶段由几个步骤组成:
- 组成一个程序的每个源文件通过编译器进行编译分别转换为目标代码。
- 各个目标代码文件由链接器链接在一起,形成一个单一完整的可执行程序。链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它也可以搜索程序员个人的程序库,将其中需要使用的函数也链接到程序中。
Q:链接函数的实现时,只链接那些调用过的函数吗?还是所有声明过的函数的实现都会被链接?
其中编译过程本身也由几个阶段组成:
- 预处理阶段。在这个阶段中,预处理器在源代码上执行一些文本操作。例如,用实际值代替由 #define 指令定义的符号以及读入由 #include 指令包含的文件的内容。
对源代码进行解析,判断它的语句的意思。在这个阶段是产生绝大多数错误和警告信息的地方(运行阶段是另一个产生错误的阶段)。
执行环境
程序的执行过程也需要经历几个阶段:
程序必须载入内存中。在宿主环境中(也就是具有操作系统的环境),这个任务由操作系统完成。在独立环境中,程序的载入必须由手工安排,也可能是通过把可执行代码置入只读内存(ROM)来完成。那些不是存储在堆栈中的尚未初始化的变量将在这个时候得到初始值。
- 程序开始执行。在宿主环境中,通常一个小型的启动程序和程序链接在一起,它负责处理一系列日常事务,接着调用 main 函数。在绝大多数机器里,程序将使用一个运行时堆栈,它用于存储函数的局部变量和返回地址。程序同时使用静态内存,存储于静态内存中的变量在程序的整个执行过程中将一直保留它们的值。
- 程序的终止。
词法规则
词法规则就像是英语中的拼写规则,决定你在源程序中如何形成单独的字符片段,也就是标记。
字符
标准并没有规定 C 环境必须使用哪种特定的字符集,但它规定了字符集必须包括英文所有的大写字母和小写字母、数字 0~9、空白字符、三字母词、转义字符,以及下面的这些符号:
( ) [ ] { } < > “ ‘ + - * / % = \ ! ^ ~ # , . ? : ; _ |
空白字符
空格、换行符、水平制表符、垂直制表符和格式反馈字符称作空白字符。因为它们被打印出来时,在页面上出现的是空白而不是各种记号。
三字母词
标准还定义了几个三字母词,三字母词就是三个字符的序列,合起来表示另一个字符。三字母词是以两个问号?开头再尾随一个字符的形式来表示。因为这种形式一般不会出现在其他表达行驶中,不致于引起误解。下面列举一些三字母词:
三字母词 | 代表的字符 | 三字母词 | 代表的字符 |
---|---|---|---|
??( | [ | ??) | ] |
??< | { | ??> | } |
??! | | | ??’ | ^ |
??= | # | ??/ | ~ |
??- | ~ |
Q:为什么要用三字母词?
A:因为三字母词使 C 环境可以在某些缺少一些必须字符的字符集上实现。
转义字符
当你在编写某些 C 源代码时,你在一些上下文环境里想使用某个特定的字符,却坑能无法如愿,因为该字符在这个环境里有特别的意义。例如,双引号 “ 用于定界字符串常量,你如何在一个字符串常量内部包含一个双引号呢?转义字符就是用于克服这个难题的。
概念:用于表示难以表示或无法输入的字符,每个转移字符都以反斜杠()开始。
常用转义字符:1. \n 表示换行;2. \t 表示 Tab 键;3. \b 表示退格键。
转义字符由一个反斜杠 \ 加上一个或者多个其他字符组成,表示 \ 后面的字符是被转换了意义的。例如,? 在书写连续多个问号时使用,防止它们被解释为三字母词;” 用于表示一个字符串常量内部的双引号;\ddd,其中 ddd 表示 1~3 个八进制数字。这个转义符表示的字符就是给定的八进制数值所代表的字符。\xddd,和上例类似,只是八进制数换成了十六进制数。
标识符
标识符就是变量、函数、类型等的名字。它们由大小写字母、数字和下划线组成,但不能以数字开头。C 是一种区分大小写的语言。标识符的长度没有限制,但标准允许编译器忽略第 31 个字符以后的字符。[
](https://blog.csdn.net/qq_40395874/article/details/115935351)