C++面向对象基础-构造函数

1、构造函数

1.1 基本使用

构造函数是一种特殊的成员函数,用于创建对象,写法上有以下要求:

  • 函数名必须与类名完全相同
  • 构造函数不写返回值
  • 如果程序员不手动编写构造函数,编译器就会自动添加一个无参的构造函数

C++面向对象基础-构造函数_第1张图片

手动添加构造函数,编译器就不会自动添加构造函数。

    • 构造函数在创建对象时,常用于给对象的属性赋予初始值。

#include 

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词首字母大写
class MobilePhone
{
private: // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string modle;   // 型号
    int weight;     // 重量

public: // 权限:public是最开放的权限。

    MobilePhone() // 默认构造函数
    {
        brand = "8848";
        modle = "M6 巅峰版";
        weight = 500;
    }
    // 品牌的get函数
    string get_brand()
    {
        return brand;
    }
    // 型号的get函数
    string get_modle()
    {
        return modle;
    }
    // 重量的get函数
    int get_weight()
    {
        return weight;
    }
    // 品牌的set函数
    void set_brand(string b)
    {
        brand = b;
    }
};
int main()
{
    // 栈内存对象
    MobilePhone mp1;
    cout << mp1.get_brand() << endl;
    cout << mp1.get_modle() << endl;
    cout << mp1.get_weight() << endl;
    return 0;
}

    • 构造函数也支持函数重载和函数参数默认值

#include 
using namespace std;
// 帕斯卡命名法(大驼峰命名法)
// 每个单词首字母大写
class MobilePhone
{
private: // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string modle;   // 型号
    int weight;     // 重量
public: // 权限:public是最开放的权限。
    MobilePhone() // 默认构造函数
    {
        brand = "8848";
        modle = "M6 巅峰版";
        weight = 500;
    }
    // 有参构造函数(函数重载)
    MobilePhone(string b,string m,int w)
    {
        brand = b;
        modle = m;
        weight = w;
    }
    // 品牌的get函数
    string get_brand()
    {
        return brand;
    }

    // 型号的get函数
    string get_modle()
    {
        return modle;
    }

    // 重量的get函数
    int get_weight()
    {
        return weight;
    }
    // 品牌的set函数
    void set_brand(string b)
    {
        brand = b;
    }
};
int main()
{
    // 栈内存对象
    MobilePhone mp1("vivo","iqoo12",300);
    cout << mp1.get_brand() << endl;
    cout << mp1.get_modle() << endl;
    cout << mp1.get_weight() << endl;
    return 0;
}

C++面向对象基础-构造函数_第2张图片

在创建对象的时候,必须调用任意构造函数。

    • 函数参数默认值(全缺省默认构造函数)
  • 缺省部分往前写

C++面向对象基础-构造函数_第3张图片

#include 
using namespace std;
// 帕斯卡命名法(大驼峰命名法)
// 每个单词首字母大写
class MobilePhone
{
private: // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string modle;   // 型号
    int weight;     // 重量
public: // 权限:public是最开放的权限。
    MobilePhone() // 默认构造函数
    {
        brand = "8848";
        modle = "M6 巅峰版";
        weight = 500;
    }
    // 有参构造函数
    MobilePhone(string b,string m,int w)
    {
        brand = b;
        modle = m;
        weight = w;
    }
    MobilePhone(int w,string b = "oppo",string m = "findx")
    {
        brand = b;
        modle = m;
        weight = w;
    }
    // 品牌的get函数
    string get_brand()
    {
        return brand;
    }

    // 型号的get函数
    string get_modle()
    {
        return modle;
    }

    // 重量的get函数
    int get_weight()
    {
        return weight;
    }

    // 品牌的set函数
    void set_brand(string b)
    {
        brand = b;
    }
};


int main()
{
    // 栈内存对象
    MobilePhone mp1;
    cout << mp1.get_brand() << endl;
    cout << mp1.get_modle() << endl;
    cout << mp1.get_weight() << endl;

    MobilePhone *mp2 = new MobilePhone();

    return 0;
}

1.2 构造初始化列表

构造初始化列表是一种更简单的给成员变量赋予初始值的写法。传参遵寻就近原则。

注意:当构造函数的局部变量与成员变量同名时,除了使用后面学习的this指针外,也可以使用构造初始化列表的方式进行区分。

C++面向对象基础-构造函数_第4张图片

#include 
using namespace std;
// 帕斯卡命名法(大驼峰命名法)
// 每个单词首字母大写
class MobilePhone
{
private: // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string modle;   // 型号
    int weight;     // 重量
public: // 权限:public是最开放的权限。
    // 构造初始化列表:编译器成员变量与局部变量可以做区分
    MobilePhone()://直接赋值,成员变量已经创建。
        brand("8848"),modle("M6 巅峰版"),weight(500){}
    MobilePhone(string brand,string modle,int weight)://默认传参
        brand(brand),modle(modle),weight(weight){}
            //对象创造之前被调用,还没有创造对象,成员变量已经创建。不能放到函数体内,函数无法识别局部变量与成员变量
    // 品牌的get函数
    string get_brand()
    {
        return brand;
    }
    // 型号的get函数
    string get_modle()
    {
        return modle;
    }
    // 重量的get函数
    int get_weight()
    {
        return weight;
    }
};
int main()
{
    // 栈内存对象
    MobilePhone mp1;
    cout << mp1.get_brand() << endl;
    cout << mp1.get_modle() << endl;
    cout << mp1.get_weight() << endl;

    MobilePhone *mp2 = new MobilePhone("三星","note7",100);
    cout << mp2->get_brand() << endl;
    cout << mp2->get_modle() << endl;
    cout << mp2->get_weight() << endl;

    delete mp2;
    mp2 = NULL;
    return 0;
}

1.3 隐式调用构造函数

截至到目前为止,对于构造函数的调用都是显式调用的。

#include 

using namespace std;

class Test
{
private:
    int val;
public:
    Test(int v,int i,string str,double d)
    {
        val = v;
        cout << "构造函数" << endl;
    }

    int get_val()
    {
        cout << &val << endl;
        return val;
    }

};

int main()
{
//    Test t1(2);  // 显式调用构造函数
//    cout << t1.get_val() << endl;
    Test t2 = {123,2,"string",2.4}; // 隐式调用构造函数,多参数传递C++11支持
    cout << t2.get_val() << endl;

    return 0;
}


建议使用隐式调用,可以使用explicit关键字屏蔽隐式调用语法。

C++面向对象基础-构造函数_第5张图片

1.4 拷贝构造函数

1.4.1 概念

当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数进行实现。

#include 

using namespace std;

class Test
{
private:
    int val;
public:
    Test(int v)
    {
        val = v;
        cout << "构造函数" << endl;
    }
    // 手动添加默认的拷贝构造函数
    Test(Test &t)
    {
        val = t.val;
    }

    int get_val()
    {
        return val;
    }

};

int main()
{
    Test t1(4);
    cout << t1.get_val() << endl;

    Test t2(t1); // 调用拷贝构造函数
    cout << t2.get_val() << endl;

    return 0;
}

C++面向对象基础-构造函数_第6张图片

没有任何构造函数的时候:编译器会给我们创建一个默认构造函数、拷贝构造函数。

如果我们有一个默认构造函数,编译器不会创建默认构造函数,会创建拷贝构造函数。

如果我们有一个有参构造函数,编译器不会创建默认构造函数,会创建拷贝构造函数。

如果我们有一个拷贝构造函数,编译器不会创建默认构造函数,不会创建拷贝构造函数。

【思考】:拷贝构造函数会存在隐患吗?

存在,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的成员变量指向同一处,改变一处另一处也会改变不符合面向对象的设计规范,这种现象被称为”浅拷贝“。

1.4.2 浅拷贝

存在的问题:更改一处三处都会改变

#include 
#include 
using namespace std;
class Dog
{
private:
    char *name;
public:
    Dog(char *n)
    {
        name = n;
    }
    void show_name()
    {
        cout << name << endl;
    }
};
int main()
{
    char arr[20] = "旺财";
    Dog d1(arr);
    Dog d2(d1); // 调用的是拷贝构造函数,不调用构造函数
    strcpy(arr,"大黄");//两个都会改变
    d1.show_name(); // 大黄
    d2.show_name(); // 大黄
    return 0;
}

C++面向对象基础-构造函数_第7张图片C++面向对象基础-构造函数_第8张图片

这种情况必须要手写构造函数,使每次赋值都创建一个新的副本,从而每个对象单独持有自己的成员变量,这种方式被称为“深拷贝”。

1.4.3 深拷贝

new开辟的空间无法释放,造成内存泄漏的问题。

#include 
#include 
using namespace std;
class Dog
{
private:
    char *name;
public:
    Dog(char *n)
    {
        name = new char[20];
        strcpy(name,n);
    }
    Dog(Dog &d)
    {
       name = new char[20];
       strcpy(name,d.name);
    }
    void show_name()
    {
        cout << name << endl;
    }
};
int main()
{
    char arr[20] = "旺财";
    Dog d1(arr);
    Dog d2(d1); // 拷贝构造函数

    strcpy(arr,"大黄");

    d1.show_name(); // 旺财
    d2.show_name(); // 旺财

    return 0;
}

#include 
#include 
using namespace std;

class Dog
{
private:
    char *name;
public:
    // 构造函数,初始化对象的name成员变量
    Dog(char *n)
    {
        name = n; // 将传入的指针赋值给name,这会导致问题后面会解释
    }
    // 拷贝构造函数,用于创建一个新的对象并复制已有对象的数据
    Dog(Dog &d)
    {
        name = new char[20]; // 为name分配内存空间
        strcpy(name, d.name); // 复制参数对象的name值到当前对象的name
    }
    // 打印狗的名字
    void show_name()
    {
        cout << name << endl;
    }
};

int main()
{
    char arr[20] = "旺财"; // 创建一个字符数组,并存储字符串"旺财"
    Dog d1(arr); // 创建一个Dog对象d1,传入arr作为参数
    
    Dog d2(d1); // 使用拷贝构造函数,创建一个新的Dog对象d2,复制d1的数据
    
    strcpy(arr, "大黄"); // 修改arr中的字符串为"大黄"
    
    d1.show_name(); // 输出:旺财,因为d1的构造函数只是将name指针指向arr的地址,所以修改arr后,d1的name也改变了
    
    d2.show_name(); // 输出:旺财,因为拷贝构造函数在创建d2时复制了d1的name,所以不受arr的修改影响
    
    return 0;
}

C++面向对象基础-构造函数_第9张图片

  1. 创建有参构造函数,编译器不会创建无参构造函数,会创建拷贝构造函数。
  2. 创建无参构造函数,编译器不会创建无参构造函数,会创建拷贝构造函数。
  3. 创建拷贝构造函数,编译器不会创建无参构造函数,不会创建拷贝构造函数。

【思考】 深拷贝的代码是否存在隐患?

存在,new开辟的空间无法释放,造成内存泄漏的问题。

你可能感兴趣的:(C++,c++)