C++Primer笔记(第二章)
关键词 : 对象 , 变量 , 引用 , 指针 , const , auto , decltype
第二章 :
有关对象 :
对象是指一块能存储数据并具有某种类型的内存空间 , 当对象创建时获得了一个特定的值 , 这个行为被称作初始化 . 初始化与赋值的区别在于赋值是把对象的当前值擦除 , 而以一个新值来替代.(“擦除”和”替代”)
有关变量 :
声明 : 规定了变量的类型和名字
定义 : 任何包含了显式初始化的声明即成为定义(申请储存空间并赋值)
如果想声明一个变量而非定义 , 就在变量名前添加关键字extern
, 而且不要显式的初始化变量 . 在函数体内部 , 如果试图初始化一个extern
关键字标记的变量将引发错误.
变量能且只能被定义一次 , 但是可以被多次声明.(怎么理解?)
变量的定义必须出现在且只能出现在一个文件中 , 而其他用到该变量的文件必须对其进行声明 , 却绝对不能重复定义.
有关作用域 :
::
称为作用域操作符 , 当左侧为空的时候 , 向全局作用域请求获得操作符右侧名字的变量.
有关引用和指针 :
引用和指针应该算是第二章里比较重点 , 同时也比较绕的内容 , 特将内容梳理如下以备查阅与理解:
引用 : 引用是为已经存在的对象起的另外一个名字.
- 引用必须被初始化 : 在定义引用的时候 , 程序就把引用和它的初始值绑定在了一起 , 因为无法重新绑定到其他对象 , 所以引用必须被初始化 .
- 因为引用本身不是一个对象(可寻址的空间) , 所以无法定义引用的引用.
- 引用只能绑定在对象上 , 而不能绑定在字面值或者某个表达式的计算结果上.(原因 : 首先字面值本身不是一个对象 , 所以不能绑定 , 表达式的计算结果应该是一个临时变量 , 也不是一个对象所以无法绑定 ? 怎么理解 ?)
指针 : 指针本身就是一个对象 , 允许对指针进行赋值以及拷贝 , 在指针的生命周期里可以先后指向不同的对象 . 指针无需在定义时赋初值 , 如果定义时未初始化则有一个不确定的值.
同样(与引用类似) , 一个指向常量的指针可以指向一个非常量对象.
引用不是对象 , 没有实际地址 , 所以无法定义指向引用的指针.
指针的值应属于以下四种状态之一 :
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针 , 没有指向任何对象
- 无效指针 , 除了上述情况的其他值
指针伴随着一对操作符 : 取地址符&
和解引用符*
, 前者获得对应对象的地址 , 后者获得地址所指的对象(解引用操作仅适用于那些指向了有效地址的指针).
尽量初始化所有的指针 , 并且在可能的情况下 , 尽量等定义了对象再定义指向它的指针 , 如果不知道应该指向何处 , 可以把它初始化为nullptr或者0 .
指针可以使用==
进行比较 , 当指向的地址值相同时为true.
void 是一种特殊的指针类型 , 可以用于存放任意对象的地址 . 可以用来拿它和别的指针比较 , 作为函数的输入和输出 , 或者赋给另外一个void指针 .
关于复合类型的声明 :
最简单的办法就是从右向左阅读r的定义,离变量名最近的符号对变量的类型有最直接的影响 .
关于const限定符 :
const对象必须初始化.
只能在const类型的对象上执行不改变其内容的操作 , 默认情况下 , const变量仅在文件中有效 , 当多个文件中出现了同名的const变量时 , 其实等同于在不同文件中定义了独立的变量.
如果想在多个文件之间共享const对象 , 必须在变量的定义前添加extern关键字.
可以把引用绑定到const对象上 , 称之为对常量的引用 , 不能被用作修改它所绑定的对象.
引用的使用场景(怎么理解?) :
- 别名–>减少拷贝开销
- 参数传递–>减少拷贝开销 , 避免空指针
- 函数返回值–>
1 | double dval = 3.14; |
常量引用仅仅对引用可参与的操作进行了限制 , 即不能通过该引用改变其引用的对象 , 对于对象本身是否是常量未做限定.
指针和const
指向常量的指针(
pointer to const
)不能用于改变其所指对象的值 , 存放常量对象的地址 , 只能使用指向常量的指针.1
2
3
4const double pi = 3.14;
double *ptr = π // 非法
const double *cptr = π // 合法
*cptr = 43; // 非法, 不能给*cptr赋值.
同样指向常量的指针也对指向的对象本身是否是常量未做限定 , 仅仅是不能通过该指针改变所指对象的值.
常量指针(
const pointer
) , 指针本身是常量 , 根据const的规则 , 必须初始化 , 初始化完成后其值不可改变 , 不可变的是指针本身而不是指向的值 , 是否可以通过该指针改变其指向的值取决于它指向的值是否是常量.“是否可以通过该指针改变其指向的值, 取决于它指向的值是否是常量” , 这句话并不意味着常量指针可以指向常量对象, 详见例子:
1
2
3
4
5const int a = 1;
int *const p = &a; // 错误, 常量指针指向了常量对象
const int *p1 = &a; // 正确, 指向常量的指针指向了常量对象
int *p2 = &a; // 错误, 普通指针指向了常量对象
const int *const p3 = &a; // 正确, 指向常量的常量指针指向了常量对象, 此处对常量对象的引用主要取决于指向常量的指针, 对于指针本身是否是常量并不做要求
顶层const与底层const
顶层const(top-level const
) : 表示指针本身是个常量
底层const(low-level const
) : 表示指针所指的对象是个常量
1 | int i = 0; |
constexpr和常量表达式 :
值不会改变并且在编译过程中就能得到计算结果的表达式 .
1 | int staff_size = 27; // 不是常量表达式, staff_size可以改变 |
将会提到 , 函数体内定义的变量一般来说并非存放在固定地址中 , 因此constexpr指针不能指向这样的变量, 定义于所有函数体之外的对象地址固定不变, 能用来初始化constexpr指针 , 允许函数定义一类有效范围超出函数本身的变量, 这类变量和定义在函数体之外的变量一样有固定地址, 因此, constexpr引用能绑定到这样的变量上, constexpr指针也能指向这样的变量. (后面提到再看吧……)
关于类型别名 :
可以使用typedef和using两种关键字进行定义.
1 | typedef char* pstring; |
关于auto类型说明符 :
auto定义的变量必须有初始值 .
当引用被用作初始值时 , 真正参与初始化的其实是引用对象的值 , 编译器以引用对象的类型作为auto的类型.
auto一般会忽略掉顶层const , 同时底层const则会保留下来 :
1 | int i = 0, &r = i; |
符号&和*只从属于某个声明符 , 而非基本数据类型的一部分 , 因此初始值必须是同一种类型.
decltype类型指示符:
类似于lua中的type()用来推断类型, 它的作用是选择并返回操作数的数据类型 , 编译器分析表达式并得到它的类型, 却不实际计算表达式的值.
1 | decltype(f()) sum = x; // sun类型为函数f的返回类型 |
如果decltype使用的表达式是一个变量, 则decltype返回该变量的类型 (包括top-level const和引用在内) ; 如果decltype使用的表达式不是一个变量 , 则decltype返回表达式结果对应的类型.
decltype((variable))
的结果永远是引用 , 而decltype(variable)
结果只有当variable
本身是一个引用时才是引用.
1 | decltype((i)) d; // d是int&, 必须初始化 |
有关头文件 :
头文件通常只包含那些只能被定义一次的实体, 如类 , const和constexpr变量.
1 |