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

相关文章
|
14天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
25 2
|
20天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
54 5
|
26天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
56 4
|
27天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
65 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
23 1
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
2月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)