C++ 构造函数&析构函数

目录

构造函数

1.构造函数的定义

2.构造函数的性质

​编辑释义1  :无返回值不需要添加void,并且构造函数的函数名称要和类名一致

 ​编辑释义2:构造函数可以重载

​编辑释义3:对象被实例化时编译器自动调用对应构造函数 

        1.括号法

        2.显示法

        3.隐式转换法 

​编辑释义4:C++编译器会自动生成一个无参的默认构造函数,但如果用户显示定义构造函数,那么编译器将不再生成默认的构造函数

​编辑释义5:无参构造函数和全缺省构造函数都被称之为默认构造函数,但是默认构造函数只能有一个

 析构函数

        1.什么是析构函数?

        2.析构函数特性?

         3.析构函数的被调用顺序


构造函数

1.构造函数的定义

        构造函数是一种特殊的成员函数,它在创建对象时由编译器自动执行,其主要作用是初始化对象。

学生类展示:

class Student {
public:
	Student() {
		ID = 01;
		age = 20;
	}
	void Print() {
		cout << "ID:" << ID << endl << "age:" << age << endl;
	}
private:
	int ID;
	int age;
};
int main()
{
	Student s1;
	s1.Print();
	return(0);
}

2.构造函数的性质

  1. 无返回值不需要添加void,并且构造函数的函数名称要和类名一致
  2. 构造函数可以重载
  3. 对象被实例化时编译器自动调用对应构造函数
  4. C++编译器会自动生成一个无参的默认构造函数,但如果用户显示定义构造函数,那么编译器将不再生成默认的构造函数
  5. 无参构造函数和全缺省构造函数都被称之为默认构造函数,但是默认构造函数只能有一个
释义1  :无返回值不需要添加void,并且构造函数的函数名称要和类名一致

如下无参构造函数示例:

 释义2:构造函数可以重载

构造函数的重载类型:

1.按照参数进行分类:有参构造和无参构造

//无参构造
Student() {
	ID = 01;
	age = 20;
}
//有参构造
Student(int ID, int age) {
	this->ID = ID;
	this->age = age;
}

无参构造结果展示:

有参构造结果展示:

        初始化列表:

        初始化列表是构造函数中的一种语法糖,用于在对象创建时直接初始化成员变量,而不是在构造函数体内赋值,这样做会更加高效,而且在某些情况下是必须的,比如const成员变量和引用类型必须在初始化列表中初始化:

        

class Student {
public:
	Student(int ID, int age):ID(ID),age(age) {

	}
	void Print() {
		cout << "ID:" << ID << " " << "age:" << age << endl;
	}
private:
	int ID;
	const int age;
};

2.按照类型进行分类:普通构造函数和拷贝构造函数

        拷贝构造函数:是一种特殊的构造函数,具有一般构造函数的特性。可以实现用现有对象去完成对新建对象的赋值操作,使用const修饰。

        拷贝构造函数声明:

ClassName(const ClassName& other);

其中:

  • ClassName 是类的名称,other 是另一个同类型实例对象的引用

代码展示:

//无参构造
Student() {
	ID = 01;
	age = 20;
	cout << "无参构造" << endl;
}
//拷贝构造
Student(const Student& a) {
	this->ID = a.ID;
	this->age = a.age;
	cout << "拷贝构造" << endl;
}

代码结果展示:

从图中我们不难看出:

        首先使用无参构造实例化变量s1,初始化s1的ID为1,age为20;接着使用拷贝构造对实例对象s2进行初始化赋值操作,使s2的ID为1,age为20;最后我们调用Print函数输出ID和age,此时注意!!!

        我们在调用Print函数时,使用的是值传递的方式,因此会再创建一个Student对象的副本用于传参,这时又触发了拷贝构造函数,使得输出了两次“拷贝构造”。

        如果我们希望最后调用Print函数的时候,能够减少拷贝构造函数的调用,该怎么做呢?

可以考虑将Print函数的参数改为引用传递

改为引用传递后的结果展示:

释义3:对象被实例化时编译器自动调用对应构造函数 

首先,我们需要知道构造函数的调用方法:

  • 括号法
  • 显示法
  • 隐式转换法
        1.括号法

语法:

类名 对象名 (参数值);

Student a(2,21);

//有参构造
Student(int ID) {
	this->ID = ID;
	age = 20;
	cout << "1个参数的有参构造" << endl;
}
Student(int ID, int age) {
	this->ID = ID;
	this->age = age;
	cout << "2个参数的有参构造" << endl;
}
int main()
{
	Student s1(1);    //括号法调用有参构造  
	Student s2(2,21); //括号法调用有参构造 
	s1.Print(s1);
	s2.Print(s2);
	return(0);
}

代码结果展示:

由图可知:

        编译器会根据括号中传入的参数个数,自动匹配对应的有参构造函数,从而初始化实例对象。

        2.显示法

语法:

类名 对象名 = 类名(参数值);

Student a = Student(2,21);

代码方面只有创建实例对象时不同:

int main()
{
	Student s1 = Student(1);
	Student s2 = Student(2,21);
	s1.Print(s1);
	s2.Print(s2);
	return(0);
}

代码结果展示:

        3.隐式转换法 

        注意:对于隐式转换法来说,多参数的不适用

语法:

类名 对象名 = 数值;

Student a = 1;

代码展示:

int main()
{
	Student s1 = 1;
	s1.Print(s1);
	return(0);
}

结果展示:

对于图可知:

        从结果上可知,我们调用了两次“1个参数的有参构造”,这是因为:
        隐式转换法不可以用于多参数,原因是“,”的运算符的运算规则指挥返回最后一个表达式的值,即返回了(2,21)中的21,因此不可以用于多参数。

释义4:C++编译器会自动生成一个无参的默认构造函数,但如果用户显示定义构造函数,那么编译器将不再生成默认的构造函数

如果我们自己不去显示定义构造函数,使用编译器默认的构造函数:

class Student {
public:
	void Print() {
		cout << "ID:" << ID << " " << "age:" << age <<endl;
	}
private:
	int ID;
	int age;
};

int main()
{
	Student s1;
	s1.Print();
	return(0);
}

 

可知输出值为随机值。那么为什么编译器会产生默认构造函数?又为什么会输出随机值呢?那么,我们先来了解一下默认构造函数 :

    默认构造函数类型:

    • 编译器默认生成的构造函数,是默认构造函数
    • 无参构造函数,是默认构造函数
    • 全缺省函数,是默认构造函数

    C++会把变量分为两种类型:
    1.内置类型:int,char,double,指针......

    2. 自定义类型:class,struct等自己定义的类型对象

            C++默认的构造函数不会对内置类型变量进行处理,只有对自定义类型的变量才会进行处理。这就是为什么刚刚没有对ID和age进行初始化处理,因为其是内置类型变量(int类型的变量)

    代码示例:

            首先,我们创建一个自定义类A,含有成员变量a,并且无参构造对a进行初始化

    class A {
    public:
    	A() {
    		cout << "A的无参构造" << endl;
    		a = 1;
    	}
    private:
    	int a;
    };
    

                接着,我们将A类作为自定义类型加入到Student的成员变量中

    class Student {
    public:
    	void Print() {
    		cout << "ID:" << ID << " " << "age:" << age <<endl;
    	}
    private:
    	int ID;
    	int age;
    	A b;
    };
    
    int main()
    {
    	Student s1;
    	s1.Print();
    	return(0);
    }

    运行结果展示:

    由图可知:

            编译器先是调用了类A的无参构造;对于s1.ID和s1.age的值,返回的是随机值;可知,默认构造函数对自定义类型才会进行处理,也就是说,当出现内置类型时,我们就需要自己写构造函数了。

    释义5:无参构造函数和全缺省构造函数都被称之为默认构造函数,但是默认构造函数只能有一个

    默认构造函数类型:

    • 编译器默认生成的构造函数,是默认构造函数
    • 无参构造函数,是默认构造函数
    • 全缺省函数,是默认构造函数

    我们可以通过直接在创建类的时候就对成员变量进行赋值初始化,从而达到即使不显示编写构造函数,也可以对内置类型进行处理:

    class Student {
    public:
    	//编译器会生成一个默认无参构造函数
    	void Print() {
    		cout << "ID:" << ID << " " << "age:" << age <<endl;
    	}
    private:
    	//C++11打的补丁,针对编译器自己默认成员函数不初始化的问题
    	int ID = 01;
    	int age = 20;
    };
    
    int main()
    {
    	//没有显示定义构造函数,这里调用编译器提供的默认构造函数
    	Student s1;
    	s1.Print();
    	return(0);
    }
    

    总结:

    1. 构造函数是类中默认就有的,即使自己没有显式定义,但日常最好自己显式定义一个默认构造函数,注意不要使无参和全缺省构造函数同时存在。
    2. 如果使用默认构造函数,内置类型的变量不会被初始化,会得到一个随机值;自定义类型的变量会调用默认构造函数;如果不想出现随机值,可以在内置类型声明的时候就进行初始化。

     析构函数

            1.什么是析构函数?

            析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁的工作是由编译器完成的,而对象在被销毁时会自动调用析构函数,完成类的一些资源清理工作。

            2.析构函数特性?

    1. 析构函数的函数名是在类名前面加上~
    2. 无参无返回值
    3. 一个类只有一个析构函数,如果用户没有显示定义析构函数,系统会提供默认析构函数;并且编译器默认生成的析构函数,会对自定类型成员调用它的析构函数
    4. 对象生命周期结束时,编译器自动调用析构函数

     举例析构函数:

    ~Student() {
    	cout << "~Student" << endl;
    }

    举例实例: 

    class Student {
    public:
    	Student(int ID, int age):ID(ID),age(age) {
    
    	}
    	~Student() {
    		cout << "~Student" << endl;
    	}
    	void Print() {
    		cout << "ID:" << ID << " " << "age:" << age << endl;
    	}
    
    private:
    	int ID;
    	const int age;
    };
    
    int main()
    {
    	Student s1(2,21);
    	s1.Print();
    	return(0);
    }

    如图可知,最后在对象生命周期结束后,编译器调用了析构函数,输出了“~Student”;

            那么,析构函数的目的是完成资源清理,它清理的是什么内容呢?对于我们定义的ID和age这些变量就不需要清理,因为其出了函数栈就被销毁,真正需要析构函数清理的是malloc,new,fopen等等。

             3.析构函数的被调用顺序

     析构顺序规则:

    1. 成员变量的析构:首先,对象的非静态成员变量的析构函数会被调用。这些成员变量的析构顺序与它们在类定义中声明的顺序相反。
    2. 基类的析构:如果对象是某个类的派生类实例,基类的析构函数会被调用,对于多层继承,基类的析构函数调用顺序与继承顺序相反,即从最派生类到最基类。
    3. 局部对象的析构:在函数作用域中,局部对象的析构顺序与它们被创建的顺序相反,这意味着先创建的对象后被析构。

    实例代码:

    class base {
    public:
    	base() {
    		cout << "base()" << endl;
    	}
    	~base() {
    		cout << "~base()" << endl;
    	}
    };
    class Derived :public base{
    public:
    	Derived() {
    		cout << "Derived()" << endl;
    	}
    	~Derived() {
    		cout << "~Derived()" << endl;
    	}
    };
    class anotherDerived :public base {
    public:
    	anotherDerived() {
    		cout << "anotherDerived()" << endl;
    	}
    	~anotherDerived() {
    		cout << "~anotherDerived()" << endl;
    	}
    };
    class A {
    public:
    	Derived d;
    	anotherDerived ad;
    
    	A() {
    		cout << "A()" << endl;
    	}
    	~A() {
    		cout << "~A()" << endl;
    	}
    };
    
    int main()
    {
    	A a;
    	return(0);
    }

    代码结果展示:

    析构顺序分析:

    • 先销毁A对象a:实现调用A的析构函数
    • 再销毁成员变量ad:然后调用anotherDerived的析构函数,并且调用基类base的析构函数
    • 最后销毁成员变量d:最后调用Derived的析构函数,并且调用基类base的析构函数

    之所以先调用ad的析构函数,再调用d的析构函数,是因为在类A中,我们先定义了成员变量“Derived d”再定义了“anotherDerived ad”,因此可知。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值