目录
统一的列表初始化
C++11
扩大了用大括号括起的列表
(
初始化列表
)
的使用范围,使其可用于所有的内置类型和用户自
定义的类型,
使用初始化列表时,可添加等号
(=)
,也可不添加
。
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
//一切皆可用{}初始化
int main()
{
int a = 0;
//不推荐
int b = { 1 };
int c{ 1 };
int array1[]{ 1, 2, 3, 4, 5 };
Point p{ 1,2 };
// 多参数
// C++98 构造
Date d1(2024, 3, 23);
// C++11
// 先构造 + 再拷贝构造 -> 优化,直接构造
// 多参数的隐式类型转换
Date d2 = {2024, 3, 23};
Date d3 { 2024, 3, 23 };
// 单参数
// 构造
string s1("1111");
// 先构造 + 再拷贝构造 -> 优化,直接构造
string s2 = "1111";
Date* darr1 = new Date[3]{d1,d2,d3};
Date* darr2 = new Date[3]{ { 2024, 3, 23 } ,{ 2024, 3, 23 } ,{ 2024, 3, 23 } };
Date* darr3 = new Date(2023,3,34);
Date* darr4 = new Date{ 2023, 3, 34 };
return 0;
}
开辟空间这种初始化写法可以参考一下,其他的认识即可~不过这种初始化一般不用于内置类型中~因为看着怪怪的~
initializer_list
std::initializer_list
一般是作为构造函数的参数,
C++11
对
STL
中的不少容器就增加
std::initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为
operator=
的参数,这样就可以用大括号赋值.

其实initializer_list本质就是两个指针,一个指向{}头,一个指向{}尾~

int main()
{
Date d1 = { 2024,9,3 };
vector<int> v1 = { 1,2,3,4,5,6,7,8,9};
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
二者虽然同为先构造再拷贝构造,但vector是走的特殊的构造,在这里可以不限制参数个数~

我们再来深入了解其他场景下的应用~
int main()
{
pair<string, string> kv1("sort", "排序");
pair<string, string> kv2("string", "字符串");
//可以识别为pair类型的initializer_list
map<string, string> dict1 = { kv1, kv2 };
//那么这个又是怎么识别的呢
map<string, string> dict2 = { {"sort", "排序"}, {"string", "字符串"} };
for (auto& kv : dict2)
{
cout << kv.first << ":" << kv.second << endl;
}
}




decltype
关键字decltype将变量的类型声明为表达式指定的类型。
// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}
ps:顶层const是指修饰自身的const,底层const是指修饰器指向的内容,而在查看变量类型时会默认去掉顶层const~
右值引用
左值是一个表示数据的表达式
(
如变量名或解引用的指针
)
,
我们可以获取它的地址
+
可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边
。定义时
const
修饰符后的左
值,不能给他赋值,但是可以取它的地址。
左值引用就是给左值的引用,给左值取别名。
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值
(
这个不能是左值引
用返回
)
等等,
右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址
。
右值引用就是对右值的引用,给右值取别名。
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
从语法层面上看,引用都是作为其别名,不开空间~左值引用是给左值取别名,右值引用是给右值取别名~
从底层来看,引用是用指针实现的,左值引用是存当前左值的地址。而右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址~
特殊情况:
int func1()
{
static int x = 0;
return x;
}
int main()
{
// 左值引用能否给右值取别名 不能
// 但是const左值引用可以
const int& r1 = func1();
const int& r2 = 10;
// 右值引用能否给左值取别名 不能
// 但是右值引用可以给move以后的左值可以
int x = 0;
int&& rr1 = move(x);
return 0;
}
我们来分析以下场景:
当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。


简要概括后我们再来深入理解右值引用出现的意义~
引用最开始被设计的意义是什么?
为了减少拷贝~
那么右值引用出现的意义是什么?
是为了解决左值引用不能解决的问题~
左值引用:
可以解决传递参数时的拷贝问题~例如 void func(const T& x)
可以解决部分返回对象拷贝的问题~(出了作用域后返回对象还在,那么就可以用左值返回来减少一次拷贝)



1435

被折叠的 条评论
为什么被折叠?



