封装(3.构造和析构函数)

引言

构造函数和析构函数是在对象创建和销毁时被自动调用的成员函数,构造函数一般用来初始化成员,或者申请动态内存等资源,析构函数用来释放对象申请占用的资源。

构造函数

定义完基本类型的变量,比如int x;,要注意给它赋初值。这是因为尚未初始化的变量里面的值是乱七八糟的值,在C/C++中读取未初始化的变量的值可能会导致程序崩溃。

对于类类型的对象来说,也是一样的,Human p;定义好对象p以后,p里面的成员p.age等也是乱七八糟的值(p.namestring类型的对象,会由string类型的默认构造函数初始化为空字符串)。因此要调用p.init("张三", true, 20, "123456");这样的成员函数来给各成员赋值。

#include <iostream>
#include <string>
using namespace std;
class Human{
    public:
        void init(string n, bool is_m, int a, string i)
        {    
            name = n;
            is_male = is_m;
            age = a;
            id = i;
        }
        ...
    private:
        string name;
        bool is_male;        
        int age;
        string id;

};
int main()
{
    Human p;
    p.init("张三", true, 20, "123456");
    p.introduce();
}

如果有相应的构造函数,那直接创建对象就好了,并提供相应的值即可。

#include <iostream>
#include <string>
using namespace std;
class Human{
    public:
        // 带参数的构造函数
        Human(string n, bool is_m, int a, string i)
        {
            name = n;
            is_male = is_m;
            age = a;
            id = i;
        }        
    private:
        string name;
        bool is_male;        
        int age;
        string id;

};
int main()
{
    Human p("张三", true, 20, "123456"); // 自动调用带参数的构造函数
    p.introduce();
}

构造函数是与类同名,没有返回类型,也不用声明void,构造函数必须这样写。

如果只存在这样一个构造函数,那我们也只能这样创建人类对象。即必须提供姓名等属性值。

析构函数

构造函数是在创建对象时自动调用的成员函数,析构函数与构造函数调用时机相反,是在对象被销毁时自动调用的成员函数。(函数内的局部变量和参数变量在函数退出时被销毁,或者动态创建的对象被使用delete被消耗)

如果对象占用的某些资源(比如动态分配了内存),那么需要释放这些资源。往往就在析构函数中释放它们。

假设我们要自己定义类似于标准库中的string类型。那下面我们就尝试写出部分代码。代码仅是起说明构造和析构函数的用法的作用,不具有实用性。

String类要存放字符串,如果要支持变长字符串,字符数组的内存需要动态申请,动态申请的内存用完需要释放,我们可以把delete释放内存的代码放到String的析构函数中。 每个String对象在被销毁时都会调用析构函数,因此每个String对象动态申请的内存一定会被释放。

class String
{
    public:
        String()  // 默认构造函数,空字符串
        {
            len = 0;
            p = new char;
            *p = '\0';
        }
        String(char *str)  // 接收一个字符串参数的构造函数
        {
            len = strlen(str);
            p = new char[len + 1];
            strcpy(p, str);
        }
        ...
        ~String()  // 析构函数
        {
            cout << "String destruction called" << endl;
            if (p != NULL)
            {
                delete []p;
                p = NULL;
            }
        }
    private:
        char *p;
        int len;
};

int main()
{

    String s1;
    String s2("hello, world");

    return 0;
}

语法

参考程序员实验室-C++基础教程-4.1 类(Classes)

构造函数其它知识点

默认构造函数

创建对象时肯定会调用“某一款”构造函数。不带参数的构造函数也叫默认构造函数或者缺省构造函数。如果类里面没有定义任何构造函数,编译器会帮助生成一个默认构造函数(里面什么也没做),一旦程序员定义了任何构造函数,编译器就不会帮助生成此默认构造函数。

  • 比起调用其它构造函数,调用默认构造函数的语法比较特殊,但我们也经常调用默认构造函数。

    class Foo
    {
    public:
        Foo(int x_)  // 带一个参数的构造函数
        {
            x = x_;
        }
        Foo()  // 默认构造函数
        {
            x = 0;
        }
        int x;    
    };
    
    int main()
    {
        Foo f1(10); // 调用带一个带参数的构造函数
        Foo f2; // 调用默认构造函数,不能写成 Foo f2();
    }
  • 如果类里面没有定义任何构造函数,编译器会帮助生成一个默认构造函数(里面什么也没做),一旦程序员定义了任何构造函数,编译器就不会帮助生成此默认构造函数。

    class FooWithNoExplicitConstructor
    {
    public:  // 没有任何构造函数,编译器会自动生成一个“什么都不做”的默认构造函数 
        int x;    
    };
    
    class Foo
    {
    public:
        Foo(int x_)  // 带一个参数的构造函数,此时就没有自动生成的默认构造函数了
        {
            x = x_;
        }
        int x;    
    };
    
    int main()
    {
        FooWithNoExplicitConstructor f1; // 调用默认构造函数(但是里面什么也没做,f1.a没有初始化)
        Foo f2(10); // 调用带一个参数的构造函数
        Foo f3; // 编译错误,此时没有默认构造函数
    }

构造函数的成员初始化列表

之前我们都是在构造函数内部给成员赋值的

class Foo
{
    public:
        Foo(int x_, int y_)  // 带一个参数的构造函数
        {
            x = x_;
            y = y_;
            cout << "x:" << x << ",y:" << y << endl;
        }
       int x;
       int y;    
};

int main()
{
   Foo f1(10, 20); // 调用带一个带参数的构造函数
   return 0;
}

实际上,创建对象f1时,可以看成两步:

  1. 创建对象的各成员(f1.x和f1.y)。
  2. 调用Foo类的构造函数{}内的代码。

因此,在Foo类的构造函数{}内的代码执行之前,其成员x和y就已经被创建出来。这意味着在构造函数{}内部给成员赋值是普通的赋值语句,而不是成员的初始化。就像

int x; // 先定义变量
x = 10;  // 后给变量赋值

初始化是指在创建变量的时候就给变量赋值,就像

int x = 10; // 定义变量并初始化它

怎样在创建类的成员变量的时候给它初值呢?那要使用构造函数的成员初始化列表。

class Foo
{
    public:
        Foo(int x_, int y_) : x(x_), y(y_) // 构造函数的成员初始化列表
        {
            cout << "x:" << x << ",y:" << y << endl;
        }
       int x;
       int y;    
};

int main()
{
   Foo f1(10, 20);
   return 0;
}

这里写图片描述

可见,使用成员初始化列表可以给类成员赋初值。对于基本类型的成员变量,是否使用成员初始化列表初始化关系不大,而对于类类型的成员变量,有时必须使用成员初始化列表来初始化它们。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值