文章目录
- 一.什么是变量
- C语言中为什么要有类型?
- C语言中的类型为什么有这么多种呢?
- 定义变量的本质
- 为什么需要定义变量
- 定义变量的本质
- 定义变量时的规则
- 二.深刻理解signed/unsigned定义的变量
- 1.运算时的符号位
- 2.数据的存储情况
- 3.unsigned定义时的小细节
- 三.大小端问题
- 四.signed定义的数据类型的取值范围
- 五.浮点数的一些奇奇怪怪的理解
- 六.数据在内存中的存储
一.什么是变量
C语言中为什么要有类型?
本质是对内存的空间合理划分,按需索取。int就是在内存开辟4个子节的空间,不会多开辟,也不会少开辟。
C语言中的类型为什么有这么多种呢?
因为应用场景不同,解决应用场景对应的计算方式也是不同的,需要空间的大小同样也是不同的。就行定义身高,你就是要用浮点型。定义年龄你只会用int型,而不是用20.1来代表你的年龄。
本质就是用最小的成本来解决各种多样化的问题。
定义变量的本质
在内存中开辟特定的大小的空间,来保存数据。
为什么需要定义变量
换句话说,为什么需要变量,因为有数据需要保存起来,等待后续的处理,如果不定义变量来保存,之后在用的时候可能就没了。
定义变量的本质
本质就是在内存中开辟一个空间用来保存数据。但是为什么一定要在内存中开辟呢?因为定义变量也是程序逻辑的一部分,在定义的时候,写的程序已经被加载到内存中了(程序在被加载之前都存在硬盘当中)。
定义变量时的规则
以下讲的都是比较推荐的写法,当然你要怎么定义就怎么定义。
- 用最短的名字传递最多的信息。
- 全局变量名字前最好加一个g_。
- 宏定义,只读变量,枚举常量定义的字母要全部大写,单词之间要用_来隔开。
- 定义变量要初始化。
这里稍微讲一下为什么要初始化,在任何函数包括主函数在程序运行时都会经过压栈,才能给函数在内存中开辟好空间,但不是所有编译器在开辟好空间后都会给你初始化成0,也就是说如果在里面定义变量又不初始化的时候,那些变量的值可能就是随机值。而后来你又不小心用到这些没有初始化的变量就会出错。所以不管你用不用这个变量,一定要先初始化。
二.深刻理解signed/unsigned定义的变量
signed/unsigned一般修饰的都是整型。
1.运算时的符号位
如果这里有一个有符号的两个变量,在计算的时候符号位要不要进行计算?
直接说结论:要。
2.数据的存储情况
先看这样的一个代码:
int main()
{
unsigned int a = -10;
printf("%d\n", a);
printf("%u\n", a);
return 0;
}
这代码的结果:
为什么会有如此大的反差呢?
接下来我一步一步分析。
先看-10是如何存的。
unsigned int只是为10开辟了一块4字节的空间:
但是-10在存之前是不看a是什么类型的。也就是说-10在存之前已经转换成了对应的二进制补码:
假如a这个变量的类型是char类型的,存进来后就会发生截断。
以上就是-10如何存到内存当中的,主要记住的一个点就是:数据是直接转化成对应的二进制补码之后才开始存到内存中的。
然后再看如何取的:
printf("%d\n", a);
printf("%u\n", a);
-10已经存到对应的变量里的,现在-10默认的是unsigned int类型的。入乡随俗嘛,你进到a这个变量里了,你就是和a类型一样了。但是这也只是默认的。
那%d,%u是什么意思呢?就是你在取得时候也不会考虑a这个变量是什么类型的,我用%d的方式来取,我就当你a里面存的就是有符号类型的值。现在读我就当里面的二进制数:
11111111 11111111 11111111 11110110是一个负数的补码。所以在打印的时候把最高位当成符号位然后转化成原码打印出来。
%u也是一样,它也不管a是什么类型,就当里面存的二进制数是一个无符号整数。所以直接把上面那个二进制数转成10进制数后打印出来也就是我们上面看到的那个很大的数。
小总结一下:
数据在存的时候不看要存的那个变量是什么类型的,直接先转化成对应的二进制补码,如果那个变量类型空间够就直接存,不够就发生截断在放进去。
取出的过程中,同样也不管你变量a是什么类型的,如果以%d形式打印我就认为你存的是一个有符号数。如果以%u打印我就认为你存的是一个无符号数。
3.unsigned定义时的小细节
推荐在定义无符号类型时后面+u。
int main()
{
//大小写u都可以
unsigned int b = 10U;
printf("%d\n", b);
printf("%u\n", b);
return 0;
}
这样加上之后就知道你这个数字它就是无符号类型,当然你不加也没关系,加上的话,如果你这个数字是负数还会报错。
三.大小端问题
本质是数据和空间以字节为单位的一种映射关系。
四.signed定义的数据类型的取值范围
这里以signed char来举例。
char会开辟一个字节也就是8个比特位的空间,取值范围应该是:
因为它是有符号数据,所以最高位应该是符号位,所以取值范围应该是:
仔细看可以发现一个问题,0在这里好像出现了两次:
10000000,00000000也就是一个+0,一个-0.
但是计算机为了提高效率不可能让这两个都表示0,同时为了让效率最大化也不能丢掉其中一个0.所以现在需要用多的那个0来表示一个其它数据,而多的那个0最好用10000000,因为00000000来表示0是最好不过的了。
现在的问题就是10000000用来表示谁呢?经过上图可以发现-127~127都有人来表示了,所以10000000只有128和-128两种选择。经过重重考量其实用10000000来表示-128是最好的了。为什么呢?首先这是有符号类型,也就是说最高位是符号位,而10000000最高位是1,说明它是一个负数,如果用它表示128这个整数就会有歧义。所以这里把它定义成128是最好的了。
所以通过这些可以判断出有符号char类型的取值范围是-128~127.
但是还有最后一个问题,凭啥?凭啥你说它代表-128就是-128.下面我们来看看-128是怎么存到空间中的:
先看-128的原码。
发现它是9位的,存到char里面要发生截断:
现在发现了一件神奇的事情-128的原码是110000000,它转化成补码后还是110000000.
然后在做一件事情:把10000000取出来:
因为之前发生了截断,所以不能正常的提取出来。所以在这里做了一个规定(主要啊,不是我规定的)10000000代表的就是-128,以后看到10000000就知道它是-128.
为了加深记忆,这里我画了一个图:
五.浮点数的一些奇奇怪怪的理解
我们先看一个代码:
int main()
{
double a = 1.0;
double b = 0.9;
printf("%.50lf\n", a);
printf("%.50lf\n", b);
printf("%.50lf\n", a - b);
return 0;
}
再来看结果:
打印数据的后50位,发现了一个神奇的现象,1确实还是1,但是0.9和1-0.9好像就不是原来的数了。
接下来在看段代码:
int main()
{
double a = 1.0;
double b = 0.9;
if (a - b == 0.1)
{
printf("相等");
}
else
{
printf("不相等");
}
return 0;
}
看结果:
发现1-0.9竟然不等于0.1,这两个不正常的现象是为什么呢?因为浮点数本身放在内存中后会有精度丢失的,可能比原来小一丢丢,也有可能大一丢丢。所以浮点数本身是不能直接比较的。
但如果我们就是想比较,该怎么做呢?比如说想比较a-0.1和b的值是否相等。此时可以在其基础上加上一个修正值。我们可以包含一个头文件:
#include <float.h>
这个文件里有两个宏定义:
这两个宏是什么意思呢?我拿其中一个说:有一个数字它加上任何一个数字,都能改变这个数字的大小。而DBL_EPSILON是这些数字中最小的那一个。
这个DBL_EPSILON就是我们需要添加的修正值。如果判断两个数是否相等,这两个数相减,值应该等于0对吧,但是因为精度的确实,这个值肯定会比0稍微大那么一丢丢丢丢或者小一丢丢丢丢。但是如果这个值在-DBL_EPSILON和DBL_EPSILON之间的话,我们还是可以认为它们俩相等的,所以代码可以这样写:
#include <float.h>
#include <math.h>
int main()
{
double a = 1.0;
double b = 0.9;
//需要比较的两个值是a-0.1和0.9
if (fabs(a - 0.1 - 0.9) < DBL_EPSILON)
{
printf("相等");
}
else
{
printf("不相等");
}
return 0;
}
既然相间的范围在-DBL_EPSILON和DBL_EPSILON之间,是不是可以写成绝对值在0和DBL_EPSILON之间。
所以如果只在这个区间内,我们还是可以认为这两个数相等的。
既然这样,我在提一个问题,上面的小于号可不可以写成小于等于:
#include <float.h>
#include <math.h>
int main()
{
double a = 0.0;
double b = 0.9;
if (fabs(a - 0.0) <= DBL_EPSILON)
{
printf("相等");
}
else
{
printf("不相等");
}
return 0;
}
上面先简化一下代码让a和0比较。首先答案时不推荐加上等号
首先这个代码是用来判断a和0是否相等,如果上面的条件加上等号就说明在a==DBL_EPSILON的时候,a和0相等这个逻辑也是成立的。但是DBL_EPSILON是引起一个数中变化最小的那个值,满足:b + DBL_EPSILON != b的这个条件。如果说a在等于DBL_EPSILON的时候同时满足a = = 0,不就相当于a = = DBL_EPSILON = = 0.但是0的概念是:b + 0 = = b.这和b + DBL_EPSILON != b有冲突,所以不能加等号。
六.数据在内存中的存储
数据在内存中的存储(一)
数据在内存中的存储(二)