15
OOP
-
派生类必须在其内部对所有重新定义的虚函数进行生命 可以加
virtual
也可以不加 -
c++11也允许派生类显示的注明它将使用哪个成员函数改写基类的虚函数 具体是在参数列表后面加上
override
关键字
override只能加在类内 类外的函数定义不能加override -
使用基类的引用或者指针
调用虚函数
时才会发生动态绑定 -
通常定义一个
虚析构函数
-
virtual
只能出现在声明处 不能出现在类外部的函数定义处 -
如果在基类中是虚函数 在派生类中也隐式的是虚函数
-
成员函数如果是非虚函数 则继续过程发生在编译时而非运行时
-
编译器会隐式的执行派生类到基类的转换
-
派生类首先初始化基类的部分 然后按声明顺序初始化其自身数据成员
-
想用某个类作为基类 此类必须已经定义而非声明
-
防止继承的方法final关键字 类名后面加上
final
关键字表示此类无法作基类了 -
必须为每一个虚函数都提供定义
-
派生类中虚函数可以有virtual关键字也可以没有 以为一旦一个函数被声明为虚函数 那么在所有派生类中都是虚函数
-
一个派生类的函数如果覆盖了某个继承而来的虚函数 则形参和返回类型必须完全一致
- 返回类型一样可以有一个例外 就是虚函数返回的是类本身的指针或者引用时 派生类的虚函数可以返回派生类的指针或者引用
比图 A 中的虚函数返回 A* 或者 A& 那么 B:public A 中的虚函数可以返回 B* 或者 B&但是要求D向B的类型转换是允许的
- 返回类型一样可以有一个例外 就是虚函数返回的是类本身的指针或者引用时 派生类的虚函数可以返回派生类的指针或者引用
-
一般情况下 派生类覆盖基类的虚函数需要参数和返回类型一样 但是如果偶尔写错了检查这样的错误会很苦难 因为这样是允许的
写错了的话派生类的名字会隐藏基类的名字 哈 在c++11中在virtual函数后面加上override
关键字可以检查这种错误
具体意思看下面代码的解释class A { public: virtual void f(int v) { cout << v << endl; }; }; class B : public A { public: void f() { cout << "B" << endl; } }; B b; // 这一句会报错 因为名字查找到B的f函数 是没有参数的 正确调用是 b.f() // 一般情况下 应该是f写错了 如果在B的f后加上 override 可以检查出这个B中的f定义错误 少了int参数 b.f(1);
-
final也可以用来修饰函数
override
和final
在const或者引用限定符的后面或者尾置返回类型后面 -
虚函数的默认实参 如果某次调用使用了默认实参
则该实参由本次调用的静态类型决定
通常基类和派生类中的默认实参最好保持一致 也不能忽略 忽略的话会有如下问题
最好B也提供和A一样的默认参数class A { public: virtual void f(int v1, int v2 = 2, int v3 = 5) { cout << v1 << " cc " << v2 << " cc " << v3 << endl; }; }; class B : public A { public: virtual void f(int v1, int v2, int v3) override { cout << v1 << " xx " << v2 << " xx " << v3 << endl; }; }; int main() { A *a = new B(); a->f(99); // 虽然上面的a的调用没有问题 但是下面的b的调用有问题 会编译失败因为f调用参数不匹配 // B *b = new B(); // b->f(99); return 0; }
-
回避虚函数的话 可以使用域运算符强制指定
一般只有成员函数(比如需要继承基类中函数的通用操作)或者友元中才需要避免虚函数的机制
比如 基类A 派生类B b是B的一个指针 可以这样指定b->A::f()
指定为A的函数f -
纯虚函数
-
在函数体的位置(声明的分号前)书写
=0
=0
只能出现在类内的函数声明处 -
也可以为纯虚函数提供定义 但是只能在类外定义
-
含有(或者未经覆盖直接继承)纯虚函数的类是
抽象基类
无法创建抽象基类
的对象
-
-
派生类向基类转换的可访问性
-
D公有的继承B 受保护或者私有继承都不行
-
不论D以什么方式继承B D的成员函数和友元都能使用使用派生类向基类的转换
-
如果D是公有或者守护继承B的 则
D的派生类
的成员和友元可以使用D向B的类型转换
-
-
就像友元关系不能传递一样 友元关系也不能继承
-
可以使用using声明改变个别成员的可访问性质 派生类只能为那些他可以访问的名字提供using声明
只指定一个名字 不需要形参列表或者什么别的 比如一个func(int) 只需要 using base::func 即可
-
前面有一个例子是派生类中同样的名字隐藏父类的例子(介绍override的时候) 如何避免这种隐藏进行重载呢
-
因为名字查找规则 相当于现在派生类中查找名字 就不会在基类找了o
-
在编译时进行名字查找 一个对象 引用或者指针的静态类型 决定了该对象的哪些成员可见
尽管静态类型和动态类型不一样 但是允许访问哪些成员还是静态类型决定的上文的默认实参有个这样的例子
-
如果希望派生类使用所有的重载版本 那么要么需要覆盖全部的版本要么一个也不覆盖 不然就会发生如上的隐藏
-
另一种方法 如上所述 使用 using 声明就哈啦啦 这就很方便了
-
虚析构函数
-
一开始就说了通常定义一个
虚析构函数
这样才能正确析构哇 -
如果基类的析构函数不是虚函数 delete一个指向派生类的基类指针将产生未定义行为
-
因为继承过程中的构造顺序和析构顺序
在构造或者析构函数中调用虚函数会执行与构造函数或者虚函数对应的类的虚函数版本
如果动态绑定gg 比如在基类的构造函数调用到派生类那里去了 但是此时派生类都没构建完成 就gg拉
继承的构造函数
-
类不能继承 默认 拷贝 移动 构造函数 这些函数按照正常规则合成的
比如B继承了A 然后B没有定义默认构造函数 尽管B可以直接使用A的构造函数 但是其实是合成的默认构造函数 而不是继承来的
-
可以使用using声明 显示的使用基类的构造函数 比如
using A::A
-
通常情况下 using声明语句只是另某个名字在当前的作用域可见
但是用于构造函数时 using声明将另编译器产生代码 对于基类的每个构造函数将生成类似于如下的派生类构造函数
derived(params):base(params){}
如果派生类有自己的数据成员 则这些成员默认初始化
16
模板
-
可以定义非类型模板参数 一个非类型模板参数表示一个值 通过特定的类型名而非typename或者class关键字来指定非类型参数
-
一个模板被实例化时 非类型参数被一个用户提供或者编译器推断的值所代替 这些值必须是
常量表达式
如果这个非类型参数是一个指针或者引用 则提供的值必须具有静态的生存期
-
函数模版也可以是inline和constexpr的 和普通函数一样 位置在模版参数列表之后 返回类型之前
-
编译器遇到模板定义时 并不会生成代码 只有当我们实例化出末班的一个特定版本时 编译器才会生成代码
为了生成一个特定的实例化版本 编译器需要掌握函数模板或者类模板的成员函数的定义
因此模板的头文件通常包括声明也包括实现
-
编译器可以为函数末班进行参数类型的推断 比如
template <typename T> void f(T, T)
当调用f(2,2)
时会自动推断T为int
但是不能为类模板进行推断 使用类模板时 必须提供额外的模板参数 -
一个类模板的每个实例都会形成一个独立的类
-
定义在类模板内的代码默认为内联的
-
默认情况下 对一个实例化的类模板 其成员只有在使用时才被实例化 就算是static成员变量也是使用时实例化
-
在类模板内部自己的作用域中 可以直接使用模板名而不提供实参
template<T> C 在C的内部 比如函数可以直接返回C 而不用写C<T>
但是如果在类的外部定义的时候 返回值还是要C<T> 因为返回值这里还不在类的作用域中
-
可以将模板类型参数声明为友元
-
模板也可以使用typedef或者using
-
typedef Blob<string> StrBlob 因为模板不是一个类型 所以不能typedef一个模板 即 typedef Blob<T> 但是可以用using
-
template <typename T> using twin = pair<T, T> ---------> twin<int>
-
template <typename T> using twin = pair<T, unsigned> ---------> twin<int>
-
-
模板的static成员 每一个不一样的实例都有其自己的static成员 毕竟不同的实例 类型不一样 如下图
template <typename T> class A { public: static int x; }; template <typename T> int A<T>::x = 0; int main() { A<int>::x = 1; A<string>::x = 2; cout << A<int>::x << " " << A<string>::x << endl; return 0; }
-
模板声明必须包含模板参数 比如函数模板和类模板声明
template <typename T> int compare(const T&, const T&); template <typename T> class Blob; // 注意这里的Blob不需要写成Blob<T>
-
一个特定文件所需的所有模板声明通常一起放置在头文件开始位置 出现于任何使用这些模板的代码之前
可以避免因为没有声明而实例化一个模板其实明明有一个更好的重载版本的
通常没有声明就使用会编译失败 但是有模板的话会实例化模板
-
在模板中会有一种困惑
T::size_type * p
无法确定是定义变量p 还是将T的一个名为size_type的static数据成员与p相乘
默认情况下 通过作用域运算符访问的名字不是类型 我们通过关键字typename来显示告诉编译器该名字是一个类型
typename T::size_type * p
-
可以模板默认实参 比如
template <typename T = int> class xxxx
如果要使用默认值的话需要template <> classname;
当然如果是函数模板的话就不需要这样啦 因为类模板的参数无法推倒 但是函数模板的可以 -
一个类不论是普通类还是模板类都可以包含本身是模板的成员函数 叫做成员模板 成员模板不可以是虚函数
-
模板在使用时才会实例化 意味着相同的实例可能出现在很多地方 这样额外的开销可能非常严重
可以用过显示实例化避免这种类模板的实例化定义会实例化该模板的所有成员
extern template declaration;实例化声明 --------- extern template class Blob<string>;
template declaration;实例化定义 --------- template int compare<const int&, const int&>;
-
关于函数模板的实参推断
-
如果参数不是模板参数 正常转换
-
是模板参数的话只允许以下几种转换
-
非const对象引用(指针)传递给const引用(指针)
-
数组或者函数指针转换
-
其他的类型明显不会转换拉 会为其自动生成一个新的模板实例
template int compare(const T&, const T&);
long v = 4;
compare(1, v) 就会错误模板参数推断错误失败
-
-
-
显示指定函数模板实参 和类一样加到<>里面即可 顺序就死从左至右的顺序
显示指定了类型的模板实参也可以正常转换
-
有的时候无法知道返回的准确类型 可以使用尾置返回
template <typename IT> ??? &fun(IT beg, IT end) { return *beg; } template <typename IT> auto fun(IT beg, IT end) -> decltype(*beg) { return *beg; }
-
函数参数类型推断比如
template <typename T> f(const T*)
T
的结果一定不会是const
因为cosnt被单独拿出来了 -
右值也是类似的
template <typename T> f(T&&)
同理比如f(3) T是一个int而已 而不是T&& -
P608 609 的引用折叠太难了 自己翻去吧 基本总结如下
一个模板参数定义为T&&时 可以传递任意实参 传递左值时 T被推段为一个左值引用 int& 等等
函数的参数会被推断为 比如 int& && 折叠为int& 即T和函数参数都会被推断为int&
这样搞大概率会有bug 因为T可以被推断为引用也可以不是引用
-
通常用右值引用参数有两种情况
-
模板重载 这样就可以各自匹配不混乱啦啦啦
template <typename T> void f(T&&); template <typename T> void f(const T&);
-
模板转发其实参 意思就是可以用
T&&
和std::forward
形参保持输入的类型传递转发给其他函数
-
-
从一个左值引用使用static_cast强制转换到右值引用是可以的
-
有多个重载模板对一个调用提供同样好的匹配时 一般的普通函数可能就二义性了 但是模板函数这里会选择更
特例化的版本
如果非模板函数和模板函数重载了 显然选择非模板的 因为这个非模板版本更加的特例化
-
可变参数模板 包扩展
-
模板特例化其实是实例化一个模板 而非重载 因此特例化不影响函数匹配
-
特例化一个函数模板需要
template<>
开头表示我们将为原模板的所有模板参数提供实参 -
特例化需要与原模板在同一个命名空间
-
特例化类模板可以
template<>
表示全特例化
也可以部分特例化比如P628
也可以特例化类的某些成员比如P629