一、导读
目标:
书籍推荐:
二、转换函数
2.1 conversion function,转换函数
示例:Fraction
转换为double
double
函数不可以有参数;没有返回类型;函数名称一定是operator type()
;只要合理,一个类可以设计多个转换函数。
编译器在编译上面的代码时,在处理这行代码:
double d = 4 + f;
的时候,会先查看是否有写函数Fraction operator+(double, Fraction&)
,目前的代码中没有这个函数,于是编译器就另寻他路,看是否能将f
转换为double
,就会到代码中查看是否设计了转换函数,然后就发现了operator double()
这个转换函数。于是编译器就将f
转换为 3/5=0.63/5 = 0.63/5=0.6,所以最终的结果 d=4.6d = 4.6d=4.6。
正确的代码应该如下:
/*************************************************************************> File Name: 001.Fraction_to_double.cpp> Author: Maureen > Mail: Maureen@ > Created Time: 三 11/17 23:52:46 ************************************************************************/#include <iostream>using namespace std;class Fraction {public:Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}operator double() const {cout << "double function is called" << endl;return ((double)m_numerator / m_denominator);}private:int m_numerator;int m_denominator;};int main(){Fraction f(3, 5);double d = 4 + f;cout << "f = " << f << endl; //0.6cout << "d = " << d << endl; //4.6return 0;}
标准库中的应用:
reference operator[]
函数要返回一个bool
值,则reference
类型中一定有个转换函数将结果转换为bool
值类型,果然,在__bit_reference
中有个转换函数operator bool() const
。
2.2 non-explicit-one-argument ctor
此处的one-argument
是指至少有一个实参,若有多个实参也可以。
示例:将double
转换为Fraction
2.3 conversion function vs. non-explicit-one-argument ctor
绿色的部分可以将 4 转换为Fraction
,黄色部分可以将f
转换为double
。这两条路径都可行,产生了歧义,所以编译器不知道应该选择哪个路径。
2.4 explicit-one-argument ctor
explicit
关键字的意思是告诉编译器,不要将 4 自动转换为4/1
。
此处的+
要将4
转换为Fraction
类型,但是因为构造函数加了explicit
关键字,所以 4 无法转换为Fraction
,因此出现错误。
百分之九十的情况,explicit
关键字出现在构造函数前。
三、智能指针
3.1 pointer-like classes,关于智能指针
将一个class
设计出来像一个指针或者函数。
sp->method()
这行代码,->
作用于sp
,即调用了T * operator->()
函数,得到了px
,而->
得到的结果如果要继续执行下去就要继续调用->
,这是 C++ 中->
的特性;智能指针类中一定有指针类型的变量,且一定要写操作符*
和->
的函数,写法一般都是如上所示。
3.2 pointer-like classes,关于迭代器
标准库中的链表:
迭代器也是智能指针,但是相比较于shared_ptr
中的操作符重载的写法,有所变化;迭代器不仅要实现操作符*
和->
,还要实现其他的操作符。
四、仿函数
4.1 function-class classes,所谓仿函数
设计一个class,让它像一个函数。
4.2 标准库中的仿函数的奇特模样
4.3 标准库中,仿函数所使用的奇特的 base classes
五、namespace经验谈
六、模板
6.1 class template,类模板
类模板使用的时候需要指定类型6.2 function template,函数模板
此处的template <class T>
中的class
,可以修改为typename
;使用函数模板的时候不需要指定类型;模板会编译一次,实际使用的时候会再编译一次;6.3 member template,成员模板
问:把一个由鲫鱼和麻雀构成的pairt
,放进(拷贝到)一个由鱼类和鸟类构成的pair
中,可以吗?
答:可以。
问:反之,可以吗?
答:不可以。
6.4 specialization,模板特化
6.5 partial specialization,模板偏特化—— 个数的偏
6.6 partial specialization,模板偏特化—— 范围的偏
所谓的”范围的偏“,指的是之前的泛化版本是任意类型,而偏特化版本是指针类型;C<string> obj1;
使用的是泛化版本;C<string*> obj2;
使用的是偏特化版本,因为是指针类型。6.7 template template parameter,模板模板参数
容器中的类型未定,容器本身就是个模板;容器还有其他模板参数,平时没有写是因为有默认值; 此处的两个×不是模板模板参数有错,而是智能指针unique_ptr
和weak_ptr
的特性导致此处的错误。这不是 template template parameter
使用第二模板参数的时候,必须写成list<int>
,因为已经绑定了int
,不再是模糊的,所以它不是模板模板参数。
七、关于C++标准库
八、更多细节深入
九、C++2.0
9.1 了解你的编译器对C++2.0的支持度
9.2 确认支持C++11:marco __cplusplus
/*************************************************************************> File Name: test.cpp> Author: Maureen> Mail: Maureen@> Created Time: 一 11/22 16:23:29 ************************************************************************/#include <iostream>using namespace std;int main() {cout << __cplusplus << endl; //03return 0;}
9.3 三大主题
9.3.1 variadic templates(since C++11)
数量不定的模板参数
sizeof...(args)
可以获得pack
里的参数个数。
完整代码:
/*************************************************************************> File Name: 02.variadic_templates.cpp> Author: Maureen > Mail: Maureen@ > Created Time: 一 11/22 16:36:59 ************************************************************************/#include <iostream>using namespace std;void print() {}template <typename T, typename... Types>void print(const T& firstArg, const Types&... args) {//cout << "pack numbers: " << sizeof...(args) << endl;cout << firstArg << endl;print(args...);}int main() {print(7.5, "hello", bitset<16>(377), 42);return 0;}
9.3.2 auto(since C++11)
auto ite;
没有赋值的时候,编译器是无法推导出ite
的类型的,所以使用错误。
9.3.3 ranged-base for (since C++11)
如果要改变原来的值就使用引用。
9.4 reference
reference一定要设初值,说明它代表什么东西;自此之后,reference就不能代表其它了;reference底层有个指针指向它所代表的东西;示例:
9.5 reference的常见用途
引用的底层是用指针实现的,所以当传的数据很大的时候,通过引用传递速度更快;签名不包含 return type。十、object model
回顾:
Composition(复合)关系下的构造和析构 Inheritance(继承)关系下的构造和析构 Inheritance+Composition关系下的析构和构造
10.1 对象模型(Object Model):关于 vptr 和 vtbl
虚指针和虚表。
子类中有父类的成分;子类不仅会继承父类的数据,还会继承函数的调用权利;如果类中有虚函数,类就会多一个指针的大小,即虚指针;不管类中有多少个虚函数,都只有一个虚指针vptr
,指向虚函数表vtbl
;vtbl
中放置的是函数指针;如果父类有虚函数,子类也一定有虚函数;B
继承了A
,所以B
继承了A
的虚函数,B
中也有两个虚函数vfunc1
和vfunc2
,只是B
又重写了方法vfunc1
;“从p
到ptr
到vtbl
到 某个方法的流程” 编译器解析出来就是(*(p->vptr)[n])(p);
或者(* p->vptr[n])(p);
为了放置不同大小的形状,所以要放入指针,指向父类;希望做到:遍历容器,调用每个指针所指向的draw
;所以draw
必须是虚函数,才能实现这种功能;虚函数的这种用法叫做多态(polymorphism);
总结:C++编译器处理函数调用的时候,要考虑是静态绑定还是动态绑定。
静态绑定被编译成call xxx
,其中的xxx
是函数地址;如果符合以下三个条件就会进行动态绑定:① 必须通过指针调用;②指针是向上转型的up-cast
;③调用的是虚函数。编译器会将其编译成(*(p->vptr)[n])(p);
或者(* p->vptr[n])(p);
,具体调用的是哪个函数要看p
指向的是什么。
多态、虚函数和动态绑定其实都是一回事。
10.2 对象模型(Object Model):关于 this
通过某个对象调用函数,则该对象的地址就是this
;
上述的是模板方法(Template Method);
this->Serialize();
函数满足动态绑定的三个条件,所以编译器会进行动态绑定;
10.3 对象模型(Object Model):关于Dynamic Binding
此处的a.vfunc1()
,通过对象调用函数,所以是静态绑定; 此处的虚函数调用,满足动态绑定过的三个条件,是动态绑定。十一、谈谈const
const
只能放在成员函数的后面,不能放在全局函数的后面;const
修饰成员函数是为了告诉编译器这个成员函数不打算修改类中的成员数据的意图,请编译器帮忙把关是否违反了这个意图。const
属于签名;如果涉及到引用,可能就会有共享,为了防止被修改,就要拷贝一份进行修改,即涉及到共享,就要考虑Copy On Write
;常量字符串不必考虑Copy On Write
;十二、关于new,delete
12.1 回顾
类中的new
和delete
都可以进行重载,可用于内存池的设计;12.2 重载::operator new, ::opeartor delete, ::operator new[], ::operator delete[]
12.3 重载member operator new/delete
重载的函数会接管new
/delete
分解后的步骤中的相同函数名的函数12.4 重载member operator new[]/delete[]
12.5 示例
说明:
Foo* pf = new Foo;delete pf;
如果没有重载运算符new
和delete
的成员函数,就调用全局的operator new
;
Foo* pf = ::new Foo;::delete pf;
无论是否有重载运算符new
和delete
的成员函数,都调用全局的operator new
。
Foo with virtual dtor
的size
比Foo without virtual dtor
的size
大 4 的原因是,类中如果有虚函数,就会多一个虚指针,在32bit机器上占 4 个字节;Foo* pArray = new Foo[5]
的大小为 64, 其中多出来的 4 是计数counter,表示有5个元素,将 “array 的整包大小 + 一个计数的counter” 进行分配; 无论是否调用的是重载的new
和delete
函数,在数组的前面加一个 counter 的逻辑是不会变的
12.6 重载 new(), delete()
12.7 basic_string 使用 new(extra) 扩充申请量
标准库中placement new
的例子: