c++:构造函数

        

目录

构造函数:

默认的构造函数:

1.编译器自动生成的构造函数

2.全缺省值的构造函数

3.无参构造方法

 拷贝构造方法:

总结:


        C语言在创建栈,队列等数据结构时,首先要进行初始化,使用完之后还需要对动态开辟的空间进行释放,构造函数和析构函数是C++对结构体初始化和使用完后空间清理的操作所作的优化,将手动的初始化和清理变成自动,构造函数功能是对结构体初始化,析构函数负责对空间清理.

构造函数:

        1.函数名与类名相同

        2.无返回值

        3.实例化对象时自动调用

        4.可以重载

如下:Person类包含了一个无参构造方法和一个有参构造方法:

c++:构造函数_第1张图片

 在main()函数中,我们没有调用,实例化对象时会自动调用,来完成对成员变量的初始化:

c++:构造函数_第2张图片

构造函数是在实例化对象时,对成员变量初始化,而不是为成员变量开辟空间

默认的构造函数:

1.编译器自动生成的构造函数

        在类中,当未显示定义构造函数时,编译器会自动生成一个构造函数,我们称它为默认构造函数,该自动生成的构造方法我们无法观察到,但是在编译的时候它确实存在:

c++:构造函数_第3张图片

c++:构造函数_第4张图片

 自动生成的构造函数在对成员变量初始化时,对内置类型的变量和自定义类型的变量区别处理:

1.对内置类型变量赋随机值

2.对自定义类型变量,调用该自定义类型变量的默认构造函数

3.注意在调用无参构造方法时,不需要再在对象后面加(),如果加上(),Person p1() ,编译器会无法分辨出它是一个函数,还是你在调用无参构造方法.

2.全缺省值的构造函数

c++:构造函数_第5张图片

 当我们给构造函数全缺省值时,在对象初始化时,会根据缺省值初始化.

3.无参构造方法

c++:构造函数_第6张图片

 拷贝构造:

        拷贝构造方法是用另一个实例化对象完成对新对象的实例化,二者的成员变量内容相同.

特点: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
对象创建新对象时由编译器自动调用

        拷贝构造函数也是特殊的成员函数,其特征如下:
        1. 拷贝构造函数是构造函数的一个重载形式。
        2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(const Date& d) // 正确写法
	Date(const Date& d) // 错误写法:编译报错,会引发无穷递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		//这里的成员变量都是私有,只要是在类里面,对象都可以访问自己私有的成员变量
	}

	//默认拷贝构造对内置类型进行浅拷贝,对自定义类型,调用它的默认拷贝构造
	//默认拷贝构造是浅拷贝,即逐字节的拷贝,当被拷贝的指针 ,与拷贝的指针指向同一块动态开辟的空间时,在最后自动调用析构函数时,会对该空间释放两次
	//同时,浅拷贝的空间,通过对被拷贝的变量操作,会影响拷贝的变量
	// 
	// 这也是为什么自定义类型的数据在拷贝的时候需要调用拷贝构造函数,如果不是调用拷贝构造函数,数据的拷贝只能实现浅拷贝
	// 类似于同一块空间释放两次的问题(由于析构函数的存在)不好解决
	// 
	//而如果需要深拷贝,需要自己实现

	void Print()
	{
		cout << _year <<"年"<< _month <<"月"<< _day <<"日"<< endl;
	}
private:
	int _year;
	int _month;
	int _day;

};


//先为d开辟空间,然后拷贝,即先调用拷贝构造函数,拷贝构造函数this地址是d的地址,拷贝构造函数形参d1,
//开辟空间,调用拷贝构造函数,this是d1的地址


int main()
{
	Date d2(2023,5,2);
	Date d1(d2);
}

        解释下代码:

c++:构造函数_第7张图片

        这里用d2来实例化d1,意思就是将d2中的成员变量的值依次赋值给d1中的成员变量的值,Date类中定义了拷贝构造函数:

 

c++:构造函数_第8张图片

        C++中,拷贝构造函数的形参必须是结构体的引用:        

         在C++中,定义两个相同类型的结构体赋值,即将d2中的内容拷贝给d1,需要调用结构体中的拷贝构造函数,如果未显示定义拷贝构造函数,那么编译器也是会默认生成拷贝构造函数,但是这里的拷贝是浅拷贝,即将两个结构体内的成员内容逐字节的复制.

        如果用结构体作为实参,形参不是结构体的引用,而是用一个相同类型的结构体变量接收,会出现死递归:

        上面说C++的结构体之间的复制必须调用拷贝构造函数:

c++:构造函数_第9张图片

         fun()函数的形参d接收d2的值,要先调用拷贝构造函数,如果这里的拷贝构造函数形参是对应的结构体类型变量,形参d同样会再次调用拷贝构造方法.

c++:构造函数_第10张图片

c++:构造函数_第11张图片

         拷贝构造函数存在的原因:

       1. 假设d1成员变量day_是指针,并且指向动态开辟的空间,d2对d1进行拷贝,我们说默认的拷贝构造函数是浅拷贝,那么d2中day_变量也会指向d1中day_变量指向的动态开辟的空间,到这还没有问题,但是当两个变量出了作用域,编译器自动调用析构函数的时候,会对该动态开辟的空间释放两次,程序会崩溃.

        2.由于两个day_指向的时同一块空间,如果对d1中的day_修改,d2中day_中的值也会改变

        所以,当拷贝的结构体中有指针变量时,需要我们手动定义拷贝构造函数,让两个结构体中的成员变量指向不同的动态空间.

初始化列表:

        上面的构造函数对成员变量的初始化都是在函数体内,出了在函数体内,C++还有一种初始化方式,就是初始化列表.

        初始化列表以一个冒号开始,接着是一个以逗号分隔的数据,每个"成员变量"后面跟一个放在括
号中的初始值或表达式:

c++:构造函数_第12张图片

         


        初始化列表存在原因是当成员变量含有自定义类型,引用类型或者被const修饰时,只能在初始化列表中才能被初始化,给出下面的示例代码:

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
class B
{
public:
	B(int a, int ref)
		:_aobj(a)
		, _ref(ref)
		, _n(10)
	{}
private:
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const
};

        上面的const成员和引用类型成员,共同的特征是必须在定义的时候初始化.

        引用是对另一个变量起了别名,语法规定引用类型必须在定义的时候赋值,这一步就好像是将该引用变量与引用的对象进行绑定,绑定后该引用变量就一直引用该对象.

        而const修饰的变量同样要求定义时赋值,一旦赋值后,该变量就一直表示该值,也有点类似绑定的意思.

        由于要体现不同对象间的差异,不能在定义类的时候就给成员变量赋值,但是二者的特性又要求定义的时候赋值,所以为了解决上述问题就有了初始化列表.

        同时注意我们说的成员变量的定义值的是c++:构造函数_第13张图片这个位置,而在构造函数内我们对变量是赋值,编译器不认为构造函数体内是成员变量定义的地方.


         同时对于自定义类型,前文也提到过,内置类型编译器不做处理,而自定义类型编译器会去调用它的默认构造,并且就是在初始化列表中调用的默认构造,就算我们不在初始化列表中显示的调用它的默认构造,编译器也会自动调用.

        这里的问题是,编译器只能调用它的默认构造,当你不想调用A类的默认构造,而是想通过有参构造对A对象初始化怎么办?如果没有初始化列表的话你会发现没有办法.

        所以初始化列表对与自定义成员的变量来说,就是为了让你能够调用它的有参构造从而对其进行初始化.


           还有一种初始化的方式:

c++:构造函数_第14张图片

     `这里的赋值操作不是初始化,而是给了缺省值,这里是C++11打的补丁,该缺省值会传递给初始化列表.本来我们说编译器不会对内置类型进行处理,但是如果给了缺省值,编译器就会将内置类型初始化成该默认值.

        但是如果在初始化列表中你已经给了值,那么这个缺省值就没了作用.

        最后说一下还要注意的几个问题:

        1.初始化列表中只能对成员变量初始话一次

        2.上述示例代码对成员变量ref初始化时引用了一个局部变量,正确的用法是,在类外部传入一个变量,让该成员引用外部的变量.

    

总结:

1.构造方法是为了自动化结构体初始化的过程.

2.当未显式定义构造函数时,编译器会自动生成默认构造方法,编译器自动生成的默认构造方法对内置类型变量和自定义类型变量区别处理:

        (1) 对内置类型变量初始化时,赋随机值

        (2) 对自定义类型变量初始化时,调用该变量的默认构造方法

3.无参构造方法,全缺省构造方法,编译器自动生成的构造方法 ,这三种构造方法被统称为默认构造方法,因为他们都不需要传递参数,并且,三种形式的构造方法在同一个类中只能有一个.

4.拷贝构造方法只有一个参数,并且必须是类的引用,如果是结构体类型,会出现死递归

5.编译器自动生成的拷贝构造函数只能实现浅拷贝,如果成员变量中有指向动态开辟的指针,需要自定义拷贝构造函数完成深拷贝

6.初始化列表是为了能够实现引用类型和const变量定义时初始化,且为了能显示调用自定义类型的有参构造.

你可能感兴趣的:(c++,开发语言)