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

2023-12-31 15:31:23

1、构造函数

1.1?基本使用

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

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

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

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

#include?<iostream>

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?<iostream>
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;
}

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

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

#include?<iostream>
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指针外,也可以使用构造初始化列表的方式进行区分。

#include?<iostream>
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?<iostream>

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关键字屏蔽隐式调用语法。

1.4?拷贝构造函数

1.4.1?概念

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

#include?<iostream>

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;
}

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

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

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

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

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

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

1.4.2?浅拷贝

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

#include?<iostream>
#include?<string.h>
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;
}

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

1.4.3?深拷贝

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

#include?<iostream>
#include?<string.h>
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?<iostream>
#include?<string.h>
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;
}

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

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

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

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