c++———————————————c++11
1.c++11
C++11 是 C++ 的第⼆个主要版本,并且是从 C++98 起的最重要更新。它引⼊了⼤量更改,标准化了既 有实践,并改进了对 C++ 程序员可⽤的抽象。在它最终由 ISO 在 2011 年 8 ⽉ 12 ⽇采纳前,⼈们曾使 ⽤名称“C++0x”,因为它曾被期待在 2010 年之前发布。C++03 与 C++11 期间花了 8 年时间,故⽽这 是迄今为⽌最⻓的版本间隔。从那时起,C++ 有规律地每 3 年更新⼀次。

2.列表初始化
2.1c++98传统的{}
c++98中数组和结构体可以用{}来初始化
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
2.2c++11中的{}
在c++11以后就可以用{}初始化,这个也叫初始化列表
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// C++98⽀持的
int a1[] = { 1, 2, 3, 4, 5 };
int a2[5] = { 0 };
Point p = { 1, 2 };
// C++11⽀持的
// 内置类型⽀持
int x1 = { 2 };
// ⾃定义类型⽀持
// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化
d1
// 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的
Date d1 = { 2025, 1, 1};
// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象
const Date& d2 = { 2024, 7, 25 };
// 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
Date d3 = { 2025};
Date d4 = 2025;
// 可以省略掉=
Point p1 { 1, 2 };
int x2 { 2 };
Date d6 { 2024, 7, 25 };
const Date& d7 { 2024, 7, 25 };
// 不⽀持,只有{}初始化,才能省略=
// Date d8 2025;
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐
v.push_back({ 2025, 1, 1 });
3.右值引用和移动语义
c++98语法中就有引用的语法,c++11新增了右值的语法,无论是左值和右值都是给对象取别名
3.1左值和右值
左值表示一个数据的表达式,一般是持久状态只要在程序结束的时候才会释放,一般存储在内存当中,我们也可以获取他的地址左值可以出现赋值符号的左边,也可以出现在赋值符号右边定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。
右值也是一个数据的表达式,也是字面常量要么是表达式求值过程中创建的临时对象 等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
// 左值:可以取地址
// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值
10;
x + y;
fmin(x, y);
string("11111");
3.2左值引用和右值引用
Type& r1 = x; Type&& rr1 = y;
第⼀个语句就是左值引⽤,左值引⽤就是给左值取别
名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。
左值引用不能直接引用右值,但是const的左值可以直接引用
右值不能直接引用左值,但是可以用move(左值)
move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换,当然他还涉及⼀些引⽤折叠的知
识,这个我们后⾯会细讲。
需要注意的是表达式都是左值,也就意味一个右边被右值绑定后,右值引用的表达式是左值
3.3延长生命周期
右值引用可以为临时变量延长生命周期,const 引用也可以延长生命周期但是两个都不可以被修改
int main()
{
std::string s1 = "Test";
// std::string&& r1 = s1; // 错误:不能绑定到左值
const std::string& r2 = s1 + s1; // OK:到 const 的左值引⽤延⻓⽣存期
// r2 += "Test"; // 错误:不能通过到 const 的引⽤修改
std::string&& r3 = s1 + s1; // OK:右值引⽤延⻓⽣存期
r3 += "Test"; // OK:能通过到⾮ const 的引⽤修改
std::cout << r3 << '\n';
return 0;
}
3.4
左值和右值的参数匹配
C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。
C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数,那么实参是左值会
匹配f(左值引⽤),实参是const左值会匹配f(const 左值引⽤),实参是右值会匹配f(右值引⽤)。
void f(int& x)
{
std::cout << "左值引⽤重载 f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "到 const 的左值引⽤重载 f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引⽤重载 f(" << x << ")\n";
}
int main()
{
int i = 1;
const int ci = 2;
f(i); // 调⽤ f(int&)
f(ci); // 调⽤ f(const int&)
f(3); // 调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&)
f(std::move(i)); // 调⽤ f(int&&)
// 右值引⽤变量在⽤于表达式时是左值
int&& x = 1;
f(x); // 调⽤ f(int& x)
f(std::move(x)); // 调⽤ f(int&& x)
return 0;
}
4.移动构造和移动赋值
移动构造是一种构造,类似于拷贝构造
移动构造函数要求第⼀个参数是该类类型的引
⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。
移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函
数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有
意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要“窃取”引⽤的
右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率,因为他的资源在函数结束之后本身就是要释放的,竟然要释放不如我们直接给他拿走这样就提高了很大的效率,减少了拷贝构造。下⾯的test::string 样例实现了移动构造和移动赋值,我们需要结合场景理解
namespace test
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
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);
}
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;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 拷贝赋值" <<
endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
~string()
{
cout << "~string() -- 析构" << endl;
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];
if (_str)
{
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)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
int main()
{
test::string s1("xxxxx");
// 拷⻉构造
test::string s2 = s1;
// 构造+移动构造,优化后直接构造
test::string s3 = bit::string("yyyyy");
// 移动构造
test::string s4 = move(s1);
cout << "******************************" << endl;
return 0;
}
在新的编译器中会直接优化,不想看优化的可以去linux下面试验一下-fno-elide-constructors 关闭优化环境 下编译器的处理,然后就发现编译器优化还是很恐怖的
4.1右值引⽤和移动语义在传参中的提效
当实参是一个左值的时候,容器内部会拷贝构造,将拷贝到容器创建的对象当中
当实参是一个右值的时候,容器会进行移动构造,将右值的对象资源移动到容器对象的空间上
我们还是用上面大的代码来看一下
int main()
{
std::list<test::string> lt;
test::string s1("111111111111111111111");
lt.push_back(s1);
cout << "*************************" << endl;
lt.push_back(test::string("22222222222222222222222222222"));
cout << "*************************" << endl;
lt.push_back("3333333333333333333333333333");
cout << "*************************" << endl;
lt.push_back(move(s1));
cout << "*************************" << endl;
return 0;
}
string(char* str)
string(const string& s) -- 拷⻉构造
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(string&& s) -- 移动构造
*************************
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构
5.引用折叠
c++中不能直接定义引用的引用int& && r = i; ,这样写会直接报错,通过模板或 typedef 中的类型操作可以构成引⽤的引⽤。
通过模板或 typedef 中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规
则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
下⾯的程序中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则,⼤家需要⼀个⼀
个仔细理解⼀下
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&
t)
// 所以Function内部会编译报错,x不能++
Function(b); // const 左值
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&
t)
// 所以Function内部会编译报错,x不能++
Function(std::move(b)); // const 右值
return 0;
}
这个说明如果你传的是左值推导的就是int& 的类型如果传递是右值的话就是int
6.完美转发
template<class T>
void Function(T&& t)
{
Fun(t);
//Fun(forward<T>(t));
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&
t)
Function(b); // const 左值
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&
t)
Function(std::move(b)); // const 右值
return 0;
}
//Fun(forward<T>(t))
他再去调用fun(t)的话全部是左值上面我们也讲到这一点,

那我们怎么能把传的左值调用左值,传递右值的时候调用右值呢
这个时候就需要完美转发
//Fun(forward<T>(t))
这个也可以说是变相的强制转换
好了今天的分享就到这样有什么不足的地方大家可以指出来谢谢大家