1.双击实质--->加载内存
windows系统里面,双击的本质就是运行程序,把程序加载到内存里面;
任何程序运行的时候都必须加载到内存里面;
程序没有运行之前在硬盘里面,为什么程序运行之前必须加载到内存里面呢?
这个时候就有必要了解一下冯诺依曼体系结构:
我们输入的数据要到内存里面,经过CPU的处理和分析,最后显示到输出设备上面;
为什么一定要加载到内存上面呢,因为CPU访问内存的速度非常快,如果要是放到硬盘里面;
CPU访问硬盘的速度就会比较慢了,因此加载了以后就要放到内存里面。
2.变量的实质
(1)所有变量都在内存里面的某个位置开辟空间,变量在运行的时候才会开辟空间,程序运行的时候才会加载到内存里面,程序运行的时候变量已经在内存里面了,因此变量只能够在内存里面开辟空间,而不能在硬盘等其他的位置开辟空间。
(2)因为内存里面的数据要被临时存储,所以有了变量的存在;
(3)声明和定义:定义变量,定义只能定义一次,变量的定义是要开辟空间的;而声明相当于一个告知的作用,可以进行多次的声明;
3.生命周期&&作用域
生命周期:相当于一个时间概念,什么时候被开辟,什么时候被释放;
作用域:该变量的有效区域,局部变量在自己的代码块是有效的,全局变量整个程序都是有效的;
4.最宽宏大量的关键字------auto
auto关键字一般只修饰局部变量,而且一般省略不写,这个关键字已经很老套了,一般不使用;
5.最快的关键字-----register
寄存器,cache属于CPU范畴,距离CPU越近,代价成本越高,存储本身是存在分级的,任何一种硬件,都在充当着上游硬件的缓存,这样方便CPU访问数据的时候,降低成本,提高效率;
CPU内部集成了一组存储硬件,我们把这组硬件叫做寄存器;
register就是把变量放到寄存器里面,提高效率,局部变量(存在的时间比较短,如果是全局变量,使用register修饰的话,就会长时间占用空间),变量不会被写入,变量被高频读取(这个可以提高效率),这几种情况下都可以使用register修饰;
因此,我们称register为最快的关键字,相信你也知道了原因;
对于寄存器上面的变量,我们尽量不要使用取地址,图可见寄存器变量取地址程序会报错;但是我们可以进行写入,这个地方的意思就是比如我们想要对变量a=10进行加加操作,CPU具有运算的能力,计算了成为11以后要重新写回到内存里面,因此也就是我们可以对他进行赋值;
6.最名不符实的关键字-----static
(1)多文件系统:
首先要亮出一个结论:所有的变量在进行声明的时候,不能设置初始值!
我们可以尝试在vs里面新建2个.c文件,如果我们在文件1里面定义一个函数,我们在文件2里面不进行声明也是可以调用的,这个时候编译器警告,但是能够运行;
但是如果我们在文件1里面定义一个变量,这个时候就不能够在文件2里面打印输出这个变量的值,我们如果想要打印输出,就需要进行声明外部符号,使用extern关键字,但是使用extern声明外部符号的时候,不能对这个变量进行赋值,因为生命的时候不会开辟空间的,但是我们进行赋值就要把这个值放到变量对应的内存空间里面去,显然是会报错的,但是我们定义的时候开辟了内存空间,所以我们应该定义变量的时候对其进行初始化,声明的时候不给初始值;
(2)源文件&&头文件
相信有很多的初学者经常使用的是源文件,不是很清楚源文件和头文件的区别,下面我通过一些例子向你细细说到:
我们通过上面的多文件系统就可以了解到这个不同的文件使用并非在自己的文件里面定义的变量的时候,都需要声明外部变量符号,这个时候就需要考虑一个问题,如果我们搞一个项目的话,这个时候就会有很多的源文件,难道每个源文件都需要进行声明嘛,这样做的话未必也就太麻烦了吧,而且如果我们这样做,后期我们进行项目的维护的时候就会变得比较困难,例如我们想要修改某个变量或者函数;
这个时候源文件就出现了,在源文件里面我们可以把函数的声明,变量的声明放一份,其他文件就可以只需要包含头文件就可以了;
我们使用尖括号包含库里面的头文件,使用双引号包含自己的头文件
但是这个时候可能会出现头文件会被多次包含,我们只需要在头文件里面敲入#pragma once就可以解决这个问题了;
这个地方展示一个完整的使用案例:为了规范我们的代码风格,在进行变量的声明以及函数的声明的时候我们尽量加上extern关键字,声明函数的时候尽量加上参数的变量名字以及变量的类型,尽管进行函数的声明的时候只写函数参数的类型就足够了,我们尽量还是补充完整,规范自己的代码
(3)static修饰全局变量
我们通过上面的一系列的例子已经认识到了,全局变量是可以跨文件进行访问的,函数也是可以跨文件进行访问的,但是static这个时候就可以发挥自己的作用了
*static修饰全局变量之后,static修饰全局变量,只影响作用域,不影响生命周期;这个全局变量只有自己的文件可以访问,其他的文件不可以直接进行访问,一定要注意这里的“直接”两字,因为这个地方是不能直接进行访问,但是我们可以间接进行访问啊,并不是这个全局变量被static关键字修饰以后其他的文件就不能够进行访问了,下面通过一个具体的实例来了解一下吧:
下面我们看一下我们如何间接访问:
(4)static修饰函数
函数被static修饰了以后,这个函数不能够被其他的文件访问;但是同理,只是不能够被直接访问,是可以被间接访问的,就是通过其他的函数调用这个被static修饰的函数,以达到访问的目的
(5)static修饰局部变量
我们都知道局部变量具有临时性,只有在自己的代码块里面才是有效的,如果我们static修饰局部变量,就可以改变局部变量的生命周期,不影响作用域(这个地方和全局变量恰好相反),相当于这个时候的局部变量具有了全局性,延长了其生命周期;但是作用域不变,就是如果你在主函数里面输出stativ修饰的变量的值,是无法打印输出的,因为尽管他被修饰了,作用域(作用的范围)还是只在函数的内部;
没有static修饰:
这个时候调用函数,创建变量,开辟空间,调用函数完成以后,这块空间就会被销毁,相当于i每一次都是从0开始进行加加;
加上static修饰之后:
局部变量具有了全局性,每次函数的调用完成以后就不会被销毁变量空间,因此i能够从0开始,循环输出知道10停止;
(6)C程序地址空间
这个我们只需要理解为什么static修饰局部变量以后,他的生命周期具有了全局性,主要是变量的存储位置发生了改变,没有修饰之前的局部变量在栈区里面,被static修饰了以后,就在全局数据区,存储位置发生改变,是其生命周期变长的本质原因,栈区里面的数据存储就像手枪里面的弹夹,先进后出,后进先出,这个分别对应入栈,出栈2个动作,通过这张图我们也可以看出来,栈区里面的变量定义是从高地址到低地址,而堆区恰恰与之相反;
7.sizeof关键字
sizeof用来求不同的数据类型开辟的内存空间的大小;因为程序设计有许多不同的场景,因此我们设计了不同的类型用来存储与之对应的数据,这样可以更加高效的利用空间;
sizeof不是函数,只是用来求不同的数据类型的大小,这个类型既可以是我们已知的C语言内置数据类型,也可以是我们自己定义的指针类型等等;如:
整形数据占4个字节;64位操作系统,指针大小8字节,数组里面4个int*类型的数据,sizeof(arr)求的是整个数组的大小,4*8=32字节的大小;
8.unsigned和singned关键字
这两个一个是有符号(signed),一个是无符号类型(unsigned);
整形在内存里面的存储
(1)计算机里面储存的是补码,补码经过符号位不变,其他位按位取反,得到的是我们的反码,反码加一,得到的是我们的原码,实际上进行加一操作的时候,符号位也是要进行的,但是一般我们不会遇到这么大的数据,因为要想让符号位加上1,就表示符号位后面的所有位都是进过位的,或者是和符号位相近的的位数发生了进位的操作,一般我们是遇不到的;
(2)存储的本质:
我们可以自己亲手实践一下,定义一个无符号的变量,但是把一个负数赋值给这个变量,我们大部分情况下都会认为无符号的变量里面怎么能够存放一个负数呢?当我们运行起来的时候,发现程序并不会报错,这个是因为定义变量的时候就会先进行空间的开辟,这个时候把已经转换成为补码的二进制序列存进去,存进去的时候已经是二进制的补码了,存的时候不会关注数据的类型,所以无符号的数据类型也是可以存放负数的;
我们定义一个变量就要在内存上面开辟一块空间,这个时候进行数据存的过程,存放数据的时候,我们是不关心这个数据是有符号的还是无符号的,计算机只是单纯的把数据的补码存进去就可以了,这个时候是不关心数据的类型的,但是当我们取数据的时候,就要关心他的数据类型,关心他是有符号还是无符号的,因为这个决定我们是否要关注他的符号位,以及原码反码补码之间的相互转换;因为如果是无符号的,我们就会直接进行转换,如果是有符号的,我们就要关心他的符号位,而且无符号的符号位也会当作数值位进行计算的;
(3)大小端:低位放在低地址,高位放在高地址就是小端存储,低位放在高地址,高位放在低地址就是大端存储;首先我们要有一个基本常识:在我们的vs stdio里面,打开我们的内存窗口,里面显示的是数据的内存排布,这个时候从上到下地址从高到低,从左到右,地址也是由高到低,概括起来就是左上高,右下低(这个里面的高低指的是地址的高低);如图所示
这个时候我们借助大小端理解一下数据在内存里面的存储,实际上面数据的存储和大小端是有关的,计算机存的时候,取的时候,都是按照自己的大端的规则或者小端的规则,所以我们看的数据是不受影响的,但是数据的存储还是经历了大小端的这个过程;