类的继承
简单继承
多重继承
多态性和虚函数
多重继承
多态性和虚函数
简单继承
1.派生类的定义
2.基类成员的引用和访问的规则
3.导出类的构造函数
4. 覆盖 作用域分辨
5.派生类的类型转换
6. 导出类的指针
简单继承
从一个类可以派生出新的类——派生类或导出类,也叫子类.
原来的类叫基类也叫父类.
1.派生类的定义形式
class 新类名:public 基类名 //公有继承
{·········};
class 新类名:private 基类名 //私有继承
{·········};
//缺省为私有继承,
//基类是struct类时缺省是公有继承
2.基类成员的引用和访问的规则
3.导出类的构造函数
4. 覆盖 作用域分辨
5.派生类的类型转换
6. 导出类的指针
简单继承
从一个类可以派生出新的类——派生类或导出类,也叫子类.
原来的类叫基类也叫父类.
1.派生类的定义形式
class 新类名:public 基类名 //公有继承
{·········};
class 新类名:private 基类名 //私有继承
{·········};
//缺省为私有继承,
//基类是struct类时缺省是公有继承
基类中的成员自动成为导出类的成员。
基类限制词:public和private指明基类成员在导出类中的访问限制
基类限制词:public和private指明基类成员在导出类中的访问限制
例:class roadVehicle
{ int wheels, passengers;
public:
roadVehicle(int, int);
void SetWheels(int num);
void SetPassengers(int num);
int GetWheels(void);
int GetPassengers(void);
void print( )
{cout<<“\nwheels=”<<wheels
<<“\nPassengers=”<<passengers;
}
};
{ int wheels, passengers;
public:
roadVehicle(int, int);
void SetWheels(int num);
void SetPassengers(int num);
int GetWheels(void);
int GetPassengers(void);
void print( )
{cout<<“\nwheels=”<<wheels
<<“\nPassengers=”<<passengers;
}
};
class truck : public roadVehicle
{ int cargo;
public:
truck(int,int,int);
void SetCargo(int size);
int GetCargo(void);
void Show(void);
void print( );
};
{ int cargo;
public:
truck(int,int,int);
void SetCargo(int size);
int GetCargo(void);
void Show(void);
void print( );
};
Truck类有三个数据成员:
wheels,passengers
cargo.
有9个成员函数
不继承的部分
wheels,passengers
cargo.
有9个成员函数
不继承的部分
1。构造函数和析构函数
2。重载的操作符和赋值符
3。友元
2。重载的操作符和赋值符
3。友元
基类中同名的成员被派生类中相应的名字覆盖
2.基类成员的引用和访问的规则
|
class A |
class B : public A |
class C : public B |
|
private |
不可见 |
不可见 |
|
protected |
protected |
protected |
|
public |
public |
public |
|
| ||
|
class A |
class B : private A |
class C : private B |
|
private |
不可见 |
不可见 |
|
protected |
private |
不可见 |
|
public |
private |
不可见 |
保护成员protected数据在基类中同private
公有基类的保护成员和公有成员是导出类的保护和公有成员
私有基类的保护成员和公有成员是导出类私有成员
基类的私有成员在导出类中不可见只能通过基类的公有成员函数调用
公有基类的保护成员和公有成员是导出类的保护和公有成员
私有基类的保护成员和公有成员是导出类私有成员
基类的私有成员在导出类中不可见只能通过基类的公有成员函数调用
例 void truck::print(void)
{ cout<<“\nwheels=”<<wheels //出错
<<“\npassengers=”<<passengers //出错
<<“\ncargo=”<<cargo;}
{ cout<<“\nwheels=”<<wheels //出错
<<“\npassengers=”<<passengers //出错
<<“\ncargo=”<<cargo;}
应改为
void truck::print(void)
{ roadVehicle::print( );
cout <<“\ncargo=”<<cargo; }
void truck::print(void)
{ roadVehicle::print( );
cout <<“\ncargo=”<<cargo; }
3.导出类的构造函数
派生类不继承构造函数和析构函数
派生类的构造函数必须包含基类构造函数的信息
要为基类构造函数提供初始化参数
派生类的构造函数必须包含基类构造函数的信息
要为基类构造函数提供初始化参数
class truck 的构造函数:
truck::truck(int a,int b,int c) : roadVahicle(a,b)
truck::truck(int a,int b,int c) : roadVahicle(a,b)
如果 roadVehicle类中
roadVehicle::roadVehicle(int x,int y)
{wheels=x; passengers=y;}
则truck类中
truck::truck(int a, int b, int c) : roadVehicle(a,b)
{ cargo=c; }
roadVehicle::roadVehicle(int x,int y)
{wheels=x; passengers=y;}
则truck类中
truck::truck(int a, int b, int c) : roadVehicle(a,b)
{ cargo=c; }
注意:
导出类的构造函数中未写明基类构造函数
基类构造函数的参数无法传递,
这时导出类自动调用基类的无参构造函数,
如果基类没有无参构造函数,就会出错。
如果派生类中还有基类的对象成员
导出类的构造函数中未写明基类构造函数
基类构造函数的参数无法传递,
这时导出类自动调用基类的无参构造函数,
如果基类没有无参构造函数,就会出错。
如果派生类中还有基类的对象成员
class truck : public roadVehicle
{ int cargo;
roadVehicle a;
public:
truck(int,int,int,int,int);
truck(int x):roadVehicle(x,x+1),a(x,x){ cargo=x; }
void SetCargo(int size);
int GetCargo(void);
void Show(void);
void print( );
};
{ int cargo;
roadVehicle a;
public:
truck(int,int,int,int,int);
truck(int x):roadVehicle(x,x+1),a(x,x){ cargo=x; }
void SetCargo(int size);
int GetCargo(void);
void Show(void);
void print( );
};
例 truck类的构造函数
truck::truck(int a1, int a2, int b1, int b2, int c) : roadVehicle(a1,a2), a(b1,b2)
{ cargo=c; }
基类的构造函数由基类名标出
对象成员的构造函数由对象名标出
{ cargo=c; }
基类的构造函数由基类名标出
对象成员的构造函数由对象名标出
构造函数的调用顺序
先基类再导出类
析构函数相反
先基类再导出类
析构函数相反
4. 覆盖 作用域分辨
基类中同名的成员被派生类中相应的名字覆盖
例 class A
{ ······
public:
······
int fun( )
{return 1;}
}
例 class A
{ ······
public:
······
int fun( )
{return 1;}
}
class B : public A
{ ······
public:
······
int fun( )//覆盖A中同名元
{return 2;}
}
A a; B b; int i=a.fun( ); // i=1
int j=b.fun( ); // j=2
i=b.A::fun( ); // i=1 作用域分辨
int j=b.fun( ); // j=2
i=b.A::fun( ); // i=1 作用域分辨
class B : public A
{ ······
public:
······
int fun( ){return 2;}
int f( ){return A::fun( );}
}
B b; int i=b.fun( ); //i=2
int j=b.f( ); //i=1
例 truck类中的print函数
void truck::print(void)
{ roadVehicle::print( );
cout <<“\ncargo=”<<cargo; }
truck t(4,3,4);
t.print( );
输出: wheels=4
passengers=3
cargo=4
{ roadVehicle::print( );
cout <<“\ncargo=”<<cargo; }
truck t(4,3,4);
t.print( );
输出: wheels=4
passengers=3
cargo=4
5.派生类的类型转换
class A
{······};
class B : public A
{······};
{······};
class B : public A
{······};
B是A的派生类,
B是A的超集
可以将类B的对象自动转换
为A类对象,反之不成立.
B是A的超集
可以将类B的对象自动转换
为A类对象,反之不成立.
A a; B b;
A *ap=new A;
B *bp=new B;
a=b; //正确
ap=bp; //正确
b=a; //错误
bp=ap; //错误
A *ap=new A;
B *bp=new B;
a=b; //正确
ap=bp; //正确
b=a; //错误
bp=ap; //错误
用户定义类型转换
类型转换函数重载
X::operator T( );
X是原定义类型,T是转换后类型。
不带参数,没有返回类型。T就是返回类型。
例 String::operator char *(void )
{return str;}
operator char *(void )是String类的成员函数
String类对象可以作char*字符串类型使用。
X::operator T( );
X::operator T( );
X是原定义类型,T是转换后类型。
不带参数,没有返回类型。T就是返回类型。
例 String::operator char *(void )
{return str;}
operator char *(void )是String类的成员函数
String类对象可以作char*字符串类型使用。
X::operator T( );
X类对象可以作T类型使用
X s;
T a=s; //隐式转换 用于初始化
T fun( T t);
a=fun(s); //隐式转换 用于函数参数
X f( X t);
a=f(s); //隐式转换 用于函数返回值
X s;
T a=s; //隐式转换 用于初始化
T fun( T t);
a=fun(s); //隐式转换 用于函数参数
X f( X t);
a=f(s); //隐式转换 用于函数返回值
隐式类型转换的规则:
用于初始化,函数参数,函数返回值
1.标准转换优先(转换成标准类型)。
2.不允许转换再转换
3.必须唯一确定,不能有二义性。
不能用隐式类型转换时用强制类型转换
a= T(s); //显式转换 强制类型转换
1.标准转换优先(转换成标准类型)。
2.不允许转换再转换
3.必须唯一确定,不能有二义性。
不能用隐式类型转换时用强制类型转换
a= T(s); //显式转换 强制类型转换
6. 导出类的指针
1 公有基类
指向导出类对象的指针可以隐式转换为指向基类对象的指针 反之不成立
class A
{······};
class B : public A
{······};
A *ap=new A;
B *bp=new B;
ap=bp; //正确
bp=ap; //错误
bp=(B*)ap;//正确,
//显式转换
指向导出类对象的指针可以隐式转换为指向基类对象的指针 反之不成立
class A
{······};
class B : public A
{······};
A *ap=new A;
B *bp=new B;
ap=bp; //正确
bp=ap; //错误
bp=(B*)ap;//正确,
//显式转换
2.私有基类——不能隐式转换
多重继承
1.多重继承的定义 由多个基类派生出一个类
class A;
class B;
class C :public A, public B
{······};
class B;
class C :public A, public B
{······};
每个基类都应有自己的存定符
A,B的成员都自动成为C的成员,存取权限同单继承
A,B的成员都自动成为C的成员,存取权限同单继承
2.构造函数
导出类的构造函数要为基类提供初始化参数
方法与单继承相同。
调用时先基类再导出类。基类的次序从左到右。
C::C(int a, int b, int c):A(a),B(b)
{······}
方法与单继承相同。
调用时先基类再导出类。基类的次序从左到右。
C::C(int a, int b, int c):A(a),B(b)
{······}
3. 二义性和覆盖
两个基类中有同名成员都被继承。导出类对象调用时出现二义性,要用类名域加以区别。
也可以在导出类中定义一个同名成员加以覆盖,消除二义性。
也可以在导出类中定义一个同名成员加以覆盖,消除二义性。
class A
{ public:
void f( ); };
{ public:
void f( ); };
class B
{ public:
void f( ); };
{ public:
void f( ); };
class C : public A, public B
{ public:
······ };
{ public:
······ };
C c;
c.f( ); //二义性 A的f, B的f ?
c.A::f( ); //合法
c.B::f( ); //合法
c.f( ); //二义性 A的f, B的f ?
c.A::f( ); //合法
c.B::f( ); //合法
class C : public A, public B
{ public:
······ };
{ public:
······ };
void f( ){A::f( );
B::f( ); } };
B::f( ); } };
C c;
c.f( ); //二义性 A的f, B的f ?
c.A::f( ); //合法
c.B::f( ); //合法
c.f( ); //二义性 A的f, B的f ?
c.A::f( ); //合法
c.B::f( ); //合法
4.虚基类
一个基类被派生出两个以上导出类,这两个导出类再生成一个多重导出类.
在导出类D中, A的一个元素通过两个途径被继承了两次。这种情况叫双重继承。
在导出类D中, A的一个元素通过两个途径被继承了两次。这种情况叫双重继承。
虚基类定义可以避免双重继承
class A;
class B : virtual public A;
class C : virtual public A;
Class D : public B, public C;
class B : virtual public A;
class C : virtual public A;
Class D : public B, public C;
这样定义的D类中A的成员只继承一次,可以直接访问A的成员,也可以用任意路径访问。
基类的所有直接导出类中都要用virtual声明为虚基类,否则仍然是多重继承。
构造函数的调用次序
先虚基类(依声明次序从左到右)
后非虚基类(依声明次序)
后非虚基类(依声明次序)
多态性和虚函数
1.多态性
不同的对象调用同一个函数
导致不同的操作称为多态性
运算符重载和函数重载
是最简单的多态性
不同的对象调用同一个函数
导致不同的操作称为多态性
运算符重载和函数重载
是最简单的多态性
1.动态联编
程序在编译时决定由某对象对实例发出的消息,翻译并完成所要求的操作,叫前期装配,或早联编(early binding),也叫静态联编。
程序在编译时不确定由某对象对实例发出的消息,而是在运行过程中确定由哪个对象实例发出的消息,翻译并完成所要求的操作,叫后期装配,或后联编(later binding),也叫动态联编。
程序在编译时决定由某对象对实例发出的消息,翻译并完成所要求的操作,叫前期装配,或早联编(early binding),也叫静态联编。
程序在编译时不确定由某对象对实例发出的消息,而是在运行过程中确定由哪个对象实例发出的消息,翻译并完成所要求的操作,叫后期装配,或后联编(later binding),也叫动态联编。
虚函数的用法
1. 动态联编只能在基类和派生类中有同名同参的函数才能使用。同名不同参不能覆盖。
2.只要在基类成员函数(公共或保护部分)使用 virtual ,中间类同名同参的函数自然也是虚函数
3.如果调用函数为show(B*t)时,中间类也要声明
virtual函数
2.只要在基类成员函数(公共或保护部分)使用 virtual ,中间类同名同参的函数自然也是虚函数
3.如果调用函数为show(B*t)时,中间类也要声明
virtual函数
4.如果派生类要通过虚函数机制存取虚函数,必须建立一条基类到派生类的虚函数路径。即每个中间类都必须有同名同参的函数。
5.如果某个中间类不需要这样的函数,而其后的派生类需要这个虚函数。可以在中间类中声明一个同名同参的空虚函数。
5.如果某个中间类不需要这样的函数,而其后的派生类需要这个虚函数。可以在中间类中声明一个同名同参的空虚函数。
virtual print( ){ }
任何对象都不能调用空虚函数
任何对象都不能调用空虚函数
私有函数也可以声明为虚函数
不能将全局函数或静态函数声明为虚函数
全局函数,静态函数与对象无关。
不能将全局函数或静态函数声明为虚函数
全局函数,静态函数与对象无关。
构造函数不能声明为虚函数
构造函数可以调用虚函数
小心:构造函数中基类构造函数优先
如果基类构造函数调用虚函数,
则必定调用了基类中的虚函数
而派生类中的构造函数则调用派生类中的虚函数,如果派生类中没有这个虚函数则调用基类中的虚函数。
2.纯虚函数
构造函数可以调用虚函数
小心:构造函数中基类构造函数优先
如果基类构造函数调用虚函数,
则必定调用了基类中的虚函数
而派生类中的构造函数则调用派生类中的虚函数,如果派生类中没有这个虚函数则调用基类中的虚函数。
2.纯虚函数
类等级的顶层可以将所有公共函数都声明为纯虚函数。这就可以使每个公用函数都成为后联编实现动态多样性。这样的类不需要任何操作,也不必实例化,即不用定义对象。
纯虚函数的声明
virtual int f( )=0; //表示这个函数没有定义
抽象类
纯虚函数的声明
virtual int f( )=0; //表示这个函数没有定义
抽象类
至少有一个纯虚函数的类是一个抽象类。
抽象类不能定义对象,但可以定义指针
指向它。
抽象类的派生类必须覆盖基类中所有纯虚函数,否则纯虚函数被继承,使派生类仍然是抽象类,而不能定义对象。
抽象基类相当于派生类的模板,它的数据和方法可以被所有派生类共享
抽象类不能定义对象,但可以定义指针
指向它。
抽象类的派生类必须覆盖基类中所有纯虚函数,否则纯虚函数被继承,使派生类仍然是抽象类,而不能定义对象。
抽象基类相当于派生类的模板,它的数据和方法可以被所有派生类共享
一个类的虚函数可以定义为另一个
类的友元。
友元函数不能是虚函数。
运算符可以定义为虚函数。
析构函数也可以定义为虚函数。
类的友元。
友元函数不能是虚函数。
运算符可以定义为虚函数。
析构函数也可以定义为虚函数。
何时要用虚函数
1.类顶或其附近的类中。
2.函数的性质依赖于类的结构或类型。
3.实现类的输入或输出操作的函数。
4.函数的操作是为专门的类而定义的。
2.函数的性质依赖于类的结构或类型。
3.实现类的输入或输出操作的函数。
4.函数的操作是为专门的类而定义的。
如何使多态性失效
用作用域分辨符分辨操作符,就可以使多态性失效。
派生类中的虚函数可以调用基类中的同名虚函数,只要使用作用域分辩符,指明调用基类函数,就不会引起无穷循环。
异质链表
派生类中的虚函数可以调用基类中的同名虚函数,只要使用作用域分辩符,指明调用基类函数,就不会引起无穷循环。
异质链表
#include "iostream.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
class list;
本文详细介绍了类的简单继承和多重继承的概念,包括派生类的定义、基类成员的访问规则、构造函数的调用顺序等内容,并探讨了多态性和虚函数的应用。
741

被折叠的 条评论
为什么被折叠?



