面向对象的那些坑
刚刚接触C++面向对象编程的时候,大家肯能会对一些新的概念感到很陌生,比如说析构函数,拷贝构造函数这些东西,往往会说——“我也不用这些东西呀,要这有什么用呢”,但是,确实啊,不得不说,比如说析构函数,在大一的阶段里基本上是用不到的,有些题要是硬用析构函数可能还会给你抛个异常,但是如果咱们实实力见长以后又会这其实是比较重要的东西,直接关系了你程序的崩溃与否,今天就来从我最近犯的一个错误来和大家聊聊C++中著名的三原则。
先放上让我犯错的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
| #include<iostream> using namespace std; template<class T> class Array { private: public: int size; T* element ; Array(int newsize) :size(newsize){ element = new T[size]; } ~Array() { delete[] element; cout << "析构函数调用" << endl; }; Array(const Array& p) { size = p.size; element = new T[p.size]; for (int i = 0; i < p.size; i++) { element[i] = p.element[i]; } cout << "拷贝构造函数调用" << endl; }
Array &operator=(const Array& p) { cout << "赋值运算符重载调用" << endl; size = p.size; if (element != NULL) { delete[] element; element = NULL; } else { element = new T[p.size]; for (int i = 0; i < p.size; i++) { element[i] = p.element[i]; } } }
friend istream &operator>>(istream &input,Array& inarray){ for (int i = 0; i < inarray.size; i++) { input >> inarray.element[i]; } return input; } friend ostream &operator<<(ostream & output,Array outarray){ for (int i = 0; i <outarray.size ; i++) { output << outarray.element[i] << " "; } return output; }
void mysort() {
for (int i = 0; i < size; i++) { for (int j = i + 1; j < size;j++) { if (element[i] > element[j]) { T temp; temp = element[i]; element[i] = element[j]; element[j] = temp; } } }
cout << *this; cout << endl; }
};
class student { public: int id; string name; int grade;
bool operator >(student& s2) { return this->grade > s2.grade ? true : false; } friend ostream& operator <<(ostream& out, student& st) { out << st.id; return out; } friend istream& operator >>(istream& in, student& st) { in>>st.id>>st.name>>st.grade; return in;
} };
int main() { int num; cin >> num;
Array<int> intnum(num); cin >> intnum;
Array<double> doublenum(num); cin >> doublenum;
Array<char> charnum(num); cin >> charnum;
Array<student> stnum(num); cin >> stnum;
intnum.mysort(); doublenum.mysort(); charnum.mysort(); stnum.mysort(); return 0; }
|
代码其实还蛮简单的哈,就是让你创建一个模板数组类,这个类里面有输入输出还有排序,然后分别处理下int double char和student的排序就行。
其实我的问题一开始也不是析构函数这出问题了,一开始的问题在于,输出函数忘了加引用了,因此导致了一系列的问题。
1 2 3 4 5 6 7 8 9 10
| friend ostream &operator<<(ostream & output,Array outarray){ for (int i = 0; i <outarray.size ; i++) { output << outarray.element[i] << " "; } return output; }
|
但是我一开始也没有意识到,所以咱们来看看如果这里不加引用会发生什么,然后引出咱们今天的主角C++三原则
首先我们知道按引用传递和按值传递的区别,按引用传递函数体内用到的还是原来的值,按值传递的话,编译器会先给你创造一个临时变量,然后把要传输的值复制给临时变量,既然说到复制了,那自然得想到拷贝构造函数,没错这个复制过程正是通过拷贝构造函数实现的,大家执行代码可以发现输出如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 3 3 2 1 1.2 1.1 1.3 c b a 10000 zhao 92 20000 li 93 30000 zhao 94 拷贝构造函数调用 1 2 3 析构函数调用
拷贝构造函数调用 1.1 1.2 1.3 析构函数调用
拷贝构造函数调用 a b c 析构函数调用
拷贝构造函数调用 10000 20000 30000 析构函数调用
析构函数调用 析构函数调用 析构函数调用 析构函数调用
|
没错,在每一次进行输出前,都会调用一次拷贝构造函数,在执行输出后还要调用一次析构函数,这个析构函数也很好理解,临时变量需要删掉嘛,自然需要析构一下。
但这个时候问题来了,如果我们一开始没有定义拷贝构造函数,那么就会用编译器提供的默认拷贝构造函数来使用,但是默认的拷贝构造是潜拷贝,这就导致了临时变量没有new 一个新内存,而是直接指向原来变量的地址。
那么这个时候问题就出了,既然临时变量指向的内存就是原始数据的内存,那么,临时变量的析构函数岂不是直接把原始数据给析构了?
但这个时候原始数据还没有到达生命周期,于是乎原始数据自己到达生命周期时忽然发现,wc我居然已经没了?于是这个时候就出现了异常。
这也就引出了我们今天的主角————C++三原则
需要析构函数的类也需要拷贝构造函数和拷贝赋值函数
需要拷贝操作的类也需要赋值操作,反之亦然
析构函数不能是私有的
这三个思想呢大概就是个啥意思捏,用到析构函数,证明用到堆中的内存,一旦涉及堆中的内存,原来的默认浅拷贝这个时候就失效了,需要我们手工写一个深拷贝来表示。