带指针成员类、析构函数出错、复制/赋值构造函数的注意事项
2017-08-02 21:27
288 查看
今天下午看面经,有个前辈说 面试被问到了 朋友圈-并查集,然后我发现自己以前没有看并查集,然后就去《王道》上看了看小米的那道朋友圈的面试题,也在网上看了一些大牛的关于并查集的博客。
花了一个多小时大概了解了并查集,然后就编写朋友圈代码,代码编号了,然后发现有人把 这个写成了一个类,并且自己好长时间没有编写类这块的代码了,所以 打算花一点时间写一下。
结果......
悲剧了......
各种问题出现了。
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
),编译没有错误,若不调用析构函数(即不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
一下午加一晚上,学习了并查集,复习了带指针成员函数类的书写,复习 构造函数、复制构造函数、赋值构造函数和析构函数,收获还是挺大的!
花了一个多小时大概了解了并查集,然后就编写朋友圈代码,代码编号了,然后发现有人把 这个写成了一个类,并且自己好长时间没有编写类这块的代码了,所以 打算花一点时间写一下。
结果......
悲剧了......
各种问题出现了。
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
),编译没有错误,若不调用析构函数(即不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
一下午加一晚上,学习了并查集,复习了带指针成员函数类的书写,复习 构造函数、复制构造函数、赋值构造函数和析构函数,收获还是挺大的!
相关文章推荐
- [Boolan] C++第二周(创建一个带指针成员变量的类)[注意事项]
- [Boolan] C++第一周(创建一个不带指针成员变量的类)[注意事项]
- C++ 构造函数使用 ":成员变量(形参)" 的形式给类里面成员变量赋值,如果成员变量和形参是指针,那么需要注意的事项
- 类中有指针成员变量时的一些注意事项
- 类成员变量中存在引用,const,和指针类型时需要注意的事项
- C++ 与“类”有关的注意事项总结(五):指向类成员的指针
- 指针使用中的注意事项
- 【C++注意事项】4 指针 Pointers
- 【黑马程序员】C语言指针及指针注意事项
- C++ 函数返回指针注意事项
- Go语言学习笔记 --- 指针和自增自减运算及相关注意事项
- const使用注意事项 指针 引用,指针引用区别
- 指针使用时注意事项
- mysql复制注意事项
- delete 类对象指针的注意事项]
- C++中指针初始化和使用注意事项
- SQL SERVER 2008 复制(发布、订阅)注意事项
- 改 c 程序笔记_2_申请指针的注意事项
- 内存管理之常用智能指针的用法和注意事项
- 赋值运算符函数的注意事项 ,指针和多维数组的讨论 (c/c++)