c++中的构造函数和析构函数

article/2025/10/2 6:06:34

类和对象中,包括构造函数和析构函数,比较重要,通过学习总结一下,以便以后可以回顾!

目录

构造函数

1.默认构造函数

2.有参构造函数

 3.委托构造函数

4.复制(拷贝)构造函数

5.移动构造函数

左值引用与右值引用

析构函数

析构函数


构造函数

构造函数,就是在对象创建的时候,把类中的属性进行一个初始化,而且创建对象时候会自动调用并且只调用一次。(就比如我们买一个手机,最开始它会有相应的系统设置(这个就有点类似构造函数),后面可以修改其设置,也就相当于类中函数对属性修改。)

下面介绍几个常用的构造函数...

1.默认构造函数

不含参数的构造函数,举个例子,创建一个Person类,包括年龄和身高两个属性,年龄(整形),身高(整形指针),默认构造初始画年龄是0,身高0。如果我们不写的话系统会默认空实现

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();int m_Age;int* m_Height;
};
//初始化年龄和身高
Person::Person() 
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
int main()
{Person p;cout << "此人的年龄是: " << p.m_Age << endl;cout << "此人的身高是: " << *(p.m_Height) << endl;return 0;
}

显然我们在创建函数的同时,把属性进行了初始化,测试把属性输出,另外注意一点的是创建对象时是Person p; 如果我们这样写Person p(); 编译器会认为是创建了一个函数p 返回值类型Person,形参为空  {}空实现  这个一定注意。


2.有参构造函数

下面介绍一下有参构造,还是接着用上面的例子进行添加,继续,如果我们想创建一个指定年龄和身高的对象,这时候就要用到有参构造了,例子

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);int m_Age;int* m_Height;
};
//默认构造
Person::Person() 
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age,int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
int main()
{Person p(18,175);cout << "此人的年龄是: " << p.m_Age << endl;cout << "此人的身高是: " << *(p.m_Height) << endl;return 0;
}

 现在创建一个对象p(18,175),创建时可以直接赋值,实现对对象的初始化。


 3.委托构造函数

顾名思义,委托构造函数就是把自己构造的事情,交给其他的构造函数顺带完成,例如我把默认构造函数修改一下,

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);//拷贝构造Person(const Person& p);int m_Age;int* m_Height;
};
默认构造
Person::Person() :Person(0, 0)
{cout << "委托构造函数的调用!" << endl;
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{cout << "拷贝构造函数的调用!" << endl;this->m_Age = p.m_Age;this->m_Height = new int(*p.m_Height);
}
int main()
{Person p;cout << "p的年龄是: " << p.m_Age << endl;cout << "p的身高是: " << *(p.m_Height) << endl;return 0;
}

这里默认构造用: 把构造交给有参构造顺带完成!所以先会调用有参构造,其次委托构造,输出年龄和身高验证一下:


4.复制(拷贝)构造函数

上面都是开始是给出属性的值进行初始化,而拷贝构造函数在开始是传递一个对象,把对象的个属性拷贝到此对象中,进行对象的初始化(可以把传去的参数,看成一只羊,拷贝构造后克隆了一只新的羊,两只羊的属性一样)根据这样可以实现对象的创建。

这里为了方便把委托构造函数还原成默认构造函数了,如下

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);//拷贝构造Person(const Person& p);int m_Age;int* m_Height;
};
//默认构造
Person::Person()
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{cout << "拷贝构造函数的调用!" << endl;this->m_Age = p.m_Age;this->m_Height = new int(*p.m_Height);
}
int main()
{Person p(18, 175);cout << "p的年龄是: " << p.m_Age << endl;cout << "p的身高是: " << *(p.m_Height) << endl;Person p2(p);cout << "p2的年龄是: " << p2.m_Age << endl;cout << "p2的身高是: " << *(p2.m_Height) << endl;return 0;
}

 这里我用了深拷贝构造,这样可以防止堆区的内存重复被析构函数释放,保证代码的安全。深浅拷贝以后有时间我再整理一下,拷贝前面加入const限定可以防止修改成员属性保证完全一致。这个用了引用左值,进行一个赋值,进行理解,上面为左值参数的引用拷贝。


5.移动构造函数

左值引用与右值引用

   首先要了解左值和右值的引用,左值是位于赋值左侧的对象变量(依附于对象);右值是赋值语句在右侧的值(不依附于对象)。简单可以理解为能用&取地址的就是左值,否则是右值。

int age=20;//左值
int Func()
{return 100;
}
int ret=Func();//ret是左值,Func()右值
int x=2*2;//x左值,2*2右值

接下来进行右值引用的移动构造函数,即要创建一个右值,然后调用,为了方便输出展示信息,我添加了一个成员函数ShowPerson()展示信息

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);//拷贝构造Person(const Person& p);//移动构造函数Person(Person&& p);//展示信息void ShowPerson();int m_Age;int* m_Height;
};
//默认构造
Person::Person()
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{cout << "拷贝构造函数的调用!" << endl;/*this->m_Age = p.m_Age;this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{cout << "移动构造函数的调用!" << endl;this->m_Age = p.m_Age;this->m_Height = p.m_Height;p.m_Height = NULL;
}
//展示信息
void Person::ShowPerson()
{cout << "年龄是: " << this->m_Age << endl;cout << "身高是: " << *(this->m_Height) << endl;
}
//返回一个右值的对象
Person GetPerson()
{Person p;return p;
}
int main()
{GetPerson().ShowPerson();return 0;
}

分析:在主函数中先调用GetPerson()函数创建一个右值对象,调用时创建了一个Person p,所以会先输出调用默认构造,其次调用了右值移动构造,这里进行了深拷贝,防止堆区数据重复释放!右值对象属于无法取地址的对象所以很快就会被清除,用移动构造处理

 这里借用:一枚小码农介绍中的一张图,对于普通数据可以直接赋值,但对于指针类型,要把原本的指向赋值为空,然后进行销毁内存!

接下来整理析构函数的时候简单介绍下浅拷贝对指针信息的影响。

析构函数

析构函数是用于在对象被删除之前的清理工作,在对象生命周期即将结束时被自动调用。(有始有终,什么都会生死,析构函数可以清理对象并且释放内存)

析构函数

在上面的介绍没有介绍到析构函数,可能不会没有问题。析构函数~加上类名()进行操作,目的时清空数据,释放内存。

现在加上析构函数再次测试调用并且进行思考,例如最简单的,创建一个默认构造函数p,进行默认构造,然后调用展示信息函数输出信息,最后析构函数,释放内容,防止内存泄漏!

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);//拷贝构造Person(const Person& p);//移动构造函数Person(Person&& p);//析构函数~Person();//展示信息void ShowPerson();int m_Age;int* m_Height;
};
//默认构造
Person::Person()
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{cout << "拷贝构造函数的调用!" << endl;/*this->m_Age = p.m_Age;this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{cout << "移动构造函数的调用!" << endl;this->m_Age = p.m_Age;this->m_Height = p.m_Height;p.m_Height = NULL;
}
//展示信息
void Person::ShowPerson()
{cout << "年龄是: " << this->m_Age << endl;cout << "身高是: " << *(this->m_Height) << endl;
}
Person::~Person()
{cout << "析构函数的掉用!" << endl;if (this->m_Height != NULL){delete this->m_Height;}	
}
//返回一个右值的对象
Person GetPerson()
{Person p;return p;
}
int main()
{Person p;p.ShowPerson();return 0;
}

发现打错字了,懒得改了!后面有时间总结一下深浅拷贝,虚函数,虚析构等等,到时候再总结吧!以上就是我总结的内容,欢迎大佬指正,谢谢!

移动构造的测试解释 std::move函数可以把左值改成右值,所以测试其代码

#include<iostream>
using namespace std;
class Person
{
public://默认构造函数Person();//有参构造Person(int age, int height);//拷贝构造Person(const Person& p);//移动构造函数Person(Person&& p);//析构函数~Person();重载运算符=//Person& operator=(Person p);//展示信息void ShowPerson();int m_Age;int* m_Height;
};
//默认构造
Person::Person()
{cout << "默认构造函数的调用!" << endl;this->m_Age = 0;this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{cout << "有参构造函数的调用!" << endl;this->m_Age = age;this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p):m_Age(p.m_Age),m_Height(new int (*p.m_Height))
{cout << "拷贝构造函数的调用!" << endl;/*this->m_Age = p.m_Age;this->m_Height = new int(*p.m_Height);*/
}
//移动构造函数
Person::Person(Person&& p)
{cout << "移动构造函数的调用!" << endl;this->m_Age = p.m_Age;this->m_Height = p.m_Height;//delete p.m_Height;p.m_Height = nullptr;
}
//重载运算符=
//Person& Person::operator=(Person p)
//{
//	this->m_Age = p.m_Age;
//	this->m_Height = new int(*p.m_Height);
//	return *this;
//}
//展示信息
void Person::ShowPerson()
{cout << "年龄是: " << this->m_Age << endl;cout << "身高是: " << *(this->m_Height) << endl;
}
Person::~Person()
{if (this->m_Height != NULL){cout << "析构函数的调用!" << endl;delete this->m_Height;}	
}
int main()
{Person p(18, 180);Person p2(move(p));return 0;
}

是不是感觉觉得有问题,首先p有参构造,然后move把p变成右值,后调用移动构造函数,在移动构造函数中把p的*m_Height指向空,所以就看不到析构这个的提示了,但是它确实发生了析构,他会有生命周期的结束,就比如把移动构造的NULL用new出一个,它还是会发生析构。这个就不展开了。


 这里查阅了一些资料,主体是学习B站上黑马程序员C++的讲解例子,进行自己总结,并且借鉴了一些csdn大佬上面的一些内容,继续学习,Keep energetic!加油 


http://chatgpt.dhexx.cn/article/0ivITNlT.shtml

相关文章

C++类构造函数和析构函数

11.3 类构造函数和析构函数 构造函数&#xff1a;是为了在定义对象时自动初始化其成员变量的值。 构造函数没有返回值&#xff0c;也没有被声明为void类型&#xff1b;因此&#xff0c;构造函数没有声明类型。 11.3.1 声明和定义一个构造函数 构造函数原型&#xff1a;在这…

C++ 构造函数和析构函数可以是虚函数嘛?

简单总结就是&#xff1a;构造函数不可以是虚函数&#xff0c;而析构函数可以且常常是虚函数。 构造函数不能是虚函数 1. 从vptr角度解释 虚函数的调用是通过虚函数表来查找的&#xff0c;而虚函数表由类的实例化对象的vptr指针(vptr可以参考C的虚函数表指针vptr)指向&#x…

构造函数和析构函数顺序

父子类 1、构造顺序&#xff1a; 创建一个子类对象&#xff0c;则父类、子类的构造方法都执行&#xff0c;且是 先父类 构造方法&#xff0c;再子类构造方法 派生类的构造顺序&#xff1a;先父类&#xff0c;后子类&#xff0c;因为子类很有可能会用到从父类继承来的成员 2、析…

【C++】构造函数与析构函数

1. 概述 构造函数&#xff1a;用于初始化对象&#xff0c;没有返回值&#xff0c;函数名和类名相同&#xff0c;只有在对象初始化的时候才会被调用。构造函数的分类&#xff1a; 默认构造函数&#xff1a;是编译器自动生成&#xff0c;没有任何参数的构造函数。 有参构造函数&…

何时调用构造函数和析构函数

何时调用构造函数和析构函数 构造函数的作用是保证每个对象的数据成员都有何时的初始值。 析构函数的作用是回收内存和资源&#xff0c;通常用于释放在构造函数或对象生命期内获取的资源。 一般我们都知道构造和析构的次序&#xff1a; 构造从类层次的最根处开始&#xff0c…

C++篇----构造函数和析构函数

在很多时候&#xff0c;当写了初始化&#xff0c;动态开辟的&#xff0c;需要写销毁函数&#xff0c;写了销毁函数之后&#xff0c;但是却忘记了调用这些函数&#xff0c;忘记调用初始化函数还好&#xff0c;编译器会报错&#xff0c;但是如果是忘记调用销毁函数&#xff0c;那…

c++构造函数和析构函数

一、构造函数和析构函数的特点 构造函数和析构函数是一种特殊的公有成员函数&#xff0c;每一个类都有一个默认的构造函数和析构函数&#xff1b;构造函数在类定义时由系统自动调用&#xff0c;析构函数在类被销毁时由系统自动调用&#xff1b;构造函数的名称和类名相同&#…

构造函数与析构函数

一&#xff0c;引言 由于c语言常常会忘记初始化与销毁&#xff0c;造成许多麻烦。所以c就引入了构造函数与析构函数&#xff0c;分别用来完成初始化与清理工作&#xff0c;且由编译器自动调用&#xff0c;这就避免了许多麻烦。 二&#xff0c;构造函数 构造函数是一个特殊的成…

构造函数和析构函数

文章目录 前言 1.构造函数<1>概念<2>特性 2.初始化列表<1>概念<2>特征 3.析构函数<1>概念<2.>特征 前言 如不清楚类的定义可以点击此篇文章&#xff1a;类的定义与引入 C为很么要引入构造函数和析构函数呢&#xff0c;前文讲到大佬引入了…

C++ 构造函数和析构函数 详解

目录 概述构造函数的分类1. 无参(默认)构造函数2. 有参构造函数3. 委托构造函数4. 复制(拷贝)构造函数5. 移动构造函数 构造函数调用规则析构函数 概述 C中用构造函数和析构函数来初始化和清理对象&#xff0c;这两个函数将会被编译器自动调用。对象的初始化和清理是非常重要的…

java异常处理及自定义异常

异常处理的实际上就是&#xff1a; 有风险的行为&#xff08;方法&#xff09;可能会将异常抛出&#xff08;throws&#xff09;。调用该方法的程序会尝试&#xff08;try&#xff09;去运行,运行的同时捕捉&#xff08;catch&#xff09;异常。 简而言之&#xff0c;就是对有…

java异常 — — 自定义异常

三、自定义异常 3.1、概述 为什么需要自定义异常类: Java中不同的异常类分别表示看某一种具体的异常情况&#xff0c;那么在开发中总是有些异常情况是SUN没有定义好的此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题&#xff0c;考试成绩负数问题等等。 在上…

JAVA自定义异常处理

自定义异常处理可以分为两种&#xff0c;一种是自定义编译处理&#xff0c;另一种是自定义运行处理 1.自定义编译处理需要创建一个异常类用于继承Exception类 重写构造器 在出现异常的地方用throw new 自定义对象抛出 作用&#xff1a;编译时异常时编译阶段就报错&#xff…

Java的自定义异常类

Java的异常处理机制可以让程序具有极好的容错性&#xff0c;让程序更加健壮。当程序运行出 现意外情形时&#xff0c;系统会自动生成一个 Exception对象来通知程序&#xff0c;从而实现将“业务功 能实现代码”和“错误处理代码”分离&#xff0c;提供更好的可读性。 Java把所…

Java自定义异常及统一处理,信息返回

开始操作 创建enums&#xff0c;exception包&#xff1a; enums包下&#xff1a; 创建BaseCodeEnum接口 创建Response类&#xff1a;为统一信息返回类 创建ResponseCode枚举类&#xff1a;在这里定义我们需要的异常 exception包下&#xff1a; 创建HandlerException类&#…

Java自定义异常类统一处理异常

当程序发生异常时&#xff0c;会返回一大堆不友好的内容&#xff0c;非常不美观&#xff01; 我们在写代码的时候&#xff0c;对异常处理一般是try catch或者抛出异常throws Exception。 try catch大家都知道&#xff0c;代码中大量的try catch会占用内存影响性能&#xff0c…

Java中的自定义异常

代码实现 自定义异常类型主要实现代码 public class Exception_demo extends Exception{//自定义异常&#xff0c;需要把自定义异常类继承于Exception异常类&#xff0c;自定义异常类属于异常类的子类public Exception_demo(){//构造方法也叫做构造器&#xff0c;构造方法的名…

【Java异常】自定义异常

Java中定义了大量的异常类&#xff0c;虽然这些异常类可以描述编程时出现的大部分异常情况&#xff0c;但是在程序开发中有时可能需要描述程序中特有的异常情况,例如在设计divide()方法时不允许被除数为负数。为了解决这样的问题,Java允许用户自定义异常&#xff0c;但自定义的…

JAVA项目中自定义异常

JAVA项目中自定义异常 1.数据返回处理类 Data public class R<T> implements Serializable {private static final long serialVersionUID -8497670085742879369L;ApiModelProperty(value "返回码", example "200")private Integer code200;Api…

Java自定义异常理解

前言&#xff1a;看了许多博客和书&#xff0c;都对自定异常一笔带过&#xff0c;总让人感觉莫名奇妙&#xff0c;一直在问自己一个问题&#xff0c;我们能很好的解决异常就很不错了&#xff0c;为什么还要自己自定义异常&#xff0c;让自己去自找麻烦呢&#xff1f;后来我才理…