关于C++类中访问权限的若干疑问(虚函数访问权限)

下面这样一个程序:(以下程序都是在VS2010下实现)

  1: class Base
  2: {
  3: public:
  4: 	virtual void func()
  5: 	{
  6: 		cout<<"Base virtual func"<<endl;
  7: 	}
  8: };
  9:
 10: class Derived: public Base
 11: {
 12: private:
 13: 	virtual void func()
 14: 	{
 15: 		cout<<"Derived virtual func"<<endl;
 16: 	}
 17: };
 18:
 19: int main()
 20: {
 21: 	Base *pb = new Derived();
 22: 	pb->func();
 23: 	cin.get();
 24: }

注意第3行和第12行,派生类访问权限为private,但程序输出的结果是:

说明指针pb还是通过多态实现了访问派生类虚函数func。

 

到底是怎么回事呢?派生类明明是private却还是可以访问?

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

再看一个例子:

class CTest
{
public:
    explicit CTest(int prii=111,int proi=222,int pubi=333)
    {
        m_prii=prii;
        m_proi=proi;
        m_pubi=pubi;
    }
    void show() const
    {
        cout<<"m_prii="<<m_prii <<" m_proi="<<m_proi<<" m_pubi="<<m_pubi<<endl;
    }
private:
    int m_prii;
protected:
    int m_proi;
public:
    int m_pubi;
};

int main()
{
    CTest tobj;
    tobj.show();
    int *pi= (int *)(&tobj);//*****注意强制转换,如有疑问参读《inside C++ object model》
    cout<<"&tobj="<<&tobj<<" \n";//tobj对象本身的地址
    cout<<pi<<"="<<*pi<<" ";//pi的地址和指向的值
    pi++;
    cout<<pi<<"="<<*pi<<" ";//pi+1后的地址和指向的值
    pi++;
    cout<<pi<<"="<<*pi<<" "<<endl;//pi+1后的地址和指向的值
    cin.get();
}

理论上我们是无法访问CTest类m_prii,m_proi两个数据成员的,因为CTest类没有提供public数据操作接口。

上述程序输出:

我们使pi指向tobj对象,所以pi的地址和tobj对象的地址是一样的都是 0x0042FC20;该地址存放的值是111,即tobj对象的m_prii成员的值,

擦,原来即使类没有提供public接口操作private数据成员,我们一样可以通过获取类对象地址来操作私有数据成员。

通过观察上述demo结果,我们发现了两点:

1.类对象数据成员在内存中是连续存放的,和我们在类中数据成员的声明次序一样(以上结果只是MS的VS2010编译器结果)

2.从tobj对象的起始内存地址后,依次连续存放3个数据成员,中间没有什么其他数据存在(比如说用于访问权限设置的数据…);

 

原来只要通拿到了对象数据成员的内存地址,便可以随意对其进行读写,并没有什么访问权限设置。那为什么我们在程序中使用

tobj.m_prii会出现编译错误呢?

其实所谓的访问权限(public ,protected ,private)从来都只是编译器强加给我们的规定,从而使我们编写出来的程序更符合OO特性,

实际一般程序在内存中数据读取没有任何限制,只要你能拿到数据地址就能读写数据(这也是很多程序外挂的原理,通过抓到的地址直接修改内存数据)。

 

成员函数的原理则不一样了,我们知道当我们通过tobj.show()调用成员show函数时,其实编译器是把对象的this指针传给在系统内存某处的show()函数,

类似于这样_show(&tobj)调用,可见在类成员函数与对象本身并没有什么关系(即你无法通过类对象本身地址计算出该对象成员函数地址),不像数据成员

无论什么访问权限都可以通过对象本身的地址访问到。成员函数的访问权限的作用就是由编译器决定根据你定义的访问权限,给不给你该成员函数在内存

中的具体地址,如果是public则就可以在程序中通过类似tobj.show()这种由对象调用成员show函数;如果是private,则由编译器控制不给你地址(其实

该函数在内存中仍然存在,不可见不等于不存在),即private成员函数的地址在你的程序中不可见,所以你通过tobj.show()这样访问即错误。所以这一切

都是由编译器控制的,在真实内存中是不存在访问权限这种设置的,(以上讨论仅适用与C++对象对象模型在内存中的分布情况)。而且很重要的一点是编

译器所有的能耐都只能在程序编译期发挥,一旦程序进入运行时,则全权交由计算机控制,按代码指令流程一步一步执行直至over。

 

下面我们回到起始的问题来解释一下:

 21: 	Base *pb = new Derived();

22: 	pb->func();

 

在执行22行时,在编译期如果调用的是虚函数的话,编译器能做的就是判断该基类指针pb所对应的静态类型的func虚函数的访问权限,这是编译器在编译期可以做到

的事,而我们知道虚函数的动态绑定是在程序的运行时,这时候编译器已经不起作用了,所以对应pb所绑定的真正执行的虚函数(一般是派生类对应虚函数)到底是什么

访问权限,超出了编译期编译器已经无法控制,所以即使派生类虚函数是private,pb基类指针则仍可以访问该虚函数,编译器所做的就是设定一个虚函数绑定流程,

一般是根据虚函数在基类vtbl(虚函数表)中的索引(所以虚函数在基类中是public访问权限则可获得该索引,否则获取不到该索引,获取不到该索引值,则无法在vtbl中

获取真正执行的虚函数内存地址,没有函数内存地址,自然无论是基类还是派生类都是无法调用该虚函数的),找到真正绑定的派生类对象的vtbl并拿着之前获得的索

引值得到真正绑定执行的虚函数内存地址,其实这个流程是在程序真正进入内存执行的时候发生的,这时候的内存访问是不受编译器控制的,所以就没用所谓的访问

权限问题了。根据索引找到派生类虚函数内存地址就可以执行了。

 

小结:

1.数据成员访问权限的作用是:由编译器限制你编码(不可随意访问私有数据成员),从而使你写出的程序更符合OO规范。

2.成员函数访问权限的作用是:由编译器根据你设置的访问权限给不给你该成员函数的真实内存地址,即在你的程序中是否能通过该地址调用该函数。无论你设置什么

访问权限,该函数在内存永远存在,只是你无法在你的程序中访问而已。即不可加不代表不存在。

3.访问权限的作用仅在程序编译期(由编译器控制),一旦超出编译期进入执行期(程序脱离编译器控制),访问权限则消失的无影无踪(即在真实内存中没有任何访问权限设置痕迹)。

 

一句话总结:

类的访问权限设置仅在程序编译期起作用,一旦程序进入执行期(虚函数动态绑定就发生在这个时候,所以即使派生类为private访问权限),就没有访问权限控制了(这时候编译器不起作用了);

 

以上内容,纯属个人见解,如有错误,欢迎指出!谢谢

 

建议阅读书籍《inside C++ object model》

关于C++类中访问权限的若干疑问(虚函数访问权限)

时间: 2024-08-02 11:04:51

关于C++类中访问权限的若干疑问(虚函数访问权限)的相关文章

多继承时,多个基类中存在型别相同的虚函数,该怎么做?

#include "stdafx.h" #include<iostream> using namespace std; class A{ public: virtual void show(){ cout<<"in classA"<<endl; } virtual ~A(){} }; class B{ public: virtual void show(){ cout<<"in classB"<

C++继承后的虚函数访问权限

今天在写代码时发现对继承后的函数访问权限不太清楚,于是自己做了个测试: 1.头文件(test.h) 1 #include <iostream> 2 using namespace std; 3 4 class A{ 5 private: 6 void print(){ 7 cout << "this is A" << endl; 8 } 9 }; 10 11 class B:public A{ }; A为基类,B为A的子类. 2.源文件(test.c

C++中的重载,隐藏,虚函数,多态浅析

直到今日,才发现自己对重载的认识长时间以来都是错误的.幸亏现在得以纠正,真的是恐怖万分,雷人至极.一直以来,我认为重载可以发生在基类和派生类之间,例如: 1 class A { 2 public: 3 void test(int); 4 }; 5 class B : public A { 6 public: 7 void test(int, int); 8 }; 9 10 void main() 11 { 12 B b; 13 14 b.test(5);  //错误,应该b.A::test(5)

C++虚函数访问权限的改变

如果在基类中虚函数的访问权限是一种情况,那么派生类在继承基类的时候,派生类可以重新定义基类虚函数的访问权限,经过实例验证是正确的. 从这里也说明了函数的覆盖或者说重定义跟前面的访问权限修饰没多大关系 //Base.h #pragma once #include <iostream> using namespace std; class Base { public: Base(void){} ~Base(void){} virtual void fun(){cout<<"T

使用在类中定义的有返回值的函数,如何得到执行结果反馈?

我们做开发时,程序执行完后最好会有个反馈结果,尤其是在庞大的程序中,如果有bug可以及时发现,不然很浪费时间...最近做sde的二次开发,创建数据集时要用到带有返回值的函数,因为在数据集上创建要素类需要用到这个数据集,声明一个全局变量,可以直接用这个返回值.但是在捕获错误方面受到了限制,因为有返回值的函数每一个节点都要有返回值,想直接得到反馈是不行的,想在方法执行后写结果反馈的代码是检测不到的. 只能向师傅求救,给我提供了两种方法解决,感觉很巧妙. 原来的代码: Form1中的代码: priva

面向对象程序设计——概述,定义基类和派生类,虚函数

一.OOP:概述 面向对象程序设计的核心思想是数据抽象.继承和动态绑定.通过使用数据抽象,我们可以将类的接口和实现分离:使用继承,可以定义相似的类型并对其相似关系建模:使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象. 1)继承 通过继承联系在一起的类构成一种层次关系.通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类.基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自的成员. 在C++语言中,基类将类型相

C++——派生类中的访问——可见性问题

C++中派生类对基类成员的访问形式主要有以下两种: 1.内部访问:由派生类中新增成员对基类继承来的成员的访问. 2.对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问.今天给大家介绍在3中继承方式下,派生类对基类成员的访问规则. 1.私有继承的访问规则 当类的继承方式为私有继承时,基类的public成员和protected成员被继承后成为派生类的private成员,派生类的其它成员可以直接访问它们,但是在类的外部通过派生类的对象无法访问.基类的private成员在私有派生类中是不

MFC如何在CMainFrame类中访问CxxxView视图类中的成员

在视图类中,我们可以通过调用AfxGetMainWnd()函数得到框架窗口的指针. 注意,需要在view类实现部分添加#include "MainFrm.h". 1 CMainFrame* pMFram = (CMainFrame*)AfxGetMainWnd(); 如果想在框架窗口中想调用CxxxView类中的函数,也需要得到相应View视图类的指针. 但是需要注意,这里不仅得包含xxxView.h,还需要包含xxxDoc.h头文件, 还需要注意头文件的顺序,先包含Doc.h,再包含

C++类中的static数据成员,static成员函数

C++类中谈到static,我们可以在类中定义static成员,static成员函数!C++primer里面讲过:static成员它不像普通的数据成员,static数据成员独立于该类的任意对象而存在,每个static数据成员是与类关联的对象,并不与该类的对象相关联!这句话可能比较拗口,其实可以这么理解:每个static数据成员可以看成是类的一个对象,而不与该类定义的对象有任何关系!下面我们就来具体看看类中的static数据成员! 谈到数据成员,我们最先想到的应该是怎么去定义一个static数据成