C语言概述
首先,关于C语言的历史发展,您应该去书上找到答案,这里省略之。
编程语言是用于控制计算机执行特定任务的语言,程序员使用特定的编程语言描述需要执行的任务的具体计算过程,由计算机进行识别、处理并运行,最终输出执行结果。
最早时候,操作计算机需要程序员直接操作二进制指令,利用卡带等方式进行录入,效率低下;
后来将一些特定的二进制指令用一些简短的英文缩写代替,例如 add ax,1
表示将ax寄存器加1,这种助记符大大简化了程序的编写;
然而汇编语言仍然十分低级,尽管助记符让程序员不再需要输入冗长的01代码,但是仍然需要亲自考虑计算机的运行逻辑,效率依然低下;
人们希望能够将注意力更多地集中到解决问题本身,而不需要将时间浪费在操作计算机的运行逻辑上,高级编程语言就此出现。
高级语言隐藏了计算机运行时复杂的底层逻辑和过程,让程序员能够将注意力更多地放在需要解决的问题本身上,大大提高了效率,而随着计算机的发展,编程语言也在不断地更新、演化。
C语言就是高级语言(其语法接近自然语言)的一种,也是最为重要的一门语言,各种现代操作系统的内核几乎都是由C语言编写。
C语言并不是汇编之后的第一门高级语言,但是其仍然保留了许多底层的一些行为,例如使用指针直接访问内存等,这让C语言不仅有着高级语言的便捷性,同时又保留了其极高的运行效率;但是运行效率带来的问题就是,它仍然不够抽象,C程序员很多情况下仍然需要注意计算机的运行逻辑对C程序的影响。
学习C语言时需要注意:
- 由于C的抽象度并没有Java、Python等现代语言高,因此在很多方面无法从语言层面提供便利;
- C语言有着一些特殊设计、设计缺陷,许多代码往往隐藏着陷阱,它们的运行效果可能会让人出乎意料,感到迷惑,这一点需要您多加小心;
- 不要太过纠结原理,C语言仍然是一门偏底层的语言,初学时一味地探究原理并不是一件好事,这往往会让您浪费掉大量时间。
算法是什么
算法
是指解决问题的一种方法或一个过程。
算法
是若干指令的有穷序列,满足以下性质:
- 输入:有外部提供的量作为算法的输入
- 输出:算法产生至少一个量作为输出—算法必须有结果
- 确定性:组成算法的每条指令是清晰,无歧义的
- 有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的
无论是解决像“计算两个数的最大公因数”这样的简单数学问题,还是“规划一条导航路线”这样复杂的实际问题,都可以找出其解决方案,满足上面的性质,就是一个算法。
编程语言与算法
前面说,“编程语言是用于控制计算机执行特定任务的语言”,一般而言,这些特定任务就是各种算法。
我们学习C语言,是为了使用这门语言来描述各种算法,让计算机代为劳之。
C语言的特点
- 简洁且紧凑。C语言一共只有32个关键字、9种控制语句,代码非常紧凑干练。
- 强大的类型系统。C语言的数据类型非常丰富,包括整形、浮点型、字符型、数组类型、指针类型、结构体类型和共用体类型等,进而能够轻松实现各种复杂的数据结构。
但是另一方面,C语言的类型系统并不是类型安全
的,这与C语言的设计初衷和思想有关。这个问题现在讨论还为时尚早。 - 自由且高效。C语言的
指针
是其灵魂所在,C程序可以自由地访问硬件、操作内存,因此C语言可以写出十分灵活且效率极高的程序,编写对性能有着极高要求的软件(例如操作系统)时有着极大的优势。
但是C语言的特点在某些程度上,也是其缺点所在,读者越是向后学习,就越能体会的到,这里也并不方便展开讨论。
C语言程序是什么
C代码是怎么样的
我们上一个最简单,最经典的代码—Hello World!程序,作为经典的入门代码。
这个代码的效果是:向终端(命令行)输出一个“Hello,World!”字符串。
代码如下:
/*
这是一段C风格的注释---这是一个hello world程序
*/
// 这是一行注释,这种单行注释的写法实际上借鉴于C++
#include <stdio.h> // 包含一个头文件stdio.h,为了使用printf函数
int main() {// 这是一个函数,而且是C程序的入口,一切必须从这里开始执行
printf("Hello,World!"); // 一条语句,这条语句的作用是调用printf函数,用于输出一个字符串,这里输出"Hello World!"
return 0; // 代表结束main函数,并返回一个0,这里先不用关心其作用,只要记住main函数要加上这条语句即可
}
下面将程序拆解为几块来分别进行讲解。
头文件包含#include<stdio.h>
作为描述算法的语言,程序的编写就是描述算法的过程。一个完整的算法中会包含许多小操作,这些小操作甚至也是一些子算法,整个算法分为几个不同的步骤去进行,其中一些最基本的不可或缺的操作或者已经开发好的算法就没有必要再去手动重新编写,也就是说别人已经造好了轮子,你先拿来用就行。这种已经写好的代码被称为库
。
C语言也有许多写好的“库”可以直接使用,我们想要使用它们,一般(不是绝对)都要加上库自带(提前写好的)的“导入信息”,C语言中称为头文件。实际上,头文件包含了许多函数/自定义类型等元素的声明(后面说明),想要使用某个函数,那么必须让编译器知道我导入了这个函数(事先声明好)。
例子中的stdio.h
就是一个头文件,它是standard in/out
的缩写,即标准输入输出
,后缀名.h
表明这是一个C头文件(header).
而#include <header file>
则是C代码中导入头文件的语法.
主函数main()
无规矩不成方圆,一个C程序必须有管事的老大,我们的代码都从main()函数开始(也就是主函数),无论执行什么函数,都必须从main()中开始调用或者由其他函数进行进一步的调用.
int main()
叫做main函数的函数头
,它包含了这个函数的基本信息—函数的名称、需要传递的参数和函数的返回值类型。c语言的函数和数学意义上的函数完全等价,例如f(x)=x+1,其中x为整数. 这个函数就可以写为:
int f(int x){
// int 代表x为整型变量(保存整数),第一个int代表这个函数返回值(可以理解为f(x))为整型;
// 第二个 int 代表这个函数的参数(即x)为整型
return x+1;
}
最后,函数头后面紧跟的就是函数体,函数体里包含了这个函数的一切操作,函数体以{}作为边界.
函数内的语句
一个函数中包含的所有内容就是这个函数要执行的运算,还有一些辅助的/必要的数据(局部变量或函数参数),每一个操作叫做一条语句,语句必须由一个;来结束,一个语句可复杂可简单,一条语句由若干个表达式构成,表达式越多,越复杂冗长,这条语句就越复杂。所以,如果可以,尽可能把一个复杂的操作分成几条语句(几步分开的操作)来运算。
例如printf("Hello,World!\n");
这条语句就是执行了一个输出字符串的操作,其中只包含了一个表达式—函数调用表达式—调用了printf()这个函数.
注意:很多入门教程都不会提到"函数调用表达式"这个概念,请注意没有"函数调用语句"这个东西!
C语言的大部分语句是由表达式构成的!
再例如return 0;
这条语句就是执行了一个函数返回的操作,任何函数,一旦执行到return语句,即意味着这个函数的执行立即结束,后面的代码无论多少都不会再继续执行,直接返回.return后面跟着的那个值也是一个表达式,可以放置任意和函数类型符合的,可以求值的表达式,此处main函数默认返回一个0,0就是一个值为0的常量表达式.
总结
我们可以发现,一般情况下,一个完整c程序必须或应该包含以下几个部分:
- 使用
#include<>
命令进行头文件的包含,不同的头文件内包含了我们这个程序可能用到的各种函数等的信息
例如我们用于输出的printf()
函数就在stdio.h这个头文件内
- 一个必须存在的函数,他是一切的开始—int main()函数
一个最规范正确(没有参数时)的main函数的格式应该是这样:
int main(void){ // 代表main函数的返回值为int类型,同时没有任何参数
//其他语句
return 0;
}
我们最多可以把那个括号中的void省略,因为main中的void是在新标准中推荐添加的,我们基于C99版本进行学习即可.
你还可能看到这样的写法,我们暂时用不到,但他们是没有问题的:
//这样的:
#include <stdio.h>
int main(int argc,char *argv[],char *envp[]){
//其他语句
return 0;
}
//或者是这样的:
int main(int argc,char *argv[],char **envp){
//其他语句
return 0;
}
//甚至是这样的:
int main(int argc,char *argv[],char *envp[]){
//其他语句
return 0;
}
上面这3种都没有问题,但是我们暂时用不到,特别是第3种.
另外,一切教导你这样写main函数的教材或书,请你扔掉它:
main(){
//main前面没有int
//其他语句
//没有return 0;
}
-
其他过程,也就是其他的辅助函数,用于将功能独立出来
-
一些注释,用于标注这个程序是用来干什么的,或者某一段代码是干什么的
注意:一定要养成写注释的习惯,特别是程序越发复杂,更应该编写注释来以备日后回看,方便改进和维护.
警惕"烂书"教你的错误写法
之所以让你"扔书",是想让你知道什么是该学的规范写法,什么是业余的写法:
-
只有旧标准才允许main前不加返回类型(也就是那个int).
-
此外,如果你发现有这么写的
void main()
,请毫不犹豫地把这本书扔掉,因为不加返回类型勉强可以说是对的,只不过是旧写法,但是唯独void main()
,从来没有任何标准中写过这种写法是正确的。现在(2023年)的各种主流编译器基本都已经不在支持这种写法:
从这两张图可以看出至少main()
没有报错。实际上,main()
的写法没有报错是为了对老程序的兼容性—毕竟前面所述,这是一种旧的写法,而不是完全错误的写法。我们学习,就要写int main()
,或者int main(void)
更加标准。
注:事实上这里仍然没有提到更加细节的内容,实际上int main(void)
才是标准的写法,这是由于C语言中,参数列表为空的函数声明,实际上指代的是这个函数可以接受任意个数的参数(注意并非可变参数)。
C代码是如何成为计算机程序的
学习C语言,或者说学习编程,就是和计算机打交道,多少要了解一些计算机的原理,否则,你学的C语言永远是花架子,找不到精髓.
一个计算机程序=算法+数据结构,同时他们也有交叉,一个完备的算法应该考虑到对应的数据结构;一种特定的数据结构应该为一个或一系列算法服务.
C代码,说到底还是一个文本文档,他只是记录了一个程序的逻辑,并不是一个可用的程序,那么作为一种编译型语言,想要把C源文件转换为可执行程序,就需要使用到专门的工具—C编译器(一切编译型语言都有自己的编译器,例如RUST有RUST自己的编译器),我们最常见的就是3种:GCC编译器,MSVC编译器,Clang编译器,我个人使用GCC来进行c代码的编译,他们大同小异,初学阶段无需考虑其差异,任何IDE都有对应的编译器可供使用.
这里的C源文件就是例如hello.c这样的以.c为后缀的文本文档,实际上它和.txt没有任何区别.他并不是二进制的文件.C编译器做的工作就是"编译",同时这个"编译"实际上包含了许多操作:“预处理”->“编译”->“汇编”->“链接”,最终链接完成后才是最终的可执行文件,这里的知识点暂时没什么用,可以自行了解.你只需知道,源文件经过编译后才能成为真正的二进制程序用于运行.
一旦成为计算机程序,就和语言无关了,因为二进制程序是和系统架构严格相关的,RUST编译出来的程序,C++编译出来的程序,C编译出来的程序,都是给同一个系统使用的,也就是说,不管内部细节的话,某种程度上他们已经是和语言无关了.
我们学了C语言是要干什么
了解了C语言的过程,我们作为C这门技术的使用者,我们的职责就是:
设计算法(或者由其他人设计好,例如数学家等)
->构思程序
->使用C语言进行程序的编写
->进行调试测试
->确认无误后生成正式的程序
->将程序用于实际的生产作业来发挥作用
->维护并改进
.
最关键的就是程序编写和调试测试.
---WAHAHA,2023.9.19
注:文章原文在本人博客https://gngtwhh.github.io/上发布。