本文最后更新于:2 years ago

Chapter 4: 组合、继承和模板

4.1 组合

语义角度:

Aggregation: 子对象可以在没有父对象存在时存在。(车和轮子)

composition: otherwise。(书和书页)

4.1.1 初始化

Note

(1)const, 引用和成员对象都是在初始化列表里面初始化的。

(2)构造顺序与定义顺序相同,析构顺序倒过来。

4.2 继承

image-20200827155854867

4.2.1 初始化

顺序:基类ctor -> 派生类ctor,析构反过来。

可以通过using关键字继承基类的构造函数。(Beginning C++17, P528

image-20200827160939643

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;

image-20200827162052342

(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 虚基类

为了解决以下问题:

image-20200827162832667

(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为基类对象(不是指针不是引用),派生类对象被切片,数据丢失。

image-20200827170140789

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:

image-20200827172154206

4.2.14 VTABLE & VPTR

VTABLE:在编译期建立;

VPTR:在运行期建立。

image-20200827172439702

4.2.14 抽象类

有纯虚函数,不能定义对象。(所以不会发生切片)全部override了才能定义对象。

纯虚函数可以有界面(就算有界面也得override了):

image-20200827172830303

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;
};

image-20200828090159188

class adapter

image-20200828090435000

private继承而非复合。

5.3 Simple Factory

把会改变的和不会改变的分开。

image-20200828090859293

(1)一个产品类,有很多派生类。

(2)一个工厂类,制造这些产品。

(3)一个商店类,与实例化解耦。

image-20200828091530112

不满足open-closed principle

image-20200828091726959

5.4 Factory Method(creational)

image-20200828091925467

image-20200828092150998

image-20200828092221222

可以使用参数化:原有代码使用simple factory,新种类使用factory method。

5.5 Absstract Factory(creational)

image-20200828093018191

5.6 Observer(creational)

image-20200828093437987

image-20200828094035073

image-20200828094207286

5.7 Strategy(behavioral)

image-20200828100704597

5.8 Template Method(behavioral)

image-20200828103425435

以上两种方式的结合:

image-20200828103730633

5.9 Proxy(structural)

image-20200828104223431

5.10 Command State(behavioral)

image-20200828105105822

image-20200828105208408

image-20200828105314634

image-20200828105404982

image-20200828110043226

image-20200828110239072

State Pattern与其类似。

image-20200828110336489

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类中

image-20200828115315942

可以定义一个比较类后,用比较类定义一个自动排序的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 迭代器模式

模板 :解耦 算法 和 数据类型;

迭代器:解耦 算法 和 数据结构类型;

image-20200828131704156

image-20200828131718351

image-20200828132055991

image-20200828132627152

6.7 一些有用的STL

image-20200828133622250


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

Daily Learning: 使用宏和shell script Previous
OOP复习笔记-Part I Next