侯捷cpp-oop(下) 笔记

Search for a command to run...

No comments yet. Be the first to comment.
头文件与类声明 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, 构造函数) ...

类型限制了一个变量可以接受的有效值的集合,对数据可以进行的操作,数据的意义。 空类型(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...


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_numerator;
int m_denominator;
};
转换函数没有返回值
函数名为 operator double
no parameters
转换这个语义本身不改变成员变量,因此需要加 const 修饰。虽然不加 const 也可以通过编译。
Fraction(int num, int den=1)这个ctor,是2 parameters, 1 argument. 只需要一个 argument(实参) 就可以实例化。
class Fraction {
public:
Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){};
Fraction operator+ (const Fraction& f) {
return Fraction(0, 0); // ignore reality.
}
private:
int m_numerator;
int m_denominator;
};
int main() {
Fraction f(3, 5);
Fraction d2 = f + 4;
return 0;
}
f + 4, 会查找到 Fraction operator+ (const Fraction& f) 这个函数.
然后会尝试调用non-explict ctor把 4 转化为 Fraction 类型, 再传递进入 operator+ .
explict 会告诉编译器不要进行隐式转换, 可以用来修饰ctor和conversion function. 如果需要进行转换,需要在调用处显式指出。
修饰 conversion function 这一点大部分资料不会指出,但其实是可行的. 可以参考 user-defined conversion function .
一般pointer能做的事情,pointer-like classes 也可以做。因此能应用到 pointer 上的运算符,也可以应用到 pointer-like classes, 如operator*()、 opreator->().

对于 sp->method(), 操作符 -> 会作用在 sp 上, 调用 T* shared_ptr::operator->()const.
调用后,如果 -> 是一般的操作符,如*, 会被消耗, 变为 px method().
但是 -> 有一个特性,会在有操作数存在的情况下,一直存在,因此经过一次调用后不会被消耗,仍然是 px->method().


迭代器也是一类pointer-like class, 使用者调用 * 意图获得 data 本身; 使用者调用 ->, 意图获得 data 的地址,并且在改地址上继续复用 -> 来进行一系列操作。
operator() 也叫 function call operator.
只要 class 内部重载了 operator() 那么就可以称为 functor(仿函数).


unary_function 和 binary_function 既没有成员变量,也没有成员函数,理论上实际的大小是 0.
至于为什么要有 c++ 基本概念之 functor 这篇文章讲的不错。

这样做可以让ctor更有 "弹性", 可以接收多种类型的参数。

泛化和特化互为相反。
#include <iostream>
template <typename Key>
class hash {};
template<>
class hash<char> { // 格式类似于这种, 把上面的不确定的 typename 拿下来, 变为确定的类型.
public:
size_t operator() (char ch) {
return size_t(ch);
}
};
template<>
class hash<long> {
public:
size_t operator() (long l) {
return size_t(l);
}
};
int main() {
hash<long>()(1l);
hash<char>()('a'); // 第一个() 构造出一个匿名对象, 第二个() 调用重载后的小括号运算符.
return 0;
}
特化 也叫 全特化(full specialization), 也有 partial specialization.
函数模版只能全特化, 而类模版可以偏特化和全特化。
特化必须在同一个命名空间下进行,但 std 是一个例外,用户可以特化其中的模版,但不可以添加模版。
偏特化的顺序只能从左向右绑定模版参数.
template <typename T1, typename T2>
struct PPair {
int hash() {
std::cout << "in general hash() " << std::endl;
}
};
template <typename T2>
struct PPair<int, T2> {
int hash() {
std::cout << "in <int, T2> partial hash()" << std::endl;
}
};
int main() {
PPair<double, float>().hash();
PPair<int, double>().hash();
return 0;
}
in general hash()
in <int, T2> partial hash()

template<typename T>
struct JoJo {
void stand() {
std::cout << "in general stand()" << std::endl;
}
};
template<typename U>
struct JoJo<U*> {
void stand() {
std::cout << "in U* type stand()" << std::endl;
}
};
int main() {
JoJo<double>().stand();
JoJo<double*>().stand();
JoJo<int*>().stand();
return 0;
}
in general stand()
in U* type stand()
in U* type stand()




查看 C++ 版本
std::cout << __cplusplus << std::endl;
关键字 typename... 可以使用 sizeof...(args) 来获取剩余的参数数量。

void print() {}
template<typename T, typename... TS>
void print(const T& firstArg, const TS&... args) {
cout << firstArg << "\t rest number of TS is " << sizeof...(args) << endl;
print(args...);
}
int main() {
print(1.2, 1, "hello");
return 0;
}
1.2 rest number of TS is 2
1 rest number of TS is 1
hello rest number of TS is 0
将函数的参数分为 一个 和 一包,递归处理第一个,一包 的数量可以为 0, 最后递归调用到 void print().

支持两种遍历, pass by value 和 pass by reference.


“reference是一种漂亮的pointer”

reference 通常不用于声明变量,而用于 参数类型(paramters type) 和 返回类型(return type) 的描述。
const 也是函数签名的一部分// 这两个函数的签名不同
void m_func(const int&) {}
void m_func(const int&) const {}

这里的结果和“面向对象(上)”里实验的结果一致。


范型通过指针实现,是因为指针的内存大小都相同。 而在继承体系中,不同类的占用内存大小不一定相同。
动态绑定(多态、虚机制)的三个条件:
指针调用
指针向上转型 (up-case)
调用的是 virtual function


这里通过对象调用,而非通过指针,因此这里是 static binding, 调用了父类的函数,而非 dynamic binding.


函数重载 不必考虑函数的返回值,只考虑函数签名(非返回值的部分)。
char ch = s[5]; 和 s[5] = 'a'; 分别调用 const函数 和 非const函数.
常量字符串 只调用 const函数, 不必考虑 COW.
new Obj() 是 new expression, 会调用 operator new() 来进行内存分配.

这些函数由编译器调用。

class Foo {
public:
void* operator new(size_t t) {
std::cout << "in overloading operator new()" << std::endl;
return ::operator new(t);
}
void operator delete(void* ptr, size_t t) { // the second parameter size_t is optional
std::cout << "in overloading operator delete(void*, size_t)" << std::endl;
::operator delete(ptr, t);
}
void operator delete(void* ptr) {
std::cout << "in overloading operator delete(void*)" << std::endl;
::operator delete(ptr);
}
};
int main() {
Foo* p = new Foo();
delete p;
}
in overloading operator new()
in overloading operator delete(void*)

new, new[] 完成三件事情
调用 malloc 分配内存(这一步由::operator::new(size_t)完成),
再调用构造函数创建对象, A::A()
返回指针
delete, delete[] 完成两件事 调用析构函数(~A()),再释放内存(operator delete(a))
可以通过 ::new , ::delete 来绕过 member operator new/delete, 来使用全局默认的 new & delete





