【Effective Modern C++】学习第一章

顶层const和底层const

cpp 复制代码
#include <iostream>

int main()
{
    const int a = 10;
    int b = a;

    const int* const p = new int(10);
    int* p1 = p;  //"const int *" 类型的值不能用于初始化 "int *" 类型的实体
    int* const p2 = p;  //"const int *" 类型的值不能用于初始化 "int *const" 类型的实体
    const int* p3 = p;

    int* p4 = &a; //"const int *" 类型的值不能用于初始化 "int *" 类型的实体

    const int& r1 = 20;
    int &r2 = a;  //将 "int &" 类型的引用绑定到 "const int" 类型的初始值设定项时,限定符被丢弃
    int& r3 = r1; //将 "int &" 类型的引用绑定到 "const int" 类型的初始值设定项时,限定符被丢弃
}

当执行对象拷贝操作时,常量是顶层const还是底层const区别明显。顶层const不受影响

拷入和拷出对象必须具有相同的底层const资格

const int* const p3 = p2; //靠右的是顶层const,靠左的是底层const

修饰指针本身的是顶层const,修饰指针所指对象的是底层const

const int& ra = 10; 引用只有底层const

值类型与右值引用

cpp 复制代码
int geta()  {
    int a = 10;    return a;}

int main()  {    int x = geta(); }

(取消掉返回值优化之后)调用这个函数,会执行两次拷贝

return a的时候会先将a拷贝给一个临时对象,然后再由临时对象拷贝给这个函数的赋值对象

C++98的表达式类型

  • 左值:指向特定内存的具名对象
  • 右值:临时对象,字符串除外的字面量

C++11的表达式类型

泛左值 分为 左值 和 将亡值 ,右值分为 将亡值 和 纯右值

将亡值?

cpp 复制代码
int x = 1l;
int& y = -10;  //非常量引用的初始值必须为左值
int&& y = -10;

int* p1 = &x++;  //表达式必须为左值或函数指示符
int* p2 = &++x;
auto p3 = &"hello world";
int&& m = get_x();

左值引用不接受右值,所以出现只接受右值的右值引用 Type&&

移动语义: std::move 可以将左值转为右值

如何得到将亡值:

将泛左值转换为将亡值

  • static_cast <type&&>(xxx)
  • std::move(xxx) 移动语义没移动

临时量实质化(C++17引用)

需要注意的点:

类中没有实现移动构造,std::move 之后仍然是拷贝

右值引用仍是左值,最后取地址,这三个的地址都一样

数组指针与函数指针

数组相关的数据类型

int array5 = {1,2,3,4,5}; array 的数据类型是 int5 ,不是int*

cpp 复制代码
int* ptr = array;  数组名退化为指针
int (*ptr)[5] = &array   数组名取地址的类型 ≠ 数组名的类型  这是一个数组指针  类型为 int(*) [5]
如果不加括号会变成: int* ptr3[5] = {&a,&a,&a,&a,&a};  指针数组
int(&ptr) [5] = array;  数组引用  类型为 int (&) [5]
要加(),《C++ Primer》P147 

数组名作为参数传递会导致退化
void fun1(int a[100]);   void fun1(int a[5]);   void fun1(int* a);  //这一行等价
void fun2(int (*a) [100]);    void fun2(int(*a)[5]); (带参数的传参)   //这一行等价  
// 这两行不等价
cpp 复制代码
"hello world";
char str[12] = "hello world";
const char* strrrr = "hello world";

&"hello world";
char* strptr = &"hello world";  //"const char (*)[12]" 类型的值不能用于初始化 "char *" 类型的实体
const char* strPtr1 = &"hello world";  // "const char (*)[12]" 类型的值不能用于初始化 "const char *" 类型的实体
const char* strptr2[12] = &"hello world";  

带参数的传参和不带参数的传参

cpp 复制代码
bool fun(int a, int b) {}  //--> bool(int,int)  函数数据类型
bool (*funptr)(int a, int b);  //--> bool (*)(int,int); 指针

int main{
int bbb[6];
fun2(bbb);  //"int *" 类型的实参与 "int (*)[5]" 类型的形参不兼容
fun3(bbb);
}

函数相关的数据类型

cpp 复制代码
funptr = &fun; //函数指针赋值
bool c = (*funptr)(1, 2); //函数指针使用
void fun2(int c, bool(*funptr)(int, int));  //函数指针作为形参
bool(*fun2(int c))(int,int);  //函数指针作为返回值
bool(&funref)(int, int) = fun;  //函数的引用

类型别名(typedef / using)

cpp 复制代码
typedef int int_name;
int_name ooo = 10;
using int_name = int;
using int_name = int;

typedef bool (*fun3ptr)(int,int); --> fun3ptr == bool (*)(int,int);
typedef bool fun(int,int);  --> fun == bool(int,int);
using fun3ptr = bool(*)(int,int);  更符合直觉
using fun3xxxx = bool(int,int);

理解模板类型推导

cpp 复制代码
template <typename T>
  void f(const T param);
  void f(T const param);
//一般这个const在哪里没区别,但是如果这个 T 是一个指针的话,就有一些不同

fun(int a);  与
fun(const int a); 不构成重载
顶层const不构成

区别使用() 与 {} 创建对象

cpp 复制代码
A a = 10;
A a(10);
A a = (10);
A a{ 10 };
A a = { 10 };

B b = 10, 5;  //error
B b(10, 5);
B b = (10, 5); //error
B b{ 10,5 };
B b = { 10,5 };

return({10,5});   //可以剩下一次拷贝函数

使用()可能会导致这个,导致本来想初始化一个东西,结果变成了函数声明

但是使用{}就不会这样了

理解auto类型推导

cpp 复制代码
auto x1 = 27;
auto x2(27);
auto x3{ 27 };  //int
auto x4 = { 27 };  //initializer_list<int>

优先考虑别名声明而非typedef

typename关键字:用来澄清模板内部的 T 标识符代表的是某种类型

当编译器遇到类似 T::xxx 这样的代码时,它不知道 xxx是T类型成员还是 T数据成员,知道实例化时才能知道,但是为了处理模板,编译器必须知道名字是否代表一个类型,默认情况下C++语言假设通过作用域运算发的名字不是类型

cpp 复制代码
struct test1 {
    static int subType;
};

struct test2 {
    typedef int subType;
};

template<typename T>
class myClass {
public: void foo() { 
    typename T::subType* ptr;  // 如果没有typename,会显示"ptr": 未声明的标识符
} 
};
cpp 复制代码
template <class T>
using myVector = std::vector<T>;

template<class T> 
struct myVector2 {
    std::vector<T> type;
}

理解std::move 与 std::forward

std::move 的实现

  • 传入参数的类型
  • 本质就是类型转换 static_cast<type&&>(xxx);
  • 如何去掉引用 std::remove_reference<T>::type
cpp 复制代码
template<typename T>
T&& mymove(T&& param) {
    return static_cast<T&&>(param);
}

int main()
{
    int mm = 10;
    int&& nn = mymove(mm);  //error  无法将右值引用绑定到左值
    int&& nn = mymove(10);  //编译通过
}

std::move 实际做的事情:

  • 转化实参为右值
  • 对一个对象使用std::move 就是告诉编译器,这个对象很适合被移动
  • 对于 const 右值引用 只能匹配拷贝构造
cpp 复制代码
class Annotation {
    public: 
        explicit Annotation(const A a):a(std::move(a)) {}
        A a;
}

初探 std::forward

万能引用

学习视频:基础2:值类型与右值引用_哔哩哔哩_bilibili

相关推荐
Fruiticecake37 分钟前
C 程序员速通现代 C++(一):lambda、std::function、移动语义与智能指针
c语言·c++
青少儿编程课堂1 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:中缀表达式求值解析
c++·python·算法·bfs·信息学竞赛
坤坤子吖4 小时前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
一晌小贪欢4 小时前
Python基础:第 09 篇 · while 循环:重复直到不满足
开发语言·c++·python·数据可视化·面向对象·python办公
xiaoye-duck5 小时前
《C++ 项目:手写大模型接入 SDK》LLMManager架构设计与实现
开发语言·网络·c++·语言模型
一条大祥脚5 小时前
CuTe 与 CuTe DSL:从 C++ 模板元编程到 Python DSL 的 GPU 编程演进
开发语言·c++·python·cutlass·cute·cute dsl
·present·5 小时前
CSAPP(lecture 13_02 Linking)
c语言·c++
旖旎夜光6 小时前
【C++ 基础入门】C++ 多态(上):虚函数、重写、override/final 与抽象类
开发语言·c++·笔记·学习
青少儿编程课堂8 小时前
蓝桥杯青少组图形化 STEMA 省考(报名倒计时·11/22开考)+ 全国青少年信息素养大赛 2026—2027赛季 + 电子学会图形化考级:校园华容道大闯关
c++·python·算法·bfs·信息学竞赛