一、类的访问权限
class A {
private:
int x;
public:
int z;
public:
void test() {
x = 1;//ok
z = 1;//ok
A a;
a.x = 1;//error
a.z = 1;//ok
}
};
f是类外部函数,不能访问类的私有成员,可以访问类的公有成员
protected是保护类型,在没有类继承时与private权限相同
二、类的继承与派生
1.派生类的定义——派生方式+基类表
class B : public A {
private:
int l;
protected:
int m;
public:
int n;
};
<基类表>的一般格式为:<派生方式> <基类名1>,… ,<派生方式> <基类名n>
2.派生类的访问权限
派生类继承来的成员的属性取决于基类中的权限操作符和派生方式
派生类继承来的成员的属性——从严原则
派生类的继承不影响类的封装性
举例:
class baseCla {//定义基类
int privData;
protected:
int protData;
public:
int publData;
};
class publDrvCla : public baseCla {
public:
void usebaseClaData() {
publData = 11; //OK!
protData = 12; //OK!
privData = 13; //ERROR!
}
};
class claD21 : public publDrvCla {
public:
void usebaseClaData() {
publData = 111; //OK!
protData = 121; //OK!
privData = 131; //ERROR!
}
};
class protDrvCla : protected baseCla {
public:
void usebaseClaData() {
publData = 21; //OK!
protData = 22; //OK!
privData = 23; //ERROR!
}
};
class claD22 : public protDrvCla {
public:
void usebaseClaData() {
publData = 211; //OK!
protData = 221; //OK!
privData = 231; //ERROR!
}
};
class privDrvCla : private baseCla {
public:
void usebaseClaData() {
publData = 31; //OK!
protData = 32; //OK!
privData = 33; //ERROR!
}
};
class claD23 : public privDrvCla {
public:
void usebaseClaData() {
publData = 311; //ERROR!
protData = 321; //ERROR!
privData = 331; //ERROR!
}
};
允许派生类中函数与基类函数重名:不加类名限定时默认为是处理子类成员,访问父类重名成员要通过类名限定
三、派生类的构造函数
派生类的构造函数的一般格式如下:
<派生类名>(<参数总表>):<初始化符表>
{
<构造函数体>
}
初始化符表完成对基类和对象成员的初始化,基类名与对象成员名的次序无关紧要
<基类名>(<基类参数表>),... ,<对象成员名>(<对象成员参数表>), ...
#include<iostream>
using namespace std;
class Box
{
protected:
double length{ 1.0 };
double width{ 1.0 };
double height{ 1.0 };
public:
Box(double lv, double wv, double hv): length(lv), width(wv), height(hv)
{
cout << "Box(double,double,double) called";
}
explicit Box(double side) :Box(side, side,side)
{
cout << "Box(double) called.";
}
Box() { cout << "Box() called."; }
double volume() const
{
return length * width * height;
}
};
class Carton : public Box
{
private:
string material{ "Cardboard" };
public:
Carton(double lv, double wv, double hv, string mat) : Box(lv, wv, hv), material(mat)
{
cout << "Carton(double,double,double,string) called.";
}
explicit Carton(string mat) : material(mat)
{
cout << "Carton(string) called.";
}
Carton(double side, string mat) :Box(side), material(mat)
{
cout << "Carton(double,string) called.";
}
Carton() { cout << "Carton() called."; }
};
int main() {
Carton carton1;
cout << endl;
Carton carton2("Thin cardboard");
cout << endl;
Carton carton3(4.0, 5.0, 6.0, "Plastic");
cout << endl;
Carton carton4(2.0, "paper");
cout << endl;
return 0;
}
派生类构造函数与基类构造函数的联系
1.在派生类构造函数中,只要基类不是使用无参的默认构造函数都要显式给出基类名和参数表
2.如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数
3.如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数
派生类构造函数执行的一般次序
1.调用各基类的构造函数,调用顺序为派生继承时的基类声明顺序class CD :public CB, public CC {...}
2.若派生类含有对象成员的话,调用各对象成员的构造函数,调用顺序按照派生类中对象成员的声明顺序
{
int d;
CC obcc;
CB obcb;
...
}
3.执行派生类构造函数的函数体
#include<iostream>
using namespace std;
class CB {
int b;
public:
CB(int n) {
b = n;
cout << "CB::b=" << b << endl;
};
};
class CC {
int c;
public:
CC(int n1, int n2) {
c = n1;
cout << "CC::c=" << c << endl;
};
};
class CD :public CB, public CC {
int d;
CC obcc;
CB obcb;
public:
CD(int n1, int n2, int n3, int n4) :CC(n3, n4), CB(n2), obcb(100 + n2), obcc(100 + n3, 100 + n4)
{
d = n1; cout << "CD::d=" << d << endl;
};
};
void main(void) {
CD CDobj(2, 4, 6, 8);
}
*派生类构造函数的进一步讨论
1.派生类初始化表中不能不通过基类的构造函数对继承过来的成员直接初始化
如果把中Carton类的构造函数Carton(double lv, double wv, double hv, stringmat)
改为:
Carton(double lv, double wv, double hv, string mat):length{lv}, width{wv}, height{hv}, material{mat}
{
cout<<"Carton(double,double,double,string) called.";
}
2.派生类初始化表如果不显示调用基类构造函数,就会调用基类的默认无参构造函数
如果把中Carton类的构造函数Carton(double lv, double wv, double hv, stringmat)
改为:
Carton(double lv, double wv, double hv, string mat) :material{ mat }
{
length = lv; width = wv; height = hv;
cout << "Carton(double,double,double,string) called.";
}
四、派生类的拷贝构造函数
1.如果不显式地调用基类的拷贝构造函数,将自动调用基类的无参构造函数为派生类创建基类部分:
为基类Box和派生类Carton分别添加拷贝构造函数,派生类的拷贝构造函数,没有显示地调用基类拷贝构造函数,将会调用基类无参构造函数
Box(const Box& box) :length{box.length}, width{box.width}, height{box.height}
{
cout<<"Box copy constructor";
}
Carton(const Carton& carton) : material {carton.material}
{
cout<<"Carton copy constructor";
}
2.定义派生类拷贝构造函数时,显式地调用基类的拷贝构造函数,将派生类的基类部分“深拷贝”给相应的派生类对象
Box(const Box& box) :length{ box.length }, width{ box.width }, height{ box.height }
{
cout << "Box copy constructor";
}
派生类的拷贝构造函数显示地调用基类拷贝构造函数Box(carton)
Carton(const Carton& carton): Box(carton), material{ carton.material }
{
cout << "Carton copy constructor";
}
举例:
#include<iostream>
using namespace std;
class Box
{
protected:
double length{ 1.0 };
double width{ 1.0 };
double height{ 1.0 };
public:
Box(double lv, double wv, double hv) : length(lv), width(wv), height(hv)
{
cout << "Box(double,double,double) called" << endl;
}
explicit Box(double side) :Box(side, side, side)
{
cout << "Box(double) called." << endl;
}
Box() { cout << "Box() called." << endl;
}
double volume() const
{
return length * width * height;
}
Box(const Box& box) :length{ box.length }, width{ box.width }, height{ box.height }
{
cout << "Box copy constructor" << endl;
}
};
class Carton : public Box
{
private:
string material{ "Cardboard" };
public:
Carton(double lv, double wv, double hv, string mat) : Box(lv, wv, hv), material(mat)
{
cout << "Carton(double,double,double,string) called." << endl;
}
explicit Carton(string mat) : material(mat)
{
cout << "Carton(string) called." << endl;
}
Carton(double side, string mat) :Box(side), material(mat)
{
cout << "Carton(double,string) called." << endl;
}
Carton() { cout << "Carton() called." << endl;}
Carton(const Carton& carton) : Box(carton), material{ carton.material }
{
cout << "Carton copy constructor" << endl;
}
};
int main() {
Carton carton(4.0, 5.0, 6.0, "Plastic");
Carton cartonCopy = carton;
cout << carton.volume() << endl;
cout << cartonCopy.volume();
return 0;
}
五、派生类的析构函数
析构函数各部分执行次序与构造函数相反
#include<iostream>
using namespace std;
class CB {
int b;
public:
CB(int n) {
b = n;
cout << "CB::b=" << b << endl;
}
~CB() {
cout << "CB destructing" << endl;
};
};
class CC {
int c;
public:
CC(int n1, int n2) {
c = n1;
cout << "CC::c=" << c << endl;
}
~CC() {
cout << "CC destructing" << endl;
}
};
class CD :public CB, public CC {
int d;
public:
CD(int n1, int n2, int n3, int n4):CC(n3, n4), CB(n2)
{
d = n1; cout << "CD::d=" << d << endl;
}
~CD() { cout << "CD destructing" << endl; }
};
void main(void) {
CD CDobj(2, 4, 6, 8);
}
改写为:
class CD :public CB, public CC {
int d;
CC obcc;
CB obcb;
public:
CD(int n1, int n2, int n3, int n4)
:CC(n3, n4), CB(n2), obcb(100 + n2),
obcc(100 + n3, 100 + n4) {
d = n1; cout << "CD::d=" << d << endl;
};
~CD() { cout << "CDobj is destructing" << endl; };
};
六、友元的继承
1.如果基类有友元类或友元函数,则其派生类不因继承关系也有此友元类或友元函数
2.如果基类是某类的友元类,则这种友元关系将被继承
3.被派生类继承过来的成员,如果原来是某类的友元,那么它作为派生类的成员仍然是某类的友元
七、静态成员的继承(代码调试)
如果基类的静态成员是公有的或是保护的,则它们被其派生类继承为派生类的静态成员
1.这些成员通常用“<类名>::<成员名>”方式访问
2.这些成员无论有多少个对象被创建,都只有一个拷贝,它为基类和派生类的所有对象所共享
八、赋值兼容性问题
1.如果派生类有自己的赋值运算符的重载定义,按重载后的运算符含义处理
2.派生类未定义自己的赋值操作,而基类定义了赋值操作,则系统自动定义派生类赋值操作(按位拷贝),其中基类成员的赋值按基类的赋值操作进行
3.二者都未定义专门的赋值操作,按位进行拷贝
基类对象和派生类对象之间允许有下述的赋值关系:
1.基类对象 = 派生类对象:只赋“共性成员”部分
2.指向基类对象的指针 = 派生类对象的地址:访问非基类成员部分时,要经过指针类型的强制转换((derived *)pb)->getb()
3.基类的引用 = 派生类对象:通过引用只可以访问基类成员部分
*下述赋值不允许:派生类对象 = 基类对象、指向派生类类型的指针 = 基类对象的地址、派生类的引用 = 基类对象
#include <iostream>
using namespace std;
class base {
int a;
public:
base(int sa) { a = sa; }
int geta() { return a; }
};
class derived :public base {
int b;
public:
derived(int sa, int sb) :base(sa) { b = sb; }
int getb() { return b; }
};
void main() {
base bs1(123);
cout << "bs1.geta()=" << bs1.geta() << endl;
derived der(246, 468);
bs1 = der;
cout << "bs1.geta()=" << bs1.geta() << endl;
der = bs1;//ERROR! “派生类对象 = 基类对象”
base* pb = &der;
cout << "pb->geta()=" << pb->geta() << endl;
cout << pb->getb() << endl;//ERROR! 直接访问非基类成员部分
cout << "((derived *)pb)-> getb() = "<<((derived *)pb)->getb()<<endl;//访问非基类成员部分时,要经过指针类型的强制转换
derived * pd = &bs1;//ERROR! “指向派生类类型的指针=基类对象的地址”
}
九、二义性问题
情形一:单继承时父类与子类间重名
对子类而言,不加类名限定时默认为是处理子类成员,访问父类重名成员时,则要通过类名限定
情形二:多继承情况下二基类间重名
对子类而言,不加类名限定时默认为是处理子类成员,而要访问父类重名成员时,则要通过类名限定
情形三:多级混合继承包含两个基类实例
多级混合继承情况下,若类D从两条不同“路径”同时对类A进行了一般性继承,则类D的对象中会同时包含着两个类A的实例,要通过类名限定来指定访问两个类A实例中的哪一个
小结:不重名的成员变量具有唯一性,可以直接访问,重名变量的访问不加限定时访问的是子类
举例:
#include <iostream>
using namespace std;
class A {
public:
int a;
A(int x) { a = x; }
};
class B :public A {
public:
int b;
B(int x) :A(x - 1) { b = x; }
};
class C :public A {
public:
int c;
C(int x) :A(x - 1) { c = x; }
};
class D :public B, public C {
public:
int d;
D(int x, int y, int z) :B(x + 1), C(y + 2) { d = z; }
};
void main() {
D Dobj(101, 202, 909);
//在类D定义范围内,要通过类名限定来指定访问 两个类A实例中的哪一个
cout << "Dobj.C::a=" << Dobj.C::a << endl;
cout << "Dobj.B::a=" << Dobj.B::a << endl;
cout << Dobj.a;//error!(由二义性引发的问题)
}
十、虚基类
多级混合继承情况下,若类D从两条不同“路径”同时对类A进行了虚拟继承的话,则类D的对象中只包含着
类A的一个实例,被虚拟继承的基类A被称为虚基类
虚基类的说明:在定义派生类时增加关键字virtual
class A
class B : virtual public A
class C : virtual public A
class D : public B, public C
情形一:
#include <iostream>
using namespace std;
class A {
public:
int a;
A(int x) { a = x; }
A() { a = 0; }
};
class B : virtual public A //对类A进行了虚拟继承
{
public:
int b;
B(int x) :A(x - 1) { b = x; }
};
class C : virtual public A //对类A进行了虚拟继承
{
public:
int c;
C(int x) :A(x - 1) { c = x; }
};
class D : public B, public C
{
public:
int d;
D(int x, int y, int z) :B(x + 1), C(y + 2) { d = z; }
};
int main() {
D Dobj(101, 202, 909);
cout << "Dobj.C::a=" << Dobj.C::a << endl;
cout << "Dobj.B::a=" << Dobj.B::a << endl;
cout << Dobj.a;
return 0;
}
情形二:
#include <iostream>
using namespace std;
class A {
public:
int a;
A(int x) { a = x; }
A() { a = 0; }
};
class B : virtual public A //对类A进行了虚拟继承
{
public:
int b;
B(int x) :A(x - 1) { b = x; }
};
class C : virtual public A //对类A进行了虚拟继承
{
public:
int c;
C(int x) :A(x - 1) { c = x; }
};
class D : public B, public C
{
public:
int d;
D(int x, int y, int z) :B(x + 1), C(y + 2) { d = z; }
};
int main() {
D Dobj(101, 202, 909);
Dobj.a = 11;
cout << "Dobj.C::a=" << Dobj.C::a << endl;
cout << "Dobj.B::a=" << Dobj.B::a << endl;
cout << Dobj.a;
return 0;
}
结果:类D中只含一个自己的a,改变这个a时,B,C中的a均会改变
十一、函数重载与函数重写
1.函数重载
允许多个不同函数使用同一个函数名,但要求这些同名函数具有不同的参数表
1.参数表中的参数个数不同
2.参数表中对应的参数类型不同
3.参数表中不同类型参数的次序不同
2.函数重写
仅在基类与其派生类的范围内实现
允许多个不同函数使用完全相同的函数名、函数参数表以及函数返回类型
3.函数重写与静态绑定
#include <iostream>
using namespace std;
class graphelem {
protected:
int color;
public:
graphelem(int col) {
color = col;
}
graphelem() {
color = 0;
}
void draw() { cout << "draw graphelement" << endl; };
};
class line :public graphelem {
public:
void draw() { cout << "draw line" << endl;};
};
class circle :public graphelem {
public:
void draw() { cout << "draw circle" << endl;};
};
class triangle :public graphelem {
public:
void draw() { cout << "draw triangle" << endl;};
};
void main() {
line ln1;
line* pl = &ln1;
ln1.draw();
ln1.graphelem::draw();
graphelem* pg = &ln1; //基类指针指向子类对象
pg->draw();
}
当基类指针指向子类对象时,通过基类指针只能访问子类里面的基类部分,因此,pg->draw() 指的是基类的draw(),这是因为编译器采用静态绑定!
编译器认为pg是基类 graphelem 的指针,就去查找基类graphelem 的定义,发现确实存在draw()函数,于是将函数调用的地址绑定为基类的draw()函数的地址,此过程叫静态绑定
静态绑定引起的问题
void call_draws(graphelem* pg) {
pg->draw(); //graphelem::draw()
}
void main() {
line ln1;
circle cir1;
triangle tri1;
call_draws(&ln1);
call_draws(&cir1);
call_draws(&tri1);
}
call_draw函数的形参类型是基类指针,无论实参传入基类或是子类的类对象,pg->draw()函数都会调用基类的draw()函数,这并不是我们想要的结果!
期望结果:传入line的对象时,可以调用line::draw();传入circle的对象时,可以调用circle::draw();即表现出多态性
4.虚函数与动态绑定
虚函数可解决静态绑定的问题,实现动态绑定
1.定义基类(或其派生类)时,若将其中的某一函数成员的属性说明为virtual,则称该函数为虚函数
2.若基类中某函数被说明为虚函数,则意味着其派生类中有该函数的重写函数
3.在基类中定义虚函数,其派生类的重写函数默认为虚函数,可省略virtual关键字
例如,将graphelem类中的void draw() { cout << "draw graphelement" << endl; };
改写为virtual void draw() { cout << "draw graphelement"<< endl; };
即发生了动态绑定,运行结果:
当实参传入&ln1 时,pg 作为基类指针指向子类的对象 ln1,pg->draw()调用过程会发生动态绑定,调用line::draw(),而不是基类graphelem::draw(),这种现象称为多态性,传入&cri1 和 &tri1 的结果同理
动态绑定发生的条件
1.必须把动态绑定的行为定义为类的虚函数
2.必须通过指针或引用调用虚函数
5.多态性发生的条件
1.必须把动态绑定的行为定义为类的虚函数
2.类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来
3.必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数
6.更复杂的动态绑定
#include <iostream>
using namespace std;
class A {
public:
virtual void vfunc1() { cout << "A::vfunc1" << endl; }
virtual void vfunc2() { cout << "A::vfunc2" << endl; }
void func1() { cout << "A::func1" << endl; }
void func2() { cout << "A::func2" << endl; }
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1() { cout << "B::vfunc1" << endl; }
void func1() { cout << "B::func1" << endl; }
private:
int m_data3;
};
class C : public B {
public:
virtual void vfunc2() { cout << "C::vfunc2" << endl; }
void func2() { cout << "C::func2" << endl; }
private:
int m_data1, m_data4;
};
int main() {
A a;
a.func1(); //静态绑定
a.func2(); //静态绑定
a.vfunc1(); //静态绑定
a.vfunc2(); //静态绑定
cout << endl;
B b;
b.A::func1(); //A::func1(); 静态绑定
b.A::func2(); //A::func2(); 静态绑定
b.A::vfunc1(); //A::vfunc1(); 静态绑定
b.A::vfunc2(); //A::vfunc2(); 静态绑定
cout << endl;
b.vfunc1(); //B::vfunc1(); 静态绑定
b.func1(); //B::func1(); 静态绑定
cout << endl;
C c;
c.B::A::func1();
c.B::A::vfunc1();
c.B::A::func2();
c.B::A::vfunc2();
cout << endl;
c.B::vfunc1();
c.B::func1();
cout << endl;
c.func2();
c.vfunc2();
return 0;
}
int main() {
A a;
B b;
C c;
A* pA = &b;
pA->vfunc1();//动态绑定+函数重写
pA->vfunc2();//动态绑定+函数未重写
pA->func1();//静态绑定
pA->func2();//静态绑定
cout << endl;
pA = &a;
pA->vfunc1();//动态绑定
pA->vfunc2();//动态绑定
pA->func1();//静态绑定
pA->func2();//静态绑定
cout << endl;;
pA = &c;
pA->vfunc1();//动态绑定+函数未重写
pA->vfunc2();//动态绑定+函数重写
pA->func1();//静态绑定
pA->func2();//静态绑定
return 0;
}
十一、纯虚函数、抽象基类
1.纯虚函数
如果不准备在基类的虚函数中做任何事情,则可使用如下的格式将该虚函数说明成纯虚函数:virtual <函数原型> = 0;
纯虚函数不能被直接调用,它只为其派生类的各虚函数规定了一个一致的“原型规格”,该虚函数的实现将在它的派生类中给出
2.抽象基类
含有纯虚函数的基类称为抽象基类
1.不可使用抽象基类来说明并创建它自己的对象,只有在创建其派生类对象时,才有抽象基类自身的实例伴随而生
2.如果一个抽象基类的派生类中没有定义基类中的纯虚函数、而只是继承了基类之纯虚函数的话,则这个派生类还是一个抽象基类
#include <iostream>
using namespace std;
class Vehicle //交通工具,基类
{
public:
Vehicle(int w) {weight = w;}
virtual void ShowMe() { cout << "我是交通工具!重量为" << weight << "吨" << endl; }
int weight;
};
class Car : public Vehicle//汽车,派生类
{
public:
Car(int w, int a) :Vehicle(w) {aird = a;}
virtual void ShowMe() {cout << "我是汽车!排气量为" <<aird <<"CC" << endl;}
int aird;
};
class Boat : public Vehicle//船,派生类
{
public:
Boat(int w, float t) :Vehicle(w) {tonnage = t;}
virtual void ShowMe() {cout << "我是船!排水量为" << tonnage << "吨" << endl;}
float tonnage;
};
int main() {
Vehicle* pv = new Vehicle(10);
pv->ShowMe();
Car c(15, 200);
Boat b(20, 1.25);
pv = &c;
pv->ShowMe();//基类指针,实现动态绑定
Vehicle v(10);//创建一个基类对象
v = b; //将派生类对象赋值给基类对象
v.ShowMe(); //基类对象访问虚函数,未实现动态绑定
pv = &b;
pv->ShowMe();//基类指针,实现动态绑定
return 0;
}
将ShowMe()函数改为纯虚函数
class Boat : public Vehicle//船,派生类
{
public:
Boat(int w, float t) :Vehicle(w) {tonnage = t;}
virtual void ShowMe() {cout << "我是船!排水量为" << tonnage << "吨" << endl;}
float tonnage;
};
int main() {
Vehicle* pv = new Vehicle(10); //ERROR
pv->ShowMe();//ERROR,纯虚函数不能直接调用
Car c(15, 200);
Boat b(20, 1.25f);
Vehicle* pv = &c;//用派生类对象进行初始化
pv->ShowMe();//基类指针,实现动态绑定
pv = &b;
pv->ShowMe();//基类指针,实现动态绑定
return 0;
}