本系列相关链接:
【侯捷】C++11
包含泛型编程、对象模型,共有25小节;
(
学习视频观看顺序如下,第一行为前序号,第二行为后序号:
14 17 22 24 18 21 16 19 20 26 35 34 28 36 25 27 33 32 30 29 31 23 37 38 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 19(1) 20 21 22 23 24
)
关注内容如下,其中c++11的部分见另一个博客;
其中泛型编程即模板技术;
对象模型包括this指针、虚指针、虚表、虚机制、虚函数及多态;
class转换出去:
数据类型转换操作符的重载,没有返回值类型,通常末尾加const;
转换进class来:?
构造函数;
non-explicit-one-argument ctor,只有一个无缺省值的构造函数,如果不加explicit ,则可能发生自动类型转换继而调用构造函数;
如果不想允许,则需要加explicit;
例如,智能指针、迭代器;
也就是必须由*操作符、->操作符;
template<class T>
class shared_ptr{
public:
T& operator*() const { return *px; }
T* operator->() const { return px; }
shared_ptr(T* p) : px(p) { *pn = 0; }
private:
T* px;
long* pn;
};
另外,操作符->有特殊功能,也就是会继续指下去;
除了*操作符、->操作符,还需要设计++、–等若干操作符;
T& operator*() const { return (*node).data; }
T* operator->() const { return &(operator*()); }
也即仿函数;
也就是能接受小括号()操作符的class,可以带形参;
标准库中的仿函数,都会使用奇特的base classes(没有数据,size=1虽然理论上是0),例如unary_function、binary_function;
template<class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
Namespace
防止名称冲突;简单,不说了;
与普通类可以相互继承,共有4种组合方式;
编译器会对function template进行实参推导,其中&、const的推导类似auto,比较复杂;
即类模板中的函数模板;
把一个由Derived1和Derived2构成的pair,放进一个由Base1和Base2构成的pair中,可以;
反之,不可以;
为了实现这个,就要在构造函数中用到成员模板;
再例如,智能指针为了实现up-cast,也需要用到成员模板技术;
有特化版本时,调用时优先调用特化(specialization)版本;
偏特化,partial specialization;
template template parameter,也就是模板参数本身又是一份模板;
非常常用,例如标准库:
template<typename T, template<typename T> class Container>
class XCls{
private:
Container<T> c;...
};
template<typename T>
using Lst = list<T, allocator<T>>; //容器需要接收2个模板参数
///
XCls<string, Lst> mylst1;
// XCls mylst2; //error
语法糖,数量不定的模板参数;也就是将一包拆分成一个+剩下一包;
注意**"…"**的三个位置,没有道理可言,是规定;
**sizeof…(args)**可以获悉一包到底是几个;
void print(){}
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args)
{
cout << firstArg << endl;
print(args...);
}
自动类型推导;
list<string> c;
auto ite = find(c.begin(), c.end(), target);
//
// auto ite; 这两行出错,这样编译器无法知道ite到底推导成什么类型
// ite = find(c.begin(), c.end(), target);
基于范围的for遍历;
vector<double> vec;
for(auto elem : vec) { cout << elem << endl; } // pass by value
for(auto& elem : vec) { elem *= 3; } // pass by reference
对象和其引用的大小相同,地址也相同,不过这些都是编译器可以制造的假象;
引用一旦被定义,之后再也不能指向其他人;
引用可以传递;
引用的常见用途:传参、传返回值;
注意:形参的引用不属于函数签名,例如
double imag(const double& im) {...}
double imag(const double im) {...} // 不可并存,否则调用的时候编译器不知道你想调用谁
那么,函数末尾的const属于函数签名吗?属于,是!!
虚指针与虚表;正是它们的配合,才实现了强大的“多态”。
多态的必要条件:
所谓“继承函数”,继承是函数的调用权,而不是函数的内存;
带虚函数的class包含一个虚指针,虚指针指向一个虚表,虚表中有若干指针,分别指向各自的虚函数。
通过一个对象调用函数,对象的地址就是this指针;
涉及的设计模式叫“Template Method";
B b;
A a = (A)b;
a.vfunc1(); //静态绑定,调用A::vfunc1;
A* pa = new B;
pa->vfunc1(); //满足三要素,动态绑定,调用B::vfunc1;
pa = &b;
pa->vfunc1(); //同上,满足三要素,动态绑定,调用B::vfunc1;
const object只能调用const成员函数;
non-const object既能调用const成员函数,也能调用non-const成员函数;
当成员函数的const和non-const版本(overloading,重载)同时存在,const object只会调用const版本,non-const object只会调用non-const版本;
例如,标准库中string的[]操作符,如果多个string共享一个内存,当使用[]时用户可能是访问、也可能是修改。如果想修改的内容是共享的,必须用"Copy-On-Write"即"COW",也就是单独拷贝一份再修改,而不影响其他string。
此时,需要两个版本的[]函数,一个是const函数,不必考虑COW;另一个是non-const函数,需要考虑COW;这样可以大大提高效率。
new表达式实际有3个步骤,其中通过调用operator new() 最终调用mallloc()申请内存;
delete表达式实际有2个步骤,其中通过调用operator delete()最终调用free()释放内存;
new/delete表达式不可改变,而内部调用的operator new/delete()操作符可以重载;用于设计内存池,自己管理内存。
重载全局new/delete/new[]/delete[]的影响无边无际,危险!
重载class自己的成员操作符,ok。
也就是允许带额外参数的operator new();其中第一个参数必须是size_t;
Foo* pf = new(300, 'c')Foo;
也可以重载class member operator delete(),写出多个版本,分别对应各自的兄弟placement new()。但它们绝不会被delete调用。只有当new所调用的ctor抛出异常,才会调用这些重载版的operator delete()。它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory。
basic_string需要做引用计数的一个头部,再加实际的string字符串内容,所以需要悄悄摸摸地申请额外的内存。
完结撒花!