-
explicit 关键字
-
示例
template<typename T> class Vec { public: Vec(); explicit Vec(std::string::size_type n, const T &val = T()); ... }
-
explicit 只对构造函数起作用,用于抑制隐式转换
Vec<int> vec = Vec<int>(100); // 正确 Vec<int> vec = 100; // 错误,有explicit以后无法将100隐式转换成Vec<int>类型
-
-
类型定义
-
示例
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; ... }
-
作用
(1) 提供用户使用的类型名称
(2) 隐藏类的实现细节
-
-
运算符重载
-
示例
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]; } ... }
-
定义运算符重载函数,也要定义函数名、参数、返回类型,只不过函数名一定是 operatorXXX,XXX代表被重载的运算符
-
运算符重载函数,同样可以被重载。示例中1用于非常量Vec对象,2用于常量Vec对象
-
运算符重载函数的参数个数和运算符的操作数一样多,但是左操作数就是对象本身,所以[]运算符重载函数只有一个参数,代表索引
-
第一种形式的[]运算符重载函数,返回T&的原因是方便可读可写,因为返回引用可以直接作为左值
-
第二种形式的[]运算符重载函数,整体用const修饰,代表常量Vec对象调用这个函数,返回const T& 类型,一来避免T很复杂时的拷贝开销,二来用const修饰避免被修改
-
-
返回迭代器
-
示例
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种,返回 const_iterator 代表返回 const T*类型,const修饰T,即指向的内容不可变
-
-
复制构造函数
-
应用场景
(1) 显式复制
Vec<int> v_copy = Vec<int>(v);
(2) 隐式复制
Vec<int> v_copy = v;
-
复制构造函数不是简单的把已有对象的全部数据成员都复制一份,而是要根据逻辑重写一下。例如复制后的对象和原对象的内部指针数据肯定不一样
-
复制构造函数和类名同名,只带一个参数且该参数和类本身类型相同
示例
template<typename T> class Vec { public: // 复制构造函数 Vec(const Vec& v) { create(v.begin(), v.end()); } ... }
-
-
赋值运算符
-
应用场景
把一个已经存在的值擦去,然后代之以一个新的值
-
赋值运算符不属于构造函数,属于运算符重载函数
template<typename T> class Vec { public: Vec<T>& operator=(const Vec<T>& rhs) { if (&rhs != this) { uncreate(); create(rhs.begin(), rhs.end()); } return *this; } ... }
-
返回引用的原因是为了连续赋值
-
首先要进行自我判断,防止出现自赋值的情况
自赋值的情况十分危险,因为赋值的语义要求先擦除,自赋值会导致把自身完全擦除掉,然后用内存中的随机值进行赋值
-
-
初始化和赋值的区别
-
发生初始化的时刻
(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; // 隐式复制构造函数
-
赋值只在表达式使用=时会被调用,并且变量已经存在
string s5; // 初始化 s5 = s1; // 赋值
-
构造函数始终只控制初始化操作,operator=成员函数只控制赋值操作
-
-
析构函数
-
一个在局域范围内被创建的对象,在它的生存范围以外时会被自动删除;动态分配内存的对象,在delete时会被删除
-
析构函数的作用是定义对象被删除时的操作,一般用于释放资源
示例
template<typename T> class Vec { public: ~Vec() { uncreate(); } ... }
-
-
一个完整的类应该至少包括4种函数
- 构造函数
- 复制构造函数
- 赋值运算符函数
- 析构函数
如果类中没有定义任何操作,编译器会自动为类生成相应的默认版本的函数
对于编译器默认生成的构造函数,会对其内部成员进行递归的初始化,直到到达基本数据类型
自动生成的函数不是很靠谱(例如不会释放指针的内存空间),所以好的编程习惯是显式定义这四种函数(而且要定义就定义全,只定义析构函数,不定义赋值和复制函数后果更可怕)
“三位一体”规则
T::T(); // 一个或多个构造函数 T::~T(); // 析构函数 T::T(const T&) // 复制构造函数 T::operator=(const T&) // 赋值运算符函数
-
灵活的内存管理
-
new/delete 方式存在的问题:
会使用T::T()构造函数为一个类型为T的数组中的每一个元素都进行初始化,开销很大
-
<memory>头文件中提供一个 allocator<T> 的类,用于管理内存
-
一共有四个 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*参数所指的元素
-
还有两个相关的非成员函数
这两个函数,都假定目标内存块未被初始化,也就是说刚刚allocate之后的状态
(1) void uninitialized_fill(T*, T*, const T&);
前两个参数指针区间内的内存块,填充第三个参数的值
(2) T* uninitialized_copy(T*, T*, T*);
把前两个参数指针区间中的值,复制到第三个参数指针所指向的目标内存块,并返回下一个未填充的位置
-
示例
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; }
-
allocator 广泛用于stl中
-