cpp_06_缺省构造_拷贝构造_拷贝赋值_初始化表

发布时间:2023年12月24日

1? 构造函数

1.1? 构造函数可重载:

? ? ? ? ? ? ? ? 构造函数可以通过形参表差别化形成重载关系

? ? ? ? ? ? ? ? 重载关系的构造函数,通过构造函数的实参类型进行匹配

? ? ? ? ? ? ? ? 使用缺省参数可以减少构造函数重载的数量

// consover.cpp 构造函数的重载
#include <iostream>
using namespace std;

class Human {
public:
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
/*  Human( ) {
        cout << "1. Human() --> ";
        m_age = 0;
        m_name = "无名";
    }
    Human( int age ) {
        cout << "2. Human(int) --> ";
        m_age = age;
        m_name = "无名";
    }*/
    Human( int age=0, const char* name="无名" ) {//使用缺省值,减少上2个重载构造函数
        cout << "3. Human(int,const char*) --> ";
        m_age = age;
        m_name = name;
    }
private:
    int m_age;
    string m_name;
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()
    h.getinfo( );

    Human h2(22); // 定义h2,利用h2.Human(22)
    h2.getinfo( );

    Human h3(22,"张飞"); // 定义h3,利用h3.Human(22,"张飞")
    h3.getinfo( );
    return 0;
}

1.2? 四类构造函数:

? ? ? ? 构造函数本没有分类,按参数个数,可分为四类:

? ? ? ? ? ? ? ? 多参构造函数? ? ? ? ? ? ? ? ? ?//2参及以上

? ? ? ? ? ? ? ? 无参缺省)构造函数? ? //0参

? ? ? ? ? ? ? ? 拷贝构造函数? ? ? ? ? ? ? ? ??//单参

? ? ? ? ? ? ? ? 类型转换构造函数? ? ? ? ? ?//单参

2? 缺省构造函数

2.1? 理论

????????1)也称无参构造函数,但其未必真的没有任何参数:

? ? ? ? ? ? ? 为一个有参构造函数的每个形参都提供一个缺省值,同样可以达到无参构造函数的效果。

? ? ? ? 2)如果一个类没有定义任何构造函数,那么编译器会为其提供一个无参构造函数:

? ? ? ? ? ? ? ? 对基本类型的成员变量进行定义,并初始化为随机数;

? ? ? ? ? ? ? ? 对类类型的成员变量进行定义,调用相应类型的无参构造函数。

??????????????(由此强烈建议,为每个类提供无参的构造函数,因为它可能作为另外类的成员变量)

? ? ? ? 3)如果一个类定义了构造函数,无论这个构造函数是否带有参数,编译器都不再为这个类

? ? ? ? ? ? ? 提供无参构造函数,

??????????????但仍会对成员变量进行定义/初始化为随机数,程序员自己按需再赋值即可:

????????????????? ? ????????Human( int age=0, const char* name="无名" ) {
? ? ? ? ? ? ? ? ? ? ? ? ????????? //【int m_age;】定义m_age,初值为随机数
? ? ? ? ? ? ? ? ? ? ????????? ? ? //【string m_name;】定义m_name,利用m_name.string()
? ? ? ? ? ? ? ? ? ????????? ? ? ? m_age = age;
? ? ? ? ? ? ? ? ? ????????? ? ? ? m_name = name;
? ????????????????????????? }

// defaultcons.cpp 缺省构造函数
#include <iostream>
using namespace std;

class Human {
public:
//  如果类没有提供任何构造函数,编译器将提供一个无参的构造函数
/*  Human() {
       【int m_age;】定义m_age,初值为随机数
       【string m_name;】定义m_name,利用m_name.string()
    }*/
    Human( int age=0, const char* name="无名" ) {
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类缺省构造函数被调用" << endl;
        m_age = age;
        m_name = name;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
    h.getinfo( );

    Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
    h2.getinfo();
    return 0;
}

?

2.2? 缺省构造过程

????????

? ? ? ?

2.3? 缺省构造函数?必要性!

????????强烈建议,为每个类提供无参的构造函数,因为它可能作为另外类的成员变量:

//hastodeflt.cpp
// 强烈建议大家在设计一个类时,给这个类提供一个无参构造函数,
//因为这个类对象非常有可能作为另一个的成员变量出现
#include <iostream>
using namespace std;

class A { // 当前A类有无参构造 
public:
    A( int i=0 ) { 
        m_i = i;
    }
private:
    int m_i;
};

class Human {
public:
    Human( int age=0, const char* name="无名" ) {
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        //【A m_a;】定义m_a,利用m_a.A()
        cout << "Human类缺省构造函数被调用" << endl;
        m_age = age;
        m_name = name;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
    A m_a; // 类类型的成员变量
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
    h.getinfo( );

    Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
    h2.getinfo();
    return 0;
}

3? 拷贝构造函数

3.1? 理论

? ? ? ? what? ①构造函数只有1个形参? ? ? ②且参数类型与类名相同。

? ? ? ? class? 类名{

? ? ? ? ? ? ? ? 类名( const? 类名&? that ){ ... }? ?// const 和 & 非必须,有了更好

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // &提升效率,const变万能引用

? ? ? ? };?

? ? ? ? 1)作用:利用一个已定义的对象,来定义其同类型的副本对象,即对象克隆

? ? ? ? 2)如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个默认拷贝构造函数。

? ? ? ? ? ? ? -对基本类型成员变量进行定义,并赋初值(按字节复制);

? ? ? ? ? ? ? -对类类型成员变量进行定义,并调用相应类型的拷贝构造函数。

? ? ? ? 3)如果自己定义了拷贝构造函数,编译器将不再提供默认拷贝构造函数,这时所有与成员复制有关的操作,都必须自己编写代码完成。

? ????????????????? Human( const Human& that ) {?
? ? ? ? ? ? ? ? ? ? ? ? ? //【int m_age;】定义m_age,初值为随机数? ?编译器完成
? ? ? ? ? ? ? ? ? ? ? ? ? //【string m_name;】定义m_name,利用m_name.string()? ?编译器完成
? ? ? ? ? ? ? ? ? ? ? ? ? cout << "Human类拷贝构造函数被调用" << endl;? ? //手写
? ? ? ? ? ? ? ? ? ? ? ? ? m_age = that.m_age;? ? ? ? //手写
? ? ? ? ? ? ? ? ? ? ? ? ? m_name = that.m_name;? //手写
? ????????????????? }

? ? ? ? 4)由3)可知,自己写的效率低。因定义和赋值,进行了2次赋值(用初始化表可规避)。

????????5)若拷贝构造函数不能满足要求,需自己定义。

// copycons.cpp 拷贝构造函数
#include <iostream>
using namespace std;

class Human {
public:
//  如果类没有提供任何构造函数,编译器将提供一个无参的构造函数
/*  Human() {
       【int m_age;】定义m_age,初值为随机数
       【string m_name;】定义m_name,利用m_name.string()
    }*/
    Human( int age=0, const char* name="无名" ) {
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类缺省构造函数被调用" << endl;
        m_age = age;
        m_name = name;
    }
//  如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/*  Human( const Human& that ) { 
       【int m_age=that.m_age;】定义m_age,初值为that.m_age
       【string m_name(that.m_name);】定义m_name,
                               利用m_name.string(that.m_name)-->string类拷贝构造函数
    }*/
    Human( const Human& that ) { 
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类拷贝构造函数被调用" << endl;
        m_age = that.m_age;
        m_name = that.m_name;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
    h.getinfo( );

    Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
    h2.getinfo();

    Human h3(h2); // = h2; // 定义h3,利用h3.Human(h2)-->触发拷贝构造函数
    h3.getinfo();
    return 0;
}

3.2? 拷贝构造过程

????????

3.3? 拷贝构造函数的调用时机

? ? ? ? 一切出现对象克隆的情况,都会调用拷贝构造函数:

? ? ? ? 1)用已定义对象作为同类型对象的构造实参?

? ? ? ? 2)以对象的形式向函数传递参数?

? ? ? ? 3)从函数中返回对象?

? ? ? ? 注意:某些拷贝构造过程会因编译优化而被省略

// copytime.cpp 拷贝构造函数被调用的时间点
#include <iostream>
using namespace std;

class Human {
public:
    Human( int age=0, const char* name="无名" ) {
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        m_age = age;
        m_name = name;
    }
    Human( const Human& that ) { 
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类拷贝构造函数被调用" << endl;
        m_age = that.m_age;
        m_name = that.m_name;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
void foo( Human v ) {
    // ...
}
Human bar( ) {
    Human m;
    return m; // 利用m克隆一个无名对象
}
int main( void ) {

    Human h2(22,"张飞");

    Human h3(h2); // = h2; // (1)触发拷贝构造函数

    foo( h3 ); // (2)触发拷贝构造函数

    Human h4 = /*|...|*/ bar(); // 会触发2次拷贝构造函数,但编译器优化为0次

    // -fno-elide-contructors   不让编译器优化
    return 0;
}

4? 拷贝构造与拷贝赋值的区别

????????拷贝构造函数的对象是新创建的, 最好用初始化表

? ? ? ? 拷贝赋值函数的对象是已有的,? ? 不能用初始化表

5? 拷贝赋值函数(不是构造函数)

5.1? 理论

? ? ? ? what?? ① operator=? ? ② 参数只有一个? ③ 参数类型与类名相同

????????class? 类名{

? ? ? ? ? ? ? ? 类名&? operator=( const? 类名&? that ) { ... }? // &提升传参效率,const变万能引用

? ? ? ? };

? ? ? ? 1)作用:用于一个已定义存在的对象,给已存在的同类型对象赋值,即对象赋值

? ? ? ? 2)如果一个类没有定义拷贝赋值函数,那么编译器会为其提供一个默认拷贝赋值函数。

? ? ? ? ? ? ? ? -对基本类型成员变量,值传递(按字节复制)

? ? ? ? ? ? ? ? -对类类型成员变量,调用相应类型的拷贝赋值函数。

? ? ? ? 3)如果自己定义了拷贝赋值函数,编译器将不再提供默认拷贝赋值函数,这时所有与成员复制有关的操作,都必须自己写代码完成。(编译器也不会在自定义的函数中定义成员变量/包子馅,因为这不是构造函数。成员变量早就有了。)

? ? ? ? 4)若默认拷贝赋值函数不能满足要求时,需自己定义

// copyassignment.cpp 拷贝赋值函数:用于 对象之间的赋值
#include <iostream>
using namespace std;

class Human {
public:
//  如果类没有提供任何构造函数,编译器将提供一个无参的构造函数
/*  Human() {
       【int m_age;】定义m_age,初值为随机数
       【string m_name;】定义m_name,利用m_name.string()
    }*/
    Human( int age=0, const char* name="无名" ) {
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类缺省构造函数被调用" << endl;
        m_age = age;
        m_name = name;
    }
//  如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/*  Human( const Human& that ) { 
       【int m_age=that.m_age;】定义m_age,初值为that.m_age
       【string m_name(that.m_name);】定义m_name,
                                 利用m_name.string(that.m_name)-->string类拷贝构造函数
    }*/
    Human( const Human& that ) { 
        //【int m_age;】定义m_age,初值为随机数
        //【string m_name;】定义m_name,利用m_name.string()
        cout << "Human类拷贝构造函数被调用" << endl;
        m_age = that.m_age;
        m_name = that.m_name;
    }
//  如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
/*  Human& operator=( const Human& that ) {
        this->m_age = that.m_age;
        this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->
                                    // string类的拷贝赋值函数
        return *this;
    }*/
    Human& operator=( const Human& that ) {
        // 编译器不会再拷贝赋值函数中塞任何操作
        cout << "Human类的拷贝赋值函数被调用" << endl;
        this->m_age = that.m_age;
        this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->
                                    // string类的拷贝赋值函数
        return *this;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
    h.getinfo( );

    Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
    h2.getinfo();

    Human h3(h2); // = h2; // 定义h3,利用h3.Human(h2)-->触发拷贝构造函数
    h3.getinfo();

    Human h4; // 定义h4,利用h4.Human()-->h4维护的内容为(无名,0)
    cout << "h4被赋值前---";
    h4.getinfo();

    h4 = h3; // h4.operator=(h3)-->触发拷贝赋值函数
    cout << "h4被赋值后---";
    h4.getinfo();
    return 0;
}

5.2? 拷贝赋值过程

????????

6? 初始化表

6.1? 理论

? ? ? ? 初始化表只能构造函数中使用。

????????通过在类的构造函数中使用初始化表,可以通知编译器该类的成员变量如何被初始化

? ? ? ? 类中的基本类型成员变量,最好在初始化表中显示指明如何初始化,否则初值不确定

? ? ? ? 类中的类类型成员变量,最好在初始化表中显示指明如何初始化,否则将调用相应类型的无参构造函数(维护一个空串"",即\0)

// initlist.cpp
// 初始化表 -- 只能在构造函数中使用(指导编译器如何给定义出来的成员变量做初始化)
#include <iostream>
using namespace std;

class Human {
public:
//  如果类没有提供任何构造函数,编译器将提供一个无参的构造函数
/*  Human() {
       【int m_age;】定义m_age,初值为随机数
       【string m_name;】定义m_name,利用m_name.string()
    }*/
    Human( int age=0, const char* name="无名" ) : m_age(age),m_name(name) {
        //【int m_age=age;】定义m_age,初值为age
        //【string m_name(name);】定义m_name,利用m_name.string(name)
        cout << "Human类缺省构造函数被调用" << endl;
    }
//  如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/*  Human( const Human& that ) { 
       【int m_age=that.m_age;】定义m_age,初值为that.m_age
       【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)-->
                                       //                     string类拷贝构造函数
    }*/
    Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) { 
        //【int m_age=that.m_age;】定义m_age,初值为that.m_age
        //【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)
        cout << "Human类拷贝构造函数被调用" << endl;
    }
//  如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
/*  Human& operator=( const Human& that ) {
        this->m_age = that.m_age;
        this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->
                                    //                  string类的拷贝赋值函数
        return *this;
    }*/
    Human& operator=( const Human& that ) {
        // 编译器不会再拷贝赋值函数中塞任何操作
        cout << "Human类的拷贝赋值函数被调用" << endl;
        this->m_age = that.m_age;
        this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->
                                    //                  string类的拷贝赋值函数
        return *this;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
    }
private:
    int m_age; // 基本类型的成员变量
    string m_name; // 类类型的成员变量
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
    h.getinfo( );

    Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
    h2.getinfo();

    Human h3(h2); // = h2; // 定义h3,利用h3.Human(h2)-->触发拷贝构造函数
    h3.getinfo();

    Human h4; // 定义h4,利用h4.Human()-->h4维护的内容为(无名,0)
    cout << "h4被赋值前---";
    h4.getinfo();

    h4 = h3; // h4.operator=(h3)-->触发拷贝赋值函数
    cout << "h4被赋值后---";
    h4.getinfo();
    return 0;
}

6.2? 初始化表 过程图?

?

6.3? 必须在初始化表中指明的情境

? ? ? ? 类的常量型成员变量和引用型成员变量,必须在初始化表中显示初始化。

// hastoinit.cpp 必须使用初始化表的情况
#include <iostream>
#include <cstring>
using namespace std;

class Human {
public:
    Human( int age=0, const char* name="无名", float score=0.0 )
                    : m_age(age),m_name(name),m_score(score),m_len(strlen(name)) {
        //【int m_len=strlen(name);】
        //【int m_age=age;】定义m_age,初值为age
        //【string m_name(name);】定义m_name,利用m_name.string(name)
        //【const float m_score=score;】
        cout << "Human类缺省构造函数被调用" << endl;
    }
    Human( const Human& that ) 
        :m_age(that.m_age),m_name(that.m_name),m_score(that.m_score),m_len(that.m_len){
        //【int m_len = that.m_len;】
        //【int m_age=that.m_age;】定义m_age,初值为that.m_age
        //【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)
        //【const float m_score=that.m_score;】
        cout << "Human类拷贝构造函数被调用" << endl;
    }
    void getinfo( ) {
        cout << "姓名: " << m_name << ", 年龄: " << m_age << ", 成绩:" << m_score
             << ", 名字长度:" << m_len << endl;
    }
private:
    int m_len; // 保存名字字符串的长度
    int m_age;
    string m_name; // 保存名字字符串
    const float m_score; // 常量型的成员变量
//    int m_len; // 保存名字字符串的长度
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {

    Human h2(22,"张飞",88.5); // 触发缺省构造函数
    h2.getinfo();

    Human h3(h2); // 触发拷贝构造函数
    h3.getinfo();

    return 0;
}

6.4? 初始化顺序

? ? ? ? 类的成员变量按其在类中的声明顺序依次被初始化,而与其在初始化表中的顺序无关。

? ? ? ? 为了?避开?顾及声明顺序,在初始化表中定义每个成员变量时,尽量不要牵扯其他成员变量,可以考虑用形参代替其他成员变量。。

// twodimensional.cpp 设计一个二维坐标系的 类
#include <iostream>
using namespace std;

class TwoDimensional {
public:
    TwoDimensional( int x=0, int y=0 ) : m_x(x), m_y(y) {
        //【int m_x=x;】定义m_x初值为x
        //【int m_y=y;】 定义m_y初值为y
    }
    TwoDimensional( const TwoDimensional& that ) : m_x(that.m_x), m_y(that.m_y) {
        //【int m_x=that.m_x;】
        //【int m_y=that.m_y;】
    }
    TwoDimensional& operator=( const TwoDimensional& that ) {
        m_x = that.m_x;
        m_y = that.m_y;
    }
    void getinfo( /* TwoDimensional* this */ ) { // 非常函数
        cout << "横坐标: " << m_x << ", 纵坐标: " << m_y << endl;
    }
private:
    int m_x; // 横坐标
    int m_y; // 纵坐标
};

int main( void ) {
    TwoDimensional a(100,300); // 定义a,利用a.TwoDimensional(100,300)
    a.getinfo( );

    TwoDimensional b(a); // = a;  --> 触发拷贝构造函数
    b.getinfo( );

    TwoDimensional c;
    c.getinfo( );
    
    c = b; // --> 拷贝赋值函数
    c.getinfo( );

    return 0;
}

文章来源:https://blog.csdn.net/qq_33331020/article/details/135172791
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。