本文最后更新于:2 years ago
Chapter 4: 组合、继承和模板
4.1 组合
语义角度:
Aggregation: 子对象可以在没有父对象存在时存在。(车和轮子)
composition: otherwise。(书和书页)
4.1.1 初始化
Note:
(1)const, 引用和成员对象都是在初始化列表里面初始化的。
(2)构造顺序与定义顺序相同,析构顺序倒过来。
4.2 继承
4.2.1 初始化
顺序:基类ctor -> 派生类ctor,析构反过来。
可以通过using关键字继承基类的构造函数。(Beginning C++17, P528)
Note:ctor, dtor, “=”都默认不继承,不定义的话会自动合成。
4.2.3 final & override
final:禁用一个类的继承/一个成员函数的复写。
class A final {}
class A{
void f() final {}
};
override:确保是要override基类里面的虚函数。
4.2.4 权限
(1)private继承时通过using把基类中的public成员重新设置为public。
public:
using Pet::eat;
(2)默认继承方式是private。
4.2.5 重名问题
class A{
void getDensity();
};
class B{
void getDensity();
};
class C: public A, public B{ }
int main()
{
C obj;
obj.getDensity(); //WRONG!
obj.A::getDensity(); //RIGHT!
static_cast<A&>(obj).getDensity();
}
4.2.6 虚基类
为了解决以下问题:
(CerealPack将有两份Common的拷贝)使用virtual即可避免。
4.2.7 Name Hiding
class Pet{
public:
void sleep();
void sleep() const;
}
class Cat{
public:
void sleep(int a);
}
int main() {
Cat cat;
cat.sleep(); //ERROR!sleep()(const/non-const) is hidden
}
解决方法:转发/using。
using Pet::sleep; //多个重名的被同时publicize,想隐藏一部分可以再声明为private
void sleep() {
return Pet::sleep();
}
4.2.8 Upcasting
将派生类的引用/指针转换为基类的引用/指针。只在public继承的条件下有效。
4.2.9 Copy Ctor & Move Ctor
在继承和复合中会自动生成,调用子对象和基类的copy ctor。
利用upcasting,可以这样写:
class C : public B
{
A a;
C(const C& c) : B{c}, a{c.a} {}
C(C &&c) : B{move(c)}, a{move(c.a)}
};
执行顺序:先继承后复合。
4.2.10 对象切片
如果直接将派生类对象cast为基类对象(不是指针不是引用),派生类对象被切片,数据丢失。
4.2.11 多态
Note 1 : virtual关键字只需要在声明时出现。
Note 2:基类virtual派生类全virtual。
Note 3:如果基类虚函数有缺省参数,而且你通过指针/引用调用该函数,得到的缺省值是基类中的。
4.2.12 关键字typeid
头文件:
cout << typeid(double).name() << endl; //"double"
Derived obj; Base& ref = obj;
cout << typeid(ref).name() << endl; //"Derived"
4.2.13 static_cast(again) & dynamic_cast
static_cast:
- 是静态检查的,也就是说是在编译过程中检查。
- 也可以将基类转回派生类,但不安全!
- C-style cast: (int)a 不安全!
dynamic_cast:
- 只能用在多态类型上(有至少一个虚函数)。
- 比static_cast安全(在运行时才进行转换,对指针转换不成功返回nullptr,对引用转换不成功停止程序、抛出异常)
- 可以进行crosscast:
4.2.14 VTABLE & VPTR
VTABLE:在编译期建立;
VPTR:在运行期建立。
4.2.14 抽象类
有纯虚函数,不能定义对象。(所以不会发生切片)全部override了才能定义对象。
纯虚函数可以有界面(就算有界面也得override了):
4.2.15 Virtual与ctor, dtor的关系
- ctor/dtor中调用虚函数,多态故障,会调用当前类的成员函数。(在其它成员函数中调用可行)
- ctor不可以是virtual的(因为VPTR都还没有)
- dtor可以是纯虚的,但需要定义函数体。
- dtor一般都是虚的。(有别的虚函数就应当是虚的)
NOTE:多态需要额外开销。
Chapter 5:设计模式
5.0 OOP Principle
- 针对接口编程。
- open-closed principle:对扩展开放,对修改封闭(增量开发原则)
- single responsibility:分割任务,交给不同的对象。
5.1 Singleton(creational)
Dynamic Singleton:
class Singleton {
public:
static Singleton* Instance() {
if (instance_ == nullptr) {
instance_ = new Singleton();
}
return instance_;
}
private:
Singleton() {}
Singleton(const Singleton&);
Singleton& operator = (const Singleton&);
~Singleton(); //禁止通过这里释放
static Singleton* instance_;
}
5.2 Adapter(structural)
使用已有的类完成新类的书写。
class MyStack{
public:
void push(int i) {
m_data.push_back(i);
}
private:
std::vector<int> m_data;
};
class adapter:
private继承而非复合。
5.3 Simple Factory
把会改变的和不会改变的分开。
(1)一个产品类,有很多派生类。
(2)一个工厂类,制造这些产品。
(3)一个商店类,与实例化解耦。
不满足open-closed principle。
5.4 Factory Method(creational)
可以使用参数化:原有代码使用simple factory,新种类使用factory method。
5.5 Absstract Factory(creational)
5.6 Observer(creational)
5.7 Strategy(behavioral)
5.8 Template Method(behavioral)
以上两种方式的结合:
5.9 Proxy(structural)
5.10 Command State(behavioral)
State Pattern与其类似。
Chapter 6:其它
6.1 运算符重载
方法一:成员函数
x @ y;
相当于 x.operator @ (y);
@ x;
相当于 x.operator @();
,也即const TYPE& operator ++()
。
x @;
相当于 x.operator @(0);
,也即const TYPE& operator ++(int)
。
方法二:友元函数
限制:用法不能变,不能定义新的运算符,不能改变运算符优先级,不能改变运算元个数,有些运算符不能重载(<.>, <.*>, <::>, <?>)
friend关键字是为了访问类内的私有成员,如果不需要的话不一定需要加。
friend const Integer& operator++(Integer& a);
friend const Integer operator++(Integer& a, int);
当左操作数不是自定义类型时,只能作为友元函数重载。
6.2 流运算符
cin is istream, cout is ostream
friend ostream& operator << (ostream &output, const Point& point) {
// NOTE: output <<
return output;
}
6.2 “=”
有指针时自己定义。
TYPE& operator = (const TYPE& src)
{
if (&src != this){ //avoid self assignment
//assignment
}
return *this;
}
注意:
(1)返回引用;
(2)全部赋值,而不是传递指针。
6.3 Move Assignment
相当于 bitcopy + 原来的置空。
6.4 函数调用运算符“()”
函数对象:一个特殊类的对象,这个特殊类带有重载过的函数调用运算符(一定以成员函数的方式重载)。在STL中常常使用。
class FuncObj_Type{
public:
Return_Type operator() (ParameterList);
}
class F{
int data;
public:
F(int val) : data{val} {}
bool operator() (int x) {
return (x > data);
}
}
int main() {
F f{30};
bool res = f(43);
return 0;
}
在STL排序中:
bool myfunction (int i,int j) { return (i>j);}
struct myclass {
bool operator() (int i,int j) { return (i>j); }
} myobject;
int main ()
{
int myints[] = {32,71,12,45,26,80,53,33};
vector<int> myvector (myints, myints+8);
vector<int>::iterator it;
//使用 < 排序
sort (myvector.begin(), myvector.end()+4);
//使用 comp
sort (myvector.begin(), myvector.end(), myfunction);
//使用函数对象
sort (myvector.begin(), myvector.end(), myobject);
sort (myvector.begin(), myvector.end(), greater<int>() );
}
在map类中:
可以定义一个比较类后,用比较类定义一个自动排序的map:
struct PntCmp{
bool operator () (cosnt Point& pnt1, const Point& pnt2) {}
}
typedef map<Point, string, PntComp> PntSMap;
注意:当map.find()未找到时,空元素会被添加进去。
注意:重载比较运算符时最好重载3个:< , >, ==。
可以将函数对象作为参数,传到函数中。函数对象可以包含一些有用的成员函数。
6.5 模板
应当在头文件中定义。
// 函数模板:编译期间实例化
template <class T1, class T2, ...> //class / typename,
//类型标识符(T1, T2)不能重复,
//且每个类型标识符都应当在函数参数列表中出现
ReturnType FunctionName(ParameterList) {
// Function Body
}
// 特化:specialization
template <>
void sort<char*>(char** L, int length) {}
int main()
{
sort<int>(arr, 8);
sort(arr, 8); //implicit
}
// 类模板
template <class T = int, int max = 10> //int/uint/long等可以转换为int的作为参数
//在编译期间被实例化为常数,可以有缺省值
class Stack{
T arr[];
}
// 类模板可以继承 普通类 / 实例化的模板类
template <class T>
class Sortable_list : public Stack<T> {
}
int main()
{
Stack<int> intStack;
}
NOTE:
(1)因为模板实例化会复制代码,所以对于不需要类参数的代码部分应当做成基类。
(2)泛型编程(generic programming):与数据类型无关的编程。
(3)元编程(meta programming):编译期间计算。
(4)可以在函数对象中使用模板:
template <class T>
class IsPass {
T _p;
public:
IsPass(T p) : _p{p} {}
bool operator() (T& s) { return s >= _p; }
}
谓词:函数对象/指针/lambda表达式,返回true/false。
6.6 迭代器模式
模板 :解耦 算法 和 数据类型;
迭代器:解耦 算法 和 数据结构类型;
6.7 一些有用的STL
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!