带指针成员类、析构函数出错、复制/赋值构造函数的注意事项


今天下午看面经,有个前辈说 面试被问到了 朋友圈-并查集,然后我发现自己以前没有看并查集,然后就去《王道》上看了看小米的那道朋友圈的面试题,也在网上看了一些大牛的关于并查集的博客。

花了一个多小时大概了解了并查集,然后就编写朋友圈代码,代码编号了,然后发现有人把 这个写成了一个类,并且自己好长时间没有编写类这块的代码了,所以 打算花一点时间写一下。

结果......

悲剧了......

各种问题出现了。

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

一下午加一晚上,学习了并查集,复习了带指针成员函数类的书写,复习 构造函数、复制构造函数、赋值构造函数和析构函数,收获还是挺大的!




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值