楼主发表于:2009-07-24 10:11:03是这样的,去一家公司面试,遇到了一道c语言的题,个人
感觉很怪,以前没见过类似的,然后主考官说这是关于C语言强弱类型转换什么的,我感觉他
自己都不完全清楚,然后回来在网上查到这是英国剑桥大学网上出的计算机题 :
#include <stdio.h>
#define init_employee(X,Y) {(X),(Y),wage_emp}
typedef struct Employee Em;
struct Employee {int hours,salary;int (*wage)(Em*);};
int wage_emp(Em *ths) {return ths->hours*ths->salary;}
#define init_manager(X,Y,Z) {(X),(Y),wage_man,(Z)}
typedef struct Manager Mn;
struct Manager {int hours,salary;int (*wage)(Mn*);int bonus;};
int wage_man(Mn *ths) {return ths->hours*ths->salary+ths->bonus;}
int main(void) {
Mn m = init_manager(40,10,20);
Em *e= (Em *) &m;
printf("%d/n",e->wage(e));
return 0;
}
最后输出是420,可我写的是400,有请高人能详细解释下这题的意思么,为什么函数指针调用
的不是wage_emp,参数都不匹配阿,而且Em *e= (Em *) &m到底有什么意义,因为它后面还有
添加的问题是用C++来重写并说明好处,那它这么实现是想用C实现多态的性质么,总之感觉
很怪,望高人解答!!
-------------------------------
nit_manager时,写入到结构体中的指针就是wage_man,而不是wage_emp,后面虽然&m被强
制转换成了Em*类型,但类型强制转换并不会改变结构体变量中的数据。因此那个函数指针
指向的仍然是wage_man这个函数。
这里运用了一些巧合:虽然wage_emp跟wage_man两个函数的签名是不一样的,但从底层来
看,并无多大区别,参数都是一个四字节的指针,返回值都是int,这就使得它们的底层调
用机制是一样的,函数调用无非是参数压栈、指针跳转这类指令,参数和返回值类型都一
样,调用的指令就肯定是完全一样的。
因此虽然是传递一个Em*类型的参数而实际调用的是wage_man,但由于这些巧合,编译器就
这样被轻而易举地欺骗……,也就是说,最后被实际调用的wage_man仍然把参数当成Mn*,
而实际上它又确确实实是个Mn*,于是也不会出任何问题。
如果用C++来写,由于C++有内置地支持多态的机制,就用不着使用这些令人费解的欺骗编
译器的手法了。
-------------------------------
实际上观察Employee 和Manager 的结构不难发现
Manager 只是比Employee 在最后多了一个数据成员而已
在同一个编译器同一个内存对齐规则下Manager 拥有Employee 相同的内存布局(重叠部分
布局相同)
如果从C++的多态的角度去理解
此时,Employee 是一个基类,Manager 是一个派生类
当进行强制类型转换时,这里将Manager 类型强制转换成Employee 类型
由基类指针访问派生类对象实现多态
由于c中并没有c++的多态,但是利用这种强弱类型的转换可以模拟C++的多态
--------------------------------------------------
HUFAN 于 Jul 27 12:32:10 2009 提到: 删除 这个结果和标准有关。 取模运算的定义是:a%b=a-(a/b)*b; C89里面负数取整是“向0靠拢”,所以-1/4=0,所以-1%4=-1。 C99里面修改为更加通用准确的定义法:“不大于其值的最大整数”,因此-1/4=-1,所以 -1%4=3。 因此你的那个例子在C99标准的编译器下结果是3,而在老的C89下就是-1了。 HUFAN 于 Aug 17 01:18:23 2009 提到: 删除 int* p = new int[0];会不会分配内存?? 同样还有malloc(0); 我在vc6下测了malloc ==================== void* p=0; p = malloc(0); ==================== p在malloc之后被赋了一个地址。 但是我不清楚有没有内存被分配? 哪位知道的能给讲讲吗? 回复内容 【taodm】: 这个,你可以下载C和C++标准,自己在上面搜。标准对此有规定。 new int[0]肯定分配内存。 malloc的忘了,自己查吧。 【jinwei1984】: mark! 【zagu2000】: 如果分配内存的话,是分配多少啊? 哪里有C++标准下载?我记得好像要收费的吧? 【songzhenqiu】: 分配,在vc中, int* p = new int[0]; 得到内存内容FD FD FD FD; int* p = new int[1]; 为:CD CD CD CD FD FD FD FD; int* p = new int[2]; 为:CD CD CD CD CD CD CD CD FD FD FD FD; 【taodm】: 标准,csdn上就有。自己搜搜就出来了。 【fengdream】: p是一个指针占4个字节 malloc(0) 没有为其分配空间,所以p所指数据为NULL 【Oversense】: 1个字节,不过前后有保护的话,多点,还要记录信息 【iamcaicainiao】: malloc也同样分配内存. 当参数是0的时候,编译器会自动把0变成1,如此而已. 【foochow】: 那里有说变成1? 【activateMan】: 在标准C++中new int[0]这样的语句是不会通过编译的,编译器会指出这是一个错误。 【taodm】: 摘自C99标准 — The amount of storage allocated by a successful call to the calloc, malloc, or realloc function when 0 bytes was requested (7.20.3) 【Lunar_lty】: 貌似这样的问题很无聊@!!! 【Aaron_Jerry】: 摘自C99标准:7.20.3 Memory management functions If the size of the space requested is zero, the behavior is implementation- defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object. 从上面的规定可以看出 malloc(0) 不一定能分配到空间。 摘自C++标准: Even if the size of the space requested is zero, the request can fail. If the request succeeds, the value returned shall be a nonnull pointer value (4.10) p0 different from any previously returned value p1, unless that value p1 was subsequently passed to an operator delete. The effect of dereferencing a pointer returned as a request for zero size is undefined. HUFAN 于 Aug 17 01:21:02 2009 提到: 删除 多态(Polymorphism)按字面的意思就是“多种形状”。引用Charlie Calverts对多 态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术, 赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“ Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类 类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现 的。 多态性是允许将父对象设置成为和一个或多个它的子对象相等的技术,比如Parent:= Child; 多态性使得能够利用同一类(基类)类型的指针来引用不同类的对象,以及根据所引 用对象的不同,以不同的方式执行相同的操作. 多态的作用:把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异 ,写出通用的代码,做出通用的编程,以适应需求的不断变化。 赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就 是说,父亲的行为像儿子,而不是儿子的行为像父亲。 举个例子:从一个基类中派生,响应一个虚命令,产生不同的结果。 比如从某个基类继承出多个对象,其基类有一个虚方法Tdoit,然后其子类也有这个方 法,但行为不同,然后这些子对象中的任何一个可以附给其基类的对象,这样其基类的对 象就可以执行不同的操作了。实际上你是在通过其基类来访问其子对象的,你要做的就是 一个赋值操作。 使用继承性的结果就是可以创建一个类的家族,在认识这个类的家族时,就是把导出 类的对象 当作基类的的对象,这种认识又叫作upcasting。这样认识的重要性在于:我们 可以只针对基类写出一段程序,但它可以适 应于这个类的家族,因为编译器会自动就找出 合适的对象来执行操作。这种现象又称为多态性。而实现多态性的手段又叫称动态绑定( dynamic binding)。 简单的说,建立一个父类的变量,它的内容可以是这个父类的,也可以是它的子类的 ,当子类拥有和父类同样的函数,当使用这个变量调用这个函数的时候,定义这个变量的类 ,也就是父类,里的同名函数将被调用,当在父类里的这个函数前加virtual关键字,那么 子类的同名函数将被调用 class A { public: A() {} virtual void foo() { cout << "This is A." << endl; } }; class B : public A { public: B() {} void foo() { cout << "This is B." << endl; } }; int main(int argc, char* argv[]) { A *a = new B(); a->foo(); return 0; } 这将显示: This is B. 如果把virtual去掉,将显示: This is A. 前面的多态实现使用抽象类,并定义了虚方法. |