C++构造函数

理想的状态是创建初对象时,程序员可以指定初始值,这样可以创建出属性不同的对象

构造函数可以使对象在创建之初就具备了自定义的属性值,而不是每次创建出一样的对象

1、构造函数的概念

构造函数是类中一种特殊的函数,构造函数的功能有:

● 创建对象

● 对属性初始化

如果程序员不写构造函数,编译器会自动添加一个没有参数的、函数体为空的构造函数

如果程序员手动编写任意一个构造函数,编译器不再为类内添加任何构造函数

部分代码

// 构造函数
MobilePhone(){} 					// 如果不写,编译器自动添加
    
MobilePhone mp1; 					// 在此处默认调用
MobilePhone* mp2 = new MobilePhone; // 在new MobilePhone处默认调用

构造函数是一种特殊的函数,不写返回值类型,因为返回值类型都是创建的对象本身。函数名称与类名完全一致。

2 、构造函数的重载

构造函数支持重载,如果程序员手动编写任意一个构造函数,编译器将不在为类添加任何构造函数。

此时如果仍要调用无参的构造函数且手写的不是无参构造函数时 ,则需要利用构造函数重载来实现多个构造函数。

构造函数支持参数默认值

构造函数重载示例

#include <iostream>

using namespace std;

class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
public:
   MobilePhone(string b,string m,int w) // 重载的构造函数
   {
       brand = b;
       model = m;
       weight = w;
   }

   MobilePhone() 						// 构造函数参数默认值
   {
       brand = "山寨";
       model = "8848";
       weight = 188;
   }

   void show()
   {
       cout << brand << " " << model << " " << weight << endl;
   }
};


int main()
{
    // 调用三个参数的构造函数
    MobilePhone mp1("小米","13",188);
    mp1.show(); // 小米 13 188

    MobilePhone* mp2 = new MobilePhone("苹果","14",177);
    mp2->show();
    delete mp2; // 苹果 14 177

    MobilePhone mp3;
    mp3.show(); // 山寨 9948 188

    return 0;
}

构造函数也支持默认值

3 、构造初始化列表

构造初始化列表是在构造函数中给成员变量赋值的简便写法,可以根据实际情况决定是否采用。

构造函数初始化列表三种形式


格式1:
// 在构造函数的函数体中赋值
MobilePhone(string b, string m, int w) 
{
	brand = b;
    model = m;
    weight = w;
}

格式2:
// 构造初始化列表
MobilePhone(string b, string m, int w):brand(b), model(m), weight{w}{} 

//以上两种写法基本相同,存在部分区别
//1.对常成员变量赋值------------const关键字用法时详细讲解
如果成员变量是const修饰的,不能使用构造函数的函数体赋值。
//2.执行时机--------------------对象的创建和销毁流程时讲解
构造初始化列表要比函数体中赋值执行效率更高

格式3:
//如果构造初始化列表影响了代码的可读性,也可以考虑不使用或混合使用
MobilePhone(string b, string m, int w):brand(b), weight{w} //混用
{
    model = m;
}

4 、隐式调用构造函数

隐式调用示例

#include <iostream>

using namespace std;

class Bird
{
private:
    string name;
    int age;

public:
    Bird(string n)
    {
        name = n;
        cout << "创建了一只鸟:" << name << endl;
    }

    Bird(int a,string n = "Empty")
    {
        age = a;
        name = n;
        cout << "创建了一只鸟:" << name << endl;
    }

    string get_name()
    {
        return name;
    }

    int get_age()
    {
        return age;
    }
};


int main()
{
    string name = "Tom";
    // name的类型是string字符串类型,参数b的类型是Bird,本质上无直接联系
    // 赋值过程中,编译器发现右边的字符串name能够作为左边的参数构造出来
    // 编译器自动完成了对象b的创建并将name送进去
    Bird b = name; // 隐式调用了构造函数 
    cout << b.get_name() << endl;	// Tom

    Bird b2 = 12; // 隐式调用了构造函数
    cout << b2.get_name() << endl;	// Empty
    cout << b2.get_age() << endl;	// 12

    return 0;
}

执行结果

创建了一只鸟:Tom
Tom
创建了一只鸟:Empty
Empty
12

原因分析:

name的类型是string字符串类型,对象b的类型是Bird,本质上无直接联系

赋值过程中,编译器发现右边的字符串name能够作为左边的参数构造出来

编译器帮程序员完成了对象b的创建并将name送进去

以上用法可能会无意中创建了对象

只需要给构造函数增加explicit关键字 修饰,即可屏蔽隐式调用构造函数这种用法

隐式调用在C++使用较多——方便

显式调用在C++使用较少,主要在QT课程中使用

explicit 显式调用

#include <iostream>

using namespace std;

class Bird
{
private:
    string name;
    int age;

public:
    explicit Bird(string n)
    {
        name = n;
        cout << "创建了一只鸟:" << name << endl;
    }

    explicit Bird(int a, string n = "Empty")
    {
        age = a;
        name = n;
        cout << "创建了一只" << age << "岁的鸟:" << name << endl;
    }

    string get_name()
    {
        return name;
    }

    int get_age()
    {
        return age;
    }
};


int main()
{
    string name = "Tom";
//    Bird b = name; 错误
    Bird b(name); // 显示调用构造函数
    cout << b.get_name() << endl;

//    Bird b2 = 12; 错误
    Bird b2(12); // 显示调用构造函数
    cout << b2.get_name() << endl;
    cout << b2.get_age() << endl;

    return 0;
}

5、 拷贝构造函数

克隆、复制粘贴

编译器会自动添加一个拷贝构造函数

拷贝构造函数也是一个重载的构造函数

5.1 拷贝构造函数的概念

程序员在一个类中不写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,与其它的构造函数一样支持重载,用于对象的拷贝构建

拷贝构造函数创建出的对象仅仅与原对象数据相同,对象之间相互独立,修改其中任何一个对另外一个都没有任何影响

示例

void show()
	{
       cout << brand << " " << model << " " << weight << endl;
       cout << &brand << " " << &model << " " << &weight << endl;
   	}

	MobilePhone mp1("小米", "13", 188);
    mp1.show();  
	//小米 13 188
	//地址1 地址2 地址3

    MobilePhone mp2(mp1);	//拷贝构造函数
    mp2.show();  
	//小米 13 188
	//地址4 地址5 地址6
	// map2与mp1是两个对象,相互数据独立

5.2 浅拷贝

默认的拷贝构造函数仅仅把两个对象的属性值进行了赋值操作,当一个类中的成员变量出现了指针类型,默认的拷贝构造函数会出现浅拷贝问题

直接赋值操作会导致两个指针保存的地址相同,指向同一份内存,这不符合面向对象的特性


#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char* name; // 指针

public:
    Dog(char* n)
    {
        name = n;	//字符数组n赋给name,name为char *类型指针,指向字符数组首地址c
    }

	// 编译器自动添加拷贝构造函数------拷贝构造函数也是一个重载的构造函数
    Dog(const Dog& d) 	// d是引用,是d1的别名,相当于将d1传递进来
    {
        name = d.name;	// d1的name赋给了d2的名字,二者均为指针,指针拷贝的是指向的地址
                        // 故:新创建的地址也是c
    }

    void show_name()
    {
        cout << name << endl;
    }
};

int main()
{
    char c[20] = "wangcai";

    Dog d1(c); 		// 字符数组c传递到构造函数d1中
    Dog d2(d1); 	// 此时调用默认的拷贝构造函数

    strcpy(c,"xiaobai");

    d1.show_name(); // xiaobai---与面向对象相悖
    d2.show_name(); // xiaobai---与面向对象相悖
    
    return 0;
}

对象的封装目的是让对象独享这一块私有的数据,不应该受到其它对象或外界因素的影响

浅拷贝代码中出现了使用指针带来的问题(浅拷贝)

如果非要用指针怎么解决这种问题?

引入深拷贝 ↓

5.3 深拷贝
深拷贝为解决浅拷贝资源不独占的问题
思路:每次指针赋值时,单独开辟内存空间,让当前指针独享(独自去指向)
拓展:字符数组支持在C++中以new的方式创建堆内存

解决浅拷贝资源不独占问题,但造成内存泄漏

public:
    Dog(char* n)
    {	
        //开启一个堆内存区域
        name = new char[20];	// 创建一个char类型数组对象,让name去指向独享区
        strcpy(name, n);		// 拷贝数据
    }

    Dog(const Dog& d)
    {
        //开启一个堆内存区域
        name = new char[20];	// 创建一个char类型数组对象,让name去指向独享区
        strcpy(name, d.name);	// 拷贝数据
    }

【思考】以上深拷贝代码有无问题?

new开辟的空间就必须有delete释放空间,会造成内存泄漏(类似mallocfree)

引入析构函数 ↓

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值