C++11 新特性

本文介绍了C++11的新特性,包括自动类型推导、空指针nullptr、lambda表达式、for范围遍历、初始化列表、类型别名和智能指针。详细说明了各特性的语法、使用方法、限制及注意事项,如auto的使用限制、智能指针的分类和避免内存泄漏的方法等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、自动类型推导:

1.1auto初始化

  • 在C++11之前,auto关键字用于指明变量的存储类型,它和static是相对的。但是一般都省略不写了。
  • 在C++11以后,auto关键字被赋予了新的含义,可以使用它来做自动的类型推导。也就是说,在使用了auto
  • 关键字以后,编译器会在编译时自动推导出 变量的类型,这样我们就不用手动指定变量的类型了。 语法如下:
 auto 变量名name=变量的值value;

注意:使用auto以后,必须给变量马上进行初始化,这样编译器才可以根据value来推导auto的类型。

 //auto初始化,auto的单独用法和混合用法
void test01()
{
    //单独用法
    //auto s;//auto使用的时候必须立刻初始化,才能推导auto的类型
    auto a = 3;//auto是int类型
    auto b = 4.45;//auto是double
    auto c = 'c';//auto是char
    cout << typeid(a).name() << "," << typeid(b).name() << "," << typeid(c).name() << endl;
    cout << sizeof(a) << "," << sizeof(b) << "," << sizeof(c) << endl;

1.2auto的混合用法

  • auto除了可以独立使用,还可以跟某些类型混合使用,此时auto表示的就是部分类型,或者叫半个类型。

示例:

 //混合用法
 int x = 10;
 auto* p1 = &x;//p1是int*类型,auto推导为int
 cout << "p1类型:" << typeid(p1).name() << endl;;
 auto p2 = &x;//p2是int*类型,auto推导为int*
 cout << "p2类型:" << typeid(p2).name() << endl;
 auto& r1 = x;//r1是int&类型,打印出来显式int类型,auto推导为int
 cout << "r1类型:" << typeid(r1).name() << endl;
 auto r2 = r1;//r2是int类型,auto推导为int,当右边的赋值是一个引用类型时,auto会直接推导出引用类型的原始类型。
 cout << "r2类型:" << typeid(r2).name() << endl;

1.3auto的使用限制

  • 除了上面说的auto必须马上初始化之外,还有一些使用限制: (

  • 1)auto不能用于定义数组。

  • (2)auto不能用于类的成员变量(属性)。

  • (3)auto不能在函数参数中使用。因为形参只是一种声明,并没有赋值,只有在函数调用的时候才会给参数赋值。

  • (4)auto作为函数返回值时,可以用于函数的定义,但不能用于函数的声明。

  • (5)auto不能用于模板参数。

  • 示例:

  •   //auto的使用限制
      //auto不能用于类的成员变量(属性)。
      class TestAuto
      {
      public:
          //auto m_Age=20;//即使马上赋值也不行
          int m_nAge;
      };
      //auto作为函数返回值时,可以用于函数的定义,但不能用于函数的声明。
      //auto mySum(int num);//auto作为返回值,不能用于函数的声明中
      auto mySum(int num)
      {
          int sum = 0;
          for (int i = 1; i <= num; i++)
          {
              sum += i;
          }
          return sum;
      }
      //auto不能用于模板参数。
      template<class T>
      class A
      {
          T m_Age;
      };
      //auto不能在函数参数中使用。
      int add(auto a=2, auto b=4)//即使赋值也不行
      {
          return a + b;
      }
    

1.4auto的使用场合

  • (1)定义函数的局部变量并且马上初始化
  • (2)用于循环语句中的循环控制变量并且马上初始化
  • (3)在遍历容器的时候,使用auto来定义迭代器类型
//auto的使用场合
void test02()
{
    //用于循环语句中的循环控制变量并且马上初始化
    int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
    for (auto i = 0; i < 10; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    
    //接下来的循环中使用auto,体会它的好处
    //在遍历容器的时候,使用auto来定义迭代器类型
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    //之前的写法,做容器遍历
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    //使用auto之后,做容器遍历
    for (auto it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    //auto用在map的迭代器定义
    map<int, int> m;
    map<int, int>::iterator it = m.begin();//原来的写法
    auto newit = m.begin();//使用auto后的写法
}

二、空指针nullptr:

  • 在C语言里,常量0有着int常量和空指针的双重角色,NULL被定义为(void)0

  • 在C++语言中,空指针NULL被定义为0

  • 以上这样的定义,会导致一些问题,如下:

    void fun(char* p);
    void fun(int n);
    
  • 在C++中,在使用上面这两个重载的函数时,如果传入的参数是NULL,那么会调用哪个函数?

  • 我们发现,会调用 void fun(int n);这个函数,这显然不是我们期望的,我们期望调用第一个函数。 所以,在C++11以后,引入了一个新的关键字nullptr,来充当单独的空指针常量,来纠正上面的问题它的类型是nullptr_t类型, 它可以隐式转换为任意类型的指针,并且可以和这些指针进行比较。

  • 但是,nullptr不能隐式转换为整型,也不能跟整型进行比较。

//空指针nullptr
//如下两个函数重载,当我们传入NULL时,希望调用第一个函数,但结果会调用第二个函数。
void fun(const char* p) { const char* c = "你好啊"; p = c; cout << p <<",调用第一个" << endl; }
void fun(int n) { cout << n << "调用第二个" << endl;}

 fun(NULL);//这里使用NULL存在问题,执行了第二个函数
 fun(nullptr);//这次执行的是第一个函数,这是我们期望的
 //nullptr可以隐式转换成任意类型的指针,如下:
 char* p_char = nullptr;
 int* p_int = nullptr;
 int i = nullptr;//nullptr不能跟整型int进行隐式转换
 int n = 0;
 if (n==nullptr){}//nullptr不能跟整型进行比较

三、lambda表达式:

lambda表达式也成为lambda函数,或者叫匿名函数,就是没有名字的函数。匿名函数结尾要有分号结束符。

3.1语法结构:

捕捉列表参数列表mutable->返回值类型{函数体代码;

  • capture(捕获列表): 指定了在lambda函数体中可以访问的外部变量。可以通过值捕获([x])、引用捕获([&x])或混合捕获([&, x]或[=, &x])。
  • parameter_list(参数列表): 与普通函数的参数列表相似,用于接收传递给lambda的参数。
  • return_type(返回类型): 指定lambda表达式的返回类型。可以省略,如果省略则返回类型由编译器推导。
  • 函数体: 包含了实际的代码块,定义lambda函数的主体。
 [capture](parameter_list) -> return_type 
 {   
	 // 函数体
 }  

解释:

  • [捕捉列表]:它总是出现在起始位置,编译器根据它来判断是否为匿名函数。它的作用是捕捉当前作用域中的变量,给匿名函数使用。

  • 捕捉列表的具体用法:[]:匿名函数不使用任何外部变量(但是仍然可以使用全局变量和静态变量)

  • [变量a,b,c…] 以传值的方式使用多个外部变量,用逗号分开。(传值捕捉来的变量具有常性,因为匿名函数默认是常函数,不能在匿名函数体中修改变量的值)

  • [&a,&b,&c…] 以引用的方式使用外部变量。(引用捕捉的变量,不具有常性,可以修改它们的值)

  • [=] 捕捉所有变量,并且都是用值传递的方式。

  • [&] 捕捉所有变量,并且都是用引用传递的方式。

  • (参数列表):它跟普通函数的参数列表用法相同,参数是不具有常性,可以修改它们的值。同时如果没有参数,()是可以省略的。当指定返回值时,参数列表()不能省略

  • mutable:它的作用就是取消值捕捉来的变量的常性,让它们可以在匿名函数的函数体内被修改。跟使用引用捕捉不一样,引用捕捉是直接改变原值。 (使用mutable时,参数列表不能省略,即使参数为空)

  • ->返回值类型:它代表匿名函数的返回值类型,如果没有返回值或者有明确的返回值类型也是可以省略。

  • **{函数体代码;}😗*匿名函数执行的具体代码段,这里面可以使用捕捉来的变量,也可以使用参数。 最简单的匿名函数是:[]{}

3.2匿名函数的使用

(见下面的代码)

//lambda表达式,匿名函数
void test03()
{
    //最简单的匿名函数,啥都做不了
    [] {};
    //下面演示可用的匿名函数
    int a = 1;
    int b = 2;
    [=] {return a + b; };//捕捉当前作用域所有的变量,值捕捉,没有参数,省略了参数列表,有返回值,但是省略了返回值的书写,让它自动推导
    //调用匿名函数,采用()符号来调用
    cout << "a+b=" << [=] {return a + b; }() << endl;
    //定义一个带参数的匿名函数
    [](int num1, int num2) {return num1 + num2; };
    cout << [](int num1, int num2) {return num1 + num2; }(5, 10) << endl;
    //定义指定返回值的匿名函数
    cout << []()->int {return 100; }() << endl;
    //结合auto使用,将匿名函数当作一个对象来使用
    auto f = []() {return 100; };
    cout << f() << endl;
    //引用捕捉可以去掉捕捉变量的常性,就可以修改它们的值
    auto f1 = [&](int c) {b = a + c; };
    f1(10);
    cout << "引用捕捉,修改了b的值,b=" << b << endl;
    //mutable的用法,它可以取消值捕捉变量的常性
    int x = 1;
    int y = 2;
    //auto add1 = [x, y]()->int {x *= 2; };//报错,值捕捉不能修改捕捉变量的值,具有常性
    auto add1 = [x, y]()mutable->int {x *= 2; return x; };//加上mutable关键字,可以取消常性,值可以被修改
    cout << "添加mutable之后,x=" << add1() << endl;

    //匿名函数可以在函数体内部调用其他普通函数
    auto fun_mysum = []()->int {return mySum(10); };
    cout << fun_mysum() << endl;

    //匿名函数用于函数的参数,给容器的排序算法指定排序规则
    vector<int> vec;
    vec.push_back(4);
    vec.push_back(6);
    vec.push_back(2);
    vec.push_back(8);
    //使用排序算法进行默认的升序排序
    sort(vec.begin(), vec.end());
    for (auto it = vec.begin(); it != vec.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    //使用匿名函数进行降序排序
    sort(vec.begin(), vec.end(), [](int x, int y) {return x > y; });
    cout << "降序排序后" << endl;
    for (auto it = vec.begin(); it != vec.end(); it++)
    {
        cout << *it << " ";
    }
}

3.3匿名函数实现的原理:

  • 匿名函数是在执行时,由编译器在全局生成一个类,类里面重载了operator()函数,函数体的内容就是匿名函数的内容。

四、for范围遍历:

  • 这是一种新的遍历方式,可以对一个可遍历的数据进行逐个遍历。但是这种遍历方式无法指定遍历的范围,如果要制定范围,还是用传统for循环的形式。

4.1语法:

for(变量类型 变量名:可遍历的序列数据){       循环体代码; }
  • **解释:**这里的变量类型要跟可遍历数据中的元素类型一致,通常为了方便,使用auto来定义。

注意:变量参数默认是值传递的变量,也可以定义成引用传递的方式,如果用引用方式取值,就可以改变可便利序列数据中的元素值。

for(变量类型& 变量名:可遍历的序列数据) 
{      
    循环体代码;    
}
//for范围遍历
void test04()
{
    int arr_int[10] = { 0,1,2,3,4,5,6,7,8,9 };
    //原来的遍历方式
    for (int i = 0; i < sizeof(arr_int)/sizeof(arr_int[0]); i++)
    {
        cout << arr_int[i] << " ";
    }
    cout << endl;
    //遍历字符数组
    char arr_char[] = "hello world";
    for (auto c : arr_char)
    {
        cout << c;
    }
    cout << endl;
    //遍历容器
    vector<int> int_vec = { 1,2,3,4,5 };
    for (auto i : int_vec)
    {
        cout << i << " ";
    }
    cout << endl;
    //引用遍历
    //默认的是值遍历,不能修改原值
    for (auto i : int_vec)
    {
        cout << ++i << " ";
    }
    cout << endl;
    for (auto i : int_vec)
    {
        cout << i << " ";
    }
    cout << endl;
    //使用引用的方式遍历,改变容器中的原值
    cout << "下面是引用遍历:" << endl;
    for (auto& i : int_vec)
    {
        cout << ++i << " ";
    }
    cout << endl;
    for (auto i : int_vec)
    {
        cout << i << " ";
    }
    cout << endl;
}

Map容器遍历:

 //写一个打印map容器的函数模板
template<class T1,class T2>
void printMap(map<T1, T2>& m)
{
    for (auto ele : m)
    {
        cout << ele.first << ":" << ele.second << endl;
    }
    cout << endl;
}

//使用函数模板打印map
    map<int, int> int_map = { {1,10},{2,20},{3,30} };
    map<int, string> int_str_map = { {9293,"张三"},{9494,"李四"},{8473,"王五"} };
    printMap(int_map);
    printMap(int_str_map);

4.2注意事项:

  • 1)for范围遍历不能实现指定范围的遍历。
  • 2)遍历范围可确定的,STL中的各类容器都可以正常使用。
  • 3)for范围遍历在遍历字符串时,会连通字符串的结束符’\0’一起遍历。
  //strlen函数不会统计结束符
    int count = 0;
    char arr_char_hello[] = "hello";
    for (int i = 0; i < strlen(arr_char_hello); i++)
    {
        count++;
    }
    cout << count << endl;//5
    
    //for范围遍历会统计结束符
    count = 0;
    for (auto c : arr_char_hello) { count++; }
    cout << count << endl;//6

五、初始化列表:

使用{}来初始化 C++11以前,允许使用{}对数组元素进行统一的初始化。 C++11以后,扩大了{}的适用范围,可以用于所有的内置基本类型和用户自定义类型的初始化。 使用初始化列表的时候,等号=可以加也可以不加。

//初始化列表{}
//自定义类型也可以使用初始化列表
class Date
{
public:
    int m_Year;
    int m_Month;
    int m_Day;
    Date(int y,int m,int d):m_Year(y),m_Month(m),m_Day(d){}
};
void test05()
{
    //数组使用初始化列表
    int arr1[] = { 4,2,42,5 };
    int arr2[5] = { 8 };
    //对类使用初始化表
    Date d1 = { 2024,1,25 };
    Date d2{ 2023,12,25 };
    cout << d1.m_Year << "-" << d1.m_Month << "-" << d1.m_Day << endl;
    cout << d2.m_Year << "-" << d2.m_Month << "-" << d2.m_Day << endl;
    //在堆内存申请内存的时候直接使用初始化列表
    int* p1 = new int(100);//直接初始化成100
    cout << "*p1=" << *p1 << endl;
    int* p2 = new int[5] {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)
    {
        cout << *(p2 + i) << " ";
    }
    cout << endl;
    Date* p3 = new Date[3]{ {2024,2,4},{2024,2,14},{2024,2,10} };
    cout << p3->m_Year << "-" << p3->m_Month << "-" << p3->m_Day << endl;

    //初始化列表的类型:class std::initializer_list<T>
    auto ls = { 10,20,30,40 };
    cout << typeid(ls).name() << endl;
}

六、类型别名:

  • 类型别名和类名等价,我们可以给某些类型重新起别名,这样就能用别名代替原来的类型名。

  • 类型别名主要有两种语法:

    • 1)typedef关键字 语法:typedef 类型名 类型别名;
    • 2)using关键 语法:using 别名=类型原名;
  • 以上两种别名定义方式的区别: 在语义上是等价,只有一个区别,就是typedef不能给模板类起别名,而using可以。

//类型别名
void test06()
{
    //typedef的方式
    //给Date类类型起别名
    typedef Date D;
    D d1 = { 2024,2,2 };
    typedef double dou;
    dou d = 23.55;

    //using的方式
    using test = TestAuto;
    test t1;
    
    //两种方式的区别:typedef不能给模板定义别名,应该使用using的方式
    using a = A<int>;

}

七、智能指针

7.1常见内存泄露

​ 什么是内存泄漏?
​ 指的是由于人为疏忽或者异常错误等原因,造成内存使用完没有被释放,导致这些内存无法继续被使用。内存泄露不是指物理上的消失。
​ 内存泄露的危害:长期运行内存泄露的程序,会导致程序越来越慢,最终卡死。
内存泄露的分类:
​ 1)堆内存泄露(heap leak)
​ 指的是通过malloc或者new等语句从堆内存分配的空间,用完后没有通过free或者delete语句释放,这些内存就无法再被使用,导致泄露。
​ 栈内存由编译器负责管理,会自动释放,不需要我们管理。
​ 2)系统资源泄露
​ 指的是操作系统分配的一些资源,比如说套接字、文件描述符、管道等等,使用完之后没有通过对应的函数释放掉,导致系统资源变少,也会导致一些问题。
​ 如何避免内存泄露?
​ 首先遵守设计规范,养成好的编码习惯,记得去释放不用的内存。
​ 可以采用一些第三方的检测工具,来检查你的代码有没有泄露。比如visual leak detector工具。
​ 但是即使做到以上,仍然无法避免内存泄露,因为有时候程序会遇到异常,异常后就会中断执行。如果释放内存的语句是在异常代码之后,那就无法执行了。

7.2智能指针概念

  • 以上分析我们得知,内存泄露很麻烦而且无法百分百避免,所以C++11之后,推出了智能指针,来解决这个问题,帮我们管理内存,自动释放内存。

智能指针是采用了RAII(资源获取即初始化)技术来实现的。就是利用对象的生命周期来管理资源的技术。在对象构造的时候获取资源,在对象生命周期内,保持对资源的访问,在对象析构的时候,释放资源,把资源管理任务委托给了对象。

  • 智能指针就是根据RAII思想创建的一个类模板,并且重载了指针的运算符,使它具有指针一样的行为。
  • 智能指针是封装了指向堆内存的原始指针(裸指针)的一个类模板,能够确保在离开智能指针对象作用域的时候,通过调用析构函数自动释放堆内存,防止内存泄露。
//模拟一个智能指针
template<class T>
class SmartPtr
{
public:
	T* ptr;//裸指针,指向堆内存
	SmartPtr(T* p):ptr(p){}
	~SmartPtr()//析构函数负责回收内存
	{
		if (ptr!=nullptr)
		{
			cout << "智能指针帮我们回收了内存" << endl;
			delete ptr;
			ptr = nullptr;
		}
	}
};
//模拟智能指针
SmartPtr<int> ptr_int(new int(1));
SmartPtr<string> ptr_string(new string("abc"));

7.3智能指针分类

  • 使用系统提供的智能指针,需要引入头文件**#include **

  • 指针指针根据对资源的所有权来分类,分成三类:
    1)共享智能指针shared_ptr
    2)独占智能指针unique_ptr
    3)弱共享智能指针weak_ptr

  • 下面分别介绍这三种类型:

7.3.1 共享智能指针shared_ptr:

​ 共享智能指针是指多个智能指针同时管理一块内存,它们共享一个内存,同时内部共享一个引用计数。每增加一个智能指针就增加1,每减少一个指针指针,
​ 就减少1,当计数为0的时候,就要释放这块内存,这样保证这块内存智能被释放一次。

共享指针的初始化:
​ 构造函数:shared_ptr sp(要管理的堆内存地址); 如:shared_ptr sp1(new int(100));

//构造共享智能指针
shared_ptr<int> ptr(new int(1));
cout << ptr << endl;
cout << *ptr << endl;//智能指针重载了指针的运算符,可以像指针一样使用	
*ptr = 30;//修改
cout << *ptr << endl;

​ 拷贝构造:shared_ptr sp(已存在的智能指针对象);拷贝构造之后它们就共同管理了一块内存。

shared_ptr<int> ptr1(ptr);//拷贝构造,此时ptr1和ptr都共同管理同一块内存
cout << *ptr1 << endl;//30	

​ 辅助函数:[make_shared 如:shared_ptr sp1=make_shared(100);]

//赋值函数初始化
shared_ptr<int> ptr2 = make_shared<int>(10);
cout << *ptr2 << endl;
shared_ptr<int> ptr3 = make_shared<int>();//无参,管理一个没有初始化的空间
  • 共享指针的成员函数:
    • **use_count();**查看当前这块内存有多少共享指针在同时管理。也就是统计引用计数。
    • **reset();**重置方法,不带参数的情况下,如果当前共享指针是唯一一个管理该内存的,那就释放内存并且置空该指针。如果它不是唯一管理者,则不需要释放内存,只需要引用计数-1,然后再将自己置空
    • reset(要管理的新地址);重置方法,带参数的情况下,如果当前共享指针是唯一一个管理该内存的,那就释放内存并且管理新内存地址。
      如果它不是唯一管理者,则不需要释放内存,只需要引用计数-1,然后再管理新内存地址。
    • get();返回共享指针封装的原始指针,也叫裸指针。
    • operator=():赋值运算符=的重载方法,完成共享指针之间的赋值操作,赋值之后它们就共同管理了一块内存。
//查看引用计数
cout << ptr.use_count() << endl;//2
cout << ptr1.use_count() << endl;//2
cout << ptr2.use_count() << endl;//1
cout << ptr3.use_count() << endl;//1
ptr3 = ptr2;//赋值函数,ptr3和ptr2共同管理一块内存
cout << *ptr3 << endl;//10
cout << ptr2.use_count() << endl;//2
cout << ptr3.use_count() << endl;//2
//重置方法
ptr3.reset();//ptr3,不是唯一管理者,它不需要释放所管理的空间,只需要引用计数-1,然后被重置
cout << ptr3.use_count() << endl;//0,ptr3被重置了,不管理任何空间了
cout << ptr2.use_count() << endl;//1,ptr3被重置了,只剩下ptr2管理原来的空间,所以计数为1
ptr2.reset();
cout << ptr2.use_count() << endl;//0,ptr2是唯一的管理着,被重置,引用计数变成0,原来空间被回收
ptr1.reset(new int(5));//ptr1被重置并且马上派去管理新空间5
cout << ptr1.use_count() << endl;//1
cout << ptr.use_count() << endl;//1
cout << *ptr1 << endl;//5

删除器:
​ 删除器其实是用于回收内存的一个函数,作为参数使用,通过函数来调用它。
​ 为什么要使用删除器?是因为对于自定义类型的连续多个内存空间,是无法被默认的析构函数回收的。需要我们手写删除器来完成回收工作。
​ 删除器往往使用匿名函数来实现。

//删除器
void test02()
{
	//shared_ptr<Test> ptr5(new Test[5]);//如果是多个连续的对象空间,默认的智能指针无法完成全部空间的释放
	shared_ptr<Test> ptr5(new Test[5], [](Test* t) {delete[]t; });//通过一个匿名函数实现连续空间的释放,这个函数叫做删除器
	cout << ptr5.use_count() << endl;
	shared_ptr<Test> ptr6(new Test(100), [](Test* t) {delete t; cout << "Test对象100被删除器释放了"; });
	ptr6->print();

	//删除器的其他用法,使用系统提供的默认删除器
	shared_ptr<Test> ptr7(new Test[3], default_delete<Test[]>());//第一种写法
	shared_ptr<Test[]> ptr8(new Test[4]);//第二种写法

}
class Test
{
	int m_num;
public:
	Test() { cout << "Test类的无参构造" << endl;; }
	Test(int x) { m_num = x; cout << "Test类的有参构造" << endl;; }
	~Test() { cout << "Test类的析构" << endl;; }
	void setValue(int v) { m_num = v; }
	void print() { cout << "m_num=" << m_num << endl; }
};
  • 使用共享指针的注意事项:
    1)不能直接使用new方法来返回一个空间指针给共享指针,而是需要使用构造函数或者辅助函数来完成空间的管理。
    2)一个普通指针指向的内存交给智能指针管理,此时就不能手动去回收普通指针的内存了,这样会造成重复释放同一块内存。
    3)不要用同一个原始指针初始化多个共享指针,原因是这样会造成重复释放同一块内存。我们让多个共享指针共同管理同一块内存的时候,
    应该使用赋值运算符或者拷贝构造的方式,这样共享指针之间才会共享引用计数,就不会造成多次释放了。
    4)不要再函数参数中创建共享指针,应该在函数外部先把智能指针创建好,再传入函数参数中。原因是不同的编译器堆函数参数的处理顺序是不一样的。
    比如:function(第一个参数,shared_ptr(new int(10)));//不要这么做
    应该这样:shared_ptr p(new int(10));先构造好指针对象,然后再给函数传参function(第一个参数,p);
    5)不要再类的成员函数中返回一个管理当前对象指针(即this指针)的智能指针对象。
    6)避免循环引用的问题:即两个类互相含有对方类对象的智能指针,并且相互赋值。这会导致内存泄露。这个问题可以通过后面要讲的弱指针来解决。

    //注意事项
    void testShared_Attenton() 
    {
    	//1)不能直接使用new方法来返回一个空间指针给共享指针管理,而是需要使用构造函数或者辅助函数来完成空间的管理。
    	//shared ptr<Test> ptest=new Test(2);
    	shared_ptr<Test> pTemp(new Test(10));
    	shared_ptr<Test> pTemp1 = make_shared<Test>(100);
    	//2)一个普通指针指向的内存交给智能指针管理,此时就不能手动去回收普通指针的内存了,这样会造成重复释放同一块内存
    	Test* pTest = new Test(100); //普通指针指向一个test对象
    	//再交给智能指针管理
    	shared_ptr<Test>pShare(pTest);
    	//如果我们忘了这个对象已经被智能指针管理了,我们有自己释放了,就会导致重复释放
    	delete pTest; //我们释放了这块内存,但是智能指针还会释放一次,就报错了
    	//3)不要用同一个原始指针初始化多个共享指针,原因是这样会造成重复释放同一块内存。
    	int* p = new int(100);
    	shared_ptr<int> p_sharel(p);
    	shared_ptr<int> p_share2(p);
    	cout << "p_sharel的指针计数:" << p_sharel.use_count() << endl;//1
    	cout << "p_share2的指针计数:" << p_share2.use_count() << endl;//1
    	//这样做是错误的,虽然p_ shar下1和p_ share2都管理了同一块内存,但是它们没有共享引用计数,它们都会回收这个内存,导致错误
    	//应该使用拷贝构造或者赋值的方式,让它们共享引用计数,如下:
    	p_share2 = p_sharel;//这样才不会报错
    	cout << "p_sharel的指针计数:" << p_sharel.use_count() << endl;//2
    	cout << "p_share2的指针计数:" << p_share2.use_count() << endl;//2
    	//4)不要再函数参数中创建共享指针,应该在函数外部先把智能指针创建好,再传入函数参数中。
    	//应该先把智能指针构造好,起好名字,然后再给函数传参
    	 
    
    	//5)不要再类的成员函数中返回一个管理当前对象指针(即this指针)的智能指针对象。
    	shared_ptr<C> c(new C());
    	c->getP();// 报错了,因为被释放了两次,C析构了一次,调用getP()函数是被析构了一次
    	//6)避免循环引用的问题:即两个类互相含有对方类对象的智能指针,并且相互赋值。这会导致内存泄露。
    	shared_ptr<A>a(new A());
    	cout << a.use_count() << endl;//1
    	shared_ptr<B>b(new B());
    	cout << b.use_count() << endl;//1
    	//下面进行相互赋值
    	a->bsp = b;
    	b->asp = a;
    	cout << a.use_count() << endl;//2
    	cout << b.use_count() << endl;//2
    	
    	//引用计数会变成2,所以导致内存泄漏,因为a离开释放了一次,b离开释放了一次
    
    }
    
    class C
    {
    public:
    	C() { cout << "C的无参构造" << endl; }
    	~C() { cout << "C的析构" << endl; }
    	shared_ptr<C> getP() { return shared_ptr<C>(this); }
    };
    
    //(6)循环引用问题,会导致内存泄漏
    class B;//这是前向声明,因为A类中马上用B类
    class A {
    public:
    	shared_ptr<B>bsp; //A类中含有管理B类的智能指针
    	~A() { cout << "A在析构" << endl; }
    };
    class B {
    public:
    	shared_ptr<A>asp; //B类中含有管理A类的智能指针
    	~B(){ cout << "B在析构" << endl; }
    };
    
7.3.2 独占智能指针unique_ptr:
  • 独占智能指针不允许其他智能指针跟它共享,它独自管理一块内存。

  • 独占智能指针禁用了赋值函数和拷贝构造函数。

  • 独占指针的常用函数:

    • 构造函数:

      unique_ptr<T> up(管理的内存地址);
      
    • 辅助函数:

      make_unique 如:unique_ptr<int> up=make_unique<int>(100);
      
    • 重置:

      reset();解除对内存的管理,释放内存,指针置空。
      
    • 重置:

      reset(新地址);解除对原来内存的管理,释放内存,管理新地址。
      get();获取智能指针管理的原始指针
      
void test04() 
{
	unique_ptr<Test> u_p1 = make_unique<Test>(8);
	unique_ptr<Test> u_p2(new Test(9));
	u_p1->print();
	u_p2->print();
	//独占指针拷贝构造和赋值

	unique_ptr<Test> u_p3 = make_unique<Test>();
	//u_p3 = u_p2;  不允许赋值  
	//unique_ptr<Test> u_p4(u_p3); //不允许拷贝构造

}
7.3.3 弱共享智能指针weak_ptr:

​ 它可以看成共享智能指针的助手,它不管理内存,主要作用就是当共享指针的助手,像一个旁观者,用于监控共享指针管理的内存是否存在是否有效。
​ 它没有重载指针的运算符,不能像指针一样使用。同时它的增加或减少也不会造成引用计数的改变。
​ 弱共享智能指针的常用函数:
构造函数:weak_ptr wp(监视的地址);
​ 拷贝构造:weak_ptr wp(另一个弱指针对象);
​ operator=():支持赋值函数,实现两个弱指针的赋值
​ use_count();统计所监视内存的引用计数
​ expired();判断所监视的内存是否被释放
​ lock();获取所监视内存的共享指针对象
​ reset();不再监视任何内存

void test05() 
{
	shared_ptr<int>sp(new int(10));
	cout << "sp_count:  " << sp.use_count() << endl;
	weak_ptr<int> wp1;
	cout << "wp1_count:  " << wp1.use_count() << endl;
	weak_ptr<int> wp2(sp);
	cout << "wp2_count:  " << wp2.use_count() << endl;
	weak_ptr<int> wp3(sp);
	cout << "wp3_count:  " << wp3.use_count() << endl;
	//增加管理者 

	shared_ptr<int>sp1(sp);
	cout << "sp_count:  " << sp.use_count() << endl;
	
	cout << "wp3_count:  " << wp3.use_count() << endl;
	cout << wp3.expired() << endl; //返回0;
	shared_ptr<int>  ptr=wp3.lock();
	cout << *ptr << endl; 
	wp3.reset();
	cout << "wp2_count:  " << wp2.use_count() << endl;
	cout << "wp3_count:  " << wp3.use_count() << endl;

}	

弱指针解决循环引用问题:
​ 由于弱指针不会导致引用计数的改变,所以只需要将A或者B类任意一个的共享指针成员改成弱指针,就可以打破循环引用。

// 通过弱共享引用打破上面的循环引用问题

class B_break;//这是前向声明,因为A_break类中马上用B_break类
class A_break {
public:
	shared_ptr<B_break>bsp; //A_break类中含有管理B_break类的智能指针
	~A_break() { cout << "A_break在析构" << endl; }
};
class B_break {
public:
	weak_ptr<A_break>asp; //B_break类中属性换成弱指针,引用计数不会加一
	//这样A_break类的对象就可以正常析构了。当他析构的时候,就会把他的属性对象也会析构了,此时bsp的析构就会导致他管理的B_break
	//应用计数-1,从2变成1,
	~B_break() { cout << "B_break在析构" << endl; }
};

void test06() 
{
	shared_ptr<A_break> a(new A_break());
	shared_ptr<B_break> b(new B_break());
	//下面进行相互赋值
	a->bsp = b;
	b->asp = a;
	cout << a.use_count() << endl;//1
	cout << b.use_count() << endl;//2
}

智能指针总结:
1)shared_ptr通常使用在所有权不明确的场景,会有多个智能指针同时管理一个内存。
2)unique_ptr使用在独享内存的情况下,只允许一个智能指针来管理内存。
3)避免智能指针和原始指针的混合交叉使用。
4)智能指针解决内存泄露的问题,但系统资源仍然需要自己回收。
5)使用智能指针的时候,每个指针对象都应该先初始化再使用。
6)使用弱指针打破循环引用。

最后,对于智能指针的使用,还是要谨慎,因为智能指针会导致性能的下降,所以,对于性能要求较高的程序,尽量不要用智能指针。对于性能要求不高的,可以用,比如QT里面用到的智能指针。还有一种情况,如果事先不能确定某个内存最终有事来释放,也可以使用智能指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值