C++编程基础:数据类型详解

发表时间: 2023-05-24 11:10

定义变量时,不可或缺的一个要素就是数据类型。本质上讲,这就是为了实现计算需求,我们必须先定义好数据的样式,告诉计算机这些数据占多大空间,这就是所谓“数据类型”的含义。

C++支持丰富的数据类型,它内置了一套基本数据类型,也为我们提供了自定义类型的机制。

接下来我们先介绍基本数据类型,主要包括算术类型和空类型(void)。其中算术类型又包含了整型和浮点型;而空类型不对应具体的值,只用在一些特定的场合,比如一个函数如果不返回任何值,我们可以让void作为它的返回类型。

1. 整型

整型(integral type)本质上来讲就是表示整数的类型。

我们知道在计算机中,所有数据都是以二进制“0”“1”来表示的,每个叫做一位(bit);计算机可寻址的内存最小单元是8位,也就是一个字节(Byte)。所以我们要访问的数据,都是保存在内存的一个个字节里的。

一个字节能表示的最大数是28 = 256,这对于很多应用来讲显然是不够的。不同的需求可能要表示的数的范围也不一样,所以C++中定义了多个整数类型,它们的区别就在于每种类型占据的内存空间大小不同。

C++定义的基本整型包括char、short、int、long,和C++ 11新增的long long类型,此外特殊的布尔类型bool本质上也是整型。

在C++中对它们占据的长度定义比较灵活,这样不同的计算机平台就可以有自己的实现了(这跟C是一样的)。由于char和bool相对特殊,我们先介绍其它四种。C++标准中对它们有最小长度的要求,比如:

l short类型至少为16位(2字节)

l int至少2字节,而且不能比short短

l long至少4字节,而且不能比int短

l long long至少8字节,而且不能比long短

现在一般系统中,short和long都选择最小长度,也就是short为16位、long为32位、long long为64位;而int则有不同选择。我们一般使用的电脑操作系统,比如Windows 7、Windows 10、Mac OS等等的实现中,int都是32位的。

所以short能表示的数有216 = 65536 个,考虑正负,能表示的范围就是-32768 ~ 32767;而int表示的数范围则为 - 231 ~ 231 - 1。(大概是正负20亿,足够用了)

#include<iostream>

using namespace std;

int main()

{

short a = 1;

cout << "a = " << a << endl;

cout << "a的长度为:" << sizeof(a) << endl;

int b;

cout << "b的长度为:" << sizeof(b) << endl;

long c;

cout << "c的长度为:" << sizeof(c) << endl;

long long d;

cout << "d的长度为:" << sizeof(d) << endl;

cin.get();

}

这里我们用到了sizeof,这是一个运算符,可以返回某个变量占用的字节数。我们可以看到,变量占用的空间大小只跟类型有关,跟变量具体的值无关。

2.无符号整型

整型默认是可正可负的,如果我们只想表示正数和0,那么所能表示的范围就又会增大一倍。以16位的short为例,本来表示的范围是-32768 ~ 32767,如果不考虑负数,那么就可以表示0 ~ 65535。C++中,short、int、long、long long都有各自的“无符号”版本的类型,只要定义时在类型前加上unsigned就可以。

short a = 32768;

cout << "a = " << a << endl;

cout << "a的长度为:" << sizeof a << endl;

unsigned short a2 = 32768;

cout << "a2 = " << a2 << endl;

cout << "a2的长度为:" << sizeof a2 << endl;

上面的代码可以测试无符号数表示的范围。需要注意,当数值超出了整型能表示的范围,程序本身并不会报错,而是会让数值回到能表示的最小值;这种情况叫做“数据溢出”(或者“算术溢出”),写程序时一定要避免。

由于类型太多,在实际应用中使用整型可以只考虑三个原则:

  • 一般的整数计算,全部用int;
  • 如果数值超过了int的表示范围,用long long;
  • 确定数值不可能为负,用无符号类型(比如统计人数、销售额等);

3. char类型

如果我们只需要处理很小的整数,也可以用另外一种特殊的整型类型——char,它通常只占一个字节(8位)。不过char类型一般并不用在整数计算,它更重要的用途是表示字符(character)。

计算机底层的数据都是二进制位表示的,这用来表示一个整数当然没有问题,可怎么表示字母呢?这就需要将常用的字母、以及一些特殊符号对应到一个个的数字上,然后保存下来,这就是“编码”的过程 。

最常用的字符编码集就是ASCII码,它用0~127表示了128个字符,这包括了所有的大小写字母、数字、标点符号、特殊符号以及一些计算机的控制符。比如字母“A”的编码是65,数字字符“0”的编码是48。

在程序中如果使用char类型的变量,我们会发现,打印出来就是一个字符;而它的底层是一个整数,也可以做整数计算。

char ch = 65;

cout << "65对应的字符为:" << ch << endl;

char ch2 = ch + 1;

cout << "66对应的字符为:" << ch2 << endl;

char类型用来表示整数时,到底是有符号还是无符号呢?之前的所有整型,默认都是有符号的,而char并没有默认类型,而是需要C++编译器根据需要自己决定。

所以把char当做小整数时,有两种显式的定义方式:signed char 和 unsigned char;至于char定义出来的到底带不带符号,就看编译器的具体实现了。

另外, C++还对字符类型进行了“扩容”,提供了一种“宽字符”类型wchar_t。wchar_t会在底层对应另一种整型(比如short或者int),具体占几个字节要看系统中的实现。

wchar_t会随着具体实现而变化,不够稳定;所以在C++11新标准中,还为Unicode字符集提供了专门的扩展字符类型:char16_t和char32_t,分别长16位和32位。

4. bool类型

在程序中,往往需要针对某个条件做判断,结果只有两种:“成立”和“不成立”;如果用逻辑语言来描述,就是“真”和“假”。真值判断是二元的,所以在C语言中,可以很简单地用“1”表示“真”,“0”表示“假”。

C++支持C语言中的这种定义,同时为了让代码更容易理解,引入了一种新的数据类型——布尔类型bool。bool类型只有两个取值:true和false,这样就可以非常明确地表示逻辑真假了。bool类型通常占用8位(1个字节)。

bool bl = true;

cout << "bl = " << bl << endl;

cout << "bool类型长度为:" << sizeof bl << endl;

我们可以看到,true和false可以直接赋值给bool类型的变量,打印输出的时候,true就是1,false就是0,这跟C语言里的表示其实是一样的。

5. 浮点类型

跟整数对应,浮点数用来表示小数,主要有单精度float和双精度double两种类型,double的长度不会小于float。通常,float会占用4个字节(32位),而double会占用8个字节(64位)。此外,C++还提供了一种扩展的高精度类型long double,一般会占12或16个字节。

除了一般的小数,在C++中,还提供了另外一种浮点数的表示法,那就是科学计数法,也叫作“E表示法”。比如:5.98E24表示5.98×1024;9.11e-31表示9.11×10-31。

// 浮点类型

float f = 3.14;

double pi = 5.2e-3;

cout << "f = " << f << endl;

cout << "pi = " << pi << endl;

这就极大地扩展了我们能表示的数的范围。一般来讲,float至少有6位有效数字,double至少有15位有效数字。所以浮点类型不仅能表示小数,还可以表示(绝对值)非常大的整数。

(float和double具体能表示的范围,可以查找float.h这个头文件)

6. 字面值常量

我们在给一个变量赋值的时候,会直接写一个整数或者小数,这个数据就是显式定义的常量值,叫做“字面值常量”。每个字面值常量也需要计算机进行保存和处理,所以也都是有数据类型的。字面值的写法形式和具体值,就决定了它的类型。

(1)整型字面值

整型字面值就是我们直接写的一个整数,比如30。这是一个十进制数。而计算机底层是二进制的,所以还支持我们把一个数写成八进制和十六进制的形式。以0开头的整数表示八进制数;以0x或者0X开头的代表十六进制数。例如:

  • 30 十进制数
  • 036 八进制数
  • 0x1E 十六进制数

这几个数本质上都是十进制的30,在计算机底层都是一样的。

在C++中,一个整型字面值,默认就是int类型,前提是数值在int能表示的范围内。如果超出int范围,那么就需要选择能够表示这个数的、长度最小的那个类型。

具体来说,对于十进制整型字面值,如果int不够那么选择long;还不够,就选择long long(不考虑无符号类型);而八进制和十六进制字面值,则会优先用无符号类型unsigned int,不够的话再选择long,之后依次是unsigned long、long long和unsigned long long。

这看起来非常复杂,很容易出现莫名其妙的错误。所以一般我们在定义整型字面值时,会给它加上一个后缀,明确地告诉计算机这个字面值是什么类型。

  • 默认什么都不加,是int类型;
  • l或者L,表示long类型;
  • ll或者LL,表示long long类型;
  • u或者U,表示unsigned无符号类型;

我们一般会用大写L,避免跟数字1混淆;而u可以和L或LL组合使用。例如9527uLL就表示这个数是unsigned long long类型。

(2)浮点型字面值

前面已经提到,可以用一般的小数或者科学计数法表示的数,来给浮点类型赋值,这样的数就都是“浮点型字面值”。浮点型字面值默认的类型是double。如果我们希望明确指定类型,也可以加上相应的后缀:

  • f或者F,表示float类型
  • l或者L,表示long double类型

这里因为本身数值是小数或者科学计数法表示,所以L不会跟long类型混淆。

(3)字符和字符串字面值

字符就是我们所说的字母、单个数字或者符号,字面值用单引号引起来表示。字符字面值默认的类型就是char,底层存储也是整型。

而多个字符组合在一起,就构成了“字符串”。字符串字面值是一串字符,用双引号引起来表示。

  • ‘ A ’ 字符字面值
  • “Hello World!” 字符串字面值

字符串是字符的组合,所以字符串字面值的类型,本质上是char类型构成的“数组”(array)。关于数组的介绍,我们会在后面章节详细展开。

Ø 转义字符

有一类比较特殊的字符字面值,我们是不能直接使用的。在ASCII码中我们看到,除去字母、数字外还有很多符号,其中有一些本身在C++语法中有特殊的用途,比如单引号和双引号;另外还有一些控制字符。如果我们想要使用它们,就需要进行“转义”,这就是“转义字符”。

C++中规定的转义字符有:

其中,经常用到的就是符号中的问号、双引号、单引号、反斜线,还有换行符和制表符。

// 转义字符

char tchar = '\n';

cout << "tchar = " << tchar << endl;

cout << "Hello World!\t\"Hello C++!\"" << endl;

(4)布尔字面值

布尔字面值非常简单,只有两个:true和false。

7. 类型转换

我们在使用字面值常量给变量赋值时会有一个问题,如果常量的值超出了变量类型能表示的范围,或者把一个浮点数赋值给整型变量,会发生什么?

这时程序会进行自动类型转换。也就是说,程序会自动将一个常量值,转换成变量的数据类型,然后赋值给变量。

// 1. 整数值赋给bool类型

bool b = 25; // b值为true,打印为1

// 2. bool类型赋值给算术整型

short s = false; // s值为0

// 3. 浮点数赋给整数类型

int i = 3.14; // i值为3

// 4. 整数值赋给浮点类型

float f = 10; // f值为10.0,打印为10

// 5. 赋值超出整型范围

unsigned short us = 65536; // us值为0

s = 32768; // s值为-32768

转换规则可以总结如下:

  • 非布尔类型的算术值赋给布尔类型,初始值为0则结果为 false , 否则结果为true ;
  • 布尔值赋给非布尔类型,初始值为 false 则结果为0,初始值为 true 则结果为1;
  • 浮点数赋给整数类型,只保留浮点数中的整数部分,会带来精度丢失;
  • 整数值赋给浮点类型,小数部分记为0。如果保存整数需要的空间超过了浮点类型的容量,可能会有精度丢失。
  • 给无符号类型赋值,如果超出它表示范围,结果是初始值对无符号类型能表示的数值总数取模后的余数。
  • 给有符号类型赋值,如果超出它表示范围,结果是未定义的( undefined )。此时,程序可能继续工作,也可能崩溃。

C++中的数据类型转换,是一个比较复杂的话题。我们这里先了解一下变量赋值时的自动类型转换,关于更加复杂的转换,我们会在下一章继续介绍。