What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
后一修订版
前一修订版
cs:programming:cpp:courses:cpp_basic_deep:chpt_2 [2024/04/16 03:41] – [引用] codingharecs:programming:cpp:courses:cpp_basic_deep:chpt_2 [2024/04/16 15:09] (当前版本) – [对象生命周期] codinghare
行 241: 行 241:
   * 底层还是通过指针来实现   * 底层还是通过指针来实现
 ==指针的引用== ==指针的引用==
 +  * 指针是对象,因此存在指针的引用:
 +  * 引用不是对象,因此不存在引用的应用
 +<code cpp>
 +int* &refPx = ptr;
 +</code>
 +====常量与常量表达式====
 +  * 常量:不可修改的对象
 +    * 可以被视作由编译器保证其不被修改的变量
 +    * 防止非法操作:比如条件判断时可以避免误写 ''='' 代替 ''=='' 带来的问题
 +    * 编译器可以优化:常量不需要每次都判断(读取)值,但变量需要 
 +===常量指针与顶层常量===
 +指针与常量结合时,需要考虑哪个部分不能被修改:
 +  * 指针本身
 +  * 指针指向的部分
 +如果限制的是指针本身,那么:
 +<code cpp>
 +// 指针本身无法改变
 +int* const ptr = &x;
 +</code>
 +如果限制的是指针指向的内容:,那么:
 +<code cpp>
 +//指针可以改指其他位置,不能通过该指针改变其指向的内容
 +const int* ptr = &x;
 +</code>
 +<WRAP center round box 100%>
 +''const'' 出现在 ''*''** 左边**代表指针本身不能指向其他地方。
 +</WRAP>
 +==顶层常量==
 +  * 顶层常量限制的是常量本身无法被修改
 +  * 底层常量限制的是指向的内容不能修改
 +==底层常量的隐式转换==
 +  * 读写 -> 只读是可以的
 +<code cpp>
 +// int * to const int *
 +int x = 4;
 +// &x 由 int* 转化为了 const int*,该指针之前可以对 x 进行读写,但现在只能读取 x
 +const int *ptr = &x;
 +</code>
 +  * 只读 -> 可写是不行的
 +<code cpp>
 +// const int * to int *
 +const int x = 4;
 +// error, &x 无法写 x,因此 ptr 也不能对 x 进行写操作
 +// low-level const 是有传递性的,如果之前只读,那么之后也必须只读(不管通过什么样的途径,比如赋值,参数传递等等)
 +int* ptr = &x;
 +// good
 +const int* ptr = &x;
 +</code>
 +==常量引用==
 +  * const int&:无法通过该引用修改其绑定的值
 +  * 主要是用于高效的函数传递参数:
 +    * 传递某个大对象,但又不想改变该对象,就用 const int&
 +    * 传递某些无法拷贝的对象
 +    * 相比指针,引用不需要判断参数的有效性(空指针什么的)
 +    * built-in 数据不需要使用引用,值传递更效率一些
 +  * **常量引用可以绑定字面值**:
 +    * 常量引用通常作为函数的 parameter,当函数采用字面值作为函数参数的时候,需要保证引用可以绑定该字面值,才能完成调用。
 +===常量表达式===
 +不同的常量确定的时机不同。比如下面的例子:
 +<code cpp>
 +int y;
 +std::cin >> y;
 +// y1 是在运行期确定的(运行期常量)
 +const int y1 = y;
 +// y2 是在编译期确定的(编译器常量)
 +const int y2 = 3;
 +</code>
 +由于这样的问题,编译器在处理 ''y1'' 和 ''y2'' 时,就会有不同的方式。比如以上述的变量做条件判断 ''if (y1 == 3)''
 +  * ''y1'' 会以“期待运行期有输入的”方式进行汇编
 +  * ''y2'' 对编译器可见,那么 ''y2 == 3'' 一定为真。因此,编译器不会进行 ''if'' 的判断,而会直接执行该 if 下的代码段。(优化)
 +也就是说,编译器常量是可以被编译器优化的。这也是常量表达式的由来:C++ 11 中通过 ''constexpr'' 关键字,将常量直接声明为**编译期常量**。也就是说,上述的 ''const int y2 = 3'' 可以写为:
 +<code cpp>
 +// y2 被显式的声明为编译期常量
 +// y2 的类型是 const int
 +// constexpr 是一种带指导意义的限定符(//specifier//),不属于类型声明的一部分
 +constexpr int y2 = 3;
 +</code>
 +==常量表达式指针==
 +指针也可以作为编译器常量。这种情况下,需要满足两个条件:
 +  * 指针是常量
 +  * 指针指向的内容也是常量
 +只有满足这两个条件,指针才能作为编译器常量对编译器可见。也就是说,这种情况下的指针类型是 ''const type* const''
 +<code cpp>
 +constexpr const int* ptr = nullptr;
 +//字符串
 +constexpr const char* str = "abc";
 +</code>
 +<WRAP center round tip 100%>
 +可以使用 std::is_same_v<type1, type2> 来判断类型是否相同。定义于 <type_traits> 头文件
 +</WRAP>
 +====类型别名与自动推导====
 +===类型别名===
 +类型可以引入别名,便利使用(比如 ''size_t'' 实际上是一个 unsigned int)。别名的引入有两种方式:
 +  * typedef + type + alias
 +  * using alias = type
 +<code cpp>
 +//myInt 是 int 类型的别名
 +//推荐使用 using
 +//别名实际上是 myCharArr, 但 typedef 的引用看起来非常 confusing
 +typedef char myCharArr[4];
 +using myCharArr = char[4];
 +</code>
 +==类型别名与指针和引用==
 +  * 当使用别名定义指针时,别名代表了指针的整体。此时如果使用 ''const'' 修饰别名,那么 ''const'' 修饰的是指针,也就是将指针定义为**常量指针**。换句话说,下面两者等价:
 +<code cpp>
 +using intP = int*;
 +int x = 3;
 +
 +//别名定义
 +const intP constIntPAlias = &x;
 +//等同于 top-const pointer
 +int* const constIntP = &x;
 +</code>
 +  * 不能使用别名来创建引用的引用
 +===类型推导===
 +C++11 允许使用 ''auto'' 关键字让编译器自动通过**初始化表达式**的结果推断变量的类型:
 +<code cpp>
 +auto x = 3.5 + 15l;
 +</code>
 +==auto==
 +  * ''auto'' 推导出的类型是强类型(定义时已经确定)
 +  * 因为需要初始化表达式推导类型,因此自动推导下,表达式**必须初始化**
 +  * ''auto'' 可能会导致类型退化
 +<code cpp>
 +int x1 = 3;
 +int& ref = x1;
 +//类型退化,ref 作为右值,此处由 int& 退化为 Int, 而不是 int&
 +int y = ref;
 +//auto 导致类型退化, ref2 是 int 类型
 +auto ref2 = ref;
 +</code>
 +  * ''const / constexpr'' 与 ''auto'' 组合会推导出常量 / 常量表达式类型
 +<code cpp>
 +//const int 
 +const auto x1 = 3;
 +//const int& 
 +const auto& x2 = 3;
 +
 +const int x3 = 3;
 +//const int
 +const auto y1 = x3;
 +//int (top-const 的退化)
 +auto z = x3;
 +</code>
 +  * auto 加引用会避免退化
 +<code cpp>
 +//不会退化,const int&
 +auto& y2 = x3;
 +</code>
 +  * 数组的推导会退化为指向数组首元素的指针
 +<code cpp>
 +int x[3] = {1,2,3};
 +//int*
 +auto x1 = x;
 +//加引用不会退化:int(&)[3] 
 +auto& x2 = x;
 +</code>
 +==decltype==
 +decltype 获取一个表达式,并返回表达式的类型。decltype 与 auto 的区别在于,decltype **不会产生类型退化**。
 + 
 +   * decltype(val):''val'' 代表 entity(**变量名称**),那么 ''val'' 是什么类型,那么返回的就是什么类型
 +<code cpp>
 +//x is an variable name
 +int x = 3;
 +//int
 +decltype(x);
 +//注意加括号的形式,(x) 是表达式, x 是变量名
 +//int&
 +decltype((x));
 +</code>
 +  * ''decltype(exp(r-value))'' :如果表达式是非变量名称的表达式,且为右值,那么返回的是表达式评估后的类型:
 +<code cpp>
 +int x = 3;
 +int& y1 = x;
 +//int
 +auto y2 = y1;
 +//int&
 +decltype(y1) y3 = y1;
 +</code>
 +  * ''decltype(exp(l-vaule))'':如果表达式为非变量名称的左值(通常带运算符),返回的类型会自动加上一个**引用**
 +<code cpp>
 +int x = 3;
 +int* ptr = &x;
 +//*ptr 是包含了解引用操作符的表达式:此处是 l-value,此处得到的是 int& 
 +decltype(*ptr);
 +</code>
 +  * decltype(auto) [C++14]:用于简化 decltype 的使用
 +<code cpp>
 +//非常繁琐的写法
 +decltype(3.5+15l) x  = 3.5 + 15l;
 +//C++ 14 的写法
 +// 用 auto 来代表繁琐的表达式,并不会导致退化
 +// 编译器会将 auto 替换为赋值运算符右边的内容
 +decltype(autol) x  = 3.5 + 15l;
 +</code>
 +  * concept auto [C++20]:任何 concept 都会包含一系列类型,这些类型的共同特征都可以用 concept 表示。比如 ''int'', ''long'' 都可以表示为 ''std::intergal''
 +    * concept auto 让自动推导受 concept 的范围限制,比如让 auto 只能推断出整数类型的类型:
 +<code cpp>
 +#include <concepts>
 +int main()
 +{
 +    //int
 +    std::integral auto z = 3;
 +    //error, 3.5 is not an integral
 +    std::integral auto z = 3.5;
 +}
 +</code>
 +====域和对象生命周期====
 +===域 Scope===
 +域代表了程序的一部分,域中的 name 有**唯一的含义**:
 +  * 全局域:程序最外部的域,全局对象
 +  * 块域:大括号限定的域,局部对象
 +  * 其他类型的域:Namespace, class 等等
 +域可以进行嵌套,内部域中的 Name 会掩盖外部域中的 Name:
 +<code cpp>
 +int x = 3;
 +int main()
 +{
 +   int x = 4;
 +   //call local x
 +   std::cout << x << '\n';
 +}
 +</code>
 +===对象生命周期===
 +生命周期指对象从被**初始化到被销毁**的区间。
 +  * 全局对象:生命周期为程序的运行期
 +  * 局部对象:起始于**初始化**,结束于域的执行完成