C++11中实用的语法(上)

目录

C++11简介

1.统一的列表初始化

1.1        一切初始化皆可用“ { } ”,无论是内置类型还是自定义类型。

1.2initializer_list

2.声明

2.1auto

 2.2decltype

3.范围for循环

4.智能指针

4.1auto_ptr

4.2unique_ptr

4.3shared_ptr

4.4weak_ptr

4.5释放对应的资源


C++11简介

        都说十年磨一剑,C+11像脱胎于C+98的一种新语言。C++11能更好地用于系统开发和库开发、语法更加泛化和简单化、更加稳定和安全。我们来学习下现实中较为实用的语法。

1.统一的列表初始化

1.1一切初始化皆可用“ { } ”,无论是内置类型还是自定义类型。

内置类型:

int a[] = {1,2,3,4};

double d[3] = {1.1 ,2.2,3.3}; 

自定义类型:

struct A

{

    int a;

    int b; 

} ;

A a = {1,2};

还可以用于new表达式中

int *pa = new int[4]{0};

 至于map 等一些容器也可以

map<int, string> mp = { {1,"a"},{2,"b"},{3,"c"}};

vector<int> v ={2,34,54,5,6};

1.2initializer_list

        关于这些容器用花括号来调用构造函数初始化,是怎么实现的?C++增加了一个类型,叫initializer_list。常量的花括号列表被认定为initializer_list类型。

我们可以用迭代器来访问。

        讲了这么多,我们去看看这些容器为什么依靠 initializer_list支持花括号初始化的。

        C++11中新增了这样一个构造函数,用来支持initializer_list。用花括号初始化时,会调用参数含有initializer_list的构造函数。

        当然C++11也增加了operator = 的重载来支持initializer_list。

2.声明

        c++11提供了多种简化声明的方式。

2.1auto

        在C++11之前,auto是一个存储类型的说明符,表明变量是局部自动存储类型。我曾经遇到过一道题,题中问到函数形参的存储类型是什么?函数的隐含的存储类型是什么?答案是:函数形参的存储类型是auto,函数的隐含的存储类型是extern。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。当然我觉得它和范围for匹配起来很便捷,或者要定义一个迭代器时,冗长的类型很烦,可以用auto。

 2.2decltype

关键字decltype将变量的类型声明为表达式指定的类型。

3.范围for循环

        for循环后的括号由冒号 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。

        其实,用起来简单了,但范围for的底层还是用迭代器实现的。

4.智能指针

void fuc()
{
	int* a1 = new int[10];
	//...
    //抛异常
	delete a1;
}
int main()
{
	try
	{
		fuc();
	}
	catch (...)
	{
		;
	}
}

        如果fuc()函数中,在delete之前有函数抛出异常,那么根据异常捕获机制,则会跳到main函数中,进行catch,则fuc()函数中抛异常以后的语句都不会执行。那么在堆上申请的空间就不会释放,导致内存泄漏。

        内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

        所以提出了,这样一种思想。RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。衍生出智能指针的概念,就是将申请的资源交给一个对象,通过对象管理资源,在对象生命周期结束时,利用析构函数去释放资源。

        但有个问题,怎么拷贝呢?是浅拷贝还是深拷贝呢?

         两个智能指针去管理同一块资源,若是用系统默认的浅拷贝,到这俩智能指针的生命周期结束时,同时调用析构函数,但是是去析构同一片空间,导致:

         那我们用深拷贝呢?那就更不行了,我们的目的是两个智能指针管理同一片空间,如果深拷贝会导致两个智能指针管理两片资源,与起初理念相悖。

4.1auto_ptr

        C++98版本的库中就提供了auto_ptr的智能指针。我们看看这个指针解决问题了吗?

         成功了诶!但是它解决了两个智能指针管理同一片空间的问题了吗?

我们看看:

第一步 一切还好

 

一旦当完成拷贝构造之后,有东西变遭了

        诶?我第一个智能指针怎么被置为空了,说好一起管理一片空间的,但是变为第二个智能指针管理这片资源。这叫做管理权转移,被拷贝对象悬空。(说好一起到白头,他却偷偷焗了油)

        为什么呢?其实底层的拷贝构造函数就是这样实现的。 

        这样的实现会被吐槽好多年,但于事无补,auto_ptr已经被钉在了耻辱柱上。

        我们主要来学习unique_ptr和share_ptr。

4.2unique_ptr

        简单粗暴,从源头上解决问题。就是不让你拷贝。

实际上还是没解决

4.3shared_ptr

        真正解决拷贝问题的还是shared_ptr,其中用到了引用计数的方法。

        shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

        每有一个智能指针指向同一片资源,这片资源的计数加一,在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

话不多说,我们来实现下一些关键成员函数:

		void Erase()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				delete _ptr;
				_ptr = nullptr;
				delete _pCount;
				_pCount = nullptr;
			}
		}

		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
		{
			(*_pCount)++;
		}
		
		~shared_ptr()
		{
			Erase();
		}
		
		shared_ptr<T>& operator = (const shared_ptr<T> & sp)
		{

			if (this->_ptr  != sp._ptr)
			{
				Erase();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this.
		}

4.4weak_ptr

        再讲weak_ptr之前,先抛出个问题。

         这是因为智能指针赋值不了给原生指针。我们把结构改一下,把原生指针改掉。

         这下可以链接了,但是又出个问题,我们运行一下,看会不会调用析构函数。

        为什么没调用析构函数呢?这下导致了内存泄漏。因为:

按构造函数相反的顺序来调析构函数。先调p1的析构函数,p1这时计数不为1,--之后不为0所以析构不了,它要等另一个和它管理同一片资源的智能指针析构,那个智能指针就是p2的prev,p2的prev要等p2的析构函数,所以p1释放不了。轮到调p2的析构函数了,p2这时计数不为1,--之后不为0所以析构不了,它要等另一个和它管理同一片资源的智能指针析构,那个智能指针就是p1的next,p1的next要等p1的析构函数,但是p1的析构函数已经调过了,导致p1 和 p2 永远释放不了。

        是时候到weak_ptr出现了,他来了,扶大厦之将倾 挽狂澜于既倒。

可以这样修改:

         weak_ptr他不参与管理,游离于世外,他管理的资源,计数不会加加。而且它支持用shared_ptr来构造。

 结果:

来实现下,彻底搞懂 

		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())//获得shared_ptr的_ptr
		{}

		weak_ptr<T>& operator = (const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				_ptr = sp.get()
			}
		}

        实际上,weak_ptr出现的原因就是解决shared_ptr的循环计数的问题,如果遇到shared_ptr互相指向的问题,可以来使用weak_ptr。注意,他只能通过shared_ptr对象来构造。weak_ptr并没有重载operator->和operator *操作符,也不可以直接通过weak_ptr使用对象。

4.5释放对应的资源

        在写这些智能指针的时候,都是无脑的去delete,这时因为是new出来的一个变量,可以去delete。那以下得情况呢?

	unique_ptr<int> up2(new int[10]);
	unique_ptr<date> up3((date*)malloc(sizeof(date)* 10));
	unique_ptr<file> up4((file*)fopen("test.cpp", "r"));

        创建对象与对应的释放资源就根本对不上,这时要上我们的仿函数了。也要改一下我们写的智能指针的模板。

	template<class T>
	struct default_delete//默认的仿函数
	{
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
        ...

        在析构函数中,用仿函数创建对象,用对象调用()的重载函数去释放空间。

		~unique_ptr()
		{
			if (_ptr)
			{
				D del;
				del(_ptr);
				_ptr = nullptr;
			}
		}

下面的仿函数就解决了示例一。 

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout <<"delete[]"<<ptr << endl;

		delete[] ptr;
	}
};
    ...

        这样一个一个去传仿函数,便解决了对应的问题。

                                                                                                                                         未完待续...

         

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值