侯捷cpp-oop(上) 笔记

qilingzhaoqilingzhao
5 min read

头文件与类声明

Classes 两个分类

  • Class without pointer member(s): complex

  • Class with pointer member(s): string

也可以分为

  • Object-Based vs Object Oriented

构造函数

inline函数

函数如果在 class 内定义完成,则自动成为 inline 候选人.

也可以在class 外部定义,方法需要增加inline 关键字。

constructor(ctor, 构造函数)

class complex {
public:
    // default argument r, i
    explicit complex(double r = 0, double i = 0) : re(r), im(i) { // initialization list(初值列)

    }
private:
    double re, im;
};
  • 使用初值列, 可以在初始化类成员变量时赋予值,而不是在构造函数内多余使用=进行赋值(assignment)操作。

  • 不带指针的类,多半不需要写析构函数

参数传递与返回值

ctors 放在 private 区

单例(Singleton)模式

// https://en.wikipedia.org/wiki/Singleton_pattern

#include <iostream>

class Singleton {
public:
  static Singleton& get() {
    static Singleton instance;
    return instance;
  }
  int getValue() {
    return value;
  }
  void setValue(int value_) {
    value = value_;
  }
private:
  Singleton() = default;
  ~Singleton() = default;
  int value;
};

int main() {
  Singleton::get().setValue(42);
  std::cout << "value=" << Singleton::get().getValue() << '\n';
}

const member functions

当不修改成员变量(class memeber)时, 使用 const 修饰成员函数。

class complex {
public:
    double real() const {return re;}
    double imag() const {return im;}
private:
    double re, im;
};

如果去掉 imag()const 关键字,调用时就会报错。

    const complex comp;
    comp.imag();
    // 'this' argument to member function 'imag' has type 'const complex', but function is not marked const

所以,const的类对象,只可以调用 const member function.

friend(友元)

class complex {
private:
    double re, im;
    friend complex& __doapl(complex*, const complex&);
};

inline complex& __doapl(complex* ths, const complex& r) {
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

friend 进行函数声明,表明什么样的函数可以访问 private 成员变量。

  • 相同 class 的各个 object 互为 friend(友元)
class complex {
public:
    int func(const complex& param) {
        return param.im + param.re; // it's ok
    }
private:
    double re, im;
};

complex c1(2, 1);
complex c2;

c2.func(c1); // it's ok

class body 外的各种定义(defintions)

什么情况下 return by reference ?

当返回的变量不需要在函数内部申请新的内存空间时,返回reference.

inline complex& __doapl(complex* ths, const complex& r) {
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

返回的需要声明新的内存空间时,返回value, 进行值拷贝操作。

inline complex operator+ (const complex& a, const complex& b) {
    return complex(a.re+b.re, a.im+b.im);
}

这里的 operator+() 函数即申请了新的内存空间,用于返回,因此时返回value, 而非reference. 否则会导致函数退出后,在栈上申请的内存空间被释放。

操作符重载和临时对象

操作符重载-1 成员函数(this)

所有member function 都有一个隐藏的参数this.

complex& complex::operator += (const complex& r) {
    return __doapl(this, r); // do assign plus
}

reference 语法分析

传递者 无需知道 接收者 是以 reference 的形式接收。

在重载运算符时,运算符的返回值,只需要返回值,而不需要在意后面是值传递 还是 引用传递

操作符重载-1 非成员函数(no this)

inline complex operator+ (const complex& x, double y) {
    return complex(x.real()+y, x.imag());
}

class body 外的 definition

complex& complex::operator+() {
    std::cout << "just positive" << std::endl;
    return *this;
}

这个成员函数代表正号(一元操作符),而不是加号(二元操作符),编译器通过参数来确定。

Big Three: copy ctor(拷贝构造), copy assignment op(拷贝复制), dtor(析构)

只要 class 内有指针,就要自己实现 Big Three.

class String {
    String(const char* cstr = 0);
    // copy ctor
    String(const String& str);
    // copy assignment operator
    String& operator=(const String& str);
    // destructor
    ~String();
};

https://www.cs.odu.edu/~zeil/cs361/latest/Public/big3/index.html#moving-data-l-values-and-r-values

class with pointer members 必须有 copy ctor 和 copy op=

copy ctor

    MyString s1;
    MyString s2(s1); // copy ctor here
    MyString s3 = s1; // copy ctor here
    s3 = s2; // copy op= here

使用一个对象初始化构造另一个对象,无论形式是 obj2(obj1) 还是 OBJ obj2 = obj1 都是调用 copy ctor

copy assignment operator

MyString& MyString::operator=(const MyString &str) {
    std::cout << "copy op= here" << std::endl;
    // IMPORTANT: avoid self assignment
    if (&str == this) {
        return *this;
    }
    // delete origin char array
    delete[] this->m_data;
    // allocate a new char array
    this->m_data = new char[strlen(str.m_data) + 1];
    // copy data
    strcpy(this->m_data, str.m_data);
    return *this;
}

一定要在 copy assignment operator 内检查 self-assignment.

Stack, Heap and Memory management

  • stack object, 其生命在作用域(scope)结束之际结束,又叫auto object, 因为它会被自动清理。

  • static object, 其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。

{
    static Complex c2(1, 2);
}
  • global object, 其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。也可以视为一种 static object, 其作用域是 整个程序

  • heap object

new: 先分配 memory, 再调用 ctor

delete: 先调用 dtor,再释放 memory

动态分配获得的内存块(memory block)

对于 拥有两个 double(4Bytes)Complex, 真正分配的绿色部分(8Bytes), release模式(右边) 和 debug模式(左边), 都会在内存块的起始和终止位置增加红色部分的cookie

cookie的值包含内存块的大小信息, 供 malloc()free() 使用。 最后一位01, 表示该块内存对于os来说是分配出去(1),还是已经被回收(0).

动态分配得到的 array

new tt[] 读作 array new. delete[] tt 读作 array delete.

array new 一定要搭配 array delete

数组的内存空间会被释放,不会发生内存泄露。

但是数组内元素只会调用一次dtor(), 对象中动态分配的内存就不会被释放掉。

如果是Complex这种class with pointer members, 使用delete 来释放Complex[]也不会有问题。

拓展补充: 类模版,模版函数及其他

static

data members 加上 static 修饰后,类的所有实例化对象共享一份

member functions 加上 static 修饰后,仍然在内存中只有一份。但是,static member function 调用时没有 this pointer, 只能访问 static data members

class Account {
public:
    static double m_rate;
    static void set_rate(const double& x);
};

// Non-const static data member must be initialized out of line
double Account::m_rate = 8.0; // definition

int main() {
    // call static member function by class name;
    Account::set_rate(5.0);

    Account a;
    // call static member function by object;
    a.set_rate(10);
    return 0;
}

class template, 类模版

template <typename T>
class TComplex {
private:
    T re, im;
};

function template, 函数模版

template <typename T>
inline
const T& min(const T& a, const T& b) {
    return b < a ? b : a;
}

class template 使用时必须明确指定绑定的类型,

complex<int> a;

function template 不必明确指出,编译器会自动进行argument deduction.

namespace

有以下三种方法使用

更多细节

Object Oriented Design (OOP -> OOD)

  • Inheritance(继承)

  • Composition(复合)

  • Delegation(委托)

Composition(复合), 表示 has-a

一个类中包含另一个类。

从内存的角度来看,对象间是直接包含的关系(而非包含引用或指针)。 两个对象的生命周期是一致的。

Composition 下的构造和析构

Delegation(委托): Composition by reference.

Composition by reference

Composition相比,Delegation生命周期,内外部对象不一致

这种设计模式,也叫pointer to implication(pimpl). 也叫编译防火墙,Handle部分不需要再编译,只需要编译Body部分。

Inheritance(继承), 表示 is-a

Inheritance 下的构造和析构

虚函数和多态

Inheritance(继承) with virtual function(虚函数)

成员变量的继承可以从内存角度理解。

成员函数的继承不可以从内存角度理解,继承的是调用权。派生类可以调用基类的函数,就是继承了基类的成员函数。

  • 虚函数子类可以不重写,纯虚函数子类必须重写

  • 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。

父类CDocument中的Serialize()是虚函数,在实际调用时,调用的是子类CMyDoc实现的Serialize()函数。

class Base {
public:
    virtual void func() {
        cout << this << endl;
        cout << "in Base::func()" << endl;
    }

    void hi() {
        cout << this << endl;
        cout << "in Base::hi()" << endl;
        func();
    }
};

class Derived : public Base {
public:
    void func() override {
        cout << this << endl;
        cout << "in Derived::func()" << endl;
    }
};

int main() {
    Derived d;
    d.hi(); // d -> Base::hi() -> Derived::func()
    return 0;
}

输出

0x16fccf518
in Base::hi()
0x16fccf518
in Derived::func()

这个也印证了,调用成员函数的时候,会隐式传入this指针,调用时查虚函数表,也是thisvptr_table.

这种把父类的成员函数 “延缓” 子类实现的做法,叫做设计模式中的Template Method.

Inheritance + Composition 关系下的构造和析构


class Component {
public:
    Component() {
        cout << "Component()" << endl;
    }
    ~Component() {
        cout << "~Component()" << endl;
    }
};

class Base1 {
public:
    Base1() {
        cout << "Base1()" << endl;
    }
    ~Base1() {
        cout << "~Base1()" << endl;
    }
};

class Derived1 : public Base1 {
public:
    Derived1() {
        cout << "Derived1()" << endl;
    }
    ~Derived1() {
        cout << "~Derived1()" << endl;
    }
private:
    Component c;
};

int main() {
    Derived1 d1;
    return 0;
}
Base1()
Component()
Derived1()
~Derived1()
~Component()
~Base1()

第一种方式, 先构建Basepart, 再构建Componentpart. 析构时顺序相反。

第二种方式, 先构建Base part 中的 Componment part, 再构建BasePart, 这一点从内存布局上就能看出来。析构的时顺序相反。

Delegation(委托) + Inheritance(继承)

委托相关设计

0
Subscribe to my newsletter

Read articles from qilingzhao directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

qilingzhao
qilingzhao