一、内存介绍
内存是计算机用来存储数据的地方,计算机通过对内存进行划分和编号来进行内存管理。大家都知道计算机的存储的数据实际上都是二进制的代码,也就是数字 0 和 1 的组合。二进制一位只有两种变化(也就是 0 和 1),也称为 1 bit。但是一位太小了,所以通常内存的基本单位是字节(byte)。1 字节 = 8 比特,也就是 32 位,对应 232种变化。
而计算机把内存分成一个个字节,对每个字节进行编号来对内存进行管理。大家可以这样理解,为什么快递和外卖可以送到家门口,因为每家每户都有地址。那么通过对内存进行编号,也就是给了一个地址,计算机就可以精准的把数据存储内存当中,这样就不会造成内存的浪费和数据的不连续。
1. 内存分布简图
上图中的内存的地址编号采用的是十六进制,也就是 32 位的内存。一共有 232次方个地址编号,内存大小也就是 232 字节,也就是 4 GB。
在 C 语言中,内存通常划分为栈区、堆区、数据段、常量区和代码段。
在 C 语言中一共存在四种基本内置类型,分别是字符型、整型、浮点型和布尔型。其中布尔型是 C99 标准加入的,而字符型的本质其实也是整型。
1. 整型整型说明白点就是用来存储整数的类型,但是整型下面还有细分。整型分为:short、int、long、long long 四个小类,它们的区别就是存储的数据大小不同。
为什么要对整型再次细分,这其实也很好理解。对一种确定的类型来说,它所需要的内存必须要固定,这样方便计算它的内存地址和存储。但是,程序员所写的程序需要面对各种各样的场景,有的场景需要存储极大的数据,有的场景却需要存储极小的数据。所以,如果整型只有一个类型,那么设置的所需空间过大在有些场景会造成浪费,设置过小则在有些场景会不够用。这就需要把整型继续细分,这样根据不同的场景就可以选择不同的整型来使用。
按照所占内存的大小来排序:short、int、long 和 long long。short 通常占 2 个字节,int 通常占 4 个字节,long long 通常占 8 个字节。C 语言并没有规定每个整型所占的内存大小,而是后一个类型至少和前一个类型一样大,也就是 short <= int <= long <= long long(<= 是 C 语言中的小于等于)。具体占多少内存取决于你使用的环境(也就是设备、编译器这些)。
那么 int 类型占 4 个字节,也就是 32 位,可以表示 232 个数,其表示范围为 -231 - 231 - 1。同理大家可以推理出来其他三个类型的表示范围,但是肯定在想为什么这么表示?这里提示一下,最高位代表符号位,也就是用来判断正负号了。
1.2 有符号整型和无符号整型 有符号整型既可以表示正数又可以表示负数,而无符号整型只能表示正数。所有的整型类型都可以有有符号型和无符号型,拿 int 来说,signed int 表示有符号 int 类型,而 unsigned int 表示无符号 int 类型,默认 int 代表 signed int,其他三个类型也是这个道理。通过下面这张图大家就可以更加清晰的了解。
有了上面的基础,那么浮点型就是存储带小数的类型。浮点型也有细分,按照所占内存大小分为:float 和 double,也就是单精度浮点型和双精度浮点型。double 不仅表示范围比 float 大,而且精度也比 float 高。(精度就是小数后面的位数,位数多精度大)
float 通常占 32 位,而 double 通常占 64 位。
浮点型没有有无符号型之分,它都可以表示正负数。浮点型的内存分布不像整型,可以直接当作二进制计算。它的内存分布和计算比较复杂,有兴趣的可以了解一下。
3. 字符型字符型简单来说就是用单引号括起的单个字符。字符型也有多种细分,但是我们目前只需要使用 char 类型一种即可,char 类型占 1 个字节,也就是 28 = 256。
通过前面内存那块的说明,大家都知道了计算机只能存储 0 和 1 的二进制组合,那么字符又是如何存储和输出的?实际上字符型存储在计算机中仍旧是二进制编码,只不过制作了一张字符对应的数字表。比如大写字母 A 对应整数 65,当把字符 A 存入内存中时,存储的数据实际上是整数 65,当需要把字符 A 进行输出时,编译器会根据 ASCII 表找到 65 对应的字符然后输出。
3.1 ASCII 表 3.2 有符号字符型和无符号字符型为什么字符也会有有符号型和无符号型?因为字符存储的本质实际上还是整数,在以前的计算机中,内存并没有现在这么大,在使用时需要节省内存。当遇到只需使用 0 - 255 或者 -128 - 127 之间的整数的情况时,通常把 char 当作整数来使用。
所以有符号整型 signed char 的表示范围为 -128 - 127,而无符号整型 unsigned char 的表示范围为 0 - 255。但是,char 和整型不同,char 不代表 signed char,也有可能代表 unsigned char,具体取决于你使用的环境。但是这个在现在基本上不需要担心,因为以现在的内存来说,根本不需要把 char 当作整数使用。
4. 布尔型——bool布尔型只有一种,也就是 bool 类型。布尔型只有两种取值,真和假。通常用于条件判断,当条件为真时执行第一条语句,当条件为假时,执行第二条语句。由此看来只需要 1 个 bit 就可以存储,但是内存的基本单元是 byte,所以 bool 类型占 1 个字节。
布尔型通常用于循环语句的条件判断。使用 bool 型需要包含头文件 stdbool.h
首先,运算符 sizeof,接受一个参数,返回其类型所占空间大小。然后便可以使用 printf 函数指定格式打印,关于两个函数的使用会有专门的章节进行介绍,这里会用即可。
(1)代码演示
(2)运行结果
我们先来看一个数学例子:令 x = 5。在这个例子中,x 是变量,而数字 5 是常量。因为数字 5 就是数字 5,而 x 却可以通过运算来改变,如:x = x + 1。
C 语言中的变量和上面例子中的变量类似。但是,C 语言中的变量需要内存空间,没有空间没办法存储数据。而不同的类型所需要的内存空间不同,所以当你在 C 语言中创建一个变量的时候,你需要告诉编译器你创建变量的类型。
1. 声明变量和定义变量声明变量就是告诉编译器你要创建的变量的类型和名称,但是编译器并没有为其开辟空间;而定义变量需要让编译器开辟空间。而在函数中创建的变量大多数都是定义。
(1)定义变量演示
上面的代码中(等号 = 在 C 语言中被称为赋值运算符),分别定义了整型变量 a,并给其赋值 10;定义了浮点型 b,并给其赋值 2.2;定义了字符型 c,并给其赋值 ‘a’。后面的分号表示该句话结束的意思,也就是编译器从本行第一个字符开始扫描,扫描到分号结束。由此可知,定义变量的公式为:类型 变量名 = 值;
初始化变量指的是在定义变量的同时给其赋值,在上方代码中的变量全被初始化了。
为什么需要初始化变量,因为当你定义变量时,编译器要去内存中寻找一块空间给予该变量,而这块空间之前可能被使用了,所以其中存储的内容不是你想要的,也就是垃圾值,为了避免使用垃圾值,一般都会在定义变量的同时对其进行初始化。而如果真的使用了未初始化的变量,那么编译器会进行报错。
3. 定义变量的本质定义变量的本质实际上是寻找一块合适的空间,然后把这块空间的使用权交给程序员。那么当编译器寻找到合适空间的时候,记录的是这块空间的地址编号,那么为什么可以通过变量名来使用这块空间?实际上编译器把变量名和其对应的地址编号联系在了一起,当程序写完后,在编译阶段编译器会把所有的变量名都替换成其对应的地址。
为什么要使用变量名来代替地址编号?因为自己可以自己取一个有意义的变量名,如鸡蛋的数量,可以使用 num_egg,这样一眼就看出来了这个变量的意思。如果让你拿着一串地址编号,我相信你看半天也不知道它代表什么意思。
4. 通过算数运算改变变量的值 现在只需要会使用加减乘除即可,如下代码:
当然,上面的代码中小细节还是有很多的,比如 a = a + 1 的运算过程是从右往左,先计算 a + 1,然后把结果存入一个临时变量,然后赋值给变量 a。这些后面都会专门抠细节。
实际上整型就是 int 类型,其它三种类型是在 int 类型上做的变化,short 实际上是 short int,long 实际上是 long int,long long 实际上时 long long int。但是为了便于书写,就把后面的 int 省略了。实质就是把表示的整数范围进行缩小和扩大。short 也称为短整型,long 称为长整型,long long 称为长长整型。
2. 浮点型浮点型其实有三种,float(单精度浮点型)、double(双精度浮点型)和 long double(扩展精度浮点型)。从左往右不仅精度扩大,表示范围也扩大。
3. 布尔型使用布尔型需要包含头文件 stdbool.h,但是布尔型刚开始的类型名是 _Bool,但是为了书写方便,使用 bool 也可以。