将晦涩难懂的技术讲的通俗易懂
分类: C/C++
2014-08-31 16:13:13
C++中名字查找与继承
C++调用成员函数的步骤一般为:
(1) 首先确定函数调用的对象、引用或指针的静态类型。
(2) 在该类中查找函数,如果找不到就在直接基类中找,如此循环直到查找到最上层基类。
(3) 一旦找到了改名字就进行常规的参数类型检查。
(4) 假定函数调用是合法的,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定对象的动态类型运行哪个函数版本。
注意:名字查找发生在编译期(即使是虚函数调用),而且与参数检查是不同阶段。
如:
class Base
{
public:
void fun(){}
};
class Derived:public Base
{
public:
void fun(int a){}
};
Derived d;
d.fun();//调用出错,因为编译器首先找到的是Derived中的fun,所以不在查找,但是参数检查时发现类型不匹配。
对于通过基类指针或引用调用虚函数,同样编译器要在基类中查找函数名称,假定找到了函数名称,编译器就检查实参与形参是否匹配。这就是为什么虚函数必须在基类和派生类中拥有同一原型了。
名字冲突造成的覆盖问题
原则:如果基类和派生类使用相同名字的成员函数,在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽。
如:
class Base
{
public:
virtual void fun()
{
cout<<"Base::fun()"<
}
};
class Derived1:public Base
{
public:
void fun(int a) //虽然不是虚函数,但也将Base中的虚函数fun()隐藏了
{
cout<<"Derived1::fun(int)"<
}
//这里有继承自Base的虚函数fun
};
class Derived2:public Derived1
{
public:
void fun(int a) //隐藏了Derived1的fun(int)
{
cout<<"Derived2::fun(int)"<
}
void fun() //重写了Base的虚函数
{
cout<<"Derived2::fun()"<
}
};
Base b;
Derived1 d1;
Derived2 d2;
Base *pb1=&b,*pb2=&d1,*pb3=&d2;
pb1->fun(); //Base::fun()
pb2->fun(); //Base::fun()
pb3->fun(); //Derived2::fun()
下面分析两个经典面试题。
l 例1:
class A
{
protected:
int m_data;
public:
A(int data = 0)
{
m_data = data;
}
int GetData()
{
return doGetData();
}
virtual int doGetData()
{
return m_data;
}
};
class B: public A
{
protected : int m_data;
public:
B(int data = 1)
{
m_data = data;
}
int doGetData()
{
return m_data;
}
};
class C : public B
{
protected : int m_data;
public :
C(int data = 2)
{
m_data = data;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
C c(10);
cout<
cout<
cout<
cout<
cout<
cout<
cout<
cout<
return 0;
}
分析:
c.GetData(),首先在C的作用域中查找GetData,没有找到,然后再B中查找,依然没有,最后在A中查找,找到了这个函数。之后参数检查没发生错误则生成调用代码。注意在GetData(成员函数)中调用了doGetData(虚函数)。其实GetData的原型应该是:
int GetData(A* this)
{
return this->doGetData();
}
这里通过指针调用虚函数,会发生动态绑定。因为this指向的c(派生类对象),所以要调用C中的虚函数doGetData。那C中有没有这个函数呢?虽然直接没有,但是别忘了C中有继承自B中的虚函数表,也就是调用B中的这个doGetData。
之后的c.B::GetData()、c.C::GetData()、c.A::GetData()其实都一样,因为B、C中都没有GetData,所以都是调用的A中的GetData。
下面c.doGetData(),由于是通过对象c调用虚函数doGetData,所以不会发生动态绑定。和调用普通函数一样,首先在当前作用域查找doGetData,没有,然后向上在B中找到了,就会调用B中的doGetData(输出1). 至于c.B::doGetData()和c.C::doGetData()作用相同,因为在C中没哟doGetData()。
l 例2
class A
{
protected:
int m_data;
public:
A(int data = 0)
{
m_data = data;
}
int GetData()
{
return doGetData();
}
int doGetData()
{
return m_data;
}
};
class B: public A
{
protected : int m_data;
public:
B(int data = 1)
{
m_data = data;
}
int doGetData()
{
return m_data;
}
};
class C : public B
{
protected : int m_data;
public :
C(int data = 2)
{
m_data = data;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
C c(10);
cout<
cout<
cout<
cout<
cout<
cout<
cout<
cout<
return 0;
}
分析:
c.GetData(),编译器首先在C中找GetData,没有找到就像上层基类B中寻找,也没有找到,再向A中寻找,发现A中有GetData()则调用。所以c.GetData()是调用A中的GetData。而GetData中又调用doGetData,而doGetData由于不是虚函数,所以也不会发生动态绑定,调用GetData时this是指向A的指针,所以就会调用A中的doGetData(从A中开始查找doGetData的名字)。
A::GetData()、B::GetData()、C::GetData()的作用相同,只是查找的起始范围不同(分别从A、B、C中开始),但由于B、C中没有GetData,最终都是调用的A中的。
c.doGetData(),会先在C中查找doGetData,没有找到,向上一级B中查找,发现有这个函数就调用,所以调用的B中的doGetData。c.A::doGetData()、c.B::doGetData()分别指定从A、B中开始查找,因为A、B中都有doGetData,所以分别调用A、B中的doGetData(域操作符::指定名字查找开始的位置).
lvyilong3162014-09-03 19:31:52
zhiye_wang:您的原话:A::GetData()、B::GetData()、C::GetData()的作用相同,只是查找的起始范围不同(分别从A、B、C中开始),但由于B、C中没有GetData,最终都是调用的A中的.
既然B是继承于A,那么就应该继承了A的GetData函数,为什么说B和C中没有GetData?
这个你应该这么理解:B继承A,也就继承了A的函数这没错,但是函数的作用域不变。举个例子:你继承了你邻居家的物品,他们的物品属于你了,但是还是在你邻居家,并不在即房间里,虽然名义上是你的,你也可以使用,但是物品的位置没有变。这个时候假如你需要一件物品,比如电视机,你会现在自己家找,没有找到再去邻居家找。所以我说B中没有GetData(),指的是你自己家没有电视机,但并不是你没有电视机。不知道这样举例你明白了没
回复 | 举报zhiye_wang2014-09-03 09:16:35
您的原话:A::GetData()、B::GetData()、C::GetData()的作用相同,只是查找的起始范围不同(分别从A、B、C中开始),但由于B、C中没有GetData,最终都是调用的A中的.
既然B是继承于A,那么就应该继承了A的GetData函数,为什么说B和C中没有GetData?