======C++面向对象高级编程(下)第二周======
本页内容是作为 //Boolan// C++ 开发工程师培训系列的笔记。\\
Class Base {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int data1, data2;
};
Class Derived:Base {
public:
virtual void vfunc1();
void func2();
private:
int data3;
};
Class SubDerived:Dervived {
public:
virtual void vfunc1();
void func2();
private:
int data1, data2;
};
(*(p->vptr) [n]) (p);
我们把这个整个过程称为**虚机制**。其中''n'' 代表了虚表数组的 Index, 而这个 Index 的顺序则是由我们决定的:在类中,**越定义在前面的虚函数,在虚表中越靠前**。
\\
\\
值得注意的是,动态绑定的判定有三个条件: -
- 需要用指针调用函数。所有对象直接调用函数(比如 ''obj.func()'')这样的都是静态绑定。
- 调用函数的指针要完成 ''upcast'' 的过程,比如 ''Base* ptr = new Derived;''
- 调用的函数一定是虚函数。
===子类对象调用父类函数===
子类对象调用父类函数是通过 ''this'' 指针来完成的:
* 当新建一个子类对象,并用这个对象调用父类函数的时候, ''this'' 会指向子类对象
* 如果调用的是父类普通函数,那么调用方法为 ''call'',即 ''this->Basefunc()''
* 如果调用的是虚函数,那么 ''this'' 指针会在此完成 ''upcast'' 过程,即 ''(*(this->vptr)[n]) (this)''
====Const和成员函数====
''const'' 修饰类成员函数和修饰全局函数的写法不同:
void func() const {} // member function
我们知道,''const'' 实际上是表达了设计者的是否倾向于修改数据的意愿。成员函数也是相同的。对于成员函数,我们可以考虑下下面几种组合:
- //non-const// 对象调用 //non-const// 成员函数
- //non-const// 对象调用 //const// 成员函数
- //const// 对象调用// non-const// 成员函数
- //const// 对象调用 //const// 成员函数
纵观这四个组合,我们发现只有第三条有问题:我们声明了一个 ''const'' 对象,就是希望不修改对象内容。但随后其调用的成员函数则是''non-const'' 的,表明该函数允许修改对象内容,因此矛盾。
===额外的规则===
除开第三种组合,其余的组合从 ''const'' 的理念上都可以说的通。但需要注意的是这里仍然有例外。比如标准库 ''string'' 类的成员函数中有这么两个:
operator[](sizetype pos) const {...}
operator[](sizetype pos) {...}
在 ''string'' 类中,很可能存在多个函数对同一个字符串进行操作的情况。因此对于常量字符串,我们不需要考虑 //COW//(copy on write);但对于非常量字符串,我们则必须考虑 //COW//。
\\
\\
而我们发现,如果使用非常量字符串去调用常量成员函数,那么从函数的角度来看是不用考虑 //COW// 的,但从对象的角度上看是需要考虑 //COW// 的,这样也矛盾了。
\\
\\
因此,C++ 对此提出了额外的一个规则:**成员函数中如果同时存在常量和非常量版本,那么常量对象只能调用常量版本,非常量对象只能调用非常量版本**。
====New / Detete 中的相关函数重载====
前面我们学习 ''new'' 和 ''delete'' 的时候得知 ''new'' 和 ''delete'' 不能重载;但我们可以通过对 ''new'' 和 ''detele'' 过程中起作用的函数进行重载,从而达到对 ''new'' 和 ''delete'' 重载的效果。
===为何要重载 new / delete===
对于我们来说,''new / delete'' 的功能性我们是没有办法改变的。我们只能用 ''new / delete'' 来申请和删除堆空间和对象。我们所能改变的就是如何为对象分配内存,而这个正是重载的的意义:使用自定义的 ''new / delete'' 往往能够更有效的管理和分配内存。
===重载方式:operator new / delete===
一种重载 ''new / delete'' 的方式是通过重载 ''operate new'' 和 ''operate delete'' 这两个函数。而对于这两个函数来说,重载又分**全局重载**和**类成员重载**两种。
==Operator 全局重载==
全局重载的示例如下:
inline void * operator new(size_t size) { return myAlloc(size); }
inline void * operator new[] (size_t size) { return myAlloc(size); }
inline void * operator delete(size_t size) { myFree(ptr); }
inline void * operator delete[] size_t size) { myFree(ptr); }
==Operator 类成员重载==
类成员重载的实例如下:
class Foo {
....
static void* operator new(size_t size);
static void operator delete(void* pdead, size_t size);
/*array version*/
static void* operator new[](size_t size);
static void operator delete[](void* pdead, size_t size);
};
调用的方法如下:
Foo*p = new Foo;
delete p;
/*array version*/
Foo*pa = new Foo[n];
delete [] pa;
类中重载 ''operator new / delete'' 需要注意几点:
* 参数中的 ''size_t'' 代表了对象数组的大小;在普通版本的 ''new'' 和 ''delete'' 中该值为 ''1'',该参数也是可选的。
* 如果没有重载,默认调用全局函数;如果有重载,则使用类重载版本。
* 可以通过 ''::'' scope 操作符强制使用全局函数。
* ''new[]'' 创建的对象占用空间会额外多出一个计数器的空间,用于保存数组的大小。
===重载方式:new() / delete()===
C++ 中还提供了另外一种形式对 ''new'' 和 ''delete'' 进行重载://Placement// 形式。这种形式在实际的操作上也是对 ''operator new / delete'' 的重载,但是调用的形式不同:
Foo* p = new (size_t, placement_arg) Foo;
重载的写法示例如下:
void* operator new(size_t size, void* start) { return start; };
void* operator new(size_t size, long extra) { return malloc(size + extra); };
而我们也需要针对这些不同的版本写出相应的 ''operator delete'' 的重载版本:
void operator delete(void*, void*) {};
void operator delete(void*, long) {};
这类的 ''operator delete'' 用于处理没有创建成功的对象所占用的内存空间。
\\
\\
重载中有几点需要注意的是:
* 重载的第一参数类型必须是 ''size_t'' 。
* 各个重载参数的参数列表必须不同。
* 重载的 //placement// 版本的 ''operator delete'' 不会被 ''delete'' 调用;只有在 ''new'' 调用的构造函数抛出异常的时候,才会调用,用于归还未能完全创建成功的对象所占的内存。
* 如果没有对应的 ''operator delete'',编译器也不会报错;这只代表你告诉编译器放弃处理该异常。