conversion function

转换函数

non-explicit-one-argument ctor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class Fraction

{
private:
int m_numer;
int m_denom;

public:
Fraction(int num, int den = 1) : m_numer(num), m_denom(den) {}
operator double() const { return (double)(m_numer / m_denom); }
Fraction operator+(const Fraction& f) {
return Fraction((f.m_numer + 1) * this->m_denom, f.m_denom * this->m_denom);
}
~Fraction();
};

Fraction ::~Fraction() {}

int main() {
Fraction f(4, 2);
double d = f + 4;//编译出错,二义性,因为编译器不知道应该转f 还是转换 4
double c = 4 + f;
cout << d << endl;
cout << c << endl;
Fraction e = f + 4;//加了explicit 关键字后,默认转换Fra为double,编译器就不会再调用构造函数来进行转换了,同时的这里也会报错,double 无法转换得到 Fra类型
Fraction g = 4 + f;
return 0;
}

编译器会寻找转换函数,不存在,存在多个都会报错

point like classes 仿指针的类

智能指针

我们希望指针能够更加的智能,能做更多的事情,C++11中继而出现了智能指针概念

成员模板中在只能指针也有应用到,为了模拟父类指针能够指向子类

因为在继承关系中,父类的类型确实是可以被子类的值,赋值,就想动物类中可以的值有人,猫,狗各种值一样

需要注意的是 -> 这个符号是可以持续作用的(在重载该符号时,并不会被消耗

容器的对应的迭代器,实际上也可以称为智能指针

其中++ —的遍历操作的实现就是重载运算符函数

function-like classes,仿函数

标准库会继承奇特的base classes

namespace经验谈

不同的测试函数,类,全局变量使用不同的命名空间,作为分隔区

互不干扰

specialization

模板特化(模板泛化的反面)

作用就是特殊的类型如果有需要,再指定特殊的具体讨论

partial 模板的偏特化 (局部特化

  • 个数上的偏特化
    • 最小类型的指定
  • 范围上的偏特化
    • 指定范围为指针的类型无论

模板的模板参数,需要注意的是假如模板中有多个模板参数,(定义对象会出现问题,一个坑,编译器无法编译,解决方法不同)

使用的是不是模板模板参数实现的对象,观察的是:调用时,是否所有的模板参数都指定了类型,如果有模糊地带,换句话而言,就是是否还存在你想让编译器来帮你推导的数据类型。

C++标准库

吐槽

侯捷老师强烈建议,使用STL标准库,我个人认为,这种说法是不适合当前的面试的情况的,因为遇到了大部分,需要手撕链表二叉树哈希表等面试题目,平时也在刷leetcode等题库,可以体会到,当前的面试要求是,调用标准库算法,都是人下人(极端的说,不过确实有人说脚本小子,库小子的蔑称传开

在开发的时候当然还是要用标准库,毕竟标准库的算法并不是为面试提供的,而面试之后,你才能加入公司进行开发工作,而你写的手撕代码的实现,在开发中却很可能就完全用不到,调个库几乎就可以完成任务了,所以就会给人一种经典面试造火箭,工作扭螺丝的感觉。

学习标准库

第一步当然是都要调用,知道其用法

常用容器,常用,甚至不常用的算法,至少调用过一次

variadic templates

数量不定的模板参数

c++11中的语言特性

1
2
3
4
5
6
7
8
9
10
11
12
13

void print() {}
template <typename T, typename... Types>
void print(const T& fristArg, const Types&... args) {
cout << fristArg << endl;
cout <<"当前还没执行的参数"<<sizeof...(args)<<endl;
print(args...);//无所谓的处理方式,这里使用的递归
}

int main() {
print(12, "hellp", "skdjskd");
return 0;
}

需要注意的是…的位置,不同位置是不同的前后出现次序

随之更新的标准库中有很多都用到了这个新语法(新是相对98而言的),如今c++11距离至今已经11年了,c++20 加入了协程等(稍微了解了一下),和其他很多新特性,但是目前国内c++11居然还是主流(不好说。

c++11的语法糖

auto 实际上就是一个智能的指针

写迭代器的时候懒得写这么长(当然你还是得会写,这是C++程序员的基本素养所在)

用auto可以代替,类型推导都交给auto来处理,偷懒。

需要注意的是,推导的前提是得有值给编译器作为依据。

有些人干脆认为全都用auto好了,那你为啥不直接用PHP捏,写js全写any不就完了呗,开发都是用shell来写,因为shell直接就一种数据类型。(还是那句话:这是C++程序员的基本素养所在

for的新语法

主要是针对容器的遍历

语法模板

1
2
3
for(decl : coll){
statment
}

使用范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
vector<char> strs;
int i;
for (i = 0; i < 4; i++) {
strs.push_back('A' + i);
}
for (auto& str : strs) {
cout << str << endl;
}
for (auto& str : strs) {
str += i;
}
for (auto& str : strs) {
cout << str << endl;
}
return 0;
}

reference

引用

在没有看侯捷老师的课程之前

引用在我的脑子里等同于 指针常量,在编译器中也是将引用当指针来看待

但是实际上,并不是的(尽管作用相同),逻辑上讲,引用相当于别名

别名和原名在调用sizeof函数调用中是一样的大小(这是一种假象,编译器制造出来的)

ps:java中的所有变量都是reference

而指针常量呢?

1
2
3
4
5
6
int x;
int* const a = &x;
int& b = x;
int* const c = &a;
int& d = b;
//修改a

编译器中

1
2
3
4
5
6
7
8
9
10
11
int test(const int& num) const{}
//等价
int test(const int num) {}
//为了对应编译器的欺骗和处理指针的传值矛盾
//再根据重载条件
//所以这两者是不能并存的
但是以下的情况是可以并存的的,const也算函数签名声明的一部分
int test(const int& num) const{}
int test(const int& num) {}
//不打注释表示这个是更重要的东西

总的来说,能用引用就引用,省事,还快,一般用到参数传递传出的描述。

对象模型

Vptr 和 vtbl

虚指针

虚基表

子类继承父类时

父类出现虚函数,子类一定也会继承得到虚函数

子类中调用虚函数的形式时,

通过的对象的指针p再找到继承的来的虚指针vptr,通过虚指针找到虚基表vtbl,再找表中的第n个虚函数的地址进行调用。

1
(* p->vptr[n])(p);

这就是动态绑定

也就是常说的的多态。

只要基础硬得跟石头一样,在进行编程时才会知其然所以然

关于this

对象的函数的调用,在编译器看来

1
2
3
4
5
6
7

MYCLASS::funtion(&myclass);
//等价
this = &myclass;
this->function;
//如果是虚函数,再结合动态绑定
(*this->function[n])(this);

关于const

当成员函数中,const存在和non-const也存在。const 对象只会调用const的函数(防止了二义性的问题

相应的non-const object 只能调用non-const函数

我理解为编译里的读写锁机制

设计一个类,根据需求,实现功能,都要好好考量,每一个成员函数是否需要const修饰

(const就是算签名的一部分)

标准库的string应用的引用计数

总结起来就是八个字:就是读时共享,写时复制

COW ,在fork的进程中的对全局变量的处理也是相同的思想

关于new. delete

内存池中需要考虑到用到

重载delete 和 new和delete[],new[];

由于这两个函数(运算符)其实是全局的函数,把它们当运算符看也没问题,之前有说过了,new实际上是调用的malloc后构造

而delete 是调用析构后再进行free.

一般定义限定在任何一个对象的命名空间中,但是需要static修饰

既然是重载,肯定是有自己的想法的,说明单纯的new和delete已经满足不了

就衍生出了,带参数的new 和 delete

需要注意的是

带参数的new 不一定需要再写对应的delete

当这个带参数的new在执行构造抛出错误时才会调用对应的delete