序言
之前学习了 类、派生、虚函数、容器等概念和用法,这里再学习一下构造、析构函数的相关知识。
1. 构造函数
什么是构造函数
C++编译器提供的处理对象初始化问题的特殊的成员函数
当我们创建一个对象时,常需要做一些初始化操作,例如属性赋值等
构造函数的作用
给创建的对象建立一个标识符
为对象数据成员开辟内存空间
完成对象数据成员的初始化
构造函数的特点
不需要用户调用,在对象创建时自动执行
构造函数的函数名与类名相同
构造函数没有返回值类型也没有返回值
构造函数不能被显示调用
构造函数的定义也可在类外进行
构造函数也毕竟是函数, 与普通函数相同, 构造函数也支持重载(不过要防止出现二义性)
当在类中定义任意构造函数后(包括拷贝构造函数),C++编译器就不会为我们定义默认构造函数,也就是说我们定义了构造函数就必须要使用
构造函数的分类
- [1] 无参构造函数:构造函数没有函数参数
- 调用形式:Class objective;
- [2] 有参构造函数:有函数参数
- 调用形式:Class objective(arg1, arg2,…);
- [3] 拷贝构造函数:构造函数的参数为 const ClassName &vriable
- [1] 无参构造函数:构造函数没有函数参数
默认构造函数
当用户没有显示定义构造函数时,编译器会为类生成一个默认的构造函数,称为“默认构造函数”
默认构造函数不能完成对象数据成员的初始化
- 只能给对象创建一个标识符
- 并为对象中的数据成员开辟一定的内存空间
2. 析构函数
什么是析构函数
与构造函数相反, 析构函数是在对象被撤销时被自动调用
用于对成员撤销时的一些清理工作, 例如在前面提到的手动释放使用 new 或 malloc 进行申请的内存空间
析构函数的特点
析构函数的函数名与类名相同,在名称前面加上波浪号~与构造函数进行区分,如~constructor();
析构函数只能有一个,不能被重载。因为构造函数没有返回类型,也没有返回值
析构函数可以被显示调用,以释放对象中动态申请的内存
当对象生命周期临近结束时,析构函数由C++编译器自动调用
默认析构函数
当用户没有显示定义析构函数时,编译器同样会为对象生成一个默认的析构函数。
但默认析构函数只能释放类的普通数据成员所占用的空间,无法释放通过 new 或 malloc 申请的空间
因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放,以免造成内存泄漏
3. 拷贝构造函数
- 什么是拷贝构造函数
- 如果一个构造函数的第一个参数是“自身类”类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
class A
{
public:
A(); //无参构造函数
A(const A &a); //拷贝构造函数;const可省略,但此参数几乎总是一个const的引用!
...
};
拷贝构造函数的调用方式
[1] 当用对象1 初始化 对象2的时候拷贝构造函数被调用
[2] 当用对象1 括号方式初始化 对象2的时候拷贝函数被调用
[3] 当用对象(非指针或引用)做函数参数的时候,实参传递给形参的过程会调用拷贝构造函数
[4] 当用对象(非指针或引用)做函数返回值的时候,若用同一类型来接收该返回值,则会执行拷贝构造函数(此时会涉及匿名对象的去留问题,因为返回对象是个匿名对象)
浅拷贝和深拷贝
- 浅拷贝:
- 拷贝构造函数有四种调用方式,当我们不写拷贝构造函数的时候,C++默认的帮我们写了拷贝构造函数,其函数体实现的是简单的成员变量的值的拷贝。这种默认的拷贝方式,我们称为浅拷贝,即只是简单的复制成员变量的值到另一个对象中
深拷贝:
- 当我们的成员变量是动态分配的堆内存的时候,这个时候C++默认的为我们写的拷贝构造函数把成员变量的值进行了复制,那么副本也会指向原始对象所分配的堆内存,当我们在析构函数中释放堆内存的时候,就会发生两次析构现象(原始对象释放堆内存 + 副本对象释放堆内存)因此造成程序崩溃。深拷贝则不让指针指向同一地址,重新开辟一段内存给新的对象的指针数据成员
总结起来就是:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制(包括资源)过程的时候,这个过程就可以叫做深拷贝,反之如果复制过程并未复制资源的情况视为浅拷贝, 浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。 (见例4)
- 位拷贝拷贝的是地址,值拷贝拷贝的是内容
- 浅拷贝:
4. 用法举例
- (1) 无参构造函数及其调用
#include <iostream>
using namespace std;
class Circle
{
private:
double r;
public:
Circle() //无参构造函数
{
cout<<"无参构造函数被调用"<<endl;
}
void serRadius(double r)
{
this -> r = r; //this指针的使用
}
};
void main()
{
Circle circle; //无参构造函数的调用
}
- (2) 有参构造函数及其调用
#include <iostream>
using namespace std;
class Circle
{
private:
double r;
int a;
int b;
public:
Circle(int a, int b) //有参构造函数定义
{
cout<<"有参构造函数调用"<<endl;
this -> a = a;
this -> b = b;
}
void setRadius(double r)
{
this -> r = r;
}
};
void main()
{
/* 有参构造函数调用方式一 */
Circle circle1(1, 2);
/* 有参构造函数调用方式二:手工直接调用构造函数 */
Circle circle2 = Circle(1, 2);
}
- (3) 析构函数及其调用
#include<iostream>
using namespace std;
class Student
{
private:
char *name;
int height;
public:
Student(char * name, int height) //有参构造函数
{
this ->name = (char *)malloc(sizeof(name) + 1);
strcpy(this -> name, name);
this ->height = height;
}
Student(const Student &s) //拷贝构造函数,const可省略
{
this -> name = s.name;
this -> height = s.height;
}
~Student()
{
if (this ->name != NULL)
{
free(this -> name); //释放内存空间
this ->name = NULL;
this ->height = 0;
}
cout<<"析构函数被调用"<<endl;
}
};
int main()
{
Student student("张三", 175); //有参构造函数调用
return 0;
}
- (4) 浅拷贝和深拷贝
/* 浅拷贝示例 */
//紧接上例
int main()
{
Student stu("张三", 175);
Student stu_copy(stu); //两个name指针指向malloc分配的内存
return 0;
}
//当调用析构函数释放内存时,就会发生错误
/* 深拷贝示例 */
//如果将构造函数Student(Student &s)改成
Student (Student &s)
{
this ->name = (char *)malloc(sizeof(name) + 1);
this ->name = s.name;
this ->height = height;
}
//则两个name指针指向不同内存地址,析构时不再发生错误
- (5) 拷贝构造函数及其调用
#include<iostream>
using namespace std;
class Location
{
private:
int xPos;
int yPos;
public:
Location(int x, int y)
{
this ->xPos = x;
this ->yPos = y;
cout<<"有参构造函数被调用"<<endl;
}
Location(const Location& location)
{
this -> xPos = location.xPos;
this -> yPos = location.yPos;
cout<<"拷贝构造函数被调用"<<endl;
}
~Location()
{
cout<<"析构函数被执行"<<endl;
}
};
/* 对象作为函数参数 */
void setLocation(Location location)
{
cout<<"全局函数setLocation()被调用..."<<endl;
}
/* 对象作为函数返回值 */
Location getLocation()
{
Location location(10, 20); //匿名对象。有参构造函数调用,对成员变量赋值
return location;
}
void main()
{
/* 拷贝构造函数调用方式一 */
cout << "###############方式一:对象初始化另一个对象###############" << endl;
Location loca1(1, 2);
Location loca2 = loca1;
/* 拷贝构造函数调用方式二 */
cout << "###############方式二:对象括号方式初始化另一个对象###############" << endl;
Location loca3(5, 10);
Location loca4(loca3);
/* 拷贝构造函数调用方式三 */
cout << "###############方式三:对象作为函数参数###############" << endl;
Location loca5(10, 20);
setLoacation(loca5);
/* 拷贝构造函数调用方式四 */
cout << "###############方式四:对象作为函数返回值###############" << endl;
//1. 匿名对象被销毁:getLocation()产生匿名对象赋值给loca6,匿名对象执行析构函数
Location loca6;
loca6 = getLocation();
//2. 匿名对象被扶正:getLocation()产生匿名对象赋值给loca7,匿名对象直接转成新对象
Location loca7 = getLocation();
}
Acknowledgements:
http://www.cnblogs.com/mr-wid/archive/2013/02/19/2917911.html
http://www.cnblogs.com/metalsteel/p/6266505.html
http://blog.csdn.net/tiantang46800/article/details/6938762
2017.10.2