C++【7】构造函数、析构函数、拷贝构造函数

1.构造函数:

构造函数和析构函数是在类体中说明的两种特殊的成员函数。构造函数是在闯将对象时,使用给定的值来将对象初始化。

析构函数的功能正好相反,是在系统释放对象前,对对象做一些善后工作。构造函数可以带参数,可以重载,同时没有返回值。

构造函数是类的成员函数,系统约定构造函数名必须与类名相同。构造函数提供了初始化对象的一种简单的方法。

cpp 复制代码
#include<iostream>
using namespace std;

class Cdemo1;
{
	public:
			Cdemo1(int a,int b)
			{
					x = a;
					y = b;
				cout<<"x= "<< x <<","<<"y="<< y <<endl<<endl;
			}
			int sumxy()
			{
				return (x+y);	
			}
	private:
			double x,y;		
};	


int main()
{
	Cdemo1 obj1(23,24);//只要程序运行,自动会初始化构造函数(自己会找到带有两个参数的构造函数)
	cout <<"x+y="<<obj1.sumxy()<<endl;
    return 0;
}

【对构造函数的几点说明】

构造函数是C++中用于初始化对象状态的特殊成员函数。以下是关于构造函数的几点说明:

  1. 构造函数名称与类名相同。
  2. 构造函数没有返回类型,也没有参数列表。
  3. 构造函数是在创建对象时自动调用的。
  4. 构造函数可以带有参数,这些参数可以在构造函数体中使用,以便为对象初始化提供不同的值。
  5. 构造函数可以重载,即可以为同一个类定义多个构造函数,只要它们的参数列表不同即可。
  6. 默认构造函数是没有参数的构造函数。如果没有定义构造函数,编译器会自动为类创建一个默认构造函数。
  7. 构造函数可以执行任意操作,例如输入/输出、内存分配、资源初始化等。
  8. 在类的继承中,子类的构造函数会调用父类的构造函数。可以使用显式调用或隐式调用。
  9. 在C++11及更高版本中,可以使用委托构造函数,将构造函数的初始化列表中的初始化委派给另一个构造函数。
  10. 在C++17中引入了结构化绑定,可以使用auto关键字和:操作符从构造函数初始化列表中提取对象的成员变量并单独赋值。
    **2.对局部对象,静态对象,全局对象的初始化对于局部对象,每次定义对象时,都要调用构造函数。**对于静态对象,是在首次定义对象时,调用构造函数的,且由于对象一直存在。只调用一次构造函数。

对于全局对象,是在main函数执行之前调用构造函数的。

cpp 复制代码
#include<iostream>
using namespace std;

class Cdemo1
{
		public :  //公有函数参数属性
		Cdemo1()
		{
			x = 0;
			y = 0;
			cout<<"\n 初始化静态局部对象"<<endl;	
		}	
		Cdemo1(double a)
		{
			x = a;
			y = 0;	
			cout <<"初始化全局对象"<<endl;
		}
		Cdemo1(double a ,double b)
		{
			x = a;
			y = b;	
			cout<<"初始化局部对象"	<<endl;
		}
		
private:	//私有函数参数属性
	double x,y;	
};

Cdemo1 objb(6.6);  //定义全局对象初始化优先级最高,排行在第一

void FuncTest()
	{
		cout<<"程序开始进入FuncTest()函数。 \n\n";
		Cdemo1 objc(10,20);
		static Cdemo1 objd;//创建初始化局部静态对象
	}
		
int main()
{
	cout<<"\n程序开始执行-->main()函数\n\n";
	Cdemo1 obja(6.6,2.3);//定义局部对象		

	FuncTest();
	return 0;
	
}

3.【缺省的构造函数】

在定义时,若没有定义类的构造函数,则编译器自动产生一个缺省的构造函数,其格式为:

cpp 复制代码
class::Name::className(){}

缺省的构造函数并不对所生成对象的数据成员赋值,即新产生对象的数据成员的值是不确定的。

cpp 复制代码
#include<iostream>
using namespace std;

class Ctest
{
	public:
	CtestA()
		{
			cout<<"\n调用缺省构造函数 \n \n";	
		}
	void setxy(int a,int b)
		{
			x = a ;
			y = b ;	
		}
	void disp()
	{
		cout << "x = " << x << "y = " << y << endl;	
	}

private:
		double x,y;
};


int main()
{
	Ctest obja,objb; //创建两个对象  产生对象时候,自动调用缺省的构造函数,不赋值
	obja.setxy(10,23);
	cout << "obja对象的结果为:" << endl;
	cout << obja.disp() << endl;
	
	
	cout << "objb对象的结果为:" << endl;
	cout << objb.disp() << endl;
	
	return 0;	
}

【关于缺省的构造函数,几点说明】

C++中的缺省构造函数是一个没有参数或所有参数都有默认值的构造函数。下面是关于缺省构造函数的几点说明:

  1. 缺省构造函数的作用是在没有提供构造函数的情况下自动调用,用于初始化类的对象。
  2. 如果一个类没有定义任何构造函数,编译器会自动提供一个缺省构造函数,该构造函数没有参数并且不执行任何操作。
  3. 如果一个类已经定义了一个或多个构造函数,编译器就不会再自动提供缺省构造函数。此时,如果需要一个缺省构造函数,必须显式地定义它。
  4. 缺省构造函数可以带有参数,但这些参数必须具有默认值。这样,在调用缺省构造函数时,如果没有提供参数,它们将使用默认值进行初始化。
  5. 在初始化列表中,可以使用其他构造函数来初始化同一个类的对象。这种情况下,使用关键字this来引用当前对象,并使用冒号和构造函数来指定要使用的另一个构造函数。
  6. 在类的继承中,如果基类没有定义缺省构造函数,而派生类定义了缺省构造函数,则该缺省构造函数将不会自动调用基类的构造函数。为了在派生类中调用基类的缺省构造函数,必须显式地在派生类的构造函数中使用基类的构造函数。
  7. 缺省构造函数可以是静态的或虚的,但静态构造函数不能在类继承中使用。虚构造函数在类继承中会被自动调用,但只能在派生类中重写。

【构造函数与new运算符】

构造函数和new运算符在C++中都用于处理对象的创建和初始化。然而,它们在处理内存分配和对象生命周期管理方面存在一些重要的差异。

构造函数是在对象创建时自动调用的。它是类的一部分,并且可以有多个构造函数在一个类中,这被称为构造函数的重载。每个构造函数都有一个独特的参数列表,根据提供的参数类型和数量,编译器会选择适当的构造函数进行调用。构造函数的主要目的是初始化对象的状态。

new运算符在C++中用于在堆上动态分配内存。它返回指向新分配的内存的指针,并可以用于创建对象。当你使用new运算符创建一个对象时,它首先调用对象的构造函数来初始化对象,然后返回指向新创建的对象的指针。

重要的是要注意new和构造函数之间的交互。当使用new运算符创建一个对象时,以下步骤会按照以下顺序发生:

  1. new运算符在堆上分配内存。

  2. 对象的构造函数被调用以初始化对象。

  3. new运算符返回指向新创建的对象的指针。

    cpp 复制代码
    class MyClass {  
    public:  
        MyClass() {  
            // 这是构造函数,它在对象创建时被自动调用  
            std::cout << "Object created." << std::endl;  
        }  
    };  
      
    int main() {  
        MyClass* obj = new MyClass();  // 使用new运算符创建对象  
        // 在这个点上,对象的构造函数已经被调用,并且new返回指向新创建的对象的指针  
        delete obj;  // 释放通过new分配的内存  
        return 0;  
    }

    在这个例子中,当你使用new MyClass()创建对象时,会首先在堆上分配内存,然后调用MyClass的构造函数来初始化对象,最后返回一个指向新创建的对象的指针。然后你就可以使用这个指针来访问和操纵对象。当你不再需要这个对象时,你应该使用delete运算符来释放通过new分配的内存,防止内存泄漏。

例子2:

cpp 复制代码
#include<iostream>
using namespace std;

class CTestA
{
	public:
			CTestA()  //缺省的构造函数
			{
				x = 0;				
				y = 0;
				cout <<"调用缺省的构造函数 \n" << endl;
			}
			CTestA(int a , int b)  //带参的构造函数
			{
				x = a;
			    y = b;
				cout << "调用带参的构造函数 \n" << endl;	
			}
			void disp_xy()  //创建方法  'dispxy'是该类中的一个方法
			{
				cout << "x = " << x << "y = " << y <<endl;
			}
private:
	double x,y			
};


int main()
{
	CTestA *pobja, *pobjb; //创建两个对象指针
	
	pobja = new CTestA; //用new动态开辟存储空间,调用缺省构造函数
	pobjb = new CTestA(11,22); //用new动态开辟存储空间,调用带参的构造函数
	
	pobja-> dispxy();
	pobjb-> dispxy();  //调用两个对象的方法  'dispxy'是该类中的一个方法
	
	
	/*
	在C++中,当对象不再需要时,应当手动删除以避免内存泄漏。
	注意,在调用delete后,应将指针设为nullptr,
	以避免悬挂指针(dangling pointer)问题。
	*/
	delete pobja;
	delete pobjb;  
	
	return 0;	
}

4.析构函数 :

C++中的析构函数是一种特殊的成员函数,它在一个对象的生命周期结束时被调用,以进行必要的清理工作。析构函数没有返回类型,也没有参数,它的函数名与类名相同,但前面加上一个波浪符~

析构函数在以下情况下会被调用:

  1. 当一个对象离开其作用域时,例如在函数或类的生命周期结束时。
  2. 当使用delete操作符释放动态分配的对象时。
  3. 当使用容器(如std::vectorstd::list)删除其元素时。

析构函数可以用于释放对象使用的资源,例如动态分配的内存、打开的文件句柄等。它通常进行一些清理工作 ,如清除在堆上的数据结构、释放在构造函数中分配的资源等。

cpp 复制代码
#include <iostream>  
#include <string>  
  
class MyString {  
private:  
    std::string* str;  
  
public:  
    MyString(const std::string& s = "") : str(new std::string(s)) {  
        std::cout << "Constructor called." << std::endl;  
    }  
  
    ~MyString() {  
        delete str;  
        std::cout << "Destructor called." << std::endl;  
    }  
};  
  
int main() {  
    MyString s("Hello, world!");  
    // Do something with s...  
    // When s goes out of scope, its destructor is called.  
    return 0;  
}

在这个例子中,MyString类的构造函数使用new操作符在堆上分配了一个std::string对象,并在构造函数中将其初始化。当MyString对象离开其作用域时,它的析构函数将被调用,释放分配给字符串的内存,并输出一条消息。

简单例子:

cpp 复制代码
#include<iostream>
using namespace std;

class  CTestA
{
public:
		CTestA()
		{
			cout << "调用缺省的构造函数" << endl;	
		}
		CTestA(double a,double b)
		{
			x = a;
			y = b;
			cout << "调用带参的构造函数" << endl;	
		}
		~CTestA()
		{
			cout << "调用析构函数" << endl;	
		}
private:
		double x,y;
};
	
	
int main()
{
	CTestA obj1;
	CTestA obj2(2.3,2.1);
	CTestA obj3(11.1,2.01);
	
	return 0;	
}

析构函数的特点:

(1)析构函数是成员函数,函数体可写在类体内,也可写在类体外。

(2)析构函数是一个特殊的成员函数,函数名必须与类名相同,并在其前面加上字符**"~"**,以便和构造函数名相区别。

(3)析构函数不能带有任何参数,不能有返回值,不指定函数类型。

(4)一个类中,只能定义一个析构函数,析构函数不允许重载。

(5)析构函数是在撤销对象时由系统自动调用的。

在程序的执行过程中,当遇到某一对象的生存期结束时,系统自动调用析构函数,然后再收回对象分配的存储空间。

在程序的执行过程中,对象如果用new运算符开辟了空间,则在类中应该定义一个析构函数,并在析构函数中使用delete删除由new分配的内存空间。因为在撤销对象时,系统自动吸收回为对象所分配的存储空间,而不能自动收回由new分配的动态存储空间。

cpp 复制代码
    delete pobja;
	delete pobjb;  

实现类型转换的构造函数:同类型的对象可以互相赋值,相当于类中的数据成员相互赋值;如果直接将数据赋给对象,所赋入的数据需要强制类型转换,这种转换需要调用构造函数。

cpp 复制代码
#include<iostream>
using namespace std;

class  CTestA
{
public:
		
		CTestA(double a,double b)
		{
			x = a;
			y = b;
			cout << "调用带参的构造函数" << endl;	
		}
		~CTestA()
		{
			cout << "调用析构函数" << endl;	
		}
		void dispxy()
		{
			cout<< x << "," << y << endl << endl;	
		}
private:
		double x,y;
};
	
	
int main()
{
	CTestA obj1(2,3);
	obj1.dispxy();
	
	obj1 = CTestA(30,35);
	obj1.dispxy();
	
	return 0;	
}

5.拷贝构造函数:

拷贝构造函数在C++中是一种特殊的构造函数,用于创建一个新对象作为现有对象的副本。当创建新对象并初始化为现有对象的副本,或者一个函数以值传递方式接受一个对象,或者一个函数返回一个对象时,都会调用这个拷贝构造函数。

cpp 复制代码
class_name (const class_name &old_obj);

其中,class_name 是类的名称,&old_obj 是对现有对象的引用,前面的 const 是为了保证不会修改到原始对象。

例如,如果我们有一个名为 Person 的类,其拷贝构造函数可能如下:

cpp 复制代码
Person::Person(const Person &p) {  
    name = p.name;  
    age = p.age;  
    // 其他需要复制的成员变量...  
}

在这个拷贝构造函数中,我们创建一个新的 Person 对象,并将其初始化为参数 p 的一个副本。这意味着新对象的 nameage 会和 p 对象的 nameage 一样。

需要注意的是,如果没有显式地为类提供拷贝构造函数,编译器将自动为你提供一个。但是,这个默认的拷贝构造函数可能不会按你期望的方式工作,特别是当对象有动态分配的内存或其他需要"深拷贝"的资源时。在这种情况下,你需要自定义拷贝构造函数以确保正确的行为

cpp 复制代码
#include<iostream>
using namespace std;

class  CTestA
{
private:
		int *ptr;
public:
		CTestA(int a); //构造函数
		CTestA(const CTestA&obj); //拷贝构造函数
		~CTestA(); 	//析构函数
};

CTestA::CTest(int a) //构造函数
{
	cout <<"调用构造函数" <<endl;			
	ptr = new int;		//为指针分配内存空间
	*ptr = a;
}

CTestA::CTestA(const CTestA&obj) //拷贝构造函数
{
	cout <<"调拷贝构造函数并为指针ptr分配内存空间 \n";
	ptr = new int ;
	*ptr = *obj.ptr; //拷贝值	
}

CTestA::~CTestA() //析构函数
{
	cout << "释放内存空间" <<endl;	
	delete ptr;
}

void dispxy()
{
	cout<< "ptr的值为:" << obj.getptr() << endl << endl;	
}

int main()
{
	CTest obj(5000);
	dispptr(obj);
	
	return 0;	
}
相关推荐
秋の花几秒前
【JAVA基础】Java集合基础
java·开发语言·windows
偷心编程几秒前
双向链表专题
数据结构
香菜大丸1 分钟前
链表的归并排序
数据结构·算法·链表
jrrz08281 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
小松学前端3 分钟前
第六章 7.0 LinkList
java·开发语言·网络
可峰科技12 分钟前
斗破QT编程入门系列之二:认识Qt:编写一个HelloWorld程序(四星斗师)
开发语言·qt
oliveira-time13 分钟前
golang学习2
算法
咖啡里的茶i16 分钟前
Vehicle友元Date多态Sedan和Truck
c++
全栈开发圈16 分钟前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫
面试鸭21 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展