C++学习笔记5:构造函数与析构函数

构造函数

定义:名字与类名相同,可以有参数,不能有返回值(void也不行)
作用:对对象进行初始化,如给成员变量进行赋初值
性质:
     (1)如果定义类时没有写构造函数,编译器会生成一个默认的无参数的构造函数,该函数不做任何操作
     (2)如果定义了构造函数,则编译器不会生成默认无参数的构造函数
     (3)对象生成时构造函数自动被调用,对象一旦生成,再也不能执行构造函数
     (4)一个类可以有多个构造函数
为什么需要构造函数:
    (1)执行必要的初始化操作
    (2)有时对象没被初始化就使用,会导致程序出错。

eg:1 使用默认构造函数
    class Complex{
        private:
            double real, imag;
        public:
            void Set(double r, double i);
    };  // 编译器自动生成默认构造函数
    Complex c1; // 默认构造函数被调用
    Complex* pc = new Complex;  // 默认构造函数被调用

eg:2 使用自定义构造函数
    class Complex{
        private:
            double real, imag;
        public:
            Complex(double r, double i = 0);    // 自定义构造函数
    };  // 编译器自动生成默认构造函数

    Complex::Complex(double r, double i)
    {
        real = i; imag = r;
    }

    Complex c1; // error 缺少构造函数的参数
    Complex* pc = new Complex;  // error, 没有参数
    Complex c1(2);  // ok  第二个参数可缺省
    Complex c1(2, 4), c2(3, 5);
    Complex* pc = new Complex(3, 4);

eg:3 可以有多个构造函数,参数个数或者类型不同(见本工程下Complex类)
构造函数在数组中的使用
    class Test
    {
        public:
            Test(int n){}   // (1)
            Test(int n, int m){}    // (2)
            Test(){}    // (3)
    };

    Test array1[3] = {1, Test(1, 2)};
    // 三个元素分别使用 (1)(2)(3)进行初始化

    Test array2[3] = {Test(2, 3), Test(1, 2), 1};
    // 三个元素分别使用 (2)(2)(1)进行初始化

    Test* pArray[3] = {new Test(4), new Test(1, 2)};
    // 两个元素分别使用(1)(2)初始化

复制构造函数

1 定义:只有一个参数,即对同类对象的引用

2 格式:X::X(X&) 或者 X::X(const X&)  后者以常量对象作为参数

3 如果没有定义复制构造函数,编译器自动生成默认复制构造函数。默认复制构造函数完成赋值功能。

4 eg:
    class Complex{
        private:
            double real, imag;
    };
    Complex c1; // 调用缺省无参构造函数
    Complex c2(c1); // 调用缺省的复制构造函数,将c2初始化成和c1一样。

5 如果自己定义复制构造函数,则默认的复制构造函数不存在
    class Complex{
        public:
            double real, imag;
        Complex(){}
        // 自定义复制构造函数
        Complex(const Complex& c){
            real = c.real;
            imag = c.imag;
            cout << "Copy Constructor called";
        }
    };
    Complex c1;
    Complex c2(c1);

6 不允许有形如 X::X(X)的构造函数
    class CSample{
        CSample(CSample c){}    // 错误,这不是复制构造函数
    }

7 复制构造函数起作用的三种情况
    (1)当一个对象去初始化同类的另一个对象的时候
        Complex c2(c1);
        Complex c2 = c1;    // 初始化语句,非赋值语句
    (2)如果某一个函数有一个参数是类A的对象,那么该函数被调用时候,类A的复制构造函数将会被调用
        eg:
            class A
            {
                public:
                    A(){};
                    A(A& a){
                        cout << "Copy constructor called" << endl;
                    }
            };
            void Func(A a1);
            int main(){
                A a2;
                Func(a2);   // 调用复制构造函数初始化
                return 0;
            }
    (3)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
        eg:
            class A
            {
                public:
                    int v;
                    A(int n) {v = n;}
                    A(const A& a){
                        v = a.v;
                        cout << "Copy constructor called";
                    }
            }
            A Func(){
                A b(4);
                return b;
            }
            int main(){
                // 调用复制构造函数,因为Func()函数返回值是b,则复制构造函数的参数是b
                cout << Func().v << endl; return 0;
            }

8 对象间的赋值,并不导致复制构造函数被调用(见本工程CMyclass.cpp)

//
// 演示对象间的赋值并不导致复制构造函数被调用
//

#include <iostream>

using namespace std;

class CMyclass{
public:
    int n;
    CMyclass()// 默认构造函数
    {
        cout << "默认构造函数被调用" << endl;
    }
    CMyclass(CMyclass& c)   // 复制构造函数
    {
        n = 2 * c.n;
        cout << "复制构造函数被调用"<< endl;
    }
};

int main()
{
    CMyclass c1, c2;
    c1.n = 5;
    c2 = c1;    // 代表赋值  调用默认构造函数
    CMyclass c4 = c1;   // 代表初始化 需要调用复制构造函数
    CMyclass c3(c1);
    cout << c2.n << endl;
    cout << c3.n << endl;
    cout << c4.n << endl;
}
9 常量引用参数的使用
    eg:
    void func(CMyclass obj_){
        cout << "fun" << endl;
    }
    (1)这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大
    (2)可以考虑使用CMclass&引用类型作为参数
    (3)如果希望确保实参的数值在函数中不被改变,可以加上const关键字
    eg:
        void func(cosnt CMyclass& obj)
        {
            // 函数中任何试图改变obj值的语句都将时非法的
        }

类型转换构造函数

类型转换构造函数
1 目的:实现类型的自动转换
2 定义:只有一个参数,而且不是复制构造函数的构造函数,一般就可以看做是转换构造函数‘
3 调用:当需要的时候,编译器会自动调用转换构造函数,建立一个无名的临时对象(或者临时变量)
4 例子(见本工程switchConstructor.cpp)

//
// 类型转换构造函数例子
//

#include <iostream>

using namespace std;

class Complex{
public:
    double real, imag;
    Complex(int i){
        cout << "类型转换构造函数被调用" << endl;
        real = i; imag = 0;
    }
    Complex(double r, double i){
        real = r;
        imag = i;
        cout << "构造函数被调用"<< endl;
    }
};

int main()
{
    Complex c1(7, 8);   // 调用构造函数
    Complex c2 = 12;    // 调用类型转化构造函数
    c1 = 9;             // 由于类型转换构造函数的存在,9被自动转换为一个临时Complex对象,然后将这个临时对象赋值给c1
    cout << c1.real << "," << c1.imag << endl;
    cout << c2.real << "," << c2.imag << endl;
    return 0;
}

析构函数

1 定义:名字与类名相同,在前面加“~”,没有参数和返回值,一个类最多只能有一个析构函数。
2 目的:析构函数对象消亡时自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
3 如果定义类的时候没有写析构函数,编译器会自动生成缺省的析构函数,缺省析构函数什么都不做。
4 如果定义了析构函数,则编译器不会生成缺省析构函数。
5 eg:(见本工程xigou.cpp)

//
// 析构函数用例
//

#include <iostream>

using namespace std;

class String{
private:
    char* p;
public:
    // 构造函数
    String(){
        p = new char[10];
        cout << "构造函数被调用" << endl;
    }
    // 析构函数
    ~String(){
        delete[] p;
        cout << "析构函数被调用" << endl;
    }
};

int main()
{

}

6 对象数组在生命周期结束时,对象数组的每个元素的析构函数都会被调用。
7 delete运算符导致析构函数被调用
eg:
Ctest* pTest;
pTest = new Ctest; // 构造函数被调用
delete pTest; // 析构函数被调用
------------------------------------
pTest = new Ctest[3]; // 构造函数被调用3次
delete[] pTest; // 析构函数调用3次
8 若new一个对象数组,那么用delete释放时应该写[]。否则只delete一个对象(调用一次析构函数)
9 重难点:析构函数在对象作为函数返回值返回后被调用(见本工程xigoudiaoyong.cpp)

//
// 析构函数在对象作为函数返回值返回后被调用例子
//
#include <iostream>

using namespace std;

class Myclass{
public:
    ~Myclass(){
        cout << "调用析构函数" << endl;
    }
};


Myclass obj;

Myclass fun(Myclass sobj)    // 参数对象消亡也会导致析构函数被调用
{
    return sobj;    // 函数调用返回时生成临时对象返回
}

int main()
{
    obj = fun(obj); // 函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
    return 0;
}

/**
 * 函数调用分析:
 * 首先调用fun,进入fun函数内,会生成一个形参对象sobj,它使用复制构造函数进行初始化
 * 接着返回sobj,由于一个函数返回时候,这个函数内的局部变量,参数都会消亡,故sobj消亡,调用了析构函数。
 * 继续生成了一个返回值对象,返回值是一个临时对象使用复制构造函数进行初始化
 * 接着将临时对象的值赋值给obj,临时对象一般都是在包含临时对象的这条语句执行完后消亡,故执行完obj = fun(obj);语句后,临时对象消亡,调用析构函数
 * 最后整个程序结束之后,全局对象自动消亡,调用析构函数
 * */

构造函数和析构函数调用时机

1 注意:构造函数只是用来初始化,不用来分配存储空间。析构函数不负责回收整个对象所占的存储空间,它只是在对象存储空间被操作系统回收之前,做善后工作。
2 复制构造函数在不同编译器中的表现(不重要,了解即可)
3 new出来的对象,只有delete它才会消亡,如果不进行delete它是不会消亡的,哪怕整个程序都结束了,它也不会消亡。
4 下面举例说明

//
// 构造函数和析构函数调用时机案例.
//
#include <iostream>

using namespace std;

class Demo{
    int id;
public:
    // 类型转换构造函数
    Demo(int i){
        id = i;
        cout << "id = " << id << " constructed" << endl;
    }
    // 析构函数
    ~Demo(){
        cout << "id = " << id << " destructed" << endl;
    }

};

Demo d1(1); // 第一次输出

void Func(){
    static Demo d2(2);  // 静态局部变量,调用构造函数进行初始化
    Demo d3(3); // 生成对象
    cout << "func" << endl;
}   // 函数结束时,d2不会消亡(原因:静态局部变量在函数结束时候不会消亡,还会维持它原有的值,直到整个程序结束,静态局部变量才会消亡),d3会消亡

int main()
{
    Demo d4(4); // 第二次输出
    d4 = 6; // 由于有类型转换构造函数的存在,则可以直接吧整形转换成一个临时对象,接着将临时对象的值赋值给d4,临时对象执行完这条语句就会消亡,引发析构函数调用
    cout << "main" << endl;
    {
        Demo d5(5); // 局部对象生成  调用构造函数
    }   // 大括号指向结束 局部对象消亡   调用析构函数
    Func(); // 进入Func
    cout << "main ends" << endl;
    return 0;
}   // 函数结束,局部变量d4消亡,此时d4的值是6
// 整个函数结束的时候,全局变量d1和静态变量d2消亡,消亡顺序(先构造的后析构)故先d2消亡,再d1消亡

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值