C++程序员应了解的那些事(55)静态成员函数没有this指针,不与类的实例(对象)“挂钩”【即不能声明为const、volatile、virtual】

//来自对《深度探索C++对象模型 侯捷译》的一点总结、体会。
        静态成员函数没有this指针,不与类的实例(对象)“挂钩”,所以C++静态成员函数不能声明为const、volatile、virtual。那么const/ volatile /virtual这些限定符是如何通过this指针发挥功能的呢?

【1】基础:何为this指针?它有什么用?

         this指针指向类的某个实例(对象),叫它当前对象。

①举例说明:

class A
{
    int i;
    void foo ( ){
        i++;
    }
};

        void A::foo ( ); 会被编译器转化为一个外部的、非成员的、普通的函数:(实际的函数名还会包含类名与形参的编码,以区分不同的类和重载,但这里为了简单仍用foo)。

void foo ( A * this ){ // 严格的形式应是A * const this
    (this->i)++;
}

相应地,
A bar;
bar.foo(); 会被编译器转换为:foo( &bar ); // 没考虑对函数名的特殊处理(见上),下同。

②拓展,另一个带返回值的函数例子:

class A
{
    int i;
    int foo ( ){
        i++;
        return i;
    }
}bar ;
//编译器转化后如下
void foo (A * this, int& result){
    (this->i)++;
    result = this->i;
    return;
}

int a = bar.foo(); 会变成 foo( &bar, a);如果直接调用bar.foo(),会有临时变量,不考虑代码优化。

       “返回值为引用类型”跟“返回值为指针类型”的道理是一样的,引用在必要的时候是通过指针(常量指针 type * const,所以引用必须初始化,而且自始至终都代表那一个变量 ) 实现的,二者在编译器的汇编(机器指令)实现机制是一样的。“不要返回局部对象的指针(地址值)”,同理不要返回局部对象的引用。C++是面向对象的高级语言,however,本书的作者Lippman认为了解C++的实现机理对于中高级C++程序员来说是必需的。如果不知道编译器对你的代码做了什么,那么代码的效率永远都是未知数。嵌入式、硬件开发的厉害C程序员看一眼C代码,甚至都知道它在目标平台上的汇编是什么样的。这种C语言与汇编的关系,使得C语言适合某些嵌入式、驱动、操作系统等底层开发。虽然C++的设计者想在语言层面对程序员做透明化处理(比如引用),但是面对以“保证效率和兼容C的情况下提供OO”为设计目标的C++,程序员还是了解一下底层为好。

【2】多态与虚函数(C++ 的多态离不开 虚函数 和 指针(或引用),缺一不可)

class Base
{
    virtual void foo () {}
};
class Derived1: public Base
{
    void foo () {}
};
class Derived2: public Base
{
    void foo () {}
};

Base b;
Derived1 d1;
Derived2 d2;

Base * ptr = b;  ptr->foo(); // 调用的是Base的
ptr = d1; ptr->foo();        // 调用的是Derived1的
ptr = d2; ptr->foo();        // 调用的是Derived2的

b.foo();   // 调用的是Base的
d1.foo();  // 调用的是Derived1的
d2.foo();  // 调用的是Derived2的
但是:
b = d1;  b.foo();// 调用的是Base的,因为b = d1;发生了“截断”(sliced )
b = d2;  b.foo();// 调用的是Base的,因为b = d2;发生了“截断”(sliced )

        一个类如果含有虚函数,那么这个类的每个对象都会额外拥有一个编译器添加的指针(虚表指针 vptr),指向一个本类的对象所共享的一维数组(虚函数表),虚函数的地址就存放在一维数组的项(slot 槽)中(此外虚函数表还存放了类型信息,位于最开头)。【不考虑多重继承(乃至虚拟继承)这些复杂的境况,很多时候该用组合,而非继承。】仅就这种机制,父类、子类对象的不同之处在于虚函数表里存放的地址不同(类型信息也不同)。而虚函数表,一个类准备一个就好了。

       ptr->foo();会被编译器转化成:foo(ptr);  *( (ptr->vptr)[ 1 ] ) (ptr); // foo在虚函数表中的索引是1

       这样,若ptr指向的是父类对象,则ptr->vptr指向父类的虚函数表,从而调用的是父类的foo函数;若ptr指向的是子类对象,则ptr->vptr指向该子类的虚函数表,从而调用的是该子类的foo函数;这样就实现了多态。

       在执行b = d1;  时,调用copy assignment operator (拷贝赋值运算符),d1的vptr并不会赋值给b的vptr,b的vptr维持原值不变,仍然指向Base的虚函数表。

【3】const对象与const成员函数

class A
{
void foo1(){}
void foo2() const{}
};
const A a;
a.foo1();  //error
a.foo2(); // ok

        const成员函数其实就是用了const A * this指针:
        void foo2() const{}会被编译器转化为 void foo2(const A * this){} // 严格的说是const A * const this(“只读”指针与常量指针的话题这里就不说了)这样const成员函数就不能修改对象,从而const对象只调用const成员函数,不能调用非const成员函数。

【4】volatile对象与volatile成员函数

       道理与const是一样的,不再重复。将成员函数声明为 volatile 。如果一个类对象的值可能被修改的方式是编译器无法控制或检测的,则把它声明为 volatile 。与 const 类对象类似,对于一个 volatile 类对象,只有 volatile 成员函数、构造函数、析构函数可以被调用。

【5】static

class A
{ 
    static void foo(){}
} bar;
A::foo(); // 调用static成员函数无需对象
bar.foo(); //被编译器转化成 A::foo(); 
static成员函数没有this指针,所以只能访问static成员(属于类的),不能直接访问non static成员(属于对象的)。

 

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值