第三章 处理数据
第三章 处理数据
面相对象编程(OOP)的本质是设计并拓展自己的数据类型。设计自己的数据类型就是让类型与数据匹配
内置的 C++ 类型分两组:基本类型和复合类型。本章将介绍基本类型,即整数和浮点数。似乎只有两种类型,但 C++ 知道,没有任何一种整型和浮点型能够满足所有的编程要求,因此对于这两种数据,它提供 了多种变体。
3.1 简单变量
把信息存储在计算机中,程序必须记录 3 个基本属性:
- 信息将存储在哪里;
- 要存储什么值;
- 存储何种类型的信息。
实际上,程序将找到一块能够存储整数的内存,将该内存单元标记为 braincount,并将 5 复制到该内存单元中;然 后,您可在程序中使用 braincount 来访问该内存单元。
可以使用&运算符来检索 braincount 的内存地址。
3.1.1 变量名
C++ 提倡使用有一定含义的变量名,并遵循几 种简单的 C++ 命名规则:
- 在名称中只能使用字母字符、数字和下划线 ( _ );
- 名称的第一个字符不能是数字;
- 区分大写字符与小写字符;
- 不能将 C++ 关键字用作名称;
- 以两个下划线 或 下划线 + 大写字母 打头的名称被保留给实现(编译器及其使用的资源)使用;
- 以 一个下划线 开头的名称被保留给实现,用作全局标识符;
C++ 对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
倒数第二点与前面几点有些不同,因为使用像 __time_stop 或 _Donut 这样的名称不会导致编译器错误,而会导致行为的不确定性。换句话说,不知道结果将是什么。不出现编译器错误的原因是,这样的名称不 是非法的,但要留给实现使用。
如果想用两个或更多的单词组成一个名称,通常的做法是用下划线字符将单词分开,如 my_onions (用这类);或者从第二个单词开始将每个单词的第一个字母大写,如 myEyeTooth。
在 C++ 所有主观的风格中,一致性和精度是最重要的。请根据自己的需要、喜好和个人风格来使用变量名(或必要时,根据雇主的需要、喜好和个人风格来选择变量名)。
3.1.2 整型
不同 C++ 整型使用不同的内存量来存储整数。使用的内存量越大,可以表示的整数值范围也越大。
C++ 的基本整型(按宽度递增的顺序排列)分别是 char、short、int、long 和 C++11 新增的 long long,其中每种类型都有符号版本和无符号版本,因此总共有 10 种类型可供选择。
3.1.3 整型 short、int、long 和 long long
C++ 的 short、int、long 和 long long 类型通过使用不同数目的位来存储值,最多能够表示 4 种不同的整数宽度:
- short 至少 16 位;
- int 至少与 short 一样长;
- long 至少 32 位,且至少与 int 一样长;
- long long 至少 64 位,且至少与 long 一样长。
这意味着可以把 16 位单元设置成 65 536 个不同的值,把 32 位单元设置成 4 294 672 296 个不同的值,把 64 位单元设置为 18 446 744 073 709 551 616 个不同的值。作为比较,unsigned long 存储不了地球上当前的人数和银河系的星星数,而 long long 能够。
当前很多系统都使用最小长度,即 short 为 16 位,long 为 32 位。这仍然为 int 提供了多种选择,其宽度可以是 16 位、24 位或 32 位,同时又符合标准;甚至可以是 64 位,因为 long 和 long long 至少长 64 位。
类型的宽度随实现而异,这可能在将 C++ 程序从一种环境移到另一种环境(包括在同一个系统中使用不同编译器)时引发问题。
首先,sizeof 运算符返回类型或变量的长度;其次,头文件 climits(在老式实现中为 limits.h)中包含了关于整型限制的信息。具体地说,它定义了表示各种限制的符号名称。例如,INT_MAX 为 int 的最大取值,CHAR_BIT 为字节的位数。
在我的 Macbook Air 64 位 OS 上,数据类型是这样的:
int is 4 bytes.
short is 2 bytes.
long is 8 bytes.
long long is 8 bytes.
Maximum values:
int: 2147483647
short: 32767
long: 9223372036854775807
long long: 9223372036854775807
Minimum int value = -2147483648
Bits per byte = 8
赋值与声明合并在一起叫做初始化。 如果知道变量的初始值应该是什么,则应对它进行初始化。
如果不对函数内部定义的变量进行初始化,该变量的值将是不确定的。这意味着该变量的值 将是它被创建之前,相应内存单元保存的值。
3.1.4 无符号类型
要创建无符号版 本的基本整型,只需使用关键字 unsigned 来修改声明即可。注意,unsigned 本身是 unsigned int 的缩写。
3.1.5 选择整型类型
通常,int 被设置为对 目标计算机而言最为“自然”的长度。自然长度(natural size)指的是计 算机处理起来效率最高的长度。如果没有非常有说服力的理由来选择其 他类型,则应使用 int。
如果知道变量可能表示的整数值大于 16 位整数的最大可能值,则使用 long。即使系统上 int 为 32 位,也应这样做。这样,将程序移植到 16 位系统时,就不会突然无法正常工作(参见图 3.2)。如果要存储的值超过 20 亿,可使用 long long。
所以对于基因组数据上的位置信息,最好是 long 类型咯,即便 unsigned int 类型已经足够。(不过我可能还是希望用 unsigned int )
如果 short 比 int 小,则使用 short 可以节省内存。通常,仅当有大型整 型数组时,才有必要使用 short。如果只需要一个字节,可使用 char。
3.1.6 整型字面值
整型字面值(常量)是显式地书写的常量,如 212 或 1776。
诸如 cout<<hex;等代码不会在屏幕上显示任何内容,而只是修改 cout 显示整数的方式。因此,控制符 hex 实际上是一条消息,告诉 cout 采 取何种行为。另外,由于标识符 hex 位于名称空间 std 中,而程序使用了 该名称空间,因此不能将 hex 用作变量名。然而,如果省略编译指令 using,而使用 std::cout、std::endl、std::hex 和 std::oct,则可以将 hex 用作 变量名。
3.1.7 C++ 如何确定常量的类型
3.1.8 char 类型:字符和小整数
char 类型是专为存 储字符(如字母和数字)而设计的。
char 类型是另一种整型。实际上,很多系统支持的字符都不超过 128 个,因此用一个字节就可以表示所有的符号。因此,虽然 char 最常被用来处理字符,但也可以 将它用做比 short 更小的整型。
C++ 对字符用单引号,对字符串使用双引号。 cout 对象能够处理这两种情况,但正如第 4 章将讨论的,这两者有天壤之别)。
与 int 不同的是,char 在默认情况下既不是没有符号,也不是有符号。是否有符号由 C++ 实现决定,这样编译器开发人员可以最大限度地将这种类型与硬件属性匹配起来。如果 char 有某种特定的行为对您来说 非常重要,则可以显式地将类型设置为 signed char 或 unsigned char。
如果将 char 用作数值类型,则 unsigned char 和 signed char 之间的差异将非常重要。unsigned char 类型的表示范围通常为 0~255,而 signed char 的表示范围为 −128 到 127。
例如,假设要使用一个 char 变量来存储像 200 这样大的值,则在某些系统上可以,而在另一些系统上可能不可以。但 使用 unsigned char 可以在任何系统上达到这种目的。
如果大型字符 集是实现的基本字符集(如中文日文系统),则编译器厂商可以将 char 定义为一个 16 位的字节或更长的字节。其次,一种实现可以同时支持一个小型基本字符集和 一个较大的扩展字符集。8 位 char 可以表示基本字符集,另一种类型 wchar_t(宽字符类型)可以表示扩展字符集。wchar_t 类型是一种整数类型,它有足够的空间,可以表示系统使用的最大扩展字符集。这种类 型与另一种整型(底层(underlying)类型)的长度和符号属性相同。对底层类型的选择取决于实现,因此在一个系统中,它可能是 unsigned short,而在另一个系统中,则可能是 int。
3.2 const 限定符
C++ 有一种比 #define
更好的处理符号常量的方法,这种方法就是使用 const 关键字来修改变量声明和初始化。
const int Months = 12;
常量(如 Months)被初始化后,其值就被固定 了,编译器将不允许再修改该常量的值。如果您这样做,g++ 将指出程 序试图给一个只读变量赋值。关键字 const 叫做限定符,因为它限定了声 明的含义。
#define 定义符号常量的方式应抛弃。
一种常见的做法是将名称的首字母大写,以提醒您 Months 是个常量。这决不是一种通用约定,但在阅读程序时有助于区分常量和变量。另一种约定是将整个名称大写,使用#define 创建常量时通常使用这种约定。
注意: 如果在声明常量时没有提供值,则该常量的值将是不确定的,且无 法修改。
3.3 浮点数
使用浮点类型可以表示诸如 2.5、3.14159 和 122442.32 这样的数字,即带小数部分的数字。计算机将这样的值分成两部分存储。一部分表示值,另一部分用于对值进行放大或缩小。下面打个比方。对于数字 34.1245 和 34124.5,它们除了小数点的位置不同外,其他都是相同的。 可以把第一个数表示为 0.341245(基准值)和 100(缩放因子),而将第二个数表示为 0.341245(基准值相同)和 10000(缩放因子更大)。缩放因子的作用是移动小数点的位置,术语浮点因此而得名。C++ 内部表示浮点数的方法与此相同,只不过它基于的是二进制数,因此缩放因 子是 2 的幂,不是 10 的幂。
3.3.1 书写浮点数
C++ 有两种书写浮点数的方式:
- 第一种是使用常用的标准小数点表示法;
- 第二种表示浮点值的方法叫做 E 表示法,其外观是像这样的: 3.45E6,这指的是 3.45 与 1000000 相乘的结果;E6 指的是 10 的 6 次方,即 1 后面 6 个 0。因此,3.45E6 表示的是 3450000,6 被称为指数,3.45 被称为 尾数。
E 表示法确保数字以浮点格式存储,即使没有小数点。注意,既可 以使用 E 也可以使用 e,指数可以是正数也可以是负数,不要有空格。
电子的质量是 9.11e-31 kg 表示 0.000000000000000000000000000000911 kg, 而美国报警电话 911 竟然很巧合地与此相同。
3.3.2 浮点类型
C++ 也有 3 种浮点类型:float、double 和 long double。
事实上,C 和 C++ 对于有效位数的要求是,float 至少 32 位,double 至少 48 位,且不少于 float,long double 至少和 double 一样多。这三种类型的有效位数可以一样多。然而,通常,float 为 32 位,double 为 64 位, long double 为 80、96 或 128 位。另外,这 3 种类型的指数范围至少是 −37 到 37。可以从头文件 cfloat 或 float.h 中找到系统的限制。
3.3.3 浮点常量
在默认情况下,像 8.24 和 2.4E8 这样的浮点常量都属于 double 类型。如果希望常量为 float 类型,请使用 f 或 F 后缀。对于 long double 类型,可使用 l 或 L 后缀(由于 l 看起来像数字 1,因此 L 是更好的选择)。
3.3.4 浮点数的优缺点
与整数相比,浮点数有两大优点。首先,可以表示整数之间的 值。其次,由于有缩放因子,它们可以表示的范围大得多。另一方面, 浮点运算的速度通常比整数运算慢,且精度将降低。
如: 2.34E+22 是一个小数点左边有 23 位的数字。加上 1,就 是在第 23 位加 1。但 float 类型只能表示数字中的前 6 位或前 7 位,因此修改第 23 位对这个值不会有任何影响。
3.4 C++ 算术运算符
11.17 + 50.25
应等于 61.42,但是输出的却是 61.419998。这不是运算问题,而是由于 float 类型表示有效位数的能力有限。记住,对于 float,C++ 只保证 6 位有效位。如果将 61.419998 四舍五入成 6 位,将得到 61.4200,这是在保证精度下的正确值,如果用 double 则精度足够,所以直接可以获得 61.42 的值。
通常来说 double 都比 float 更精准,应尽量使用 double。
3.4.1 运算符优先级和结合性
算术运算符遵 循通常的代数优先级,先乘除,后加减。
3.4.2 除法分支
除法运算符(/)的行为取决于操作数的类型。如果两个操作数都 是整数,则 C++ 将执行整数除法。这意味着结果的小数部分将被丢弃, 使得最后的结果是一个整数。如果其中有一个(或两个)操作数是浮点 值,则小数部分将保留,结果为浮点数。
浮点常量在默认情况下为 double 类型。
3.4.3 求模运算符
求模运算符返回整数除法的余数。它与整数除 法相结合,尤其适用于解决要求将一个量分成不同的整数单元的问题。
3.4.4 类型转换
由于有 11 种整型和 3 种浮点类型,因此计算机需 要处理大量不同的情况,C++ 自动执行很多类型转换:
- 将一种算术类型的值赋给另一种算术类型的变量时,C++ 将对值进 行转换;
- 表达式中包含不同的类型时,C++ 将对值进行转换;
- 将参数传递给函数时,C++ 将对值进行转换。
C++ 允许将一种类型的值赋给另一种类型的变量。这样做时,值将 被转换为接收变量的类型。例如,假设 so_long 的类型为 long,thirty 的类 型为 short。
将一个值赋给值取值范围更大的类型通常不会导致什么问题。如,将 short 值赋给 long 变量并不会改变这个值,只是占用的字节更多而 已。然而,将一个很大的 long 值(如 2111222333)赋给 float 变量将降低 精度。因为 float 只有 6 位有效数字,因此这个值将被四舍五入为 2.11122E9。因此,有些转换是安全的,有些则会带来麻烦。
如下的报错是一个可能:
将 0 赋给 bool 变量时,将被转换为 false;而非零值将被转换为 true。
当同一个表达式中包含两种不同的算术类型时,将出现什么情况 呢?在这种情况下,C++ 将执行两种自动转换:首先,一些类型在出现 时便会自动转换;其次,有些类型在与其他类型同时出现在表达式中时 将被转换。
- 在计算表达式时,C++ 将 bool、char、unsigned char、signed char 和 short 值转换为 int。具体地说,true 被转换为 1,false 被转换为 0。这些转换被称为整型提升(integral promotion)。
int 是一种最自然的类型,运算速度也最快,要主用。
- 同样,wchar_t 被提升成为下列类型中第一个宽度足够存储 wchar_t 取值范围的类型:int、unsigned int、long 或 unsigned long。
- 将不同类型进行算术运算时,也会进行一些转换,例如将 int 和 float 相加时。当运算涉及两种类型时,较小的类型将被转换为较大的类型。
- 传递参数时的类型转换通常由 C++ 函数原型控制。
- C++ 还允许通过强制类型转换机制显式地进行类型转换。强制类型转换 的格式有两种。如下:
新格式的想法 是,要让强制类型转换就像是函数调用。这样对内置类型的强制类型转换就像是为用户定义的类设计的类型转换。
更安全的转换方式是使用 static_cast<typeName> (value)
函数。
Stroustrup 认为,C 语言式的强制类型转换由于有过多的可能性而极 其危险,这将在第 15 章更深入地讨论。运算符 static_cast<>
比传统强制 类型转换更严格。
3.4.5 C++11 中的 auto 声明
在初始化声明中,如果 使用关键字 auto,而不指定变量的类型,编译器将把变量的类型设置成 与初始值相同。
3.5 总结
C++ 的基本类型分为两组:一组由存储为整数的值组成,另一组由存储为浮点格式的值组成。整型之间通过存储值时使用的内存量及有无符号来区分。整型从最小到最大依次是:bool、char、signed char、 unsigned char、short、unsigned short、int、unsigned int、long、unsigned long 以及 C++11 新增的 long long 和 unsigned long long。还有一种 wchar_t 类型,它在这个序列中的位置取决于实现。C++11 新增了类型 char16_t 和 char32_t,它们的宽度足以分别存储 16 和 32 位的字符编码。C++ 确保了 char 足够大,能够存储系统基本字符集中的任何成员,而 wchar_t 则可以存储系统扩展字符集中的任意成员,short 至少为 16 位,而 int 至少与 short 一样长,long 至少为 32 位,且至少和 int 一样长。确切的长度取决于 实现。
浮点类型可以表示小数值以及比整型能够表示的值大得多的值。3 种浮点类型分别是 float、double 和 long double。C++ 确保 float 不比 double 长,而 double 不比 long double 长。通常,float 使用 32 位内存,double 使用 64 位,long double 使用 80 到 128 位。
对变量赋值、在运算中使用不同类型、使用强制类型转换时,C++ 将把值从一种类型转换为另一种类型。