本章是对C语言(C89)32个关键字中的部分关键字进行深度剖析以关键字为模块一个一个剖析,当然内容自然不会局限在关键字里
开搞!
#include
int main()
{
printf("hello world!\n");
return 0;
}//回到了起点哈哈哈哈
我们在执行该程序的时候通过预处理章节的知识可以知道我们的编译器通过编译连接等操作将我们的计算机语言转换成可执行程序语言(二进制语言), 然后我们的编译器会将它以文件的形式保存在硬盘中(可以找找在debug文件夹里后缀为.exe的文件就是可执行程序文件)当我们双击可执行文件时, 我们的电脑就将文件中的计算机语言加载到内存中让计算机运行.(当然大部分编译器可以直接运行程序)
也就是说我们程序在运行前需要先加载到内存中才可以被执行
变量是什么
在内存中的某个空间开辟内存用于数据的储存。
定义变量的原因
变量的作用相当于吃饭时的碗,他将我们马上要使用的数据进行储存,方便计算机的读取,从而加速了计算机的运行速度
变量的本质
我们定义变量的本质其实也就是在内存中开辟一个固定的内存来盛放我们的数据
变量的声明
声明不开辟空间只告知.extern 时将内容补齐
auto是一个很老的关键字了=-=
我们定义在代码块里的变量(及局部变量)默认都是被auto修饰的(都省略了)所以不写倒也没关系
讲一下作用域生命周期和代码块的相关知识
代码块:
用{}括起来的区域,就叫做代码块,
局部变量:
在代码块里被声明的变量叫做局部变量, 如果不被static修饰的话局部变量的作用域和生命周期都是在代码块内随着代码块的结束而释放.
全局变量:
在所有函数外(不被任何代码块包含)声明叫做全局变量,全局变量只有程序运行结束才会被释放.
注:现在编译器已经十分智能这个关键字也已经很少被使用了.
功能: 将修饰的变量推荐给寄存器,从而使编程速度加快
从推荐二字可以看出register的能力并不强,而且现在的编译器也十分智能了,知道将运算多的变量推荐给寄存器了
我们的cpu作为计算的主要硬件,为了方便运算会先将数据从内存读取到CPU中,所以我们的CPU也就要有一些暂时储存的能力.
所以我们的CPU就集成了寄存器来暂时保存数据.
所以寄存器存在的本质是为了加快运行速度因为不需要从内存中读取数值了.
首先我们需要先知道,全局变量和函数是可以跨文件使用的(记得声明及extern)
关键字功能
- 修饰局部变量加长局部变量的生命周期
- 修饰全局变量使他只能在本文件中使用
- 修饰函数使他只能在本文件中使用
修饰内容都放在静态区进行保存.
C语言有没有bool类型?
c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。
但是c99引入了
_Bool类型
(你没有看错,_Bool
就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了 bool,为了保证C/C++兼容性)。
#include
#include //没有这个头文件会报错,使用新特性一定要加上
#include
int main()
{
bool ret = false;
ret = true;
printf("%d\n", sizeof(ret));//vs2013 & 2019 和 Linux中都是1
system("pause");
return 0;
}
下面有个特殊案例
int main()
{//在vs中,光标选中BOOL,单击右键,可以看到转到定义,就能看到BOOL是什么
BOOL ret = FALSE;
ret = TRUE;
printf("%d\n", sizeof(ret));
//输出结果是4,因为在源代码中,是这么定义的:typedef int BOOL;
system("pause");
return 0;
}
这个特例是是Microsoft也就是微软自己编辑的一个bool类型是只针对微软而创建了所以在使用时只有在微软系统里才可以正常运行.
如在Linux中就会直接报错,而我们在编译代码时为了代码的可移植行不建议使用上面的特殊案例写法
在使用bool类型时
#include
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
}
system("pause");
return 0;
}
我们如果使用bool类型不建议使用==
号直接进行判断就好当然使用了也无所谓
浮点数在内存中存储,并不想我们想的,是完整存储的,在十进制转化成为二进制,是有可能有精度损失的。
注意这里的损失,不是一味的减少了,还有可能增多。浮点数本身存储的时候,在计算不尽的时候,会“四舍五入”或者其他策略
如下代码:
#include
int main()
{
double f = 3.6;
printf("%.50lf\n", f);
return 0;
}
所以因为精度损失的问题我们一定不可以简单的将两个浮点型使用==来进行比较相同
所以我们在比较两个浮点型相同时应当比较两者相减的误差
如下代码:
//那么两个浮点数该如何比较呢?
//应该进行范围精度比较
//伪代码
if((x-y) > -精度 && (x-y) < 精度)
{
//TODO
}
//伪代码-简洁版
if(fabs(x-y) < 精度)
{
//fabs是浮点数求绝对值 使用时要包含头文件math.h
//TODO
}
//精度: 自己设置?后面如果有需要,可以试试,通常是宏定义。 使用系统精度?
//暂时推荐
#include //使用下面两个精度,需要包含该头文件
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
真实转换和强制类型转换
- 真实的转换会改变数据存储数值如想要把"123456" ->123456 想把字符串类型转换成整形类型的数据时需要自己写算法或者使用函数来完成
- 强制类型转换是只转换类型内部储存的数据不变
而我们指针的零值NULL其实就是对应指针类型的0而已
我们之所以要使用NULL来代替0是为了程序员的阅读体验
void类型
首先void类型是不能直接定义变量的(因为变量是为了开辟空间void类型无空间开辟,所以编译器干脆不让通过)
void可以修饰函数或者函数参数修饰函数时表示函数无返回值,修饰函数参数时表示函数无参数(被void修饰的函数参数当你再次传参时编译器会报错或警告)
void*
类型指针
void*
可以直接定义变量因为指针类型开辟内存大小固定void*
类型的指针不能直接使用(毕竟指针类型决定了指针的跳跃能力,void*
类型跳跃能力为0一般在强转后再使用void*
可以接收任意类型的指针可以设置成通用接口.譬如在接收函数参数使用void*
类型
我们在删除数据时并没有将数据清空而只是将数据无效化了而已
return 用于终结函数并返回函数后面的值
值得注意的是我们return的返回值一般是不能返回栈空间内变量的(栈空间内主要是临时变量)返回后一般被摧毁
return;这样写也是可以的他只做终结函数这一个功能哪怕是void类型修饰的函数也可以使用return;还终结函数
(这个volatile关键字很少用的)
功能:防止内存变量被优化.(让变量一次次从内存中读取)
如果是仅看字面意思还是很迷惑人的,但是他的功能真的仅仅是防止修饰变量被优化.
那么这个鬼关键字有啥用呢?
(PS:这个有关多线程(你不懂其实我也不懂)的知识)
在多线程的情况下我们的变量值有可能会被改变
比如以下代码
#include
int main()
{
int flag = 1;
while (flag);
return 0;
}
一个简单的死循环,这时我们的变量flag很大的概率是会被我们的编译器优化的.优化后的我们的编译器就不会一次次的从内存中读取flag的变量值再进行判断了.
而是直接将flag的值放到寄存器中,或者直接不再读取flag的值而是直接进行一次次的循环运算.(对应两种优化方式)
这时我们的flag值如果发生了改变如变成0,可是我们的代码依旧在进行死循环式的运行就会出现bug.