一、前言
大家好,欢迎来到C语言深度解析专栏—C语言关键字详解第三篇,在本篇中我们将会介绍C语言当中的数据类型,并由此引出C语言当中的另外一个重要的关键字 — sizeof .
二、数据类型
1、数据类型有哪些
C语言的数据类型包括基本类型(内置类型)、构造类型(自定义类型)、指针类型以及空类型
2、为什么要有数据类型
为什么要有内置类型
我们日常生活中会遇到各种各样的场景,而不同的场景需要不同数据来表示,比如我们聚餐的人数、天气的温度、海拔的高度等事情通常用整数来描述,而像人的身高、广播的频率、商品的价格则通常用小数表示,又比如我们的车牌号、楼栋的命名、服装的尺码则需要要用字母来表示;C语言作为第一门高级程序设计语言,为了能准确描述我们生活中各种各样的场景,就有了整形、浮点型、字符型这些内置类型。
为什么要有自定义类型
我们以自定义类型中的数组类型和结构体类型举例:
数组类型:我们生活中会碰到许多相同类型的集合,比如一个学校学生的学号,每一个学生的学号都是整形,那么为了表示所有学生的学号,就势必要定义几千个整形,显然,那样太麻烦了,于是就产生了数组类型,一个数组里面的每个元素的类型都是相同的,我们在定义一个学校学生的学号时,只需要定义一个有几千个元素大小的数组即可,而不必去慢慢定义一千个整形
结构体类型:我们生活中所要描述的对象常常是复杂数据类型的集合,拿人来说,一个人有姓名、性别、身高、体重、年龄等等,这些数据类型都是不同的,那么为了能够系统的描述一个人的属性,就产生了结构体类型,它把一个人不同类型的数据都集中到一个新的类型当中,使对象描述和使用更加方便。
3、如何看待数据类型
从前面的博客中我们知道,定义变量的本质是在内存中开辟一块空间,用来存放数据,而今天我们知道不同的变量是需要定义为不同的类型的,把二者结合起来,我们就不难得出:类型决定的是变量开辟空间的大小。
这时有两个疑问点,第一、为什么要根据类型来开辟空间,我们直接开辟一块空间,将内存整体使用不好吗?答案是:不好。
原因主要有两点:
1、在任何时刻,你的电脑都不是只在运行你目前所使用的那个程序,还有其他许多的程序也在同时运行,如果把整块都分配给了你目前运行的程序,其他程序就崩溃了。
2、即使把整块内存都分配给你,你也不能保证在任何时刻都对该内存块全部都用完,这样就会导致内存的浪费。
第二、我们使用部分内存,使用多少由什么决定的?答案是:是由你的场景决定,你的计算场景,决定了你使用什么类型的变量进行计算。你所使用的类型,决定了你开辟多少字节的空间大小。这也是为什么C语言要有这么多的数据类型,就是为了满足不同的计算场景。
最后,那么不同的数据类型到底在内存开辟多少空间呢?这就需要使用我们的关键字 – sizeof 来计算了。
三、sizeof – 计算不同类型变量开辟空间的大小
1、内置类型开辟的空间大小
`#include<stdio.h> int main() { printf("%d\n", sizeof(char)); //1 printf("%d\n", sizeof(short)); //2 printf("%d\n", sizeof(int)); //4 printf("%d\n", sizeof(long)); //4 printf("%d\n", sizeof(long long)); //8 printf("%d\n", sizeof(float)); //4 printf("%d\n", sizeof(double)); //8 }`
2、自定义类型开辟的空间大小
数组大小
#include<stdio.h> int main() { int arr1[10] = { 0 }; //40 char arr2[10] = { 0 }; //10 long int arr3[10] = { 0 }; //40 long long arr4[10] = { 0 }; //80 float arr5[10] = { 0 }; //40 double arr6[10] = { 0 }; //80 printf("%d\n", sizeof(arr1)); printf("%d\n", sizeof(arr2)); printf("%d\n", sizeof(arr3)); printf("%d\n", sizeof(arr4)); printf("%d\n", sizeof(arr5)); printf("%d\n", sizeof(arr6)); return 0; }
从上面的结果我们很容易得出:数组的大小 = 数组元素的类型乘以元素个数
其他自定义类型的大小
#include<stdio.h> struct Test1{ int a; char b; float c; double d; }; union Test2{ int m; char n; }; enum Test3 { monday, tuesday, wednesday, thursday, frifay }; int main() { struct Test1 test1 = { 0 }; union Test2 test2 = { 0 }; enum Test3 test3; printf("%d\n", sizeof(test1)); //24 printf("%d\n", sizeof(test2)); //4 printf("%d\n", sizeof(test3)); //4 }
想必上面的结果与一些小伙伴心中的结果有所不同,确实,结构体、联合体、枚举这些自定义类型的大小和数组大小的求法是不相同的,其具体的求法涉及内存对齐、大小端、内存分配等相关知识,这些知识比较复杂,我会放在自定义类型详解模块中为大家讲解,现在大家不用去深究。
3、指针类型开辟的空间大小
大家可以看到,我们上面不管指针的类型是什么(整形、字符型、浮点型、数组型),指针的大小始终是四个字节或者八个字节(第一张图X86表示32位平台,结果为4,第二张图X64表示64位平台,结果为8),所以结论就是:指针在32位平台下是4个字节,在64位平台下是8个字节。(至于为什么是这样,这涉及到内存编址、地址线等相关知识,这一部分我会放在指针那里来详细讲解,现在大家只需要记住这个结论即可)
注:第二张图有警告是因为我的电脑是32位平台的,强制转成64位会发生大小不匹配的问题。
4、空类型开辟的空间大小
我们可以看到虽然这里编译器报错了,但它仍然打印出来了void的大小:0个字节
注:void类型的大小为0个字节,这仅仅是在visual studio这个编译器下运行的结果,但是,这个结果在不同的编环境下跑出来可能不同,就比如在Linux环境下,void类型的大小就为1,(由于水平的限制,这里暂时不能为大家演示);而导致这两者之间有差异的根本原因是不同编译环境对C语言的支持程度不同。
四、对sizeof 的进一步理解
1、sizeof 为什么不是函数
从上面我们可以看到,我们可以用 sizeof(a) 和 sizeof(int) 求一个整形的大小,这种方式也是大家所熟悉的,但是我们发现直接用
sizeof a 也能求出a的大小,而不需要圆括号,所以说,sizeof 是关键字(操作符)但是不是函数,因为函数参数需要用 () 起来才能正常使用。
注:sizeof int 报错是因为 sizeof 和 int 都是关键字,而不能用一个关键字去求另一个关键字的大小
2、sizeof 的其他使用
这里我们定义了一个整型变量 a 和 指针变量 p ,以及数组 arr,可以看到 a 的大小为 4,arr 的大小为40,这些我们都理解,
那么剩下的sizeof § 、sizeof(&arr) 、sizeof(arr) / sizeof(arr[0]) 是什么意思呢?下面为大家解释(涉及指针相关知识)
p 是一个指针变量,里面存放的是 a 的地址,arr数组名表示arr数组首元素的地址(记忆),&arr 表示取出整个数组的地址,相当于一个数组指针,所以sizeof§ 和 sizeof(&arr) 都是求的指针的大小,而在上面我们知道,指针在32位平台下是4个字节,所以这里结果为4。
最后,sizeof(arr) 求整个数组的大小,sizeof(arr[0]) 求第一个元素的大小,所以二者相除得到的是数组的元素个数10。
注:这里用sizeof(arr[0]) 来求一个数组元素的大小,而不是用arr[1] 、arr[2] 是因为我们不知道数组有几个元素,所以可能arr[1] 、arr[2] 根本不存在,但是只要定义了数组,那么arr[0] 就是一定是存在的,也就是说,这样做是为了安全。
更多关键字在下面博客链接
C语言关键字详解(一)auto、register关键字
C语言关键字详解(二)带你全面了解 static
码字不易,求个三连