「C++ 类和对象篇 5」 构造函数

目录

一、概念

1. 构造函数是什么?

2. 为什么C++要引入构造函数?

3. 怎么用构造函数?

3.1 创建构造函数

3.2 调用构造函数

二、构造函数的特性

三、构造函数对成员变量初始化

0. 对构造函数和成员变量分类

1. 带参构造函数对成员变量初始化

2. 无参构造函数对成员变量初始化 

拓展. 内置类型成员在编译器生成的无参构造函数中不初始化的缺陷

四、默认构造函数

1. 什么是默认构造函数?

2. 编译器生成的无参构造函数

3. 保证只有一个默认构造函数

【总结】


一、概念

1. 构造函数是什么?

        构造函数是一个特殊的成员函数,用来初始化成员变量。

2. 为什么C++要引入构造函数?

        怎么对类中的成员变量进行初始化?写一个成员函数专门用来初始化成员变量?但如果忘记调用了怎么办?为解决类初始化和忘记初始化类的问题,能不能在创建对象时就自动完成初始化的动作呢?

举个小例子: 
有以下Date类:
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

如果要使用该Date类创建对象,必须通过Init公有方法给对象设置日期,否则成员变量都是随机值,
但每次创建对象时都调用该方法,这未免有点麻烦,那能否在对象创建的同时,就将信息设置进去呢?

        为解决以上问题,C++中引入了构造函数:构造函数用于对象的初始化,在实例化对象时由编译器自动调用,保证了对象创建出来一定完成了初始化。虽然构造函数叫做构造,但构造函数并不用来开空间创建对象,而是用来初始化对象的。(也许构造函数更适合被称为初始化函数?) 
        还有一点需要声明,虽然构造函数不是用来开空间创建对象的,但如果不调用构造函数,就无法创建对象,因为编译器不知道如何初始化对象的状态。因此,构造函数是创建对象时必须调用的函数。

3. 怎么用构造函数?

3.1 创建构造函数

创建时要注意构造函数特征:函数名与类名相同、无返回值。

构造函数主要分两类:无参构造函数、带参构造函数。

用以下例子来说明如何创建无参构造函数和带参构造函数:
创建时要注意构造函数特征:函数名与类名相同、无返回值。
class Date
{
public:
	//1、无参构造函数
	Date()
	{}
	
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
     
    void Print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.2 调用构造函数

实例化对象时编译器自动调用构造函数。

接上面的例子,演示如何调用无参构造函数和带参构造函数: 
int main()
{
    // 自动调用无参构造函数
	Date d1;
    // 自动调用带参的构造函数
	Date d2(2023, 10, 1);
    d1.Print();
	d2.Print();
	return 0;
}

注意:通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。

如下不是在调用构造函数,而是在main函数中声明了一个函数名为d3函数,该函数无参,返回值为Date类型:
int main()
{
    //以下代码不是在创建对象,而是在声明d3函数,该函数无参,返回值为Date类型。
    Date d3();
    return 0;
}

二、构造函数的特性

        再次强调,开辟空间不是构造函数做的事。虽然构造函数叫做构造,但构造函数并不用来开空间创建对象,而是用来初始化对象的
        还有一点需要声明,如果不调用构造函数,就无法创建对象,因为编译器不知道如何初始化对象的状态。因此,构造函数是创建对象时必须调用的函数。

  1. 函数名和类名相同。
  2. 无返回值。
  3. 实例化对象时编译器自动调用对应的构造函数在对象整个生命周期内只调用一次。
  4. 构造函数支持缺省参数。
  5. 构造函数支持重载 :一个类中可以有多个构造函数,它们之间构成函数重载。
  6. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数。一旦用户显式定义构造函数,编译器将不再生成无参的默认构造函数:

三、构造函数对成员变量初始化

0. 对构造函数和成员变量分类

构造函数主要分两类:无参构造函数、带参构造函数。


成员变量主要分两类:内置类型(基本类型)、自定义类型。
内置类型就是语言提供的数据类型,如: int/char/double/…/指针;
自定义类型就是使用class/struct/union等定义的类型。
(注意:指针是内置类型,那自定义类型的指针当然也是内置类型。)

1. 带参构造函数对成员变量初始化

1.1 类型为内置类型的成员变量:

        有多少参数就要传入多少对应的值,设置了参数的成员变量会被初始化对应值,没有设置参数的成员变量为随机值。

1.2 类型为自定义类型的成员变量:

a. 如果有默认构造函数,类型为自定义类型的成员变量会在实例化对象时自动被初始化

看以下例子:
由于a是自定义类型A的实例,所以在实例化b对象时,会自动调用A的默认构造函数来初始化a。
class A{
public:
	A()
    {//A的默认构造函数
		cout << "A()被调用" << endl;
	}
};

class B{
public:
	B(int x = 10)
    {     
        _x = x; 
    }
private:
    int x;
    A a; //自定义类型的成员变量
    //a是一个实例化的对象
};

int main()
{
	B b;
	return 0;
}

        在B的默认构造函数中没有显式调用A的默认构造函数。但由于a是自定义类型A的实例,所以在实例化B对象时,一开始就会自动调用A的默认构造函数来初始化a对象。


b. 但如果自定义类型的成员变量没有默认构造函数,必须在定义自定义类型的成员变量时设置一个默认值,该成员的默认值在对象调用构造函数前会完成对自定义类型的成员变量的创建和初始化,否则该成员变量无法被创建(如果不调用构造函数,就无法创建对象,因为编译器不知道如何初始化对象的状态。因此,构造函数是创建对象时必须调用的函数。):


总结:类中的自定义类型的成员变量在创建对象后要先调用该成员变量的构造函数进行初始化,否则该成员变量无法被创建。只有成员变量被创建,我们才能在对象的构造函数中对其进行初始化。所以对于没有默认构造函数的自定义类型的成员变量,我们必须在调用对象的构造函数之前,就调用该成员变量的构造函数,完成对该自定义类型的成员变量的创建。

2. 无参构造函数对成员变量初始化 

2.1 类型为内置类型的成员变量:

        对于编译器生成的或没有内容的无参构造函数不会对内置类型的成员变量进行初始化,所以其还是为随机值。


2.2 类型为自定义类型的成员变量:

        对于类中的自定义类型的成员变量在创建对象后要先调用该成员变量的构造函数,否则该成员变量无法被创建。只有成员变量被创建,我们才能在对象的构造函数中对其进行初始化。所以对于没有默认构造函数的自定义类型的成员变量,我们必须在调用对象的构造函数之前,就调用该成员变量的构造函数,完成对该自定义类型的成员变量的创建:

拓展. 内置类型成员在编译器生成的无参构造函数中不初始化的缺陷

编译器生成的无参构造函数不会对内置类型的成员变量进行初始化,所以其还是为随机值。


C++11中针对该缺陷,打了个补丁,即:

        内置类型成员变量在类中声明时可以设置默认值。【这里设置的默认值实际上是在设置构造函数的缺省值,因为我们只是在声明一个类,而不是在实例化一个对象。(可能有人会认为它是在初始化类,但其实它是在设置缺省值)】


四、默认构造函数

1. 什么是默认构造函数?

        默认构造函数是调用时可不提供参数的构造函数,所以说无参构造函数、全缺省构造函数、编译器生成的无参构造函数,都可以认为是默认构造函数。

2. 编译器生成的无参构造函数

2.0 没有构造函数时编译器会自动生成一个无参的构造函数

        如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数。一旦用户显式定义构造函数,编译器将不再生成无参的默认构造函数。


2.1 编译器生成的无参构造函数作用 

a. 用于对象的拷贝:创建新对象来拷贝旧对象时,首先要调用无参构造函数来初始化这个新对象,然后再把旧对象的所有成员变量拷贝到这个新的对象中。

MyClass a; // 这里会调用无参构造函数来初始化a
MyClass b = a; // 这里会调用拷贝构造函数来初始化b
//拷贝构造函数会首先调用无参构造函数来初始化新对象b,然后再把a的所有成员变量拷贝到这个新对象b中

b. 在类的继承中使用:在C++中,如果一个子类继承了父类,那么在创建子类的对象时,如果父类没有提供构造函数,编译器会自动生成父类的默认无参构造函数。如果父类没有默认无参构造函数,那么在创建子类的对象时不显示调用父类的构造函数会出现编译错误。

3. 保证只有一个默认构造函数

        要注意的是在定义类时无参构造函数和全缺省构造函数二者只能取其一,虽然能同时存在,但是不传参数时编译器不知道该调用哪一个,这样就造成了歧义,编译时很有可能会报错,所以不建议同时写这两种构造函数。推荐构造全缺省的构造函数,这样更省事,因为在传参个数方面,不用再对构造函数进行重载。


【总结】


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烛火萤辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值