25.c/c++程序员面试宝典-继承
继承是面向对象软件语言的3要素之一,在考查面向对象知识的时候继承的相关题目出现的概率是很高的。继承的实现是基于类的概念的。子类可以使用来自父类的各种方法和属性,这样提高了代码的可复用率。继承的同时子类也可以重新定义或者重写某些属性和方法,以实现更多不同的功能。
面试题105 派生类与基类的转换***
分析:在c++中,基类指针可以指向派生类,也就是基类与派生类的类型转换。派生类转换为基类的示例代码如下:
#include <iostream>
using namespace std;
class base
{
protected:
int a;
public:
base() : a(1)
{}
virtual void print() //定义虚函数
{
cout<<a<<endl;
}
};
class derived : public base //派生类
{
private:
int b;
virtual void print()
{
base::print();
cout<<b<<endl;
}
public:
derived() : b(2)
{}
}
派生类转换为基类又被称为向上转换,它总是隐含进行的。派生类转换为基类总是合法和自动的。也就是说,派生类总是可以转换为基类的引用类型。如果有一个派生类型对象,则可以使用它的地址对基类的指针进行赋值或初始化。同样,可以使用派生的引用或对象初始化基类的引用。严格来说,对对象没有类似转换。编译器在运行时不会自动将派生类型对象转换为基类类型对象。
有的时候,编程者已经知道当前指针所指向的对象是什么类型,在可以确定基类转换为派生类是安全的情况下,可以使用强制转换完成基类转换为派生类。基类是不会自动转换为派生类的,因为基类对象只能是基类对象,它不能包含派生类型成员。基类转换为派生类也被称为向下转换。基类转换派生类的示例代码如下:
#include<iostream>
using namespace std;
class CObject //定义基类
{
public:
virtual void Serialize()
{
cout<<"CObject::Serialize()\n\n"<<endl;
}
};
class CDocument : public CObject //定义派生类
{
public:
int m_datal;
vittual void Serialize()
{
cout<<"CDocument::Serialize()\n\n"<<endl;
}
};
class CMyDoc : public CDocument
{
public:
int m_data2;
CMyDoc(CDocument &Doc) //强制转换必须实现的函数
{
m_data2=0;
}
void func()
{
cout<<"CMyDoc::func()"<<endl;
Serialize();
}
virtual void Serialize()
{
cout<<"CMyDoc::Serialize()\n\n"<<endl;
}
};
int main()
{
CDocument document;
((CMyDoc) document).func(); //基类强制转换派生类
return 0;
}
【答案】派生类总是可以转换为基类的引用类型。基类转换为派生类需要在确定安全的情况下,使用强制转换来进行转换。
面试题106 什么是虚成员?有什么作用***
分析:在c++中,虚成员也被称为虚函数,虚函数是用于面向对象中实现多态的机制。它的核心理念就是通过基类访问派生类定义的函数。虚函数必须是基类的非静态成员函数,它的访问权限可以是protected或者public,函数如果在基类中声明为虚函数,它就一直为虚函数,派生类也不可以改变。派生类重定义虚函数时,可以使用virtual保留字,但这不是必须的。
虚函数的作用是实现动态解析,也就是在程序的运行阶段动态的选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同的定义过程。事实上,虚函数只是一个工具。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。动态解析规定,只能通过指向基类的指针或基类对象的引用来调用虚函数。虚函数使用的示例代码如下:
#include<iostream>
class Cshape
{
public:
void SetColor(int color)
{m_nColor=color;}
void virtual Display(void) //定义虚函数
{cout<<"Cshape"<<endl;}
private:
int m_nColor;
};
class Csquare:public Cshape
{
public:
void virtual Display(void) //定义虚函数
{
cout<<"Csquare"<<endl;
}
};
class Ctriangle:public Cshape
{
void virtual Display(void) //定义虚函数
{cout<<"Ctriangle"<<endl;}
};
class Cellipse:public Cshape
{
void virtual Display(void) //定义虚函数
{cout<<"Cellipse"<<endl;}
};
void main()
{
Cshape obshape;
Cellipse obEllipse;
Ctriangle obTriangle;
Csquare obSquare;
Cshape *pShape[4]={&obShape,&obEllipse,&obTriangle,&obSquare};
for(int i=0;i<4;i++)
pShape[i].Display();
}
上面代码的运行结果如下:
Cshape
Callipse
Ctriangle
Csquare
注意:在派生类中,如果一个派生类函数的名称和参数列表和基类中声明的虚函数相同,则返回类型也必须与虚函数一致。如果出现不一致的情况,这个派生类函数就不会被编译。另外,虚函数也不能作为模板函数使用。
【答案】虚函数的作用是实现动态联编,当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。
面试题107 构造函数与析构函数的调用时机***
分析:构造函数是类中一种特殊的方法,它用来在创建对象时初始化对象,即为对象成员变量赋初始值。在c++中,构造函数与其他函数相比有一些特殊的方法,构造函数的命名必须和类名相同,而一般函数名则不能和类名相同。构造函数没有返回值,所以它也没有返回类型,不能使用void修饰。另外,构造函数也不可以直接被调用。定义一个类的时候,通常情况下都会同时定义该类的构造函数,如果没有提供,则c++提供一个默认的构造函数,这个默认构造函数是无参构造函数,它仅负责创建对象。一个构造函数的示例代码如下:
class Student
{
Student(int a,int b) //含参数带赋值操作的构造函数
{
menber1=a;
menber2=b;
}
}
Student stu(a,b); //声明对象
上面代码中,在构造函数中添加了参数和赋值初始化操作,构造函数将覆盖默认构造函数。另外构造函数也可以不带参数,甚至没有任何操作。
析构函数与构造函数相反,当对象脱离其作用域时,系统会自动执行析构函数。析构函数往往用来做程序的“清理善后”的工作(例如,在建立对象时使用new分配了一块内存空间,则应该在退出前在析构函数中用delete来进行释放)。
在c++中,析构函数名也应该与类名相同,只是在函数名前面加一个波浪符~,用来区别于构造函数。析构函数不带任何参数。也没有返回值,也不能使用void修饰符。类只能有一个析构函数,不能重载。如果用户没有编写析构函数,c++也会自动提供一个默认的析构函数,这个析构函数不进行任何操作。一个析构函数的示例代码如下:
class test
{
public:
bb(const test &rhs)
{... ...}
test &operator=(const test &rhs)
{... ...}
~test() //定义析构函数
{ //清理函数
}
private:
int x;
};
构造函数和析构函数的调用时机介绍如下:
在类的对象首次被使用之前,构造函数将被应用在该对象上,构造函数和析构函数的调用是自动进行的。建立对象时会调用构造函数,而销毁对象时会调用析构函数。
全局对象的构造函数在main函数之前调用,析构函数在main函数执行结束之后调用。不同全局变量的构造函数执行顺序与变量定义的顺序一致,而析构函数调用的顺序正好相反。
自动局部变量的构造函数在程序执行到定义局部变量的语句时调用,在退出包含定义局部变量的语句块时调用析构函数。不同局部变量的构造函数执行顺序与变量定义的顺序一致,而析构函数调用的顺序正好相反。
静态局部变量的构造函数在程序第一次执行,到定义静态局部变量的语句时调用,而析构函数在main函数执行结束后调用。不同静态局部变量的构造函数执行顺序与变量定义的顺序一致。而析构函数调用的顺序也是正好相反。
【答案】构造函数和析构函数的调用时自动进行的。建立对象时会调用构造函数,而销毁对象时调用析构函数。