class A { ...... } class B : public A { ..... } (1)A *pA = new B (2)B b; A &rb=b; 第(1)个是说一个基类的指针指向派生类的实例?这样做有什么意义? 第(2)个是什么意思?用途和含义又是什么的? 请说的详细一点,谢谢!
问题补充:
第(2)个中的rb的访问范围与b一样吗?有没有区别? 自己来补充回答吧: 根据我自己的实验,pA只能调用B类的虚函数、A类的公有函数(保护类型的没测试,估计也可以)。看起来访问范围应该是类A与类B的交集。 rb与pA的访问范围类似,与b的范围不同,它不可以访问b的公有函数。 陈兄所说的“指针pA和引用rb的访问范围,完全由pA和rb定义所在的范围决定,跟它们所指向的目标无关。”这句话对我是一个提醒,不过这句话也有点问题,跟目标还是有点关系的,虚函数体现出多态了,这个多态就是目标表现出来的。
基类的指针或者引用指向派生类的实例,这在面向对象编程中使用极其普遍。
A *pA = new B;这是一个基类指针指向一个派生类的实例。
B b; A &rb=b;这是一个基类引用指向(引用)派生类的实例。
至于这个指针pA和引用rb的访问范围,完全由pA和rb定义所在的范围决定,跟它们所指向的目标无关。
通过基类指针或者引用来访问派生类实例的意义在于,这种指针和引用可以通用于访问这个基类之下的所有派生类的对象,这一方面可以使用面向对象的“多态”特性,通过这个基类指针或者引用来调用虚函数的时候,实际执行的是派生类对象的函数,使用这个指针或者引用的一方的代码不必随着派生类的不同而改变,却可以达到执行最适合这个派生类的函数(也就是这个派生类自己的成员函数)的目的;另一方面可以使程序模块具有很好的可替换性,用一个派生类替换另一个派生类,程序的其它部分不需要做任何改动就可以正常运行而且发挥出新的派生类的特性。
PS:基类指针和引用可以用来访问派生类对象是把派生类对象看成基类对象。理论基础是:一个派生类对象一定也是一个基类对象
1.关于类成员的初始化顺序问题
条款13的标题是:initialization list中的members初始化次序应该和其在class内的声明次序相同。
我不知道大家在用C++开发的时候有没有注意过这个问题,反正我是从来没有往这方面想过!
下面来看例子:
{
public:
CMyIntArray( int lowBound, int highBound);
size_t GetArraySize() const { return data.size(); };
private:
std::vector< int> data;
size_t size;
int lBound, hBound;
};
CMyIntArray::CMyIntArray( int lowBound, int highBound)
: size(highBound - lowBound + 1)
, lBound(lowBound)
, hBound(highBound)
, data(size)
{
}
如果没有看过这本书,没有任何提示,我想很多搞C++开发的人看不出这简单几行代码有什么问题(当然,编译肯定是不会有问题的)。
可是,如果程序中有类似于下面的代码,问题就会在程序运行的时候爆发出来:
cout << oTest.GetArraySize() << endl;
而且你就算在debug状态下进行跟踪也很难发现问题的根源所在!
问题处在什么地方呢?就在于成员变量的声明次序上!上面变量声明的次序改为:
int lBound, hBound;
std::vector< int> data;
问题就可以迎刃而解!
惊奇吗?为什么会这样?书中给出了答案:class members系以它们在class内的声明次序来初始化;和它们在member initialization list中出现的次序完全无关。
上面代码的错误根源是:data在初始化的时候size还没有定义!也就是说要初始化一个没有定义大小的vector,当然会出错。
2.关于“切割问题”
下面是书中的一段话(条款22中):
当一个derived class object被交出去当作一个base class object时,它原本所以“成为一个derived class object”的所有特征,都会被切除(slicing)掉,只留下内部一个base class object。
下面是我写的小例子:
{
public:
virtual void Test() const { cout << "Output from CBase!" << endl; };
};
class CDerived : public CBase
{
public:
virtual void Test() const { cout << "Output from CDerived!" << endl; };
};
下面是两个函数:
{
test.Test();
}
void Test2( const CBase& test)
{
test.Test();
}
用下面的代码分别调用函数Test1和Test2:
Test1(oTest);
Test2(oTest);
问题是:调用函数Test1和函数Test2分别输出什么?
正确答案是:
Output from CBase!
Output from CDerived!
惊奇吗?一点儿也不惊奇!书中已经给出了为什么会这样:以by reference的方式传递参数,有另一个优点:可避免所谓的“切割(slicing)问题”。
3.关于非虚拟函数的静态绑定和虚拟函数的动态绑定
下面是例子:
{
public:
virtual void Test() const { cout << "Output from CBase!" << endl; };
};
class CDerived : public CBase
{
public:
void Test() const { cout << "Output from CDerived!" << endl; };
};
现在有下面的代码调用:
CBase *pB = &d;
pB->Test();
CDerived *pD = &d;
pD->Test();
输出是:
Output from CDerived!
Output from CDerived!
这个大家应该都知道为什么。可是,如果把CBase的Test方法前的virtual去掉,结果就变了!输出就是:
Output from CBase!
Output from CDerived!
这时,有些人就会奇怪了,pB和pD指向的都是d,为什么会这样输出呢?
答案就是:非虚拟函数是静态绑定的,虚拟函数是动态绑定的。
书中的条款37给了大家忠告:绝对不要重新定义继承而来的非虚拟函数。
4.关于缺省值的静态绑定
还是看例子:
{
public:
virtual void Test( int iTest = 0) const = 0;
};
class CDerived : public CBase
{
public:
void Test( int iTest = 1) const { cout << iTest << endl; };
};
下面是调用代码:
p->Test();
我们的本意是想输出1,可是结果却是输出0!
不用惊奇,书中给出了答案:虚拟函数系动态绑定(dynamically bound),而缺省参数值却是静态绑定(statically bound)。
而且,书中的条款38也给了大家忠告:绝对不要重新定义继承而来的缺省参数。