C++ Big Three详细讲解+示例
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Big Three详细讲解+示例相关的知识,希望对你有一定的参考价值。
=========简单构造函数=========
?这里主要详细介绍了构造函数,这里只需要说明一点,就是要明确一下写作规范,书写构造函数时最好使用this指针,即:
1 this->width = width; 2 this->height = height;
this指针明确指出了等号左边是当前类的成员,否则写成下面代码会让人混乱。
1 width = width; 2 height = height;
当然你也可以定义其他变量名字,改变这种歧义,并同时进行初始化列表。
=========拷贝构造函数=========
?首先,拷贝构造函数中出现的通病应该是没有考虑到基类的继承,既然写了Shape基类,就不会白写,应该在编程的过程中重视代码中的每一条语句,要做到不添加无用代码,也不忽视程序中出现的每一句代码。
?其次,拷贝构造函数中还要注意代码的鲁棒性,编写任何程序都应该注意这个问题,编写函数时要防止外部代码改变导致本函数失效或者导致程序崩溃。在本次拷贝构造函数中,你可以对leftup中的x,y分别进行赋值,但前提是在你对Point类内部完全了解的情况下进行实现的,但是如果Point类中成员发生改变,你编写的拷贝构造函数也会相应失效,所以更合理的代码是写成下列形式:
1 this->leftup = new Point(*(other.leftup));
?接下来说说拷贝构造的顺序,这也是令人容易忽略的地方,
1 inline 2 Rectangle::Rectangle(const Rectangle& other):Shape(other),width(other.width),height(other.height)
? ?这里也不费口舌详细解释了,大家也应该知道了,拷贝构造的顺序与你书写的顺序没有关系,无论你写成什么顺序,编译器里已经约定好顺序,即先父类,后原类中对数据定义的顺序。因此,这里的建议就是为了阅读代码方便,书写顺序最好与拷贝构造的顺序一致。
?最后,说说空指针的问题,如果你要拷贝构造的other是空指针,就没必要在堆中再创建分配,只能是浪费空间。因此,拷贝构造函数如下:
1 inline 2 Rectangle::Rectangle(const Rectangle& other):Shape(other),width(other.width),height(other.height){ 3 if(other.leftup != nullptr){ 4 this->leftup = new Point(*(other.leftup)); 5 } 6 else{ 7 this->leftup = nullptr; 8 } 9 }
=========拷贝赋值函数=========
?拷贝赋值函数首先要做的就是自检,如果本身自己赋值,直接返回。
1 if(this ==& other) 2 3 { 4 return *this; 5 }
?同时,拷贝赋值函数也同样需要考虑基类的继承,应该写成这样的形式:
1 Shape::operator=(other);
?这里就是把“operator=”看做一个整体,即Shape的成员函数,然后我们直接传入参数other,这样便调用了shape的默认构造函数,对no进行的赋值操作,这样做的好处是我们完全不必管Shape内部是如何实现的,只要做我们的赋值就可以了。
?最后,拷贝赋值需要考虑other.leftUp指针和自身leftup指针为空的4种情况。首先我们要判断leftup是否为空,如果当前类成员leftup不为空的情况下,继续判断other.leftup是否为空,如果other.leftup不为空直接进行赋值,如果other.leftup为空,需要先delete当前类中的leftup,然后将其指向nullptr;如果当前类成员leftup为空的情况下,仍需要继续判断other.leftup是否为空,如果other.leftup不为空需要重新分配内存,并同时在堆中初始化,如果other.leftup为空,不需要任何操作。
1 if(leftup != nullptr) 2 { 3 if(other.leftup != nullptr) 4 { 5 *leftup = *(other.leftup); 6 } 7 else 8 { 9 delete leftup;//caution: memory leak 10 leftup = nullptr; 11 } 12 } 13 else 14 { 15 if(other.leftup != nullptr) 16 { 17 leftup = new Point(*(other.leftup)); 18 } 19 }
? ?这里需要说明一点:只要代码能够表述上述意思即可,也可以先判断other.leftup,再判断leftup,效果是一样的。
===========析构函数===========
?析构函数比较简单,就是需要注意一点:
1 inline 2 Rectangle::~Rectangle() 3 { 4 delete leftup; 5 leftup = nullptr; 6 }
?delete只是对指针的指向空间的释放,并不会改变指针的值,即指针不为空。指针的本身内容,即指向空间的地址,是没有发生变化的。同时,C++是可以delete空指针的,C++不能直接delete的是野指针,是会出问题的,所以一般指针被delete之后,最好立即赋值为空,以免被再次delete而出现问题。当指针为空指针时,没有空间可释放,也就不去释放了。有些代码表面看起来没用,但是要养成好习惯,否则bug出现的时候都不知道错误在哪里。
===========补充杂谈===========
1、Singleton模式小谈:
?最好太依赖C++做Singleton模式,同时在讨论区内有Singleton模式多线程的实现说明如下:
?在单线程下,C++确保这种内置的本地static对象在首次被调用时被初始化。但是在多线程环境下,这种做法会带来不确定性,在多线程下面有几种选择:(1) 虽然是多线程,但是一个进程中一定有一个主进程并且首先被执行,可以沿用meyers的实现方式,并在主线程里面初始化所有的Instance以确保在单线程环境下的Instance。也就是说,你要在主线程启动后,首先调用雷氏A::GetInstance()的函数返回本地静态引用;(2) 可以采用所谓“lock+double check”的方法,可以写成下列形式
1. #include 2. static A* m_pInstance = NULL; 3. std::mutex Mutex; // mutex for critical section 4. 5. static A& A::GetInstance() 6. { 7. if (m_pInstance == NULL) 8. { 9. std::lock_guard lock(Mutex); //此处锁住临界区对象 10. if (m_pInstance == NULL) 11. { 12. // 此处再次检查m_pInstance,并初始化 13. m_pInstance = new A(); 14. } 15. } 16. return *m_pInstance; 17.}
使用:
1. A anInstance = A::GetInstance(); 2. anInstance.SomeMethod();
题目描述:
为 Rectangle 类实现构造函数,拷贝构造函数,赋值操作符,析构函数。
程序编写:
1 //============Rectangle.h=============== 2 5 #ifndef _RECTANGLE_ 6 #define _RECTANGLE_ 9 10 // forward declaration 11 #include 12 #include 13 using namespace std; 14 18 //class declaration 19 class Shape 20 { 21 public: 22 Shape() {no = ++cnt;} 23 Shape(const Shape& other) { no= other.no; ++cnt;} 24 Shape& operator=(const Shape& other) { no = other.no; return *this; } 25 virtual ~Shape() {--cnt;} 26 private: 27 int no; 28 static int cnt; 29 }; 30 int Shape::cnt = 0; 31 32 33 34 class Point 35 { 36 int x; 37 int y; 38 public: 39 Point(int x=0,int y=0) 40 { 41 this->x = x; 42 this->y = y; 43 } 44 int get_x() const {return x;} 45 int get_y() const {return y;} 46 }; 47 class Rectangle: public Shape 48 { 49 int width; 50 int height; 51 Point* leftup; 52 public: 53 Rectangle(int width,int height,int x,int y); 54 Rectangle(const Rectangle& other); 55 Rectangle& operator=(const Rectangle& other); 56 ~Rectangle(); 57 int Girth() const {return (width+height)*2;} 58 int Area() const {return width*height;} 59 int get_width() const {return width;} 60 int get_height() const {return height;} 61 Point* get_leftup() const {return leftup;} 62 }; 63 //class definiition 64 65 inline 66 Rectangle::Rectangle(int width=0,int height=0,int x=0,int y=0):leftup(new Point(x,y)) 67 { 68 this->width = width; 69 this->height = height; 70 } 71 72 inline 73 Rectangle::~Rectangle() 74 { 75 delete leftup; 76 leftup = nullptr; 77 } 78 79 inline 80 Rectangle::Rectangle(const Rectangle& other):Shape(other),width(other.width),height(other.height){ 81 if(other.leftup != nullptr){ 82 this->leftup = new Point(*(other.leftup)); 83 } 84 else{ 85 this->leftup = nullptr; 86 } 87 } 88 89 inline Rectangle& 90 Rectangle:: operator=(const Rectangle& other){ 91 92 93 94 if(this == &other){//check self assignment 95 return *this; 96 } 97 Shape::operator=(other); 98 width = other.width; 99 height = other.height; 100 101 102 103 if(leftup != nullptr) 104 { 105 if(other.leftup != nullptr) 106 { 107 *leftup = *(other.leftup); 108 } 109 else 110 { 111 delete leftup;//caution: memory leak 112 leftup = nullptr; 113 } 114 } 115 else 116 { 117 if(other.leftup != nullptr) 118 { 119 leftup = new Point(*(other.leftup)); 120 } 121 } 122 return *this; 123 } 124 //output 125 ostream& operator<<(ostream& os,const Rectangle& other) 126 { 127 os<<": width("<<other.get_width()<<")," 128 << "height("<<other.get_height()<<")," 129 << "leftup.x("<<other.get_leftup()->get_x()<<")," 130 << "leftup.y("<<other.get_leftup()->get_y()<<"),"<<endl 131 << " Girth="<<other.Girth()<<"," 132 <<" Area="<<other.Area()<<"."<<endl; 133 return os; 134 } 135 136 137 138 #endif // _RECTANGLE_ 139 140 // ============Rectangle.cpp============= 141 142 143 144 #include "Rectangle.h" 145 146 int main() 147 { 148 Rectangle rec0; 149 cout<<"rec0()"<<rec0<<endl; 150 Rectangle rec1(3,4,0,0); 151 Rectangle rec2(5,6,4,4); 152 cout<<"rec1()"<<rec1<<endl; 153 cout<<"rec2()"<<rec2<<endl; 154 157 cout<<"----------Copy creator rec3(rec2)---------"<<endl; 158 Rectangle rec3(rec2); 159 cout<<"rec3()"<<rec3<<endl; 160 163 cout<<"----------Copy operator rec3=rec1---------"<<endl; 164 rec3=rec1; 165 cout<<"rec3()"<<rec3<<endl;
169 cout<<"----------Copy operator rec3=rec3---------"<<endl; 170 rec3=rec3; 171 cout<<"rec3()"<<rec3<<endl; 172 }
题目总结:
1、在Shape基类中,静态私有数据成员需要在类外面定义初始化。同时,Shape类中的函数可以根据相应要求变化。
2、程序中验证了默认构造函数、拷贝构造、拷贝赋值以及自赋值,同时实现了周长和面积的计算。
3、三大函数分别为拷贝构造函数、拷贝赋值函数和析构函数,到此应该有一个详细的学习。李老师其实提到了很多次解耦思想,一个函数只要实现各自的功能即可,不要去操作其他函数内部的内容,不要去操作底层。尤其是团队合作的时候,定义好各自的接口,调用相应的接口,实现自己当前函数的功能,并不需要知道其他函数的内部实现,也不要干涉其他函数的功能。这样在其他代码变化时,才不会影响你的代码,即保证了程序的通用性和鲁棒性。
以上是关于C++ Big Three详细讲解+示例的主要内容,如果未能解决你的问题,请参考以下文章