chapter11_定义抽象数据类型

本文详细介绍了C++中定义抽象数据类型的关键概念,包括explicit关键字的使用,类型定义的作用,运算符重载的实现,返回迭代器的方法,复制构造函数和赋值运算符的细节,初始化与赋值的区别,以及析构函数的功能。同时,讨论了如何通过allocator类进行灵活的内存管理,以及在STL中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. explicit 关键字

    1. 示例

       template<typename T>
       class Vec {
       public:
           Vec();
           explicit Vec(std::string::size_type n, const T &val = T());
       
       ...
       }
      
    2. explicit 只对构造函数起作用,用于抑制隐式转换

       Vec<int> vec = Vec<int>(100);   // 正确
      
       Vec<int> vec = 100;   // 错误,有explicit以后无法将100隐式转换成Vec<int>类型
      
  2. 类型定义

    1. 示例

       template<typename T>
       class Vec {
       public:
           typedef T* iterator;    // 可读可写迭代器
           typedef const T* const_iterator;   // 只读迭代器,所以用const修饰T,代表T不能变
           typedef size_t size_type;   
           typedef T value_type;
      
       ...
       }
      
    2. 作用

      (1) 提供用户使用的类型名称

      (2) 隐藏类的实现细节

  3. 运算符重载

    1. 示例

       template<typename T>
       class Vec {
       public:
           // 1
           T& operator[](size_type i) { return data[i]; }
      
           // 2
           const T& operator[](size_type i) const { return data[i]; }
      
           ...
       }
      
    2. 定义运算符重载函数,也要定义函数名、参数、返回类型,只不过函数名一定是 operatorXXX,XXX代表被重载的运算符

    3. 运算符重载函数,同样可以被重载。示例中1用于非常量Vec对象,2用于常量Vec对象

    4. 运算符重载函数的参数个数和运算符的操作数一样多,但是左操作数就是对象本身,所以[]运算符重载函数只有一个参数,代表索引

    5. 第一种形式的[]运算符重载函数,返回T&的原因是方便可读可写,因为返回引用可以直接作为左值

    6. 第二种形式的[]运算符重载函数,整体用const修饰,代表常量Vec对象调用这个函数,返回const T& 类型,一来避免T很复杂时的拷贝开销,二来用const修饰避免被修改

  4. 返回迭代器

    1. 示例

       template<typename T>
       class Vec {
       public:
      
           typedef T* iterator;
           typedef const T* const_iterator;
      
           ...
      
           iterator begin() { return data; }
      
           iterator end() { return limit; }
      
           const_iterator begin() const { return data; }
      
           const_iterator end() const { return limit; }
      
       private:
           iterator data;
           iterator limit;
           
           ...
       }
      
    2. 同样分为2种,返回 const_iterator 代表返回 const T*类型,const修饰T,即指向的内容不可变

  5. 复制构造函数

    1. 应用场景

      (1) 显式复制

           Vec<int> v_copy = Vec<int>(v);
      

      (2) 隐式复制

           Vec<int> v_copy = v;
      
    2. 复制构造函数不是简单的把已有对象的全部数据成员都复制一份,而是要根据逻辑重写一下。例如复制后的对象和原对象的内部指针数据肯定不一样

    3. 复制构造函数和类名同名只带一个参数且该参数和类本身类型相同

      示例

           template<typename T>
           class Vec {
           public:
               // 复制构造函数
               Vec(const Vec& v) { create(v.begin(), v.end()); }
      
               ...
           }
      
  6. 赋值运算符

    1. 应用场景

      把一个已经存在的值擦去,然后代之以一个新的值

    2. 赋值运算符不属于构造函数属于运算符重载函数

       template<typename T>
       class Vec {
       public:
           Vec<T>& operator=(const Vec<T>& rhs) {
      
               if (&rhs != this) {
                   uncreate();
                   create(rhs.begin(), rhs.end());
               }
               return *this;
           }
           ...
       }
      
    3. 返回引用的原因是为了连续赋值

    4. 首先要进行自我判断,防止出现自赋值的情况

      自赋值的情况十分危险,因为赋值的语义要求先擦除,自赋值会导致把自身完全擦除掉,然后用内存中的随机值进行赋值

  7. 初始化和赋值的区别

    1. 发生初始化的时刻

      (1) 声明一个变量

      string s; // 此时调用string的默认构造函数

      (2) 在一个函数的入口处用到函数参数的时候

      split(words); // 此时对words进行初始化

      (3) 在函数返回中使用函数返回值的时候

      split(words); // 在函数出口处要进行一下构造

      (4) 在构造初始化的时候

           string s1 = "jknknckvwtuo";    // 隐式转换构造函数
      
           string s2 = string("tfak")   // 显式构造函数
      
           string s3 = string(s2);     // 显式复制构造函数
           string s4 = s2;             // 隐式复制构造函数
      
    2. 赋值只在表达式使用=时会被调用,并且变量已经存在

       string s5;    // 初始化
       s5 = s1;      // 赋值
      
    3. 构造函数始终只控制初始化操作,operator=成员函数只控制赋值操作

  8. 析构函数

    1. 一个在局域范围内被创建的对象,在它的生存范围以外时会被自动删除;动态分配内存的对象,在delete时会被删除

    2. 析构函数的作用是定义对象被删除时的操作,一般用于释放资源

      示例

           template<typename T>
           class Vec {
           public:
               ~Vec() { uncreate(); }
           
               ...
           }
      
  9. 一个完整的类应该至少包括4种函数

    1. 构造函数
    2. 复制构造函数
    3. 赋值运算符函数
    4. 析构函数

    如果类中没有定义任何操作,编译器会自动为类生成相应的默认版本的函数

    对于编译器默认生成的构造函数,会对其内部成员进行递归的初始化,直到到达基本数据类型

    自动生成的函数不是很靠谱(例如不会释放指针的内存空间),所以好的编程习惯是显式定义这四种函数(而且要定义就定义全,只定义析构函数,不定义赋值和复制函数后果更可怕)

    “三位一体”规则

         T::T();                  // 一个或多个构造函数
         T::~T();                 // 析构函数
         T::T(const T&)           // 复制构造函数
         T::operator=(const T&)   // 赋值运算符函数
    
  10. 灵活的内存管理

    1. new/delete 方式存在的问题:

      会使用T::T()构造函数为一个类型为T的数组中的每一个元素都进行初始化,开销很大

    2. <memory>头文件中提供一个 allocator<T> 的类,用于管理内存

    3. 一共有四个 allocator 类的相关成员函数

      (1) T* allocate(size_t);

      分配一块指定了类型为T,但是没有初始化的内存块,返回内存块头元素的地址

      (2) void construct(T*, T);

      在T*参数指示的地址上,进行初始化,初始化的值为T参数,生成单个的对象

      (3) void deallocate(T*, size_t);

      释放未被初始化的内存,这段内存开始于T*参数,长度为size_t参数

      (4) void destroy(T*);

      调用T的析构函数,删除T*参数所指的元素

    4. 还有两个相关的非成员函数

      这两个函数,都假定目标内存块未被初始化,也就是说刚刚allocate之后的状态

      (1) void uninitialized_fill(T*, T*, const T&);

      前两个参数指针区间内的内存块,填充第三个参数的值

      (2) T* uninitialized_copy(T*, T*, T*);

      把前两个参数指针区间中的值,复制到第三个参数指针所指向的目标内存块,并返回下一个未填充的位置

    5. 示例

       template<typename T>
       class Vec {
       public:
           ...
      
       private:
           ...
           std::allocator<T> alloc;
      
      
           void create();
           void create(size_type, const T &);
           void create(const_iterator, const_iterator);
      
           void uncreate();
      
           void grow();
           void unchecked_append(const T &);
       };
      
      
       template<typename T>
       void Vec<T>::create() {
           data = avail = limit = nullptr;
       }
      
       template<typename T>
       void Vec<T>::create(Vec::size_type n, const T &val) {
      
           data = alloc.allocate(n);
           limit = avail = data + n;
      
           std::uninitialized_fill(data, limit, val);
       }
      
       template<typename T>
       void Vec<T>::create(Vec::const_iterator i, Vec::const_iterator j) {
      
           data = alloc.allocate(j - i);
           limit = avail = std::uninitialized_copy(i, j, data);
       }
      
       template<typename T>
       void Vec<T>::uncreate() {
      
           if (data != nullptr) {
               iterator it = avail;
               while (it != data) {
                   alloc.destroy(--it);
               }
      
               alloc.deallocate(data, limit - data);
           }
      
           data = limit = avail = nullptr;
       }
      
       template<typename T>
       void Vec<T>::unchecked_append(const T &val) {
           alloc.construct(avail++, val);
       }
      
       template<typename T>
       void Vec<T>::grow() {
      
           size_type new_size = std::max(2 * (limit - data), std::ptrdiff_t(1));
      
           iterator new_data = alloc.allocate(new_size);
           iterator new_avail = std::uninitialized_copy(data, avail, new_data);
      
           uncreate();
      
           data = new_data;
           avail = new_avail;
           limit = data + new_size;
       }
      
    6. allocator 广泛用于stl中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值