侯捷cpp-oop(上) 笔记

Search for a command to run...

No comments yet. Be the first to comment.
C++程序设计兼谈对象模型-导读 conversion function, 转换函数 class Fraction { public: Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){}; operator double() const { return 1.0 * m_numerator / m_denominator; } private: int m_n...

类型限制了一个变量可以接受的有效值的集合,对数据可以进行的操作,数据的意义。 空类型(The empty type) 根据类型的定义,类型定义了可以接受的有效值集合,那么这个集合有没有可能为空?答案是有可能的,TypeScirpt 的 never 就是这种类型。 需要注意的是,空类型不同与 void, 后者是有效值集合当中只有一个值,但这个值没有任何意义。而空类型的有效值集合本身是空的。 使用场景 控制流分析 在函数调用时,标志一个函数不会返回任何值: 在调用过程中抛出异常、死循环或者程序崩溃...

不同于 Chapter03 从系统的角度来处理数据,这一章从数据科学的视角来处理数据。这章的标题是“training Data”,而非“training dataset”,因为 数据集(dataset) 意味着有限(finite)和固定(stationary), 而现实生产环境中的 数据(data) 通常是 无限 并且 不固定 的。 抽样 抽样方法在 ML 项目的生命周期中无处不在,在这一节中,我们使用生成训练数据作为例子。 那为什么需要抽样?直接使用全部数据不可以吗? 首先,在现实世界中,并不...

这一章大部分内容和DDIA很相似,作者也说 “If you’re already familiar with data systems, you might want to move directly to Chapter 4 to learn more about how to sample and generate labels to create training data. If you want to learn more about data engineering from a...

Classes 两个分类
Class without pointer member(s): complex
Class with pointer member(s): string
也可以分为
函数如果在 class 内定义完成,则自动成为 inline 候选人.
也可以在class 外部定义,方法需要增加inline 关键字。
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)操作。
不带指针的类,多半不需要写析构函数
单例(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';
}
当不修改成员变量(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.
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
什么情况下 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. 否则会导致函数退出后,在栈上申请的内存空间被释放。
所有member function 都有一个隐藏的参数this.
complex& complex::operator += (const complex& r) {
return __doapl(this, r); // do assign plus
}
传递者 无需知道 接收者 是以 reference 的形式接收。

在重载运算符时,运算符的返回值,只需要返回值,而不需要在意后面是值传递 还是 引用传递
inline complex operator+ (const complex& x, double y) {
return complex(x.real()+y, x.imag());
}
complex& complex::operator+() {
std::cout << "just positive" << std::endl;
return *this;
}
这个成员函数代表正号(一元操作符),而不是加号(二元操作符),编译器通过参数来确定。
只要 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=
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
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 object, 其生命在作用域(scope)结束之际结束,又叫auto object, 因为它会被自动清理。
static object, 其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。
{
static Complex c2(1, 2);
}
global object, 其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。也可以视为一种 static object, 其作用域是 整个程序。
heap object



对于 拥有两个 double(4Bytes) 的 Complex, 真正分配的绿色部分(8Bytes), release模式(右边) 和 debug模式(左边), 都会在内存块的起始和终止位置增加红色部分的cookie。
cookie的值包含内存块的大小信息, 供 malloc() 和 free() 使用。 最后一位0或1, 表示该块内存对于os来说是分配出去(1),还是已经被回收(0).
new tt[] 读作 array new. delete[] tt 读作 array delete.
array new一定要搭配array delete

数组的内存空间会被释放,不会发生内存泄露。
但是数组内元素只会调用一次dtor(), 对象中动态分配的内存就不会被释放掉。
如果是Complex这种class with pointer members, 使用delete 来释放Complex[]也不会有问题。

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;
}
template <typename T>
class TComplex {
private:
T re, im;
};
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.

有以下三种方法使用


Inheritance(继承)
Composition(复合)
Delegation(委托)
一个类中包含另一个类。

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


Composition by reference

和Composition相比,Delegation的生命周期,内外部对象不一致。
这种设计模式,也叫pointer to implication(pimpl). 也叫编译防火墙,Handle部分不需要再编译,只需要编译Body部分。


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

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

父类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.

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, 这一点从内存布局上就能看出来。析构的时顺序相反。

