C++:调整基类成员在派生类中的访问属性的其他方法(同名成员和访问声明)
4.3 调整基类成员在派生类中的访问属性的其他方法
4.3.1 同名函数
在定义派生类的时候,C++语言允许在派生类中说明的成员与基类中的成员名字相同,也就是
说,派生类可以重新说明与基类成员同名的成员。如果在派生类中定义了与基类成员同名的
成员,则称派生类成员覆盖了基类的同名成员,在派生类中重新说明的成员。为了在派生类
中使用基类的同名成员,必须在该成员名之前加上基类名和作用域标识符“::”,即必须使
用下列格式才能访问到基类的同名函数。
基类名::成员名
下面的程序片段说明了这个要点
class X{ public: int f(); }; class Y:public X{ public: int f(); int g(); }; void Y::g() { f(); //表示要访问的是派生类中的成员f(),即被调用的函数是Y::f() X::f() //表示要访问基类中的f() } 对于派生类的对象的访问,也有相同的结论。例如; Y obj; obj.f(); //被访问的函数是Y::f() 如果要访问基类中的声明的名字,则应使用作用域标识符限定,例如; obj.X::f(); //被访问的函数是X::f()
//例4.10 在派生类中定义同名成员
#include<iostream> #include<string> using namespace std; class Student{ //声明基类Student public: Student(int number1,string name1,float score1) //基类的构造函数 { number = number1; name = name1; score = score1; } void print() //在基类中定义了成员函数print { cout<<"number:"<<number<<endl; cout<<"name:"<<name<<endl; cout<<"score:"<<score<<endl; } protected: int number; //学号 string name; //姓名 float score; //成绩 }; class UStudent:public Student{ public: UStudent(int number1,string name1,float score1,string major1):Student(number1,name1,score1) { //定义派生类的构造函数,缀上基类的构造函数 major = major1; } void print() //在派生类中重新定义了成员函数print { Student::print(); //调用基类Student的成员函数print cout<<"major:"<<major<<endl; } private: string major; }; int main() { //Student stu(22116,"张志",95); //stu.print(); //调用基类中的成员函数print UStudent obj(22116,"张志",95,"信息安全"); obj.print(); //调用派生类中的成员函数print //obj.Student::print(); //调用基类中的成员函数print return 0; }
4.3.2 访问声明
对于公有继承,基类的公有成员函数也就是派生类的公有成员函数,这意味着外界可以用派生类
的对象调用基类的公有成员函数。但是对于私有继承,基类的公有成员函数变成了派生类的私有
成员函数。这时,外界就无法利用派生类的对象直接调用基类的成员函数,而只能通过调用派生类
的成员函数(内含调用基类成员函数的语句)间接地调用基类的成员函数。
//例4.11 访问声明的引例
#include<iostream> using namespace std; class A{ //声明基类A public: A(int x1) { x = x1; } void print() { cout<<"x="<<x<<endl; } private: int x; }; class B:private A{ //声明私有派生类B public: B(int x1,int y1):A(x1) { y = y1; } void print2() //通过派生类B的函数print2来调用基类A的成员函数print { //print(); A::print(); } private: int y; }; int main() { B b(10,20); b.print2(); return 0; } */ /* 运行结果是:x=10 如果将派生类中的语句 void print2(){print()}; 改写成语句 void print2(){A::print()}; 同时,将主函数main()中的语句 b.print2(); 改写成语句 b.print(); 程序的运行结果不会变。
*/
这就是在4.3.1节中介绍过的方法。这种方法虽然执行起来比较简单,但在实际应用中却可能带来不便。有时程序员可能希望通过基类A的个别成员还能被派生类的对象直接访问,而不是通过派生类的公有成员函数间接访问。为此,C++提供了称为访问声明的特殊机制,可个别调整基类的某些成员,使之在派生类中保持原来的访问属性。访问声明的方法就是把某些基类的保护成员或公有成员直接写至派生类定义式中的同名段中,同时给基类名和作用域标识符::,利用这种方法,该成员就成为派生类的保护成员或公有成员了。
例如,把上面的基类中的print函数以A::print的形式直接写到私有派生类B中。
class B:private A{ public: B(int x1,int y1):A(x1) { y = y1; } A::print; //访问声明 private: int y; }; 这样,基类A中的print函数就调整为派生类B的公有成员函数,外界可以直接调用它了。 --------------------------------------------------------------- //例4.12 访问声明的应用 #include<iostream> using namespace std; class A{ //声明基类A public: A(int x1) { x = x1; } void print() { cout<<"x="<<x<<endl; } private: int x; }; class B:private A{ //声明私有派生类B public: B(int x1,int y1):A(x1) { y = y1; } A::print; //访问声明,把基类的公有成员函数print调整为私有派生类的 //公有成员函数 private: int y; }; int main() { B b(10,20); b.print(); //调用基类的成员函数print return 0; }
访问声明机制可以在私有派生类中个别调整从基类继承下来的成员性质,从而使外界可以通过
派生类的界面直接访问基类的某些成员,同时也不能影响其他基类成员的封闭性。
访问声明在使用时应注意以下几点。
(1)数据成员也可以使用访问声明。例如
class A{ private: ... public: int x2; ... }; class B:private A{ private: ... public: A::x2; //把基类中的x2调整为派生类的公有成员 ... };
(2)访问声明中只含不带类型和参数的函数名或变量名。如果把上面的访问声明写成
void A::print; 或 A::print(); 或 void A::print(); 都是错误的。
(3)访问声明不能改变成员在基类中的访问属性,也就是说,访问声明只能把原基类的保护成员调整为私有派生类中的保护成员,把原基类中的公有成员调整为调整为私有派生类中的公有成员。但是对基类的私有成员不能使用访问声明。
例如:
class A{ private: int x3; public: int x1; protected: int x2; }; class B:private A{ private: A::x3; //错误 protected: A::x1; //错误 A::x2; //正确 A::x3; //错误 public: A::x1; //正确 A::x2; //错误 A::x3; //错误 };
(4)对于基类中的重载函数名,访问声明将基类中的所有同名函数起作用。这意味着对于重载函数使用访问声明是要慎重。