目录
一.统一的列表初始化
1.1.{}初始化
在在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
struct List
{
int val;
char arr[10];
};
List p = { 1,"hello" };
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 x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
List p{ 1, 2 };
int* p1 = new int(99);
cout << *p1 << endl;
int* p2 = new int[3]{ 1,3,4 };
cout << p2[0] << p2[1] << p2[2]<<endl;
int* pa = new int[4]{ 0 };// C++11中列表初始化也可以适用于new表达式中
Date d1(2022, 1, 1); // old style
Date d2{ 2022, 1, 2 }; // C++11支持的列表初始化,这里会调用构造函数初始化
Date d3 = { 2022, 1, 3 };//隐式类型的转换 + 优化
Date* d4 = new Date[2]{ {2022,9,21},{2022,9,11} };
return 0;
}
结果:
1. 2 std::initializer_list
template<class T>
class Vector {
public:
typedef T* iterator;
Vector(initializer_list<T> p)
{
_start = new T[p.size()];
_finish = _start + p.size();
_endofstorage = _start + p.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = p.begin();
while (lit != p.end())
{
*vit++ = *lit++;
}
}
Vector<T>& operator=(initializer_list<T> p)
{
Vector<T> tmp(p);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
二.声明
2.1decltype
2.2nullptr
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
三.STL中的变化
array:越界一定可以检查出来,访问更安全。
forward_list:
....................
四.右值引用和移动语义
4.1 左值引用和右值引用
C++之前存在左值引用,而C++11新增了右值引用,二者都是给对象取别名。
左值引用介绍:
左值表示一个数据的表达式,可以获取它的地址,赋值,左值只可以出现在=的左边。
举例:
int main()
{
// 以下的p、b、c、*p都是左
int* p = new int(999);
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;
}
补充:
4.2左值引用与右值引用比较
int main()
{
int p = 99;
int& k1 = p;
/* int& k2 = 99;*///报错
const int& k1 = p;
const int& k2 = 99;
return 0;
}
int main()
{
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
int a = 10;
int&& r2 = a;// error C2440: “初始化”: 无法从“int”转换为“int &&”, message : 无法将左值绑定到右值引用
int&& r3 = std::move(a);// 右值引用可以引用move以后的左值
return 0;
}
4.3左值引用的场景
先来回顾下左值引用的场景,以之前模拟实现的string进行比较。
class String
{
public:
typedef char* iterator;
String(const String& s)// 拷贝构造
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "String(const String& s) -- 深拷贝" << endl;
String tmp(s._str);
swap(tmp);
}
String& operator=(const String& s)// 赋值重载
{
cout << "String& operator=(String s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
void swap(String& s)// s1.swap(s2)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
~String()
{
delete[] _str;
_str = nullptr;
}
String& operator+=(char ch)
{
push_back(ch);
return *this;
}
...........
...........
//其余函数的实现
private:
char* _str;
size_t _size;
size_t _capacity;
};
上面String类中的那些拷贝构造中的参数,赋值重载的返回值等,都用了&,这样可以减少拷贝带来的消耗。但也存在一些函数,是不能用左值引用的。
例如:
String to_String(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
String str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;//str是一个临时对象
}
String s=to_String(1234);//会用str拷贝一个临时对象,再用那个临时对象拷贝构造s,优化后只有一次,
4.4右值引用的场景
右值也可分为纯右值,将亡值。
例如:
10;//纯右值
x+y;
string("hello"),//将亡值,只该变量马上要被销毁
string s("hello);
move(s);
上面String的模拟实现中,拷贝构造函数可以增加一个移动构造函数。
String(String&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "String(String&& s) -- 资源转移" << endl;
swap(s);
}
上面所用的是用右值去接受,可以认为s是一个将亡值,马上会被销毁,没必要去做深拷贝,只需将二者的资源进行转移即可(交换指针)。
上面的to_String函数中,返回的str是一个临时对象,出作用域后会被销毁,增加了移动构造函数后,(此处可认为编译器做了优化,会将str识别为右值,因为它马上要被销毁),则会去匹配最适合的拷贝构造函数,这时会去调用移动构造函数,这样便减小了开销,(仅仅交换了指针,没有再去开辟空间)。
移动赋值:
对于一个已经存在的对象时,用一个即将被销毁的对象去赋值给它,也没必要去深拷贝。
String& operator=(String&& s)
{
cout << "String& operator=(String&& s) -- 资源转移" << endl;
swap(s);
return *this;
}
在c++11后,许多的容器中的函数都增加了右值的版本,如果情况适合,调用右值的版本就可以减少开空间的开销。
可以看下一:
也可以用代码测试一下:
常用的swap函数:
五.完美转发
5.1模板中的&& 万能引用
先看一个简单的例子:
void show(int& x) { cout << "左值引用\n"; }
void show(int&& x) { cout << "右值引用\n"; }
void show(const int& x) { cout << "const左值引用\n"; }
void show(const int&& x) { cout << "const右值引用\n"; }
template <class T>
void Fun(T&& x)
{
show(x);
}
int main()
{
int a = 10;
const int b = 100;
Fun(a);//左值
Fun(b);
Fun(999);//右值
Fun(move(a));
return 0;
}
结果:
左值引用
const左值引用
左值引用
左值引用
解释:
模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
例如:int&& rr1 = 10; 10是右值,rr1是开了一块空间把10存储起来,是可以取地址的,则rr1是左值)
5.2完美转发
对上面的代码进行更改后:
void Fun(T&& x)
{
show(forward<T>(x));//完美转发
}
结果:
左值引用
const左值引用
右值引用
右值引用
6.新的类功能
默认成员函数
禁止生成默认函数的关键字delete:
7.可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板
template <class ...Args>
void ShowList1(Args... args)
{
// 参数个数
cout << sizeof...(args) << endl;//计算的是参数包中形参的个数
}
int main()
{
ShowList1(1,2,3,'x', 1.1);
}
结果:5
上面的例子中,先将1传给T,参数包中还剩下4个参数。最后会匹配无参的那个函数。
若参数相同,也可直接进行初始化:
也可以这样使用: