3.构造函数知识补充

3.必备知识点-构造函数知识补充


3.1 知识点

默认构造函数在对象初始化时的作用

  • 当一个自定义类 B 作为另一个类 A 的成员变量时,需要保证 B 存在默认构造函数,否则会报错
  • 对象在初始化时,会初始化成员变量并自动调用其默认无参构造函数,除非你在 A 的构造函数初始化列表中手动初始化 B
  • 注意:如果是自定义类的指针成员,不需要默认构造,因为指针只是地址变量,不会自动创建 B 类型对象,且默认值是未定义的垃圾地址(随机地址)
#pragma once
#include "B.h"
class A
{
public:
    B b;        // 直接让其调用无参构造初始化
    B* b2;      // 如果是指针,不存在这样的问题

    A() : b(100) // 通过初始化列表显式地调用有参构造
    {
    }
};
#pragma once
class B
{
public:
    B(int i)
    {
        
    }
};
  • 当 A 中存在自定义类 B 的成员变量且 B 没有无参构造时,会报错(指针变量除外)

  • 解决方式:

    • 保证自定义类有无参构造
    • 在 A 的构造函数初始化列表中调用 B 的有参构造
A a;  // 若 B 无默认构造,则此处报错或没有使用初始化列表

构造函数中初始化自定义类成员问题

  • 若一个类中有自定义类成员变量(非指针),应通过初始化列表对该成员进行初始化,而不要在构造函数体内初始化
  • 在构造函数体内重新赋值,会导致栈上对象与预期不一致

#pragma once
class B
{
public:
    int* i;
    B() 
    {
    }

    B(int i)
    {
        this->i = new int(i);
    }

    ~B()
    {
        delete i;
        i = nullptr;
    }
};
#pragma once
#include "B.h"
class A
{
public:
    B b;        // 直接让其调用无参构造初始化
    B* b2;      // 指针成员不受此影响

    A() : b(100)  // 应通过初始化列表显式初始化
    {
        // 不要在此处写:
        // b = B(100);
        // 因为栈上对象会在语句块结束后被销毁,与初始化列表的对象不同
        b2 = new B(200);  // 堆上分配的对象生命周期由你管理
    }
};
// 使用示例:
cout << *a.b.i << endl;    // 100
cout << *a.b2->i << endl;  // 200
//注意:
//如果是自定义类的指针成员,不需要在意这一点
//因为指针一般指向的会是堆上分配的内存,不会由于语句块结束而释放

默认构造函数在数组初始化时的作用

  • 在声明自定义类的数组对象时,默认会调用无参构造函数
  • 如果没有无参构造,会报错
  • 自定义类的指针数组不受此限制,指针只存地址,不会自动创建对象
#pragma once
class C
{
public:
    C()
    {
    }
    C(int i)
    {
    }
};
//在声明自定义类的数组对象时
//默认会调用自定义类的无参构造函数
//如果此时没有无参构造,会报错
C arr[5];  // 每个元素调用 C::C()
//注意:
//如果是自定义类的指针数组,不需要默认构造
//因为指针本身只是一个地址变量,不会自动创建 C 类型对象
//而且默认指针值会是一个指向未定义的垃圾值(随机地址)
// 指针数组示例,不会调用构造:
C* parr[5];

3.2 知识点代码

A.h

#pragma once
#include "B.h"
class A
{
public:
    B b;//直接让其调用无参构造初始化

    B* b2;//如果是指针 不存在这样的问题

    A() :b(100)//应该通过初始化列表显示的去初始化
    {
        //不要这样去进行初始化
        //因为栈上分配的对象 在语句块结束后会被释放
        //那么就会导致b和你想象的对象是不一样的!!!
        // 应该通过初始化列表显示的去初始化 或者让其调用无参构造初始化!
        //b = B(100);

        //因为在堆上分配的内存空间 是我们自己管理的 
        //不会因为语句块结束而释放
        b2 = new B(200);
    }
};

B.h

#pragma once
class B
{
public:
    int* i;
    B() 
    {
    }

    B(int i)
    {
        this->i = new int(i);
    }

    ~B()
    {
        delete i;
        i = nullptr;
    }
};

C.h

#pragma once
class C
{
public:
    C()
    {

    }
    C(int i)
    {

    }
};

必备知识点3_构造函数知识补充.cpp

#include <iostream>
using namespace std;
#include "A.h"
#include "C.h"
int main()
{
    #pragma region 知识点一 默认构造函数在对象初始化时的作用
    //当一个自定义类B作为另一个类A的成员变量时
    //需要保证B存在默认构造函数,否则会报错
    //因为对象在初始化时,会初始化成员变量,会自动调用其默认无参构造函数
    //除非你在A的构造函数初始化列表中手动初始化B

    //注意:
    //如果是自定义类的指针成员,不需要默认构造
    //因为指针本身只是一个地址变量,不会自动创建 B 类型对象
    //而且默认指针值会是一个指向未定义的垃圾值(随机地址)

    //一个类中存在一个自定义类的成员变量时,并且该自定义类没有无参构造
    //那么可能会报错(指针变量不会报错)
    //要解决这个报错,有两种方式:
    //1.保证自定义类有无参构造
    //2.在该类中通过构造函数的初始化列表去调用其的有参构造进行初始化
    A a;
    #pragma endregion

    #pragma region 知识点二 构造函数中初始化自定义类成员问题
    //如果一个类中有一个自定义类成员变量(非指针)
    //那么我们需要通过初始化列表的形式对该成员进行初始化
    //而不要在构造函数内部初始化

    //注意:
    //如果是自定义类的指针成员,不需要在意这一点
    //因为指针一般指向的会是堆上分配的内存,不会由于语句块结束而释放
    cout << *a.b.i << endl;//100
    cout << *a.b2->i << endl;//200
    #pragma endregion

    #pragma region 知识点三 默认构造函数在数组初始化时的作用
    //在声明自定义类的数组对象时
    //默认会调用自定义类的无参构造函数
    //如果此时没有无参构造,会报错
    C arr[5];

    //注意:
    //如果是自定义类的指针数组,不需要默认构造
    //因为指针本身只是一个地址变量,不会自动创建 C 类型对象
    //而且默认指针值会是一个指向未定义的垃圾值(随机地址)
    #pragma endregion

}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏