C/C++必须知道的小知识(16)

162 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

1.3.8 说说一个类,默认会生成哪些函数

参考答案

定义一个空类

class Empty
{
};

默认会生成以下几个函数

  1. 无参的构造函数

    在定义类的对象的时候,完成对象的初始化工作。

Empty()
{
}
  1. 拷贝构造函数

    拷贝构造函数用于复制本类的对象

Empty(const Empty& copy)
{
}
  1. 赋值运算符
Empty& operator = (const Empty& copy)
{
}
  1. 析构函数(非虚)
~Empty()
{
}

1.3.9 说说 C++ 类对象的初始化顺序,有多重继承情况下的顺序

参考答案

  1. 创建派生类的对象,基类的构造函数优先被调用(也优先于派生类里的成员类);

  2. 如果类里面有成员类,成员类的构造函数优先被调用;(也优先于该类本身的构造函数)

  3. 基类构造函数如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;

  4. 成员类对象构造函数如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;

  5. 派生类构造函数,作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)

  6. 综上可以得出,初始化顺序:

    父类构造函数–>成员类对象构造函数–>自身构造函数

    其中成员变量的初始化与声明顺序有关,构造函数的调用顺序是类派生列表中的顺序。

    析构顺序和构造顺序相反。

1.3.10 简述下向上转型和向下转型

  1. 子类转换为父类:向上转型,使用dynamic_cast(expression),这种转换相对来说比较安全不会有数据的丢失;
  2. 父类转换为子类:向下转型,可以使用强制转换,这种转换时不安全的,会导致数据的丢失,原因是父类的指针或者引用的内存中可能不包含子类的成员的内存。

1.3.11 简述下深拷贝和浅拷贝,如何实现深拷贝

  1. 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。举个简单的例子,你的小名叫西西,大名叫冬冬,当别人叫你西西或者冬冬的时候你都会答应,这两个名字虽然不相同,但是都指的是你。

  2. 深拷贝,拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了我们想要的目的,还不会出现问题,两个指针先后去调用析构函数,分别释放自己所指向的位置。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。

  3. 深拷贝的实现:深拷贝的拷贝构造函数和赋值运算符的重载传统实现:

    STRING( const STRING& s )
    {
        //_str = s._str;
        _str = new char[strlen(s._str) + 1];
        strcpy_s( _str, strlen(s._str) + 1, s._str );
    }
    STRING& operator=(const STRING& s)
    {
        if (this != &s)
        {
            //this->_str = s._str;
            delete[] _str;
            this->_str = new char[strlen(s._str) + 1];
            strcpy_s(this->_str, strlen(s._str) + 1, s._str);
        }
        return *this;
    }
    

    这里的拷贝构造函数我们很容易理解,先开辟出和源对象一样大的内存区域,然后将需要拷贝的数据复制到目标拷贝对象 , 那么这里的赋值运算符的重载是怎么样做的呢?

    这种方法解决了我们的指针悬挂问题,通过不断的开空间让不同的指针指向不同的内存,以防止同一块内存被释放两次的问题。