一、继承
1、public继承
public继承是一种接口继承,子类可以代替父类完成接口所声明的行为。此时,子类可以自动转换成父类的接口,完成接口的转换。
从语法的角度上说,public继承会保留父类中成员(成员函数和成员变量)的可见性,也就是说,如果父类中的某个函数是public,那么被子类继承后仍然是public。
2、private继承
private继承是一种实现继承,子类不能代替父类完成接口所声明的行为,此时子类不能自动转换成父类的接口。
从语法的角度上说,private继承将会将父类中的public和protected可以性的成员修改成为private可见性,这样在子类中同样可以调用父类的protected和public成员,子类的子类也可以调用被protected继承的父类的protected和public成员。
3、protected继承
protected继承是一种实现继承,子类不能代替父类完成接口所声明的行为,此时子类不能自动转换成父类的接口。
从语法的角度上说,protected继承会将父类中的public可见性的成员修改成为protected可见性,这样在子类中同样可以调用父类的protected和public成员,子类的子类也可以调用被protected继承的父类的protected和public成员。#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
{
};
class Derived2 : private Base
{
};
class A : public Derived1
{
public:
void Print()
{
printProtected();
printPublic();
}
};
class B : public Derived2
{
public:
void Print()
{
//因为Derived2是private继承自Base,所以Derived2继承自Base的成员不能被继承到class B
//所以下面两行代码会出现调用错误
printProtected();
printPublic();
}
};
int main()
{
A a;
B b;
a.Print();
b.Print();
return 0;
}
4、私有继承
#include <iostream>
using namespace std;
class Person
{
public:
void eat()
{
cout<< "Person eat" <<endl;
}
};
class Student : private Person
{
public:
void study()
{
cout<< "Student study" <<endl;
}
};
int main()
{
Person p;
Student s;
p.eat();
s.study();
s.eat(); //erro
return 0;
}
二、多态
当不同的对象调用相同名称的成员函数时,可能引起不同的行为(执行不同的代码)。这种现象称为多态性。将函数调用链接相应函数体的代码的过程称为函数联编。在C++中,根据联编时刻的不同,分为静态联编和动态联编。
不同的类可以有相同名称的成员函数(甚至还可以有相同的参数,编译器在编译时就对他们进项函数联编,这种在编译时刻进行的联编称为静态联编)静态联编所支持的多态性称为编译时的多态性,函数重载就属于编译时的多态性。
在动态联编中,程序在运行的时候后才能确定调用哪个函数。这种在运行时的函数联编称为动态联编。动态联编所支持的多态性称为运行时多态性。在C++中,只有虚函数才可能是动态联编的。可以通过定义类的虚函数和创建派生类,然后在派生类中重新实现虚函数,实现具有运行时的多态性。
#include <iostream>
using namespace std;
class Person
{
public:
virtual void print()
{
cout << "I'm a Person" << endl;
}
};
class Chinese : public Person
{
public:
virtual void print()
{
cout<< "I'm from China" <<endl;
}
};
class American : public Person
{
public:
virtual void print()
{
cout<<"I'm from USA"<<endl;
}
};
void printPerson(Person &person)
{
person.print();
}
int main()
{
Person p;
Chinese c;
American a;
printPerson(p);
printPerson(c);
printPerson(a);
return 0;
}
简单的说,虚函数是通过虚函数表实现的。事实上,如果一个雷中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚函数表(vtbl),表中每一项指向一个虚函数的地址,实现上就是一个函数指针的数组。
class Parent
{
public:
virtual void foo1(){}
void foo2(){}
};
class Child1 : Parent
{
public:
virtual void foo1(){}
void foo3(){}
};
class Child2 : Child1
{
public:
void foo1(){}
void foo2(){}
void foo3();
}
Parent类中的vtbl: Parent::foo1()
Child1类中的vtbl:child1::foo1(), Parent::foo1()
Child2类中的vtbl: Child1::foo1(),Parent::foo1();
下面来几个例子:
#include <stdio.h>
class A
{
public:
A()
{
doSth();
}
virtual doSth()
{
printf("I am A");
}
virtual doSth2()
{
printf("In A");
}
};
class B : public A
{
public:
virtual void doSth()
{
printf("I am B");
}
virtual doSth2()
{
printf("In B");
}
};
int main()
{
//在构造函数中,虚拟机制不会发生作用,因为基类的构造函数在
//派生类构造函数之前执行,当基类构造函数运行时,派生类数据
//成员还没有被初始化。如果基类构造期间调用的虚函数向下匹配
//到派生类,派生类的函数理所当然会实际本地数据成员,但是数
//据成员还没有初始化,而调用涉及对象还没有被初始化的部分自
//然是危险的,所以C++会提示编译错误。因此虚函数不会向下匹配
//到派生类,而是直接执行基类的函数。
B b; //I am A
//在构造完成后就会向下匹配
A *p = &b;
p->doSth(); //I am B
return 0;
}
再来一个例子
#include <iostream>
using namespace std;
class A
{
public:
virtual void print()
{
cout<< "A::print()" << endl;
}
};
class B : public A
{
public:
virtual void print()
{
cout <<B::print()" << endl;
}
};
class C : public A
{
public:
void print(void)
{
cout<<"C::print()"<<endl;
}
};
void print(A a)
{
//存在对象的类型转换,对象拷贝
a.print();
}
void print(A* a)
{
a->print();
}
int main()
{
A a,*pa,*pb,*pc;
B b;
C c;
pa = &a;
pb = &b;
pc = &c;
a.print();
b.print();
c.print();
pa->print();
pb->print();
pc->print();
print(a);
print(b);
print(c);
print(&a);
print(&b);
print(&c);
return 0;
}
输出:
#include <iostream>
#include <string>
using namespace std;
void println(const std::string &msg)
{
cout<< msg << endl;
}
class Base
{
public:
Base()
{
println("Base::Base()");
virt();
}
void f()
{
println("Base::f()");
virt();
}
virtual void virt()
{
println("Base::virt()");
}
};
class Derived : public Base
{
public:
Derived()
{
println("Derived::Derived()");
virt();
}
virtual void virt()
{
println("Derived::virt()");
}
};
int main()
{
Derived d;
Base *pB = &d;
pB->f();
return 0;
}
三、纯虚函数和抽象基类
纯虚函数就是基类只定义了函数体,没有实现过程。如果基类含有一个或多个纯虚函数,那么它就属于抽象基类,不能被实例化。引入抽象基类和纯虚函数的原因有一下两点:
1、为了方便使用多态特性;
2、在很多情况下,基类本身生成对象是不合情理的。
纯虚函数和虚函数有以下区别:
1、类里声明虚函数的作用是为了能让这个函数在它的子类里面覆盖,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,只是函数的声明而已,它要留到子类里面去实现。
2、虚函数在子类里面也可以不重载,但是纯虚函数必须在子类里面去实现。通常,很多函数加上virtual修辞,虽然牺牲掉一些性能,但是增加了面向对象的多态性,可以阻止父类里面的这个函数在子类面被修改实现。
3、虚函数的类用于"实作继承",也就是说继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数的类用于"介面继承",即纯虚函数关注的是接口的统一性,实现由子类完成。
4、带纯虚函数的类叫做虚基类。这种基类不能直接生成对象,只能被继承,并重写其虚函数后才能使用,这样的类也叫做抽象类。