C++11(一)

左值引用和右值引用

左值引用:左值表示数据的表达式(变量名或者解引用的指针),可以使用取地址+赋值的方式对左值进行取别名使用

1.左值引用只能引用左值,不能引用右值;

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;
}

右值引用:右值是一个表示数据的表达式,如:字面常量,表达式返回值,函数返回值(不能是左值引用返回),右值可以出现赋值符号的右边,不能出现在赋值符号的左边,右值不能取地址,右值引用就是对右值的引用,给右值取别名。

1.右值引用只能引用右值,不能引用左值。

2.右值引用可以引用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;
}

右值引用的使用场景和意义:对于编译器而言,当你调用函数进行传值返回的时候,编译器会进行一个入栈的操作,把临时变量的空间开好,放在这里,然后再进行函数的入栈,所以对于传值返回中间会产生临时变量,就会进行两次拷贝构造(新编译器进行优化后是一次拷贝构造)。但是当有了右值引用之后,我们可以使用移动构造,本质就是将参数右值的资源窃取过来,占为己有,那么就不用做深拷贝了,相当于直接窃取别人的资源构造自己。

//拷贝构造
string(const string& s)
 :_str(nullptr)
{
 cout << "string(const string& s) -- 深拷贝" << endl;
 string tmp(s._str);
 swap(tmp)
}
// 移动构造
string(string&& s)
 :_str(nullptr)
 ,_size(0)
 ,_capacity(0)
{
 cout << "string(string&& s) -- 移动语义" << endl;
 swap(s);
}
string to_string(int value)
{
    string str;
    //....
    return str; 
}
int main()
{
 string ret2 = to_string(-1234);
 return 0;
}

上述代码的to_string的返回值是一个右值,用这个二右值构造ret2,如果既有拷贝构造又有移动构造,调用就会匹配移动构造,编译器就会选择最匹配的参数调用,那么这里就是一个移动语义。

除了移动构造还有移动赋值:

// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}

当然右值引用也可以引用move后的左值。

智能指针

内存泄漏:指的是因为有一些资源没有被释放,而这一段内存无法被使用的情况下,叫做内存泄漏。

主要是C++中的内存申请忘记释放问题,主要关心以下两种方式的内存泄漏:

1.堆内存泄漏:堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏

2.系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

智能指针使用及原理

RAII:利用对象的生命周期去管理对应的内存资源的周期,也就是将内存资源和智能指针对象进行绑定,并且在绑定的时候就开始进行初始化。既可以不需要显示地释放资源,也可以在对象所需资源在其生命周期内始终保持有效。

智能指针其实就是一个类,具有RAII的性质,像指针一样使用,解决拷贝问题

auto_ptr

auto_ptr是c++98标准出来的智能指针,主要是资源管理权转移,容易造成指针对象悬空,当再次访问该指针的时候,则会访问空指针,报错。不能使用!!!

unique_ptr

为了解决auto_ptr的资源管理权转移的问题,unique_ptr则是采用delete掉了拷贝构造函数和赋值构造函数。

template<class T>
	class unique_ptr
	{
	public:
		//RAII 保存资源
		unique_ptr(T* ptr) :_ptr(ptr)
		{}
		//释放资源
		~unique_ptr()
		{
			//delete[] _ptr;
			delete _ptr;
			cout << _ptr << endl;
		}
		//
		unique_ptr(unique_ptr<T>& p) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& p) = delete;
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

shared_ptr

shared_ptr加上了引用计数(int*类型的变量)的功能,允许多个对象指向同一份资源。这个引用计数的变量可以是常量嘛?static变量可以嘛?首先引用计数不能是常量,常量会导致指向同一份资源的指针无法完成一起++ --,也不能是static 静态变量,因为静态变量是所有的对象共享着一份,当不同指针指向不同的资源的时候,就会在同一个static上进行++ --。所以可以是int* 变量,当然也可以是map<T,int>的对象,指向同一份资源的指针进行++。

template<class T>
	class shared_ptr
	{
	public:
		//RAII 保存资源
		shared_ptr(T* ptr) :_ptr(ptr),_pcount(new int(1)),_pmtx(new mutex)
		{}
		//释放资源
		~shared_ptr()
		{
			//delete[] _ptr;
			Release();
		}
		//
		void Release()
		{
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
				cout << _ptr << endl;
			}
			_pmtx->unlock();
		}
		int use_count() const 
		{
			return *_pcount;
		}
		shared_ptr(const shared_ptr<T>& p)
			:_ptr(p._ptr)
			,_pcount(p._pcount)
			,_pmtx(p._pmtx)
		{
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();

		}
		//赋值的情况需要注意以下两种特殊情况
		//sp1 = sp2
		//sp1 = sp1
		shared_ptr<T>& operator=(shared_ptr<T>& p)
		{
			//因为很多指的是同一块儿资源,所以需要比较资源的地址
			if (_ptr != p._ptr)
			{
				//需要先释放
				int flag = false;
				_pmtx->lock();
				if (--(*_pcount) == 0)
				{
					delete _pcount;
					delete _ptr;
					flag = true;
				}
				_pmtx->unlock();
				_pcount = p._pcount;
				_ptr = p._ptr;
				++(*p._pcount);
				if (flag)
					delete _pmtx;
				return *this;
			}
		}
		T* Get() const 
		{
			return _ptr;
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;    //不能是静态的,如果是两块资源的话,静态的属于整个类,属于类的所有对象,其中一块资源只有一个指针指向
		mutex* _pmtx;

	};

对于shared_ptr而言,本身是线程安全的,拷贝和析构时,加上锁之后,引用计数++,是线程安全的,shared_ptr管理资源的访问不是线程安全的,例如多线程中,同时对智能指针管理的类里面的资源进行++,--操作此时就不是线程安全的,需要在临界区进行自行加锁保护。

但是shared_ptr存在一个大问题,循环引用的问题,当自定义类型是节点的时候,就会出现循环引用,从而导致内存泄漏。

如下图所示

 循环引用分析

1.node1和node2两个智能指针对象指向2个节点,引用计数变为1,不需要手动delete

2.node1的next指向node2,node2的prev指向node1,此时引用计数都变成2

3.node1和node2析构,引用计数减1,但是next和prev还分别指向上下两个节点

4.即当next析构和prev析构之后,node1和node2才会被释放。

5.但是next是node的成员,按理来说,node1释放了,_next才会析构,而node1由prev管理,prev属于node2的成员,所以这就叫循环引用,谁也不会释放。

weak_ptr

为了解决shared_ptr中的循环引用的问题,引入weak_ptr协助shared_ptr解决此类的问题,weak_ptr不进行资源的管理,只负责指向,不增加引用计数,不接受指针,不支持RAII。

//weak_ptr
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr(const shared_ptr<T>& p) :_ptr(p.Get()) {}
		weak_ptr() :_ptr(nullptr) {}

		weak_ptr<T>& operator=(const shared_ptr<T>& p)
		{
			_ptr = p.Get();
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;

	};

定制删除器

对于上述的智能指针而言,库里实现的智能指针可以完成对数组的释放

	std::shared_ptr<int> sp1(new int[10]);
	std::shared_ptr<string> sp2(new string);
	std::shared_ptr <FILE> sp4(fopen("smart_ptr.cpp", "r"), [](FILE* ptr) {fclose(ptr); });  //可以将lamda表达式传给智能指针,作为析构函数
	std::shared_ptr <string> sp5(new string[10], [](string* ptr) {delete[] ptr; });  //可以将lamda表达式传给智能指针,作为析构函数

对于自己设计的指针,则需要加上删除器的操作,因为对于数组的删除

template<class T>
	class default_delete {

	public:
		void operator()(T* ptr)
		{
			delete ptr;
			cout << "default_delete" << endl;
		}
	};
	//传一个默认的
	template<class T,class D= default_delete<T>>
	class shared_ptr
	{
	public:
		//RAII 保存资源
		shared_ptr(T* ptr) :_ptr(ptr),_pcount(new int(1)),_pmtx(new mutex)
		{}
		//释放资源
		~shared_ptr()
		{
			//delete[] _ptr;
			Release();
		}

		//释放资源
		void Release()
		{
			_pmtx->lock();

			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);
				delete _pcount;
			}
			_pmtx->unlock();
		}
		int use_count() const 
		{
			return *_pcount;
		}
		shared_ptr(const shared_ptr<T>& p)
			:_ptr(p._ptr)
			,_pcount(p._pcount)
			,_pmtx(p._pmtx)
		{
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();

		}
		//赋值的情况需要注意以下两种特殊情况
		//sp1 = sp2
		//sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& p)
		{
			//因为很多指的是同一块儿资源,所以需要比较资源的地址
			if (_ptr != p._ptr)
			{
				//需要先释放
				int flag = false;
				_pmtx->lock();
				if (--(*_pcount) == 0)
				{
					delete _pcount;
					//delete _ptr;
					_del(_ptr);
					flag = true;
				}
				_pmtx->unlock();
				_pcount = p._pcount;
				_ptr = p._ptr;
				++(*p._pcount);
				if (flag)
					delete _pmtx;
				return *this;
			}
		}
		T* Get() const 
		{
			return _ptr;
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;    //不能是静态的,如果是两块资源的话,静态的属于整个类,属于类的所有对象,其中一块资源只有一个指针指向
		mutex* _pmtx;
		D _del;

	};



struct Fclose
	{
		void operator()(FILE* ptr)
		{
			fclose(ptr);
		}

	};

对于删除数组或者文件等智能指针管理的资源的时候,需要在传一个参数去构造

		yc:: shared_ptr<ListNode,DeleteArray<ListNode>> n2(new ListNode[10]);
		//注意lamda是一个匿名函数对象  ,decltype 是在运行的时候推导对象  而模板则是需要在编译的时候进行推导的
		yc::shared_ptr<FILE, Fclose> n3(fopen("smart_str.cpp", "r"));

列表初始化

{}初始化

c++11可以使用花括号括起来的列表进行初始化,可用于内置类型和用户自定义类型

struct Point
{
 int _x;
 int _y;
};
int main()
{
 int x1 = 1;
 int x2{ 2 };
 int array1[]{ 1, 2, 3, 4, 5 };
 int array2[5]{ 0 };
 Point p{ 1, 2 };
 // C++11中列表初始化也可以适用于new表达式中
 int* pa = new int[4]{ 0 };
 return 0;
}

lambda表达式

lamda表达式实际是是一个匿名函数

lamda表达式语法:[capture-list] (parameters) mutable -> return-type { statement }

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。 (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略 mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量 性。使用该修饰符时,参数列表不可省略(即使参数为空)。 

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。

{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。

一般使用的时候, [capture-list] (parameters) { statement }

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
   []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
   [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    //可以借助auto 变量接收一个lamda表达式
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<<a<<" "<<b<<endl;
    
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}

捕捉列表描述了上下文的那些数据是可以被lambda使用的,以及使用的方式是传值还是传引用。

[var]:表示值传递的方式捕捉var;

[=]:表示的值传递的方式捕获所有父作用域中的变量,包括this;

[&var]:表示引用传递捕捉变量;

var [&]:表示引用传递捕捉所有父作用域中的变量(包括this);

[this]:表示值传递方式捕捉当前的this指针。

在使用lambda表达式的时候注意:

a. 父作用域指包含lambda函数的语句块。 b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量 。c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。

d. 在块作用域以外的lambda函数捕捉列表必须为空。 e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。 f. lambda表达式之间不能相互赋值,即使看起来类型相同。

void (*PF)();
int main()
{
     auto f1 = []{cout << "hello world" << endl; };
     auto f2 = []{cout << "hello world" << endl; };
     // 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
     //f1 = f2;   // 编译失败--->提示找不到operator=()
     // 允许使用一个lambda表达式拷贝构造一个新的副本
     auto f3(f2);
     f3();
     // 可以将lambda表达式赋值给相同类型的函数指针
     PF = f2;
     PF();
     return 0;
}

   函数对象与lambda表达式

函数对象又叫仿函数,既可以像函数一样使用的对象,就是在类里面重载了operator()运算符的函数对象。

class Rate
{
   public:
      Rate(double rate): _rate(rate)
      {}
      double operator()(double money, int year)
      { return money * _rate * year;}
   private:
      double _rate;
};
int main()
{
    // 函数对象
     double rate = 0.49;
     Rate r1(rate);
     r1(10000, 2);
    // lamber
     auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
     r2(10000, 2);
     return 0;
}

看底层代码,这是函数对象的底层 

这是lambda表达式的底层

 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如 果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值