C++获取private的变量-偷走private
private提供了对数据的封装,使得private成员只能被类自身的成员函数以及类的友元访问,其他的函数或者类想要访问private成员只能通过该类所提供的set和get的方法进行访问,
或者返回其指针或引用(effective C++中提到过要避免返回对象内部构件的引用,指针,或迭代器。这样会提高封装性,帮助 const 成员函数产生 const 效果,并将悬空句柄产生的可能性降到最低,所以但这个方法并不是特别的好)
但如果你想获得一个类的private成员,但是该类的已经在项目被大量的使用,或者是因为其他的原因,你没有办法添加get和set方法时,又应该如何获得该类的private成员呢?
我总结出了以下几种方法
方法一:重定义
#define private public
示例
A.h定义
#pragma once
class A
{
int j;
public:
A() :i(10), j(20)
{
}
template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}
private:
int i;
};
main.cpp
#include <iostream>
#define private public
#include "A.h"
using namespace std;
int main()
{
A a;
cout << a.i << endl;
//cout<<a.j<<endl;无法获得
system("pause");
return 0;
}
该方法的优点的是简单但也有不少的缺点
1.如果在类的定义时不指定访问标号关键字(public,protected,private),使用默认的private访问限制,那么该方法就无法达到目的了,比如这里的j就无法获得
2.降低代码的可读性,改变的一个关键字的意义,没有注意到这一点的程序员会产生困扰
3.将所有使用了private访问的标号的成员的访问等级都变成了public,降低了数据的封装性
方法二:模拟内存法
A.h定义
#include <iostream>
#include "A.h"
using namespace std;
int main()
{
A a;
void *p = &a;
cout << "j:" << *(int*)p << endl;
cout << "i:" << *((int*)p+1)<< endl;// *(int*)((char*)p+4)
system("pause");
return 0;
}
C++标准中要求,在同一个访问区域中,成员的排列只需符合较晚出现的成员在类的成员中有较高的地址即可,成员之间可能会因为数据对齐所需,添加一些字节
目前各编译器都是吧一个以上的访问区域连在一起,安装声明的顺序成为一个连续的区域
所以类A的一个对象的内存布局类似于这样:
指针p指向j,将p加上一个int长度或者4个char长度就可以指向i了
但这个方法的缺点也很明显,需要程序员自己对类的内存布局有着较强的了解,考虑到数据对齐,虚函数,不同编译器的实现等等方面
比如以下两种情况
1、数据对齐
A.h定义
#pragma once
class A
{
char j;
public:
A() :i(10), j(20)
{
}
template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}
private:
int i;
};
char j占用了一个byte,而i为了数据对齐,在内存布局上并不是与j紧挨着的,而是隔了3个byte,
所以获得i和j的间隔与上一个一样,只是j的类型变了
#include <iostream>
#include "A.h"
using namespace std;
int main()
{
A a;
void *p = &a;
cout << "j:" << *((char*)p) << endl;
cout << "i:" << *((int*)p+1)<< endl;
system("pause");
return 0;
}
2.加入虚函数
A.h定义
#pragma once
class A
{
int j;
public:
A() :i(10), j(20)
{
}
virtual void show()
{
}
template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}
private:
int i;
};
编译器为了支持虚函数,会在类的每一个对象中,产生一个额外的虚函数指针指向相应的虚函数表,不同的编译器对这个指针处理不同,有点将它放在了类对象的尾端,有的将它放在了类对象的开始处
vs2013将它放在了类的开头处
所以类A的一个对象的内存布局应该类似于这样:
需要将p加上4个字节后才能指向j
#include <iostream>
#include "A.h"
using namespace std;
int main()
{
A a;
void *p = &a;
cout << "j:" << *((int*)p+1) << endl;
cout << "i:" << *((int*)p+2)<< endl;
system("pause");
return 0;
}
所以如果虚函数过多,又加入了虚继承, 类里面又有大量程序员自己定义的类型,那么该方法就会很麻烦了。
方法三:李代桃僵
A.h的定义
#pragma once
class A
{
char j;
public:
A() :i(10), j('b')
{
}
virtual void show()
{
}
template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}
private:
int i;
};
李代桃僵法是模拟内存布局的另一个实现方式
我们看到现在A里有一个虚函数,一个j和一个i
如果直接使用模拟内存法的话会很麻烦
所以我们可以另声明一个对象B,它的内存布局和A的一样,只是i和j的访问限制变成了public
这样我们可以把一个指向A的对象的指针当做一个指向B的对象指针来使用
#include <iostream>
#include "A.h"
using namespace std;
class B
{
public:
char j;
public:
B() :i(10), j('b')
{
}
virtual void show()
{
}
/*template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}*/
public:
int i;
};
int main()
{
A a;
B *b = (B*)&a;
cout<<"j:" << b->j << endl;
cout<<"i:" << b->i << endl;
system("pause");
return 0;
}
非虚成员函数show放在函数段中,并不在类对象的布局中占用空间,所以有没有show函数都可以
因为B的对象的内存布局与A一样,只是访问限制不同,所以可以利用对B对象的规则去访问A的对象
一个指向B对象的指针实际指向了一个A对象,对B中j和i的访问实际上是对A对象中i和j的访问
该方法模拟内存法容易了很多,但你需要额外声明一个B对象的定义,而且必须要确保B对象的内存布局要与A对象的一致
方法四 特化函数模板法
a.h的定义
#pragma once
class A
{
char j;
public:
A() :i(10), j('b')
{
}
virtual void show()
{
}
template<class Type>
void show(Type t)
{
cout << "Hello" << endl;
}
private:
int i;
};
这里我们发现A有个函数模板show,所以我们可以利用对函数模板show进行特化的方式合法的获得i和j的public访问权限
#include <iostream>
#include "A.h"
using namespace std;
class B
{
};
template<>
void A::show(B b)
{
cout << "j:"<<this->j << endl;
cout << "i:" << this->i << endl;
}
int main()
{
A a;
a.show(B());
system("pause");
return 0;
}
该方法合理,简单,但也有缺点就是相应的类必须要有成员模板,并且该模板的访问限制为public才可以
总结
方法 |
优点 |
缺点 |
可移植性 |
---|---|---|---|
重定义 |
简单 |
1.如果在类的定义时不指定访问标号关键字(public,protected,private),使用默认的private访问限制,那么该方法就无法达到目的了,比如这里的j就无法获得 2.降低代码的可读性,改变的一个关键字的意义,会没有注意到这一点的程序员照成困扰 3.将所有使用了private访问的标号的成员的访问等级都变成了public,降低了数据的封装性 |
中 |
模拟内存法 |
无 |
虚函数过多,又加入了虚继承, 类里面又有大量程序员自己定义的类型时,那么该方法就会很麻烦了。需要程序员对内存布局有较深的认识 |
低 |
李代桃僵 |
简单,可能在有些人看来比较清楚 |
需要额外声明一个B对象的定义,而且必须要确保B对象的内存布局要与想要访问的A对象的一致 |
中 |
特化函数模板法 |
合理,简单 |
相应的类必须要有成员模板,并且该模板的访问限制为public才可以 |
高 |
- 构建属于自己的原生docker images
- Docker-client for python使用指南
- Ansible基本配置以及使用示例
- redis超时原因系统性排查
- overlayfs存储驱动的使用以及技术探究
- 分页解决方案 之 分页算法——Pager_SQL的详细使用方法和注意事项
- 利用虚拟硬盘(把内存当作硬盘)来提高数据库的效率(目前只针对SQL Server 2000)可以提高很多
- 分页解决方案 之 分页算法——Pager_SQL的思路和使用方法
- 让你的笔记本更快一点——我的笔记本的性能测试和虚拟硬盘(把内存当成硬盘)的使用感觉
- 分页解决方案 之 数据访问函数库——另类的思路、另类的写法,造就了不一样的发展道路。
- 分页解决方案 之 QuickPager的使用方法(在UserControl里面使用分页控件的方法)
- 分页解决方案 之 QuickPager的使用方法(URL分页、自动获取数据)
- 分页解决方案 之 QuickPager的使用方法(PostBack分页、自定义获取数据)
- QuickPager asp.net 分页控件、表单控件等自定义控件下载 和介绍 【2009.09.07更新】
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法