C++对象生命周期管理:构造、析构与拷贝机制

C++对象生命周期管理:构造、析构与拷贝机制

一、构造函数

  • 构造函数和析构函数是在类体中说明的两种特殊的成员函数,构造函数是在创建对象时,使用给定的值来讲对象初始化。
  • 析构函数功能相反,是在系统释放对象前,对对象做一些善后工作,构造函数可以带参数,可以重载,同时没有返回值。
  • 构造函数是类的成员函数,系统约定构造函数名必须与类名相同,构造函数提供了初始化对象的一种简单方法。
cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
public:
    CTestA(int a,int b){
        x = a;
        y = b;
        cout << "x=" << x << ",y" << y << endl;
    }
    int sumxy() {
        return (x + y);
    }
private:
    double x, y;
};


int main() {
    CTestA obja(10,20); // 只要程序运行,自动会初始化构造函数
    cout << "x+y=" << obja.sumxy();
    return 0;
}

说明:

  • 构造函数的函数名必须与类名相同,构造函数的主要作用是完成初始化对象的数据成员以及其他的初始化工作。
  • 在定义构造函数时,不能指定函数返回值的类型,也不能指定为void类型。
  • 一个类可以定义若干个构造函数,当定义多个构造函数时,必须满足函数重载的原则。
  • 构造函数可以指定参数的缺省值。
  • 若定义的类要说明该类的对象时,构造函数必须是公有成员的函数,如果定义的类仅用于派生其他类的时候,则可将构造函数定义为保护的成员函数。
  • 由于构造函数属于类的成员函数,他对私有成员,保护的数据成员和公有的数据成员均能进行初始化。

构造函数执行顺序

对局部对象,静态对象,全局对象的初始化对于局部对象来说,每次定义对象的时候,都要调用构造函数,对于静态对象,是在首次定义对象时,调用构造函数的,且由于对象一直存在,只调用一次构造函数。对于全局对象,在main函数执行之前调用构造函数的

cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
public:
    CTestA(double a) {
        x = a;
        y = 0;
        cout << "初始化全局对象数据" << endl;
    }

    CTestA(double a,double b) {
        x = a;
        y = b;
        cout << "初始化自动局部对象" << endl;
    }

    CTestA() {
        x = 0;
        y = 0;
        cout << "初始化静态局部静态对象" << endl;

    }
private:
    double x ,y ;

};

CTestA objb(88.8); // 全局对象初始化优先级最高,排在第一

void FunctionTest() {
    cout << "程序开始进入FunctionTest()函数\n\n" << endl;
    CTestA objc(10, 20);
    static CTestA objd; // 初始化局部的静态对象
}

int main() {
    cout << "\n程序开始执行->main()函数\n\n";
    CTestA obja(4.5, 6.5); // 定义局部对象
    FunctionTest();
    return 0;
}

缺省的构造函数

缺省的构造函数:在定义类的时候,若没有定义类的构造函数,则编译器自动 产生一个缺省的构造函数,其格式为className::className(){ },缺省的构造函数并不对所产生对象的数据成员赋初值,即新产生对象的数据成员的值是不确定的。

cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
public:
    CTestA() {
        cout << "调用缺省构造函数.\n\n";
    }
    void setxy(int a, int b) {
        x = a;
        y = b;
    }
    void disp() {
        cout << "x=" << x << ",y=" << y << "\n\n";
    }
private:
    double x, y;
};

int main() {
    CTestA obja, objb; // 产生对象的时候,自动会调用缺省的构造函数,不赋值
    obja.setxy(40, 90);
    cout <<"obja结果对象为:\n" << endl;
    obja.disp();

    cout << "objb结果对象为:\n" << endl;
    objb.disp();
    return 0;
}

关于缺省的构造函数说明:

  1. 在定义类的时候,只要显式定义了一个类的构造函数,则编译器就不产生缺省的构造函数。
  2. 所有的对象在定义时,必须调用构造函数,不存在没有构造函数的对象。
  3. 在类中,若定义了没有参数的构造函数,或各参数均有缺省值的构造函数也称为缺省的构造函数,缺省的构造函数只能有一个。
  4. 产生对象时,系统必定要调用构造函数,所以任意对象的构造函数必须唯一。

构造函数与new运算符

  • 可以使用new运算符来动态建立对象,建立时要自动调用构造函数,以便完成初始化对象的数据成员,最后返回这个动态对象的起始地址。
  • new运算符产生的动态对象,在不在使用这种对象时,必须用delete运算符来释放对象所占用的存储空间。
  • 用new建立类的对象时,可以使用参数初始化动态空间。
cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
private:
    double x, y;
public:
    CTestA() {  // 缺省构造函数
        x = 0;
        y = 0;
        cout << "调用缺省构造函数" << endl;
    }
    CTestA(double a, double b) {  // 带参构造函数
        x = a;
        y = b;
        cout << "调用带参构造函数" << endl;
    }
    void dispxy() {
        cout << "x=" << x << ",y=" << y << "\n\n";
    }
};

int main() {
    CTestA* pobja, * pobjb; // 创建两个对象指针
    pobja = new CTestA; // 用new动态开辟存储空间,调用缺省构造函数

    pobjb = new CTestA(200,800);// 用new动态开辟存储空间,进行初始化,调用带参构造函数

    pobja->dispxy();
    pobjb->dispxy();

    delete pobja;  // 使用delete释放动态开辟存储空间
    delete pobjb;
    return 0;
}

实现类型转换的构造函数

同类型的对象可以相互赋值,相当于类中的数据成员相互赋值;如果直接赋值将数据赋给对象,所赋入的数据需要强制类型转换,这种转换需要调用构造函数。

cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
public:
    CTestA(double a, double b) {
        x = a;
        y = b;
        cout << "调用带参的构造函数." << endl;
    }
    ~CTestA(){
        cout << "调用析构函数" << endl;
    }
    void dispxy() {
        cout << x << "," << y << endl << endl;
    }
private:
    double x, y;
};

int main() {
    CTestA obj1(2, 3);
    obj1.dispxy();
    obj1 = CTestA(90, 20);
    obj1.dispxy();
    return 0;
}

析构函数

什么是析构函数

析构函数的作用与构造函数正好相反,是在对象的生命周期结束时,释放系统为对象所分配的空间,既要撤消一个对象。析构函数也是类的成员函数,定义析构函数的格式为:

cpp 复制代码
ClassName::~ClassName(){
	....
	// 函数体
}

示例:

cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
private:
    double x, y;
public:
    CTestA() {
        cout << "调用缺省构造函数" << endl;
    }
    CTestA(double a, double b) {
        x = a;
        y = b;
        cout << "调用带参构造函数" << endl;
    }
    ~CTestA() {
        cout << "调用析构函数" << endl;
    }
};

int main() {
    CTestA obj1;
    CTestA obj2(5.6, 70.8);
    CTestA obj3(10, 20);
    return 0;
}

析构函数的特点

  • 析构函数是成员函数,函数体可写在类体内,也可写在类体外。
  • 析构函数是一个特殊的成员函数,函数名必须与类名相同,并在其前面加上~,以便和构造函数名相区别。
  • 析构函数不能带有任何参数,不能有返回值,不能指定函数类型。
  • 一个类中,只能定义一个析构函数,析构函数不允许重载。
  • 析构函数是在撤消对象时,由系统自动调用的。
  • 在程序的执行过程中,当遇到某一对象的生存期结束时候,系统自动调用析构函数,然后再收回为对象分配的存储空间。
  • 在程序的执行过程中,对象如果用new运算符开辟了空间,则在类中应该定义一个析构函数,并在析构函数中使用delete删除由new分配的内存空间。因为在撤消对象时,系统自动收回对象所分配的存储空间,而不能自动收回由new分配的动态存储空间。

缺省的析构函数

若在类的定义中,没有显式地定义析构函数时,则编译器自动地产生一个缺省的析构函数,其格式为:

cpp 复制代码
ClassName::~ClassName(){};

任何对象都必须有构造函数和析构函数,但在撤消对象时,要释放对象的数据成员用new运算符分配的动态空间时候,必须显式地定义析构函数。

拷贝构造函数

拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其形参必须是引用,但并不限制为const,一般普遍的会加上const限制,此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。常用于常量方式调用,另外也可以用非常量方式调用。

什么是拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化创建的对象。

拷贝构造函数通常用于:

  1. 通过使用另一个同类型的对象来初始化新创建的对象;
  2. 复制对象把它作为参数传递给函数;
  3. 复制对象并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

拷贝构造函数常见格式如下:

cpp 复制代码
classname(const classname &obj){
	// 构造函数的主体
}

例如:

cpp 复制代码
#include <iostream>
using namespace std;

class CTestA {
public:
    CTestA(int a); // 构造函数
    CTestA(const CTestA &obj); // 拷贝构造函数
    ~CTestA(); // 析构函数
    int getPtr() {
        return *ptr;
    }
private:
    int* ptr;
};
// 构造函数
CTestA::CTestA(int a) {
    cout << "调用构造函数" << endl;
    ptr = new int;
    *ptr = a;
} 
// 拷贝构造函数
CTestA::CTestA(const CTestA& obj) {
    cout << "调用拷贝构造函数并为指针ptr分配内存空间.\n" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
} 
// 析构函数
CTestA::~CTestA() {
    cout << "释放内存空间" << endl;
    delete ptr;
}

void dispptr(CTestA obj) {
    cout << "ptr值为:" << obj.getPtr() << endl;
}
int main() {
    CTestA obj(6000);
    dispptr(obj);
    return 0;
}