博览网:C++面向对象高级编程(下)第二周笔记

一、虚指正(vptr)和虚表(vtbl)

我们以下图介绍上述两者:

博览网:C++面向对象高级编程(下)第二周笔记_第1张图片

1、当类中存在虚函数就会出现虚指针vpt,无论虚函数有多少个,有且仅有一个虚函数,指向虚表(rvtbl)的地址;

2、虚表是什么呢??

我们可以将它理解为一种表格,每个表格的位置存放一个虚函数对应内存的地址;

例如:基类A中包含两个虚函数vfunc1()、vfunc2(),那么类A的对象在在内存中表现如上图a(A object),其存储类的两个基本数据:m_data1、2m_data2以及两个虚函数对应的虚指针vptr,而虚指针指向虚表的地址,虚表存放虚函数内存的两个地址:0x401ED0、0x401FD0;同理,A类的子类B,B类的子类C也有类似的原理;

将vptr实现vtbl内容翻译为C:

(*p->vptr)n;

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

3、动态绑定: 虚机制

动态绑定(dynamic binding):动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

C++中,通过基类的引用或指针调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的;

C++中动态绑定条件发生需要满足2个条件:

(1)只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不能进行动态绑定

(2)必须通过基类类型的引用或指针进行函数调用

所谓的动态类型,当引用或指针调用了虚函数时,它就是动态类型,它的行为要到程序运行时才能定义 ;

当我们用派生类去初始化基类的引用或指针后,假如调用的是非虚函数,那么这时实际调用的函数是基类的函数;假如调用的是虚函数,那么这是调用的是派生类自己定义的虚函数  下面是具体的例子来说明静态类型和动态类型

class A{

public:

virtual void show(){cout<<"j基类的show()"<

void get(){cout<<"基类的get()"<

};

class B:public A{

public:

virtual void show(){cout<<“派生类的show()”<

void get(){cout<<"派生类的get()"<

};

main:

A a;

B b;

A &c=b;

c.show();//show函数是虚函数,并且此时使用派生类的对象去初始化基类的引用,发生了动态绑定,调用的是实际类        型B的show()----"派生类的show"

c.get();//此时不满足动态绑定的条件,c是静态类型,结果是-------基类的get()

二、this指针

1、C++this指针,一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址;

注:简单点说,通过一个对象调用函数时,函数的地址就是this;

举例:

博览网:C++面向对象高级编程(下)第二周笔记_第2张图片

父类CDocument

子类CMyDoc,子类对虚函数Serialise()进行了重新定义;

通过语句myDoc.OnFileOpen()调用父类函数时,子类对象myDoc的地址即为this,上述语句可表述为:CDocument::OnFileOpen(&myDoc),&myDoc就是this,this调用虚函数,进行动态绑定,通过this->Serialize()调用了子类的虚函数,而不是父类的虚函数,this->Serialize()也可以表达为虚指针、虚表的形式,即:*(this->vptr)[n](this),这样我们就可以更好的理解this以及虚机制;

三.动态绑定

再第一章节已介绍过动态绑定,这里就不再赘述;

四、const

课件中已做了详细的介绍,我这里简单总结下,并做些衍生:

博览网:C++面向对象高级编程(下)第二周笔记_第3张图片

1、常数对象可以调用常函数;

非常数对象可以调用常函数;

常数对象不可以调用非常函数;

非常数对象可以调用非常函数;

注:当成员函数的常数版本和非常版本同时存在时(以函数重载形式出现),常数对象只可以调用常函数;非常数对象只可以调用非常函数。

2、注意的几点:

1)const一般放在成员函数后头,不放在全局函数后头, 例:void function() const { return data;} ;

2)在成员函数后面加const是属于签名, 就是当两个成员函数传参相同,那么加不加const也会被区分成两个函数. ;

3、const的其他使用方法:

1)定义常量

(1)const修饰变量,以下两种定义形式在本质上是一样的。

它的含义是:const修饰的类型为TYPE的变量value是不可变的。

TYPE const ValueName = value;

const TYPE ValueName = value;

(2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义.

extend const int ValueName = value;

2)指针使用CONST

(1)指针本身是常量不可变

char* const pContent;

(2)指针所指向的内容是常量不可变

const char *pContent;

(3)两者都不可变

const char* const pContent;

(4)还有其中区别方法,沿着*号划一条线: 如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。

五、关于New,Delete

new对象的流程不能更改,但是实现过程中的函数可以被更改.

operator new

operator delete

array new一定要array delete;

回忆前边的内容:delete 某个对象,其实质是先调用析构函数,再释放内存

六、重载::operator new, ::operator new[],::operator delete ,::operator delete[]

在全局当中:

Note: 如果你重载了全局的操作符, 所以要额外小心.

这些重载不可以被声明在一个namespace中.

//这里的函数是编译器去调用, 所以size是编译器给出.

void* operator new( size_t size )

{ return malloc(size);}

void* operator new[]( size_t size )

{ return malloc(size);}

void* operator delete(void* ptr )

{ free(ptr);}

void* operator delete[](void* ptr )

{ free(ptr);}

重载 member new , delete

在class里面重载new, delete

class foo{

public:

void* operator new(size_t size);

void operator delete(void *, size_t size); //size为可选

…….

};

那么你在:

foo *a = new foo;

delete a;

就会调用上面重载的函数.

new[] , delete[] 也如此.

七、实例

当类中重载了new , delete , 而又想调用全局的new , delete

可以这样写:

::delete a;

string类内其实是一个指针.

当创建一个数组的时候, 内存当中就会多分配一个指针,该指针用于保存当前数组个数.

八、重载new(),delete()示例

允许重载成员函数new(….) 其中参数中,必须有第一个且第一个必须是size_t size. 其余参数以new所指定的placement argument为初值.

Foo* p = new(300,’c’)Foo; //这里是三个参数

我们也可以重载类成员函数 operator delete() ,写出多个版本. 但他们绝不会被 通常所使用的delete调用.只有当new所调用的ctor抛出 异常,才会调用这些重载版的operator delete(). 它们只能这样被调用,主要用来归还未能完全创建成功的对象所占用的内存.

九、Basic_String使用new(extra)扩充申请量

Basic_String在重载new()过后,传递了一个extra参数, 用于后台自动多申请extra空间。

你可能感兴趣的:(博览网:C++面向对象高级编程(下)第二周笔记)