今天下午看面经,有个前辈说 面试被问到了 朋友圈-并查集,然后我发现自己以前没有看并查集,然后就去《王道》上看了看小米的那道朋友圈的面试题,也在网上看了一些大牛的关于并查集的博客。
花了一个多小时大概了解了并查集,然后就编写朋友圈代码,代码编号了,然后发现有人把 这个写成了一个类,并且自己好长时间没有编写类这块的代码了,所以 打算花一点时间写一下。
结果......
悲剧了......
各种问题出现了。
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.youkuaiyun.com/sinat_31135199/article/details/76589295
一下午加一晚上,学习了并查集,复习了带指针成员函数类的书写,复习 构造函数、复制构造函数、赋值构造函数和析构函数,收获还是挺大的!