1.统一的列表初始化
{}列表初始化
C++11规定,所有的内置类型和自定义类型,可以使用列表初始化,例如:
struct point
{
int x;
int y;
};
int main()
{
point a1 = {1,2 };
int a2 = { 1 };
int a3[] = { 1,2,3,4,5 };
string s = { "lijinpeng" };
return 0;
}
不过看起来没什么用,确实没什么大用
还可以把= 去掉,这样效果和上面是一样的
struct point
{
int x;
int y;
};
int main()
{
point a1{ 1,2 };
int a2 { 1 };
int a3[] { 1,2,3,4,5 };
string s{ "lijinpeng" };
return 0;
}
std::initializer_list 初始化
initializer_list是一个类,一般是用来作为构造函数的参数,C++11对STL中的不少容器就增加了initializer_list作为参数的构造类型,这样初始化容器就方便很多了
int main()
{
vector<int> v = { 1,2,3,4 };
list<int> lt = { 1,2 };
// 这里{"sort", "排序"}会先初始化构造一个pair对象,用pair对象作为一个元素,进行dict的构造
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
// 使用大括号对容器赋值
v = { 10, 20, 30 };
return 0;
}
2.C++11新增关键字
1.auto
auto可以自动推断出数据的类型,非常方便
it1和it2的类型是一样的,这样auto用起来就很方便,但是如果不清楚数据的类型,还是尽量不这样写,容易看不懂自己写的代码;
2.decltype
decltype可以拿到数据的类型,还可以用来声明该数据,比如:
decltype拿到a.begin()的类型,并用来声明it3,
3.右值引用
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;
}
什么叫右值?什么是右值引用?
右值也是一个表示数据的表达式,右值不能取地址,右值引用就是给右值取别名
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;
}
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被储存到特定位置,且可以取到该位置的地址,并修改该右值,
2.左值引用和右值引用的区别
左值可以引用左值,但是不能引用左值,const 左值既可以引用左值,也可以引用右值,
int main()
{
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
右值可以引用右值,但是不能引用左值,右值可以引用move后的左值
int main()
{
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
return 0;
}
3.右值引用的场景和意义
引用的意义是减少拷贝,提高效率,但是左值引用返回值的问题没有彻底解决,如果返回函数中局部对象,不能引用返回,还是要拷贝一下,
这时候就要用右值引用返回了;
namespace ljp
{
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;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
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;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
}
当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
对于这个问题,我们使用右值引用,进行移动构造,因为"-1234"传参可以识别成右值,然后走移动构造,这样就可以只走一次构造了减少了构造
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{
ljp::string ret1;
ret1 = ljp::to_string(1234);
return 0;
}
在string类中增加移动赋值函数,再去调用to_string(1234) 走的就是移动赋值,
右值引用本身是左值
4.完美转发
模板中&& 表示万能引用
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
//函数模板里,这里可以叫做万能引用,其既能接受左值,又能接受右值
template<class T>
void PerfectForward(T&& t)
{
//当t是左值时,就调用左值引用
//但是t是右值时,由于右值引用本身是左值,所以Fun(t)中的t是左值,这样模板只能实例化出左值的引用
Fun(t);
}
解决办法就是用完美转发
template<class T>
void PerfectForward(T&& t)
{
//Fun((T&&)t);
Fun(std::forward<T>(t));//std::forward<T>(t)在传参过程中保持了t的原生类型,传的是左值就是左值
//传的是右值就是右值
}
还有一种办法就是强制转换成T&&类型,效果和forward<T>(t)一样的
5.新增类功能
原来C++类中有六个默认成员函数
1.构造函数
2.析构函数
3.拷贝构造函数
4.赋值重载
5.取地址重载
6.const 取地址重载
C++新增了两个 默认成员函数:
移动构造函数和移动赋值函数
主要是针对右值传参
如果你没有自己实现移动构造函数,且没有实现拷贝构造 赋值重载 和析构函数,那么编译器会自己实现一个默认移动构造函数,默认的移动构造函数,对内置类型,会按字节拷贝,对自定义类型成员,要看这个成员是否实现移动构造,如果实现了移动构造就调用移动构造,如果没有实现就调用拷贝构造;
如果你没有实现移动赋值重载函数,且没有实现拷贝构造,赋值重载和析构函数,那么编译器会默认生成移动赋值,默认生成的移动赋值函数,对对内置类型,会按字节拷贝,对自定义类型成员,要看这个成员是否实现移动赋值,如果实现了移动赋值就调用移动赋值,如果没有实现就调用拷贝赋值
default:强制生成默认函数的关键字
C++11可以让你更好的控制要使用的默认函数,假如你要使用某个默认的函数,但是因为一些原因这个函数没有生成,就可以使用default关键字强制生成默认函数;
delete:禁止生成默认函数的关键字
在C++11中,只需要在该函数声明上加上=delete,那么编译器就不默认生成该函数,称=delete函数为删除函数
6.lambda表达式
lambda表达式实际上是一个匿名函数,
书写格式:
:[capture-list] (parameters) mutable -> return-type { statement }
[capture-list]是捕捉列表,该列表出现在lambda函数的开始位置,捕捉列表能够捕捉上下文的变量供lambma函数使用
parameters 参数列表,和普通参数传参一样,如果不需要传参可以省略
mutable:默认情况下,lambma函数总是一个const函数,mutable可以取消其常量性 使用该修饰符时,参数列表不可省略(即使参数为空)
->returntype:返回值类型,函数可以自动推导,如果没有返回值,可以省略,如果有返回值,返回值明确的情况下,也可以省略,所以我们常见的写法都是省略了;
statement :函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
lambda表达式可以理解为无名函数,该函数无法直接调用,如果想要调用,可以借助auto将他赋值给一个变量
注意:每个lambda的返回类型都是不一样的,所以我们借助auto直接推导lambda的类型,方便我们调用,
lambda的底层就是仿函数,类似于范围for的底层是迭代器一样,
传参时用引用传参,可以修改x和y的值
捕捉列表说明:
[var]: 表示捕捉变量var
[=]: 表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用捕捉变量var
[&]:表示引用传递捕捉所有父作用域的变量(包括this)
[this]: 表示值传递方式捕捉当前this指针
[=]捕捉所有的变量,但是默认是const属性的,mutable修饰后,可以取消其常量性,
还可以混合捕捉:
7.可变参数模板
//T是一个模板参数包,args是一个函数形参参数包
//声明一个参数包T...args,这个参数包中可以包含0到任意个模板参数
template<class ...T>
void ShowList(T...args)
{
}
上面的参数args前面有省略号,表示他是一个可变模板参数,也叫做参数包,他里面包含了从0到N个模板参数,我们无法直接获取参数包中的参数,只能通过展开参数包的方式获取参数,比如
用递归方式展开参数包
void showlist()
{
cout <<"\n";
}
template<class T,class ...Args>
void showlist(T value, Args...args)
{
cout << value << " ";
showlist(args...);
}
template<class ...Args>
void _showlist(Args...args)
{
showlist(args...);
}
int main()
{
showlist(1);
showlist(1, 'A');
showlist(1,'A','B',"ljp");
return 0;
}