说明

看《C++ Primer Plus》时整理的学习笔记,部分内容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,张海龙 袁国忠译,人民邮电出版社。只做学习记录用途。

目录

3.1 简单变量

内置的 C++ 类型分两组:基本类型复合类型,本章主要介绍基本类型

3.1.1 变量名

变量命名时,需遵循以下规则:

  • 名称只能使用字母数字下划线
  • 名称第一个字符不能是数字
  • 区分大写字母与小写字母。
  • 名称不能是 C++ 关键字
  • 两个下划线下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
  • C++ 对名称没有长度限制,但有些平台有长度限制。

倒数第二点原因:使用像 _time_stop_Donut 这样的名称不会导致编译错误,但会导致行为的不确定性,不知道结果将是什么。最后一点需注意:ANSI C (C99标准) 只保证名称中的前 63 个字符有意义(前 63 个字符相同的名称被认为是相同的,即使第 64 个字符不同)。目前比较流行的有 4 种命名习惯:

  1. 小驼峰命名法:第一个单词首字母小写,后面单词首字母大写,如 myVariableName
  2. 大驼峰命名法:又称帕斯卡命名法(Pascal),所有单词的首字母都大写,如 MyVariableName
  3. 匈牙利命名法:变量名 = 属性 + 类型 + 对象描述,如 m_lpctstrStudentName 表示类中一个成员变量(属性m_),类型为指向字符串常量的长指针(类型lpctstr),所指字符串常量用来表示学生姓名(对象描述StudentName)。
  4. 下划线命名法:用下划线分隔单词,如 my_variable_name

3.1.2 整型

计算机语言只能表示所有整数的一个子集,存储时使用的内存量越大,可表示的整数值范围也越大。C++ 的基本整型按内存量宽度排序有 charshortintlong和 C++11 新增的long long,其中每种类型都有符号版本无符号版本,因此总共有 10 种类型可供选择(实际上shortshort int的简称,longlong int的简称,long longlong 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关键字,这将在后续章节学习。

3.1.4 无符号整型

前面介绍的 4 种符号整型都有对应的无符号版本,仅当数值不会为负时才使用无符号版本:unsigned shortunsigned intunsigned longunsigned 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

对于不带后缀的十进制整数,使用下面几种类型中能够存储该数的最小类型做默认值:intlonglong long

对于不带后缀的十六进制和八进制整数,使用下面几种类型中能够存储该数的最小类型做默认值:intunsigned intlongunsigned longlong longunsigned 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_tchar32_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++constANSI Cconst 有两点主要区别: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 种浮点类型:floatdoublelong 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 位。

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++ 将自动执行类型转换:

  • 将一种算术类型的值赋给另一种算术类型的变量时。
  • 表达式中包含不同算术类型时。
  • 将参数传递给函数时。

自动类型转换时的规则如下:

  1. 赋值以及普通初始化:将一种算术类型的值赋给另一种算术类型的变量时,值将被转换为接收变量的类型。有些转换是安全的,但转换时也可能出现以下问题:

    转换 潜在问题
    大浮点数转小浮点数(如doublefloat 精度(有效位数)降低,超过取值范围的结果是不确定的
    浮点数转整型(如doubleint 小数部分丢失(向零取整),超过取值范围的结果是不确定的
    大整型转小整型(如longshort 超过取值范围的结果会出问题,此时只复制右边的字节
  2. 列表初始化:C++11 将使用大括号\(\{\}\)的初始化称为列表初始化,这种初始化方式对类型转换的要求更严格,它不允许缩窄转换,上面表格中浮点数转整型在这是不被允许的,当大浮点数转小浮点数、大整型转小整型时,若超过了目标类型的取值范围,也是不被允许的。

  3. 表达式中的转换:编译器通过校验表以及整型级别来确定算术表达式中的类型转换。有符号整型按级别从高到低依次为long longlongintshortsigned char,无符号整型的排列顺序与有符号整型相同,类型charsigned charunsigned char的级别相同,类型bool的级别最低,wchar_tchar16_tchar32_t的级别与其底层类型相同。校验表的规则较复杂,这里只举一个简单的例子:两个short整型进行算术运算时将先做整型提升至int,然后再运算,最后再转回short

  4. 传递参数时的转换:传递参数时的类型转换通常由 C++ 原型控制,也可取消原型对参数传递的控制,这将在第 7 章介绍。

  5. 强制类型转换:C++ 允许通过强制类型转换机制显式地进行类型转换。强制转换的格式有两种:

    //C语言风格
    (typeName) value;
    
    //纯粹的C++风格
    typeName (value);
    

    强制类型转换不会修改变量本身,而是创建一个新的、指定类型的值,可以在表达式中使用这个值。C++ 还引入了 4 个强制类型转换运算符,这将在第 15 章介绍,其中static_cast<>可用于将数值从一种数字类型转换为另一种数值类型:

    //使用运算符强制转换为typeName类型
    static_cast<typeName> (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;