C++学习笔记(二)——构造函数和析构函数
我的博客:竹山听雨
文章目录
当建立一个对象时,对象的状态(数据成员的取值)是不确定的。构造函数是C++中一个特殊的成员函数,它可自动进行对象的初始化。而析构函数在对象撤销时执行清理任务,进行善后处理
特性
-
没有返回值说明,也就是定义构造函数和析构函数不能指出函数返回值得类型,void也不能有
-
它们不能被继承
-
构造函数可以有缺省常熟
-
析构函数可以是虚的,单构造函数不可以是虚的
-
不可取他们的地址
-
不能用常规调用方法调用析构函数,当使用完全的限定名(带对象名。类名和函数名)时,可以调用析构函数
-
当定义对象时,编译程序自动调用构造函数,当删除对象时,编译程序自动地调用析构函数
构造函数
对象的初始化是指对象数据成员的初始化,在使用对象前,一定要初始化,但因为数据成员一般为私有的(private),所以不能直接赋值。
一种方法是类中提供一个普通成员函数来初始化,但是可能会造成使用不便(使用对象前必须显式调用该函数)和不安全(未调用初始化函数就使用)
另一种方法就是使用了构造函数对对象进行初始化。
定义: 构造函数是一个与类同名,没有返回值的特殊成员函数。构造函数既可在类外定义,也可以作为内联函数在类内定义。
使用: 在定义该类对象时,可以将参数传递给构造函数来初始化对象。一般用于初始化类的数据成员,每当创建一个对象时(包括用new动态创建),会自动调用构造函数
声明: <类名>::构造函数
//类内定义
class Student{
public:
Student(int id,string name)
{
...
}
...
}
//类外定义
class Student{
public:
Student();
...
}
Student::Student(int id,string name)
{
...
}
//调用:
int main()
{
Student s1(2,"bamboosir920");
return 0;
}
注意: 构造函数也可以进行重载,构造函数也可以使用缺省参数
缺省构造函数(默认构造对象)
功能: 缺省构造参数是个无参构造函数,它仅负责创建对象,而不做任何初始化
背景: C++规定,每个类必须有一个构造函数,没有构造函数,就不能创建任何对象。若用户未显式定义一个类的构造函数,则提供给一个缺省的构造函数
eg:
class test{
public:
test();
test(int n);
}
test::test()
{
cout<<"xxx";
}
test::test()
{
cout<<"xxx"
}
int main()
{
test x;//调用无参数的构造函数
test y(2);//调用带两个参数的构造函数
}
创建对象数组
test stu[5]={test(2),test(3),test(4),test(5),test(1)}
拷贝构造函数(复制构造函数)
功能: 是用一个已有的对象来初始化一个被创建的同类对象,是一种特殊的构造函数,具有一般构造函数的所有特性
特点: 其形参是本类对象的引用
声明:类名(类名& 对象名)
class Student{
public:
Student();
Student(Student &);//拷贝构造函数的声明
private:
int id;
String name;
double score;
...
}
Student::Student(Student &other)
{
id=other.id;
name=other.name;
score=other.score;
}
以下四种情况会自动调用拷贝构造函数
方法1
Student s1;
Student s2(S1);
方法2
用类的一个对象去初始化另一个对象的
Student s2=s1;
方法3
对象作为函数参数传递时,调用拷贝构造函数
void f(Student a){} //定义f函数,形参为Student类对象
Student b;
f(b);
方法4
Student f() //定义f函数,返回值为Student类对象
{
Student a;
...
return a;
}
Student b;
b=f();
深拷贝构造函数和浅拷贝构造函数
区别:
- 浅拷贝,即只复制对象空间,不复制对象资源
- 深拷贝,既复制对象空间,又复制资源
默认提供的构函数只是对对象进行浅拷贝
一般情况下,只需要使用系统提供的浅拷贝构造函数即可,但是如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,因为两个对象都拥有一个同一个资源,因此必须自定义构造函数
条件:
- 肯定要调用拷贝构造函数
- 数据成员包括指向堆内存的指针
class test{
public:
test(char *na) //构造函数
{
name=new char[strlen(na)+1];
if(name!=0)
{
strcpy(name,na);
}
...
}
test(test& p) //深拷贝构造函数
{
name=new char[strlen(p.name)+1]; //复制资源
if(name!=0)
{
strcpy(name,p.name); //复制对象资源
}
}
~test(){delete name;} //析构函数
private:
char* name;
}
int main()
{
test a("su");
test li(a);
return 0;
}
当在main()
中调用了深构造函数,如果却没有定义深构造函数,就会默认调用缺省的浅拷贝构造函数,就会发生报错
当数据成员有使用new
动态分配内存空间,在析构函数中使用delete
进行动态内存空间的释放以及赋值=运算符进行重载,应该自定义深拷贝构造函数
构造函数对数据成员的初始化
构造函数可以采用以下几种不同的形式对数据成员进行初始化
在构造函数的函数体中进行初始化
void test(int sid,string sname)
{
id=sid;
name=sname;
}
使用构造初始化表对数据成员进行初始化
<类名> ::<构造函数>(<参数表>):<变量1>(<初值1),....<变量n>(<初值n>)
{…}
注意:
- 数据成员初始化的持续取决于他们在类定义中的声明次序,与他们在成员初始化表中的次序无关
- 常量和引用的初始化必须放在构造函数正在建数据成员结构的时候,也就是放在构造函数的冒号后面
class test
{
public:
test(int sid,string sname):id(sid),name(sname)
{}
private:
int id;
string name;
}
类类型和基本数据类型的转换
构造函数用作类型转换
前提: 此类一定是只带一个参数的构造函数
class A
{
public:
A();
A(int);
}
类类型转换函数
在函数体中声明
operator type();
type为要转换的基本类型名,既没有参数也没有返回类型,但是在函数体重必须具有一个type类型的对象
定义转换函数的函数体
类名::opertor type()
{
...
return type 类型的值
}
使用类型转换函数
析构函数
功能: 当对象被撤销时,释放该对象占用的空间
作用: 当对象消亡时,系统将自动调用析构函数,执行一些在对象撤销前必须执行的清理任务
特点:
- 不能有任何的返回类型,也不能使用void
- 析构函数没有参数,每个类只能有一个析构函数
- 析构函数的函数名为
~类名
- 析构函数即可以系统自动调用也可以手工调用
自动调用的三种情况:
-
一个动态分配的对象被删除,即使用delete删除对象时,编译系统会自动调用析构函数
-
程序运行结束时
-
一个编译器生成的临时对象不再需要时
析构函数的手工调用
对象名.类名::析构函数名
析构函数与构造函数的调用顺序
先构造后析构
class A
{
public:
A(){..}
A(A & S){...} //拷贝构造函数
~A(){
cout<<<"删除";
}
...
}
int main(){
A X();
A y(x); //拷贝构造
return 0;
//函数运行结束,先析构x,再析构y
}