第 3 章 处理数据
说明
看《C++ Primer Plus》时整理的学习笔记,部分内容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,张海龙 袁国忠译,人民邮电出版社。只做学习记录用途。
目录- 说明
- 3.1 简单变量
- 3.1.1 变量名
- 3.1.2 整型
- 3.1.3 符号整型
- 3.1.4 无符号整型
- 3.1.5 选择整型类型
- 3.1.6 整型字面值
- 3.1.7 C++ 如何确定常量的类型
- 3.1.8 char 类型:字符和小整数
- 3.1.9 bool 类型
- 3.2 const 限定符
- 3.3 浮点数
- 3.3.1 书写浮点数
- 3.3.2 浮点类型
- 3.3.3 浮点常量
- 3.3.4 浮点数的优缺点
- 3.4 C++ 算术运算符
- 3.4.1 运算符优先级和结合性
- 3.4.2 除法分支
- 3.4.3 求模运算符
- 3.4.4 类型转换
- 3.4.5 C++11 中的 auto 声明
3.1 简单变量
内置的 C++ 类型分两组:基本类型和复合类型,本章主要介绍基本类型。
3.1.1 变量名
变量命名时,需遵循以下规则:
- 名称只能使用字母、数字或下划线。
- 名称第一个字符不能是数字。
- 区分大写字母与小写字母。
- 名称不能是 C++ 关键字。
- 以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
- C++ 对名称没有长度限制,但有些平台有长度限制。
倒数第二点原因:使用像 _time_stop 或 _Donut 这样的名称不会导致编译错误,但会导致行为的不确定性,不知道结果将是什么。最后一点需注意:ANSI C (C99标准) 只保证名称中的前 63 个字符有意义(前 63 个字符相同的名称被认为是相同的,即使第 64 个字符不同)。目前比较流行的有 4 种命名习惯:
- 小驼峰命名法:第一个单词首字母小写,后面单词首字母大写,如
myVariableName。 - 大驼峰命名法:又称帕斯卡命名法(Pascal),所有单词的首字母都大写,如
MyVariableName。 - 匈牙利命名法:变量名 = 属性 + 类型 + 对象描述,如
m_lpctstrStudentName表示类中一个成员变量(属性m_),类型为指向字符串常量的长指针(类型lpctstr),所指字符串常量用来表示学生姓名(对象描述StudentName)。 - 下划线命名法:用下划线分隔单词,如
my_variable_name。
3.1.2 整型
计算机语言只能表示所有整数的一个子集,存储时使用的内存量越大,可表示的整数值范围也越大。C++ 的基本整型按内存量宽度排序有 char、short、int、long和 C++11 新增的long long,其中每种类型都有符号版本和无符号版本,因此总共有 10 种类型可供选择(实际上short是short int的简称,long是long int的简称,long long是long long int的简称,但一般不使用长名称)。最好在声明变量时就对它的值进行初始化,即将声明语句与赋值语句合并在一起,以防出现未初始化就使用变量的情况,这时变量的值是不确定的。C++ 变量初始化有以下几种方式:
//传统C语言方式
int uncles = 5;
//C++支持的方式
int uncles(5);
//C++98支持的方式
int uncles = {5};
//C++11支持的方式
int uncles = {5};
int uncles{5};
//C++11将变量初始化为0
int uncles = {};
int uncles{};
3.1.3 符号整型
不是在所有的系统中,每种类型的宽度都相同,例如int不总是 32 位,在早期的 16 位操作系统中,int是 16 位,但在后来的 32 位操作系统以及 64 位操作系统中,int是 32 位。C++ 标准只确保了最小宽度:
short至少 16 位。int至少和short一样宽。long至少 32 位,且至少和int一样宽。long long至少 64 位,且至少和long一样宽。
想知道某种整型的内存量大小,可以使用sizeof运算符返回类型或变量的内存量宽度,单位为字节(字节的含义也依赖于实现,在一个系统中一个字节可能是 8 位,而在另一个系统中可能是 16 位),对类型名(如int)使用sizeof运算符时,应将名称放在括号里面,但对变量名(如n_short)使用该运算符,括号是可选的。
//对类型名使用sizeof运算符(必要括号)
cout << "int is " << sizeof(int) << " bytes.\n";
//对变量名使用sizeof运算符(括号可选)
cout << "short is " << sizeof(n_short) << " bytes.\n";
cout << "short is " << sizeof n_short << " bytes.\n";
想知道某种整型所能表示的整数范围,一种方式是根据该整型的内存量宽度进行计算,例如:若short为 16 位,则其符号版本可表示的整数范围为 \([-2^{15}, 2^{15}-1]\) 即 \([-32768, 32767]\),无符号版本可表示的整数范围为 \([0, 2^{16}-1]\) 即 \([0, 65535]\),这样计算的原因可参考原码补码反码的相关资料。另一种方式是#include ,这个库文件里面包含了关于整型限制的信息,使用示例如下:
//获得int所能表示的最小整数值
cout << "Minimum int value = " << INT_MIN << endl;
//获得当前系统一个字节占多少位
cout << "Bits per byte = " << CHAR_BIT << endl;
climits文件中将上述限制信息定义为宏:
#define INT_MIN -2147483648
编译指令#define的工作方式与文本编辑器的全局搜索并替换的命令相似,它告诉预处理器:查找独立的标记INT_MIN并将其替换为-2147483648,但它会跳过嵌入的INT_MIN,不会将PINT_MINTM替换为-2147483648。C++ 有一种更好的创建符号常量的方法,就是使用const关键字,这将在后续章节学习。
3.1.4 无符号整型
前面介绍的 4 种符号整型都有对应的无符号版本,仅当数值不会为负时才使用无符号版本:unsigned short、unsigned int、unsigned long和 unsigned long long。整型变量的取值如果超过了取值限制,其值将成为另一端点的值。依然以 16 位short为例,其符号版本的最大值加 1 后,值会变成 -32768,最小值减 1 后,值会变成 32767,其无符号版本的最大值加 1 后,值会变成 0,最小值减 1 后,值会变成 65535,这种现象分别称为整型的上溢与下溢。
3.1.5 选择整型类型
int被设置为计算机处理起来效率最高的长度,若没有非常有说服力的理由选择其他类型,则应首选int。若变量取值不可能为负数,则应首选无符号版本。若变量取值可能超过 16 位整数的最大值,则应首选long类型,这样可确保程序移植到 16 位系统时不会出现问题。若变量取值超过 20 亿(\(2^{31}=2147483648\)),则可选择long long。若存在大型数组且节省内存很重要,可考虑使用short。
3.1.6 整型字面值
整型字面值是显式地书写的常量,C++ 能够以三种不同的计数方式来书写整数:基数为 10、基数为 8(老式UNIX版本)、基数为 16(硬件黑客的最爱)。C++ 使用前一(两)位来标识数字常量的基数。如果第一位为 1~9,则基数为10(十进制),例如 93。如果第一位为 0,第二位为 1~7,则基数为8(八进制),例如 042(等于十进制数 34)。如果前两位为 0x 或者 0X,则基数为16(十六进制),例如 0x42(等于十进制数 66)。
//十进制
int chest = 42;
//八进制
int waist = 042;
//十六进制
int inseam = 0x42;
3.1.7 C++ 如何确定常量的类型
数字常量后面可以添加后缀以指明类型:
| 后缀 | 常量类型 |
|---|---|
| l , L | long |
| u , U | unsigned int |
| UL , LU , ul , lu , Ul , Lu , uL , lU | unsigned long |
| LL , ll | long long |
| ULL , ull , Ull , uLL | unsigned long long |
对于不带后缀的十进制整数,使用下面几种类型中能够存储该数的最小类型做默认值:int 、long 、long long。
对于不带后缀的十六进制和八进制整数,使用下面几种类型中能够存储该数的最小类型做默认值:int 、unsigned int、long、unsigned long、long long、unsigned long long。
3.1.8 char 类型:字符和小整数
char 类型是专为存储字符(如字母和数字)而设计的,通常为 8 位,也用来表示比 short 更小的整型。常用的符号集有 ASCII字符集 、国际Unicode字符集,例如字符 A 的 ASCII 码是 65,字符 a 的 ASCII 码是 97。另外还有一种特殊的字符:转义字符,常用的转义字符有:
| 字符名称 | ASCII符号 | C++代码 | 十进制ASCII码 | 十六进制ASCII码 |
|---|---|---|---|---|
| 换行符 | NL(LF) | \n |
10 | 0xA |
| 水平制表符 | HT | \t |
9 | 0x9 |
| 垂直制表符 | VT | \v |
11 | 0xB |
| 回车 | CR | \r |
13 | 0xD |
| 反斜杠 | \ | \\ |
92 | 0x5C |
| 单引号 | ' | \' |
39 | 0x27 |
| 双引号 | " | \" |
34 | 0x22 |
可以使用数字转义序列(只支持八进制、十六进制)或符号转义序列来表示转义字符,但数字转义序列与特定的编码方式(如ASCII)相关,而符号转义序列适用于任何编码方式,可读性也更强,比如\n 、\012 与\0xA 都表示换行符。C++ 标准还允许实现提供扩展源字符集和扩展执行字符集,这种机制独立于任何特定的键盘,使用的是通用字符名,通用字符名以 \u 或者 \U 打头,\u 后接 8 个十六进制位,\U 后则是 16 个十六进制位,这些位表示的是字符的 ISO 10646 码点。
将 char 用于表示整型时,其默认情况下既不是没有符号,也不是有符号,这由具体的 C++ 实现来决定。可以显式地对其进行设置:
//默认可能是无符号,也可能是有符号
char fodo;
//显式声明为无符号
unsigned char bar;
//显式声明为有符号
signed char snark;
宽字符类型 wchar_t 可以表示扩展字符集,它的内存量宽度由 C++ 实现来决定,可能是 16 位,也可能是 32 位,可能是有符号的,也可能是无符号的,其底层类型可能是 int 也可能是 unsigned short ,前缀 L 可以用来指明宽字符常量与宽字符串:
//右边为宽字符常量
wchar_t bob = L'P';
//输出宽字符串
std::wcout << L"tall" << endl;
C++11 新增了类型 char16_t 和 char32_t ,这两者都是无符号的,分别长 16 位、32 位,其底层类型也是一种内置整型,具体底层类型随系统而变化。前缀 u 用来指明 char16_t 字符常量和字符串常量,前缀 U 用来指明 char132_t 字符常量和字符串常量,可使用通用字符名来对它们进行初始化。
3.1.9 bool 类型
ANSI/ISO C++ 标准添加了一种名叫 bool 的新类型,它只有两种取值 true 或者 false,常用于判断语句,内存量宽度一般为 8 位。过去,C++ 和 C 一样,是没有布尔类型的。布尔类型支持隐式转换,任何数字值或指针值都可以转换为 bool 值,转换时任何非零值都被转为 true ,零值转换为 false ,布尔值也可以隐式提升为 int 类型,true被转换为 1,false被转换为 0。
//布尔值隐式提升为1
int ans = true;
//布尔值隐式提升为0
int ans = false;
//非零被转换为true
bool ans = -100;
//零值被转换为false
bool ans = 0;
3.2 const 限定符
创建常量的通用格式如下:
const type name = value;
应该在声明常量时就对其进行初始化,如果在声明常量时没有初始化,则该常量的值是不确定的,且无法修改。常量被初始化后,其值就被固定了,编译器将不允许再修改该常量的值。const 常量相比于 #define 常量的好处有三点:明确指定类型、作用域限制、可用于复杂类型。
C++ 的 const 与 ANSI C 的 const 有两点主要区别:C++ 版本有作用域限制、可在C++ 中用来声明数组长度。
3.3 浮点数
浮点类型是 C++ 的第二组基本类型,它能够表示带小数部分的数字。
3.3.1 书写浮点数
C++ 有两种方式书写浮点数,一种是常用的标准小数点表示法,另一种是 E 表示法。
//标准小数点表示法
12.34;
//E表示法(中间不能有空格)
2.52e+8;
2.52e8;
2.52E+8;
2.52E8;
2.52e-8;
2.52E-8;
3.3.2 浮点类型
和 ANSI C 一样,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 位。可以通过 #include 获取各类型浮点数所能表示的数值范围,各浮点类型所能表示的精度(有效位数)一般为:float至少 6 位,double至少 15 位,long double至少 18 位。
3.3.3 浮点常量
浮点常量后面可以添加后缀以指明类型(部分老式编译器可能不支持浮点常量后缀):
| 后缀 | 常量类型 |
|---|---|
| f , F | float |
| l , L | long double |
对于不带后缀的浮点常量,默认类型为 double。
3.3.4 浮点数的优缺点
与整数相比,浮点数有两大优点:可表示整数之间的值、可表示的数值范围更大。
与整数相比,浮点数也有两大缺点:运算更慢、精度将降低。
3.4 C++ 算术运算符
C++ 提供了 5 种运算符来完成基本算术计算:
- 加法运算符 + 执行加法运算,例如 \(4+20=24\)。
- 减法运算符 - 执行减法运算,例如 \(12-3=9\)。
- 乘法运算符 * 执行乘法运算,例如 \(12*4=112\)。
- 除法运算符 / 执行除法运算,例如 \(1000/5=200\)。
- 求模运算符 % 执行取余运算,两个操作数必须都为整数,它生成第一个整数除以第二个整数后的余数,计算结果满足等式 \(a\%b=a-(a/b)*b\)。
3.4.1 运算符优先级和结合性
当多个运算符可用于同一操作数时,C++ 使用优先级规则来决定首先使用哪个运算符,也可用括号来执行自己定义的优先级。乘法、除法和求模运算符的优先级相同,加法和减法运算符的优先级相同,但比其他三个要低。当两个运算符的优先级相同且作用于同一个操作数时,C++ 使用结合性规则来决定首先使用哪个运算符,上述 5 种运算符的结合性规则都是从左到右。
3.4.2 除法分支
除法运算符 / 的行为取决于操作数的类型。如果两个操作数都是整数,则 C++ 将执行整数除法,结果的小数部分将被丢弃,即向零取整。若其中有一个(或两个)操作数是浮点值,则结果的小数部分将保留,结果为浮点数。
3.4.3 求模运算符
求模运算符 % 返回整数除法的余数,尤其适用于解决要求将一个量分成不同的整数单元的问题,例如单位转换。
3.4.4 类型转换
整型和浮点型统称为算术类型,在以下情况下 C++ 将自动执行类型转换:
- 将一种算术类型的值赋给另一种算术类型的变量时。
- 表达式中包含不同算术类型时。
- 将参数传递给函数时。
自动类型转换时的规则如下:
-
赋值以及普通初始化:将一种算术类型的值赋给另一种算术类型的变量时,值将被转换为接收变量的类型。有些转换是安全的,但转换时也可能出现以下问题:
转换 潜在问题 大浮点数转小浮点数(如 double转float)精度(有效位数)降低,超过取值范围的结果是不确定的 浮点数转整型(如 double转int)小数部分丢失(向零取整),超过取值范围的结果是不确定的 大整型转小整型(如 long转short)超过取值范围的结果会出问题,此时只复制右边的字节 -
列表初始化:C++11 将使用大括号\(\{\}\)的初始化称为列表初始化,这种初始化方式对类型转换的要求更严格,它不允许缩窄转换,上面表格中浮点数转整型在这是不被允许的,当大浮点数转小浮点数、大整型转小整型时,若超过了目标类型的取值范围,也是不被允许的。
-
表达式中的转换:编译器通过校验表以及整型级别来确定算术表达式中的类型转换。有符号整型按级别从高到低依次为
long long、long、int、short和signed char,无符号整型的排列顺序与有符号整型相同,类型char、signed char和unsigned char的级别相同,类型bool的级别最低,wchar_t、char16_t和char32_t的级别与其底层类型相同。校验表的规则较复杂,这里只举一个简单的例子:两个short整型进行算术运算时将先做整型提升至int,然后再运算,最后再转回short。 -
传递参数时的转换:传递参数时的类型转换通常由 C++ 原型控制,也可取消原型对参数传递的控制,这将在第 7 章介绍。
-
强制类型转换:C++ 允许通过强制类型转换机制显式地进行类型转换。强制转换的格式有两种:
//C语言风格 (typeName) value; //纯粹的C++风格 typeName (value);强制类型转换不会修改变量本身,而是创建一个新的、指定类型的值,可以在表达式中使用这个值。C++ 还引入了 4 个强制类型转换运算符,这将在第 15 章介绍,其中
static_cast<>可用于将数值从一种数字类型转换为另一种数值类型://使用运算符强制转换为typeName类型 static_cast(value);
3.4.5 C++11 中的 auto 声明
C++ 重新定义了 C 语言中的 auto 关键字,在初始化声明中,如果使用关键字auto ,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同(自动类型推断):
//自动推断为int类型
auto n = 100;
//自动推断为double类型
auto x = 1.5;
//自动推断为long double类型
auto y = 1.3e12L;