Visual C++从入门到精通(第5版)
上QQ阅读APP看书,第一时间看更新

2.3 数据类型

C++语言中常用的数据类型包括数值类型、字符类型、数组类型、布尔类型、枚举类型、结构体类型、共用体类型、指针类型、引用类型和自定义类型。本节将详细介绍这些数据类型。

2.3.1 数值类型

C++语言中数值类型主要分为整型和实型(浮点类型)两大类。其中,整型按符号划分,可以分为有符号和无符号两大类;按长度划分,可以分为普通整型、短整型和长整型3类,如表2.2所示。

表2.2 整型类型

说明

表格中的[]为可选部分。例如,[signed] long [int]可以简写为long。

实型主要包括单精度型、双精度型和长双精度型,如表2.3所示。

表2.3 实型类型

在程序中使用实型数据时需要注意以下几点。

(1)实数的相加

实型数据的有效数字是有限制的,如单精度float的有效数字是6~7位,如果将数字86041238.78赋值给float类型,显示的数字可能是86041240.00,个位数“8”被四舍五入,小数位被忽略。如果将86041238.78与5相加,输出的结果为86041245.00,而不是86041243.78。

(2)实数与零的比较

在开发程序的过程中,经常会进行两个实数的比较,此时尽量不要使用“==”或“!=”运算符,而应使用“>=”或“<=”之类的运算符,许多程序开发人员在此经常犯错。例如:

上述代码并不是高质量的代码,如果程序要求的精度非常高,可能会产生未知的结果。通常在比较实数时需要定义实数的精度。例如:

【例2.2】 利用实数精度进行实数比较。(实例位置:资源包\TM\sl\2\2)

2.3.2 字符类型

在C++语言中,字符数据使用“' '”来表示,如'A''B''C'等。定义字符变量可以使用char关键字。例如:

在计算机中字符是以ASCII码的形式存储的,因此可以直接将整数赋值给字符变量。例如:

输出结果为“a”,因为97对应的ASCII码为“a”。

2.3.3 数组类型

数组是指具有相同数据类型的一组元素的集合,数组中的元素是按顺序存储在内存中的。数组按维数划分,可以分为一维数组、二维数组和多维数组。

1. 一维数组

在C++语言中,一维数组的定义格式如下:

例如,下面的代码定义了一个具有10个元素的整型数组。

在定义数组后,还需要访问数组中的元素。数组元素是通过数组名和下标来访问的,例如:

上面的代码将数组array中的第2个元素值设置为1。

注意

数组的下标是从0开始的。array数组共包含10个元素,下标分别为0、1、2、…、9。如果出现array[10],将会导致数组访问越界,发生意想不到的后果。编译器并不能识别此类错误,因此在访问数组元素时一定要谨慎,不要发生数组访问越界的情况。

在定义数组时,用户可以直接为数组赋初值。例如:

也可以只对部分元素赋初值。例如:

上面的代码只对array数组的前5个元素设置了初值。注意,不能给数组提供超过数组长度的初始值,例如下面的代码是不能通过编译的,将提示太多的初始值。

如果将数组元素全部初始化为0,用户可以简写为:

但是上述方式不能够将全部数组元素初始化为其他的值,例如将全部数组元素初始化为1。

上面的代码将导致第1个数组元素的值为1,其他数组元素的值为0。

如果需要对数组全部元素进行初始化,可以省略数组长度,但是数组下标符号“[]”不能省略。例如:

2. 二维数组

在C++语言中,二维数组的定义格式如下:

例如:

二维数组元素也是通过数组名和下标来访问的。例如:

可以认为二维数组是一个特殊的一维数组,只是数组中的每一个元素又是一个一维数组。例如,array[3][4]可以认为是一个一维数组array[3],其中的每一个元素又是一个包含4个元素的一维数组。

在定义二维数组时也可以直接进行初始化。例如:

用户也可以在一个大括号内直接初始化所有的元素。例如:

但是并不提倡该方法,因为如果数组元素过多,将很难界定每一个元素。

与一维数组类似,二维数组也可以只对部分元素进行初始化。例如:

结果是对每一行第1个元素赋值,其他元素为0。

对于二维数组,还可以只对某一个元素或某一行赋值。例如:

在定义二维数组时,如果需要提供全部元素的初始值,可以省略第一维的长度,但是不能省略第二维的长度。例如:

注意

最后一行代码,只提供了11个元素的初始值,但是数组array却包含12个元素,最后一个元素被初始化为0。

2.3.4 布尔类型

在逻辑判断中,结果通常只有真和假两个值。C++语言中提供了布尔类型bool来描述真和假。bool类型共有两个取值,分别为true和false。顾名思义,true表示真,false表示假。在程序中,bool类型被作为整数类型对待,false表示0,true表示1。将bool类型赋值给整型是合法的,反之,将整型赋值给bool类型也是合法的。例如:

2.3.5 枚举类型

在开发程序时,一个对象可能会存在多个状态。例如,Oracle数据库具有关闭、打开、装载、卸载等状态。如果直接在程序中使用0表示关闭状态,1表示打开状态……会使得代码难以阅读。有些用户定义了有意义的常量来表示各个状态,但是在涉及具体函数调用时,无法限制只允许使用“0、1、2、3”。

枚举类型提供了解决上述问题的最好方法。枚举类型提供了一组常量的集合。在定义函数时,将函数参数设置为枚举类型,这样可以限制调用函数必须提供枚举类型中的某个常量,而不能随意输入一个整数。在C++语言中,可以使用enum关键字定义枚举类型。定义格式如下:

使用enum关键字定义一个枚举类型,例如:

在定义枚举类型时,可以为各个常量提供一个整数值,如果没有提供整数值,默认第1个常量值为0,第2个常量值为1,以此类推。例如上面的代码中,CLOSE常量的值为0,OPEN的值为1……

下面为枚举类型设置常量值。例如:

在上面的代码中,将枚举常量CLOSE设置为1,MOUNT设置为4。那么OPEN和UNMOUNT的值是多少呢?由于没有为OPEN和UNMOUNT提供常数值,它们的值应为前一个常量值加1,即OPEN和UNMOUNT的值分别为2和5。下面来演示一下枚举类型的实际应用。

【例2.3】 应用枚举类型。(实例位置:资源包\TM\sl\2\3)

2.3.6 结构体类型

结构体是一组变量的集合。与数组不同,结构体中的变量可以有各种类型。通常将一组密切相关的信息组合为一个结构体,以描述一个对象。例如,描述学生信息,包括姓名、性别、年龄、地址等信息,可以定义一个结构体来描述学生的所有信息。

【例2.4】 定义结构体类型。

其中,关键字struct用于声明一个结构体类型。结构体中的变量被称为成员,如name、sex等。

注意

在声明结构体时,不要忘记末尾的分号。

在声明一个结构体后,可以定义一个结构变量。在C语言中定义结构变量的语法格式如下:

例如,下面的代码采用C语言的形式定义结构体变量。

在C++语言中定义结构体变量的格式与定义普通变量的格式相同。例如:

当定义一个结构体变量时,编译器将为变量分配足够的空间以容纳结构体中所有的成员。在声明结构体类型时,也可以直接定义结构体变量。例如:

【例2.5】 定义结构体类型时直接定义结构体变量。

上述代码在声明结构体Student的同时,定义了一个结构体变量stdnt。此外,在定义结构体时,如果只需要定义一次结构体变量(在其他地方不需要定义该类型的结构体变量),可以不写结构体类型的名称,而只给出结构体变量。例如:

在使用结构体时,需要访问结构体中的各个成员。可以使用“. ”符号来访问结构体中的成员。例如:

两个整型变量可以相互赋值,那么两个结构体变量能否直接赋值呢?答案是可以的。观察如下代码。

【例2.6】 结构体变量之间的赋值。(实例位置:资源包\TM\sl\2\4)

执行上述代码,结果如图2.2所示。

从图2.2中可以发现,another变量的age成员与stdnt变量的age成员是相同的;不仅如此,这两个变量的其他成员数据也相同。

在定义结构体变量时,编译器会为变量分配足够的空间以容纳结构体的所有成员。如果定义如下的一个结构体变量,编译器将为其分配多大的空间呢?

图2.2 结构体变量赋值

分析结构体成员,其中memOne类型为double,占用8个字节;memTwo类型为char,占用1个字节;memThree类型为int,占用4个字节。在定义结构体ByteAlign的变量时,应分配13个字节。但实际使用sizeof函数测试时,发现结构体ByteAlign的变量占用了16个字节。究竟是如何多出3个字节的呢?这涉及结构体的字节对齐问题。编译器在为结构体变量分配空间时,保证下一个成员的偏移量应为该成员数据类型长度的整数倍。分析一下ByteAlign结构在内存中的简单布局。首先为memOne成员分配空间,假设起始位置从0开始,memOne成员将占用0、1、2、3、4、5、6、7共8个字节。接下来为成员memTwo分配空间,由于char类型占用1个字节,因此,memTwo将占据8的位置,因为当前位置8与4是整除的。接下来为memThree成员分配空间,该成员为int类型,占用4个字节。当前位置为9,并不是4的整数倍,因此需要空出3个字节(9、10、11),memTree从12的位置开始分配4个字节的空间。这样就导致了实际分配的大小与“理论上”的大小不一致。

在开发应用程序时,有时需要在一个字节中表示多项内容。例如,在描述IP协议的首部时,其首部长度占4位(bit),版本号占4位,在定义描述IP协议首部的结构体时,该如何实现呢?与其他计算机语言不同,C/C++语言提供了位域,允许用户单独访问一位数据。例如,下面的代码定义了一个IP结构体,用于描述首部长度和版本号。

其中,headerlen成员类型为无符号字符型,理应占1个字节(8位),通过使用位域符号“:”和长度4,headerlen成员只占了“半”个字节—4位。在定义位域字段时,也可以不指定成员名称,这样可以预留一些空间。例如:

用户在访问memTwo成员时,将直接从一个字节的第5位开始读取数据。

2.3.7 共用体类型

共用体类型提供了一种机制,使得多个变量(共用体中的成员)可以共享同一个内存地址。下面的代码定义了一个共用体类型unType。

【例2.7】 定义共用体类型。

定义共用体与定义结构体类似,只是关键字不同,共用体使用union关键字。在共用体unType中,成员cdata与idata的内存起始位置相同,如图2.3所示。

由于共用体成员共用内存空间,因此如果视图改变了一个共用体成员的值,其他成员的值也会发生改变。但是,对于共用体来说,通常一次只需要使用一个成员。当定义一个共用体变量时,编译器会根据共用体成员中占用最多内存空间的变量分配空间,这样使得共用体中的所有成员都能够获得足够的空间。例如,定义一个unType类型的变量tg,编译器将为其分配4个字节的空间,因为idata需要4个字节的空间,cdata只需要1个字节的空间。

图2.3 共用体成员内部布局示意图

注意

共用体的内存空间可以用来存放数种不同类型的成员,但是在某一时刻只有一个成员起作用,起作用的成员是最后一次存放的成员。

2.3.8 指针类型

在C++语言的数据类型中,指针类型是最难掌握的,也是最重要的数据类型之一。灵活地应用指针,能够提高代码的执行效率。在介绍指针之前,先来回顾一下变量的属性。变量具有左值和右值两个属性,即变量的地址和变量的实际数据。指针是用来存放变量的地址的,即指针的值能够存储变量的地址。直接使用变量时,就称之为直接访问,而通过指针获得变量地址进行使用的方式被称为间接访问。这就像将一个物品放在银行的保险箱里,如果将钥匙带在身上,需要时直接打开保险箱拿东西,这就是直接访问。而为了安全起见,将保险箱的钥匙锁在家中的抽屉里,当你要拿东西时,首先要回家取得保险箱的钥匙,然后再使用钥匙打开保险箱,这就相当于用指针调用变量的地址所进行的间接访问。

如果一个指针存储了变量的地址,那么通过指针就能够访问到变量的值,因为变量的值是存储在变量的地址上的。假设有变量var,指向变量var的指针为pavr,如图2.4所示描述了指针与变量的关系。

图2.4 指针与变量的关系

在C++中,定义指针的语法格式如下:

例如,下面的代码定义了一个整型的指针变量。

只定义一个指针变量是没有意义的,还需要为指针变量赋值。指针变量的值应该是一个有意义的地址(通常是某个变量的地址),而不是数据。如何将变量的地址赋值给指针变量呢?C++中可以使用“&”运算符来获得变量的地址。下面的代码演示了通过“&”运算符获得变量地址,并将其赋值为指针变量。

当指针被赋予一个有效的变量地址后,如何通过指针访问或修改变量的数据呢?在指针变量前使用“*”运算符,即可直接访问变量的数据。

【例2.8】 使用“*”运算符访问指针数据。(实例位置:资源包\TM\sl\2\5)

执行代码,结果如图2.5所示。

分析上述代码,首先定义了一个整型变量ivar,将其初始化为10;然后定义一个指针变量pvar,将其初始化为ivar的地址;接着修改指针变量指向的地址上的数据;最后输出变量ivar的值和指针变量pvar的值。从图2.5中可以发现,变量ivar的值为8,表明通过指针变量修改了ivar的值。

指针变量不仅可以指向简单的变量,还可以指向数组变量。例如:

在上面的代码中,&iarray[0]表示获取数组中第1个元素的地址,即数组的首地址。对于数组来说,数组名同样表述数组的首地址,因此下面的代码与上述两行代码是完全相同的。

图2.6描述了当前指针变量pvar与数组iarray之间的关系。

图2.5 使用指针变量修改数据

图2.6 指针变量pvar与数组iarray之间的关系

当一个指针变量指向一个数组后,通过指针变量即可访问该数组中的每一个元素。例如,下面的代码利用指针输出数组中的每一个元素。

【例2.9】 使用指针变量输出数组元素。(实例位置:资源包\TM\sl\2\6)

执行上述代码,结果如图2.7所示。

在上述代码中,注意“pvar = pvar + 1;”语句,该语句的作用是使指针指向数组中的下一个元素,而不是简单地将指针地址加1或将指针值加1。语句“pvar = pvar + 1;”是移动指针pvar指向的地址,移动量不是1个字节,而是1个数组元素的大小,更准确地说应该是指针pvar的类型(这里为int类型)的大小。

在开发程序时,如果需要使用一组指针,可以定义一个指针数组。例如,下面的代码定义了一个整型的指针数组。

对于指针数组来说,数组中的每一个变量均是一个指针变量。下面的代码演示了指针数组的应用。

【例2.10】 利用指针数组存储数据。(实例位置:资源包\TM\sl\2\7)

执行上述代码,结果如图2.8所示。

图2.7 利用指针遍历数组

图2.8 指针数组应用

在定义指针时,也可以使用const关键字。例如:

对于指针pvar来说,用户不能够修改pvar指向的值,但是可以修改pvar指向的地址。例如:

在使用const关键字定义指针时,也可以将const关键字放置在指针变量的前面,但是指针变量的性质发生了改变。观察如下代码:

上面的代码定义了一个常量指针pvar,使用户不能够修改pvar指向的地址,但是可以修改pvar指向的数据。

在定义指针时也可以同时使用两个const关键字。例如:

在上面的代码中,用户既不能修改pvar指向的地址,也不能修改pvar指向的数据。例如:

2.3.9 引用类型

引用可以认为是一个“别名”,即目标的代名词。当定义一个引用时,需要为其指定一个对象,即目标。此时,引用就成了目标的代名词,操作引用与操作实际的目标对象是相同的。定义引用的格式如下:

下面的代码定义了一个引用对象。

在定义引用对象后,即可使用引用对象操作目标对象。

【例2.11】 使用引用对象代替目标对象。(实例位置:资源包\TM\sl\2\8)

执行上述代码,结果如图2.9所示。

从图2.9中可以看到,通过设置引用变量的值,修改了变量ivar的值。在程序中如果对引用对象进行取地址,返回的将是目标的地址,因为引用是目标的代名词。

【例2.12】 访问引用对象的地址。(实例位置:资源包\TM\sl\2\9)

执行上述代码,结果如图2.10所示。

图2.9 使用引用操作对象

图2.10 对引用对象进行取地址

说明

从图2.10中可以发现,变量ivar的地址与rvar的地址相同,对引用对象进行取地址运算,其实就是对目标对象进行取地址运算。

2.3.10 自定义类型

在C++语言中,用户可以使用typedef关键字自定义数据类型。自定义数据类型并不是真的创建新的数据类型,而是为已存在的数据类型定义一个新的名称。自定义类型的语法格式如下:

例如,下面的代码定义了一个UINT数据类型。

当定义完新的数据类型后,就可以像定义普通数据类型变量一样定义新数据类型的变量。例如,下面的代码定义了一个UINT类型的变量。

在程序中使用自定义类型的好处是能够提高程序的移植性。同一种数据类型,在不同机器或不同操作系统上,其长度或性质可能是不同的。如果程序中统一使用了自定义类型,在修改程序时,只需要修改自定义类型的基类型即可,代码的其他地方不需要改动。