1.C++类继承的三种关系
C++中继承主要有三种关系:public、protected和private
(1)public继承
(1)父类的public成员成为子类的public成员,允许类以外的代码访问这些成员;
(2)父类的protected成员成为子类的protected成员,只允许子类成员访问;
(3)父类的private成员仍旧是子类的private成员,子类成员不可以访问这些成员;
(2)protected继承
(1)父类的public成员成为子类的protected成员,只允许子类成员访问;
(2)父类的protected成员仍为子类的protected成员,只允许子类成员访问;
(3)父类的private成员仍旧是子类的private成员,子类成员不可以访问这些成员;
子类的子类可以调用被protected继承的父类的protected和public成员。
(3)private继承
(1)父类的public成员成为子类的private成员,只允许子类成员访问;但是子类的子类就不可以再调用被private继承的父类的public成员了。
(2)父类的protected成员成为子类的private成员,只允许子类成员访问;但是子类的子类就不可以再调用被private继承的父类的protected成员了。
(3)父类的private成员仍旧是子类的private成员,子类成员不可以访问这些成员;
虽然子类中同样还是可以调用父类的protected和public成员,但是子类的子类就不可以再调用private继承的父类的成员了。
#include<iostream>
using namespace std;
class Base
{
protected:
void printProtected() { cout << "print Protected" << endl; }
public:
void printPublic() { cout << "print Public" << endl; }
};
class Derived1 :protected Base //protected继承
{
};
class Derved2 :private Base //private继承
{
};
class A :public Derived1
{
public:
void print()
{
printProtected();//正确, Derived1类通过protected继承Base类,因此它的派生类A
printPublic(); //正确, 可以访问Base基类的protected和public成员函数
}
};
class B :public Derved2
{
public:
void print()
{
printProtected();//编译错误,不能访问 Derived2类通过private继承Base类,因此它的派生类B
printPublic(); //编译错误,不能访问 不可以访问Base基类的任何成员函数
}
};
int main()
{
class A a;
class B b;
a.print();
b.print();
return 0;
}
2.C++继承关系
//2.C++继承关系
//查找程序中的对错
#include<iostream>
using namespace std;
class Parent
{
public:
Parent(int var = -1)
{
m_nPub = var;
m_nPtd = var;
m_nPrt = var;
}
public:
int m_nPub;
protected:
int m_nPtd;
private:
int m_nPrt;
};
class Child1 :public Parent
{
public:
int getPub() { return m_nPub; }
int getPtd() { return m_nPtd; }
int getPrt() { return m_nPrt; } //错误,m_nPrt是基类Parent的私有变量,不能被派生类访问
};
class Child2 :public Parent
{
public:
int getPub() { return m_nPub; }
int getPtd() { return m_nPtd; }
int getPrt() { return m_nPrt; } //错误,m_nPrt是基类Parent的私有变量,不能被派生类访问
};
class Child3 :public Parent
{
public:
int getPub() { return m_nPub; }
int getPtd() { return m_nPtd; }
int getPrt() { return m_nPrt; } //错误,m_nPrt是基类Parent的私有变量,不能被派生类访问
};
int main()
{
Child1 cd1;
Child2 cd2;
Child3 cd3;
int nVar = 0;
cd1.m_nPub = nVar; //正确,Child1是public继承,可以访问并修改基类Parent的public成员变量
cd1.m_nPtd = nVar; //错误,m_nPtd是基类Parent的protected成员变量,通过公有继承后变成了派生类Child1的
//protected成员,因此只能在Child1类内部访问,不能使用Child1对象访问
nVar = cd1.getPtd(); //正确,可以通过Child1类的成员函数访问其protected变量
cd2.m_nPub = nVar; //错误,child2是protected继承,其基类Parent的public和protected成员都变成了它的protected成员,
//因此m_nPub只能在Child2类内部访问,不能使用Child2对象访问。
nVar = cd2.getPtd(); //正确,可以通过Child2类的成员函数访问其protected变量
cd3.m_nPub = nVar; //错误,Child3是private继承,其基类Parent的public和protected成员都变成了它的private成员,
//因此m_nPub只能在Child3类内部访问,不能使用Child3对象访问
nVar = cd3.getPtd(); //正确,可以通过Child3类的成员函数访问其private变量
return 0;
}
3.多态
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。
//3.多态
//多态、封装和继承是面向对象思想的“三大特征”。
#include<iostream>
using namespace std;
class Persion
{
public:
virtual void print() { cout << "I'm a Persion" << endl; }
};
class Chinese :public Persion
{
public:
virtual void print() { cout << "I'm from China" << endl; }
};
class American :public Persion
{
public:
virtual void print() { cout << "I'm form USA" << endl; }
};
void printPersion(Persion &persion)
{
persion.print(); //运行时决定调用哪个类中的print()函数
}
int main()
{
Persion p;
Chinese c;
American a;
printPersion(p); //调用Persion类中的print()函数
printPersion(c); //调用Chinese类中的print()函数
printPersion(a); //调用American类中的print()函数
//在运行时通过基类Persion的对象,可以来调用派生类Chinese和American中的实现方法。
//Chinese和American的方法都是通过覆盖基类中的虚函数方法来实现的。
return 0;
}
I'm a Persion
I'm from China
I'm form USA
I'm from China
I'm form USA
4.虚函数(一)
//4.虚函数(一)
#include<iostream>
using namespace std;
class A
{
public:
virtual void print(void)
{
cout << "A::print()" << endl;
}
};
class B :public A
{
public:
void print(void)
{
cout << "B::print()" << endl;
}
};
class C :public A
{
public:
void print(void)
{
cout << "C::print()" << endl;
}
};
void print(A a) //注意和上一题中的区别,这里参数是值传递,上一题中参数是传引用
{
a.print();
}
void main()
{
A a, *pa, *pb, *pc;
B b;
C c;
pa = &a;
pb = &b;
pc = &c;
//分别使用类A,类B和类C的各个对象来调用其print()成员函数,因此执行的是各个类的print()函数
a.print();
b.print();
c.print();
//使用3个类A的指针来调用print()函数,而这三个指针分别指向类A,类B和类C的3个对象。
//由于A的print()函数是虚函数,因此这里有多态,执行的是各个类的print()函数
pa->print();
pb->print();
pc->print();
//全局的print()函数的参数使用的是传值方式(注意和传引用的区别,如果是引用,则又是多态),
//在对象a、b、c分别传入时,在函数栈中会分别生成类A的临时对象,因此执行的都是类A的print()成员函数
print(a);
print(b);
print(c);
}
A::print()
B::print()
C::print()
A::print()
B::print()
C::print()
A::print()
A::print()
A::print()
B::print()
C::print()
A::print()
B::print()
C::print()
A::print()
A::print()
A::print()
5.虚函数(二)
//5.虚函数(二)
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
virt();
}
void f()
{
cout << "Base::f()" << endl;
virt();
}
virtual void virt()
{
cout << "Base::virt()" << endl;
}
};
class Derived :public Base
{
public:
Derived()
{
cout << "Derved::Derived()" << endl;
virt();
}
virtual void virt()
{
cout << "Derived::virt()" << endl;
}
};
int main()
{
Derived d; //构造Derived对象d。首先调用Base的构造函数,然后调用Derived的构造函数。在Base类的构造函数中,有调用的虚函数
//virt(),此时虚拟机制还没有开始起作用(因为是在构造函数中),所以执行的是Base类的virt()函数。
//同样,在Derived类的构造函数中,执行的是Derived类的virt()函数。
Base *pB = &d;
pB->f(); //通过Base类的指针pB访问Base类的共有成员函数f()。f()函数又调用了虚函数virt(),这里出现了多态。由于指针pB
//是指向Derived类对象的,因此实际执行的是Derived类中的virt()函数。
return 0;
}
Base::Base()
Base::virt()
Derved::Derived()
Derived::virt()
Base::f()
Derived::virt()
Base::virt()
Derved::Derived()
Derived::virt()
Base::f()
Derived::virt()
6.多重继承
实际生活中,一些事物往往会拥有两个或两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念。
多重继承的优点是对象可以调用多个基类中的接口。
多重继承的缺点是容易出现继承向上的二义性。
//6.多重继承
#include<iostream>
using namespace std;
class Persion
{
public:
void sleep() { cout << "sleep" << endl; }
void eat() { cout << "eat" << endl; }
};
class Author :public Persion
{
public:
void writeBook() { cout << "write Book" << endl; }
};
class Programmer :public Persion
{
public:
void writeCode() { cout << "write code" << endl; }
};
class Programmer_Author :public Programmer, public Author //多重继承
{
};
int main()
{
Programmer_Author pa;
pa.writeBook();//正确,调用基类Author的方法
pa.writeCode();//正确,调用基类Programmer的方法
//错误,二义性
pa.eat(); //因为通过多重基础的Programmer_Author类拥有Author类和Programmer类的一份拷贝,而Author类和Programmer类
pa.sleep();//都分别拥有Persion类的一份拷贝,所以Programmer_Author类拥有Persion类的两份拷贝,在调用Persion类的接口时
//编译器会不清楚需要调用哪一方拷贝,从而产生错误。对于这个问题,通过有以下两个解决方案:
//(1)加上全局符确定调用哪一份拷贝。比如 pa.Author::eat()调用属于Author的拷贝
//(2)使用虚拟继承,使得多重继承类Programmer_Author只拥有Persion类的一份拷贝。
// 在类Author和Programmer的定义时的继承语句加入virtual就可以了
// class Author:virtual public Persion
// class Programmer:virtual public Persion
}
7.多重继承和虚拟继承
//(1)首先讨论不存在virtual继承的情况
//多重继承类对象的构造顺序与其继承列表中基类的排列顺序一致,而不是与构造函数的初始化列表顺序一致。
#include<iostream>
using namespace std;
class Parent
{
public:
Parent() :num(0) { cout << "Parent" << endl; }
Parent(int n) :num(n) { cout << "Parent(int)" << endl; }
private:
int num;
};
class Child1 :public Parent
{
public:
Child1() { cout << "Child1()" << endl; }
Child1(int num) :Parent(num) { cout << "Child1(int)" << endl; }
};
class Child2 :public Parent
{
public:
Child2() { cout << "Child2()" << endl; }
Child2(int num) :Parent(num) { cout << "Child2(int)" << endl; }
};
//多重继承类对象的构造顺序与其继承列表中基类的排列顺序一致,而不是与构造函数的初始化列表顺序一致。
//在这里,Derived继承的顺序是Child1、Child2
//构造Child1.由于Child1继承自Parent,因此先调用Parent的构造函数,再调用Child1的构造函数。
//构造Child2,与上一步类似,先调用Parent的构造函数,在调用Child2的构造函数
//最后调用Derived的构造函数
//也就是会调用两次Parent的构造函数
class Derived :public Child1,public Child2
{
public:
Derived() :Child1(0), Child2(1) { cout << "Derived()" << endl; }
Derived(int num) :Child2(num), Child1(num + 1) { cout << "Derived(int)" << endl; }
};
int main()
{
Derived(4); //会调用两次Parent的构造函数
return 0;
}
Parent(int)
Child1(int)
Parent(int)
Child2(int)
Derived(int)
Child1(int)
Parent(int)
Child2(int)
Derived(int)
//(2)讨论存在virtual继承的情况
//多重继承类对象的构造顺序与其继承列表中基类的排列顺序一致,而不是与构造函数的初始化列表顺序一致。
//当Child1和Child2都改为virtual继承Parent时,当系统碰到多重继承的时候就会自动先加入一个虚拟基类(Parent)的拷贝,
//即首先调用虚拟基类(Parent)默认的构造函数,然后再调用派生类(Child1和Child2)的构造函数和自己(Derived)的构造函数.
//由于只生成一份拷贝,因此以后再也不会调用虚拟基类(Parent)的构造函数了,在Child1和Child2指定调用Parent的构造函数就无效了。
//也就是指调用了一次Parent的构造函数
#include<iostream>
using namespace std;
class Parent
{
public:
Parent() :num(0) { cout << "Parent" << endl; }
Parent(int n) :num(n) { cout << "Parent(int)" << endl; }
private:
int num;
};
class Child1 :virtual public Parent
{
public:
Child1() { cout << "Child1()" << endl; }
Child1(int num) :Parent(num) { cout << "Child1(int)" << endl; }
};
class Child2 :virtual public Parent
{
public:
Child2() { cout << "Child2()" << endl; }
Child2(int num) :Parent(num) { cout << "Child2(int)" << endl; }
};
class Derived :public Child1,public Child2
{
public:
Derived() :Child1(0), Child2(1) { cout << "Derived()" << endl; }
Derived(int num) :Child2(num), Child1(num + 1) { cout << "Derived(int)" << endl; }
};
int main()
{
Derived(4); //只会调用一次Parent的构造函数
return 0;
}
Parent
Child1(int)
Child2(int)
Derived(int)
Child1(int)
Child2(int)
Derived(int)
8.为什么要引入抽象基类和纯虚函数
纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像Java中的接口函数。如果基类含有一个或多个纯虚函数,那么它就属于抽象基类,不能被实例化。
为什么要引入抽象基类和纯虚函数呢?主要有以下两点:
(1)为了方便使用多态特性
(2)在很多情况下,基类本身生成对象不和情理的。
//8.抽象基类和纯虚函数
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void sleep() = 0; //纯虚函数,必须在派生类被定义
virtual void eat() = 0; //纯虚函数,必须在派生类被定义
};
class Tiger : public Animal
{
public:
void sleep() { cout << "Tiger sleep" << endl; }
void eat() { cout << "Tiger eat" << endl; }
};
class Lion :public Animal
{
void sleep() { cout << "Lion sleep" << endl; }
void eat() { cout << "Lion eat" << endl; }
};
void main()
{
Animal *p; //Animal指针,不能使用Animal animal定义对象
Tiger tiger;
Lion lion;
p = &tiger; //指向Tiger对象
p->sleep(); //调用Tiger::sleep()
p->eat(); //调用Tiger::eat()
p = &lion; //指向Lion对象
p->sleep(); //调用Lion::sleep()
p->eat(); //调用Lion::eat()
}
Tiger sleep
Tiger eat
Lion sleep
Lion eat
Tiger eat
Lion sleep
Lion eat
9.虚函数与纯虚函数有什么区别?
(1)类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样编译器就可以使用后期绑定来达到多态了。
纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
(2)虚函数在子类里面也可以不重载;但纯虚函数必须在子类里实现。通常在很多函数上加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为很难预料到父类里面的这个函数在子类里面需不需要修改它的实现。
(3)虚函数的类用于“实作继承”,也就是说继承接口的同时也继承了父类的实现。当然,大家也可以完成自己的实现。纯虚函数的类用于“介面”继承,即纯虚函数关注的是接口的统一性,实现由子类完成。
(4)带纯虚函数的类叫做虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。