Ⅰ、C语言程序的基本结构
一个最简单的入门C语言程序:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
C语言的基本结构可以包括以下几个方面:
1、头文件 #include<文件名>
对于一个程序而言,可以没有输入,但是要有输出,否则,即使程序真的做了什么,我们也是看不到的,对于初学者而言,输出才能知道程序真的做了什么。对于输出,初学者需要的是调用一个函数(完成某个特定功能的一系列操作),对于上面程序则是printf函数,它将括号里面的内容(Hello, World!\n)打印在一个叫做控制台Console的窗口界面上。而这个函数具体的操作在某个叫做stdio.h 标准输入输出头文件中,所以需要将其包含include进来。
2、函数
- C语言由函数构成,每个程序可以包含多个函数。
- 函数是一段完成特定任务的代码块(由花括号{}及里面的内容组成)以及函数签名(比如int main())组成,可以被其他函数或主函数调用执行。
3、主函数
- 每个C语言程序都有且仅有一个main函数,它是程序的入口点。
- main函数具有返回值,一个数字(return 0;返回0,表示程序正常结束),用于表示程序的退出状态码。
- 当程序开始执行时,操作系统会调用main函数,并从该函数开始执行程序。
- main函数的结束标志着程序的结束。
4、语句
- 每个语句以分号作为结束标志,表示一条指令的结束。
- 可以使用花括号将多条语句包含在一个语句块中,形成复合语句。
Ⅱ、C语言程序的基本组成
基本元素
C语言中的基本元素包括关键字、标识符、字面量、运算符、分隔符、注释等,它们组成了语句和函数。
关键字
关键字是C语言中具有特定功能的保留字,它们在程序中被赋予特定的意义和功能。关键字在程序中具有特殊的语法作用,不能用作其他用途。ANSI标准定义了32个C语言的关键字,包括数据类型关键字、流程控制关键字和标识符关键字等。
关键字 | 简单说明 |
void | 无类型 |
char | 字符型 |
int | 整型 |
float | 单精度浮点数 |
double | 双精度浮点数 |
short | 短整型 |
long | 长整型 |
signed | 有符号类型 |
unsigned | 无符号类型 |
struct | 结构体类型 |
union | 联合体类型 |
enum | 枚举类型 |
typedef | 类型定义 |
sizeof | 计算数据类型或变量所占用的内存大小 |
auto | 自动类型提升 |
register | 寄存器优化 |
extern | 用于声明外部变量或函数 |
const | 常量 |
volatile | 易变变量 |
static | 静态变量 |
流程控制关键字 | 简单说明 |
return | 从函数中返回一个值 |
continue | 跳过当前循环的剩余部分,进入下一次循环 |
break | 无条件跳出循环或switch语句 |
goto | 无条件跳转到指定的标签位置 |
if | 条件判断和分支结构 |
else | 如果条件不满足时执行的分支结构 |
switch | 多分支选择结构 |
case | switch语句中的条件分支 |
default | switch语句中没有匹配到的情况 |
for | 循环结构 |
do | do-while循环结构 |
while | 循环结构 |
标识符
标识符是用来命名变量、函数、数组等的名称。标识符由字母、数字和下划线组成,且必须以字母或下划线开头。标识符在程序中用于声明变量、调用函数和访问数据等操作。
以下为一些正确的标识符
_abc king think book
字面量
字面量是程序中使用的固定值或常量,整数、浮点数、字符、字符串等形式。字面量在程序中直接表示具体的数值或字符,具有常量性。
运算符
运算符用于对数据进行操作和计算。C语言提供了多种运算符,包括算术运算符(如加法、减法、乘法、除法)、关系运算符(如等于、不等于)、逻辑运算符(如与、或、非)等。运算符用于连接表达式,实现各种计算和逻辑判断。
分隔符
分隔符用于将代码块或语句分隔开来,使代码更加清晰易读。C语言中的常见分隔符包括分号(;)、换行符(\n)、花括号({})等。它们用于标识代码的起止位置,确保程序的逻辑正确性。
注释
注释是对代码的解释和说明,其目的是让人们能够更加轻松地了解代码。注释是编写程序时,写程序的人给一个语句、程序段、函数等的解释或提示,能提高程序代码的可读性。注释只是为了提高可读性,不会被计算机编译。在C语言中有两种注释方法:单行注释 多行注释
// 单行注释以//开头,到行尾全算是注释内容
int integer_number = 12;
//上述语句 关键字(int) 标识符(integer_number) 运算符(=) 字面量(12) 分隔符(;)当然空格也算,作为区分基本元素
/*
两个斜杆配合*组成一个多行注释区
*/
基本逻辑
前面说过,任何一个完整的C语言程序,以main函数为执行入口,一旦main函数结束,程序也就结束了。这是程序的大体逻辑,或者说是嵌套逻辑,比如以下两个程序:
// pragam1
#include<stdio.h>
int main()
{
printf("begin main!\n");
if(1){
printf("it's 1\n");
}else{
printf("it's not 1\n");
}
printf("end main!\n");
return 0;
}
// pragam2
#include<stdio.h>
int main()
{
printf("begin main!\n");
if(0){
printf("it's 1\n");
}else{
printf("it's not 1\n");
}
printf("end main!\n");
return 0;
}
两个程序十分相像,但是结果不同,第一个输出
第二个输出
因为分支逻辑的存在,程序的具体执行流向存在了可选择的路径。这类似于从一个水源离开的水流,无论经过何种路径都总将流向大海,程序的奇妙就在于这经过的路径的千变万化。
Ⅲ、编程机制
一个可供人阅读、理解乃至编写的C语言程序(源代码)要让计算机去理解与执行,需要将我们的源码翻译成二进制可执行程序。
这个“翻译”通常分为两种方式:
一种叫做编译(compliation),一种叫做解释(interpretation)。使用编译的编程语言,可称之为编译型语言,编译即将要运行的程序全部翻译成二进制代码形式然后执行;使用解释方式的编程语言,也称之为解释型编程语言,只有执行到某条语句时,某条语句才会翻译成二进制代码形式。
C属于前者。下面通过命令行的形式(这相当原始,对于习惯使用IDE的编程人员而言),讲解C语言程序的编译过程,这里使用的命令在Linux和Windows操作系统下是基本通用的。
编写hello world C程序:
// hello.c
#include <stdio.h>
int main(){
printf("hello world!\n");
}
编译C语言程序需要使用到一个编译器gcc
GCC,全称为GNU Compiler Collection(GNU编译器套件),是GNU项目下的一个开源编译器集合,支持多种编程语言的编译。
上述gcc命令成功将一个hello.c的C源程序翻译成了一个可执行的二进制程序。
通用的,编译一个单一的源文件并生成可执行文件,可以使用如下命令:
gcc source_file.c -o output_executable
其中,source_file.c
是源代码文件名,.c
表明这是C语言源文件,-o output_executable
指定输出的可执行文件名。
如果程序中没有main函数,或者说没有写正确,就会有如下错误 Undefined reference to `WinMain@16`:
虽然以上的命令一步到位将源代码翻译成了二进制的可执行文件,但这并不意味着,转换是一步到位的。为了特别的需要,我们也可以一步步生成一个C语言的可执行程序,以下将从四个步骤依次“编译” C程序,这也是以上命令在背后的执行过程。
1. 预处理(Preprocessing)
预处理阶段用于处理源代码中的预处理指令,并生成预处理后的源文件。使用以下命令进行预处理:
gcc -E source.c -o source.i
其中,source.c
是源代码文件,source.i
是预处理后的文件。是C预处理器和C编译器之间的中间(intermediate)文件
可以看到i文件与源代码文件相比扩大了22倍,这时候的i文件仍然是文本格式,可以打开阅读:
源代码的内容大部分在文本最后可以看到,那么前面的多出来的是什么,其实就是stdio.h头文件里面的内容,即#include指令在预处理时将替换为被包含的文件。
为了验证这一点,这里做一个实验如下,这里创建了一个自己的头文件,并使用include指令,注意,指令应该独占一行。我们直接编译,结果编译成功。
接下来试试预处理,可以验证,在预处理后,头文件被包含了进来,至于其它的,我也不知道是什么东西。
除了include指令,在预处理阶段也会进行其它指令的处理,比如define,不过这里没有涉及,但是主要的作用都类似,都是一个替换。
2. 编译(Compilation)
这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。编译的指定如下:
编译阶段将预处理后的源文件转换为汇编代码或目标文件。使用以下命令进行编译:
gcc -S source.i -o source.s
或者直接将编译和预处理合并成一步:
gcc -S source.c -o source.s
其中,source.i
是预处理后的文件,source.s
是汇编代码文件。 s文件是汇编程序。
经过编译后的文件,可以看到体积缩小了,和源代码的文件大小相同,此时的S文件也是可读的,不过可读性进一步降低了。
至于为什么大小回到了正常大小,读者可以认为,我们只需要程序运行实际需要的东西,而把不需要的剔除了。
3. 汇编(Assemble)
汇编阶段将汇编代码转换为机器语言的目标文件。使用以下命令进行汇编:
gcc -c source.s -o source.o
其中,source.s
是汇编代码文件,source.o
是目标文件。 o文件是obj文件,程序编译时生成的中间代码文件。目标文件,一般是程序编译后的二进制文件,再通过链接器和资源文件链接就成可执行文件了。OBJ只给出了程序的相对地址,而可执行文件是绝对地址。-c 选项表示只编译而不链接。
4. 链接(Linking)
链接阶段将多个目标文件和库文件组合在一起,生成可执行文件或动态库文件。使用以下命令进行链接:
gcc source1.o source2.o -o executable
或者也可以使用以下方式链接库文件:
gcc source.o -llibrary -o executable
其中,source1.o
和source2.o
是目标文件,executable
是生成的可执行文件。-l
选项用于指定链接的库文件,library
是库文件的名称。
链接两个文件:
gcc -c program1.c -o program1.o
gcc -c program2.c -o program2.o
gcc program1.o program2.o -o program3.exe
后面两步操作在实际程序的运行过程中,还会涉及到其他问题而导致出错,后面会一一说明。
以上基于我的个人理解,可能有不对不全面的地方,请读者自行斟酌,注意实践。