C++特殊成员函数介绍

深入理解C++特殊成员函数

在c++中 特殊成员函数有下面六个:构造函数 析构函数 拷贝构造函数 拷贝赋值运算符

以Weight类为实例 其特殊成员函数的签名如下

class Widget
{
    Widget();                             // 构造函数
    ~Widget();                            // 析构函数
    Widget(const Widget &rhs);            // 复制构造函数(拷贝构造函数)
    Widget &operator=(const Widget &rhs); // 赋值运算符(拷贝运算符)
    //Widget(Widget &&rhs);                 // 移动构造函数
    //Widget &operator=(Widget &&rhs);      // 移动运算符
};

构造函数

构造函数的作用是 帮助构建对象的实例 并对实例进行初始化操作

在c++中 以下两种形式的语句会调用类的构造函数

Widget Widget ();
Widget *w = new Widget ();

调用构造函数时 会创建一个类的实例 如果类内有成员变量 就需要为类编写构造函数 在构造函数中对类内的成员变量进行初始化

实例代码

class Widget
{
public:
    Widget(int width, int hight) : width_(width), hight_(hight) {}

private:
    int width_;
    int hight_;
};

int main()
{
    Widget w(1, 2);

    return 0;
}

如果不为其创建含有参数的构造函数 那么此时创建的对象中的成员的值时随机的 很显然这是不好的

class Widget
{
public:
    // Widget(int width, int hight) : width_(width), hight_(hight) {}
    int getW()
    {
        return width_;
    }
    int getH()
    {
        return hight_;
    }
private:
    int width_;
    int hight_;
};

int main()
{
    Widget w;
    std::cout << w.getW() << std::endl;
    std::cout << w.getH() << std::endl;

    return 0;
}

但在c++11以后 成员变量的初始值可以在类内定义

在这种情况下 所有该类创造出来的对象的成员变量都拥有相同的值 如果你希望创建出来的对象的成员变量的值不相同 那么还是需要添加含参数的构造函数

class Widget
{
public:
    // Widget(int width, int hight) : width_(width), hight_(hight) {}
    int getW()
    {
        return width_;
    }
    int getH()
    {
        return hight_;
    }
private:
    int width_{1};
    int hight_{2};
};

int main()
{
    Widget w;
    std::cout << w.getW() << std::endl;
    std::cout << w.getH() << std::endl;

    return 0;
}

析构函数

析构函数的作用是帮助销毁一个实例

但是到底什么时候需要自定义析构函数呢?

看下面的代码 思考是否需要自定义析构函数

class Person
{
public:
    Person(std::string name, int age, int id) : sname(name), sage(age), sid(id) {}

public:
    std::string getName()
    {
        return sname;
    }

    int getAge()
    {
        return sage;
    }

    int getId()
    {
        return sid;
    }

private:
    std::string sname;
    int sage;
    int sid;
};

答案是不需要 默认的析构函数就会清理类内的这些数据

在思考下面这段代码 是否需要自定义析构函数

class Person
{
public:
    Person(const char *s, std::size_t n) : sname(new char[n])
    {
        memcpy(sname, s, n);
    }

public:
    char *getName()
    {
        return sname;
    }

private:
    char *sname{nullptr};
};

这里必须自定义析构函数 因为默认析构函数只会把sname置为nullptr 而不会释放new所创建的空间

因此要将上面的代码修改为

class Person
{
public:
    Person(const char *s, std::size_t n) : sname(new char[n])
    {
        memcpy(sname, s, n);
    }
    // 析构函数
    ~Person()
    {
        if (sname)
        {
            delete[] sname;
        }
    }

public:
    char *getName()
    {
        return sname;
    }

private:
    char *sname{nullptr};
};

拷贝构造函数 & 拷贝赋值操作符

复制构造函数的作用是使用一个已经存在的对象去创建一个新的对象

赋值运算符的作用是将一个对象的所有成员变量赋值给另一个已经创建的对象

二者的区别在于一个是创建一个新对象 一个是赋值给一个已经存在的对象

Rule Of Three定理

当一个类需要用户自定义的构造函数 或 自定义的析构函数 或 自定义的拷贝复制运算符 三者中的一个时 那么大概率这三个函数都要出现

首先介绍一个c++11的关键字 explicit

#include <string>
#include <iostream>

class Student
{
private:
    int age;
    std::string name;

public:
    explicit Student(int age_) : age(age_), name("suwq") {}
    Student(const std::string name_) : age(24), name(name_) {}

    void Display()
    {
        std::cout << "student's name is : " << name << std::endl
                  << "student's age is: " << age << std::endl;
    }
};

int main()
{
    Student A(10);
    A.Display();

    Student B("adb");
    B.Display();

    Student C = 40;
    C.Display();

    return 0;
}

它的作用就是防止了C这种情况 因为C是Student这个类创建的对象 但如果不用explicit 却可以被赋值为int类型的值 因为发上了隐式转换 将40 转换为Student类型 这是反直觉的 所以需要explicit来修饰构造函数

继续讲解Rule Of Three定理

class Person
{
private:
    char *name;
public:
    Person(const char *name_, std::size_t n) : name(new char[n])
    {
        memcpy(name, name_, n);
    }
    // 这里const char *s = "" 是为了传入空值时可以使用默认构造函数
    explicit Person(const char *s = "") : Person(s, std::strlen(s) + 1) {}
    
    ~Person()
    {
        if (name)
        {
            delete[] name;
        }
    }
    
    char *getName()
    {
        return name;
    }
};

int main()
{
    Person p("hello");
    Person p2("tom");

    Person p3(p);
    p2 = p;

    return 0;
}

第一个问题: 在执行Person p3§; 这一条语句时 编译器会识别出来这是一个**拷贝构造 **

它的作用是: 1. 创建一个新的Person对象p3 2. 使用另一个已经存在的对象Person p作为模板

然后编译器会查找Person类的拷贝构造函数 但是并未找到 所以会调用默认拷贝构造函数 ** 但默认拷贝构造函数使用的是浅拷贝** 会导致p3.name 和 p. name指向同一块内存空间(0x1111) 看起来再创建时并没有什么问题 但是再被销毁时 p3和p调用析构函数时 会导致0x1111这块空间被重复释放 导致程序崩溃

第二个问题: 在执行p2 = p;这条语句时 p2这个指针会指向p所在的内存 导致原本p2的内存空间会没有变得没有任何指针指向 无法释放 导致内存泄漏问题 然后再赋值时 会存在问题一相同的问题 浅拷贝 导致公用一块内存空间 从而导致析构时重复释放内存空间

解决方案: 自定义拷贝构造函数 和 拷贝复制运算符

class Person
{
private:
    char *name;

public:
    Person(const char *name_, std::size_t n) : name(new char[n])
    {
        memcpy(name, name_, n);
    }
    // 这里const char *s = "" 是为了传入空值时可以使用默认构造函数
    explicit Person(const char *s = "") : Person(s, std::strlen(s) + 1) {}

    // 拷贝构造函数 用other为模板构造新对象
    Person(const Person &other) : Person(other.name) {}

    // 拷贝赋值操作符
    Person &operator=(const Person &other)
    {
        // this是指向p2的指针 比较 只能把other也取地址
        if (this == &other)
        {
            return *this;
        }

        // 先开辟空间 +1为\0预留空间
        std::size_t n{std::strlen(other.name) + 1};
        char *nString = new char[n];

        // 拷贝内容
        memcpy(nString, other.name, n);

        // 删除旧的空间
        delete[] name;
        name = nString;

        return *this;
    }

    ~Person()
    {
        if (name)
        {
            delete[] name;
        }
    }

    char *getName()
    {
        return name;
    }
};

int main()
{
    Person p("hello");
    Person p2("tom");

    Person p3(p);
    p2 = p;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值