写在开头
在正式开始认识C语言之前, 我们可以先从一个抽象但简单的角度, 来聊一聊编程
编程是什么?
在我个人看来, 编程 是人与计算机之间交流的一种方式
人可以把想法告诉计算机, 而计算机会尝试理解这些想法, 并给出响应. 这种响应, 完全依赖于人给出的指令
比如, 如果你告诉计算机要显示一张图片, 它就只会显示一张图片; 如果你让它播放一首音乐, 它就只会播放音乐. 这其实就是一种交流
但交流通常是需要媒介的
在人类社会中, 人与人之间靠语言沟通. 如果两个人要顺利交流, 前提是必须使用一种彼此都能理解的语言, 比如中文
当然, 如果两人之间没有一种彼此都能理解的语言, 也能借助翻译工具进行交流
人与计算机之间的交流, 也同样如此
最初, 人与计算机的交流, 是通过简单却复杂的二进制完成的
举个例子来帮助理解:
假设有一个房间, 里面有一排8个灯泡和一扇门, 灯泡只存在亮和灭两种状态
如果规定:
- 8个灯泡状态为 亮 灭 灭 灭 灭 灭 亮 亮 时, 表示开门
- 8个灯泡状态为 灭 亮 亮 亮 亮 亮 灭 灭 时, 表示关门
那么, 这8个灯泡的状态组合就形成了了一种表达信息的媒介
人与计算机进行交流是类似的思路: 通过一段特定组合的二进制数据, 让计算机理解其所代表的含义, 就能与计算机进行交流
例如, 一段二进制数据10000011
可以表示「加法运算」. 将它交给计算机, 计算机就知道要执行加法运算
这是一种低级但有效的语言, 它之所以能被计算机理解, 是因为计算机设计者赋予了它这种”语言”的含义
事实上, 二进制, 就是计算机真正能够理解的语言
但直接使用 二进制与计算机交流, 意味着每个”对话”都需要用一长串没有语义的数字进行, 这太费劲了
所以, 在二进制的基础上, 助记符诞生了: 用一个更方便人记忆的词, 表示计算机能理解的特定二进制数据段
比如, ADD
表示10000011
, 那么要让计算机理解 需要进行加法运算, 就不用告诉计算机10000011
, 只需要告诉它ADD
, 它就能够理解需要加法运算
基于这种思路, 汇编语言诞生了, 汇编语言结合了助记符、操作数、寄存器等概念, 形成一种人与计算机交流的媒介
再后来, 为了更方便与计算机进行交流, 所使用的语言也越来越向人类语言发展
直到出现了一门简明、高效的集大成者, 也就是本篇文章的重点: C语言
用C语言告诉计算机进行加法运算, 只需使用+
, 计算机就能理解需要进行加法运算
如何理解C语言?
你可以把C语言(或任何高级编程语言), 想象成人类和计算机”都能听懂”的”普通话”
计算机的”母语”是二进制 (比如
10000011
) , 但人类用二进制和它交流就像用摩斯密码聊天一样, 效率极低而C语言, 它用人类能看懂的关键字(
if
、for
、printf
)和符号(+
、-
、=
)作为与计算机交流的指令虽然计算机不能直接理解这些关键字, 但是可以使用工具把这些指令翻译成计算机能执行的二进制
举个栗子🌰:
二进制是计算机真正能够理解的语言, 你想让计算机算1 + 1
:
使用二进制: 可能要写一长串
101010...
(晕)使用C语言: 直接写
1 + 1
但计算机看不懂, 所以编译器(翻译官)会帮你把
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
文件不同, 因为它是一个可以被执行的二进制程序
你可以将它类比为Windows
中的.exe
文件
gcc
是C语言代码的一种编译器可以使用
gcc
将人可以看懂的C语言代码, 编译成操作系统可以看懂的二进制然后操作系统才能去执行
在命令行输入./a.out
, 并按下回车:
可以看到终端中出现了Hello World⏎
的字样
恭喜你!你刚刚成功运行了 可能是你人生中的第一个C语言程序!
再来回看这段代码
#include <stdio.h>
int main() {
printf("Hello World");
return 0;
}
即使你从没接触过C语言, 大概也能从字面上猜出printf("Hello World")
的含义:
print
是英文中的”打印”printf()
这个”命令”的作用, 就是在终端中打印出一段文本
所以整段代码的效果就是: 向屏幕输出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()
函数中实现对应的代码
就像:
#include <stdio.h>
int main() {
printf("Hello World");
return 0;
}
在main()
函数中执行了printf("Hello World");
最终编译出来的程序, 执行之后可以打印Hello World
不直接或间接在main()
函数中调用的语句, 是不会执行的
软件使用小贴士:
cat
是Linux
中查看文件内容的命令
cat file
, 可以在命令行中输出文件内容
暂时不需要理解的小知识:
C语言从源文件到可执行文件的过程, 可以被简单地统称为”编译”(广义)
但实际中间经历的过程有: 预处理、编译(狭义)、汇编、链接