C++11学习笔记

c++方法后面加const

const CString &GetCurrentDir() const; //得到当前程序目录

前面一个const表明返回值不能修改,后一个const表明方法只能读取类的成员变量,不能给成员变量赋值。

C++ 智能指针 unique_ptr

unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
查看下面的示例:

#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout << "Task::Constructor" << std::endl;
    }
    ~Task() {
        std::cout << "Task::Destructor" << std::endl;
    }
};

int main()
{
    // 通过原始指针创建 unique_ptr 实例
    std::unique_ptr<Task> taskPtr(new Task(23));

    //通过 unique_ptr 访问其成员
    int id = taskPtr->mId;
    std::cout << id << std::endl;
    
    return 0;

}

输出:

Task::Constructor
23
Task::Destructor

✈️ unique_ptr 对象 taskPtr 接受原始指针作为参数。现在当main函数退出时,该对象超出作用范围就会调用其析构函数,在unique_ptr对象taskPtr 的析构函数中,会删除关联的原始指针,这样就不用专门delete Task对象了。
这样不管函数正常退出还是异常退出(由于某些异常),也会始终调用taskPtr的析构函数。因此,原始指针将始终被删除并防止内存泄漏。

  • 使用原始指针创建 unique_ptr 对象

要创建非空的 unique_ptr 对象,需要在创建对象时在其构造函数中传递原始指针,即:

std::unique_ptr<Task> taskPtr(new Task(22));
  • 使用 std::make_unique 创建 unique_ptr 对象 / C++14
    std::make_unique<>() 是C++ 14 引入的新函数
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
  • 获取被管理对象的指针
    使用get()·函数获取管理对象的指针。
Task *p1 = taskPtr.get();
  • 重置 unique_ptr 对象
    在 unique_ptr 对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。
taskPtr.reset();

lambda表达式

​ lambda表达式是形如auto f = [ ]{ }的函数。

​ lambda可指定其捕获列表的类型,[&]表示捕获列表采用隐式引用捕获方式lambda函数体中所使用的来自所在函数的实体都采用引用方式使用,[=]表示采用值捕获方式。

/*f1的sz是隐式值捕获,f2的sz是隐式引用捕获  
auto f1 = [=](const string &s)  
      { return s.size() >=sz; }  
auto f2 = [&](const string &s)  
      { return s.size() >=sz; }  

C++模板之typename和class关键字的区别

  1. template与template一般情况下这两个通用,但有一个特例,就是当 T 是一个类,而这个类又有子类(假设名为 innerClass) 时,应该用 template:
  2. typename T::innerClass myInnerObject;这里的 typename 告诉编译器,T::innerClass 是一个类,程序要声明一个 T::innerClass 类的对象,而不是声明 T 的静态成员,而 typename 如果换成 class 则语法错误。
template <class T>
class MyClass{
    typename T::SubType * ptr;
    ...
};

字面量

  • R"xxxx(djdfljaljlfajl)xxxx"
  • xxxx 左右必须一致
int main()
{
    std::string filePath = R"hell2o(C:\hello\world\test.txt)hell2o";
    std::string str2 = R"(<html>
        <title>
        Test 字面量
        </title>
        </html>)";
    std::cout << filePath<<std::endl;
    std::cout << str2 << std::endl;
    std::cout << "Hello World!\n";
}

输出

C:\hello\world\test.txt                                                               <html>                                                                               		<title>                                                                               Test 字面量                                                                           </title>                                                                             </html>                                                                         
Hello World! 

指针空值类型

cp:null

  • NULL 的定义:
#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

可以看到在C++中是 0, C中是(void *)0

在C++中,不允许void* 隐式转换成其他

  • NULL
void func(char* p)
{
    cout << "void func(char *p)" << endl;
}

void func(int p)
{
    cout << "void func(int p)" << endl;
}

func(10);
func(NULL);

输出

void func(int p)                                                                     void func(int p) 
  • nullptr
func(nullptr);

nullptr 可是 int* char* …等等

出于兼容性的考虑,C++11 标准并没有对 NULL 的宏定义做任何修改,而是另其炉灶,引入了一个新的关键字 nullptrnullptr 专用于初始化空类型指针,不同类型的指针变量都可以使用 nullptr 来初始化

int*    ptr1 = nullptr;
char*   ptr2 = nullptr;
double* ptr3 = nullptr;

常量 constexpr

看下面的error了解定义

void func(const int num)
{
    const int count = 24;
    int array[num]; // error,num是一个只读变量,不是常量
    int array1[count];         // ok,count是一个常量

    int a1 = 520;
    int a2 = 250;
    const int& b = a1;
    b = a2; // error
    a1 = 1314;
    cout << "b: " << b << endl;     // 输出结果为1314
}

定义

const int m = f();  // 不是常量表达式,m的值只有在运行时才会获取。
const int i=520;    // 是一个常量表达式
const int j=i+1;    // 是一个常量表达式

constexpr int i=520;    // 是一个常量表达式
constexpr int j=i+1;    // 是一个常量表达式

常量表达式函数

为了提高 C++ 程序的执行效率,我们可以将程序中值不需要发生变化的变量定义为常量,也可以使用 constexpr 修饰函数的返回值,这种函数被称作常量表达式函数,这些函数主要包括以下几种:普通函数/类成员函数类的构造函数模板函数

auto 自动推导类型

  • 无const
int temp = 100;
//int*
auto* a = &temp;
//int *
auto b = &temp;
//int  c是引用
auto& c = temp;
//int
auto d = temp;
  • 有const
    • 当变量不是指针或者引用类型时,推导的结果中不会保留 const、volatile 关键字
    • 当变量是指针或者引用类型时,推导的结果中会保留 const、volatile 关键字
int tmp = 250;
//const int
const auto a1 = temp;
//int  因为a2不是指针 不是引用  所以const不会被保留
auto a2 = a1;
//const int&
const auto& a3 = tmp;
//const int&
auto& a4 = a3;
//const int*  因为指针所以保留 const
//const 其实是锁定 指针指向的地址的值
auto* pt3 = &a1;
  • auto限制

    • 不能作为函数参数使用。

    • int func(auto a, auto b)	// error
      {	
          cout << "a: " << a <<", b: " << b << endl;
      }
      
    • 不能用于类的非静态成员变量的初始化

    • class Test
      {
          auto v1 = 0;                    // error
          static auto v2 = 0;             // error,类的静态非常量成员不允许在类内部直接初始化
          static const auto v3 = 10;      // ok
      }
      
    • 不能使用 auto 关键字定义数组

    • 无法使用 auto 推导出模板参数

  • 使用auto 遍历

std::map<int, std::string> mp;
mp.insert(std::make_pair(1, "ace"));
mp.insert(std::make_pair(2, "bco"));
mp.insert(std::make_pair(3, "bco3"));

//std::map<int, std::string>::iterator it = mp.begin();
auto it = mp.begin();
for (; it != mp.end(); it++) {
    std::cout << "key " << it->first << " value " << it->second << std::endl;
}
  • 使用auto 和模板类配合
class T1
{
public:
	static int get()
	{
		return 0;
	}
};

class T2
{
public:
	static std::string get()
	{
		return "hello, world";
	}
};


template<class T>
void func() {
	auto val = T::get();   //ask T:: ??
	std::cout << "value:" << val << std::endl;
}

调用:

func<T1>();
func<T2>();

返回值 后置

template<typename T1,typename T2>
auto Add(T1 a, T2 b) -> decltype(a+b)
{
	return a + b + b;
}

调用:

int a = 1;
auto b = 1.4332;
auto  ret1 = Add(a, b);

final 用法

C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写,和 Jave 的 final 关键字的功能是类似的。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面。

  • final修饰函数
class Base {
public:
	//定义virtual 让后面去重写
	virtual void Test() {
		std::cout << "base test....";
	}
};

class Child :public Base {
public:
	//修饰final后 后面的类不能继承
	void Test() final{
		std::cout << "child test/....";
	}
};

class ChildChild :public Child {
public:
	void Test() {  //error,这里会报错,被final修饰了。..

	}
};
  • final修饰类

    class Child final: public Base

override

class ChildChild :public Child {
public:
	void test() override {
		std::cout << "child test/....";
	}
};

对模板 右括号 自动优化

原来写 > > 现在写 >

函数模板的默认参数

代码:

template<typename U=long,typename V=int>
void mytest(U u = 'a', V v = 'b') {
	std::cout << u << " " << v << std::endl;
}

输出:

mytest();
mytest<char>();
mytest<char>('c');
mytest<char, int>('c');

97 98                                                                                     a 98                                                                                      c 98                                                                                     c 98                                                                                    

Using

typedef

using

都是 定义别名 不是创建新的

  • 简单的别名
typedef int t1;
t1 a;
t1 b;

using t2 = int;
t2 a1;
t2 a2;
  • 函数别名

我自己写的Linq

template <typename T>
using linqCmp = bool(*)(T);   // bool(*linqCmp)(T)

template <typename T>
std::unique_ptr<std::vector<T>> linqTest(std::vector<T>* vec, linqCmp<T> cmp) {  //linqCmp<T> linqCmp
	std::unique_ptr<std::vector<T>> vecPtr(new std::vector<T>());
	typename std::vector<T>::iterator it = vec->begin();
	typename std::vector<T>::iterator end = vec->end();
	while (it != end)
	{
		if (cmp(*it)) {
			vecPtr->emplace_back(*it);
		}
		it++;
	}
	return vecPtr;
}



int main()
{
	//c++ linq small实现
	int arr[] = { 1,2,5,7,88,8,3,4 };
	std::vector<int> vArr(arr, arr + (sizeof(arr) / sizeof(arr[0])));
	//auto lst = linqTest(vArr, myCmp);
	vArr = *(linqTest<int>(&vArr, [](int x) { return  x > 5 && x <10 ; })).get();
	std::cout << "start"<<std::endl;
	for (auto item : vArr) {
		std::cout << item<<std::endl;
	}
	std::cout << "end" << std::endl;
}

:: 运算符

:: 是运算符中等级最高的,它分为三种:

​ 1**)global scope(全局作用域符**),用法(::name)

::a

​ 2)class scope(类作用域符),用法(class::name)

class ww
{
  public:
    int a();
}
int ww::a()//表示a是属于ww的
{
 return 0;
}

​ 3)namespace scope(命名空间作用域符),用法(namespace::name) 他们都是左关联(left-associativity) 他们的作用都是为了更明确的调用你想要的变量,

std::cout

如在程序中的某一处你想调用全局变量a,那么就写成::a,

如果想调用class A中的成员变量a,那么就写成A::a,

另外一个如果想调用namespace std中的cout成员,你就写成std::cout(相当于using namespace std;cout)意思是在这里我想用cout对象是命名空间std中的cout(即就是标准库里边的cout)

模板的别名

  • typedef
template<typename T>
struct MyMap {
	typedef std::map<int, T> mapType; 
};



template<typename T>
class Container {
public:
	static void print(T& t) {
		auto it = t.begin();
		for (; it != t.end(); it++) {
			std::cout << it->first << "," << it->second << std::endl;
		}
	}
};

调用:

MyMap<std::string>::mapType m1;
m1.insert(std:: make_pair(1, "1"));
m1.insert(std::make_pair(2, "2"));
m1.insert(std::make_pair(3, "3"));

Container<MyMap<std::string>::mapType>::print(m1);
  • using
MMP2<std::string> m2;
m2.insert(std::make_pair(1, "23"));

委托构造函数

  • 主语不要链式调用成一个环了
class MyTest {
public:
	
	MyTest(int max) {
		this->m_max = max > 0 ? max : 100;
	}
	MyTest(int max,int min):MyTest(max) {
		this->m_min = min > 0 && min < max ? min : 1;
	}
	MyTest(int max, int min, int mid):MyTest(max,min) {
		this->m_middle = mid < max&& mid > min ? mid : 50;
	}

private:
	int m_max;
	int m_min;
	int m_middle;
};

继承构造函数

C++11 中提供的继承构造函数可以让派生类直接使用基类的构造函数,而无需自己再写构造函数,尤其是在基类有很多构造函数的情况下,可以极大地简化派生类构造函数的编写。先来看没有继承构造函数之前的处理方式:

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};

class Child : public Base
{
public:
    using Base::Base;
};

调用:

Child c1(520, 13.14);

c++类的构造函数两种初始化成员方式

方式一: 赋值初始化

CSomeClass::CSomeClass() 
{ 
  x=0; 
  y=1; 
} 

🚡 对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。

方式二: 列表初始化

CSomeClass::CSomeClass() : x(0), y(1) 
{ 
} 

🚡 列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。

推荐:第二种,效率高

初始化列表

  • 普通使用:
class Person {
public:
	Person(int num) :age(num) {}
private:
	int age;

};

使用:

Person c = { 1.3232 };  //error 因为没有double的构造函数
Person c2 { 1 };
int aa = { 12 };
int aa2{ 12323 };
int aar[]{ 1,2,3,4,5 };

int* p = new int{ 520 };
double b = double{ 38.232 };
int* array = new int[] {23323, 43242, 3424};
  • 聚合体

    • 普通数组本身可以看做是一个聚合类型
    • 无用户自定义的构造函数。
    • 无私有或保护的非静态数据成员。
    • 无基类。
    • 无虚函数。
    • 类中不能有使用 {}= 直接初始化的非静态数据成员(从 c++14 开始就支持了)。

    结构体中的静态变量 z 不能使用列表初始化进行初始化,它的初始化遵循静态成员的初始化方式。

    struct T2
    {
        int x;
        long y;
    protected:
        static int z;
    }t{ 1, 100};		// ok
    // 静态成员的初始化
    int T2::z = 2;
    
    • 非聚合类初始化

      ✈️ 用到构造函数。

Lambda

lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式简单归纳如下:

[capture](params) opt -> ret {body;};
  • capture

    • []- 不捕捉任何变量
    • [&]- 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
    • [=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用
      • 拷贝的副本在匿名函数体内部是只读的
    • [=, &foo] - 按捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
    • [bar] - 按值捕获 bar 变量,同时不捕获其他变量
    • [&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
    • [this] - 捕获当前类中的 this 指针
  • params

    • auto f = [](){}
    • 也可省略 auto f = []{}
  • opt:

    opt 选项, 不需要可以省略

    • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw ();

使用举例:

capture this 捕获当前this指针中的值

class MyLambda {
public :
	void func(int x, int y) {
		auto f = [this]() {
			return num;
		};
	}
private :
	int num;
};

**修改外部变量 mutable **

class MyLambda {
public :
	void func(int x, int y)  {
		[=](int z) mutable {		
			x = x + 4343;
			std::cout << "x:" << x << std::endl;
		}(34);
		std::cout << "x:" << x << std::endl;

	}
private :
	int num;
};

调用:
x:4344                                                                                   x:1                                                                                                               

所以可以看到 当用 = 时,lambda里面只是复制变量,改变值后,外部的值不改变。

返回类型的自动推导

auto f1 = [](int x)  {
	return x;
};
//错误示范
auto f2 = [](){
    return {1,2}   //错的 好像要改  -> int[]  但是不起效果
}

函数本质

为什么通过值拷贝的方式捕获的外部变量是只读的:

  1. lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
  2. 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。

mutable 选项的作用就在于取消 operator () 的 const 属性。

//没有捕获外部数据的  可以相当于 函数指针
auto ff1 = [](int x) {return x; };


//仿函数  就用可以使用std::function和std::bind来存储和操作lambda表达式:
std::function<int(int)> f1 = [](int a) {return a; };
std::function<int(int)>  f2 = bind([](int a) {return a; }, std::placeholders::_1);

左值、右值&&

左值:能取地址

右值:不能取地址

  • 初始化
//左值
int num = 9;
//左值引用
int& a = num;
//右值
//右值引用
int&& b = 8;


//常量右值引用
const int&& d = 6;


//常量左值引用
const int& c = num;
const int& f = b;
const int& g = d;
  • 作用: 通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量将会一直存活下去

** 补充概念 拷贝构造函数

class Line
{
public:
	Line(int len);             // 简单的构造函数
	Line(const Line& obj);      // 拷贝构造函数


private:
	int* ptr;
};

Line::Line(int len) {
	ptr = new int;
	*ptr = len;
}

Line::Line(const Line& obj) {
	ptr = new int;
	*ptr = *obj.ptr; //拷贝值
}

移动构造函数

解释:移动构造函数只有在 有临时变量的时候(右侧是临时变量) 才会被调用

不然就到 const的拷贝构造函数

做法:让当前对象的指针 指向原来的 原来的=nullptr

class Line
{
public:
	Line() :Line(1) {
		std::cout << "Line(1)" << std::endl;
	};
	Line(int len);             // 简单的构造函数
	// 拷贝构造函数
	Line(const Line& obj);      
	~Line() { 
		std::cout << "destruct" << std::endl;
	};
	//用于临时对象
	//移动构造函数  -》 服用其他对象中的资源(堆内存)
	Line(Line&& obj):ptr(obj.ptr) {  //ptr 浅拷贝
		//让原来的资源 不再享有这块空间
		obj.ptr = nullptr;
		std::cout << "move";
	}


private:
	//int * p = new int[3]; //申请一个动态整型数组,数组的长度为[]中的值
	//int *p = new int(10); // p指向一个值为10的int数。
	int* ptr;
};

Line::Line(int len) {
	ptr = new int;
	*ptr = len;
}
//拷贝值
Line::Line(const Line& obj):ptr(new int(*obj.ptr)) {
	std::cout << "copy construct" << std::endl;
}

Line GetLine() {
	Line t;
	return t;
}

输出:

Line c = GetLine();

Line(1)
movedestruct
destruct		

ask:不理解 为为什么 析构函数只被调用一次

输出:

Line c = GetLine();
Line d = c;

Line(1)
movedestruct
copy construct
destruct
destruct

类似解释:通过修改,在上面的代码给 Test 类添加了移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj(); 操作的时候并没有调用拷贝构造函数进行深拷贝,而是调用了移动构造函数,在这个函数中只是进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。

如果不使用移动构造,在执行 Test t = getObj() 的时候也是进行了浅拷贝,但是当临时对象被析构的时候,类成员指针 int* m_num; 指向的内存也就被析构了对象 t 也就无法访问这块内存地址了

如果没有移动构造函数,使用右值引用的要求更高些,

要求右侧是一个临时的不能取地址的对象

Line GetLine2() {
	return  Line();
}
Line&& GetLine3(){
    return Line();
}

Line&& f = GetLine2();

👿👼

**重点:**C++11 中右值可以分为两种:一个是将亡值( xvalue, expiring value),另一个则是纯右值( prvalue, PureRvalue):

  • 纯右值非引用返回的临时变量、运算表达式产生的临时变量原始字面量和 lambda 表达式
  • 将亡值:与右值引用相关的表达式,比如,T&& 类型函数的返回值、 std::move 的返回值等

变换

int x = 520, y = 1314;
//因为x是左值 所以v1是左值引用
auto&& v1 = x;
//250 右值  v2是右值引用
auto&& v2 = 250;

结论:左值和右值是独立于他们的类型的,右值引用类型可能是左值也可能是右值。

右值引用传递

#include <iostream>
using namespace std;

void printValue(int &i)
{
    cout << "l-value: " << i << endl;
}

void printValue(int &&i)
{
    cout << "r-value: " << i << endl;
}

void forward(int &&k)
{
    printValue(k);
}

int main()
{
    int i = 520;
    printValue(i);
    printValue(1314);
    forward(250);

    return 0;
};

输出:

l-value: 520                                                                             r-value: 1314
l-value: 250 

▶️ 解释 forward(250),为什么最后是左值:

​ 先进 forward的时候 k是右值引用 但是当调用printValue(k);时,可以理解为k被 **[具名化]**了,那么就变成左值了!

转移 和 完美转发

move

在这之前先考虑下 到底为什么要用到右值???

网上的解释如下:

右值引用与左值引用

int main()
{
	int&& rRef = 8;
	int& lRef = 7;//报错,普通引用不能引用右值
	const int& lRef2 = 9; //const左值引用既能够引用左值也能够引用右值
}

从上面的代码能够看到,普通的左值引用只能引用左值,而const的左值引用既能引用左值,又能引用右值。那么为何C++11还须要引入右值引用的概念呢?这就涉及到了移动语义的问题。.net


移动语义

移动语义:将一个对象中的资源移动到另外一个对象中,一般用来移动一个将亡值。3d

这里就拿STL中的string来举例子。
C++ STL : 模拟实现STL中的string类c++11

移动语义最经常使用的地方就是拷贝构造和赋值运算符重载。code

例如string中的拷贝构造函数

string(const string& s)
	: _size(s._size)
	, _capacity(s._capacity)
	, _str(new char[_size + 1])
{
	strcpy(_str, s._str);
}

能够看到,这里首先这里实际上是很传统的写法,开空间,而后把数据拷贝过来,这样的代码对于左值来讲彻底没有什么问题,而对于右值,则多了彻底没必要要的操做,由于若是拷贝构造的对象是一个将亡值,那就表明着他的空间立刻就要销毁,这时咱们开空间拷贝一个将要销毁的数据,是效率很低的一件事,因此咱们能够不用想往常同样操做,而是直接将他的空间拿过来,这样效率就大大的提升了。

string(string&& s)
	: _size(s._size)
	, _capacity(s._capacity)
	, _str(nullptr)
{

	std::swap(_str, s._str);
}

这就是一般所说的移动构造,就是在进行拷贝构造时,若是拷贝构造的对象是一个将亡值,就直接将他的资源移动到咱们的对象中便可。

一样的,移动赋值也是这样的原理。

string& operator=(string&& s)
{
	_size = s._size;
	_capacity = s._capacity;
	std::swap(_str, s._str);

	return *this;
}

这样光说可能看不出他的具体效果在哪里,下面就举个更明显的例子。

string operator+(const string& s2)
{
	string ret(*this);

	return ret;
}
int main()
{
	lee::string s1("hello");
	lee::string s2("world");
	lee::string s3("111");
	s3 = s1 + s2;
	return 0;
}

在这里插入图片描述

在这里插入图片描述
接下来看看加入了移动构造和移动赋值后的效果
在这里插入图片描述

在这里插入图片描述
能够看到,由于这里+运算的返回值和(s1+s2)都是右值,因此这里和上面不一样的是进行了一次移动构造和移动赋值,避免了对将亡值的数据拷贝,致使效率大幅度的提高,这也就是为何c++11在有了const&的状况下还要增长右值引用的缘由,就是为了单独处理将亡值的移动问题。

总结一下就是,须要经过右值引用来划分开左值的拷贝语义和右值的移动语义。


move

按照语法来讲,右值引用应该只能引用右值,可是在不少状况下,咱们须要使用引用左值来实现移动语义,因此为了实现这个功能,c++11又加入了一个move函数。这个函数的主要做用就是将一个左值强行转换为右值(STL还有另外一个move,那个的做用就是将一个范围中的元素搬移到另外一个位置,容易搞混)
例如

int main()
{
	lee::string s1("hello");
	lee::string s2(move(s1));
}

在这里插入图片描述
须要注意的是被转化的左值,其生命周期并无随着左值的转化而改变,即std::move转化的左值变量lvalue不会被销毁。可是在其进行完移动语义后,本来的资源被转移到别的对象中,此时他的资源就会失效
例如:

int main()
{
	string s1("hello");
	cout << s1 << endl;

	string s2(move(s1));
	
	cout << s1 << endl;
	cout << s2 << endl;

	return 0;
}

在这里插入图片描述
s2将s1转为右值后进行移动构造以后,s1的资源失效。

现在重新,来开始move学习:

​ move 将资源A 转 到资源B

  • 左值转右值
Line&& dd = GetLine();
//dd 本身时右值引用  当作参数之后 好像是左值了
Line&& dd2 = std::move(dd);

Line c = GetLine();
Line&& c2 =std::move(c);
  • 转移资源 ls1不用后 转到 ls3上
std::list<std::string> ls1{
    "fdafa","fafafdeefe","degafaf"
};
std::list<std::string> ls2 = ls1;
std::list<std::string> ls3 = std::move(ls1); //会到移动构造上面

运行后:可以看到:
    ls1 为空
    ls2 size 3  这是不是可以说明 是复制了??
    ls3 size 3
那如何证明是复制了?   改改ls3看看!!
    ls3.clear();	
	当我加上上面这句话后,ls3为空了,但是ls2依然size 3 所以说明是复制了

forward

​ 后续学

共享智能指针

首先delete概念

当类型为int,float等内置类型时,new、delete、new[]、delete[]不需要配对使用,当是自定义类型时,new、delete和new[]、delete[]才需要配对使用,当然我们平时编程过程中,为了代码可读性以及为了养成编程良好习惯最好确保所有情况都配对使用。

不太记得delete的概念了:

这里做了尝试:

int a = new int(232) 错的 返回的是指针

int a ; delete a 错的 delete后面跟指针
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

SharePtr

再次加深印象:

  • 拷贝构造函数中实现了深拷贝处理。
  • 移动构造函数: 内存的处理不是拷贝而是转移。注意参数类型是右值引用。

头文件 :#include<memory>

初始化:
//构造函数
std::shared_ptr<int> ptr1(new int(1));
std::cout << "ptr1 :" << ptr1.use_count() << std::endl;

//通过移动构造和拷贝构造函数初始化
std::shared_ptr<int> ptr2 = std::move(ptr1);
std::cout << "ptr2 :" << ptr2.use_count() << std::endl;

//makre share
std::shared_ptr<int> ptr3 = std::make_shared<int>(8);
std::cout << "ptr3 :" << ptr3.use_count() << std::endl;

std::shared_ptr<MyTest> ptr4 = std::make_shared<MyTest>(12);

//reset初始化
ptr4.reset();
ptr4.reset(new MyTest(32323));

在这里插入图片描述

指定删除器
//指定删除器
std::shared_ptr<MyTest> ptr5(new MyTest(1000), [](MyTest* t) {
    std::cout << "---------------------------" << std::endl;
    delete t; 

});

//默认删除器
std::shared_ptr<MyTest> ptr6(new MyTest[5], std::default_delete<MyTest[]>());

封装一个 make_shared_array 方法来让 shared_ptr 支持数组

template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
    // 返回匿名对象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

⚗️⚗️ **注意: C++11 中shared_ptr默认调用的析构函数是default_delete(),而非default_delete<_Ty[]>,很显然,如果分配数组,当然应该使用delete[], 所以直到C++17才被支持。 **

🛰智能指针可以自己释放内存,不用我们手动释放。但是智能指针的默认释放规则是不支持释放数组的,这时,需要我们再稍加操作,就可以完美释放!!!

但是

现在新版本都支持了!!

std::shared_ptr<MyTest[]> pp(new MyTest[4343]);
std::unique_ptr<MyTest[]> pp(new MyTest[4343]);

也就是说 会自动释放数组 不然你得这样写:

std::shared_ptr<MyTest[]> pp2(new MyTest[4343],std::default_delete<MyTest[]>);
  • 学习并没有完成…待续 还有很多新特性没有学习!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值