今天下午看面經,有個前輩說 面試被問到了 朋友圈-並查集,然後我發現自己以前沒有看並查集,然後就去《王道》上看了看小米的那道朋友圈的面試題,也在網上看了一些大牛的關於並查集的博客。
花了一個多小時大概瞭解了並查集,然後就編寫朋友圈代碼,代碼編號了,然後發現有人把 這個寫成了一個類,並且自己好長時間沒有編寫類這塊的代碼了,所以 打算花一點時間寫一下。
結果......
悲劇了......
各種問題出現了。
class說明:
class friends{
public:
friends(int n);//構造函數
friends(const friends& f);//複製構造函數
friends& operator=(const friends& f);//賦值構造函數
~friends();//析構函數
int find(int x);
void merge(int x, int y);
int friendsCircles(int n, int m, int r[][2]);
private:
int _n;
int* _set;
};
這個類包括了一個指針變量,所以 需要自己寫析構函數、複製構造函數和賦值構造函數(三法則)!
那麼需要注意的地方來了:
1、構造函數
friends::friends(int n):_n(n), _set(new int[n+1]){//構造函數,n+1是因爲數組的第0個元素沒有使用,所以n個元素需要申請n+1個空間
cout << "調用構造函數開始!" << endl;
int i;
for(i = 1; i <= n; ++i)
_set[i] = i;
cout << "調用構造函數結束!" << endl;
}
(1)關於n+1。
因爲n代表人數,且數組_set中的第0個元素_set[0]沒有使用,所以數組大小應爲n+1。
這裏還應注意:若寫成_set(new int[n]),編譯沒有錯誤,若不調用析構函數(即不delete _set所指向的空間),也沒錯誤。
但在調用析構函數的情況下,即delete _set所指向的空間時,系統崩潰,並會報錯"DAMAGE:after Normal block"!
(2)必須在初始化列表初始化的成員變量(3種):
沒有默認構造函數的類類型的成員、const類型的成員變量和引用類型的成員變量。
2、析構函數:
friends::~friends(){//析構函數
cout << "調用析構函數開始!" << endl;
delete[] _set;
cout << "調用析構函數結束!" << endl;
}
(1)因爲_set 指向的 new出來的一個數組,所以需要用delete [] _set,而不是 delete _set。
(2)delete 只能釋放堆中的空間,即new出來的空間,若delete 的指針指向 棧的空間,運行會報錯!
(3)調用析構函數的幾種情況
1)若實例在堆中,即用new創建的實例,eg.
void test1(){//測試在堆上實例化對象
int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};
int n = 9;
int m = 6;
friends *f = new friends(n);//f在堆上
int count = f->friendsCircles(n, m, r);
cout << "朋友圈的個數:" << count << endl;
delete f;//因爲f在堆上,所以程序結束時,不會自動調用friends的析構函數,只有用delete時,才調用析構函數
}
若沒有delete f,程序結束時,並不會自動調用析構函數;
此時,只有用delete f 才調用析構函數,這裏的delete 是delete operate,是運算符,delete operate 的過程是:先調用 類的析構函數,然後調用operator delete函數,
operate delete函數可以被重載,而delete operate 不可以!同理,new 是delete operate,是運算符,delete operate 的工程是:先調用operate new 函數,然後調用類的構造函數, operate new 可以被重載!
2)若實例在棧中,eg.
void test2(){//測試在棧上實例化對象
int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};
int n = 9;
int m = 6;
friends f(n);//f在棧上
int count = f.friendsCircles(n, m, r);
cout << "朋友圈的個數:" << count << endl;//因爲f在棧上,所以程序結束時,會自動調用friends的析構函數。
}
當程序結束了,自動調用析構函數。
關於帶指針成員變量的類的書寫,大家可以看一下:http://www.cnblogs.com/lucy-lizhi/p/6551308.html
3、複製構造函數
friends::friends(const friends& f){//深複製
cout << "調用複製構造函數開始!" << endl;
_n = f._n;
_set = new int[_n+1];
memcpy(_set, f._set, (f._n+1) * sizeof(int));
cout << "調用複製構造函數結束!" << endl;
}
(1)深複製與淺複製深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生複製過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但複製過程並未複製資源的情況視爲淺拷貝。淺拷貝資源後在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
簡單來說:有指針的時候,一定要 深複製!!!
可以參考http://www.cnblogs.com/BlueTzar/articles/1223313.html
(2)調用複製構造函數的情況(有新的對象產生),eg.
void test3(){//測試複製函數
int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};
int n = 9;
int m = 6;
friends f(n);//f在棧上
//friends ff(f);
friends ff = f;
int count = ff.friendsCircles(n, m, r);
cout << "朋友圈的個數:" << count << endl;
}
對象ff是新產生的,所以此時調用複製構造函數,而不是賦值構造函數。
注意:friends ff(f); 與 friends ff = f是等價的!!!
4、賦值構造函數
void test4(){//測試賦值函數
int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};
int n = 9;
int m = 6;
friends f(n);//f在棧上
friends ff(n+2);
ff = f;
int count = ff.friendsCircles(n, m, r);
cout << "朋友圈的個數:" << count << endl;
}
(1)調用賦值構造函數的情況(沒有新對象產生),eg.
void test4(){//測試賦值函數
int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};
int n = 9;
int m = 6;
friends f(n);//f在棧上
friends ff(n+2);
ff = f;
int count = ff.friendsCircles(n, m, r);
cout << "朋友圈的個數:" << count << endl;
}
ff = f , 此時,對象ff不是新產生的,所以調用 賦值構造函數!
朋友圈完整代碼見:http://blog.csdn.net/sinat_31135199/article/details/76589295
一下午加一晚上,學習了並查集,複習了帶指針成員函數類的書寫,複習 構造函數、複製構造函數、賦值構造函數和析構函數,收穫還是挺大的!