3.3向下造型
将基类类型的指针或引用转换为子类类型的指针或引用
这种转换扩大了指针或引用的操作范围,让编译器看来是不安全的,所以不能隐式转换,但是可以显示转换。
#include<iostream>
using namespace std;
//人类(基类)
class Human{
public:
Human(const string& name,int age)
:m_name(name),m_age(age){}//初始化
void eat(const string& food)const{//常函数(const *this)不需要修改成员变量
cout<<"我在吃"<<food<<endl;
}
void sleep(int time)const{
cout<<"我睡了"<<time<<"小时"<<endl;
}
protected: //保护成员可以在子类中访问,外部不能访问
string m_name;
int m_age;
};
//基类子对象:子类对象中包含基类的部分
//学生类(人类派生的子类)
class Student:public Human{
public:
Student(const string& name,int age,int no):
Human(name,age),m_no(no){}//初始化
void who(void)const{//不修改成员变量的都尽量使用常函数
cout<<"我叫"<<m_name<<",今年"<< \
m_age<<"岁,学号"<<m_no<<endl;
}
void learn(const string& course)const{
cout<<"我在学"<<course<<endl;
}
private:
int m_no;
};
//教师类(人类派生的另外一个子类)
class Teacher:public Human{
public:
Teacher(const string& name,int age,double salary):
Human(name,age),m_salary(salary){}
void teach(const string& course)const{
cout<<"我在讲"<<course<<endl;
}
void who(void)const{
cout<<"我叫"<<m_name<<",今年"<< \
m_age<<"岁,工资:"<<m_salary<<endl;
}
private:
double m_salary;
};
int main(void){
Students("悟空",30,10001);
s.who();
s.eat("桃子");
s.sleep(8);
s.learn("打坐");
Teacher t("唐僧",31,5000.5);
t.who();
t.eat("蔬菜");
t.sleep(6);
t.teach("佛法");
//向上造型,缩小了指针的操作范围,所以安全,可以做隐式类型转换
//(12个字节只访问前8个字节,安全!有12块钱,拿8块钱)
Human* ph=&s; //ph指向子类对象的基类指针
ph->eat("桃");//可以访问基类中有的部分
ph->sleep(10);//可以访问基类中有的部分
//ph->who();error
//Student* ps=ph; 向下造型,理论上可以,但扩大了范围,编译不过
//安全的向下造型
Student *ps=static_cast<Student*>(ph);
ps->who();
Human h("黛玉",19);
//Teacher* rt=&h; 向下造型 编译不过
//危险的向下造型 (有8块钱,拿12块钱)
Teacher* pt=static_cast<Teacher*>(&h);
pt->who();
return 0;
}
图:
3.4子类继承基类的成员
1)在子类中,可以直接访问基类中的所有公有成员和保护成员,如同这些成员在子类中声明的一样。
2)基类的私有成员,在子类中依然存在但是不可见,所以无法直接访问,但是通过基类提供的公有或保护的接口函数来间接地访问。
3)基类的构造函数和析构函数子类无法继承,但是可以在子类自己的构造函数中显示指明基类部分(基类子对象)的初始化方式。
#include <iostream>
using namespace std;
//人类(基类)
class Human{
public:
Human(const string& name,int age):
m_name(name),m_age(age),m_id(1234){}
void eat(const string& food)const{
cout << "我在吃" << food << endl;
}
void sleep(int time)const{
cout << "我睡了" << time << "小时" <<endl;
}
protected://保护成员可以在子类中直接访问
const int& id(void)const{
return m_id;
}
string m_name;
int m_age;
private:
int m_id;
};
//基类子对象:子类对象中包含基类的部分
//学生类(人类派生的子类)
class Student:public Human{
public:
/*可以在子类构造函数的初始化表中显式指明基类子对象
的初始化方式,如果不指明,基类子对象将以无参的方式
进行初始化。*/
Student(const string& name,int age,int no):
Human(name,age),m_no(no){}
void who(void)const{
cout << "我叫" << m_name << ",今年" <<
m_age << "岁,学号:"<< m_no << ",id:" << id() << endl;
}
private:
int m_no;
};
int main(void)
{
Student s("悟空",30,10011);
cout << sizeof(s) << endl;
s.who();
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
16
我叫悟空,今年30岁,学号:10011,id:1234
3.5 子类隐藏基类的成员
1)子类和基类中定义同名的成员函数(包括静态成员函数),因为作用域不同,不能构成重载关系,而是一种隐藏的关系。
2)如果需要在子类中访问隐藏的成员函数,可以通过作用域限定操作符“::”显示的指明
*3)通过using声明可以将基类的成员函数引入到子类的作用域,形成重载
#include<iostream>
using namespace std;
class A{
public:
void foo(void){
cout<<"A::foo()"<<endl;
}
};
class B:public A{
public:
void foo(int i){
cout<<"B::foo(int)"<<endl;
}
void bar(void){
//无法直接访问A中继承过来的foo函数,因为被B类自己的foo隐藏了,可以通过A::foo()说明
A::foo();
foo(100);
}
//把A的foo函数声明到子类B中,这样也可以和B中的foo函数形成重载
// using A::foo();
};
int main(){
Bb;
b.bar(); // A::foo() B::foo(int)
//b.foo();error
b.foo(100); // B::foo(int)
return 0;
}
4、继承方式和访问控制属性
访问控制 属性 内部 子类 外部 友元
限定符 访问 访问 访问 访问
Public 公有成员 OK OK OK OK
Protected 保护成员 OK OK no OK
Private 私有成员 OK no no OK
基类中的 在公有子 在保护子 在私有子
类中变成 类中变成 类中变成
公有成员 公有成员 保护成员 私有成员
保护成员 保护成员 保护成员 私有成员
私有成员 私有成员 私有成员 私有成员
#include <iostream>
using namespace std;
class A{
public:
intm_pub;
protected:
intm_pro;
private:
intm_pri;
};
class B:public A{};
class C:protected A{};
class D:private A{
public:
voidfoo(void){
m_pub= 10;
m_pro= 10;
//m_pri= 10;
}
};
class X:public B{
public:
voidfoo(void){
m_pub= 10;
m_pro= 10;
//m_pri= 10;
}
};
class Y:public C{
public:
voidfoo(void){
m_pub= 10;
m_pro= 10;
//m_pri= 10;
}
};
class Z:public D{
public:
voidfoo(void){
//m_pub= 10;
//m_pro= 10;
//m_pri= 10;
}
};
int main(void)
{
Bb;
b.m_pub= 10;//ok
//b.m_pro= 10;//
//b.m_pri= 10;
Cc;
//c.m_pub= 10;
//c.m_pro= 10;
//c.m_pri= 10;
Dd;
//d.m_pub= 10;
//d.m_pro= 10;
//d.m_pri= 10;
}
5、私有继承的子类和保护继承的子类,不能向上造型
1#include<iostream>
2using namespace std;
3class Base{
4public:
5 int m_pub;
6};
7class Derived:/*private*/ protected /*public*/ Base{};
8int main(void){
9 Derived d;
10 Base* pb=&d;//error pb是基类公有指针,子类是私有的,不能访问
11 //Base* pb=static_cast<Base*>(&d);error
12 return 0;
13 }
6、子类的构造函数
1)如果子类的构造函数没有显示指明基类子对象的初始化方式,那么编译器将会调用基类的无参构造函数初始化基类子对象
2)如果希望基类子对象以有参的方式初始化,就必须在子类构造函数的初始化表中显示指明基类子对象的初始化方式。
#include<iostream>
using namespace std;
class Base{
public:
Base(void):m_i(0){
cout << "Base::Base()" << endl;
}
Base(int i):m_i(i){
cout << "Base::Base(int)"<< endl;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void){
cout <<"Derived::Derived()" <<endl;
}
Derived(int i):Base(i){
cout << "Derived::Derived(int)"<<endl;
}
};
int main(void){
Derived d;
cout << d.m_i << endl;
Derived d2(100);
cout << d2.m_i << endl;//100
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
Base::Base()
Derived::Derived()
0
Base::Base(int)
Derived::Derived(int)
100
3)子类对象构造构造过程
à分配内存
à构造基类子对象(继承表的顺序)
à构造成员子对象(按声明顺序)
à执行子类构造函数代码
#include<iostream>
using namespace std;
class Member{
public:
Member(void):m_i(0){
cout<< "Member::Member()" <<endl;
}
Member(int i):m_i(i){
cout<< "Member::Member(int)" <<endl;
}
int m_i;
};
class Base{
public:
Base(void):m_i(0){
cout << "Base::Base()" << endl;
}
Base(int i):m_i(i){
cout << "Base::Base(int)"<< endl;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void){
cout <<"Derived::Derived()" <<endl;
}
Derived(int i,int j):Base(i),m_member(j){
cout << "Derived::Derived(int)"<<endl;
}
Member m_member;
};
int main(void){
/* Derived d;
cout << d.m_i << endl;
Derived d2(100);
cout << d2.m_i << endl;//100 */
Derived d3(100,200);
cout<<d3.m_i <<endl;
cout << d3.m_member.m_i<< endl;
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
Base::Base(int)
Member::Member(int)
Derived::Derived(int)
100
200
7、子类的析构函数
1)子类的析构函数会自动调用基类的析构函数,析构基类子对象
2)子类对象析构的过程
-》执行子类的析构函数
-》析构成员子对象(按声明逆序)
-》析构基类子对象(按继承表逆顺序)
-》释放内存
#include<iostream>
using namespace std;
class Member{
public:
Member(void):m_i(0){
cout<< "Member::Member()" <<endl;
}
Member(int i):m_i(i){
cout<< "Member::Member(int)" <<endl;
}
~Member(void){
cout<< "Member::~Member()" <<endl;
}
int m_i;
};
class Base{
public:
Base(void):m_i(0){
cout << "Base::Base()" << endl;
}
Base(int i):m_i(i){
cout << "Base::Base(int)"<< endl;
}
~Base(void){
cout << "Base::~Base()" << endl;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void){
cout <<"Derived::Derived()" <<endl;
}
Derived(int i,int j):Base(i),m_member(j){
cout << "Derived::Derived(int)"<<endl;
}
~Derived(void){
cout <<"Derived::~Derived()" <<endl;
}
Member m_member;
};
int main(void){
/* Derived d;
cout << d.m_i << endl;
Derived d2(100);
cout << d2.m_i << endl;//100 */
Derived d3(100,200);
cout<<d3.m_i <<endl;
cout << d3.m_member.m_i<< endl;
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
Base::Base(int)
Member::Member(int)
Derived::Derived(int)
100
200
Derived::~Derived()
Member::~Member()
Base::~Base()
3)基类的析构函数不能调用子类的析构函数,对一个“指向子类对象的基类指针”使用delete运算符,实际被调用的仅仅是基类的析构函数,子类如果有动态资源将会形成内存泄露
class A{….}
class B:public A{};
int main(){
A*pa=new B(….);
deletepa;
}
8、子类的拷贝构造和拷贝赋值
8.1 拷贝构造
1)子类没有定义拷贝构造函数,编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,初始化基类子对象
2)子类定义拷贝构造函数,但是没有显示指明基类子对象的初始化方式,那么基类子对象将以无参的方式被初始化。//逻辑错误
3)子类定义拷贝构造函数,同时在初始化表显示指明了基类子对象也以拷贝的方式进行初始化,那么基类子对象也会得到正常的拷贝。//OK
8.2拷贝赋值
1)子类没有定义赋值运算符函数,编译器将提供缺省的拷贝赋值运算符函数,它会自动调用基类的拷贝赋值运算符函数,复制基类子对象
2)子类定义拷贝赋值运算符函数,但是没有复制基类子对象,基类子对象保持赋值之前的状态。//逻辑错误
3)子类定义拷贝赋值运算符函数,显示调用基类的拷贝赋值运算符函数,复制基类子对象,那么也可以得到正确的赋值操作。//OK
举例:
#include<iostream>
using namespace std;
class Base{
public:
Base(void):m_i(0){}
Base(int i):m_i(i){}
//自定义拷贝构造函数
Base(const Base& that):m_i(that.m_i){
cout<<"Base::Base(const Base&)"<<endl;
}
//自定义拷贝赋值函数 (和缺省的一样)
Base& operator=(const Base& that){
cout<<"Base::operator="<<endl;
if(&that!=this){
m_i=that.m_i;
}
return *this;
}
int m_i;
};
class Derived:public Base{
public:
Derived(void):m_i(0){}
Derived(int i,int j):Base(i),m_i(j){}
//以拷贝的方式初始化
Derived(const Derived& that):m_i(that.m_i),Base(that){}
//赋值(代替缺省,一般不自己写,用缺省.和自定义的赋值函数配套)
Derived& operator=(const Derived& that){
if(&that!=this){
m_i=that.m_i;
//显示调用基类的拷贝赋值函数,来复制基类子对象
Base::operator=(that);
}
return *this;
}
int m_i;
};
int main(void){
Derived d1(100,200);
Derived d2(d1);//拷贝构造(缺省的)
cout<<d1.Base::m_i<<' '<<d1.m_i<<endl;//100 200
cout<<d2.Base::m_i<<' '<<d2.m_i<<endl;//100 200
Derived d3;
d3=d1;//拷贝赋值
cout<<d3.Base::m_i<<' '<<d3.m_i<<endl;
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
Base::Base(const Base&)
100 200
100 200
Base::operator=
100 200
9、多重继承
1)一个子类可以同时继承多个基类,这样继承方式称为多重继承。
技术员 经理
\ /
技术主管
电话 播放器计算机
\ | /
智能手机
2)将继承自多个基类子类类型的指针或引用,转换为基类类型的指针或引用时,编译器会根据各个基类子对象的内存布局,进行适当的偏移计算,保证指针的类型和所指向的目标对象的类型一致。
#include<iostream>
using namespace std;
class Phone{
public:
Phone(const string& number):m_number(number){}
void call(const string& number){
cout<<m_number<<"打给"<<number<<endl;
}
private:
string m_number;
};
class Player{
public:
Player(const string& media):m_media(media){}
void play(const string& music){
cout<<m_media<<"播放器播放"<<music<<endl;
}
private:
string m_media;
};
class Computer{
public:
Computer(const string& os):m_os(os){}
void run(const string& app){
cout<<"在"<<m_os<<"系统上运行"<<app<<endl;
}
private:
string m_os;
};
class Smartphone:public Phone,publicPlayer,public Computer{
public:
Smartphone(const string& number,\
const string& media,const string& os):
Phone(number),Player(media),Computer(os){}
};
int main(){
Smartphonesp("15062203320","MP3","Android");
sp.call("0530-3233192");
sp.play("最炫小苹果");
sp.run("Angrybird");
//向上造型会自动做指针偏移,保证指针类型和所指向的目标对象类型一致
Smartphone* p1=&sp;
Phone* p2=p1;//向上造型
Player* p3=p1;
Computer* p4=p1;
cout<<"p1="<<p1<<endl;
cout<<"p2="<<p2<<endl;
cout<<"p3="<<p3<<endl;
cout<<"p4="<<p4<<endl;
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
15062203320打给0530-3233192
MP3播放器播放最炫小苹果
在Android系统上运行Angry bird
p1=0xbf957110
p2=0xbf957110
p3=0xbf957118
p4=0xbf95711c
图:
3)名字冲突问题
一个子类的多个基类中如果存在相同的名字,当通过子类访问这些名字的时候,编译器会报歧义错误----名字冲突;
解决名字冲突的一般做法就是显示的通过作用域限定指明所访问的名字继承自哪个基类。
如果产生冲突的名字是成员函数并且满足重载的条件,则可以通过using声明,在子类形成重载关系,通过重载解析解决名字冲突。
#include<iostream>
using namespace std;
class A{
public:
void foo(){
cout<<"A::foo"<<endl;
}
int m_data;
};
class B{
public:
void foo(int i){
cout<<"B::foo(int)"<<endl;
}
typedef int m_data;//给int重命名,m_data是一种类型
};
class C:public A,public B{
public:
using A::foo;
using B::foo;
};
int main(void){
Cc;
c./*A::*/foo();
c./*B::*/foo(100);
c.A::m_data=10;
C::B::m_data i=20;
cout<<c.A::m_data<<','<<i<<endl;
return 0;
}
tarena@tarena-virtual-machine:~/day43$./a.out
A::foo
B::foo(int)
10,20
4)钻石继承
一个子类的多个基类源自共同的祖先,这样的继承结构体称为钻石继承
A
/ \
B C
\ /
D
人类
/ \
学生 教师
\ /
课代表