C++中派生类和基类的转换和访问控制

区分“派生类对象”和“派生类”对基类成员的访问权限。

l “派生类对象”对基类成员的访问权限:

   (1)对于公有继承,只有基类的公有成员可以被“派生类对象”访问,其他(保护和私有)成员不能被访问。

   (2)对于私有继承和保护继承,基类中所有成员都不能被“派生类对象”访问

l “派生类”对基类中成员的访问权限:

  (1)对于公有继承,基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。

  (2)对私有继承和保护继承,也是基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。


在没有继承之前,类的只有两类用户:类本身类的使用者。把类成员通过public和private划分恰好体现了这一分割:类的使用者只能访问类的public部分,它们一般是类的接口;而类成员则既可以访问public又可以访问private部分,private部分是类的具体实现。当有了继承之后,类的使用者多了一类:基类的派生类。派生类通常要访问基类的一些具体实现,而又不希望让一般的用户访问这些实现,所以就要把这些内容放置在一起,用protected标号来说明。从该类的使用者的角度,protected成员是私有的,你不能访问基类对象的保护成员,但是从该类的继承者的角度,protected则是共有的,因为他可以被继承下来供派生类使用。

为了更进一步限制派生类对基类成员的访问权限,C++又通过访问列表中的访问标号来控制。具体的说

1.如果继承方式为public,那么基类中的public成员在派生类总仍为public,基类中的protected仍为protected,基类中的private在派生类中不能被访问

2.如果继承方式为protected,那么基类中的public成员在派生类中为protected,基类中的protected成员在派生类中为protected,基类的private成员在派生类中不能被访问

3.如果继承方式为private,那么基类中的public成员在派生类中为private,基类中的protected成员在派生类中为private,基类的private成员在派生类中不可访问

 总结一下:

 

 基类公有成员基类保护成员基类私有成员
公有继承派生类公有成员派生类保护成员派生类不可访问
保护继承派生类保护乘员派生类保护乘员派生类不可访问
私有继承派生类私有成员派生类私有成员派生类不可访问
派生类的成员可以访问基类的protected成员(因为派生类成员派生类的内部)
派生类的对象不可以访问基类的protected成员(因为派生类对象在应用程序域,已经出了类内部域)
派生类的的对象只能访问各种类的公有成员,或通过公有成员函数间接访问私有或受保护的成员

基类对象对派生类对象的切割问题

在c++的世界中有这样两个概念,向上类型转换向下类型转换,分别描述的是子类向基类,和基类向子类的强制类型转换。

向上强制类型转换

切割:覆盖方法和子类数据丢失的现象生成切割(slice)

class Base
{
public:
 int b;
 virtual void Test()
 {
  cout << "base" <<endl;
 }
};
class Derived:public Base
{
public:
 int d;
 virtual void Test()
 {
  cout << "derived" <<endl;
 }
};
int main()
{
 
 Derived d;
 Base b = d;//直接赋值(产生切割)
 b.Test();
 Base& b2 = d;//使用引用赋值(不产生切割)
 b2.Test();
 Base* b3 = &d;//使用指针赋值(不产生切割)
 b3->Test();
 
 return 1;
}


因此,我们得出结论,在向上强制转换过程中,使用指针和引用不会造成切割,而使用直接赋值会造成切割

 再如如下一例子:

#include <iostream>
using namespace std;
class A
{
	private:
		int x;
	public:
		A(){};
		virtual ~A(){};
		virtual void Fx()
		{
			cout<<"A, Fx()"<<endl;
		}
		void Fy()
		{
			cout<<"A, Fy()"<<endl;
		}
};
class B:public A
{
	private:
		int y;
	public:
		B(){};
		~B(){};
		virtual void Fx()//重写
		{
			cout<<"B, Fx()"<<endl;
		}
		void Fy()//重定义
		{
			cout<<"B, Fy()"<<endl;
		}
};
int main()
{
	B* pcb = new B();
	pcb->Fx();      
    A* pca = (A*)pcb;
	pca->Fx();          
	delete pcb;

	pca = new A();
	pcb = (B*)pca;
	pcb->Fx();      //第一处疑惑
	pcb->Fy();      //第二出疑惑
	delete pca;
}


结果为:



B* pcb = new B();

pcb->Fx();                          

A* pca = (A*)pcb;

pca->Fx();          


pcb->Fx();      //第一处疑惑:这个由于调用虚方法,所以是运行时多态,编译的时候看不出调的是哪个,运行时发现指针指向的是A对象,所以调用A的方法

pcb->Fy();      //第二出疑惑:这个由于调用普通方法,所以编译时就注定了会调用pcb所代表类型(B*)的方法,而不会管改指针所指向的对象模型(其实该指针指向的是A对象,只不过这个指针变量被强制转换成了B*,实际上并没有改变改指针的指向



向下强制类型转换

使用dynamic_cast进行向下强制类型转换。使用此关键字有一下几个条件

1.必须有虚函数

2.必须打开编译器的RTTI开关(vc6: progect-> settings -> c/c++ tab ->category[c++ language]-> Enable RTTI)

3.必须有继承关系

Base *b = new Derived;
 Derived *d = dynamic_cast<Derived*>(b);
 if(!d)
 {
  cout << "dynamic cast err!"<<endl;
 }
 else
 {
  d->Test();
 }


 本例子中,符合以上条件,转换成功。否则,会抛出std::bad_cast异常,转换返回NULL

因此,我们可以使用dynamic_cast来判断两个类是否存在继承关系



class Window
{
public:
    std::string name() const;
    virtual void display() const { cout << "Window.display"; };
};
 
class WindowWithScrollBars: public Window
{
public:
    virtual void display() const { cout << "WindowWithScrollBars.display"; };
};
 
void printNameAndDisplay(Window w)
{
    std::cout << w.name();
    w.display();
}<pre name="code" class="cpp" style="color: rgb(51, 51, 51);">//调用
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
//输出:
WindowWithScrollBars.display
 
当一个derived class 对象以by value 方式传递并被视为一个base class 对象,base class 的拷贝构造函数 会被调用,而“造成此对象的行为像个derived class 对象”的那些 特化性质 全被切割掉了,仅仅留下一个 base class 对象,因为正是 base class  构造函数建立了它。
避免切割问题,可用 pass-by-reference-to-const 代替 pass-by-value。即
 
void printNameAndDisplay(const Window &w)
{
    std::cout << w.name();
    w.display();
}

http://blog.csdn.net/windows_nt/article/details/7415402

派生类访问基类的私有成员

派生类不能直接访问基类的私有成员,若要访问必须使用基类的接口,即通过其成员函数。实现方法有如下两种:
1.在基类的声明中增加保护成员,将基类中提供给派生类访问的私有成员定义为保护成员。
2.将需要访问基类私有成员的派生类成员函数声明为友元

http://www.2cto.com/kf/201209/152686.html


派生类的构造函数问题

用户在声明类时可以不定义构造函数,系统会自动设置一个默认的构造函数,在定义类对象时会自动调用这个默认的构造函数。这个构造函数实际上是一个空函数,不执行任何操作。如果需要对类中的数据成员初始化,应自己定义构造函数。

构造函数的主要作用是对数据成员初始化。基类的构造函数是不能继承的,在声明派生类时,派生类并没有把基类的构造函数继承过来,因此,对继承过来的基类成员初始化的工作也要由派生类的构造函数承担。所以在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化。 也就是说,希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是,在执行派生类的构造函数时,调用基类的构造函数

任何派生类都包含基类的成员,简单的派生类只有一个基类,而且只有一级派生(只有直接派生类,没有间接派生类),在派生类的数据成员中不包含基类的对象(即子对象)。

#include <iostream>
#include<string>
using namespace std;
class Student//声明基类Student
{
   public:
   Student(int n,string nam,char s) //基类构造函数
   {
      num=n;
      name=nam;
      sex=s;
   }
   ~Student( ){ } //基类析构函数
   protected : //保护部分
   int num;
   string name;
   char sex ;
};
class Student1: public Student //声明派生类Student1
{
   public : //派生类的公用部分
   Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)//派生类构造函数
   {
      age=a; //在函数体中只对派生类新增的数据成员初始化
      addr=ad;
   }
   void show( )
   {
      cout<<"num: "<<num<<endl;
      cout<<"name: "<<name<<endl;
      cout<<"sex: "<<sex<<endl;
      cout<<"age: "<<age<<endl;
      cout<<"address: "<<addr<<endl<<endl;
   }
   ~Student1( ){ } //派生类析构函数
   private : //派生类的私有部分
   int age;
   string addr;
};

int main( )
{
   Student1 stud1(10010,"Wang-li",'f',19,"115 Beijing Road,Shanghai");
   Student1 stud2(10011,"Zhang-fun",'m',21,"213 Shanghai Road,Beijing");
   stud1.show( ); //输出第一个学生的数据
   stud2.show( ); //输出第二个学生的数据
   return 0;
}


在上例中也可以将派生类构造函数在类外面定义,而在类体中只写该函数的声明:
   Student1(int n, string nam, char s, int a, string ad);

在类的外面定义派生类构造函数:

Student :: Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
{
    age=a;
    addr=ad;
}

请注意,在类中对派生类构造函数作声明时,不包括基类构造函数名其参数表列(即Student(n, nam, s))。只在定义函数时才将它列出。这里介绍的在派生类构造函数中对基类成员初始化,就是普通类构造函数初始化表。也就是说,不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。
 
例如,例11.5中派生类的基类构造函数的定义采用了下面的形式:
Student1(int n, string nam,char s,int a, string ad):Student(n,nam,s)
{
    age=a; //在函数体中对派生类数据成员初始化
    addr=ad;
}
可以将对age和addr的初始化也用初始化表处理,将构造函数改写为以下形式:
   
 Student1(int n, string nam,char s,int a, string ad):Student(n,nam,s),age(a),addr(ad){}

这样函数体为空,更显得简单和方便。
 
在建立一个对象时,执行构造函数的顺序是:
派生类构造函数先调用基类构造函数;
再执行派生类构造函数本身(即派生类构造函数的函数体)。
 
对上例来说,先初始化num,name,sex,然后再初始化age和addr。
 
在派生类对象释放时,先执行派生类析构函数~Student1( ),再执行其基类析构函数~Student( )。

 
#include<stdio.h>
class BaseClass
{
public:
    BaseClass(int ValPl = 1, int ValPt = 2, int ValPr = 3 ):iValPulic(ValPl),iValProtected(ValPt),iValPrivate(ValPr){ printf("BaseClass Inital\n");}
    ~BaseClass(){ printf("BaseClass UnInitial\n"); };
    int iValPulic;
     int GetPublicVal(){ return 1; }
    int GetProtectedVal(){ return iValProtected; };
    int GetPrivateValPublic(){ return iValPrivate; };
protected:
    int iValProtected;
    int GetPrivateVal(){ return iValPrivate; };
private:
    int iValPrivate;
};


class DriveClass:public BaseClass
{
public:
    int iDrivePublic;
    DriveClass(int iValDrive, int ValPl, int ValPt, int ValPr ):iDrivePublic(iValDrive),BaseClass(ValPl, ValPt, ValPr){ printf("DriveClass Inital\n"); }
    ~DriveClass(){ printf("DriveClass UnInitial\n"); }
    int GetPublicVal(){ return iValPulic; }
    int GetPublicValSELECT(){ return this->GetPublicVal(); }
    int GetPublicValBase(){ return BaseClass::GetPublicVal(); }
    int GetProtectedVal(){ return iDrivePublic; }
    int GetBaseProtectedVal(){ return GetPrivateValPublic(); };
};
int main()
{
    BaseClass BSClassObject(1,2,3);
    printf("GetProtectedVal = %d, iValPulic = %d\n",BSClassObject.GetProtectedVal(),
        BSClassObject.iValPulic);
    DriveClass DRClass(4,5,6,7);
    printf("GetProtectedVal = %d, GetBaseProtectedVal = %d\n",DRClass.GetProtectedVal(),
        DRClass.GetBaseProtectedVal());
    printf("GetPublicValSELECT = %d, GetPublicValBase = %d\n",DRClass.GetPublicValSELECT(),
        DRClass.GetPublicValBase());
    return 0;
}

结果为:

 再来看一个例子:

//基类
class Base
{
public:
Base(int i = 0):baseVal_public(i),baseVal_protected(i),baseVal_private(i){}
int baseVal_public;
void printVal()
{
cout<<baseVal_protected<<endl;
cout<<baseVal_private<<endl;
};
protected:
int baseVal_protected;
private:
int baseVal_private;

};


//派生类
class Derived_public:public Base
{
public:
Derived_public(int i = 0):Base(i){}
void getVal()
{
//基类的公有成员在派生类中仍未为公有
cout<<baseVal_public<<endl;
//基类受保护成员变仍未受保护成员
cout<<baseVal_protected<<endl;
//基类的私有成员不能被派生类继承
//cout<<baseVal_private<<endl;
}


};


class Derived_protected:protected Base
{
public:
Derived_protected(int i = 0):Base(i){}
void getVal()
{
//基类的共有成员、受保护成员变为受保护成员
cout<<baseVal_public<<endl;
cout<<baseVal_protected<<endl;
//基类私有成员不能被派生类继承
//cout<<baseVal_private<<endl;
}

};

class Derived_private:private Base
{
public:
Derived_private(int i = 0):Base(i){}
void getVal()
{
//基类的公有成员、受保护成员变为派生类的私有成员
cout<<baseVal_public<<endl;
cout<<baseVal_protected<<endl;
//基类的私有成员不能被继承
//cout<<baseVal_private<<endl;
}

};
int main()
{
Base b;
//公有成员可以被用户访问
cout<<b.baseVal_public<<endl;
//私有成员、受保护成员不能被用户访问
//cout<<b.baseVal_private<<endl;
//cout<<b.baseVal_protected<<endl;
//私有成员、受保护成员可以被类成员访问
b.printVal();

//公有继承下
Derived_public d1;
//基类的public成员仍为public,可以访问
cout<<d1.baseVal_public<<endl;
//私有成员,受保护成员仍为私有、受保护,不能访问
//cout<<d1.baseVal_protected<<endl;
//cout<<d1.baseVal_private<<endl;
d1.getVal();
//受保护继承
Derived_protected d2;
//基类的public变为派生类的protected
//cout<<d2.baseVal_public<<endl;
//基类的protected变为派生类的protected
//cout<<d2.baseVal_protected<<endl;
//基类的private不能被继承
//cout<<d2.baseVal_private<<endl;
d2.getVal();

//私有继承
Derived_private d3;
//基类的公有成员、受保护成员变为派生类的私有
//cout<<d3.baseVal_public<<endl;
//cout<<d3.baseVal_protected<<endl;
//私有成员不能被继承
//cout<<d3.baseVal_private<<endl;
d3.getVal();
return 0;
}


当然,虽然你通过访问列表中的访问标号进行了限制,但是总是可以通过在派生类中加一个using声明来恢复它的权限(注意,只能恢复,不能提高)
class Derived_private:private Base
{
public:

Derived_private(int i = 0):Base(i){}
void getVal()
{
//基类的公有成员、受保护成员变为派生类的私有成员
cout<<baseVal_public<<endl;
cout<<baseVal_protected<<endl;
//基类的私有成员不能被继承
//cout<<baseVal_private<<endl;
}<div><span style="color: rgb(51, 51, 51);"><span data-wiz-span="data-wiz-span"><span style="font-size:14px;">using Base::baseVal_public1;</span></span></span></div><div><span style="color: rgb(51, 51, 51);"><span style="font-size:14px;">
</span></span></div><div><span style="color: rgb(51, 51, 51);"><span data-wiz-span="data-wiz-span"><span style="font-size:14px;">};</span></span></span></div>

那就可以
cout<<d3.baseVal_public1<<endl;

还有一点需要注意的,就会友元关系在继承和派生以后会发生什么变化。

总的来说,分为两点:1.如果一个类被授予为另一个类的友元,它的派生类并不不是这个类的友元。2.基类的友元对基类的派生类不起作用。举个例子来说明:

//基类  
class Base  
{  
friend class Frnd;  
public:  
    Base(int i = 0):baseVal_private(i){}  
      
private:  
    int baseVal_private;  
  
};  
  
class D1:public Base  
{  
private:  
    int derivedVal;  
  
};  
  
class Frnd  
{  
public:  
    int mem(Base b){return b.baseVal_private;}  
    //基类的友元对派生类来说不起作用  
    //int mem(D1 d){return d.derivedVal;}  
  
};  
  
class D2:public Frnd  
{  
public:  
    //友元类的派生类不再是授予有缘的那个类的友元  
    //int mem(Base b){return b.baseVal_private;}  
};


最后有一点需要注意,如果你使用的是class定义的派生类,那么如果不写访问标号,那么默认继承关系为private;如果使用struct定义的派生类,不写访问标号,则默认继承关系为public

 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值