引言:
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芯片程序开发都偏爱硬件。
初步认识了C语言的结构之后 博主将带您进入关键字的世界了
1. 关于变量
1.1 什么是变量
在内存中开辟特定大小的空间,用来保存数据
所有的变量,本质都是要在内存中的某个位置开辟空间的!
1.2 变量的定义与声明
类型 变量名 = 默认值 //定义类型格式 char c = 'c'; //初始化 c = 'd'; //赋值
赋值的意思就是这个变量c已经存在了,存在的本质就是这个变量对应的空间已经开辟出来了 ,换言之就是将'd'字符直接放到一个已经存在的空间中。
很遗憾,在C语言中,初始化和赋值的差别不是很大,唯一可能会出现的差别是在结构体数组那块内容,但是对于一般的内置类型基本上是没什么差别的,但是在C++和Java里面还是有差别的,这里不再赘述。
1.3 为什么要定义变量
计算机是为了进行计算的,而计算就需要数据,但是不是所有的数据都要立马被计算,有的数据是要先保存起来,等待后续处理
就像是吃饭,不是所有的饭菜都要立马被吃掉,要一口口吃,还没吃到的菜就需要暂时放到盘子里
盘子就如同变量,饭菜就相当于数据。
那么有人可能会说为什么要用“盘子”,直接去锅里不就行了吗?这当然没问题,但是效率太低了
1.4 变量定义的本质
定义就是用来开辟空间的 — 只能有一次
声明是用来告知的 — 声明可以多次
举个不恰当的例子:你去和你喜欢的女同学表白,表白就相当于定义,而表白成功之后就需要进行声明告知你的朋友们,告知可以有很多次,但是开辟空间只能是一次
2. 关键字
C语言一共多少个关键字呢?一般的书上,都是32个(包括本书),但是这个都是 C90(C89) 的标准。其实 C99 后又新增了5个关键字。不过,目前主流的编译器,对 C99 支持的并不好,我们后面默认情况,使用 C90 ,即认为32个
前面讲了蛮长的废话的(看了也没用),看到这里请不要着急,因为我们是两万字长文(收藏起来慢慢看)
接下来让我们一起细细地去品一下这些关键字,由这些关键字去学习or复习自己C语言的重要知识点
2.1 最宽宏大量的关键字 - auto
2.1.1 局部变量
包含在代码块中的变量叫做局部变量,局部变量具有临时性。
这里提一嘴:代码块我们就简单理解为花括号里的语句,花括号括起来的内容是不予外界代码进行直接交接的,可以用extern等关键字来交互(这样是为了保证程序代码的独立性和可维护性)
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
在学习register之前啊,我们还是先补充一下计算机硬件方面的知识吧
2.3.1 计算机的分级存储
CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了。
所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存。
金字塔从上到下容量越来越大、速度越来越慢、价格越来越低
一般距离CPU越近,存储效率越高,单价成本越高且容量一般是比较小的。
其中,快的设备给慢的设备做缓存内存,显示器。
计算机的分级存储让我们用最小的成本,使用上最高效率的计算机。我们现在使用的计算机,就是考虑成本和效率的结果
2.3.2 寄存器
我们可以不关心硬件细节,只要知道CPU内集成了一组存储硬件即可,这组硬件叫做寄存器
寄存器存在的本质:在硬件层面上,提高计算机的运算效率。因为不需要从内存里读取数据啦
我们知道了寄存器存在的作用之后再来看看寄存器变量—register吧
2.3.3 register 修饰变量
尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
register只是起建议作用,具体会不会将修饰的变量存放到寄存器中去还要看具体的场景
那么什么样的变量,可以采用register呢?
1. 局部的(全局会导致CPU寄存器被长时间占用,可能会加重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 的初始化动作,永远只会初始化一次,第一次!
之后就是在此基础上再对 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
“最冤枉”:sizeof会被很多人误解为函数,但是这里谨记: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(&数组名)
- sizeof(数组名)
对了,在一般的学校考试,二级考试都会经常性的让你求字符串的空间容量,这里大家要注意的就是sizeof求字符串所占空间容量时,会把字符串结束符‘\0’也当作一个字节算在内
2.8 signed、unsigned关键字
整型的存储
2.8.1 原码、反码、补码的补充
我们在将计算机中的数据拿出来,并将二进制形式转化为十进制形式有两种方法:
方法一:
1111 1111 1111 1111 1111 1111 1110 1100 补码
1111 1111 1111 1111 1111 1111 1110 1011 反码(由补码减1得到)
1000 0000 0000 0000 0000 0000 0001 0100 原码 (符号位不变,其余为按位取反)
方法二:原反补码之间的转化是由计算机硬件完成的,原码<=>反码<=>补码
按照下面的方法转换可以使用一条硬件电路完成转化
1111 1111 1111 1111 1111 1111 1110 1100 补码
1000 0000 0000 0000 0000 0000 0001 0011 反码 (符号位不变,其他位直接按位取反)
1000 0000 0000 0000 0000 0000 0001 0100 原码
方法一:先-1,在符号位不变,按位取反。
方法二:将原码到补码的过程在来一遍。
2.8.2 深入理解变量内容的存入和取出
在图中提到了空间是不关心内容的,那么这里的变量类型什么时候起效果呢?
我们在这里举个“栗子”:假如我口袋有一百元,那么我身上有多少钱?
答案可能是一百也可能不是,因为我们并不知道这里的100是哪种货币
so,100在这里是没有意义的;数字要带上类型才有意义
是在读取的时候,具有意义!!!
类型决定了如何解释空间内部保存的二进制序列
变量存的过程:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带+-号。和变量是否有符号无关!
变量取的过程:取数据一定要先看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端)
-10:1111 1111 1111 1111 1111 1111 1111 0110
4294967286:1111 1111 1111 1111 1111 1111 1111 0110
虽然打印出来转化后的二进制序列数值是一样的,但是十进制数值却不同,这就是由于最高位是否为符号位造成的差异
2.8.3 二进制快速转化口诀
2.8.4 大小端
如何理解大小端:
大家应该都知道int 类型的变量大小是四个字节,而内存存储也是以字节为单位的,所以我们在存储数据时,数据也要按照字节为单位划分成若干块,数据按照字节为单位,也是有高权值和低权值之分的,所以我们存取的顺序是有区别的,但是,无论如何放,只要同等条件去取,都是可以的!
如何存取是由计算机、内存硬件厂商决定的,但是每个厂商都有不同的标准,由此产生了两种存储方案:
1.大端:按照字节为单位,低权值位数据存储在高地址处,就叫做大端
2.小端:按照字节为单位,低权值位数据存储在低地址处,就叫做小端
举个例子:
int a = 0x11223344
地址: 0xA0 0xA1 0xA2 0xA3
11 22 33 44 大端
44 33 22 11 小端
2.8.5 记忆口诀
低权值位 — 权值位比较小
低地址处 — 地址数字比较小
权值位比较小的放在地址数字比较小的地方,就叫做小端存储
所以我们可以用“小小小”来记忆小端存储,不然则为大端存储的
大小端是如何影响数据存储的
大小端存储方案,本质数据和空间按照字节为单位的一种映射关系
2.8.6 练习题
2.9 if、else组合
2.9.1 什么是语句
C语言中由一个分号;隔开的就是一条语句。
比如:
printf("hehe");
1+2;
2.9.2 什么是表达式
C语言中,用各种操作符把变量连起来,形成有意义的式子,就叫做表达式。
操作符:+,-,*,/,%,>,<,=,==...
2.9.3 基本语法
语法结构: //1 if(表达式) 语句; //2 if(表达式) 语句1; else 语句2; //3.多分支 if(表达式1) 语句1; else if(表达式2) 语句2; else 语句3; //4.嵌套 if(表达式1){ 语句1; if(表示式x){ 语句x; } else{ 语句y; } } else if(表达式2){ 语句2; } else{ 语句3; }
2.9.4 if和else匹配问题
else 匹配采取就近原则
它总是与离他最近的if进行匹配
在书写代码的过程中尽量使用锯齿型,如上面的嵌套代码,就不会有匹配问题
2.10 _BOOL类型
2.10.1 什么是布尔类型
//测试代码 #include <stdio.h> #include <stdbool.h> //布尔类型的头文件 int main() { bool ret = false; ret = true; printf("%d\n", sizeof(ret)); //vs2013 和 Linux中都是1 system("pause"); return 0; }
为了深刻的理解布尔类型,我们要去查看布尔类型的源码
在vs中,光标选中bool,双击,可以转到定义,就能看到BOOL是什么
bool就是用宏定义表示_Bool , 0 就用false表示;1 就用true表示所以bool就是表示真假的
#define bool _Bool //c99中是一个关键字哦,后续可以使用bool #define false 0 //假 #define true 1 //真
2.10.2 bool值和0比较
#include <stdio.h> #include <stdbool.h> int main() { int pass = 0; //0表示假,C90,我们习惯用int表示bool //bool pass = false; //C99 if(pass == 0) { //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐 //TODO } if(pass == false) { //不推荐,尽管在C99中也可行 //TODO } if(pass) { //推荐 //TODO } //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐 //另外,非0为真,但是非0有多个,这里也不一定是完全正确的 if (pass != 1){ //TODO } if (pass != true){ //不推荐,尽管在C99中也可行 //TODO } if (!pass) { //推荐 //TODO } return 0; }
2.10.3 bool类型总结
C89,C90没有标准的bool类型,C99有正式的bool类型的
推荐写法:直接小写加true或false,在这里要记得C99标准的bool、true、false全部都要小写
如果大写的话就是微软的另一套布尔类型的标准了,Microsoft版本可移植性较差,尽量不用
和0比较的话,if直接判定布尔值结果,不用进行等于,等于0,或者等于false这样的写法,
就是bool类型,直接判定,不用操作符进行和特定值比较
2.10.4 浮点型和指针类型与零值的比较
相关分析在我的另一篇博客【C和0的不解之缘】里有详细的解析,请大家移步观看。
http://t.csdn.cn/4R2Uhhttp://t.csdn.cn/4R2Uh