对象的构造和析构1
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数主要用于对象 销毁前系统自动调用,执行一些清理工作。
1.构造和析构函数
1.1 构造函数和析构函数的概念
构造函数语法:
- 构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
- ClassName(){}
析构函数语法:
- 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
- ~ClassName(){}
class Person{
public:
Person(){
cout << "构造函数调用!" << endl;
pName = (char*)malloc(sizeof("John"));
strcpy(pName, "John");
mTall = 150;
mMoney = 100;
}
~Person(){
cout << "析构函数调用!" << endl;
if (pName != NULL){
free(pName);
pName = NULL;
}
}
public:
char* pName;
int mTall;
int mMoney;
};
void test(){
Person person;
cout << person.pName << person.mTall << person.mMoney << endl;
}
1.2 C++编译器构造析构方案 PK 对象显示初始化方案
//为什么对象需要初始化 有什么样的初始化方案
#include "iostream"
using namespace std;
/*
思考为什么需要初始化
面向对象思想来自生活,手机、车、电子产品,出厂时有初始化
怎么样进行初始化?
方案1:显示调用方法
缺点:易忘、麻烦;显示调用init,不能完全解决问题
*/
class Test21
{
public:
int m;
int getM() const { return m; }
void setM(int val) { m = val; }
int n;
int getN() const { return n; }
void setN(int val) { n = val; }
public:
int init(int m,int n)
{
this->m = m;
this->n = n;
return 0;
}
protected:
private:
};
int main()
{
int rv =0;
Test21 t1; //无参构造函数的调用方法
Test21 t2;
//t1.init(100, 200);
//t2.init(300, 400);
cout<<t1.getM()<<" "<<t1.getN()<<endl;
cout<<t2.getM()<<" "<<t2.getN()<<endl;
//定义对象数组时,没有机会进行显示初始化
Test21 arr[3];
//Test arr_2[3] = {Test(1,3), Test(), Test()};
system("pause");
return rv;
}
2 构造函数的分类及调用
- 按参数类型:分为无参构造函数和有参构造函数
- 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
class Person{
public:
Person(){
cout << "no param constructor!" << endl;
mAge = 0;
}
//有参构造函数
Person(int age){
cout << "1 param constructor!" << endl;
mAge = age;
}
//拷贝构造函数(复制构造函数) 使用另一个对象初始化本对象
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
//打印年龄
void PrintPerson(){
cout << "Age:" << mAge << endl;
}
private:
int mAge;
};
//1. 无参构造调用方式
void test01(){
//调用无参构造函数
Person person1;
person1.PrintPerson();
//无参构造函数错误调用方式
//Person person2();
//person2.PrintPerson();
}
//2. 调用有参构造函数
void test02(){
//第一种 括号法,最常用
Person person01(100);
person01.PrintPerson();
//调用拷贝构造函数
Person person02(person01);
person02.PrintPerson();
//第二种 匿名对象(显示调用构造函数)
Person(200); //匿名对象,没有名字的对象
Person person03 = Person(300);
person03.PrintPerson();
//注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型
Person person06(Person(400)); //等价于 Person person06 = Person(400);
person06.PrintPerson();
//第三种 =号法 隐式转换
Person person04 = 100; //Person person04 = Person(100)
person04.PrintPerson();
//调用拷贝构造
Person person05 = person04; //Person person05 = Person(person04)
person05.PrintPerson();
}
b为A的实例化对象,A a = A(b) 和 A(b)的区别?
当A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你A(b) 等价于 A b.
注意: 不能调用拷贝构造函数去初始化匿名对象,也就是说以下代码不正确:
class Teacher{
public:
Teacher(){
cout << "默认构造函数!" << endl;
}
Teacher(const Teacher& teacher){
cout << "拷贝构造函数!" << endl;
}
public:
int mAge;
};
void test(){
Teacher t1;
//error C2086:“Teacher t1”: 重定义
Teacher(t1); //此时等价于 Teacher t1;
}
3. 拷贝构造函数调用时机
第1个调用场景
#include "iostream"
using namespace std;
class AA
{
public:
AA() //无参构造函数 默认构造函数
{
cout<<"我是构造函数,自动被调用了"<<endl;
}
AA(int _a) //无参构造函数 默认构造函数
{
a = _a;
}
AA(const AA &obj2)
{
cout<<"我也是构造函数,我是通过另外一个对象obj2,来初始化我自己"<<endl;
a = obj2.a + 10;
}
~AA()
{
cout<<"我是析构函数,自动被调用了"<<endl;
}
void getA()
{
printf("a:%d \n", a);
}
protected:
private:
int a;
};
//单独搭建一个舞台
void ObjPlay01()
{
AA a1; //变量定义
//赋值构造函数的第一个应用场景
//用对象1 初始化 对象2
AA a2 = a1; //定义变量并初始化 //初始化法
a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
}
第2个应用场景
//单独搭建一个舞台
void ObjPlay02()
{
AA a1(10); //变量定义
//赋值构造函数的第一个应用场景
//用对象1 初始化 对象2
AA a2(a1); //定义变量并初始化 //括号法
//a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
a2.getA();
}
//注意:初始化操作 和 等号操作 是两个不同的概念
第3个调用场景
#include "iostream"
using namespace std;
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
Location( const Location & p ) //复制构造函数
{
X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
//alt + f8 排版
void f ( Location p )
{
cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ;
}
void mainobjplay()
{
Location A ( 1, 2 ) ; //形参是一个元素,函数调用,会执行实参变量初始化形参变量
f ( A ) ;
}
void main()
{
mainobjplay();
system("pause");
}
第4个调用场景
#include "iostream"
using namespace std;
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
Location( const Location & p ) //复制构造函数
{
X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
//alt + f8 排版
void f ( Location p )
{
cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ;
}
Location g()
{
Location A(1, 2);
return A;
}
//对象初始化操作 和 =等号操作 是两个不同的概念
//匿名对象的去和留,关键看,返回时如何接
void mainobjplay()
{
//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
//Location B;
//B = g(); //用匿名对象 赋值 给B对象,然后匿名对象析构
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
Location B = g();
cout<<"传智扫地僧测试"<<endl;
}
void main()
{
mainobjplay();
system("pause");
}
- 对象以值传递的方式传给函数参数
- 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
- 用一个对象初始化另一个对象
class Person{
public:
Person(){
cout << "no param contructor!" << endl;
mAge = 10;
}
Person(int age){
cout << "param constructor!" << endl;
mAge = age;
}
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
~Person(){
cout << "destructor!" << endl;
}
public:
int mAge;
};
//1. 旧对象初始化新对象
void test01(){
Person p(10);
Person p1(p);
Person p2 = Person(p);
Person p3 = p; // 相当于Person p2 = Person(p);
}
//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
void doBussiness(Person p){}
void test02(){
Person p(10);
doBussiness(p);
}
//3. 函数返回局部对象
Person MyBusiness(){
Person p(10);
cout << "局部p:" << (int*)&p << endl;
return p;
}
void test03(){
//vs release、qt下没有调用拷贝构造函数
//vs debug下调用一次拷贝构造函数
Person p = MyBusiness();
cout << "局部p:" << (int*)&p << endl;
}
编译器存在一种对返回值的优化技术,RVO(Return Value Optimization).在vs debug模式下并没有进行这种优化,所以函数MyBusiness中创建p对象,调用了一次构造函数,当编译器发现你要返回这个局部的对象时,编译器通过调用拷贝构造生成一个临时Person对象返回,然后调用p的析构函数。
我们从常理来分析的话,这个匿名对象和这个局部的p对象是相同的两个对象,那么如果能直接返回p对象,就会省去一个拷贝构造和一个析构函数的开销,在程序中一个对象的拷贝也是非常耗时的,如果减少这种拷贝和析构的次数,那么从另一个角度来说,也是编译器对程序执行效率上进行了优化。
所以在这里,编译器偷偷帮我们做了一层优化:
当我们这样去调用: Person p = MyBusiness();
编译器偷偷将我们的代码更改为:
void MyBussiness(Person& _result){
_result.X:X(); //调用Person默认拷贝构造函数
//.....对_result进行处理
return;
}
int main(){
Person p; //这里只分配空间,不初始化
MyBussiness(p);
}