看《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 声明
内置的 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
。
计算机语言只能表示所有整数的一个子集,存储时使用的内存量越大,可表示的整数值范围也越大。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 <climits>
,这个库文件里面包含了关于整型限制的信息,使用示例如下:
//获得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
关键字,这将在后续章节学习。
前面介绍的 4 种符号整型都有对应的无符号版本,仅当数值不会为负时才使用无符号版本:unsigned short
、unsigned int
、unsigned long
和 unsigned long long
。整型变量的取值如果超过了取值限制,其值将成为另一端点的值。依然以 16 位short
为例,其符号版本的最大值加 1 后,值会变成 -32768,最小值减 1 后,值会变成 32767,其无符号版本的最大值加 1 后,值会变成 0,最小值减 1 后,值会变成 65535,这种现象分别称为整型的上溢与下溢。
int
被设置为计算机处理起来效率最高的长度,若没有非常有说服力的理由选择其他类型,则应首选int
。若变量取值不可能为负数,则应首选无符号版本。若变量取值可能超过 16 位整数的最大值,则应首选long
类型,这样可确保程序移植到 16 位系统时不会出现问题。若变量取值超过 20 亿(\(2^{31}=2147483648\)),则可选择long long
。若存在大型数组且节省内存很重要,可考虑使用short
。
整型字面值是显式地书写的常量,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++ 如何确定常量的类型
数字常量后面可以添加后缀以指明类型:
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
。
char
类型是专为存储字符(如字母和数字)而设计的,通常为 8 位,也用来表示比 short
更小的整型。常用的符号集有 ASCII字符集 、国际Unicode字符集,例如字符 A 的 ASCII 码是 65,字符 a 的 ASCII 码是 97。另外还有一种特殊的字符:转义字符,常用的转义字符有:
\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
字符常量和字符串常量,可使用通用字符名来对它们进行初始化。
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++ 中用来声明数组长度。
浮点类型是 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 <cfloat>
获取各类型浮点数所能表示的数值范围,各浮点类型所能表示的精度(有效位数)一般为:float
至少 6 位,double
至少 15 位,long double
至少 18 位。
浮点常量后面可以添加后缀以指明类型(部分老式编译器可能不支持浮点常量后缀):
float
l , L
long double
对于不带后缀的浮点常量,默认类型为 double
。
与整数相比,浮点数有两大优点:可表示整数之间的值、可表示的数值范围更大。
与整数相比,浮点数也有两大缺点:运算更慢、精度将降低。
3.4 C++ 算术运算符C++ 提供了 5 种运算符来完成基本算术计算:
- 加法运算符 + 执行加法运算,例如 \(4+20=24\)。
- 减法运算符 - 执行减法运算,例如 \(12-3=9\)。
- 乘法运算符 * 执行乘法运算,例如 \(12*4=112\)。
- 除法运算符 / 执行除法运算,例如 \(1000/5=200\)。
- 求模运算符 % 执行取余运算,两个操作数必须都为整数,它生成第一个整数除以第二个整数后的余数,计算结果满足等式 \(a\%b=a-(a/b)*b\)。
当多个运算符可用于同一操作数时,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<typeName> (value);
C++ 重新定义了 C 语言中的 auto 关键字,在初始化声明中,如果使用关键字auto
,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同(自动类型推断):
//自动推断为int类型
auto n = 100;
//自动推断为double类型
auto x = 1.5;
//自动推断为long double类型
auto y = 1.3e12L;