C++_CH18_构造函数与析构函数
1 类的默认成员函数
在编写类的时候,C++编译器会默认生成6个默认的函数,但是不显示出来:
需要关注以下两个方面:
第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,
那么如何自己实现?
2 构造函数
构造函数是一种特殊类型的方法,他在类的实例化时被使用。
2.1 一个例子来说明
创建一个Entity类,并给他写一个method,Print(),这样实例化后,调用Print就可以将x,y的值打印到控制台:
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
e.Print();
std::cin.get();
return 0;
}
在低版本的编译器中,得到的输出是两个随机值,当然现在的编译器得到的结果是
0,0
因为X、Y是public的,因此我们可以打印X,Y试试
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
还是对于高版本的编译器,输出为
00
0,0
但是对于低版本的编译器,则会报错:未初始化局部变量。
2.2 诞生需求
默认为低版本编译器。我们需要在创建实例时,就把X和Y初始化为0,0而不是一个随机值。要一个新的方法。
2.3 Init()方法
在class中添加Init(),来初始化
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
void Init()
{
X = 0.0f;
Y = 0.0f;
}
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
e.Init();
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
此时,在低版本编译器下输出也为:
00
0,0
成功完成了初始化。
但是:这样我们每创建一个实例,都需要调用一次Init,这样十分的麻烦。
2.5 诞生新需求
新需求:我们创建实例的时候就自动完成了Init()类似的操作,不需要额外的代码
2.6 构造函数的诞生
2.6.1 构造函数的特点
1. 没有返回值,不需要写返回值类型
2. 函数名与类名一致
3. 对象实例化时系统会自动调用对应的构造函数。
4.构造函数可以重载。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函`数,一旦用户显式定义编译器将不再生成。
6.无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
7.我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。
e.g.
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
Entity()
{
X = 0.0f;
Y = 0.0f;
}
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
output:
00
0,0
此为一个不带参数的构造函数。
2.6.2 带参数的构造函数
以下为带参数构造函数。注意,有两个构造函数,名字都是Entity,此为函数重载。但是构造函数没有重载。
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
Entity()
{
}
Entity(float x,float y)
{
X = x;
Y = y; //用参数给成员变量赋值。
}
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e(10.0f,11.0f);
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
output
1011
10,11
2.6.3不想要默认的构造函数
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
Entity() = delete;
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
因为没有默认的构造函数,此时会报错。
3 析构函数
析构函数是构造函数的对立。它用于销毁实例。
3.1 析构函数的特点
1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值。(这里跟构造类似,也不需要加void)
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4.对象生命周期结束时,系统会自动调用析构函数。
5.跟构造函数类似,我们不写 编译器自动生成的析构函数,对内置类型成员不做处理,自定类型成员会调用他的析构函数。
6.还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
7.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。
8.一个局部域的多个对象,C++规定后定义的先析构。
3.2 实例
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
Entity()
{
X = 0.0f;
Y = 0.0f;
std::cout<<"完成对象创建"<<std::endl;
}
~Entity()
{
std::cout<<"销毁对象完成"<<std::endl;
}
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
int main()
{
Entity e;
std::cout<<e.X<<e.Y<<std::endl;
e.Print();
std::cin.get();
return 0;
}
output
完成对象创建
00
0,0
销毁对象完成
析构函数是在对象的生命结束时运行,这个例子无法看出析构函数实在main函数结束时运行的,我们稍加修改:
#include <iostream>
class Entity
{
public:
float X,Y; //二维坐标下的(x,y)点
Entity()
{
X = 0.0f;
Y = 0.0f;
std::cout<<"完成对象创建"<<std::endl;
}
~Entity()
{
std::cout<<"销毁对象完成"<<std::endl;
}
void Print()
{
std::cout<<X<<','<<Y<<std::endl;
}
};
void func()
{
Entity e;
e.Print();
}
int main()
{
func();
std::cout<<"HellO"<<std::endl;
std::cin.get();
return 0;
}
只要hello是在“销毁对象完成”之后打印的,就证明析构函数是在函数作用域结束的时候调用的。
output:
完成对象创建
0,0
销毁对象完成
HellO
成功证明。
当然用visual studio调试也可以证明。
总之析构函数就防止内存泄露的。