C语言概述

首先,关于C语言的发展历史,您应该去书上找到答案,这里省略之。

如章节1_1所述,C语言有着非常重要的历史地位,至今仍然久居高位而不下。尽管它并不是汇编之后的第一门高级语言,但是它
C语言是一门在高级语言中相对底层的编程语言。允许通过指针直接访问内存,并支持内联汇编等特性,使得C语言不仅具备了高级语言的便利性,还保留了较高的运行效率。然而,尽管C语言的高效性使其广受欢迎,但由于缺乏足够的抽象,C程序员往往需要关注计算机的运行逻辑对程序的影响。这种对底层细节的关注,虽然能够带来更大的控制力和自由度,却也增加了编程的复杂性和出错的风险。

学习C语言时需要注意:

  • 并非“高级”:由于历史与设计原因,C的抽象度并没有Java、Python这些后起之秀高,在很多方面无法从语言层面提供便利(尽管从现代语言的角度来看是非常必要的功能);
  • 设计缺陷:C语言有着许多 特殊设计 设计缺陷,C代码往往隐藏着各种陷阱,它们的运行效果可能会出乎初学者的意料,感到迷惑,特别是如果您学习过其他现代语言,这一点需要您多加小心;
  • 不要太过深究:编程讲究融会贯通,初学时一味地探究原理并不是一件好事,特别是C语言中存在许多反直觉的陷阱,这往往会让您浪费掉大量时间,随着知识和经验的积累,这些所谓的原理会慢慢的变得看起来理所应当。

算法是什么

算法是指解决问题的一种方法或一个过程。
算法是若干指令的有穷序列,满足以下性质:

  1. 输入:有外部提供的量作为算法的输入
  2. 输出:算法产生至少一个量作为输出—算法必须有结果
  3. 确定性:组成算法的每条指令是清晰,无歧义
  4. 有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的

无论是解决像“计算两个数的最大公因数”这样的简单数学问题,还是“规划一条导航路线”这样复杂的实际问题,都可以找出其解决方案,满足上面的性质,就是一个算法。

编程语言与算法

前面说,“编程语言是用于控制计算机执行特定任务的语言”,一般而言,这些特定任务就是各种算法。
我们学习C语言,是为了使用这门语言来描述各种操作,亦即算法,让计算机代为劳之。
因此,掌握基本的数学知识以及算法能力对编程开发是很有帮助的。

C语言的特点

  1. 简洁且紧凑:C语言一共只有32个关键字、9种控制语句,代码非常紧凑干练。
  2. 丰富的类型系统:C语言的数据类型非常丰富,包括整形、浮点型、字符型、数组类型、指针类型、结构体类型和共用体类型等,进而能够“氢崧”实现各种复杂的数据结构。
    但是另一方面,C语言的类型系统并不是 类型安全 的,这与C语言的设计初衷和思想有关。这个问题现在讨论还为时尚早。
  3. 自由且高效:C语言的 指针 是其灵魂所在,指针类型的存在让C程序可以自由地访问硬件、操作内存,因此C语言可以写出十分灵活且效率极高的程序,编写对性能有着极高要求的软件(例如操作系统)时有着极大的优势。
  4. 危险与效率并存:高效率和高自由度往往伴随着代价,C语言中,这种代价就是安全性问题。指针的自由与安全检查的缺乏让C代码有着极高效率的同时也伴随着安全问题,这往往会导致极其严重甚至异常昂贵的后果(例如第一个Internet蠕虫病毒)。

但是C语言的特点在某些程度上,也是其缺点所在,读者越是向后学习,就越能体会的到,这里也并不方便展开讨论。

C语言程序

C程序基本结构

我们以一个最简单,最经典的代码—Hello World!程序,作为经典的入门代码。
这个代码的效果是:向终端(命令行)输出一个“Hello,World!”字符串。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
这是一个hello world程序
(这是一段C风格的多行注释,使用/和*包围起来)
*/
// 这是一行注释,这种使用两个斜杠的单行注释的写法实际上借鉴于C++
#include <stdio.h> // #开头的预处理指令, #include表示包含一个头文件stdio.h,为了使用printf函数

// C程序的入口,一切必须从全局唯一的main函数开始执行
int main() {
printf("Hello,World!"); // 一条语句,调用了printf函数,用于输出字符串"Hello World!"
return 0; // 一条语句,返回一个值0,代表结束main函数
}

下面将程序拆解为几块来分别进行讲解。

预处理指令

预处理指令用于在预处理阶段做一些操作,这些操作基本是纯文本操作,比如选择性地插入或替换一些文本。
预处理指令以一个 # 开头,C中有许多种预处理指令,上面这个程序只使用了 #include 指令。

文件包含-#include指令

一个完整的程序由若干操作共同完成,每一步操作都完成了整个程序流程的一小部分。这些操作中有许多现成的代码,也就是说别人已经提前写好了,我们没有必要自己重新编写,直接拿来使用即可。这种已经写好的,可以重用的代码被称为库代码

与其他语言类似,C语言也有许多已经写好的“库”可以直接使用,库中的功能往往以函数的形式导出,我们在使用它们之前,需要声明它们,即告诉编译器我们需要使用这个函数。

C语言中这些声明信息存在于头文件中,我们需要将特定库的头文件包含在我们的程序中。

例如 #include <stdio.h> 就是包含了stdio.h这个头文件,即我们需要使用stdio(standard In/Out)库中的函数。在上面这个程序中,printf函数就存在于stdio中。

注:预处理指令会在后面章节讲解

主函数main()

有始有终,任何一个程序必须指定从何处开始执行,指定从何处结束执行。一个C语言程序必须从 main() 这个函数开始的( main() 也被称为主函数)。

无论一个C程序有多么庞大,整个程序必须有且仅有一个main函数,代码必定从main函数开始执行。
PS:这是对用户代码而言,实际上在运行用户代码之前,必须先将程序加载并初始化完成,这意味着有一些特定代码先于main函数执行。

尽管main函数具有唯一性,但是仍然属于一个函数。int main()叫做main函数的函数头,它包含了这个函数的所有基本信息—函数的名称、需要传递的参数和函数的返回值类型。
C语言的函数和数学意义上的函数基本等价,例如f(x)=x+1,其中x为整数. 这个函数就可以写为:

1
2
3
4
5
int f(int x){ 
// int 代表x为整型变量(integer)存储整数,第一个int代表这个函数返回值(可以理解为f(x))为整型;
// 第二个 int 代表这个函数的参数(即x)为整型
return x+1;
}

最后,函数头后面紧跟的就是函数体,函数体里包含了这个函数的一切操作,函数体以{}作为边界.

注:函数会在后面章节讲解!

函数内的语句

函数是C程序的基本组成单元,一个函数完成某个特定功能。例如 float sin(float x) 计算x的sin值。

一个函数中包含的所有内容包括:

  • 函数要执行的所有运算步骤
  • 运算中需要的一些必要的数据(局部变量或函数参数)
  • 函数的运算结果以return语句返回,类似于函数值

函数中每一个操作都一条语句,语句必须由一个 ; 来结束。
一条语句可复杂可简单,一条语句由若干个表达式构成,表达式越多,越复杂冗长,这条语句就越复杂。
所以,如果可以,尽可能把一个复杂的操作分成几条语句(几步分开的操作)来运算。

例如 printf("Hello,World!\n"); 这条语句就是执行了一个输出字符串的操作,其中只包含了一个表达式—函数调用表达式—调用了printf()这个函数.

注意:很多入门教程都不会提到"函数调用表达式"这个概念,反而会使用"函数调用语句"这个名词,请注意"函数调用语句"这个东西并不存在,实际上函数调用是属于表达式的

C语言的大部分语句是由表达式构成的,即表达式语句

再例如 return 0; 这条语句指示函数结束,并返回一个值。该语句属于 跳转语句
任何函数,一旦执行到return语句,即意味着这个函数的执行立即结束,后面的代码无论多少都不会再继续执行,该函数立即停止执行并返回到上层。
return后面跟的值也是一个表达式,可以返回任意和函数类型符合的可以求值的表达式,此处main函数默认返回一个0,0就是一个值为0的常量表达式,表示main函数正常退出。

注:语句和表达式将会在后面章节讲解。

总结

我们可以发现,一般情况下,一个完整c程序必须或应该包含以下几个部分:

  1. 使用 #include<> 命令进行头文件的包含,用以声明将要使用到的外部代码。
    例如用于输出字符串的 printf() 函数就在stdio库中,声明于stdio.h这个头文件内。
  2. 一个必须存在的函数,它是一切的开始—int main()函数
    一个最规范正确(没有参数时)的main函数应该是这样:
1
2
3
4
int main(void){ // 代表main函数的返回值为int类型,同时没有任何参数
//其他语句
return 0;
}

我们最多可以把那个括号中的void省略,因为main中的void是在新标准中推荐添加的,我们基于C99版本进行学习即可。

关于 main() 函数,你还可能会在其他的地方看到下面这样更复杂的写法,不过我们暂时用不到他们,在遇到的时候不要奇怪:

1
2
3
4
5
6
7
8
9
10
// 有2个参数的main函数,用来接收运行参数
int main(int argc,char *argv[]){
//其他语句
return 0;
}
//或者是这样的,第三个参数指向"指向执行环境变量的指针数组"
int main(int argc,char *argv[],char *envp[]){
//其他语句
return 0;
}

上面这2种实际上都没有问题,但是我们目前用不到,特别是第2种,你几乎不会遇到他。

另外注意:一切教导你这样写main函数的资料,并且没有作为反例详细说明其问题的,请你扔掉它:

1
2
3
4
5
6
7
8
9
10
11
12
13
main(){
// 1.main前面没有int,过时的写法
// 其他语句
}
int main() {
// 其他语句
// 2.没有return 0; 不良好的行为,C99前行为未定义
}
void main() {
// 3.与int不兼容的返回值类型(void),会导致返回给宿主环境(一般指命令行)的值未指定
// 并且一般会报错
// 其他语句
}
  1. 其他过程,也就是其他的辅助函数,用于将功能独立出来

  2. 一些注释,用于标注这个程序是用来干什么的,或者某一段代码是干什么的

注意:一定要养成写注释的习惯,特别是程序越发复杂,更应该编写注释来以备日后回看,方便改进和维护。

C代码如何运行

使用C语言这样的高级语言编写的代码存储于一个源文件中,说到底依旧是一个纯文本文档,可以供人阅读,但无法让计算机运行。
源代码文本只是记录了一个程序的逻辑,并不是一个可用的程序。为了将其转换为计算机可以执行的程序,需要使用到专门的工具—C编译器(一切编译型语言都有自己专属的编译器,例如RUST语言有自己的RUST编译器,Go语言有自己的Go编译器)。
C语言最常见的编译器有以下3种:GCC编译器,MSVC编译器,Clang编译器。
作为学习者用途,使用GCC即可,对于学习阶段,各编译器大同小异,无需考虑其差异。

一旦使用编译器对代码文本进行编译,生成的二进制程序往往就和语言无关了,因为二进制程序是和系统架构严格相关的,任何语言的代码一旦编译成特定平台的程序,就难以恢复成原来的源代码。当然,逆向工程技术可以一定程度上做到代码重构,这部分内容与本教程无关。

我们学了C语言是要干什么

了解了C语言的过程,我们作为C这门技术的使用者,我们的职责就是:

设计算法,即程序的基本流程->构思程序结构->使用C语言进行程序的编写->进行调试测试->确认无误后生成正式的程序->将程序用于实际的生产作业来发挥作用->维护并改进

最关键的就是程序编写和调试测试.


---WAHAHA,2023.9.19



上一篇:写在前面

下一篇:C语言教程-1_2-关键概念