第二章 变量和基本类型
2.1 基本内置类型
2.1.1 算术类型
-
C++算术类型
类型 含义 最小尺寸 bool 布尔类型 未定义 char 字符 8位 wchar_t 宽字符 16位 char16_t Unicode字符 16位 char32_t Unicode字符 32位 short 短整型 16位 int 整型 16位 long 长整型 32位 long long 长整型 64位 float 单精度浮点数 6位有效数字 double 双精度浮点数 10位有效数字 long double 扩展精度浮点数 10位有效数字 -
一个
char
的大小和一个机器字节一样 -
字节(byte):可寻址的最小内存块
-
字(word):存储的基本单元,通常由几个字节组成
-
计算机以比特序列存储数据,每个比特非0即1,大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节
-
大多数计算机将内存中的每个字节与一个数字(被称为地址(address))关联起来
-
带符号类型和无符号类型
- 除去布尔型和扩展的字符型之外,其他整型可以划分为带符号的(signed)和无符号的(unsigned)
int
、short
、long
和long long
都是带符号的,再其之前添加unsigned
就可以得到无符号类型- 字符型分为三种:
char
、signed char
和unsigned char
,但是字符的表现形式只有两种:带符号的和无符号的,类型char
会表现为上述两种形式的一种
-
如何选择数据类型:
- 当明确知晓数值不可能为负时,选用无符号类型
- 使用
int
执行整数运算,在实际应用中,short
显得太小而long
一般和int
有一样的尺寸,如果数值超过了int
的表示范围,选用long long
- 算术表达式中不要使用
char
或者bool
。char
是否有符号依据机器,使用一个不大的整数,那么明确指出类型为signed char
或者unsigned char
- 执行浮点数运算选用
double
2.1.2 类型转换
- 对象的类型定义了对象能包含的数据和能参与的运算
- 类型所能表示的值的范围决定了类型转换(convert)的过程:
- 非布尔类型的算术值赋给布尔类型:0的结果为false,否则为true
- 布尔值赋给非布尔类型:false→0,true→1
- 浮点数赋给整数类型:仅保留浮点数中小数点前的部分
- 整数类型赋给浮点类型:小数部分记为0
- 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数
- 赋给带符号类型一个超过它表示范围的值时,结果是未定义的(undefined)
- 在表达式中切勿混用带符号类型和无符号类型
2.1.3 字面值常量
- 字面值常量(literal)的形式和值决定了它的数据类型
- 整型和浮点型字面值
- 整型字面值可以写作十进制数、八进制数(0开头)、十六进制数(0x或0X开头)
- 默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的
- 十进制字面值的类型是能容纳其数值的
int
、long
、long long
中尺寸最小的那个 - 八进制和十六机制字面值的类型是能容纳其数值的
int
、unsigned int
、long
、unsigned long
、long long
和unsigned long long
中的尺寸最小者 - 默认浮点型字面值是
double
- 字符和字符串字面值
- 单引号括起来的一个字符称为
char型字面值
,双引号括起来的零个或多个字符则构成字符串型字面值
- 字符串字面值的类型实际上是由常量字符构成的数组(array),编译器在每个字符串的结尾处添加一个空字符(’\0’),因此字符串字面值的实际长度要比它的内容多1。
- 单引号括起来的一个字符称为
- 转义序列
-
两类程序员不能直接使用的字符,需要用到转义序列(escape sequence)
- 不可打印(nonprintable)字符:退格或其他控制字符,因为其没有可视的图符
- 有特殊含义的字符:单引号、双引号、问号、反斜线
-
C++规定的转义序列:
换行符 \n 横向制表符 \t 报警(响铃)符 \a 纵向制表符 \v 退格符 \b 双引号 \'' 反斜线 \\ 问号 \? 单引号 \' 回车符 \r 进纸符 \f -
泛化的转义序列
\\x
后紧跟1个或多个十六进制数字\\
后紧跟1个、2个或3个八进制数字
-
- 指定字面值的类型:添加前缀或者后缀,改变整型、浮点型和字符型字面值的默认类型
-
字符和字符串字面值
前缀 含义 类型 u Unicode 16 字符 char16_t U Unicode 32 字符 char32_t L 宽字符 wchar_t u8 UTF-8 char -
整型字面值
后缀 最小匹配类型 u or U unsigned l or L long ll or LL long long -
浮点型字面值
后缀 类型 f or F float l or L long double
-
- 布尔字面值和指针字面值
true
和false
是布尔类型的字面值nullptr
是指针字面值
2.2 变量
- 变量提供一个具名的、可供程序操作的存储空间,对象是指一块能存储数据并具有某种类型的内存空间。在C++中,变量(variable)和对象(object)一般可以互换使用
2.2.1 变量定义
- 基本形式:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束
- 初始值
- 在用一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量
- 在C++语言中,初始化和赋值是两个完全不同的操作,初始化的含义是在创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新的值替代
- 列表初始化(list initialization)
- 默认初始化(default initialized)
- 如果定义变量时没有指定初值,则变量被默认初始化
- 定义于任何函数体之外的变量被初始化为0
- 定义在函数体内部的内置类型变量将不被初始化,其值未定义
- 类的对象如果没有被显式地初始化,则其值由类确定
2.2.2 变量声明和定义的关系
- C++支持分离式编译(separate compilation)机制,允许将程序分割为若干个文件,每个文件可被独立编译
- C++将声明和定义区分开,声明(declaration)使得名字为程序所知,定义(definition)负责创建与名字关联的实体
- 声明和定义都规定了变量的类型和名字,但定义还申请了存储空间,也可能为变量赋一个初始值
- 声明变量而非定义,则在变量名前添加关键字
extern
,而且不要显式地初始化变量,任何包含显式初始化的声明即成为定义
2.2.3 标识符
-
标识符(identifier)由字母、数字和下划线组成
- 必须以字母或下划线开头
- 长度没有限制
- 对大小写敏感
- 自定义标识符不能连续出现两个下划线
- 自定义标识符不能以下划线紧连大写字母开头
- 定义在函数体外的标识符不能以下划线开头
-
C++关键字
alignas alignof asm auto bool break case catch char char16_t char32_t class const constexpr const_cast continue decltype default delete do double dynamic_cast else enum explicit export extern false float for friend goto if inline int long mutable namespace new noexpect nullptr operator private protected public register reinterpret_cast return short signed sizeof static static_assert static_cast struct switch template this thread_local throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while -
C++操作符替代名
and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq
2.2.4 名字的作用域
- 全局作用域(global scope)与块作用域(block scope)
- 作用域能彼此包含:内层作用域(inner scope)和外层作用域(outer scope)
2.3 复合类型
- 复合类型(compound type)指基于其他类型定义的类型
- 一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成
2.3.1 引用
- 引用(reference)为对象起了另外一个名字,引用类型引用另外一种类型
- 通过将声明符写成
&d
的形式来定义引用类型,其中d
为变量名 - 引用是绑定(bind),不是拷贝,引用并非对象,只是为已存在对象起了一个别名
- 引用的定义
- 允许在一条语句中定义多个引用,其中每个引用标识符都必须以
&
开头 - 除了两种例外情况,所有引用的类型都要和与之绑定的对象严格匹配
- 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起
- 允许在一条语句中定义多个引用,其中每个引用标识符都必须以
2.3.2 指针
- 指针本身就是一个对象,无需再定义时赋初值
- 定义指针类型的方法:将声明符写成
*d
的形式,其中d
为变量名,允许在一个语句中定义多个指针变量,每个变量前面必须有符号*
- 指针存放某个对象的地址,想要获取该地址,使用取地址符
&
- 除了两种例外情况,所有指针的类型都要和它所指向的对象严格匹配
- 指针值应属于下列4种状态之一
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,上述情况以外的其他值
- 通过指针访问对象
- 使用解引用符
*
来访问该对象 - 解引用操作仅仅适用于确实指向了某个对象的有效指针
- 使用解引用符
- 空指针
-
空指针(null pointer)不指向任何对象
-
空指针的创建方式:
int *p1 = nullptr; //最佳选择 int *p2 = 0; // 需要首先#include cstdlib int *p3 = NULL;
-
建议初始化所有的指针
-
- void 指针*
-
一种特殊的指针类型,可以存放任意对象的地址
int i; void *p = i;
-
2.3.3 理解复合类型的声明
-
指向指针的指针
-
通过
*
的个数区分指针的级别int ival = 1024; int *pi = &ival; int **pii = π
-
-
指向指针的引用:引用不是对象,指针是对象,故只存在指向指针的引用,不存在指向引用的指针
int i = 42; int *p; int *&r = p; //r是一个对指针p的引用 r = &i; *r = 0;
2.4 const限定符
- const对象一旦创建之后其值不能改变,编译器会在编译时将所有const对象替换为对应值,所以const对象必须初始化
- 只能在const类型的对象上执行不改变其内容的操作
- 默认情况下,const对象被设定为仅在文件内有效
- 想在多个文件之间共享const对象,必须在变量的定义和声明之前添加
extern
2.4.1 const的引用
-
对常量的引用(reference to const):把引用绑定在const对象上
const int ci = 1024; const int &r1 = ci;
-
引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系
-
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可
-
对常量的引用可能引用一个并非const的对象
int i = 42; int &r1 = i; const int &r2 = i; r1 = 0; //正确:r1为非常量,i的值修改为0 r2 = 0; //错误:r2为常量引用,不能通过其改变i的值
2.4.2 指针和const
-
指向常量的指针(pointer to const)
- 不能用于改变其所指对象的值
- 想要存放常量对象的地址,只能使用指向常量的指针
-
允许一个指向常量的指针指向一个非常量对象
double dval = 3.14; const double *cptr = &dval
-
常量指针(const pointer)
-
指针为对象,允许把指针本身定为常量
-
必须初始化,定义完成则它的值(即存放的地址)就不能再改变了
int errNumb = 0; int *const curErr = &errNumb; //curErr一直指向errNumb const double pi = 3.14159; const double *const pip = π //pip是一个指向常量对象的常量指针
-
指针本身为一个常量不意味着不能通过指针修改其所指对象的值
-
2.4.3 顶层const
- 指针本身是不是常量以及指针所指的是不是一个常量是两个独立的问题
- 顶层const(top-level const):指针本身是个常量
- 底层const(low-level const):指针所指的对象为一个常量
- 更一般的:
- 顶层const可以表示任意的对象是常量,对任何数据类型都适用
- 底层const与指针和引用等复合类型的基本类型部分有关
- 指针类型可以是顶层const也可以是底层const
- 当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显
- 顶层const不受什么影响
- 拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换
2.4.4 constexpr和常量表达式
- 常量表达式(const expression):值不会改变并且在编译过程就能得到计算结果的表达式
- constexpr变量:允许将变量声明为
constexpr
类型以便由编译器来验证变量的值是否是一个常量表达式 - 声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化
2.5 处理类型
2.5.1 类型别名
-
类型别名(type alias)是一个名字,是某种类型的同义词,类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名
-
使用关键字typedef
typedef double wages; //wages是double的同义词 typedef wages base, *p;//base是double的同义词,p是double*的同义词
-
使用别名声明(alias declaration)
using SI = Sales_item;
2.5.2 auto类型说明符
- auto类型说明符
- auto让编译器通过初始值来推算变量的类型
- auto定义的变量必须有初始值
2.5.3 decltype类型指示符
- decltype类型说明符:选择并返回操作数的数据类型,编译时只分析类型,不实际计算表达式的值
- 如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)
- decltype和引用
- 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型
- 如果表达式的内容是解引用操作,则decltype将得到引用类型
decltype((variable))
的结果永远是引用,而decltype(variable)
的结果只有当variable本身就是一个引用时才是引用
2.6 自定义数据结构
- 从最基本的层面理解,数据结构是把一组相关的数据元素组织起来然后使用它们的策略和方法
- C++允许用户以类的形式自定义数据类型
Struct
- 尽量不要把类定义和对象定义放在一起
- 类可以以关键字struct开始,紧跟类名和类体
- 类数据成员:类体定义类的成员
- C++11:可以为类数据成员提供一个类内初始值(in-class initializer)
编写自己的头文件
- 头文件通常包含那些只能被定义一次的实体:类、const和constexpr变量
- 预处理器(preprocessor):确保头文件多次包含仍能安全工作
- 当预处理器看到#include标记时,会用指定的头文件内容代替#include
- 头文件保护符(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义
- #ifdef已定义时为真
- #ifndef未定义时为真
- 头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。