C++ Primer Plus(第6版)第三章 数据处理

面向对象编程 (OOP) 的本质是设计并扩展自己的数据类型。设计自己的数据类型就是让类型与数据匹配。如果正确做到了这一点,将会发现以后使用数据时会容易得多。然而,在创建自己的类型之前,须了解并理解 C++ 内置的类型,因为这些类型是创建自己类型的基本组件。内置的 C++ 类型分两组:基本类型和复合类型。本章将介绍基本类型,即整数和浮点数。似乎只有两种类型,但 C++ 知道,没有任何一种整型和浮点型能够满足所有的编程要求,因此对于这两种数据,它提供了多种变体。第 4 章将介绍在基本类型的基础上创建的复合类型,包括数组、字符串、指针和结构。当然,程序还需要一种标识存储的数据的方法,本章将介绍这样一种方法-使用变量:然后介绍如何在 C++ 中进行算术运算;最后,介绍 C++ 如何将值从一种类型转换为另一种类型。

3.1 简单变量

为把信息存储在计算机中,程序必须记录三个基本属性:

  • 信息将存储在哪里;
  • 要存储什么值
  • 存储何种类型的信息。
3.1.1 娈量名

C++提倡使用有一定含义的变量名。如果变量表示差旅费,应将其命名为 cost_of_trip 或 costOfTrip 而不要将其命名为 x 或 cot。必须遵循几种简单的C++命名规则。

  • 在名称中只能使用字母字符、数字和下划线 ( _ )
  • 名称的第一个字符不能是数字。
  • 区分大写字符与小写字符。
  • 不能将 C++ 关键字用作名称。
  • 以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以个下划线开头的名称被保留给实现,用作全局标识符
  • C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制.倒数第二点与前面几点有些不同,因为使用像time_ stop 或 Donut 这样的名称不会导致编译器错误,而会导致行为的不确定性。换句话说,不知道结果将是什么。不出现编译器错误的原因是,这样的名称不是非法的,但要留给实现使用。全局名称指的是名称被声明的位置,这将在第 4 章讨论。

下面是一些有效和无效的C++名称:

int poodle;       // valid
int Poodle;       // valid and distinct from poodle
int POODLE;       // valid and even more distinct
Int terrier;      // invalid has to be int, not Int int my stars3
int my stars3     // valid
int _Mystars3;     // valid but reserved starts with underscore
int 4ever;        // invalid because starts with a digit
int double;       // invalid -- double is a C++ keyword
int begin;        // valid -- begin is a Pascal keyword
int __fools:        // valid but reserved starts with two underscores
int the very best variable i can be version 112; // valid
int honky-tonk;   // invalid no hyphens allowed

如果想用两个或更多的单词组成一个名称,通常的做法是用下划线字符将单词分开,如 my_onions; 或者从第二个单词开始将每个单词的第一个字母大写,如 myEyeTooth。(C 程序员倾向于按 C 语言的方式使用下划线,而 Pascal 程序员喜欢采用大写方式。) 这两种形式都很容易将单词区分开,如 carDrip 和 cardRip 或 boat_sport 和 boats_ porto。

3.1.2 整型

整数就是没有小数部分的数字,如 2、98、-5286 和 0。整数有很多,如果将无限大的整数看作很大,则不可能用有限的计算机内存来表示所有的整数。因此,语言只能表示所有整数的一个子集。有些语言只提供一种整型(一种类型满足所有要求!,而 C++ 则提供好儿种,这样便能够根据程序的具体要求选择最合适的整型。不同 C++ 整型使用不同的内存量来存储整数。使用的内存量越大,可以表示的整数值范围也越大。另外,有的类型(符号类型,可表示正值和负值,而有的类型(无符号类型)不能表示负值。术语宽度 (width)用于描述存储整数时使用的内存量。使用的内存越多,则越宽。C++的基本整型(按宽度递增的顺序排列)分别是 char、short、int、long 和 C++11 新增的 long long,其中每种类型都有符号版本和无符号版本,因此总共有 10 种类型可供选择。下面更详细地介绍这些整数类型。由于 char 类型有一些特殊属性(它最常用来表示字符,而不是数字),因此本章将首先介绍其他类型。

3.1.3 整型 short、int、long 和 long long

计算机内存由一些叫做位 (bit)的单元组成。C++ 的 short、int、long 和 long long 类型通过使用不同数目的位来存储值,最多能够表示 4 种不同的整数宽度。如果在所有的系统中,每种类型的宽度都相同,则使用起来将非常方便。例如,如果 short 总是 16 位,int 总是 32 位,等等。不过生活并非那么简单,没有一种选择能够满足所有的计算机设计要求。C++ 提供了一种灵活的标准,它确保了最小长度(从 C 语言借鉴而来),如下所示,

  • short 至少16位:
  • int 至少与 short 一样长;
  • long 至少32位,且至少与 int 一样长;
  • long long 至少64位,且至少与long 一样长。

系统中整数的最大长度,可以在程序中使用 C++ 工具来检查类型的长度。首先,sizeof 运算符返回类型或变量的长度,单位为字节(运算符是内置的语言元素,对一个或多个数据进行运算,并生成个值。例如,加号运算符+将两个值相加)。前面说过,“字节” 的含义依赖于实现,因此在一个系统中,两字节的 int 可能是 16 位,而在另一个系统中可能是 32 位。其次,头文件 climits(在老式实现中为 limits.h)中包含了关于整型限制的信息。具体地说,它定义了表示各种限制的符号名称。例如,IT MAX 为 int 的最大取值,CHAR_ BIT 为字节的位数。程序清单 3.1演示了如何使用这些工具。该程序还演示奶何初始化,即使用声明语句将值赋给变量。

// 程序清单 3.1 limits.cpp
// limits. cpp some integer limits

#include <iostream>
#include <climits>
int main() {
    using namespace std;
    int n_int = INT_MAX;
    short n_short = SHRT_MAX;
    long n_long = LONG_MAX;
    long long n_llong = LLONG_MAX;

    // sizeof operator yields size of type or of variable
    cout << "int is " << sizeof (int) << "bytes." << endl;
    cout << "short is " << sizeof n_short << "bytes." << endl;
    cout << "long is " << sizeof n_long << "bytes." << endl;
    cout << "long long is " << sizeof n_llong << " bytes." << endl;
    cout << endl;

    cout << "Maximum values:" << endl;
    cout << "int: " << n_int << endl;
    cout << "short: " << n_short << endl;
    cout << "long: " << n_long << endl;
    cout << "long long: " << n_llong << endl;

    cout << "Minimum int value = " << INT_MIN << endl;
    cout << "Bits per byte = " << CHAR_BIT << endl; 
    return 0;
}

下面是程序清单 3.1 中程序的输出:

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 无符号类型

前面介绍的 4 种整型都有一种不能存储负数值的无符号变体,其优点是可以增大变量能够存储的最大值。例如,如果short 表示的范围为-32768 到+32767,则无符号版本的表示范围为 0-65535。当然,仅当数值不会为负时才应使用无符号类型,如人口、粒数等。要创建无符号版本的基本整型,只需使用关键字 unsigned 来修改声明即可:

unsigned short change;
unsigned int rovert;
unsigned quarterback:
unsigned long gone;
unsigned long long long_long;

注意,unsigned 本身是 unsigned int 的缩写,程序清单 3.2 演示了如何使用无符号类型,再看一看预处理器语句 #define

// 程序清单 3.2 exceed.cpp
// exceed.cpp -- exceeding some integer limits

#include <iostream>
#define ZERO 0
#include <climits>
int main() {
    using namespace std;
    short sam = SHRT_MAX;
    unsigned short sue = sam;

    cout << "Sam has " << sam << " dollars and Sue has " << sue;
    cout << " dollars deposited." << endl 
         << "Add $1 to each account." << endl << "Now ";
    sam = sam + 1;
    sue = sue + 1;
    cout << "Sam has " << sam << " dollars and Sue has " << sue;
    cout << " dollars deposited.\nPoor Sam!" << endl;
    
    sam = ZERO;
    sue = ZERO;
    cout << "Sam has " << sam << " dollars and Sue has " << sue;
        cout << " dollars deposited." << endl 
             << "Take $1 from each account." << endl << "Now ";
    sam = sam - 1;
    sue = sue - 1;
    cout << "Sam has " << sam << " dollars and Sue has " << sue;
    cout << " dollars deposited." << endl << "Lucky Sue!" << endl;
    return 0;
}

下面是该程序的输出

Sam has 32767 dollars and Sue has 32767 dollars deposited.
Add $1 to each account.
Now Sam has -32768 dollars and Sue has 32768 dollars deposited.
Poor Sam!
Sam has 0 dollars and Sue has 0 dollars deposited.
Take $1 from each account.
Now Sam has -1 dollars and Sue has 65535 dollars deposited.
Lucky Sue!

该程序将一个 short 变量 (sam) 和一个 unsigned short 变量 (sue) 分别设置为最大的short 值,在我们的系统上,是 32767。然后,将这些变量的值都加1。这对于 sue 来说没有什么问题,因为新值仍比无符号整数的最大值小得多;但 sam 的值从 32767 变成了 -32768!同样,对于 sam,将其设置为。 并减去 1,也不会有问题:但对于无符号变量 sue,将其设置为 。并减去后,它变成了 65535。可以看出,这些整型变量的行为就像里程表。如果超越了限制,其值将为范围另一端的取值。C++ 确保了无符号类型的这种行为:但 C++ 并不保证符号整型超越限制(上溢和下溢)时不出错,而这正是当前实现中最为常见的行为。一个思考题:无符号 0 - 1 为啥变成了 65535 ?

3.1.5 选择整型类型

C++ 提供了大量的整型,应使用哪种类型呢?通常,int 被设置为对目标计算机而言最为“自然” 的长度。自然长度 (natural size)指的是计算机处理起来效率最高的长度。如果没有非常有说服力的理由来选择其他类型,则应使用 int。现在来看看可能使用其他类型的原因。如果变量表示的值不可能为负,如文档中的字数,则可以使用无符号类型,这样变量可以表示更大的值。如果知道变量可能表示的整数值大于 16 位整数的最大可能值,则使用 long。即使系统上 int 为 32 位也应这样做。这样,将程序移植到 16 位系统时,就不会突然无法正常工作。如果要存储的值超过 20 亿,可使用 long long。如果数值比 int 小,则使用 short 可以节省内存。但通常,仅当有大型整型数组时,才有必要使用 short.(数组是一种数据结构,在内存中连续存储同类型的多个值。)如果节省内存很重要,则应使用 short 而不是使用 int, 即使它们的长度可能是一样的。例如,假设要将程序从 int 为 16 位的系统移到 int 为 32 位的系统,则用于存储 int 数组的内存量将加倍,但 short 数组不受影响。请记住,节省一点就是赢得一点。如果只需要一个字节,可使用 char,这将稍后介绍。

3.1.6 整型字面值

整型字面值(常量)是显式地书写的常量,如 212 或 1776。与 C 相同,c++ 能够以三种不同的计数方式来书写整数:基数为 10、基数为 8 和基数为 16,这里将介绍 C++ 表示法。c++ 使用前一(两)位来标识数字常量的基数。如果第一位 1~9,则基数为 10(十进制);因此 93 是以 10 为基数的。如果第一位是 0,第二位为 1~7,则基数为 8 (八进制),因此 042 的基数是 8,它相当于十进制数 34。如果前两位为 0x 或 0X,则基数为 16(十六进制),因此 0X42 为十六进制数,相当于十进制数 66。对于十六进制数,字符 a~f 和 A~F 表示了十六进制位,对应于 10~15。0xF 为 15, 0xA5 为165(10 个 16 加 5 个 1)。程序清单 3.3 演示了这三种基数。

// 程序清单 3.3 hexoct.cpp
// hexoctl.cpp -- shows hey and ontal literals

#include <iostream>
int main() {
    using namespace std;
    int chest = 42;
    int waist = 0X42;
    int inseam = 042;

    cout << "Monsieur cuts a striking figure! \n";
    cout << "chest = " << chest << "(42 in decimal)\n";
    cout << "waist = " << waist << "(0x42 in hex)\n";
    cout << "inseam = " << inseam << "(042 in octal)\n";
    return 0;
}

在默认情况下,cout 以十进制格式显示整数,而不管这些整数在程序中是如何书写的,如下面的输出所示:

Monsieur cuts a striking figure! 
chest = 42(42 in decimal)
waist = 66(0x42 in hex)
inseam = 34(042 in octal)

顺便说一句,如果要以十六进制或八进制方式显示值,则可以使用 cout 的一些特殊特性。前面指出过头文件 iostream 提供了控制符 endl, 用于指 cout 重起一行。同样,它还提供了控制符 dec、hex 和 oct, 分别用于指示 cout 以十进制、十六进制和八进制格式显示整数。程序清单 3.4 使用了 hex 和 oct 以上述三种格式显示十进制值 42。默认格式为十进制,在修改格式之前,原来的格式将一直有效。

// 程序清单 3.4 hexoct2.cpp
// hexoct2. cpp -- display values in hex and octa.

#include <iostream>
int main() {
    using namespace std;
    int chest = 42;
    int waist = 42;
    int inseam = 42;

    cout << "Monsieur cuts a striking figure! \n";
    cout << "chest = " << chest << " (decimal for 42)\n";
    cout << hex;
    cout << "waist = " << waist << " (hexadecimal for 42)\n";
    cout << oct;
    cout << "inseam = " << inseam << " (octal for 42)\n";
    return 0;
}

下面是运行该程序时得到的输出:

Monsieur cuts a striking figure!
chest = 42 (decimal for 42)
waist = 2a (hexadecimal for 42)
inseam = 52 (octal for 42)
3.1.7 C++ 如何确定常量的类型

程序的声明将特定的整型变量的类型告诉了 C++ 编译器,但编译器是如何知道常量的类型呢?假设在程序中使用常量表示一个数字:

cout << "year = " << 1492 << "\n";

程序将把 1492 存储为 int、long 还是其他整型呢?答案是,除非有理由存储为其他类型(如使用了特殊的后级来表示特定的类型,或者值太大,不能存储为 int ),否则 C++ 将整型常量存储为 int 类型。首先来看看后缀。后缀是放在数字常量后面的字母,用于表示类型。整数后面的 l 或 L 后缀表示该整数为 long 常量,u 或 U 后级表示 unsigned int 常量,ul (可以采用任何一种顺序,大写小写均可)表示 unsigned long 常量(由于小写 l 看上去像 1,因此应使用大写L作后级)。例如,在 int 为 16 位、long 为 32 位的系统上,数字 22022 被存储为 int, 占 16 位,数字 22022L 被存储为 long, 占 32 位。同样, 22022LU 和 22022UL 都被存储为 unsigned long。 C++11 提供了用于表示类型 long long 的后缀 ll 和 LL,还提供了用于表示类型 unsigned long long 的后缀 ull、Ull、uLL 和 ULL 。

在 C++ 中,对十进制整数采用的规则,与十六进制和八进制稍微有些不同。对于不带后缀的十进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、long 或 long long 在 int 为 16位、long 为 32 位的计算机系统上,20000 被表示为 int 类型,40000 被表示为 long 类型,3000000000 被表示为 long long 类型。对于不带后级的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、 unsigned int 、 unsigned long、long long 或 unsigned long long。在将 40000 表示为 long 的计算机系统中,十六进制数 0x9C40(40000) 将被表示为 unsigned int。这是因为十六进制常用来表示内存地址,而内存地址是没有符号的,因此,usigned int 比 long 更适合用来表示16位的地址。

3.1.8 char 类型:字符和小整数

下面介绍最后一种整型:char 类型。顾名思义,char 类型是专为存储字符(如字母和数字)而设计的。现在,存储数字对于计算机来说算不了什么,但存储字母则是另一回事。编程语言通过使用字母的数值编码解决了这个问题。因此,char 类型是另一种整型。它足够长,能够表示目标计算机系统中的所有基本符号:所有的字母、数字、标点符号等。实际上,很多系统支持的字符都不超过 128 个,因此用一个字节就可以表示所有的符号。因此,虽然char 最常被用来处理字符,但也可以将它用做比 short 更小的整型。在美国,最常用的符号集是 ASCII 字符集。字符集中的字符用数值编码(ASCII 码)表示。例如,字符 A 的编码为 65,字母 M 的编码为 77。为方便起见,本书在示例中使用的是 ASCII 码。

// 程序清单 3.5 chartype.cpp
// chartype.cpp -- the char type

#include <iostream>
int main() {
    using namespace std;
    char ch;
    cout << "Enter a character: " << endl;
    cin >> ch;
    cout << "Hola! Thank you for the " << ch << " character." << endl;
    return 0;
}

下面是该程序的输出:

Enter a character: 
M
Hola! Thank you for the M character.

有趣的是,程序中输入的是 M,而不是对应的字符编码 77。另外,程序将打印 M,而不是 77。通过查看内存可以知道,77 是存储在变量 ch 中的值。这种神奇的力量不是来自 char 类型,而是来自 cin 和 cout, 这些工具为您完成了转换工作。输入时,cin 将键盘输入的 M 转换为 77;输出时,cout 将值 77 转换为所显示的字符 M; cin 和cout 的行为都是由变量类型引导的。如果将 77 存储在 int 变量中,则 cout 将把它显示为 77(也就是说,cout 显示两个字符了)。程序清单 3.6 说明了这一点,该程序还演示了如何在 C++ 中书写字符字面值:将字符用单引号括起,如 'M' (注意,示例中没有使用双引号。C++ 对字符用单引号,对字符串使用双引号。cout 对象能够处理这两种情况,但正如第4章将讨论的,这两者有天壤之别)。

// 程序清单 3.6 morechar.cpp
/ / morechar. cpp -- the char type and int type contrasted

#include <iostream>
int main() {
    using namespace std;
    char ch = 'M';
    int i = ch;
    cout << "The ASCII code for " << ch << " is " << i << endl;
    cout << "Add one to the character code:" << endl;
    ch = ch + 1;
    i = ch;
    cout << "The ASCII code for " << ch << " is " << i << endl;
    return 0;
}

下面是该程序的输出

The ASCII code for M is 77
Add one to the character code:
The ASCII code for N is 78
3.1.9 bool 类型

ANSI/ISO C++标准添加了一种名叫bool 的新类型(对C++来说是新的)。它的名称来源于英国数学家 George Boole,是他开发了逻辑律的数学表示法。在计算中,布尔变量的值可以是 true 或 false。过去,C++ 和 C 一样,也没有布尔类型。在第 5 章和第 6 章中将会看到,C++ 将非零值解释为 true, 将零解释为 false。然而,现在可以使用 bool 类型来表示真和假了,它们分别用预定义的字面值 true 和 false 表示。也就是说,可以这样编写语句:

bool is ready = true;

字面值 true 和 false 都可以通过提升转换为 int 类型,true 被转换为 1,而 false 被转换为0:

int ans = true;
int promise = false;

另外,任何数字值或指针值都可以被隐式转换(即不用显式强制转换)为 bool 值。任何非零值都被转换为 true, 而零被转换为 false:

bool start = -100:
bool stop = 0;

后面我们还会详细讲,现在先简单了解一下这种数据类型 bool

3.2 const 限定符

现在回过头来介绍常量的符号名称。符号名称指出了常量表示的内容。另外,如果程序在多个地方使用同一个常量,则需要修改该常量时,只需修改一个符号定义即可。本章前面关于 #define 语句的说明(旁注“符号常量一预处理器方法”)指出过,C++ 有一种更好的处理符号常量的方法,这种方法就是使用 const 关键字来修改变量声明和初始化。例如,假设需要一个表示一年中月份数的符号常量,请在程序中输入下面这行代码:

const int Months = 12;

这样,便可以在程序中使用 Months, 而不是 12 了(在程序中,12 可能表示一英尺有多少英寸或一打面包圈是多少个,而名称 Months 指出了值 12 表示的是什么)。常量(如 Months)被初始化后,其值就被固定了,编译器将不允许再修改该常量的值。如果您这样做,g++ 将指出程序试图给一个只读变量赋值、关键字 const 叫做限定符,因为它限定了声明的含义。

一种常见的做法是将名称的首字母大写,以提醒您 Months 是个常量。这决不是一种通用约定,但在阅读程序时有助于区分常量和变量。另一种约定是将整个名称大写,使用 #define 创建常量时通常使用这种约定。还有一种约定是以字母 k 打头,如 kmonths。当然,还有其他约定。很多组织都有特殊的编码约定,要求其程序员遵守。

如果以前使用过 C 语言,您可能觉得前面讨论的 #define 语句已经足够完成这样的工作了。但 const 比 #defien 好。首先,它能够明确指定类型。其次,可以使用 C++ 的作用域规则将定义限制在特定的函数或文件中(作用域规则描述了名称在各种模块中的可知程度,将在第 9 章讨论)。第三,可以将 const 用于更复杂的类型,如第4 章将介绍的数组和结构。

3.3 浮点数

了解各种 C++ 整型后,来看看浮点类型,它们是 C++ 的第二组基本类型。浮点数能够表示带小数部分的数字,如 M1 油箱的汽油里程数 (0.56MPG),它们提供的值范围也更大。如果数字很大,无法表示为 long 类型,如人体的细菌数(估计超过 100兆),则可以使用浮点类型来表示。

使用浮点类型可以表示诸如 2.5、3.14159 和 122442.32 这样的数字,即带小数部分的数字。计算机将这样的值分成两部分存储。一部分表示值,另一部分用于对值进行放大或缩小。下面打个比方。对于数字 34.1245 和 34124.5,它们除了小数点的位置不同外,其他都是相同的。可以把第一个数表示为 0.341245(基准值)和100(缩放因子),而将第二个数表示为 0.341245(基准值相同)和 10000(缩放因子更大)。缩放因子的作用是移动小数点的位置,术语浮点因此而得名。C++ 内部表示浮点数的方法与此相同,只不过它基于的是二进制数,因此缩放因子是 2 的幂,不是 10 的幂。幸运的是,程序员不必详细了解内部表示。重要的是,浮点数能够表示小数值、 非常大和非常小的值,它们的内部表示方法与整数有天壤之别。

3.3.1 书写浮点数

C++ 有两种书写浮点数的方式。第一种是使用常用的标准小数点表示法:

12.34
939001.32
0.00023
9.0

即使小数部分为 0(如 8.0),小数点也将确保该数字以浮点格式(而不是整数格式)表示。(C++ 标准允许实现表示不同的区域,例如,提供了使用欧洲方法的机制,即将逗号而不是句点用作小数点。然市,这些选项控制的是数字在输入和输出中的外观,而不是数字在代码中的外观。)

第二种表示浮点值的方法叫做 E 表示法,其外观是像这样的:3.45E6,这指的是 3.45 与 1000000 相乘的结果,E6 指的是 10 的 6 次方,即 1 后面 6 个 0。因此,3.45E6 表示的是 3450000,6被称为指数,3.45 被称为尾数。下面是一些例子:

2.52e+8
8.33E-4
7E5
-18.32e13
1.69e12
5.98E24

d.dddE+n 指的是将小数点向右移口位,而 d.dddE-n 指的是将小数点向左移 n 位。之所以称为“浮点”,就是因为小数点可移动。

3.3.2 浮点类型

C++ 有三种浮点类型:float、double 和 long double。这些类型是按它们可以表示的有效数位和允许的指数最小范围来描述的。有效位 (significant figure)是数字中有意义的位。例如,加利福尼亚的 Shasta 山脉的高度为 14179 英尺,该数字使用了 5 个有效位,指出了最接近的英尺数。然而,将 Shasta 山脉的高度写成约 14000 英尺时,有效位数为 2 位,因为结果经过四舍五入精确到了千位。在这种情况下,其余的了位只不过是占位符而已。有效位数不依赖于小数点的位置。例如,可以将高度写成 14.162 千英尺。这样仍有 5 个有效位,因为这个值精确到了第 5 位。事实上,C 和 C++ 对于有效位数的要求是, float 至少 32 位,double 至少 48 位,且不少于 float, long double 至少和 double 一样多。这三种类型的有效位数可以一样多。然而,通常,float 为 32 位,double 为 64位,long double 为 128 位。另外,这了种类型的指数范围至少是 -37 到 37。可以从头文件 cfloat 或 float.h 中找到系统的限制。

程序清单 3.8 演示了 float 和 double 类型及它们表示数字时在精度方面的差异(即有效位数)。该程序预览了将在第 17 章介绍的 ostream 方法 setf()。这种调用迫使输出使用定点表示法,以便更好地了解精度,它防止程序把较大的值切换为 E 表示法,并使程序显示到小数点后6位。参数 ios base: fixed 和 ios base: floatfield 是通过包含 iostream 求提供的常量

// 程序清单 3.8 floatnum.cpp
// floatnum. cpp -- floating-point types

#include <iostream>
int main() {
    using namespace std;
    cout.setf(ios_base::fixed, ios_base::floatfield);
    float tub = 10.0 / 3.0;
    double mint = 10.0 / 3.0;
    const float million = 1.0e6;
    cout << "tub = " << tub;
    cout << ", a million tubs = " << million * tub;
    cout << ", \nand ten million tubs = ";
    cout << 10 * million * tub << endl;

    cout << "mint = " << mint << " and a million mints = ";
    cout << million * mint << endl;
    return 0;
}

下面是该程序的输出

tub = 3.333333, a million tubs = 3333333.250000, 
and ten million tubs = 33333332.000000
mint = 3.333333 and a million mints = 3333333.333333
3.3.3 浮点常量

在程序中书写浮点常量的时候,程序将把它存储为哪种浮点类型呢?在默认情况下,像 8.24 和 2.4E8 这样的浮点常量都属于 double 类型。如果希望常量为 float 类型,请使用 f 或 F 后缀。对于 long double 类型,可使用 l 或 L 后缀(由于 l 看起来像数字 1,因此 L 是更好的选择)。下面是一些示例:

1.234f             // float
2.45E20F           // float
2.345324E28        // double
2.2L               // long double
3.3.4 浮点数的优缺点

与整数相比,浮点数有两大优点。首先,它们可以表示整数之间的值。其次,由于有缩放因子,它们可以表示的范围大得多。另一方面,浮点运算的速度通常比整数运算慢,且精度将降低。程序清单 3.9 说明了最后一点。

// 程序清单 3.9 fltadd.cpp
// fltadd.cpp -- precision problems with float

#include <iostream>
int main() {
    using namespace std;
    float a = 2.34E+22f;
    float b = a + 1.0f;
    
    cout << "a = " << a << endl;
    cout << "b - a = " << b - a << endl;
    return 0;
}

该程序将数字加 1,然后减去原来的数字。结果应该为 1。下面是在我 MAC 系统上运行时该程序的输出:
a = 2.34e+022
b - a = 0
问题在于,2.34E+22 是一个小数点左边有 23 位的数字。加上 1,就是在第 23 位加 1。但 float 类型只能表示数字中的前 6 位或前 7 位,因此修改第 23 位对这个值不会有任何影响。

3.4 C++ 算术运算符

C++ 提供了几种运算符来完成 5 种基本的算术计算:加法、减法、乘法、除法以及求模。每种运算符都使用两个值(操作数)来计算结果。运算符及其操作数构成了表达式。例如,在下面的语句中:

int wheels = 4 + 2;

4 和 2 都是操作数,+ 是加法运算符,4 + 2 则是一个表达式,其值为 6。下面是 5 种基本的 C++ 算术运算符。

  • +运算符对操作数执行加法运算。例如,4 + 20 等于 24.
  • -运算符从第一个数中减去第二个数。例如,12 - 3 等于9
  • *运算符将操作数相乘。例如,28 * 4 等于 112.
  • /运算符用第一个数除以第二个数。例如,1000 / 5 等于 200。如果两个操作数都是整数,则结果为商的整数部分。例如,17 / 3 等于 5,小数部分被丟弃。
  • %运算符求模。也就是说,它生成第一个数除以第二个数后的余数。例如,19 % 6为 1,因为 19 是 6 的了倍余 1。
// 程序清单 3.10 arith.cpp
// arith.cpp -- some C++ arithmetic

#include <iostream>
int main() {
    using namespace std;
    float hats, heads;
    
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << "Enter a number: ";
    cin >> hats;
    cout << "Enter a other number: ";
    cin >> heads;

    cout << "hats = " << hats << "; heads = " << heads << endl;
    cout << "hats + heads = " << hats + heads << endl;
    cout << "hats - heads = " << hats - heads << endl;
    cout << "hats * heads = " << hats * heads << endl;
    cout << "hats / heads = " << hats / heads << endl;
    return 0;
}

下面是该程序的输出,从中可知 C++能够完成简单的算术运算:

Enter a number: 50.25
Enter a other number: 11.17
hats = 50.250000; heads = 11.170000
hats + heads = 61.419998
hats - heads = 39.080002
hats * heads = 561.292480
hats / heads = 4.498657
3.4.1 除法分支

除法运算符 ( / ) 的行为取决于操作数的类型。如果两个操作数都是整数,则 C++ 将执行整数除法。这意味着结果的小数部分将被丢弃,使得最后的结果是一个整数。如果其中有一个(或两个)操作数是浮点值,则小数部分将保留,结果为浮点数。程序清单 3.11 演示了 C++ 除法如何处理不同类型的值。和程序清单 3.10 一样,该程序也调用 setf() 成员两数来修改结果的显示方式。

// 程序清单 3.11 divide.cpp
// divide. cpp -- integer and floating-point division

#include <iostream>
int main() {
    using namespace std;
    cout.setf(ios_base::fixed, ios_base::floatfield);   
    cout << "Integer division: 9/5 = " << 9 / 5 << endl;
    cout << "Floating-point division: 9.0/5.0 = " << 9.0 / 5.0 << endl;
    cout << "Mixed division: 9.0/5 = " << 9.0 / 5 << endl;
    cout << "double constants: 1e7/9.0 = " << 1e7 / 9.0 << endl;
    cout << "float constants: 1e7f/9.0f = " << 1e7f / 9.0f << endl;
    return 0;
}

程序清单 3.11 中程序的输出:

Integer division: 9/5 = 1
Floating-point division: 9.0/5.0 = 1.800000
Mixed division: 9.0/5 = 1.800000
double constants: 1e7/9.0 = 1111111.111111
float constants: 1e7f/9.0f = 1111111.125000

从第一行输出可知,整数 9 除以 5 的结果为整数 1。4 / 5 的小数部分(或 0.8)被丢弃。在本章后面学习求模运算符时,将会看到这种除法的实际应用。接下来的两行表明,当至少有一个操作数是浮点数时,结果为 1.8。实际上,对不同类型进行运算时,C++ 将把它们全部转换为同一类型。本章稍后将介绍这种自动转换。最后两行的相对精度表明,如果两个操作数都是 double 类型,则结果为 double 类型:如果两个操作数都是 float 类型,则结果为 float 类型。

3.4.2 求模运算符

求模运算符返回整数除法的余数。它与整数除法相结合,尤其适用于解决要求将一个量分成不同的整数单元的问题,例如将英寸转换为英尺和英寸,或者将美元转换为元、角、分、厘。第 2 章的程序清单 2.6 将重量单位英石转换为磅。程序清单 3.12 则将磅转换为英石。记住,一英石等于 14 磅,多数英国浴室都使用这种单位。该程序使用整数除法水计算合多少英石,再用求模运算符来计算余下多少磅。

// 程序清单 3.12 modulus.cpp
// modulus.cpp -- uses & operator to convert lbs to stone

#include <iostream>
int main() {
    using namespace std;
    const int Lbs_per_stn = 14;
    int lbs;
    cout << "Enter your weight in pounds: ";
    cin >> lbs;
    int stone = lbs / Lbs_per_stn;
    int pounds = lbs % Lbs_per_stn;
    cout << lbs << " pounds are " << stone << " stone, "  << pounds << " pounds(s)" << endl;
    return 0;
}

下面是该程序的运行情况:

Enter your weight in pounds: 181
181 pounds are 12 stone, 13 pounds(s)
3.4.3 类型转换

C++ 丰富的类型允许根据需求选择不同的类型,这也使计算机的操作更复杂。例如,将两个 short 值相加涉及到的硬件编译指令可能会与将两个 long 值相加不同。由于有 11 种整型和 3 种浮点类型,因此计算机需要处理大量不同的情况,尤其是对不同的类型进行运算时。为处理这种潜在的混乱,C++自动执行很多类型转换:

  • 将一种算术类型的值赋给另一种算术类型的变量时,C++将对值进行转换;
  • 表达式中包含不同的类型时,C++将对值进行转换;
  • 将参数传递给函数时,C++将对值进行转换
3.4.3.1 赋值转换

C++ 允许将一种类型的值赋给另一种类型的变量。这样做时,值将被转换为接收变量的类型。例如,假设 so_long 的类型为 long, thirty 的类型为 short,而程序中包含这样的语句:

so_long = thirty

进行赋值时,程序将 thirty 的值(通常是 16 位)扩展为 long 值(通常为 32位)。扩展后将得到一个新值,这个值被存储在 so_long 中,而 thirty 的内容不变。将一个值赋给值取值范围更大的类型通常不会导致什么问题。例如,将 short 值赋给 long 变量并不会改变这个值,只是占用的字节更多而己。然而,将一个很大的 long 值(如 2111222333) 赋给 float 变量将降低精度。因为 float 只有 6 位有效数字,因此这个值将被四舍五入为 2.11122E9。因此,有些转换是安全的,有些则会带来麻烦。表 3.3 列出了一些可能出现的转换问题。


表 3.3
// 程序清单 3.13 assign.cpp
// init.cpp -- type changes on initialization

#include <iostream>
int main() {
        using namespace std;
        cout.setf(ios_base::fixed, ios_base::floatfield);
        float tree = 3;
        int guess(3.9832);
        int debt = 7.2E12;
        cout << "tree = " << tree << endl;
        cout << "guess = " << guess << endl;
        cout << "debt = " << debt << endl;
        return 0;
}

下面是该程序在我 MAC 系统中的输出

tree = 3.000000
guess = 3
debt = 4098
3.4.3.2 表达式中的转换

当同一个表达式中包含两种不同的算术类型时,将出现什么情况呢?在这种情况下,C++ 将执行两种自动转换;首先,一些类型在出现时便会自动转换;其次,有些类型在与其他类型同时出现在表达式中时将被转换。

先来看看自动转换。在计算表达式时,C++ 将 bool、char、unsigned char、signed char 和 short 值转换为 int。具体地说,true 被转换为 1,false 被转换为 0。这些转换被称为整型提升 (integral promotion)。例如,请看下面的语句:

short chickens = 20;
short ducks = 35;
short fow1 = chickens + cuaks;

为执行第 3 行语句,C++ 程序取得 chickens 和 ducks 的值,并将它们转换为 int。然后,程序将结果转换为 short 类型,因为结果将被赋给一个 short 变量。这种说法可能有点拗口,但是情况确实如此。通常将 int 类型选择为计算机最自然的类型,这意味着计算机使用这种类型时,运算速度可能最快还有其他一些整型提升; 如果 short 比 int 短,则 unsigned short 类型将被转换为 int; 如果两种类型的长度相同,则 unsigned short 类型将被转换为 unsigned int。 这种规则确保了在对 unsigned short 进行提升时不会损失数据。

将不同类型进行算术运算时,也会进行一些转换,例如将 int 和 foat 相加时。当运算涉及两种类型时,较小的类型将被转换为较大的类型。例如,程序清单 3.11 中的程序用 9.0 除以 5。由于 9.0 的类型为 double,因此程序在用 5 除之前,将 5 转换为 double 类型。总之,编译器通过校验表来确定在算术表达式中执行的转换。C++11 对这个校验表稍做了修改,下面是 C++11 版本的校验表,编译器将依次查阅该列表。

(1) 如果有一个操作数的类型是 long double,则将另一个操作数转换为 long double。
(2) 否则,如果有一个操作数的类型是double,则将另一个操作数转换为 double。
(3) 否则,如果有一个操作数的类型是float, 则将另一个操作数转换为 float。
(4) 否则,说明操作数都是整型,因此执行整型提升。
(5) 在这种情况下,如果两个操作数都是有符号或无符号的,且其中一个操作数的级别比另一个低则转换为级别高的类型,
(6) 如果一个操作数为有符号的,另一个操作数为无符号的,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型。
(7) 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号操作数转换为有符号操作数所属的类型。
(8) 否则,将两个操作数都转换为有符号类型的无符号版本。

3.4.3.3 传递参数时的转换

正如第 7 章将介绍的,传递参数时的类型转换通常由 C++ 函数原型控制。然而,也可以取消原型对参数传递的控制,尽管这样做并不明智。在这种情况下,C++ 将对 char 和 short 类型 (signed 和 unsigned) 应用整型提升。另外,为保持与传统 C 语言中大量代码的兼容性,在将参数传递给取消原型对参数传递控制的函数时,C++ 将 float 参数提升为 double。

3.4.3.4 传递参数时的转换

C++还允许通过强制类型转换机制显式地进行类型转换。(C++认识到,必须有类型规则,而有时又需要推翻这些规则。)强制类型转换的格式有两种。例如,为将存储在变量 thorn 中的 int 值转换为 long 类型,可以使用下述表达式中的一种:

(long) thorn;
long (thorn);

强制类型转换不会修改 thorn 变量本身,而是创建一个新的、指定类型的值。强制转换的通用格式如下:

(typeName) value
typeName (value)

第一种格式来自 C 语言,第二种格式是纯粹的 C++。新格式的想法是,要让强制类型转换就像是函数调用。这样对内置类型的强制类型转换就像是为用户定义的类设计的类型转换。

视频链接: pan.baidu.com/s/1n3eFpaj7z0Hf0j7TSxWjJA
视频密码: s21g

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容