[C++学习笔记04]构造函数与析构函数

  1. 构造函数
      通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用;
      构造函数被声明为私有有特殊的用途;
      如果程序中未声明一个任意构造函数,则系统自动产生出一个默认构造函数(无参构造)
      如果程序中声明了一个任意构造函数,则系统不再产生出一个默认构造函数

      全局对象的构造先于main函数
  2. 析构函数
      
    析构函数不能被重载;
      如果没有定义析构函数,编译器会自动生成一个默认析构函数。

  3. 构造函数与析构函数的直接调用
    int main(void)
    {
        Test(); // 没有定义对象,可以直接调用构造函数
                // 产生一个临时对象,如果没有定义变量接收,那么自动就调用析构函数
    
        //~Test(); // Error,“Test”不定义该运算符或到预定义运算符可接收类型的转换
    
        Test test; // 定义了对象,通过该对象只能直接调用析构函数
        test.~Test(); //OK
        //test.Test(); // Error,  位于“.”运算符右边时非法
    
        return 0;
    }
  4. 构造与析构的顺序
      先构造的对象后析构,当然是同一种情况下,比如都是栈上构造的对象。
  5. 转换构造函数
      1.单个参数的构造函数;
      2.将其它类型转换为类类型;
      3.类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
    int main(void)
    {
        //Test t(10); // 充当的是普通构造函数的功能
    
        Test t2;
        t2 = 10; // 将10这个整数赋值给t2对象
                // 1.编译器调用类型转换构造函数将整数10转换成Test类类型(生成一个临时对象)
                // 2.将临时对象赋值给t2(调用的是=运算符)
                // 3.释放临时对象
    
        cout << "main end.." << endl;
        return 0;
    }
  6. 赋值与初始化区别
    class Test {
    public:
        Test() {
            num_ = 0;
            cout << "Default Construction." << endl;
        }
        Test(int num) {
            num_ = num;
            cout << "A single parameter" << endl;
        }
        ~Test() {
            cout << "Default Deconstruction." << endl;
        }
        // 重载 = 操作
        Test& operator=(const Test &other) {
            cout << "operator = " << endl;
            this->num_ = other.num_;
            return *this;
        }
    
    private:
        int num_;
    };
    
    int main(void)
    {
        Test t = 10; // 初始化,相当于Test t(10);只是调用一个参数的构造函数(普通构造函数)
    
        t = 20; // 赋值操作,调用转换构造函数生成临时对象,将临时对象赋值给t,再销毁临时对象
    
        return 0;
    }
  7. explicit
      只提供给类的构造函数使用的关键字;
      编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象。
    int main(void)
    {
        // 在带一个参数的构造函数之前声明explicit,explicit Test(int num);
        //Test t1 = 10; // Error, 无法从“int”转换为“Test”
                        // 此时Test t1 = 10将不等价与Test t1(10);
    
        Test t1(10); // OK
        //t1 = 20; // 1.Error,不能转换生成临时对象,错误类型是没有int参数的=重载
                    // 2.当我们重载=为Test& operator=(const int other);此时将可以,不会再生成临时Test对象
    
        return 0;
    }
  8. 构造函数初始化列表
      构造函数的执行分为两个阶段
        初始化段
        普通计算段(函数体内的语句,赋值等操作)
    class Test {
    public:
        Test() : num_(0) // 初始化操作
        {
            //num_ = 0; // 赋值操作
            cout << "Default Construction." << endl;
        }
        Test(int num) : num_(num) // 初始化操作
        {
            //num_ = num; // 赋值操作
            cout << "A single parameter" << endl;
        }
        ~Test() 
        {
            cout << "Default Deconstruction." << endl;
        }
    
    private:
        int num_;
    };
  9. 对象成员及其初始化
      一个类中定义成员变量是另一个类类型,最好使用初始化列表初始化,因为会出现那个类没有默认构造函数等情况,你直接赋值是不行的。
        必须使用初始化列表初始化的情况1.
    #include <iostream>
    using namespace std;
    
    class Object {
    public:
        // Object类无默认构造函数
        Object(int num) : num_(num)
        {
            cout << "Object Construction " << num_<<" ..." << endl;
        }
        ~Object()
        {
            cout << "Object Deconstruction " << num_ << " ..." << endl;
        }
    
    private:
        int num_;
    };
    
    class Container {
    public:
        // Container类的Object对象无默认构造函数,必须要使用初始化列表初始化
        // 这是一种必须使用初始化列表初始化的情况
        Container(int num1, int num2) : obj2_(num1), obj1_(num2)
        {
            //obj_ = num; // Error
            cout << "Container Construction  ..." << endl;
        }
        ~Container()
        {
            cout << "Container Deconstruction  ..." << endl;
        }
    
    private:
        // obj1_比obj2_先构造,析构顺序相反,与初始化列表的顺序无关
        Object obj1_;
        Object obj2_;
    };
    
    int main(void)
    {
        /**
            Object 20 构造->Object 10 构造->Container 构造函数->Container 析构函数->Object 10析构->Object 20析构
        */
        Container c(10, 20);
    
        return 0;
    }
  10. const成员、引用成员初始化
      
    必须使用初始化列表初始化的情况2.
    class Object {
    public:
        // 类中有const常量或者引用的时候必须在初始化列表初始化
        // 必须在初始化列表初始化的情况2
        Object(int num) : num_(num), rnum(num)
        {
            //num_ = num; // Error
            //rnum = num; // Error
            cout << "Object Construction " << num_ << " ..." << endl;
        }
        ~Object()
        {
            cout << "Object Deconstruction " << num_ << " ..." << endl;
        }
    
    private:
        const int num_;
        int &rnum;
    };
  11. 类中的常量-枚举
      
    根据以上的例子我们可以看出const的常量在每个对象中的值是不相同的,如果要每个对象都相的常量,可以使用枚举类型,当然个人觉得还是可以使用static const。
      通过使用static const实现:
    // 通过使用static const来实现所有对象的常量
    class Object {
    public:
        // 无法通过构造函数初始化静态类数据
        Object(int num) //: num_(num)
        {
            cout << "Object Construction " << num_ << " ..." << endl;
        }
        ~Object()
        {
            cout << "Object Deconstruction " << num_ << " ..." << endl;
        }
    
    private:
        static const int num_;
    };
    
    const int Object::num_ = 200;

      通过使用enum实现:

    // 通过使用枚举来实现所有对象的常量
    class Object {
    public:
        enum TYPE {
            TYPE_A = 10,
            TYPE_B = 20
        };
    
        /* 
        enum TYPE_ {
            TYPE_A, // Error, 枚举中的枚举数不能重复定义
            TYPE_B
        };*/
    };
    
    int main(void)
    {
        Object obj;
    
        // 引用枚举的三种方式
        cout << Object::TYPE::TYPE_A << endl;
        cout << Object::TYPE_A << endl;
        cout << obj.TYPE_A << endl;
    
        return 0;
    }

  12. 拷贝构造函数
      
    关于拷贝构造函数
         
    功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
        声明: 只有一个参数并且参数为该类对象的引用
         如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员
      
    拷贝构造函数调用的几种情况
    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int num = 0) : num_(num)
        {
            cout << "Default Construction " << num_ << endl;
        }
        Test(const Test &t) : num_(t.num_)
        {
            cout << "Copy Construction " << num_ << endl;
        }
    
        ~Test()
        {
            cout << "Deconstruction " << num_ << endl;
        }
    
    private:
        int num_;
    };
    
    void Func1(const Test t)
    {
    
    }
    
    void Func2(const Test &t)
    {
    
    }
    
    Test Func3(const Test &t)
    {
        return t;
    }
    
    const Test &Func4(const Test &t)
    {
        return t;
    }
    
    int main(void)
    {
        Test t1;
        // Test t2(t1); // 1. 使用存在的对象初始化另一个对象,不会产生临时对象
        //Test t2 = t1; // 1. 使用存在的对象初始化另一个对象,不会产生临时对象
    
        //Func1(t1); // 2. 做函数参数,实参传给形参的过程调用拷贝构造,新参不是引用以及指针,产生临时对象,立即析构
        //Func2(t1); // 引用不会调用拷贝构造
    
        //Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,没有对象来接收,立即析构掉
        //Test t2 = Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,有对象来接收,匿名对象变为有名对象,不会立即析构
    
        /*
        Test t2;
        t2 = Func3(t1);  //这种情况会产生临时对象,然后赋值,赋值完临时对象立即析构
        */
    
        //Test &t2 = Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,引用去引用了临时对象,临时对象不会立即析构
    
        //Test t2 = Func4(t1); // 1.在初始化时调用拷贝构造
        const Test &t2 = Func4(t1); // 当然不会拷贝构造
    
        cout << "......" << endl;
    
        return 0;
    }

     

  13. 深拷贝与浅拷贝
       当类中有动态分配内存的指针成员变量时候,要进行深拷贝操作,如果默认使用浅拷贝,对象拷贝的时候,多指针变量指向同一块内存空间,当析构的时候,多次delete,会出错。
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class String {
    public:
        String(char *str="") 
        {
            str_ = allocAndCpy(str);
        }
        String(const String &s)
        {
            str_ = allocAndCpy(s.str_);
        }
        ~String() 
        {
            delete []str_;
        }
        void display()
        {
            cout << str_ << endl;
        }
    
        char *allocAndCpy(char *str)
        {
            int len = strlen(str) + 1;
            char *s = new char[len];
            memset(s, 0, len);
            strcpy(s, str);
            return s;
        }
    
        String &operator=(const String &s)
        {
            // 因为这是赋值,所以本身已经有内存空间了,要先释放之前分配的内存
            if (this == &s) // 这里只是判断自己赋值给自己
                return *this;
    
            delete[]this->str_;
            this->str_ = allocAndCpy(s.str_);
            return *this;
        }
    
    private:
        char *str_;
    };
    
    int main(void)
    {
        String str1("AAAA");
        str1.display();
    
        // 在我们没提供拷贝构造函数之前
        //String str2 = str1; // 我们没有提供拷贝构造函数,使用默认的拷贝构造函数
                            // 发生运行时错误
    
        // 在我们提供了拷贝构造函数之后
        //String str2 = str1; // 不会出现运行时错误
    
        //不光默认拷贝构造会出现浅拷贝,默认的=操作也是浅拷贝
        // 我们没提供=操作重载之前
        //String str2;
        //str2 = str1; // 发生运行时错误
    
        // 我们提供=操作重载之后
        String str2;
        str2 = str1; // 不会出现运行时错误
    
        return 0;
    }
  14. 禁止拷贝
       某些对象是独一无二的,不允许拷贝,只需要将拷贝构造函数与=重载的操作放到private下即可。
  15. 空类默认产生的成员
      空类的大小为1个字节,默认产生的成员如下:
    class Empty {};
    Empty();             // 默认构造函数,不提供任意的构造函数(包括拷贝构造函数)才提供
    Empty( const Empty& );    // 默认拷贝构造函数,不提供拷贝构造函数才提供
    ~Empty();             // 默认析构函数
    Empty& operator=( const Empty& );  // 默认赋值运算符
    Empty* operator&();                       // 取址运算符
    const Empty* operator&() const;        // 取址运算符 const

转载于:https://www.cnblogs.com/ifpelset/articles/4507707.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值