顶层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
万能引用