class Test{
public:
int a, b, c;
Test(int i){
a = i;
b = c = 0;
}
~Test(){
a = b = c = 0;
}
};
int Test::*v[] = {&Test::a, &Test::b, &Test::c};
void f(Test *a, int Test::*b, int c){
a->*b = c;
}
int main(){
// 输出结果为 1
cout << &Test::c << endl;
}
考研刷题时碰到的一道题中的一部分代码,我知道 Test::是类的作用域,但是这个&Test::c
是个什么东西,还有这个int Test::*v[] = {&Test::a, &Test::b, &Test::c}
又是一个什么神奇的数组,翻了半天书也没看出个所以然,上网查也不知道用什么关键词,有没有懂的大佬能帮我一下。
1
jmc891205 2020-10-25 22:38:41 +08:00
取址操作符
指针数组 |
2
rainboat OP @jmc891205 按理说 abc 都是类的成员变量,没有对象的话这些变量也是不存在的,为啥可以对其进行取址操作呀
|
3
ysc3839 2020-10-25 22:51:04 +08:00
|
4
codehz 2020-10-25 22:52:26 +08:00 2
|
5
jmc891205 2020-10-25 22:56:52 +08:00
@rainboat
A pointer to non-static member object m which is a member of class C can be initialized with the expression &C::m exactly. Expressions such as &(C::m) or &m inside C's member function do not form pointers to members. https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_data_members |
8
GeruzoniAnsasu 2020-10-25 23:18:47 +08:00 1
|
9
52coder 2020-10-25 23:22:48 +08:00
@GeruzoniAnsasu 正解,cout << &Test::c << endl;不管哪个成员都是 1,跟 v 和 f 函数没关系。
|
10
codehz 2020-10-25 23:37:52 +08:00 via Android 2
@GeruzoniAnsasu 不一定是偏移量,具体值是实现定义
考虑一下虚继承,其中任意子类拿出来都要可以->*方式访问 |
11
by73 2020-10-26 00:00:44 +08:00 1
> An rvalue of arithmetic, enumeration, pointer, or pointer to member type can be converted to an rvalue of type bool.
> 来源 https://stackoverflow.com/a/25553119 难怪会被转成 bool 。。 偏个题:你这是考语言律师资格证嘛😂 |
12
GeruzoniAnsasu 2020-10-26 01:35:44 +08:00
@by73 又是那句老话了 编译器行为学是 c++的一部分,务必品尝 doge
|
13
elfive 2020-10-26 07:57:36 +08:00 via iPhone
指向成员变量的指针这语法……真的有用到生产环境吗?
现在有个趋势,感觉会把人搞懵逼的:静态语言开始融合部分动态语言的特点,动态语言也开始慢慢吸收静态语言的部分特点 像我这种人真不是很能理解……虽然之前的语法规则也都能用 |
15
byaiu 2020-10-26 08:28:14 +08:00 via Android
@elfive 用在一些库的核心部分吧。看过 ns3,它的 attribute 的部分就用了这个,结合了 macro 和 template 实现了类似 qt 的 property 的功能。
|
16
codehz 2020-10-26 08:47:42 +08:00 via Android 2
@elfive 当然有,甚至很多属于非开源的代码库。。。
举个例子,根据逆向工程结果(实际上是符号表里直接看到的),mc 基岩版在处理指令注册的时候使用这个方法把类中的成员注册成指令的参数,这样,指令解析程序就可以在解析指令文本的时候把参数填入那个类了,不使用这个方法,你就需要写一大堆包装函数(虽然实质上确实用模板生成了他们) |
17
zsxzy 2020-10-26 09:11:57 +08:00
c++ primer 都有讲到
|
18
fps23dot9999 2020-10-26 09:32:18 +08:00
放心,考研绝 B 不会考
|
19
zzlhr 2020-10-26 09:57:27 +08:00
看看指针部分吧
|
20
codyfeng 2020-10-26 10:40:37 +08:00
越写 c++越不敢说自己会 c++
|
21
Akiyu 2020-10-26 11:40:54 +08:00
追加一些疑问和理解;
Test t; f(&t, &Test::a, 10); cout << "------------------" << endl; cout << &Test::a << endl; cout << "------------------" << endl 从上面代码汇编后的结果可以知 &Test::a 实际是 Test 中 a 的偏移. 其中 a 为 Test 首元素, 值为 0 (这点可以通过调用 f 函数时的第二个参数值获得). 但是为什么 &Test::a 打印后的值为 1 (值为 0 的话应该是 false. 或 0). 并且这个值在参数传递时固定为 1 (无论是 &Test::a, 还是 &Test::b, 或者 &Test::c. 甚至成员函数). 关于值为何为 1 我的理解是: 这些值实际的地址不可能是 0. 即使 &Test::a 为 0. 代表其偏移为 0. 但和实际的对象结合后, 其真实地址不可能为 0. 或许编译器对此做了优化. 后续: 我试过取成员函数(普通 /virtual, cv 限定, 我未定义但编译器自动生成的函数)地址. 都可以获取地址. 但其构造和析构无法获取. 关于构造为何无法获取. 我同意这个答案: https://softwareengineering.stackexchange.com/questions/245613/why-doesnt-c-allow-you-to-take-the-address-of-a-constructor 但是关于析构为何无法获取. 我未得到满意的答案. 有人知道么? |
22
by73 2020-10-26 12:05:55 +08:00 via Android
@Akiyu 看楼上有三张图的那个,你可以尝试 godbolt.org 去编译一下,会发现纯粹是编译器将 &Test::a 转换成了 bool 值。。
|
23
zxCoder 2020-10-26 12:20:47 +08:00
c++ 永远地神
代码最乱的一门语言 |
24
codehz 2020-10-26 12:23:48 +08:00 via Android 3
@Akiyu 别啥都以汇编为导向,c++这玩意实现定义的东西很多,如果按某个特定实现来学,很容易写出不可移植的东西出来(甚至有些编译器升级也会打破原有的假设)
打印为 1 的原因就是 1.编译器没有找到合适的 operator<<重载 2.然后发现存在一条隐式转换规则(空成员指针值变为 false ;所有其他值变为 true ) 3.接着发现它不是空成员指针,就转换成 true (注意,这里不是表示实现定义的值不为 0,实际上如果按照偏移量方式实现,那平凡结构体的首个元素就应该是 0,但是它不是空成员指针,所以仍然是 true) 4.标准约定默认布尔类型输出规则是 true -> 1 false -> 0,所以这里输出 1 |
25
Wirbelwind 2020-10-26 13:47:34 +08:00
我记得是 offset
深度探索 C++对象模型也讲过 |
26
QBugHunter 2020-10-26 13:59:01 +08:00
总觉得这是回的第四种写法。。。。。
|
27
QBugHunter 2020-10-26 14:11:36 +08:00
题目没太仔细看,我有几个问题
1.如果 Test 是个类,那成员变量为什么是 public 的? 2.如果 Test 作为一个结构,为什么不用 Test.a,Test->b 这种写法? 3.Test 的析构函数里的那行代码有什么意义,一个类的析构函数把成员变量的值设为 0 ? |
28
codehz 2020-10-26 16:34:15 +08:00 1
@QBugHunter #27 有意义啊,就像函数指针有啥意义,为啥不用函数一样,通过指针可以实现间接访问,而计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决(
总所周知,c++暂时还没有编译期反射这种东西,没法按照名字来访问未知类型的成员,但是有了这个机制,你至少可以让用户传入成员的指针,然后在模板函数中实现对成员的操作。。。 这种事在 c 的世界也不是没有,那时候通常都是用偏移量( offsetof ),显然直接的偏移量在 c++的世界有诸多水土不和的问题(例如多继承的问题和类型安全的问题),自然要设计一个适合于 c++的"偏移量"解决方案。。。 |
29
QBugHunter 2020-10-26 16:36:40 +08:00
@codehz
你是回复我 3 个问题中的那个? |
30
codehz 2020-10-26 16:46:52 +08:00
@QBugHunter #29 是回的第四种写法(
其他三个问题没啥好问的,问就是作者想这样做而已,毕竟目的不是为了可维护性什么的,就为了演示 /实验这个用法。。。 -- 我猜测你可能把这个用法当作“绕过 private 访问”的用法了,才会有这三个问题( |
31
Hconk 2020-10-26 16:50:35 +08:00
@QBugHunter
1. C++ 允许变量是 public 的,这里的变量就是用 public 修饰。 2. .和-> 是类的实例化对象使用的,类通过::访问成员。 3. 本身这段代码就没什么意义,只是用来演示语法的。。析构函数没什么意义。 |
32
QBugHunter 2020-10-26 17:01:56 +08:00
@codehz
不是。。。我的意思是这种语法是否有实际用途,也就是说将类的成员设为公有,直接操作类成员而不是通过接口,因为在我看来,公有类成员是非常危险的 |
33
v2Mark 2020-10-26 17:07:43 +08:00
有意思的题目,考察的还挺细节的
|
34
wutiantong 2020-10-26 17:30:53 +08:00
@QBugHunter 我倒觉得无脑上 getter setter 那才叫脱裤子放屁,多此一举。。。
|
35
user8341 2020-10-26 17:37:42 +08:00
编译的时候不知道要访问哪个数据成员,执行的时候才知道。
|
36
codehz 2020-10-26 17:52:56 +08:00
@QBugHunter #32
首先,c++的 class 和 struct 根本没有自带立场,只是某些编码规范强加的属性。。。 其次,直接访问成员的危险性在于破坏了潜在的维护性,但前提是它真的是需要隐藏的实现细节。。。 然后这个语法也不是不可以在类内部导出成员指针。。。 最后,这个语法也是另一个层面的“接口”,只不过抽象程度更高,而不具备一般意义上的形式而已。 相当于提供了一个特定上下文下,设置和读取特定值的方法。 然后它们都可以被模板化(上下文类由用户编译期指定,用成员函数来指定特定的成员),只要你认同模板的封装的功能,你就应该理解这种语法也有它存在的意义(结合上面的)。。。 |
37
no1xsyzy 2020-10-26 20:36:59 +08:00
@QBugHunter 如果不需要类似 ORM 反射之类的修改存取过程,那用 getter 和 setter 没什么意义,至于某天突然发现需要了,还可以一键重构。
另外,这个语法听起来也可以变成指向方法的指针,之后可以 obj->*methp(params) ,不知道有没有理解错…… |
38
ysc3839 2020-10-26 21:10:44 +08:00 via Android
|
39
GeruzoniAnsasu 2020-10-26 21:58:38 +08:00
|
40
Tony042 2020-10-26 22:16:42 +08:00
@ysc3839 这个是 class member pointer 和 class member function pointer,是可以指向方法的,std::bind 和这个关系不太大,bind 对 function,function like,和 member function pointer 做了偏特化,我感觉用起来区别还是很大的,bind 主要是用来固定和调整某些 function parameter,class member function pointer 这东西就是 C++黑科技之一,有用到的时候,但用的情况不是很多
|
41
codehz 2020-10-26 23:00:42 +08:00 via Android
@Tony042 还是有点关系的,std::bind 也可以应用于指向成员的指针
std::bind(&Test::a, _1)是可行的(相当于拿到了一个 int &(Test&)的函数) |
42
littlewing 2020-10-26 23:29:14 +08:00
果然这辈子都学不会 c++了
|
43
Wirbelwind 2020-10-27 13:32:54 +08:00
@Tony042
x64 上,对成员函数,可以改变函数写法 class.func(a,b,c); -> func_converted(&class,a,b,c); std::mem_fun (原函数最多两个参数,否则不能编译) 对成员变量,通过上面的方法可以拿到对应的成员变量 auto interface = std::mem_fn(&class::data); interface(&class); |