目录
1. C++11简介
2. 统一的列表初始化
2.1 {}初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[]={1,2,3,4,5};
int array2[10]={1};
Point p={1,2};
int *arr=new int[7];
cout<<endl;
delete[] arr;
system("pause");
return 0;
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Point
{
int _x;
int _y;
};
int main()
{
// int array1[]={1,2,3,4,5};
// int array2[10]={1};
// Point p={1,2};
// int *arr=new int[8];
// for(int i=0;i<8;i++){
// arr[i]=i+1;
// }
// for(int i=0;i<8;i++){
// cout<<arr[i]<<endl;
// }
int x1=1;
int x2{2};
int array1[]{1,2,3,4,5};
int array2[5]{0};
Point p{1,2};
vector<int> v=vector<int> (10,0);
vector<int> v2(10,0);
int *arr=new int[10]{0};
cout<<endl;
delete[] arr;
system("pause");
return 0;
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
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(2023,1,1);
Date d2{2023,1,2};
Date d3{2023,1,3};
system("pause");
return 0;
}
2.2 std::initializer_list
std::initializer_list的介绍文档:
http://www.cplusplus.com/reference/initializer_list/initializer_list/ std::initializer_list是什么类型:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
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;
};
void test02(){
auto il={10,20,30};
cout<<typeid(il).name()<<endl;
}
void test01(){
// Date d1(2023,1,1);
// Date d2{2023,1,2};
// Date d3{2023,1,3};
}
int main()
{
test02();
system("pause");
return 0;
}
运行结果
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值。
#include<iostream>
#include<vector>
#include<list>
#include<algorithm>
using namespace std;
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;
};
void test02(){
auto il={10,20,30};
cout<<typeid(il).name()<<endl;
}
void test01(){
// Date d1(2023,1,1);
// Date d2{2023,1,2};
// Date d3{2023,1,3};
}
void test03(){
vector<int> v{1,2,3,4};
vector<int> v1={1,2,3,4};
list<int> lt={1,2};
list<int> lt2{1,2};
cout<<endl;
}
int main()
{
// test02();
test03();
system("pause");
return 0;
}
让模拟实现的vector也支持{}初始化和赋值
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
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;
};
void test02()
{
auto il = {10, 20, 30};
cout << typeid(il).name() << endl;
}
void test01()
{
// Date d1(2023,1,1);
// Date d2{2023,1,2};
// Date d3{2023,1,3};
}
void test03()
{
vector<int> v{1, 2, 3, 4};
vector<int> v1 = {1, 2, 3, 4};
list<int> lt = {1, 2};
list<int> lt2{1, 2};
cout << endl;
}
namespace myvector
{
template <class T>
class vector
{
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
}
vector<T> & operator=(initializer_list<T> l){
vector<T> _tmp(l);
swap(_start,_tmp._start);
swap(_finish,_tmp._finish);
swap(_endofstorage,_tmp.endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
void test04(){
myvector::vector<int> v{1,2,3,4};
cout<<endl;
}
int main()
{
// test02();
// test03();
test04();
vector<int> v(2,0);
system("pause");
return 0;
}
3. 声明
c++11提供了多种简化声明的方式,尤其是在使用模板时。
3.1 auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将
其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include<map>
// #include<string>
#include<cstring>
using namespace std;
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;
};
void test02()
{
auto il = {10, 20, 30};
cout << typeid(il).name() << endl;
}
void test01()
{
// Date d1(2023,1,1);
// Date d2{2023,1,2};
// Date d3{2023,1,3};
}
void test03()
{
vector<int> v{1, 2, 3, 4};
vector<int> v1 = {1, 2, 3, 4};
list<int> lt = {1, 2};
list<int> lt2{1, 2};
cout << endl;
}
namespace myvector
{
template <class T>
class vector
{
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
}
vector<T> & operator=(initializer_list<T> l){
vector<T> _tmp(l);
swap(_start,_tmp._start);
swap(_finish,_tmp._finish);
swap(_endofstorage,_tmp.endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
void test04(){
myvector::vector<int> v{1,2,3,4};
cout<<endl;
}
void test05(){
int i=10;
auto p=&i;
auto pf=strcpy;
cout<<typeid(p).name()<<endl;
cout<<typeid(pf).name()<<endl;
map<string,string> dict={{"sort","排序"},{"insert","插入"}};
auto it=dict.begin();
}
int main()
{
// test02();
// test03();
// test04();
// vector<int> v(2,0);
test05();
system("pause");
return 0;
}
运行结果
3.2 decltype
关键字decltype将变量的类型声明为表达式指定的类型。
解释(test06中):这里 x*y 结果是double 类型,那么声明的ret 变量的类型就double 类型的。
&x 类型是一个指针,那么p的类型就是一个指针F(1,'a') 类型是一int 类型,结果就int 类型的。
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <map>
// #include<string>
#include <cstring>
using namespace std;
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;
};
void test02()
{
auto il = {10, 20, 30};
cout << typeid(il).name() << endl;
}
void test01()
{
// Date d1(2023,1,1);
// Date d2{2023,1,2};
// Date d3{2023,1,3};
}
void test03()
{
vector<int> v{1, 2, 3, 4};
vector<int> v1 = {1, 2, 3, 4};
list<int> lt = {1, 2};
list<int> lt2{1, 2};
cout << endl;
}
namespace myvector
{
template <class T>
class vector
{
public:
typedef T *iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
}
vector<T> &operator=(initializer_list<T> l)
{
vector<T> _tmp(l);
swap(_start, _tmp._start);
swap(_finish, _tmp._finish);
swap(_endofstorage, _tmp.endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
void test04()
{
myvector::vector<int> v{1, 2, 3, 4};
cout << endl;
}
void test05()
{
int i = 10;
auto p = &i;
auto pf = strcpy;
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
map<string, string> dict = {{"sort", "排序"}, {"insert", "插入"}};
auto it = dict.begin();
}
template <class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
void test06()
{
const int x=1;
double y=2.2;
decltype (x*y) ret;//double
decltype(&x) p;//int *
cout<<typeid(ret).name()<<endl;
cout<<typeid(p).name()<<endl;
F(1,'a');
}
int main()
{
// test02();
// test03();
// test04();
// vector<int> v(2,0);
// test05();
test06();
system("pause");
return 0;
}
3.3 nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
3.4 constexpr
constexpr
是C++11引入的一个关键字,用于声明一个常量表达式。它的作用是告诉编译器,可以在编译时求值并确定结果的表达式可以被视为一个编译时常量。
constexpr
可以用于以下情况:
-
声明常量:使用
constexpr
可以将一个变量声明为编译时常量。这意味着该变量的值在编译期间就能够确定,并且可以被用于编译期间的计算。constexpr int MAX_COUNT = 100;
-
函数求值为常量表达式:使用
constexpr
关键字标记的函数,如果其参数和返回值都是可在编译期间确定的,那么该函数就可以在编译期间被求值,结果可以作为编译时常量使用。constexpr int square(int x) { return x * x; }
在这种情况下,函数
square()
可以用于编译时常量的求值,例如constexpr int result = square(5)
使用 constexpr
可以提供编译期间的优化和性能提升,以及更好的表达意图。它使得程序员可以在编译期间进行一些复杂的计算,而不需要在运行时进行,从而提高了程序的效率。
需要注意的是,constexpr
的使用有一些限制。例如,它要求表达式能够在编译期间求值,不能包含运行时只能确定的操作,如输入输出操作、动态内存分配等。在C++14及以后的标准中,对于 constexpr
的限制有所放宽,更多的情况下可以使用 constexpr
修饰函数和变量。
4 范围for循环
C++11 引入了范围 for 循环,它可以方便地遍历一个容器或者数组中的元素。范围 for 循环的语法如下:
for (auto& element : container) {
// do something with element
}
其中,element 是容器中的元素,container 是一个容器或者数组。在循环的过程中,element 会依次取到 container 中的每个元素。
下面是一个使用范围 for 循环遍历 vector 的例子:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto& x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
在上面的代码中,使用范围 for 循环遍历了一个 vector,输出了 vector 中的每个元素。
范围 for 循环也可以用于遍历数组,例如:
c++复制代码
#include <iostream>
int main() {
int a[] = {1, 2, 3, 4, 5};
for (auto& x : a) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
在上面的代码中,使用范围 for 循环遍历了一个数组,输出了数组中的每个元素。
5 智能指针:
参考这一部分:【c++复习笔记】——智能指针详细解析(智能指针的使用,原理分析)_c++ 智能指针_努力学习的少年的博客-CSDN博客
6 STL新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和
unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。
容器中的一些新方法
如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得
比较少的。
比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是
可以返回const迭代器的,这些都是属于锦上添花的操作。
实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:
http://www.cplusplus.com/reference/vector/vector/emplace_back/
http://www.cplusplus.com/reference/vector/vector/push_back/
http://www.cplusplus.com/reference/map/map/insert/
http://www.cplusplus.com/reference/map/map/emplace/
但是这些接口到底意义在哪?网上都说他们能提高效率,他们是如何提高效率的?
请看下面的右值引用和移动语义章节的讲解。另外emplace还涉及模板的可变参数,也需要再继
续深入学习后面章节的知识。
7 右值引用和移动语义
7.1 左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。什么是左值?什么是左值引用?左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout<<endl;
}
int main()
{
test01();
system("pause");
return 0;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。右值指的是临时的、即将销毁的表达式或对象。简而言之,右值是不能出现在赋值运算符左侧的表达式或对象。
右值具有以下特点:
-
右值没有名称和地址:它们是临时生成的,没有持久的内存位置。
-
右值不能被修改:由于右值即将销毁,因此不能对其进行修改。
-
右值不能用作左值:在需要左值的地方,不能将右值用作左值。
以下是一些常见的右值示例:
-
字面量:例如,
int x = 10;
中的10
是一个右值,因为它是一个临时的、即将销毁的值。 -
临时对象:例如,通过调用函数返回的临时对象是右值。例如,
std::string("hello")
返回一个临时的std::string
对象,它是一个右值。 -
表达式的结果:例如,
x + y
中的x + y
是一个右值,因为它是一个临时的、即将销毁的结果。 -
移动语义生成的对象:通过移动语义生成的对象也是右值。例如,
std::move(x)
返回一个右值引用,它表示x
将要被移动。
右值在C++中用于支持移动语义、完美转发和临时对象的创建和销毁。它们在现代C++中扮演着重要的角色,可以提高代码的性能和效率。
#include <iostream>
#include <vector>
#include<math.h>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout << endl;
}
void test02()
{
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 = l;
// x + y = 1;
// fmin(x, y) = 1;
}
int main()
{
// test01();
test02();
system("pause");
return 0;
}
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
#include <iostream>
#include <vector>
#include<math.h>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout << endl;
}
void test02()
{
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 = l;
// x + y = 1;
// fmin(x, y) = 1;
}
void test03(){
int && r1=10;
int a=10;
//优质引用智能优质,不能引用左值
// int && r2=a;
//右值引用可以引用move() 以后的左值
int && r3=std::move(a);
}
int main()
{
// test01();
// test02();
test03();
system("pause");
return 0;
}
7.3 右值引用使用场景和意义
前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!
#include <iostream>
#include <vector>
#include <math.h>
#include<assert.h>
#include<string.h>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout << endl;
}
void test02()
{
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 = l;
// x + y = 1;
// fmin(x, y) = 1;
}
void test03()
{
int &&r1 = 10;
int a = 10;
//优质引用智能优质,不能引用左值
// int && r2=a;
//右值引用可以引用move() 以后的左值
int &&r3 = std::move(a);
}
namespace myspace
{
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);
}
// 左值引用的使用场景:
// 做参数和做返回值都可以提高效率。 左值引用的短板:
// 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
// 只能传值返回。例如:bit::string
// to_string(int value) 函数中可以看到,这里只能使用传值返回,
// 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。 _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
};
}
//左值引用的场景
void func1(myspace::string s){}
void func2(const myspace::string &s){
}
void test04(){
myspace::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1+='!';
}
int main()
{
// int x=10;
// assert (x==20);
// cout<<"hello"<<endl;
// test01();
// test02();
// test03();
test04();
system("pause");
return 0;
}
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)
#include <iostream>
#include <vector>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout << endl;
}
void test02()
{
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 = l;
// x + y = 1;
// fmin(x, y) = 1;
}
void test03()
{
int &&r1 = 10;
int a = 10;
//优质引用智能优质,不能引用左值
// int && r2=a;
//右值引用可以引用move() 以后的左值
int &&r3 = std::move(a);
}
namespace myspace
{
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);
}
// 左值引用的使用场景:
// 做参数和做返回值都可以提高效率。 左值引用的短板:
// 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
// 只能传值返回。例如:myspace::string
// to_string(int value) 函数中可以看到,这里只能使用传值返回,
// 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。 _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
};
}
//左值引用的场景
void func1(myspace::string s) {}
void func2(const myspace::string &s)
{
}
void test04()
{
myspace::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1 += '!';
}
namespace myspace
{
myspace::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
myspace::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
// 右值引用和移动语义解决上述问题:
// 在myspace::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
// 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。 std::reverse(str.begin(), str.end());
return str;
}
}
int main()
{
// 在myspace::string to_string(int value)函数中可以看到,这里
// 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷
贝构造)。
myspace::string ret1 = myspace::to_string(1234);
myspace::string ret2 = myspace::to_string(-1234);
return 0;
}
// 移动构造
string(string &&s)
int main()
{
// int x=10;
// assert (x==20);
// cout<<"hello"<<endl;
// test01();
// test02();
// test03();
test04();
system("pause");
return 0;
}
右值引用和移动语义解决上述问题:在myspace::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
7.4 右值引用引用左值及其一些更深入的使用场景分析
按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
#include <iostream>
#include <vector>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <algorithm>
using namespace std;
void test01()
{
//这里都是左值
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;
cout << endl;
}
void test02()
{
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 = l;
// x + y = 1;
// fmin(x, y) = 1;
}
void test03()
{
int &&r1 = 10;
int a = 10;
//优质引用智能优质,不能引用左值
// int && r2=a;
//右值引用可以引用move() 以后的左值
int &&r3 = std::move(a);
}
namespace myspace
{
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);
}
// 左值引用的使用场景:
// 做参数和做返回值都可以提高效率。 左值引用的短板:
// 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
// 只能传值返回。例如:myspace::string
// to_string(int value) 函数中可以看到,这里只能使用传值返回,
// 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。 _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
};
}
//左值引用的场景
void func1(myspace::string s) {}
void func2(const myspace::string &s)
{
}
void test04()
{
myspace::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1 += '!';
}
namespace myspace
{
myspace::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
myspace::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
// 右值引用和移动语义解决上述问题:
// 在myspace::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
// 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。 std::reverse(str.begin(), str.end());
return str;
}
}
// int main()
// {
// // 在myspace::string to_string(int value)函数中可以看到,这里
// // 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷
// // 贝构造)。
// myspace::string ret1 = myspace::to_string(1234);
// myspace::string ret2 = myspace::to_string(-1234);
// return 0;
// }
// 移动构造
// string(string &&s)
template <class _Ty>
inline typename remove_reference<_Ty>::type &&move(_Ty &&_Arg)
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type &&) _Arg);
}
void test05(){
myspace::string s1("hello world");
// 这里s1是左值,调用的是拷贝构造
myspace::string s2(s1);
// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
// 资源被转移给了s3,s1被置空了。
myspace::string s3(std::move(s1));
}
int main()
{
// int x=10;
// assert (x==20);
// cout<<"hello"<<endl;
// test01();
// test02();
// test03();
// test04();
test05();
system("pause");
return 0;
}
7.5 完美转发
模板中的&& 万能引用
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,ForwardPerfect(T&&
t) 就是如此的体现。
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void Fun(int &x){
cout<<"left 引用"<<endl;
}
void Fun(const int & x){
cout<<"const left 引用"<<endl;
}
void Fun(int &&x){
cout<<"right引用"<<endl;
}
void Fun(const int && x){
cout<<"const right 引用"<<endl;
}
template<typename T>
void PerfectForward(T &&t){
Fun(t);
}
void test01(){
PerfectForward(10);//右值引用
int a;
PerfectForward(a);//左值
PerfectForward(std::move(a));//右值
const int b=8;
PerfectForward(b);//const 左值
PerfectForward(std::move(b));//const 右值
}
int main()
{
test01();
system("pause");
return 0;
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void Fun(int &x){
cout<<"left 引用"<<endl;
}
void Fun(const int & x){
cout<<"const left 引用"<<endl;
}
void Fun(int &&x){
cout<<"right引用"<<endl;
}
void Fun(const int && x){
cout<<"const right 引用"<<endl;
}
template<typename T>
// void PerfectForward(T &&t){
// Fun(t);
// }
void PerfectForward(T && t){
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
Fun(std::forward<T> (t));
}
void test01(){
//这里使用的是没有forward<T> 的情况
PerfectForward(10);//右值引用
int a;
PerfectForward(a);//左值
PerfectForward(std::move(a));//右值
const int b=8;
PerfectForward(b);//const 左值
PerfectForward(std::move(b));//const 右值
}
//要么手动实现里面是多有情况,要么使用forward() 函数
void test02(){
//有forward<T>
PerfectForward(10);//右值引用
int a;
PerfectForward(a);//左值
PerfectForward(std::move(a));//右值
const int b=8;
PerfectForward(b);//const 左值
PerfectForward(std::move(b));//const 右值
}
int main()
{
// test01();
test02();
system("pause");
return 0;
}
完美转发实际中的使用场景:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//完美转发的使用场景
template<class T>
struct ListNode
{
public:
ListNode *_next = nullptr;
ListNode * _prev= nullptr;
T _data;
};
template <class T>
class List
{
typedef ListNode<T> Node;
public:
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
void Insert(Node *pos, T &&x)
{
Node *prev = pos->_prev;
Node *newnode = new Node;
newnode->_data = std::forward<T>(x);
prev->_next = newnode;
newnode->_prev = newnode;
newnode->_next = pos;
pos->_prev = newnode;
}
void Insert(Node *pos, const T &x)
{
Node *prev = pos->_prev;
Node *newnode = new Node;
newnode->_data = x;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
void PushFront(T &&x)
{
// Insert(_head->_next, x);
Insert(_head->_next, std::forward<T>(x));
}
void PushBack(T &&x)
{
Insert(_head, std::forward<T>(x));
}
private:
Node *_head;
};
int main()
{
List<string> l;
l.PushBack("1111");
l.PushFront("2222");
cout<<endl;
system("pause");
return 0;
}