C++学习5 对象模型

来源:互联网 发布:linux locale 修改 编辑:程序博客网 时间:2024/05/16 09:02
  • VPTR,VTBL 虚指针,虚表
  • 关于this
  • 动态绑定
  • const
  • new delete
  • 补充不能重载的操作符
  • 类型转换
    ## VPTR, VTBL 虚指针,虚表 ##
    GCC 4.9.2 64-bit
class A{public:    virtual vfunc1(){}    virtual vfunc2(){}    func1() {}    func2() {}private:    int m_data1,m_data2;};class B:public A{public:    virtual vfunc1() {}    func2() {}private:    int m_data3;};class C:public B{public:    virtual vfunc1() {}    func2();private:    int m_data1,m_data4;};

其对象模型为这里写图片描述
指针调用虚函数均是通过虚表来调用的(可以查看GCC4.9.2 汇编确定),虚表是构造函数构造时产生的。所以指针调用虚函数均可看作是动态绑定的。

关于this

例子1template method

//Application FrameworkCDocument::OnfileOpen(){...Serialise();...}//Applicationclass CMyDoc:public CDocumet{...virtual Serialize() {...}};main(){CMDoc myDoc;...myDoc.OnfileOpen();}

父类成员函数调用一个虚函数,而此虚函数的具体实现由子类决定,子类对象调用此父类函数时编译器解释为CDocument::OnFileOpen(&myDoc),此处隐式传递的this指针指向子类对象,serialize()函数调用方式为this->serialize() 故由子类的虚表中查找其定义。
总结:所有的成员函数除了static 函数都隐式传递一个this指针, 此指针不依赖于对象存在(如果运行没问题的话因为是RTTI错误要到运行时才发现)。参考下面的例子
例子2

class A {public:    virtual printA() {    cout <<(int*) this <<endl;    cout <<"class A printA" << endl;    }    printB() {    cout << (int*) this <<endl;    cout << "class A printB" <<endl;    }};int main(){    A* a = NULL;    a->printA();//wrong    a->printB();//can work}

结果是a->printA() 崩溃。a->printB() 可以运行
输出0x0, class A printB.
原因 如上所述 编译器在编译时会静态绑定每一个非虚函数(无论指针还是直接调用),和直接调用的虚函数,所以此处this = 0x0 被传递给printB()。但是 调用printA() 出错是因为此处需要动态绑定,要通过虚表才能知道printA() 地址, 但是虚表必须由构造器来构造所以此处没有产生实例对象无法构造虚表,故崩溃。

动态绑定

条件
1, 指针调用
2,up-cast
3,虚函数

条件2待定。。。先定义动态绑定 如果是RTTI ,即运行时通过虚表来确定,则2不需要。(根据GCC 4.9.2 汇编得到)
补充: 
多态是不是只能public 继承?
是的, protected and private 继承子类对象均无法access 父类。

const

const 对象只能调用const 成员函数原因
这里写图片描述

new 与delete

回顾new delete 步骤
new
1. 调用operator new 分配内存
2.类型转换 将1 产生的void* 类型转换为 类类型。
3.调用类构造函数。
delete 步骤
1.调用析构函数
2.调用operator delete 释放内存。

  1. 重载全局new delete, ::operator new ::operator delete ::operator new[] ::operator delete[]
    不能被声明于一个namespace 内。

2.重载member operator new/delete new[]/delete[]

class Foo {public:    void* operator new(size_t);//size_t 必须加    void operator delete(void*,size_t);//size_t 可选};Foo* p = new Foo -->try {    1.void* mem = operator new(sizeof(Foo));    p = static_cast<Foo*>(mem)    2.p->Foo::Foo();}delete p; -->1.p->Foo::~Foo();2.operator delete(p);class Foo {public:    void* operator new[] (size_t);    void operator delete[](void*,size_t);};Foo* p = new Foo[N];-->try{    1. void* mem = operator new (sizeof(Foo)*N + 4);//多出来4个Byte用来记录数组N大小。    2. p = static_cast<Foo*>(mem);    3. p->Foo::Foo();//N 次}delete[] p;-->1.p->Foo::~Foo();//N 次2.operator delete(p);

补充若想强制调用全局new/delete跳过自定义部分可以使用::new/::delete.
我们可以重载class memeber operator new() ,前提是每个版本都有独特的参数列,其中第一个必须是size_t,其余参数以new 所制定的placement arguments 为初值。出现于new(…)小括号内的便是所谓的placement arguments。
Foo* pf = new(300,’c’)Foo;
我们也可以重载class member operator delete(),但只有当new所调用的ctor抛出异常时才被调用,主要用于归还未能创建object 所占用的空间。

不能重载的操作符

. (成员访问或点操作符)
?: (三元操作符或条件操作符)
:: (域操作符)
.* (指向成员的指针操作符)
sizeof (取对象大小操作符)
typeid (对象类型操作符)
不具备重载的特征
->*可以被重载
1 并不是所有的操作符都能被重载。除了. ,.* ,:: ,? : ,sizeof,typeid这几个运算符不能被重载,其他运算符都能被重载
2 重载不能改变该运算符用于内置类型时的函义,程序员不能改变运算符+用于两个int型时的含义。
3 运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。这种规定可以防止程序员运用运算符改变内置类型的函义。
4 重载不能改变运算符的优先级。
5 重载不能改变运算符的结合律。
6 重载不能改变运算符操作数的个数。比如+需要两个操作数,则重载的+也必须要有两个操作数。
参考http://blog.csdn.net/wuyuan2011woaini/article/details/9407933

类型转换

C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是:TYPE b = (TYPE)a,但是c 风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向非 const对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c语言风格的类型转换没有区 分这些。还有一个缺点就是,c风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺点,引进了4种类型转换操作符(C++风格的强制转换其他的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的。):

1、static_cast:可以实现C++中内置基本数据类型之间的相互转换,enum、struct、 int、char、float等。它不能进行无关类型(如非基类和子类)指针之间的转换。

int c=static_cast(7.987);

如果涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。

class A
{};
class B:public A
{};
class C
{};

int main()
{
A* a=new A;
B* b;
C* c;
b=static_cast(a); // 编译不会报错, B类继承A类
c=static_cast(a); // 编译报错, C类与A类没有任何关系
return 1;
}
2、const_cast: const_cast操作不能在不同的种类间转换。相反,它仅仅把一个它作用的表达式转换成常量。它可以使一个本来不是const类型的数据转换成const类型的,或者把const属性去掉。
3、reinterpret_cast: (interpret是解释的意思,reinterpret即为重新解释,此标识符的意思即为数据的二进制形式重新解释,但是不改变其值。)有着和C风格的强制转换同样的能力。它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。不到万不得已绝对不用。
4、dynamic_cast:
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置的基本数据类型的强制转换。
(3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。
(4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见

0 0
原创粉丝点击