定义:开辟空间,只能有一次.
声明:是告知,可以有多次.
在内存中开辟特定大小的空间,用来保存数据。
比如在main函数中定义的临时变量,在程序运行之前是没有被开辟空间的,是在程序运行时才能在栈上开辟的。而程序在运行之前必须被加载到内存中,所以所有的变量本质都是在内存的某个位置开辟空间的,也就解释了为什么是在内存中开辟的一块空间存储数据.
因为CPU计算处理内存中的数据,也是需要一步一步来的,将磁盘中的数据加载到内存中也是一块一块放的,所以数据需要暂时保存在变量中,便于后续系统处理.
int main()
{
int a=10;//初始化(与生俱来)
int b;
b=10;//赋值(给已经开辟好空间的变量叫赋值,后天努力)
return 0;
}
生命周期:描述生存时间长短,[定义,释放]
作用域:有效作用大小范围.变量名冲突:就近优先
代码块{}
中定义的变量,在函数栈空中开辟的.auto只能修饰局部变量auto int b=10;
,区别于C++中的自动类型推导.
距离CPU越近的存储单元,效率越高,单价越贵.CPU访问数据的时候,以最小的成本,达到最高的效率.
寄存器存在的本质:在硬件层面上,提高计算机的运算效率,因为不需要从内存中读取数据,因为缓存到寄存器中了.
尽量将所修饰的变量放到CPU寄存器中,提高效率.但是被register修饰的变量是在寄存器中开辟空间进行存储的,所以是没有地址的,也无法取地址(是针对内存的).但是可以是写入修改值的,因为已经是寄存器变量.
首先:声明并没有开辟空间,=100叫做赋值或者初始化,所有的变量声明的时候都不能设置初始值.
编译器编译时分别对两个源文件编译,由于互相看不到对方的数据所以会报警.但是在最后一步链接的时候,会将两个源文件形成的可执行文件整合为一个,然后根据函数地址找到对应函数,此时是能够看到的所以可以执行.
main源文件想要使用test源文件中的变量和函数,需要对于变量进行一次声明才能使用,那么如果想要很多源文件中的变量,对于大型的项目就会很麻烦,所以我们把这些函数和变量的声明放到一个文件中整合,然后main.c再包含就完美了,所以产生了头文件.h文件.
static修饰全局变量,该变量只能在本文件内被访问,不能被其他文件
直接
访问.如果函数被static修饰,该函数只能在本文件内访问.会出现链接时报错.
static修饰局部变量,更改局部变量的生命周期,把临时的改成全局的.但是作用域仍然没有变,还是在局部代码块中.初始化的动作,只会初始化一次.
//函数调用开辟空间并初始化
//函数结束释放空间
//临时变量i每次都被释放然后重新设置为0
//+static int i=0
static void func()
{
//int i = 0;
//i++;
//printf("%d\n",i);//1111111111
static int i = 0;
i++;
printf("%d\n",i);//12345678910
}
int main()
{
for (int i = 0; i < 10; i++)
{
func();
}
//show();
//printf("val= %d\n",val);
return 0;
}
大型项目多个文件之间需要进行多个文件交互.如果不能跨文件,成本就会增加.所以C语言默认支持跨文件,但是总有些需要隐藏的数据,所以用static不让其他文件看到.
被static修饰之后,变量存储位置从栈区变为全局数据区,使得声明周期发生改变.
确定一种类型对应开辟空间的大小.
int main()
{
int a = 10;
printf("%d\n", sizeof a);//基本事实足以说明sizeof 是关键字或者操作符不是函数
printf("%d\n", sizeof (a));
printf("%d\n", sizeof (int));
return 0;
}
int main()
{
int* p = NULL;
int arr[10];
int* test[10];
int(*test1)[10];
printf("%d\n",sizeof(p));
printf("%d\n",sizeof(arr));
printf("%d\n",sizeof(test));
printf("%d\n",sizeof(test1));
}
Linux环境的gcc编译器分配是0.vs环境会编译失败.理论上是不初始化就会被分配一个随机值.
存数据根据你提供的初始化的数据转化为补码然后放到任意类型声明的空间中.
读数据根据空间类型决定是否看最高位以及是否转化补码,如果是随机值,可能存在类型和数据不匹配的情况,造成误会.比如
unsigned int b = -10;
无符号类型存了一个有符号数,存可能没什么,但是读取的时候可能会出现问题.
float s = 3.14f;//不加f会发生截断
double d = 3.14;
b提供空间,你只需要将二进制序列放进来就行,具体是什么没人关心.在将数据保存在空间b之前已经被转化为二进制了.
你要保存的数据形式是原反补那种类型是由初始化时的正负号决定的,b只是提供了空间,和b的数据类型是无关的.放的时候左右两边的类型是不重要的,因为二进制早就转化完了.
单纯的一串数字数据是没有意义的,数据是需要带上类型才是有意义的.所以是在读取的时候存在意义,类型决定如何解释空间内部保存的二进制数字.(大小端存储和读取导致)
结论:
变量存:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带±号。和变量是否有符号无关!
变量取:取数据一定要先看变量本身类型sign int,然后才决定要不要看最高符号位然后决定是原反补。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端)
小端如何取?
- 先看大小端
- 再看自身类型
- 如果是有符号数,再读符号位,需要将补码->原码
有符号数char类型是8bit,数据范围是[-128,127].short[-2^15,2^15-1]
int[-2^32,2^32-1]
答案是255长度,因为遇到0就结束了.
a[0]=-1 a[127]=-128 a[128]=127 a[255]=0
[0~255] 应该是256个数,最后一个数是0,strlen在遇到0时自动停止,只会记录前面的255个数.也就是说字符串中0不是数据长度之一,只是分割标识符而已,但是占用一个字节要注意.
C语言中0为假,非0为真(1 2 -1).
C语言中是没有bool false true类型的.C89 C90(主流)是没有的
,C99是有_Bool
类型的.为了C++的兼容性,提供头文件#include
,占用1个字节.
大写的BOOL TRUE FALSE是微软提供的标准,只能在vs系列编过,不能跨平台使用,可移植性差.
bool flag = 0;
if (flag == 0)//不推荐,和int a==0表达式容易混淆
{
printf("1\n");
}
if (flag == false)//不推荐,只有包含了头文件才行C99
{
printf("2\n");
}
if (!flag)//推荐,直接进行判定功能(跳过第一步的计算表达式结果)
{
printf("3\n");
}
浮点数在内存中存储,并不是完整保存的,在十进制转化为二进制的过程中是有可能存在精度损失的.损失不是一味地减少,还有可能是增多,浮点数本身是存在算不尽的情况的,所以会存在四舍五入之类的.
浮点数在进行比较的时候不能直接用==
进行比较判断,因为存在精度损失.
#include
提供:DBL_EPSILON
double最小精度DBL_EPSILON
范围之内就行.#include
int main()
{
double d = 3.6;
if (fabs(d - 3.6) < DBL_EPSILON)
{
printf("You can see me!\n");
}
//double类型和0进行比较
double a = 0.00000000000000001;
if (fabs(a) < DBL_EPSILON)
{
printf("a == 0\n");
}
return 0;
}
在数值上,这三个值是相同的,都是0.当’\0’->'0’时,打印的是字符0的ASCII码值48.
强制类型转化并不改变内存中的二进制组合,而是改变读取他的形式类型.
真实的转化是真的改变内存中的数据.
所以,其实NULL的底层就是0,只是为了给用户更好的区分好看.
int* p = NULL;
if (p == 0)//误会是Int类型
{
}
if (!p)//误以为是bool类型
{
}
if (NULL == p)//推荐,立马能看出来p是指针,然后避免赋值=,所以放前面
{
}