C++ Primer 第五版 阅读过程查漏补缺 chapter 2
变量的定义和声明
声明: 使得名字为程序所知,
定义:负责创建与名字相关的实体。
如果想要声明一个变量而非定义它,就需要在变量名前加上关键字__extern__。
eg. extern int i; //声明i而非定义
int j; //声明并且定义j
然而,如果给extern标记的变量赋予了初始值,则进行了定义。
变量只能被定义一次,但是可以被多次声明
复合类型
引用和指针都是c++的复合类型。他们的相似和区别如下:
相同 | 不同 | |
---|---|---|
引用 | 实现了对某个对象的间接访问 一般情况引用类型需要和绑定的对象严格匹配 | 1. 引用在定义时就要进行和对象的绑定,一经绑定无法修改 2. 引用本身不是一个对象,仅仅是一个别名,没有实际地址 3. 引用只能绑定在某个对象上,不能绑定在字面值或者计算结果 |
指针 | 实现了对某个对象的间接访问 一般情况引用类型需要和绑定的对象严格匹配 | 1. 指针本身就是一个对象,允许对指针赋值访问 2. 指针无需在定义时赋初值,而且在其生命周期,可以多次指向不同对象 3. 指针存放的是对象地址,所以赋值时要用取址符&获取对象地址 4. 使用指针时,需要使用解引用符*来访问对象 |
注: 可以有指向指针的引用,不能有指向引用的指针
int i = 42;
int *p;
int *&r = p; // r是一个指向指针p的引用
r = &i; // 给指针p赋值
*r = 0; // 得到p指向的对象并赋值
空指针:nullptr (c++11新标准引入) 等同于初始化为字面值0。
void* 指针: 一种特殊的指针类型,可以用于存放任意对象的地址。我们可以用void*指针进行指针比较、作函数输入或输出,或者赋值给别的void*指针,但是我们不能直接操作它所指向的对象。
const类型
const对象一般情况下仅在文件内有效。当多个文件中出现多个const,则说明是在多个文件中分别定义了不同的对象。
如果想要在多个文件中声明或使用某个const对象,那就需要在定义和声明时都加上extern关键字。
const的引用
前面提到引用必须严格和绑定的对象进行匹配,int配int,double配double,但是在const中,针对const的引用是允许用任意表达式作为初始值的,只要其结果可以转换为引用的类型就可以。
eg.
double dval = 3.14; //
const int &ri = dval; // 此时ri为3
当然此时对ri是无法进行赋值操作的,而且此时 编译器中实质发生的是这样的过程:
double dval = 3.14;
const int temp = dval; // temp是一个临时量对象
const int &ri = temp; // ri实际引用的是一个临时量对象,所以显然无法对它进行更改
引入了_临时量_这一概念之后,就很好理解“对const的引用可能引用一个并非const对象”的概念了。
int i = 42;
int &r1 = i; // 引用r1是一个普通的引用,绑定了i
const int &r2 = i; // 引用r2是一个对const的引用,表面绑定了i,实质绑定的是一个临时量对象
r1 = 0; // 此时i还可以通过r1更改
r2 = 0; // 这里就会报错,因为r2是无法对i进行任何更改的。
指针与const
普通指针类型无法指向常量。只有指向常量的指针才可以指向常量类型。指向常量类型的指针写作:const 数据类型 *ptr
。
前面同样提到指针类型必须和它指向的对象的类型一致,但是指向常量的指针,也是可以指向一个非常量的对象的。
eg.
double i = 3.14; // 非const
const double *cpter = &i; //指向非常量对象,但是无法通过cpter改变
常量指针(const pointer):const允许把指针本身定为常量。常量指针必须初始化, 一但初始化完成,指针就和它所指向的地址绑定了。但是指针指向的变量本身是可以改变的,换言之如果指向的是变量而不是常量,我们仍然可以使用*ptr进行访问或修改。常量指针写作:数据类型 *const ptr
;
常量指针(顶层const) | 指向常量的指针(底层const) | 指向常量的常量指针 (右边的const是顶层const 左边的const是底层const) |
---|---|---|
指针对象是一个常量 | 指针指向的对象是一个常量 | 两者都是常量 |
可以通过解引用符*修改指向的变量 | 无法通过解引用符*修改指向的变量 | 无法通过解引用符*修改指向的变量 |
无法指向其他的对象 | 可以指向其他的对象 | 无法指向其他的对象 |
声明时必须初始化 | 声明时不必定义 | 声明时必须初始化 |
顶层const和底层const
底层const | 顶层const | |
---|---|---|
定义 | 指针和引用的复合类型的基本类型部分是常量 | 任意的对象是常量 |
拷贝操作 | 拷入拷出的对象必须都是相同的底层const,或者可以相互转换(非常量可以转为常量,反之不行) | 不受影响 |
底层const和顶层const的实质理解是这样的:
对于一般对象,只存在顶层const
对于指针,由于指针包含了本身这个对象和它所指向的对象两个对象,所以指针本身的const称之为顶层const,指针指向的对象的const称之为底层const
对于引用,引用本身不是对象,所以只存在底层const
constexpr和常量表达式
常量表达式指的是在编译过程就能得到计算结果的表达式
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化
一个constexpr指针的初始值必须为nullptr或者0,或者某个存储在固定地址的对象
指针和constexpr
constexpr int *q = nullptr // q是一个指向整数的常量指针,等同于 int *const q = nullptr
constexpr可以把它所定义的对象置为顶层const。同时,constexpr指针既可以指向常量也可以指向非常量。但是当它指向一个变量名时,它的定义必须在函数体外
int j = 0;
constexpr int i = 42; //i的类型时整型常量
// i和j必须定义在函数体之外
constexpr const int *p = &i; // p是常量指针,指向整型常量i
constexpr int *p1 = &j; // p1是常量指针,指向整数j
处理类型之类型别名
指针、常量和类型别名
eg.
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指针 类似于char *const cstr = 0;而不是直接带入的const char *cstr
const pstring *ps; // ps是一个指针,指向了一个对象,该对象是一个指向char类型的常量指针
总之不能直接将typedef带入进行理解。
处理类型之auto和decltype
c++11新标准引入的新的类型说明符auto和类型指示符decltype。
- auto 让编译器通过初始值来推算变量的类型
- auto定义的变量必须具有初始值
- auto能在一条语句中声明多个变量,但是该语句中所有变量的初始基本数据类型必须一致。
- decltype 编译器分析表达式并得到它的类型,但是不计算表达式的值
- decltype如果使用的是一个不加括号的变量,则得到的结果就是该变量的类型
- decltype如果使用的是一个加括号的变量 ,则编译器会把它当作表达式,得到的结果是引用类型
auto | decltype | |
---|---|---|
相同 | 都可以通过编译器获得变量类型 | 都可以通过编译器获得变量类型 |
不同 | 1. 跟引用相关时,auto采用的是引用对象的值 2. 在处理顶层const时,auto会自动忽略掉顶层const,只保留底层const 3. 跟解引用*相关时,auto采用的是对对象地址进行解引用后的值 | 1. 跟引用相关时,decltype采用的是对象的引用 2. 在处理顶层const时,decltype会保留顶层const 3. 跟解引用相关时,decltype采用的是对对象的引用 |