写在开头
在一起开始认识C 语言之前, 来以一个抽象但又简单的视角认识一个名词: 编程
编程是什么?
在我个人看来, 编程 是人与计算机之间交流的一种方式
人可以把想法告诉计算机, 计算机能够理解人的想法并给出一定的回应
这个过程中, 计算机的回应, 完全基于人告诉计算机的想法
假如, 人告诉计算机要显示一张图片, 计算机就只显示一张图片; 告诉计算机, 要播放一首音乐, 计算机就只播放一首音乐
这是一种交流
不过, 交流, 通常是需要媒介的
在人类社会中, 人与人之间交流的媒介是语言
如果两个人要进行正确、友好地交流, 他们至少需要了解一门共同的语言, 比如: 中文. 或者需要使用可以翻译不同语言的工具
而, 人与计算机之间的交流, 也同样如此
起初, 人与计算机之间的交流通过简单又复杂的二进制进行
举一个简单的例子理解:
假设, 一个灯泡存在两种状态: 亮和灭, 一个房间存在一排八个灯泡和一个门, 以灯泡的状态来传达一定的信息
如果规定: 当灯泡的状态是 亮 灭 灭 灭 灭 灭 亮 亮 时, 表示开门; 当灯泡状态是 灭 亮 亮 亮 亮 亮 灭 灭 时, 表示关门
那么, 这八个灯泡的状态就可以充当一种交流的媒介
与计算机进行交流 是相同的思路, 告诉计算机一段特定组合的二进制数据, 让计算机可以理解这段二进制数据表示的意义, 就能与计算机进行交流
比如一段二进制数据10000011
可以表示加法, 把这段二进制数据交给计算机, 计算机就能理解需要进行加法
计算机能够理解二进制数据的能力, 是发明计算机的人赋予的, 二进制就是计算机能理解的语言
但是, 直接使用 二进制数据 与计算机交流, 意味着每个操作都需要用一长段不能明显表示意义的数字进行, 未免太费劲了
所以, 在二进制的基础上, 助记符诞生了——用一个更方便人记忆的词, 表示计算机能理解的特定二进制数据段
比如, 用 ADD
表示10000011
, 那么要让计算机理解进行加法计算, 就不用直接告诉计算机10000011
, 只需要告诉它ADD
, 它就能够理解需要进行加法
基于这种思路, 汇编语言诞生了
不过, 汇编语言不仅只由助剂符组成, 汇编语言结合了助记符、操作数、寄存器等概念, 形成一种人与计算机交流的媒介
再后来, 为了更方便与计算机进行交流, 所使用的语言也越来越向人类语言发展
直到出现了一门简明、高效的集大成者, 也就是本篇文章的重点: C 语言
如果使用C 语言与计算机进行交流, 让计算机理解要进行加法计算, 只需要使用 符号+
, 计算机就能够理解要进行加法计算
如何理解C语言是什么?
你可以把C语言或任何其他的高级编程语言, 想象成人类和计算机”都能听懂”的”普通话”
计算机的”母语”是二进制 (比如
10000011
) , 但人类用二进制和它交流就像用摩斯密码聊天一样, 效率极低而C语言, 它用人类能看懂的单词 (如
if
、for
、printf
) 和符号 (如+
、-
、=
) 作为与计算机交流的指令虽然计算机不能直接看懂, 但是可以使用工具把这些指令翻译成计算机能执行的二进制
举个栗子🌰:
你想让计算机算 1+1
:
- 使用二进制: 可能要写一长串
101010...
(晕) - 使用C语言: 直接写
1 + 1
, 编译器 (翻译官) 会帮你把1 + 1
转成二进制
前一篇文章详细介绍了 C 语言环境的搭建, 以及最基本的使用, 在正式阅读下面内容之前, 可以先阅读前一篇文章
从打印Hello World
开始
下面尝试编写并运行这个可能是你接触到的第一个C 语言代码
打开终端, 运行WSL
, 使用neovim
创建main.c
文件, 在文件中输入内容:
#include <stdio.h>
int main() {
printf("Hello World");
return 0;
}
保存并关闭文件, 执行下边这个命令, 将C 语言代码编译成可执行程序:
gcc main.c
你可以发现, 当前的路径下多出来一个文件a.out
这个文件与main.c
文件的颜色大概率有些不一样
这个a.out
文件是可以运行的, 你可以将它当作Windows
中的.exe
文件
gcc
是C 语言代码的一种编译器可以使用
gcc
将人可以看懂的C 语言代码, 编译成操作系统可以看懂的二进制然后操作系统才能去执行
在命令行输入./a.out
, 并按下回车:
可以看到命令行中出现了Hello World⏎
的字样
恭喜你, 成功运行了可能是你的第一个C 语言代码
这段代码非常简单:
#include <stdio.h>
int main() {
printf("Hello World");
return 0;
}
即使你从未接触过C 语言, 但从字面来看: 这个”命令”包含一个print
, print
这个英文单词的意思就是打印, 可以自然联想它的作用就和打印有关系
而这段代码的作用就是, 打印"Hello World"
类似
printf()
这样的可以被调用、执行、完成一些功能的”命令”, 在C 语言中, 被称为函数(Function)现阶段不需要理解什么是函数, 只需要与这个单词混个脸熟就好了
代码中的
main()
也是一个函数
从这个代码开始, 让我们来开始逐步认识C 语言
C 语言程序的入口
上面例子中的代码, 已经是一个非常简单的C 语言
代码了, 它只有一个功能, 那就是运行之后 打印Hello World
但它不是最简单的C 语言程序, 最简单的C 语言程序代码, 是这样的:
int main() {
return 0;
}
向上面一样, 执行gcc main.c
编译代码, 生成一个可执行文件
但, 这个可执行程序并没有实现任何实际功能, 至少没有像上例中打印一些东西:
那么, 再尝试一下, 如果main.c
文件一行代码都没有, 是一个空文件, 还能编译出一个可执行程序吗?
答案是, 不行
不仅没有编译出可执行程序, 而且在整个编译过程中, 还报出了错误
报错的大概意思是 “xxx路径下的crt1.o
中的_start_
函数中使用的main
没有被定义”
虽然不知道为什么, 但是这可以告诉我们一个:
一个标准的C 语言程序, 至少要有一个main()
函数, 编译器才能将C 语言代码编译成可执行程序
而这个 main()
函数, 就是C 语言程序中用户代码的入口
用更加书面的形式表述就是:
“在标准C 语言环境下, 要生成可执行程序必须提供main()
函数作为程序入口
使用空源文件编译, 虽然能通过编译阶段, 但会因缺少main()
而在链接阶段失败, 因为C运行时初始化代码需要调用它
这是由C 语言标准规定的”
main()
函数是C 语言程序的入口
这意味着, C 语言标准程序, 用户如果要实现什么功能, 就需要在main()
函数中实现对应的代码
就例如, 在上面的例子中, 在main()
函数中执行了printf("Hello World");
最终编译出来的程序, 执行之后可以打印Hello World
不在main()
函数中直接或间接调用的语句, 是不会执行的
软件使用小贴士:
cat
是Linux
中查看文件内容的命令
cat file
, 可以在命令行中输出文件内容
暂时不需要理解的小知识:
C 语言从源文件到可执行文件的过程, 可以被简单地统称为”编译”(广义)
但实际中间经历的过程有: 预处理、编译(狭义)、汇编、链接