引言:
C语言简洁、紧凑,使用方便、灵活。ANSI C标准C语言共有32个关键字,9种控制语句,程序书写形式自由,区分大小写。把高级语言的基本结构和语句与低级语言的实用性结合起来。 C 语言可以像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元。
简洁紧凑、灵活方便
ANSI C一共只有32个关键字,9种控制语句,程序书写形式自由,区分大小写。把高级语言的基本结构和语句与低级语言的实用性结合起来。 C 语言可以像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元。
C是结构式语言
结构式语言的显著特点是代码及数据的分隔化,即程序的各个部分除了必要的信息交流外彼此独立。这种结构化方式可使程序层次清晰,便于使用、维护以及调试。C语言是以函数形式提供给用户的,这些函数可方便的调用,并具有多种循环、条件语句控制程序流向,从而使程序完全结构化。
语法限制不太严格,程序设计自由度大:虽然C语言也是强类型语言,但它的语法比较灵活,允许程序编写者有较大的自由度。
允许直接访问内存地址,对硬件进行操作。由于C语言允许直接访问内存地址,可以直接对硬件进行操作,因此它既具有高级语言的功能,又具有低级语言的许多功能,能够像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元,可用来写系统软件。
生成目标代码质量高,程序执行效率高:一般只比经过高效优化的汇编程序生成的目标代码效率低10~20%。
适用范围大,可移植性好:C语言有一个突出的优点就是适合于多种操作系统,如DOS、UNIX、windows 98.windows NT;也适用于多种机型。C语言具有强大的绘图能力,可移植性好,并具备很强的数据处理能力,因此适于编写系统软件,三维,二维图形和动画,它也是数值计算的高级语言。
运算符丰富
C语言的运算符包含的范围很广泛,共有34种运算符。C语言把括号、赋值、强制类型转换等都作为运算符处理。从而使C语言的运算类型极其丰富,表达式类型多样化。灵活使用各种运算符可以实现在其它高级语言中难以实现的运算。
数据类型丰富
C语言的数据类型有:整型、实型、字符型、数组类型、指针类型、结构体类型、共用体类型等。能用来实现各种复杂的数据结构的运算。并引入了指针概念,使程序效率更高。另外C语言具有强大的图形功能,支持多种显示器和驱动器。且计算功能、逻辑判断功能强大。
同时对于不同的编译器也有各种强大的扩展功能。
另外C语言如此丰富数据类型及强大指针功能,其对硬件的管控能力极强,所以许多操作系统内核及MCU芯片程序开发都偏爱硬件。
1. 关于变量
1.1 什么是变量
在内存中开辟特定大小的空间,用来保存数据
所有的变量,本质都是要在内存中的某个位置开辟空间的!
1.2 变量的定义与声明
1. 类型 变量名 = 默认值 //定义类型格式 2. char c = 'c'; //初始化 3. c = 'd'; //赋值
赋值的意思就是这个变量c已经存在了,存在的本质就是这个变量对应的空间已经开辟出来了 ,换言之就是将'd'字符直接放到一个已经存在的空间中。
很不幸,在C语言中,初始化和赋值的差别不是很大,唯一可能会出现的差别是在结构体数组那块内容,但是对于一般的内置类型基本上是没什么差别的,但是在C++和Java里面还是有差别的,这里不再赘述。
1.3 为什么要定义变量
计算机是为了进行计算的,而计算就需要数据,但是不是所有的数据都要立马被计算,有的数据是要先保存起来,等待后续处理
就像是吃饭,不是所有的饭菜都要立马被吃掉,要一口口吃,还没吃到的菜就需要暂时放到盘子里
盘子就如同变量,饭菜就相当于数据。
那么有人可能会说为什么要用“盘子”,直接去锅里不就行了吗?这当然没问题,但是效率太低了
1.4 变量定义的本质
定义就是用来开辟空间的 — 只能有一次
声明是用来告知的 — 声明可以多次
2. 关键字
C语言一共多少个关键字呢?一般的书上,都是32个(包括本书),但是这个都是 C90(C89) 的标准。其实 C99 后又新增了5个关键字。不过,目前主流的编译器,对 C99 支持的并不好,我们后面默认情况,使用 C90 ,即认为32个
2.1 最宽宏大量的关键字 - auto
2.1.1 局部变量
包含在代码块中的变量叫做局部变量,局部变量具有临时性。
int x = 10; if(x == 10) { int y = 20; printf("code1: %d,%d",x,y); } printf("code2: %d,%d",x,y);
此时系统会报错:最后一行代码中的y变量没有定义
这是因为y变量是局部变量,只有在if代码块里面才可以使用,y的生命周期和作用域只在代码块内可以使用,出来之后y就被注销,所以code2中的y是没有定义的
2.1.2 全局变量
在所有函数外定义的变量,叫做全局变量,全局变量具有全局性。
全局变量整个程序运行期间都有效,且在任何代码块中的都可以被访问,甚至是修改
下面程序输出的是局部,也就是局部和全部同名的时候,优先局部。
int g_val = 100;//全局变量 int main() { int g_val = 200;//局部变量 printf("%d",g_val); } //程序的打印结果为200 //局部优先
2.1.3 概念补充
代码块:用{}括起来的区域,就叫做代码块
作用域:指的是可以被正常访问的代码区域
生命周期:指的是该变量从定义到被释放的时间范围,所谓的释放,指的是曾经开辟的空间“被释放”
局部变量:进入代码块,形成局部变量【开辟空间】,退出代码块,“释放”局部变量
全局变量:定义完成之后,程序运行的整个生命周期内,该变量一直有效
作用域本质上是这个变量从什么地方开始有效,到什么地方开始无效
生命周期本质上是从什么时候开始有效,到什么时候开始无效
作用域更多的是描述影响范围,生命周期更多的是描述生存时间长短的问题
2.1.4 auto相关
如何使用:auto一般修饰代码块内部定义的变量,即局部变量,默认都是auto修饰的,不过一般省略,,但是默认的所有变量并不都是auto,只是一般用来修饰局部变量
局部变量,自动变量,临时变量,都是一回事。我们统称局部变量
auto已经很老,近几年新出的C标准都不使用auto来修饰局部变量了,可以直接省略
2.3 最快的关键字—register
2.3.1 计算机的分级存储
CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了。
所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存。
金字塔从上到下容量越来越大、速度越来越慢、价格越来越低
一般距离CPU越近,存储效率越高,单价成本越高且容量一般是比较小的。
其中,快的设备给慢的设备做缓存内存,显示器。
计算机的分级存储让我们用最小的成本,使用上最高效率的计算机。我们现在使用的计算机,就是考虑成本和效率的结果
2.3.2 寄存器
我们可以不关系硬件细节,只要知道CPU内集成了一组存储硬件即可,这组硬件叫做寄存器
寄存器存在的本质:在硬件层面上,提高计算机的运算效率。因为不需要从内存里读取数据啦
我们知道了寄存器的作用之后再来看看寄存器变量—register吧
2.3.3 register 修饰变量
尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
那么什么样的变量,可以采用register呢?
1. 局部的(全局会导致CPU寄存器被长时间占用)
2. 不会被写入的(写入就需要写回内存,后续还要读取检测的话,register的意义在哪呢?)
3. 高频被读取的(提高效率所在)
4. 如果要使用,请不要大量使用,因为寄存器数量有限
register修饰的变量,不能取地址(因为已经放在寄存区中了嘛,地址是内存相关的概念)
最后呢,这个关键字现在可以不用管,因为现在的编译器已经很智能了,能够进行比人更好的代码优化
2.4 最名不符实的关键字 - static
在讲这个关键字之前我们先看一个补充的内容
2.4.1 认识多文件
.h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
.c: 我们称之为源文件,一般包含函数实现,变量定义等 (.c: c语言)
//test.h #pragma once //防止头文件被重复包含,现在只需要记住,后面用的多 #include <stdio.h> #include <windows.h>//包含了其他的windows头文件,调用函数时可以使用 //test.c #include "test.h" //""里面包含头文件,目前只需要知道是自己写的头文件,就用""包含即可 //main.c #include "test.h" //""里面包含头文件,目前只需要知道是自己写的头文件,就用""包含即可 int main() { printf("hello files!\n"); return 0; }
简单看了上面的多文件调用之后,下面我们来看两个问题:
1. 全局变量可以跨文件吗?
可以
2. 函数可以跨文件访问吗?
可以
为什么要跨文件?有一定规模的项目,一定是多文件的,多个文件之间,后续一定要进行数据“交互”(#include "test.h" , main.c 里的函数访问 test.c 里的函数)跨文件其实就是使代码模块化,方便别人使用时可以直接看到,所以在写项目的时候,跨文件是刚需。
如果不能跨文件,“交互”成本会比较高
如果我的这个文件比较自私,不想把自己定义的变量和函数给别的文件用,那么有没有可能,我们不让全局变量或者函数跨文件访问,只在本文件内部被访问呢?
我们跨文件的时候总有些代码是不能够开源的,这时就出现了static
2.4.2 static相关
static修饰全局变量和函数时,该全局变量和函数只能在本文件内被使用,是不能够被其他文件直接访问的,就相当于将代码给封装起来了,只给一个接口,这样的话别人在使用我们的程序的时候,就不能直接通过该全局变量来直接修改程序。static是一套很好的项目维护机制,提供了安全保证。
static修饰全局变量,影响的是作用域的概念,函数类似。而生命周期是不变的
static修饰局部变量,变量的生命周期变成全局周期。(作用域不变)
static void fun() { static int i = 0; i++; printf("i = %d\n", i); } int main() { for(int i = 0; i < 10; i++) { fun(); } return 0; } /*不加static时打印结果应全为1 加上static后打印结果应为: i = 1 i = 2 …… i = 10
两次结果不一样,其实是因为fun函数每次被调用之后,都会把内存释放,这样一来下一次进入时就会从i = 0开始执行。而后面加上static之后,i 在fun运行过程中,并没有被释放!!!
所以得到结论:static修饰局部变量,更改局部变量的生命周期,作用域不变
static总结:
i 的初始化动作,永远只会初始化一次,第一次!
2.5 基本数据类型—short、int、 long、char、float、double
C语言常见内置类型:short、int、 long、char、float、double等
什么是内置类型:由C语言给我们定义好的就叫做C语言的内置类型
与C语言相对应的还有一批自定义类型:指针,结构体,联合体,枚举等
前面已经说过,定义变量的本质:在内存中开辟一块空间,用来保存数据。
而定义一个变量,是需要类型的,这个是基本语法决定的。
那么,类型决定了:变量开辟空间的大小。
2.6 变量命名
我们为什么要在关键字这里学变量命名呢?
- 命名是职业化的表现
- 面试官会让我们现场写代码,如果我们命名太随意,会表现得很业余,体现了我们的职业素养
1.变量名应由“数字字母下划线”组成,且不能以数字开头
2.见名知义
命名应当直观且可以拼读,可望文知意,便于记忆和阅读
标识符最好采用英文单词或其组合,不允许使用拼音。程序中的英文单词一般不要太复
杂,用词应当准确。
3.大小驼峰
当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写。比如:
int CurrentVal;
这样的名字看起来比较清晰,远比一长串字符好得多。
4.避免混淆
程序中不得出现仅靠大小写区分的相似的标识符。
例如: int x, X; 变量 x 与 X 容易混淆
void foo(int x); 函数 foo 与 FOO 容易混淆
1(数字 1)和 l(小写字母 l)之间, 0(数字 0)和 o(小写字母 o)之间的区别
5.所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
一般来说习惯上用 n,m,i,j,k 等表示 int 类型的变量; c, ch 等表示字符类型变量; a 等表示数组; p 等表示指针。当然这仅仅是一般习惯,除了 i,j,k 等可以用来表示循环变量外,别的字符变量名尽量不要使用
C语言关键字命名清楚以上这些就足够了
2.7 最冤枉的关键字----sizeof
求特定一种类型,它对应开辟空间的大小的写法的过程中,下面哪些写法是正确的
int a = 10; printf("%d\n",sizeof(a)); //1 printf("%d\n",sizeof(int)); //2 printf("%d\n",sizeof a); //3 printf("%d\n",sizeof int); //4
4. err: sizeof作为一个关键字不能直接去求另外一个关键字的大小
其余三个都是可以的,换言之sizeof求一种特定开辟空间的大小时,有三种方法帮我们去求
分别对应上面代码块里的1,2,3 种写法
- 一种是(变量名)
- 一种是根据类型
- 还有是直接加变量名
下面我们通过数组、指针、指针数组这三个概念来理解sizeof
对了,在一般的学校考试,二级考试都会经常性的让你求字符串的空间容量,这里大家要注意的就是sizeof求字符串所占空间容量时,会把字符串结束符‘\0’也当作一个字节算在内