正确理解C++的构造函数和析构函数

目录
  • 一、构造函数
  • 二、C++类的内存模型
    • 2.1、只定义成员函数
    • 2.2、往空类中添加静态成员变量
    • 2.3、再加入非静态成员变量
  • 三、this指针
  • 四、析构函数

一、构造函数

首先,由于类只是一个模板,因此我们在定义类时无法对成员变量初始化,比如下面代码就是错误的:

class circle{
public:
    int m_L = 20;	// Error:不允许使用数据成员初始值设定项
};

因此,初始化只能发生在类创建对象的过程中,但是由于访问权限的原因,无法在类外访问某些成员变量,因此下面这种做法有时候是无效的:

circle C1;	// 实例化一个对象 C1
C1.m_L = 20;	// 通过创建的对象,来给对应变量初始化,但是如果m_L是private访问权限,则失效

为了解决这个问题,让程序员能像使用标准数据类型一样适用对象,在类内提供了一个特殊的成员函数——“构造函数”,专门用于在创建对象时初始化类对象。之所以说它特殊,是因为C++已经自动为构造函数提供了名称和使用语法,程序员只需要提供方法的定义即可,即:类名(形参列表)。具体来说,构造函数的定义如下:

class circle{
public:
    int m_L;
public: //成员函数(方法)
    circle(const int a)	//通过构造函数对成员变量进行赋值
    {
        m_L = a;
    }
};

circle C1(20);	//调用格式
cout << "C1.m_L:" << C1.m_L <<endl;

看上去似乎很简单,但是由于构造函数也是函数,因此所有C++中的形参传递方式,函数特性以及函数调用方法都能用于构造函数。 如前文所讲,C++会自动给类添加一个空的构造函数,但是如果自己在类中实现了有参构造函数,编译器便不再提供无参构造函数。举例如下:

class circle{
public:
    int m_L;
public: //成员函数(方法)
    circle(const int a)	//通过构造函数对成员变量进行赋值
    {
        m_L = a;
    }
};

circle C1(20);	//调用格式正确,能够通过构造函数赋值
circle C2; 		//错误,自己定义了有参构造函数,不再提供无参构造函数

构造函数可以重载,接着上面的例子,如果重载一个空的构造函数,那么两个调用格式都正确:

class circle{
public:
    int m_L;
public: //成员函数(方法)
    circle(){}	// 空构造函数
    circle(const int a)	//通过构造函数对成员变量进行赋值
    {
        m_L = a;
    }
};

circle C1(20);	//调用格式正确,能够通过构造函数赋值
circle C2; 		//正确,可以通过重载的空构造函数实现初始化

构造函数的参数不仅可以是标准数据类型,也可以是类。众所周知,在数值作为函数参数进行传递的时候,会重新拷贝出来一份数据作为参数传递用完即销毁,这种方式不仅浪费了内存空间,而且无法修改原始数据。为了结合这两者之间的优点,于是经常采取引用作为函数的参数。虽然引用是指针的一种特殊情况,但是指针太过于灵活,并且引用在形式上引用与普通的变量地用法并没有什么区别,因此使用起来更加方便。

class circle{
public:
    int m_L;
public: //成员函数(方法)
    circle(){}	// 空构造函数
    circle(const int a)	//通过构造函数对成员变量进行赋值
    {
        m_L = a;
    }
    //引用作为函数参数传递,并用const修饰,节省空间的同时避免修改原数据
    circle(const circle& sub_circle)
    {
        m_L = sub_circle.m_L;
    }
};

circle C1(20);	//调用格式正确,能够通过构造函数赋值
circle C2(C1); 	//正确,可以通过拷贝构造函数进行初始化

二、C++类的内存模型

C++中,一个类包括:

  • 成员变量:静态成员变量和普通成员变量
  • 成员函数:静态成员函数和普通成员函数

虽然为了集成,我们将其写到一个类里面,但是只有普通成员变量真正属于类的对象,类的所有对象共享一份静态成员函数,静态成员变量和普通成员函数。画出了内存模型,如下图所示:

为了进一步理解,我们举例如下:

2.1、只定义成员函数

class person{
public:
    // 定义一个空的构造函数
	person(int m_age, int m_ID){
	}
};
person p1(10, 20);
cout << "p1 所占的空间为:" << sizeof(p1) << endl;

输出结果为:

p1 所占的空间为:1

这个题目在《剑指offer》一书中也提到过,由空类实例化出来的对象所占的内存空间是1个而不是0个字节,因为编译其给对象 p1 分配了一个地址,来表示不同的对象存储在不同的地址空间,因此占用1个字节。

2.2、往空类中添加静态成员变量

class person{
    static int age; //静态成员变量,存在全局区,不属于类对象的一部分
    static int ID;  //静态成员变量,存在全局区,不属于类对象的一部分
public:
    // 定义一个空的构造函数
	person(int m_age, int m_ID){
	}
};
person p1(10, 20);
cout << "p1 所占的空间为:" << sizeof(p1) << endl;

输出结果为:

p1 所占的空间为:1

当向类中加入了成员函数与静态成员变量时,类的实例化对象仍然只占用1个字节的空间,足以证明这些函数和变量并不是类对象的一部分。

2.3、再加入非静态成员变量

class person
{
    static int age; //静态成员变量,存在全局区,不属于类对象的一部分
    static int ID;  //静态成员变量,存在全局区,不属于类对象的一部分
    int a;	//非静态成员变量,存在栈区,属于类对象的一部分
public:
	person(int m_age, int m_ID){
	}
};

输出结果为:

p 所占的空间为:4

因此当向类中加入了非静态成员变量时,类的实例化对象占用4个字节的空间,可以说明,非静态变量属于类对象的一部分。综上:同一个类所有实例化出来的对象共享同一份静态成员变量,所以一改全改。既然同一个类的不同对象共享同一份成员函数,那么成员函数怎么区分该访问哪个对象的普通成员变量呢?

三、this指针

接着上一小节的问题,this指针为上述问题提供了一个完美的解决方案,它指向用来调用成员函数的对象(被当作参数隐式地传递给成员函数),我们通过一张图来理解它:

此外,this指针的另一个用途是当成员函数需要返回对象时,用 return *this; 或者 return this,这种做法能够实现链式编程。比如:

p2.addPerson(p1).addPerson(p1);

首先,对象 p2 调用成员函数 addPerson(p1) ,其返回值继续调用 addPerson(p1),此时返回值就必须也是 person 类型才可以,因此使用 this 指针可以完成需求。先来看第一个例子:

class person{
public:
	int age;
	person(int age)
	{
		this->age = age; // this指针区分调用者
	}
	// 返回值为person类型,且参数加上了const限制,防止修改原数据
	person addPerson(const person& p)
	{
		this->age += p.age;	// 主要实现两个类对象年龄的相加
		return *this;	// 由于返回值是person,因此返回 *this
	}
};

person p1(20);
person p2(10);
person p3 = p2.addPerson(p1).addPerson(p1);
cout << "p1 age:" << p1.age << endl;
cout << "p2 age:" << p2.age << endl;
cout << "p3 age:" << p3.age << endl;

首先,通过构造函数分别对 p1,p2 赋了初值,然后 p2 调用函数 addPerson(p1) 修改自身的变量 age 。**由于函数通过值传递的方式返回 person 类型,所以将整个 person 类型复制了一份返回,返回值继续调用 addPerson(p1), **最后的结果赋值给了新的对象 p3。所以输出结果为:

p1 age:20

p2 age:30

p3 age:50

但是如果函数 addPerson() 修改为:

person& addPerson(const person& p)
{
    this->age += p.age;	// 主要实现两个类对象年龄的相加
    return *this;	// 虽然返回值是person&,返回值的类型也是 *this
}

person p1(20);
person p2(10);
person p3 = p2.addPerson(p1).addPerson(p1);
cout << "p1 age:" << p1.age << endl;
cout << "p2 age:" << p2.age << endl;
cout << "p3 age:" << p3.age << endl;

与上例唯一的区别就在于返回值的类型变成了引用,那么每次返回的就变成了该对象本身,而非在值传递中拷贝出来的那一份数据。那么输出就变成了:

p1 age:20

p2 age:50

p3 age:50

四、析构函数

用构造函数创建对象后,程序负责跟踪该对象,知道其过期为止。当对象过期时,程序自动调用析构函数完成清理工作。与构造函数一样,C++默认提供了一个空的析构函数,定义为:~类名( )。由于开辟在栈区的变量程序会自动释放,因此不需要析构函数执行清理工作,但是当程序员在堆区开辟空间时,需要手动执行清理工作,这时候需要析构函数来释放堆区内存。比如:

~person()
{
	// 在析构函数内写入需要执行的代码
	cout << "调用析构函数" << endl;
}
person p1(20);
person p2(10);	// 在生命周期结束后自动调用析构函数执行清理工作

输出为:

调用析构函数

调用析构函数

以上就是正确理解C++的构造函数和析构函数的详细内容,更多关于C++ 构造函数 析构函数的资料请关注我们其它相关文章!

(0)

相关推荐

  • 深入解析C++中的构造函数和析构函数

    构造函数:在类实例化对象时自动执行,对类中的数据进行初始化.构造函数可以从载,可以有多个,但是只能有一个缺省构造函数. 析构函数:在撤销对象占用的内存之前,进行一些操作的函数.析构函数不能被重载,只能有一个. 调用构造函数和析构函数的顺序:先构造的后析构,后构造的先折构.它相当于一个栈,先进后出. 复制代码 代码如下: #include<iostream>#include<string>using namespace std;class Student{ public:  Stud

  • 深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结

    1 . 用同一个类的源对象构造一个目标对象时,会调用拷贝构造函数来构造目标对象,如果没有定义拷贝构造函数,将调用类的默认拷贝函数来构造目标对象.2 . 当一个函数的返回值为一个类的对象时,如果在调用函数中,没有定义一个对象来接收这个返回对象值,会用返回一个临时对象保存返回对象的值.在被调用函数结束时,这个临时对象被销毁.而当调用函数中有一个接受对象时,就将返回对象赋值给接收对象,这个返回对象在调用函数结束时调用析构函数.3. 当类有一个带有一个参数的构造函数时,可以用这个参数同类型的数据初始化这

  • c++ 入门——浅析构造函数和析构函数

    前文回顾 本文档环境基于Vscode + GCC + CodeRunner 关于C++的环境搭建请参考下面链接: https://www.jb51.net/article/186542.htm 由于本人具有C#开发经验,部分相同的知识就不再赘述了.只列一下需要记录的知识. HelloWorld cout 代表输出<< cin 代表输入 >> endl;代表换行,清空缓冲区. #include <iostream> int main() { std::cout <&

  • C++构造函数和析构函数的使用与讲解

    构造函数(constructor) 1.构造函数是种特殊的类成员函数,遵循如下规则: a.函数名与类名必须相同. b.没有返回值 例如: class Obj { ... public: Obj() { ... } }; 2.构造函数可以带参数,也可以重载 class Obj { ... public: Obj() { ... } Obj(int x, int y) { ... } }; 3.构造函数和普通成员函数不一样,一般不显示调用.在创建一个对象时,构造函数自动调用(编译器来完成). 析构函

  • C++中构造函数与析构函数的调用顺序详解

    前言 在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序.在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用. 简单来说,其构造函数的顺序就一句话: 基类构造函数 -> 成员的构造函数 -> 构造函数体内语句 看下面一个代码示例: #include <iostream> using namespace std; class A { publ

  • C++语法详解之封装、构造函数、析构函数

    大家先了解下什么是构造函数,什么是析构函数,作用是什么? 构造函数(方法)是对象创建完成后第一个被对象自动调用的方法.它存在于每个声明的类中,是一个特殊的成员方法.作用是执行一些初始化的任务.Php中使用__construct()声明构造方法,并且只能声明一个. 析构函数(方法)作用和构造方法正好相反,是对象被销毁之前最后一个被对象自动调用的方法.是PHP5中新添加的内容作用是用于实现在销毁一个对象之前执行一些特定的操作,诸如关闭文件和释放内存等. 下面在通过具体例子看下C++语法详解之封装.构

  • 详解C++中如何将构造函数或析构函数的访问权限定为private

    今天面试被问到了这个单例模式常用到的技术手段,下面进行分析:         很多情况下要求当前的程序中只有一个object.例如一个程序只有一个和数据库的连接,只有一个鼠标的object.通常我们都将构造函数的声明置于public区段,假如我们将其放入private区段中会发生什么样的后果?这意味着什么?         当我们在程序中声明一个对象时,编译器为调用构造函数(如果有的话),而这个调用将通常是外部的,也就是说它不属于class对象本身的调用,假如构造函数是私有的,由于在class外

  • 详解C++ 编写String 的构造函数、拷贝构造函数、析构函数和赋值函数

    详解C++ 编写String 的构造函数.拷贝构造函数.析构函数和赋值函数 编写类String 的构造函数.析构函数和赋值函数,已知类String 的原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String & operate =(const String &ot

  • C++类成员构造函数和析构函数顺序示例详细讲解

    对象并不是突然建立起来的,创建对象必须时必须同时创建父类以及包含于其中的对象.C++遵循如下的创建顺序: (1)如果某个类具体基类,执行基类的默认构造函数. (2)类的非静态数据成员,按照声明的顺序创建. (3)执行该类的构造函数. 即构造类时,会先构造其父类,然后创建类成员,最后调用本身的构造函数. 下面看一个例子吧 复制代码 代码如下: class c{public:    c(){ printf("c\n"); }protected:private:}; class b {pub

  • c++基础语法:构造函数与析构函数

    说实话c++还是以前在学校的时候用过的,从毕业到现在一直用c嵌入式编程,现在重新搬出C++语法 ,如果理解上有错误的地方,还请路过的朋友多指正--- 构造函数用来构造一个对象,主要完成一些初始化工作,如果类中不提供构造函数,编译器会默认的提供一个默认构造函数(参数为空的构造函数就是默认构造函数) :析构函数是隐式调用的,delete对象时候会自动调用完成对象的清理工作. 现在主要看看继承中的构造函数和析构函数的调用: 复制代码 代码如下: class  A {} :class  B : publ

随机推荐