侯捷cpp-oop(上) 笔记


头文件与类声明
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()
使用。 最后一位0
或1
, 表示该块内存对于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
指针,调用时查虚函数表,也是this
的vptr_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()
第一种方式, 先构建Base
part, 再构建Component
part. 析构时顺序相反。
第二种方式, 先构建Base
part 中的 Componment
part, 再构建Base
Part, 这一点从内存布局上就能看出来。析构的时顺序相反。
Delegation(委托) + Inheritance(继承)
委托相关设计
Subscribe to my newsletter
Read articles from qilingzhao directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
