1.列表初始化
struct Point
{
int _x;
int _y;
};
int main()
{
int array[] = { 0,1,2,3,4,5 };
int array[5] = { 0 };
Point p = { 1,2 };
return 0;
}
struct Point
{
int _x;
int _y;
};
int main()
{
int a = 1;
int b{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4] {0};
return 0;
}
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()
{
Date d1(2024, 4, 1);
Date d2 = { 2024,4,2 };
Date d3{ 2024,4,3 };
Date* dp1 = new Date[3]{ d1,d2,d3 };
Date* dp2 = new Date[3]{ {2024,4,1},{2024,4,2},{2024,4,3} };
return 0;
}
2.std::initializer_list
initializer_list是一个特殊的容器(不实际存储数据)
此类型用于访问C++初始化列表中的值,该列表是const T类型的元素列表。编译器根据初始化列表声明自动构建此类型的对象,初始化列表声明是一个逗号分隔的元素列表,用大括号括起来
嗯...以实例来看比较好理解,这里我们用vector为例
3.变量类型推导
3.1auto
感觉auto没什么好讲的,很少用到,就是单存的适配所有类型,当然auto声明的类型必须要进行初始化,而且不要作为函数的参数和用来接收函数返回值的变量类型
int main()
{
int i = 10;
auto p = &i;
cout << typeid(p).name() << endl;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
//嫌迭代器太长、麻烦,用auto做类型(感觉已经挺过分了)
auto it = dict.begin();
return 0;
}
3.2decltype
int func1()
{
return 10;
}
int main()
{
const int x = 1;
double y = 2.2;
cout << typeid(x).name() << endl;
cout << typeid(string).name() << endl;
decltype(x) z = 1;
cout << typeid(z).name() << endl;
const int* p1 = &x;
cout << typeid(p1).name() << endl;
decltype(p1) p2 = nullptr;
cout << typeid(p2).name() << endl;
auto ret = func1();
// 假设要用vector存func1的数据
vector<decltype(ret)> v;
return 0;
}
4.STL中一些变化

array是静态数组,forward_list是单链表
5.右值引用和移动语义
5.1左值引用和右值引用
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;
}
常见的右值及右子引用
总结一下:
语法上,引用都是别名,不开空间,左值引用是给左值取别名,右值引用是给右子取别名
就底层来说,引用是用指针实现的。左值引用是存当前左值的地址;右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址
5.2左值引用和右值引用比较
左值引用:
左值引用只能引用左值,不能引用右值
但是const左值既可以引用左值,也可以引用右值
int main()
{
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra = a;
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
右值引用:
右值引用只能给右值取别名,不能给左值取别名
但是move后的左值可以被右值引用取别名
int main()
{
int x = 0;//左值
int&& rr1 = move(x);
return 0;
}
注意:
const左值引用既可以给左值取别名,也可以给右值取别名
右值引用可以给move(左值)取别名
6.右值引用使用场景和意义
6.1移动构造和移动赋值
左值引用做参数和返回值可以提高效率->解决了什么问题
1.传参的拷贝全解决了
2.传返回值的问题解决了一部分
另外一部分:
局部对象(出了作用域就销毁对象)返回的拷贝问题没有解决
由右值引用来解决
右值引用:提高移动构造/移动赋值等深拷贝场景的效率
namespace xxx
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(char* str)" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造 -- 左值
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// 移动构造 -- 右值(将亡值)
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
//string operator+=(char ch)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
xxx::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
xxx::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
reverse(str.begin(), str.end());
return str;
}
}
int main()
{
xxx::string ret = xxx::to_string(-1234);
}
string构造是string(const string& s),const左值既能引用左值又能引用右值,所以要重载一个右值版本的构造,也就是移动构造
// 移动构造 -- 右值(将亡值)
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
有了移动构造后呢
C++11对右值概念的解释(细分,方便理解)
1.纯右值(内置类型的右值)如:10、a+b
2.将亡值(自定义类型的右值)如:匿名对象、传值返回函数
string s2(tmp)
如果tmp对象将亡值,深拷贝是一种极度的资源浪费,因为深拷贝后,tmp马上又销毁
那么可以用移动构造
C++提供右值引用,本质是为了参数匹配时区分左右值
注意:不是所有的类都要移动构造(浅拷贝的类不需要,深拷贝的才要)
移动赋值结合以下场景分析
int main()
{
/*xxx::string ret = xxx::to_string(-1234);*/
xxx::string ret;
ret = xxx::to_string(-1234);
}
考虑下赋值重载
s2 = tmp
如果tmp是左值那么只能深拷贝
如果tmp是右值(将亡值)
移动将亡值的资源,并将不要的空间给将亡值,让将亡值帮忙释放
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
swap(s);
return *this;
}
总结:左值引用没有解决的问题,右值引用解决了。深拷贝对象传值返回只需要移动该资源,代价很低。C++11后所以容器都增加了移动构造和移动赋值