C++11 类的新功能(上)

简介: C++11 类的新功能

类的新功能

默认成员函数

在C++98中 类的默认成员函数一般可以认为有六个


这六个默认成员函数分别是

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值函数
  • 取地址运算符重载
  • const取地址运算符重载


之所以称之为默认成员函数意思就是即使我们不写这六个成员函数 他们也会自己生成


在实际的写代码过程中前面四个默认成员函数是比较重要的 我们需要自己实现一遍 而后面两个让系统自己默认生成就好

在C++11中为了提高效率 提出了右值引用的概念 因此默认成员函数也增加了两个 变成了八个


增加了两个默认成员函数分别是

  • 移动构造函数
  • 移动赋值重载函数

但是他们的默认生成条件有些不同 他们两个的默认生成条件分别是


移动构造函数: 没有自己实现移动构造函数 并且没有自己实现析构函数 拷贝构造函数和拷贝赋值函数

移动赋值重载函数: 没有自己实现移动赋值重载函数 并且没有自己实现析构函数 拷贝构造函数和拷贝赋值函数


这里还有一点需要特别注意的是: 如果我们自己生成了移动构造和移动赋值重载函数 那么就算我们没有生成拷贝构造和拷贝赋值 系统也不会默认生成

也就是说这两对函数之间是有相互作用的 不能单独拆开来看

默认的移动构造和移动赋值会做什么呢


  • 默认移动构造 对于内置类型 它会完成浅拷贝 对于自定义类型 如果它实现了移动构造就会调用移动构造 否则就调用拷贝构造
  • 默认移动赋值 对于内置类型 它会完成浅拷贝 对于自定义类型 如果它实现了移动赋值就会调用移动赋值 否则就调用拷贝赋值


验证默认移动构造和移动赋值

首先我们先创造出一个简单的string类 直接复用上一篇博客的代码就好

namespace shy
{
  class string
  {
  public:
    typedef char* iterator;
    // begin迭代器返回第一个元素
    iterator begin()
    {
      return _str;
    }
    // end迭代器返回最后一个元素后一个元素的位置
    iterator end()
    {
      return _str + _size;
    }
    // 构造函数
    string(const char* str = "")
    {
      _size = strlen(str); // 初始化设置字符串大小
      _capacity = _size;
      _str = new char[_capacity + 1]; // 这里要多开一个空间来存储/0
      strcpy(_str, str);
    }
    // 交换两个对象的数据 
    void swap(string& s)
    {
      ::swap(_str, s._str);
      ::swap(_size, s._size);
      ::swap(_capacity, s._capacity);
    }
    // 拷贝构造函数
    string(const string& s)
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      cout << "string(const string& s)" << endl;
      string tmp(s._str); // 调用构造函数
      swap(tmp);  // 交换私有成员变量
    }
    // 移动构造 
    string(string&& s)
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      cout << "string(string&& s)" << endl;
      swap(s);
    }
    // 赋值运算符重载(现代写法)
    string& operator=(const string& s)
    {
      cout << "string& operator=(const string& s)" << endl;
      string tmp(s); //拷贝构造出一个临时变量
      swap(tmp);// 交换这个两个对象
      return *this; // 返回左值
    }
    // 移动赋值
    string& operator= (string && s)
    {
      cout << "string& operatpr=(string&& s)" << endl;
      swap(s);
      return *this;
    }
    // 析构函数
    ~string()
    {
      delete[] _str; // 释放str的空间
      _str = nullptr;
      _size = 0;
      _capacity = 0;
    }
    //[]运算符重载
    char& operator[](size_t i)
    {
      assert(i < _size);
      return _str[i]; // 返回左值引用
    }
    // 改变容量 大小不变
    void reserve(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strncpy(tmp, _str, _size + 1); // 这里不使用strcpy的原因是字符串中哟i可能出现/0
        delete[] _str;
        _str = tmp;
        _capacity = n; // 容量改变
      }
    }
    // 尾插字符
    void push_back(char ch)
    {
      // 首先判断容量是否足够
      if (_size >= _capacity)
      {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
      }
      // 尾插到最后 最后加上\0
      _str[_size] = ch;
      _str[_size + 1] = '\0';
      _size++; 
    }
    //+=运算符重载
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    // 返回c类型的字符串
    const char* c_str() const //这里加const是修饰this指针 让它的权限变成只读 为了防止后面的只读对象调用这个函数
    {
      return _str;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
  };
}


之后我们再写出一个简单的person类 将我们写的string作为它的一个成员变量

class Person
{
public:
  //构造函数
  Person(const char* name = "", int age = 0)
    :_name(name)
    , _age(age)
  {}
  //拷贝构造函数
  Person(const Person& p)
    :_name(p._name)
    , _age(p._age)
  {}
  //拷贝赋值函数
  Person& operator=(const Person& p)
  {
    if (this != &p)
    {
      _name = p._name;
      _age = p._age;
    }
    return *this;
  }
  //析构函数
  ~Person()
  {}
private:
  shy::string _name; //姓名
  int _age;         //年龄
};


我们的person类中没有实现移动构造和移动赋值 但是实现了拷贝构造 析构和赋拷贝赋值

所以说移动构造和移动赋值并不会默认生成

那么我们下面就会有一段代码来证明上面的话

Person p1("zhangsan",18);
  Person p2 = ::move(p1);


我们这里使用了右值去构造p2 假设p2的移动构造存在的话 那么因为string是我们的自定义类型

所以说它就回去调用string的移动构造

那么我们来看看实际上允许的结果是什么

3d3fe4cc744c4f6d936107b51f09ba6b.png

实际上它调用的是string类的拷贝构造

从这个试验就可以证明并没有默认生成移动构造函数

那么如果要生成移动构造和移动赋值我们就必须将原person代码中的 析构函数 拷贝构造 拷贝赋值全部注释掉

那么注释掉之后我们再来看看效果是什么样子的

da44014031b84a58b78dbba03856cdee.png

接下来我们用另外一段代码试验一下移动赋值

Person p1("zhangsan",18);
  Person p2;
  p2 = ::move(p1);


e1b2156ef95b4482a59618adf0f881f5.png

我们可以发现 移动构造和移动赋值的生成条件符合我们之前的学习

这里还有一点需要注意的 有些比较古老的编译器可能不支持C++11 所以在这些编译器上无法进行我们上面的试验


类成员变量的初始化

C++98之前的类成员默认构造初始化规则特别奇怪 是这样子的

  • 对于自定义类型来说会调用他们的构造函数进行初始化
  • 对于内置类型不进行初始化


而在C++11中 对于内置类型打了个补丁

  • 对于自定义类型来说会调用他们的构造函数进行初始化
  • 对于内置类型我们可以使用缺省值来进行初始化


以我们的person类来说

shy::string _name; //姓名
  int _age = 18;     //年龄

如果我们使用默认的构造函数 那么他们的年龄就会被默认初始化为18

相关文章
|
1天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
41 30
|
16天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
16天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
16天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
29天前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
1月前
|
编译器 C++
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
|
1月前
|
编译器 C++
virtual类的使用方法问题之静态和非静态函数成员在C++对象模型中存放如何解决
virtual类的使用方法问题之静态和非静态函数成员在C++对象模型中存放如何解决
|
1月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
16天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
24天前
|
存储 C++
C++ dll 传 string 类 问题
C++ dll 传 string 类 问题
16 0