本章学习内容:

  • 1.const
  • 2.指针const
  • 3.inline内联函数
  • 4.函数重载
  • 5.extern “C”
  • 6.new/delete声明与释放
  • 7.namespace命名空间
  • 8.C++中的4种转换
  • 9.拷贝构造函数
  • 10.构造函数初始化列表
  • 11.析构函数
  • 12.const成员函数
  • 13.const对象
  • 14.栈、堆、静态存储区的区别
  • 15.静态成员变量/静态成员函数
  • 16.友元friend
  • 17.operator操作符重载函数
  • 18. 通过()操作符重载实现:函数对象
  • 19. 操作符重载实现:类型转换函数
  • 20.explicit显式调用(用来阻止隐式转换)
  • 21.父类和子类中的同名成员/函数
  • 22.子类对象初始化父类对象
  • 23.父类对象初始化子类对象
  • 24.纯虚函数vertual
  • 25.泛型函数模板(兼容不同类型)
  • 26.泛型类模板(兼容不同类型)
  • 27.数值型函数模板和数值型类模板(兼容不同数值)
  • 28.C++智能指针
  • 29.Qt中的智能指针

1.const

const和define宏区别

  • const常量:       由编译器处理,它会对const常量进行类型检查和作用域检查
  • define宏定义:  由预处理器处理,直接进行文本替换,不会进行各种检查

const在C++中为真正常量.示例:

  1. const int c = ; //const局部变量
  2.  
  3. int* p = (int*)&c; //会给p重新分配空间,而c还是处于常量符号表中
  4.  
  5. *p = ; //此时修改的值是新的地址上,对于c而言,依旧为0
  6.  
  7. printf("c = %d,*p=%d\n", c,*p); //打印: c = 0, *p=5

2.指针const
1) 底层const(位于*左侧)
const int *p : const修饰*p为常量,也就是说该指针指向的对象内容是个常量,只能改变指向的地址.但是可以通过其他方式修改对象内容
例如:

  1. int a=,a=;
  2. const int *p = &a;
  3. *p = ;      //error, 不能直接修改
  4. a=;       //right,通过对象本身修改内容
  5. *p = &b;     //right,可以指向其它地址

2) 顶层const(位于*右侧)
int * const p : const修饰指针p是个常量,也就是说p指向的地址是个常量
例如:

  1. int a=,a=;
  2. int * const p = &a;
  3. p = &b;         //error,p指向的地址是常量,永远为a地址,不能修改

注意:顶层const变量可以替代mutable变量

3.inline内联函数

示例如下:

  1. inline int MAX(int a, int b)
  2. {
  3.   return a > b ? a : b ;
  4. }
  • 普通函数:每次调用前,CPU都会保存现场(入栈),调用完后还要恢复现场(出栈)等额外开销.
  • 内联函数:就会在每次调用的地方,将内联函数里的代码段”内联地”展开,所以省去了额外的开销

注意:当内联函数里的代码过多,且流程复杂时,编译器可能会拒绝该函数的内联请求,从而变成普通函数

4.函数重载
参数表不同主要有以下几种

  • 1) 参数个数不同
  • 2) 参数类型不同
  • 3) 参数顺序不同

注意:

  • 重载函数需要避免使用参数默认值
  • 调用重载函数时,只会匹配函数参数表,与函数返回值无关
  • 函数重载必须发生在同一个作用域
  • 重载函数的入口地址,不能直接通过函数名来获取

5.extern “C”
可以实现调用C库代码.
示例:

  1. #ifdef __cplusplus
  2. extern "C" //通过C方式来编译add.h,也就是add()函数
  3. {
  4. #include "add.h"
  5. }
  6. #endif

6.new/delete声明与释放

示例如下:

  1. int *p = new int();  //默认值为0
  2.  
  3. int *p1= new int(); //动态分配一个int空间给p1,并赋值为1
  4.  
  5. float *p2=new float(2.0f); //2.0后面加f,表示2.0是个float类型
  6.  
  7. int *p3 = new int; //默认值为随机值
  8.  
  9. string *p4 = new string[];
  10.  
  11. delete p;
  12. delete p1;
  13. delete p2;
  14. delete p3;
  15. delete[] p4;

注意:
• 释放数组的空间时,必须使用delete[],而不是delete,避免内存泄漏

7.namespace命名空间

示例:

  1. #include <stdio.h>
  2.  
  3. namespace First //定义First命名空间
  4. {
  5.   int i = ;
  6. }
  7.  
  8. namespace Second //定义Second命名空间
  9. {
  10.   int i = ;namespace Internal   //在Second里,再次定义一个Internal空间(实现嵌套)
  11.   {
  12.     struct Position
  13.     {
  14.       int x;
  15.       int y;
  16.     };
  17.   }
  18. }
  19.  
  20. int main()
  21. {
  22.   using namespace First;         //使用First整个命名空间,成为该main()的默认空间
  23.   using Second::Internal::Position; //使用Second->Internal空间里的Position结构体
  24.  
  25.   printf("First::i = %d\n", i);
  26.  
  27.   printf("Second::i = %d\n", Second::i);
  28.  
  29.   Position p = {, };
  30.   printf("p.x = %d\n", p.x);
  31.   printf("p.y = %d\n", p.y);
  32.  
  33.   return ;
  34. }

输出结果:

  1. First::i =
  2. Second::i =
  3. p.x =
  4. p.y =

8.C++中的4种转换
static_cast(静态类型转换)
用于变量和对象之间的转换,比如(bool,char,int等)
用于有继承关系的类对象指针转换,可以通过父类对象去初始化子类对象(注意只能初始化父类的那部分)

const_cast(去常类型转换)
常用于去除const类对象的只读属性
强制转换的类型必须是指针*或者引用&

示例-去除const对象的只读属性:

  1. class Test
  2. {
  3. public:
  4. int mval;
  5. Test():mval()
  6. {
  7.  
  8. }
  9. };
  10.  
  11. int main()
  12. {
  13. const Test n1;
  14.  
  15. //n1.mval = 100; //error,不能直接修改常量对象的成员
  16.  
  17. Test *n2 = const_cast<Test *>(&n1); //通过指针*转换
  18. Test &n3 = const_cast<Test &>(n1); //通过引用&转换
  19.  
  20. n2->mval = ;
  21. cout<<n1.mval<<endl;        //打印20
  22.  
  23. n3.mval = ;
  24. cout<<n1.mval<<endl;        //打印30
  25. }

dynamic_cast(动态类型转换)
只能用在有虚函数的类中,一般在多重继承下用的比较多,比如:

  1. class BaseA
  2. {
  3. public:
  4.   virtual void funcA()
  5.   {
  6.     cout<<"BaseA: funcA()"<<endl;
  7.   }
  8. };
  9.  
  10. class BaseB
  11. {
  12. public:
  13.   virtual void funcB()
  14.   {
  15.     cout<<"BaseB: funcB()"<<endl;
  16.   }
  17. };
  18.  
  19. class Derived : public BaseA,public BaseB
  20. {
  21. };
  22.  
  23. int main()
  24. {
  25.   Derived d;
  26.   BaseA *pa=&d;
  27.   
  28.   pa->funcA(); //打印 BaseA: funcA()
  29.  
  30.   /*通过强制转换执行*/
  31.   BaseB *pb=(BaseB *)pa;
  32.   pb->funcB(); //还是打印BaseA: funcA(), 因为pb还是指向pa,执行的还是pa的虚函数表
  33.  
  34.   /*通过dynamic_cast执行*/
  35.   pb = dynamic_cast<BaseB *>(pa);
  36.   pb->funcB(); //打印 BaseB: funcB()
  37.   //编译器会去检测pa所在的地址,发现有多个虚函数表,然后根据 <BaseB *>来修正指针pb
  38.   return ;
  39. }

reinterpret_cast(解读类型转换)
对要转换的数据重新进行解读,适用于所有指针的强制转换

9.拷贝构造函数
一般用于当类对象中有成员指针时,才会自己写拷贝构造函数,因为编译器自带的默认拷贝构造函数只支持浅拷贝

  1. class Test
  2. {
  3.    //... ...
  4. Test(const Test& t)
  5. {
  6.   //copy... ...
  7. }
  8. };

10.构造函数初始化列表

  • 当类中有const成员变量时,则必须要用初始化列表进行初始化.
  • 对于其它普通变量如果不初始化的话则为随机值.
  • 初始化列表位于构造函数名右侧,以一个冒号开始,接着便是需要初始化的变量,以逗号隔开

示例如下:

  1. class Example
  2. {
  3. private:
  4.   int i;
  5.   float j;
  6.   const int ci;
  7.   int *p;
  8. public:
  9.   Test(): j(1.5),i(),ci(),p(new int(3)) //初始化i=2,j=1.5,ci=10 *p=3
  10.   {
  11.   }
  12. };

11.析构函数
注意:

  • 在类里,当定义了析构函数,编译器就不会提供默认的构造函数了,所以还要自己定义一个构造函数
  • 使用new创建的对象变量,在不使用时,需要使用delete,才能调用析构函数

构造函数的调用顺序

  • 1. 首先判断父类是否存在,若存在则调用父类构造函数
  • 2. 其次判断该对象的是否有类成员,若有则调用类成员的构造函数(调用顺序按照声明的顺序来构造)
  • 3. 最后调用对象本身的构造函数

12.const成员函数

  • cosnt成员函数里只能调用const成员函数
  • const成员函数中不能直接修改成员变量的值
  • 只有对mutable成员变量或者顶层const成员是可以修改的
  • 如果用const修饰的函数,那么该函数一定是类的成员函数

13.const对象

  • const对象的成员变量不允许被改变,
  • const对象只能调用const成员函数,而非const对象可以访问const成员函数
  • const对象是编译阶段的概念,运行时无效
  • const对象可以通过const_cast强制转换来实现改变其中成员变量的值

14.栈、堆、静态存储区的区别

用来存放函数里的局部变量,当调用某个函数时(执行某个代码段),会将该函数的变量(从数据段读出)入栈,然后退出函数的时候,会将该局部变量出栈进行销毁.
一般如果局部变量未初始化的话,都是随机值

堆由程序员分配释放new/delete,所以需要注意内存泄漏问题
一般new分配的对象变量,其成员都是随机值
静态存储区
用来存放全局变量,一直会存在的,一般编译器为自动将未赋值的全局变量进行一次清0

15.静态成员变量/静态成员函数

  • 在类里定义时直接通过static关键字修饰
  • 静态成员变量需要在类外单独分配空间,而静态成员函数则不需要
  • 静态成员变量在程序内部位于静态存储区
  • 对于public公有的静态成员变量/函数时,可以直接通过类名进行直接访问
  • 静态成员函数中不能访问非静态成员变量,因为它属于整个类的,没有隐含this指针

示例如下:

  1. class Test{
  2.  
  3. private:
  4.   static int mval;
  5.  
  6. public:
  7.   Test()
  8.   {
  9.     print();
  10.   }
  11.  
  12.   static int print() //静态成员函数是存在代码段中,所以不在类外定义也可以
  13.   {
  14.     cout<<"mval="<<mval<<endl;
  15.   }
  16. };
  17. int Test::mval=; //静态成员变量存在静态存储区中,所以需要在类外定义
  18.  
  19. int main()
  20. {
  21.   Test::print(); //通过类名直接访问静态成员函数,打印: mval=4
  22. }

16.友元friend

  • 友元的好处在于,方便快捷.可以通过friend函数来访问这个类的私有成员
  • 友元的坏处在于,破坏了面向对象的封装性,在现代已经逐渐被遗弃

示例:

  1. #include "stdio.h"
  2.  
  3. class Test{
  4. private:
  5.   static int n;
  6.   int x;
  7.   int y;
  8.  
  9. public:
  10.   Test(int x,int y)
  11.   {
  12.     this->x = x;
  13.     this->y = y;
  14.   }
  15.  
  16.   friend void f_func(const Test& t); //声明Test的友元是f_func()函数
  17.  
  18. };
  19.  
  20. int Test::n = ;
  21.  
  22. void f_func(const Test& t)
  23. {
  24.   printf("t.x=%d\n",t.x);
  25.   printf("t.y=%d\n",t.y);
  26.   printf("t.n=%d\n",t.n); //访问私有静态成员变量
  27. }
  28.  
  29. int main()
  30. {
  31.   Test t1(,);
  32.   f_func(t1);
  33.   return ;
  34. } 

17.operator操作符重载函数

使'+,-,*,/'等操作符拥有了重载能力,能够实现对象之间的操作,而不再单纯的普通变量之间的操作了.

示例如下,实现一个加法类:

  1. class Add
  2. {
  3. double mval;
  4. public:
  5. explicit Add(double t=)
  6. {
  7. mval = t;
  8. }
  9.  
  10. Add& operator +(const Add& t) //实现相同类对象相加
  11. {
  12. this->mval += t.mval;
  13. cout<<"operator +(const Add& t)"<<endl;
  14. return *this; //返回该对象,表示可以重复使用
  15. }
  16. Add& operator +(int i) //实现int型对象相加
  17. {
  18. this->mval += i;
  19. cout<<"operator +(int i)"<<endl;
  20. return *this;
  21. }
  22. Add& operator +(double d) //实现double型对象相加
  23. {
  24. this->mval += d;
  25. cout<<"operator +(double d)"<<endl;
  26. return *this;
  27. }
  28.  
  29. Add& operator = (const Add& t) //重载赋值操作符
  30. {
  31. cout<<"operator =(const Add& t)"<<endl;
  32.  
  33. if(this!=&t)
  34. {
  35. mval = t.mval;
  36. }
  37. return *this;
  38. }
  39.  
  40. double val()
  41. {
  42. return mval;
  43. }
  44. };
  45. int main()
  46. {
  47. Add a1(11.5);
  48. Add a2(1.25);
  49.  
  50. a1=a1+a2; //相当于调用两步: a1.operator =(a1.operator +(a2));
  51. cout<< a1.val() <<endl;
  52. }

运行打印:

18.通过()操作符重载实现:函数对象

  • 函数对象是指该对象具备函数的行为
  • 函数对象,是通过()调用操作符声明得到的,然后便能通过函数方式来调用该对象了.
  • ()调用操作符可以定义不同参数的多个重载函数
  • ()调用操作符只能通过类的成员函数重载(不能通过全局函数)

示例:

  1. class Test{
  2. public:
  3.   void operator () (void) //通过()重载操作符,来使对象具备函数的行为
  4.   {
  5.     cout<<"hello"<<endl;
  6.   }
  7. };
  8.  
  9. int main()
  10. {
  11.   Test t;
  12.   t(); //来调用t这个函数对象打印"hello"
  13. } 

PS:好处在于可以封装自己的成员以及其它函数,所以能够更好的面向对象.

19.操作符重载实现:类型转换函数
示例如下:

  1. class Test{
  2.  
  3.   int mValue;
  4.  
  5. public:
  6.   Test(int i=)
  7.   {
  8.     mValue=i;
  9.   }
  10.  
  11.   operator int()   //重载int类型
  12.   {
  13.     return mValue;
  14.   }
  15. };
  16.  
  17. int main()
  18. {
  19.   Test t();
  20.   int i=t;     //等价于: i=t.operator int();
  21.   cout<<i<<endl; //i=1000
  22. }

20.explicit显式调用(用来阻止隐式转换)
示例:

  1. class Test{
  2. public:
  3.   explicit Test(unsigned int i)
  4.   {
  5.     cout<<"unsigned i= "<<i<<endl;
  6.   }
  7. };
  8.  
  9. int main()
  10. {
  11.   short num=;
  12.   //Test t1=num; //Error,因为explicit阻止short类型 转换为unsigned int 类型
  13.  
  14.   /*只能有以下3个方法实现*/
  15.   Test t2=(Test)num;         //C方式强制转换,不推荐
  16.   Test t3=static_cast<Test>(num); //C++方式强制转换
  17.   Test t4(num);            //手工调用构造函数
  18.   return ;
  19. }

21.父类和子类中的同名成员/函数

  • 子类可以定义父类中的同名成员和同名函数
  • 子类中的成员变量和函数将会隐藏父类的同名成员变量和函数
  • 父类中的同名成员变量和函数依然存在子类中
  • 通过作用域分辨符(::)才可以访问父类中的同名成员变量和函数

示例1-通过子类访问父类同名函数和同名成员:

  1. class Parent{
  2.  
  3. public:
  4.  
  5.   int mval;
  6.  
  7.   Parent():mval()
  8.   { }
  9.  
  10.   void print()
  11.   {
  12.   cout<<"Parent: mval="<<mval<<endl;
  13.   }
  14.  
  15. };
  16.  
  17. class Child :public Parent
  18. {
  19.   public:
  20.   int mval;
  21.  
  22.   Child():mval()
  23.   { }
  24.  
  25.   void print()
  26.   {
  27.   cout<<"Child: mval="<<mval<<endl;
  28.   }
  29. };
  30.  
  31. int main()
  32. {
  33.   Child c;
  34.   c.Parent::print();       //调用父类的同名成员函数
  35.   cout<<c.mval<<endl;
  36.   cout<<c.Parent::mval<<endl; //打印父类的同名成员变量
  37. }

22.子类对象初始化父类对象
以上示例的Parent类Child类为例,在编译器中,可以将子类对象退化为父类对象,从而实现子类来初始化父类,比如:

  1. Parent p1(Child()); //Child()构造函数会返回一个临时对象,从而通过子类初始化父类
  2. Child c;
  3. Parent & p2 = c ; //定义p2是C对象的别名

23.父类对象初始化子类对象
只能使用static_cast或者C方式转换,以上示例的Parent类和Child类为例:

  1. Parent p;
  2. Child *c = static_cast<Child *>(&p);

24.纯虚函数vertual

  • 在父类中用virtual声明的成员函数便为虚函数
  • 虚函数的作用在于,能够正确调用某个同名函数是哪个类的对象
  • 比如:当某个子类被强制转换为父类时,则父类的虚函数也会被替代为子类的,从而实现程序灵活性

一个典型的示例,如下所示:

  1. class Base //父类
  2. {
  3. public:
  4.   virtual void func() //声明func为虚函数
  5.   {
  6.     cout<<"Base: func()"<<endl;
  7.   }
  8. };
  9.  
  10. class BaseA : public Base //子类A
  11. {
  12. public:
  13.   void func()
  14.   {
  15.     cout<<"BaseA: funcA()"<<endl;
  16.   }
  17. };
  18.  
  19. class BaseB : public Base //子类B
  20. {
  21. public:
  22.   void func()
  23.   {
  24.     cout<<"BaseB: funcB()"<<endl;
  25.   }
  26. };
  27.  
  28. void print(class Base& b)
  29. {
  30.   b.func();
  31. }
  32.  
  33. int main()
  34. {
  35.   BaseA bA;
  36.   BaseB bB;
  37.   print(bA);
  38.   print(bB);
  39.   return ;
  40. }

运行打印:

如上图可以看到,我们以print(bA)为例:
再调用print()函数时,会将BaseA bA转换为父类Base,由于父类Base有个func()虚函数,所以会被动态替换为bA子类的func()函数.所以会打印funcA()

如果将上面代码virtual void func()改为void func()重新编译运行后,打印:

如上图可以看到,没有虚函数后,整个代码都变得没有灵活性,不适合类的扩展.

PS:在QT中,virtual用的非常多,比如QWidget的showEvent函数:

  1. virtual void showEvent ( QShowEvent * event );

假如我们需要在窗口显示时加点特效时,只需要重写它即可,而QT库只需要根据vertual特性来自动调用我们重写的函数,非常灵活.

25.泛型函数模板(兼容不同类型)

函数模板是C++中重要的代码复用方式, 可通过不同类型进行调用

  • 通过template关键字来声明使用模板
  • 通过typename关键字来定义模板类型

示例:

  1. template <typename T> //声明使用模板,并定义T是一个模板类型
  2. void Swap(T& a, T& b) //紧接着使用T
  3. {
  4.   T c = a;
  5.   a = b;
  6.   b = c;
  7. }
  8. int main()
  9. {
  10.   int a=;
  11.   int b=;
  12.   Swap(a,b);       //自动调用,编译器根据a和b的类型来推导
  13.   float c=;
  14.   float d=;
  15.   Swap<float>(c,d); //显示调用,指定T是float类型
  16. }

为什么函数模板能够执行不同的类型参数?
答:

  • 其实编译器对函数模板进行了两次编译
  • 第一次编译时,首先去检查函数模板本身有没有语法错误
  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
  • 所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.

函数模板也支持多参数,示例如下(如果定义了返回值模板,则必须要显示指定返回值类型,因为编译器不知道到底返回什么类型):

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. template<typename T1,typename T2,typename T3>
  6. T1 Add(T2 a,T3 b)
  7. {
  8. return static_cast<T1>(a+b);
  9. }
  10.  
  11. int main()
  12. {
  13. // int a = add(1,1.5);      //该行编译出错,没有指定返回值类型
  14. int a = Add<int>(,1.5);    //指定T1为int形
  15. cout<<a<<endl;           //打印2
  16.  
  17. float b = Add<float,int,float>(,1.5); //指定T1,T2,T3类型
  18. cout<<b<<endl;           //2.5
  19.  
  20. return ;
  21. }

26.泛型类模板(兼容不同类型)
类模板和函数模板一样,都是进行2次编译,需要注意的是定义对象必须显示指定所有类型
示例:

  1. template<typename t1,typename t2,typename t3>
  2. class Operator{
  3.  
  4. public:
  5.   t1 add(t2 num1,t3 num2)
  6.   {
  7.     return num1+num2;
  8.   }
  9. };
  10. int main()
  11. {
  12.   Operator<float,int,float>t;
  13.   cout<<t.add(,11.5)<<endl; //11+11.5 = 22.5
  14.   return ;
  15. }

27.数值型函数模板和数值型类模板(兼容不同数值)
数值型和泛型类似,但是数值型模板必须在编译时被唯一确定

示例1-数值型函数模板:

  1. template <typename T,int N > //定义一个泛型值T,还有个int型的数值
  2. void func()
  3. {
  4.   T arr[N]; //使用模板参数T和N定义局部数组
  5. }
  6.  
  7. int main()
  8. {
  9.   func<int,>(); //相当于实现 int arr[10]
  10. }

示例2-数值型类模板(实现1+2+3+....+N值):

  1. template < int N >
  2. class Sum
  3. {
  4. public:
  5.   static const int VALUE = Sum<N->::VALUE + N; //通过Sum<N-1>::VALUE实现递归调用,并返回该临时对象
  6. };
  7.  
  8. template < >        //完全特化,因为我们知道N为1,所以不需要写< int N >
  9. class Sum < >      //重载Sum类(类似于函数重载),当N==1时调用该类
  10. {
  11. public:
  12.   static const int VALUE = ;
  13. };
  14.  
  15. int main()
  16. {
  17.   cout << "1 + 2 + 3 + ... + 10 = " << Sum<>::VALUE << endl;
  18.   cout << "1 + 2 + 3 + ... + 100 = " << Sum<>::VALUE << endl;
  19.   return ;
  20. }

28.C++智能指针
头文件<memory>
1)auto_ptr

  • 生命周期结束时,自动摧毁指向的内存空间
  • 不能指向堆数组(因为auto_ptr的析构函数删除指针用的是delete,而不是delete[])
  • auto_ptr的构造函数为explicit类型,所以只能显示初始化
  • 提供get()成员函数,可以用来查看类里的指针地址
  • 一个堆空间永远只属于一个对象(比如auto_ptr被拷贝/赋值,则自身的指针指向的地址会被抢占)

示例如下:

  1. #include <iostream>
  2. #include <memory>
  3.  
  4. using namespace std;
  5.  
  6. class Test{
  7.   int mvalue;
  8. public:
  9.   Test(int i=)
  10.   {
  11.     mvalue = i ;
  12.     cout<< "Test("<<mvalue<<")"<<endl;
  13.   }
  14.   ~Test()
  15.   {
  16.     cout<< "~Test("<<mvalue<<")"<<endl;
  17.   }
  18. };
  19.  
  20. int main()
  21. {
  22.   auto_ptr<Test> p1(new Test());
  23.   auto_ptr<Test> p2(new Test());
  24.  
  25.   cout<<"p1: addr="<<p1.get()<<endl;
  26.   cout<<"p2: addr="<<p2.get()<<endl;
  27.  
  28.   p2 = p1;
  29.   cout<<"p1: addr="<<p1.get()<<endl;
  30.   cout<<"p2: addr="<<p2.get()<<endl;
  31.   return ;
  32. }

运行打印:

如上图所示,当我们执行p2=p1后,便执行了p2的析构函数进行自动释放了.并且p1.get()=0,所以auto_ptr具备自动释放功能以及同块堆空间下只能有一个指针对象特性

2) shared_ptr (需要C++11支持)

  • 带有引用计数机制,支持多个指针对象指向同一片内存(实现共享)
  • 提供swap()成员函数,用来交换两个相同类型的对象指针地址
  • 提供unique()成员函数, 判断该指针对象地址是否被其它指针对象引用
  • 提供get()成员函数,用来获取指针对象指向的地址
  • 提供reset()成员函数,将自身指针对象地址设为NULL,并将引用计数-1(当计数为0,会自动去delete内存)
  • 提供use_count()成员函数,可以用来查看引用计数个数

示例如下所示:

  1. class Test{
  2.  
  3. public:
  4.   int mvalue;
  5.   Test(int i=)
  6.   {
  7.     mvalue = i ;
  8.     cout<< "Test("<<mvalue<<")"<<endl;
  9.   }
  10.   ~Test()
  11.   {
  12.     cout<< "~Test("<<mvalue<<")"<<endl;
  13.   }
  14. };
  15.  
  16. int main()
  17. {
  18.   shared_ptr<Test> p1(new Test());
  19.   shared_ptr<Test> p2(new Test());
  20.  
  21.   cout<<"p1: addr="<<p1.get()<<endl;
  22.   cout<<"p2: addr="<<p2.get()<<endl;
  23.  
  24.   p1.swap(p2); //互换p1和p2指针指向的地址
  25.  
  26.   cout<<"p1: addr="<<p1.get()<<endl;
  27.   cout<<"p2: addr="<<p2.get()<<endl;
  28.  
  29.   p1 = p2; //使p1指向p2指向的地址,并且释放p1之前指向的地址
  30.   cout<<"p1:addr="<<p1.get()<<", p2:addr="<<p2.get()<<endl;
  31.   cout<<"p1: unique="<<p1.unique()<<endl; //p1和p2指向同一片内存,所以为0
  32.   cout<<"p1: count="<<p1.use_count()<<endl;
  33.   return ;
  34. }

运行打印:

29.Qt中的智能指针
-QPointer

  • 当其指向的对象被销毁时,本身会自动赋值为NULL(从而避免被多次释放和野指针)
  • 缺点在于,该模板类析构时,不会自动摧毁所指向的对象(需要手工delete)

-QSharedPointer

  • 带有引用计数机制,支持多个指针对象指向同一片内存(实现共享)
  • 可以被自由地拷贝和赋值
  • 当引用计数为0(最后一个指针被摧毁)时,才删除指向的对象(和shared_ptr类似)

-QScopedPointer

  • 优点在于生命期结束后会自动删除它所指的对象(不需要手工delete)
  • 不支持多个QScopedPointer指针对象指向同一片内存(不能共享)

示例:

  1. QScopedPointer<QPushButton> p1(new QPushButton);

30.C++复习篇的更多相关文章

  1. NOIP复习篇

    NOIP复习篇---枚举 --------------------------------------------------------------------------------------- ...

  2. vue 复习篇. 注册全局组件,和 组件库

    初篇 ============================================================== 1. 编写loading组件(components/Loading/ ...

  3. Asp.Net复习篇之Asp.Net生命周期与Asp.Net页的生命周期

    Asp.Net生命周期与Asp.Net页的生命周期是一个比较重要的话题,有时可能似乎知道一些,但又说不出个所以然,而且时常把这两个概念混淆.现在也是该好好理清思路,把这两个概念搞懂. Asp.Net生 ...

  4. 复习篇(一)Activity的生命周期和启动模式

    (一)关于<intent-filter>中的<data> 当设置<data>过滤器的时候,使用intent的时候必须要设置响应的匹配,否则无法匹配成功.不过不设置则 ...

  5. sql server group by having 之复习篇

    where 与 having 之间的差别在于where 是分组前的过滤,而having是分组后的过滤 Group By中Select指定的字段限制 示例3 select 类别, sum(数量) as ...

  6. python 10月30日复习

    1.把一个数字的list从小到大排序,然后写入文件,然后从文件中读取出来文件内容,然后反序,在追加到文件的下一行中 import codecs list1 = [2,23,8,54,86,12] li ...

  7. C++学习总结 复习篇2

      延续上一小节内容:下面继续讲解虚函数和多态 虚函数和多态 基类指针可以指向任何派生类的对象,但是不能调用派生类对象的成员. 但是,基类可以调用覆盖了虚函数的函数.(现在调用将来,这有问题,说明现在 ...

  8. C++ 学习总结 复习篇

      友元的使用 分为友元类和友元函数     //友元类与友元函数的共同点:都可以让某一个类作为另一个类或者函数的参数.          //友元类:它让当前类成为另一个类的友元,然后,另一个类 ...

  9. SAP翔子_ABAP_DEMO篇索引

    序号 描述 SAP翔子_ABAP_DEMO篇1 ABAP DEMO篇1 单层反查BOM SAP翔子_ABAP_DEMO篇2 ABAP DEMO篇2 删除工艺路线 SAP翔子_ABAP_DEMO篇3 A ...

随机推荐

  1. 找一个数组的最大和的连续子数组(时间复杂度 O(n))

    设计思想 一开始的思想是求出全部的情况,再分别比较大小,这种方法适用于有限个数组,不适用于输入数组长度和内容的情况. 但也试着做了 int a[]= {-1,2,6,-10}; int size=4; ...

  2. 手动安装OpenCV下的IPP加速库

    写在前面 安装opencv的时候,往往会卡在这里: IPPICV: Download: ippicv_2019_lnx_intel64_general_20180723.tgz 其实就是墙的原因,然后 ...

  3. CPU运行原理

    问题: CPU位宽表示什么意思? 下面这个是 https://www.bilibili.com/video/av9667986?from=search&seid=336127932106862 ...

  4. [python] 溜了,溜了,七牛云图片资源批量下载 && 自建图床服务器

    故事背景: 七牛云最近一波测试域名操作真是把我坑死了!这简直和百度赠送你2T网盘,之后再限速一样骚操作.于是,痛定思痛自己买个云主机.自己搭图床应用! 1.七牛图片批量下载到本地 1.1 曲折尝试 当 ...

  5. 老司机带路:《axios从入门到开车 嘀嘀~~》

    前言:axios vue.axios 跨域.axios.js.axios get.axios post.axios中文文档 之前当vue更新到2.0之后,作者就宣告不再对vue-resource更新, ...

  6. 爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

    本文由爱奇艺技术团队原创分享,原题<爱奇艺Android客户端启动优化与分析>. 1.引言 互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Andro ...

  7. [Swift]LeetCode367. 有效的完全平方数 | Valid Perfect Square

    Given a positive integer num, write a function which returns True if num is a perfect square else Fa ...

  8. [Swift]LeetCode724. 寻找数组的中心索引 | Find Pivot Index

    Given an array of integers nums, write a method that returns the "pivot" index of this arr ...

  9. [Abp 源码分析]二、模块系统

    0.简介 整个 Abp 框架由各个模块组成,基本上可以看做一个程序集一个模块,不排除一个程序集有多个模块的可能性.可以看看他官方的这些扩展库: 可以看到每个项目文件下面都会有一个 xxxModule ...

  10. Chapter 5 Blood Type——26

    "I saw his face — I could tell." “我看到他的脸了 —— 我知道.” "How did you see me? I thought you ...