类的继承部分课程笔记
一,基础知识
1.类之间的继承关系
(1)在已有类的基础上建立新类的过程称为继承
一个类B继承A类,或者说从类A派生类B
那么类A称为基类(父类),类B称为派生类(子类)
(2)基本的语法形式
class 基类名
{ 数据成员和函数声明;
};
class 派生类名:基类名表
{数据成员和函数声明;
};
基类名表构成
控制访问基类名1,控制访问基类名2……
其中控制访问表示派生类对基类的继承方式
public 公有继承
private 私有继承
protected 保护继承
注意:不论哪种方式继承类,派生类都不能直接使用基类的私有成员
(3)派生类的生成经历了三个步骤
吸收基类成员(除了构造和析构函数,其他的成员全部吸收)
改造基类成员:通过在派生类中定义同名成员来屏蔽在派生类中不起作用的部分基类成员
添加派生类的新成员
(4)派生类对想的结构
比如以下的继承关系:
class Father
{ int a,b;
public://成员函数
};
class Son:public Father
{int c;
public://成员函数
};
基类对象的空间为
a,b两个部分
子类则含 a,b,c三个部分//但是这个地方需要注意的是,仅仅复制过来了空间,但是并没有访问权限,所以一般将成员定义成protected类型
2.公有继承
以共有方式继承的派生类,基类的public和protected成员在派生类中的性质不变。即派生类中可以使用基类中定义的public和protected成员;并且,基类的共有成员也是派生类对象的接口,可以在类模板之外被访问
例如以下的程序:
#include<iostream>
using namespace std;
class A
{ public :
void get_XY() { cout << "Enter two numbers ofx, y : " ; cin >> x >>y ; }
void put_XY() { cout << "x = "<< x<< ", y = " << y << '\n' ; }
protected: int x, y ;//定义的是protected类型数据成员
};
class B : public A
{ public :
int get_S() { return s ; };
void make_S() { s = x * y ; }; // 使用基类数据成员x,y
protected: int s;
};
class C : public B
{ public :
void get_H() { cout << "Enter a number of h :" ; cin >> h ; }
int get_V() { return v ; }
void make_V() { make_S(); v = get_S() * h ; } // 使用基类成员函数
protected: int h, v;
};
int main()
{ A objA ;
BobjB ;
CobjC ;
cout << "It is object_A :\n" ;
objA.get_XY() ;
objA.put_XY() ; //输出结果为x=1,y=2
cout << "It is object_B :\n" ;
objB.get_XY() ;//在这个地方键入x,y的值,相当于调用了A的成员函数
objB.make_S() ;//使用基类成员输出S的值
cout << "S = " << objB.get_S() << endl ;
cout << "It is object_C :\n" ;
objC.get_XY() ;//键入x,y的值
objC.get_H();//键入h的值
objC.make_V() ;
cout << "V = " << objC.get_V() << endl ;//输出的结果等于x*y*s*h;
}
在程序中,各类都是建立了保护数据成员:A类中有x,y;B类中有S,c类中有h,v,
那么在main()函数建立的过程中,会产生以下存储空间
:
objA的数据成员有:objA.x,objA.y;
objB的数据成员有:objB.x, objB.y;objB.s;
objC的数据成员有:objC.x,objC.y,objC.s,objC.h,objC.v;
3.重名成员
派生类定义了与基类同名的成员,在派生类中访问同名成员时,屏蔽了基类的同名
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名::成员
比如:(重名的数据成员)
class base
{public :
int a , b ;
} ;
class derived : public base
{public :
int b , c ;
} ; //基类中定义了成员b,而子类中也定义了b,两个成员是重名
void f ()
{ derived d ;
d. a = 1 ;
d. base :: b = 2 ;//说明这个是调用了基类中的成员
d. b = 3 ;//调用了子类中的成员
d. c = 4 ;
};
(重名的函数成员)
#include<iostream>
using namespace std ;
class A
{ public:
int a1, a2 ;
A( int i1=0, int i2=0 ) { a1 = i1; a2 = i2; }
void print() //在基类中定义的成员函数
{ cout << "a1=" << a1 << '\t' <<"a2=" << a2 << endl ; }
};
class B : public A
{ public:
int b1, b2 ;
B( int j1=1, int j2=1 ) { b1 = j1; b2 = j2; }
void print() //定义同名函数
{ cout << "b1=" << b1 << '\t' <<"b2=" << b2 << endl ; }
void printAB()
{ A::print() ; //派生类对象调用基类版本同名成员函数
print() ; //派生类对象调用自身的成员函数
}
};
int main()
{ B b ;
b.A::print();//输出的结果是:a1=0a2=0
b.printAB();
//输出的结果是 a1=0,a2=0
b1=1 b2=1
}
4.访问派生类中的静态成员
基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
派生类中访问静态成员,用以下的显示形式说明:
类名::成员
或通过对象访问对象名.成员
例如以下程序:
#include<iostream>
using namespace std ;
class B
{ public:
static void Add() { i++ ; }
static int i;
void out() { cout<<"static i="<<i<<endl; }
};
int B::i=0;
class D : private B
{ public:
void f()
{ i=5;
Add();
B::i++;
B::Add();
}
};
int main()
{ B x; D y;
x.Add();
x.out();
y.f();
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
//cout<<"static i="<<y.i<<endl;
}
Static i=1
static i=8
static i=8
5.基类的初始化
在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
派生类构造函数(变元表):基类(变元表),对象成员1(变元表)…..对象成员n(变元表)
构造类执行顺序:基类->对象成员->派生类
生类构造函数和析构函数的使用原则
5.1.基类的构造函数和析构函数不能被继承
5.2.如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
5.3.如果基类无无参的构造函数,派生类必须定义构造函数
5.4.如果派生类的基类也是派生类,则定义析构函数与所属的基类无关每个派生类只负责直接基类的构造
6.有关于派生类构造函数的要点如下:
首先创建基类对象;
派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
派生类构造函数应初始化派生类新增成员数据
关于成员初始化列表
derived:derived(type1 x,type2 y):base(x,y)
{
}
其一般格式为:
派生类::派生类名表(参数总表):基类名(参数表)
{
//派生类新增成员的初始化语句
}
//这是基类含有构造函数且有参数时使用
7.派生类的析构函数
(1)当派生类中不含对象成员时
在创建构造函数的执行顺序是:基类的构造函数->派生类的构造函数
在执行析构函数的执行顺序是:派生类的析构函数->基类的析构函数
(2)派生类中含有对象成员时
构造函数的执行顺序是:基类的构造函数->对象成员的构造函数->派生类的构造函数;
析构函数的执行顺序是:派生类的析构函数->对象成员的析构函数->基类的析构函数;
8.多继承
一个类有多个直接的继承关系称为多继承
语法声明
class 派生类名:控制访问 基类名1,控制访问 基类名2,……控制访问 基类名n
{
数据成员和成员函数声明;
}
多继承的构造函数
派生类名(总参数表):基类名1(参数表1);基类名2(参数表2)…..基类名n(参数表n)
{
//派生类新增成员的初始化语句
}
多继承方式下构造函数的执行顺序:
先执行所有基类的构造函数
再执行对象成员的构造函数
最后执行派生类的构造函数
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序
与派生类构造函数中所定义的成员初始化列表顺序没有关系。
内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致
9.赋值兼容规则
赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用共有派生类的对象来代替
赋值兼容规则中所指的替代包括以下的情况:
派生类的对象可以赋给基类对象,派生类的对象可以初始化基类的引用,派生类的对象的地址可以赋给基类类型的指针
10.赋值兼容的可行性
通过公有继承,
派生类得到了除了构造、析构函数以外的所有成员
且这些成员的访问控制属性也和基类完全相同。
这样,它便具备了基类的所有功能。
利用赋值兼容规则
a 派生类的对象可以赋给基类对象(强制类型转换)
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
例如,声明下面两个类:
class Base
{
…..
}
class Derived:public Base
{
}
(1) 可以用派生类对象给基类对象赋值。例如:
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
(2)可以用派生类对象来初始化基类的引用。例如:
Derived d;
Base &br=d;
(3)可以把派生类对象的地址赋值指向基类的指针。例如:
Derived d;
Base *bptr=&d;
这种形式的转换,是在实际应用程序中最长见到的。
(4)可以把指向派生类对象的指针赋值给基类对象的指针。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
(5)特点:
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员
(6)应当注意的问题
1)声明为指向基类的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象。例如:
class B {…};
class D:private B {…};
B b1,*pbl;D d1;
pb1=&b1; //合法,基类B的对象b1和B类的指针
pb1=&d1; //非法,不允许将基类指针指向它的私有派生类对象
(2)允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
(3) 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。
二、心得总结
在使用继承的过程中,要注意将基类中的成员设置为保护类型,子类才能够访问
还有比较重要的一点是,关于基类的初始化,要用到基类的初始化列表,可以对基类进行初始化,要注意它的初始化顺序和析构函数的使用顺序
那么,可以考虑这样一个问题,从点到圆,再到圆柱体,那么圆可以看做点的派生类,圆柱体可以看做是圆的派生类
从下面的代码中可以来考虑继承的实际使用:
classPoint
{ friend ostream &operator<<(ostream &, const Point &);//重载输入输出运算符
public:
Point( int = 0, int = 0 ) ; // 带默认参数的构造函数
void setPoint( int, int ) ; // 对点坐标数据赋值
int getX() const { return x ; } intgetY() const { return y ; }
protected: int x, y; // Point类的数据成员
};
Point::Point( int a, int b )
{ setPoint ( a , b ) ; }
// 对数据成员置值
voidPoint :: setPoint ( int a, int b ) { x =a ; y = b ; }
// 重载插入算符,输出对象数据
ostream&operator<< ( ostream &output , const Point &p )
{output << '[' << p.x << "," << p.y <<"]" ;
return output ;
}
//完成了对点的初始化定义和重新复制
classCircle : public Point
{ friend ostream &operator<<(ostream &, const Circle &); // 友元函数
public:
Circle(double r=0.0, int x=0, int y=0); // 构造函数
void setRadius(double); /*置半径*/ double getRadius() const; /*返回半径*/
double area() const; // 返回面积
protected: double radius; // 数据成员,半径
};
// 带初始化式构造函数,首先调用基类构造函数
Circle::Circle(double r, int a, int b ): Point( a, b ) { setRadius ( r ); }
// 对半径置值,同时对a,b进行初始化
voidCircle::setRadius ( double r ) { radius= ( r >= 0 ? r : 0 ); }
// 返回半径值
doubleCircle::getRadius() const { return radius; }
// 计算并返回面积值
doubleCircle::area() const { return 3.14159 * radius * radius ; }
// 输出圆心坐标和半径值
ostream& operator<< ( ostream &output, const Circle &c)
{output << "Center = " << '[' << c.x <<"," << c.y << "]" << "; Radius ="
<<setiosflags(ios::fixed|ios::showpoint) << setprecision(2) <<c.radius ;
return output ;
}
classCylinder:public Circle
{ friend ostream &operator<<(ostream &, const Cylinder &); // 友元函数
public:
Cylinder(double h=0.0, double r=0.0, intx=0, int y=0); // 构造函数
void setHeight(double); /* 置高度值*/ double getHeight() const; /* 返回高度值*/
double area() const; /* 返回面积*/ double volume() const; /* 返回体积*/
protected: double height; // 数据成员,高度
};
……..
…….
intmain()
{ Pointp(72,115);
Cirlec(2.2,37,43);
Cylindercyl(5.7,2.5,12,23);
}