5.C/C++数据类型详解:基础原理、注意事项与高效实践

一、前言

C++中类型主要分为两种:基本类型、符合类型。本文要介绍的是基本类型,也就是整数和小数。

无论学习什么编程语言,数据类型都是逃不开的,但你想过为什么会有它吗?

即使是python也不例外,只不过它数据类型比较隐蔽、且处于动态变化当中。

二、数据类型到底是什么?

从本质上来说,我们在编程语言中所定义的一切变量,都是在操作内存,比如下面这句代码:

int a=10;

上面这句代码的含义就是:分配一块int类型大小的内容,给它取了个名字叫做a,同时让这块内存存放数字10

至于什么是变量,前面已经提到过了,这里也就不再赘述了,想要更深层次的理解,可以查看另一篇文章:常量与变量

那么到底什么是内存?

这可不是你的C盘、D盘那个硬盘存储,它是一个你平时可能根本看不到的东西,但可以通过windows电脑自带的任务管理器查看:

image-20230913124546975

硬盘决定了你这台电脑可以下载、保存多少资料、程序。

而内存则决定了你电脑运行起来卡不卡,内存越大,能同时装进来运行的软件就可以更多,就越不容易卡顿。

我们在编程中所使用的数据类型所分配的内存,都是分配的这个内存。

C/C++中的基本数据类型总的来说只有两种:整数、小数。

但为了更加合理的分配内存,就出现了各种占用大小不同的类型,比如整数就有:shortintlong long,小数有:floatdouble

从本质上来说,无论是整数、还是小数,它们在内存中并没有太大的区别,都是一块指定大小的内存。

比如char类型占一个字节,long long占8个字节,double占8字节,字节相关内容可以查看:数据大小

之所以不同,取决于我们人为给它赋予的意义。

即:我们规定使用int的时候,这块内存就存放数字,使用char的时候,这块内存就存放字符。

但无论是整数、小数、还是字符,编译完成后都是数字0和1,且都放在了内存里。

其它语言不一定可以,但在C/C++语言中,你就可以利用这个特性任意强制转换解释方式:

char c='A';
print("%d",c);

上面的代码我使用%d输出数字的方式来打印一个字符类型,会报错吗?并不会。

因为char本质上来说,仅仅是占据一个字节的内存块而已,内存里面只能存0和1的数字,它存储的本身就是数字,所以你会看到打印出来的是65这个数字。

那为什么使用%c就能打印出字符呢?

这使用了一个叫做ASCII码表的东西,具体来说,就是当你想要打印字符,它就会查这个表,找到这个数字对应的字符,然后让其显示到屏幕上。

字符又是如何显示出来的?

这是提前保存到你电脑上的像素点,打印字符本质上就是将提前保存的对应字符显示到屏幕上而已。你所看到的所有汉字,之所以能够看到,就是因为你电脑上有对应的编码转换表,如果没有,那你就看不到这些汉字了。

当你理解了这个,甚至你还能更进一步理解指针了,比如我想要分配一块1024字节的缓存区,最常见的方式就是使用char,因为它占据一个字节,更好计算:

char buf[1024];

但如果你非要用int行不行?当然也行!

int buf[256];
char cbuf=(char*)buf

这两者其实就是一个意思,都是1024个字节大小的内存,只不过我们人为解释的方式不同而已。

而强制转换就是这样一种更换解释方式的语法。

三、注意点

从上面的介绍中相信大家也已经能够感受到一些奇妙之处了。

C/C++之所以有如此多的数据类型名仅仅只是一些历史遗留问题而已,因为以前的内存很珍贵,寸土寸金,而现在内存却越发的便宜了。

非要说的话,从如今的现代编程角度来看,这些名字都是没有必要的。

因为我们总的来说,只需要四种基本数据类型其实就足够了:整数、小数、字符、布尔。

比如C/C++中,charshortintlong long都是内存大小不同的数字。

至于floatdouble也只是内存大小不同的小数。

字符是个有趣且繁琐的东西,因为现代编程语言中基本都采用的是utf-8编码,每个字符可就不是占用一个字节了,而是可变的。

比如英文占用1字节,中文就占用3个字节,所以这里将其归为新的类型。至于字符串则是字符的集合。

而布尔,在C语言中它其实和char是一样的,占一个字节,只不过人为规定其不为0则为真,否则为假。

目前很火的现代语言rust中,我们就可以看到这种显著的变化:

直接指定存储数字需要分配的大小:i8,i16,i32,i64 //后面的数字为位数,要除以8才是所占字节
如果是存放正数就是:u8,u16,u32,u64
如果为小数就是:f32,f64
如果为字符就是:char   //char占四个字节,保证能表达所有类型的字符。
字符串就是:string  //为utf-8编码,每个字符所占空间都不一样,比如本站点:"kucoding.com",全英文12个字节,而如果是"酷编程网",汉字三字节,四个汉字也占12字节。
布尔类型就是:bool //它只有两种取值,true与false

因此注意点就来了,一般来说,完全不同的类型之间,就最好不要进行强制转换,否则很容易出现错误。

比如小数与整数,虽然都占相应的空间,但其底层存储、解释方式是不同的,一般不建议强转。

占位小的类型尽量不要转到占位大的类型。

比如char在C/C++中只占一个字节,你却要强制转成4字节的int来用,那么多出来的3个字节上面的数据对于我们来说就是未知的,后面使用它很容易出现问题。

而大转小就不存在这种问题了,但也要小心数据截断,比如int4字节,存255之内的数字强制转换为char自然没事,因为这些数字本身就只需要一个字节就能存。

可如果超过了255,那么就会造成超出255的数字被遗留在了高字节上,你拿到的数字只是最后低位上的一个字节大小的余数。

四、常使用点

理解数据类型本质上只是分配大小不同的内存了之后,你可能写起代码来就会更加随心所欲了。

比如一个函数需要一个数字,但这个数字肯定在255之内,那你就用一个char类型,而不是int类型。

判断一条语句是否为真的时候,你也没必要非要用bool类型,因为在C/C++中判断一个语句是否为真的条件是,它是不是不等于0

因此以下写法是完全等价的:

int a=10;
if(a!=0){}
if(a){}

还有很多时候,有些回调函数会让你传入指针,方便函数内部使用。

比如创建线程时,需要一个回调函数,同时你可以在创建线程的时候传递一个函数指针交给回调函数使用。

但有时候我就只想传入一个数字而已,用指针也太麻烦了吧?

但指针本质上也就是一个存储内存地址的内存而已,套了个娃。

所以你依旧可以强制转换:

void fun(void* p){
    int n=(int)p;
}
fun((void*)100)

先将数字100强制转换为指针,然后进入函数后,再强制转换回来即可。

但需要注意的是,指针所占用的内存大小时固定的,并且取决于当前你项目是32位还是64位的程序,指针占用内存大小与你项目位数是相同的。

但这基本仅限于C/C++语言,因为这种写法虽然写起来很爽,但如果看起来会很迷糊。

所以在现代编程语言中,都会尽量制止这种随意强制装换的写法。

五、无符号类型

C/C++编程中我们可能常常能看到unsigned关键字,它一般是加在其它类型前面的,比如:

unsigned int a;
unsigned char c;

它代表的就是无符号,说白了就是只能存放整数。

比如char占8个字节,按理来说可以存放0-255中任意的数字,但由于char本身为有符号的类型,最高位用作了判断这个数为正数还是负数,所以实际上只有7位用于存数,即只能表示0-127128个数字。

但由于最高位有了正负号,所以其实质上可以表示的是-128到127这个范围的数字。

其它类型也都类似,其实也仅仅只是更换了一个解释方法而已,内存还是那块内存。

不过用无符号类型的时候就需要注意,由于它不能表示负数,所以一旦使用减法,试图让它小于0时,它就会溢出变成一个很大的数字:

unsigned int a=0;
a-=1; //此时a不是-1,会变成一个很大的数字

六、sizeof

前面一直谈论数据类型的大小,C/C++中其实有一个运算符可以获取到这些类型的大小:sizeof

其用法非常简单:

#include<iostream>
using namespace std;
int main() {
	cout << sizeof(int) << endl;
	char c;
	cout << sizeof(c);
} 

它既可以直接获取类型的大小,也可以获取变量的大小,单位是字节

image-20231126155936343

但要注意,它是运算符,不是函数,它是在编译期间就完成了获取大小的工作,如果是无法获取动态分配内存大小的。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux