类的继承性
·类的继承性含义:
在已有类的基础上扩展出新类
·提出类的继承性的目的(作用):
代码重用,即可以减少重复代码的编写工作。
·类的继承分类:
单一继承和多重继承
//单一继承:派生类仅由一个基类派生。
//多重继承:派生类由多个基类派生
·类的继承方式:
公有继承(派生)、私有继承(派生)、保护继承(派生)
//继承和派生是一对相对的关系
单一继承的图示:
B—>A
//注意箭头指向!
A称为“基类”或“父类”【BaseClass】
B称为“派生类”或“子类”【DerivedClass】
//没有“继承类”
继承原则:
原则一:
无论何种继承方式,基类的private成员在派生类中均不可直接访问。
原则二:
公有继承:public—>public
protected---->protected
私有继承:public—>private
protected—>private
保护继承:public---->protected
protected—>protected
原则三:
关于访问基类成员的权限:
在派生类内,可以直接访问除基类成员的私有成员外的一切成员;
在派生类外(一般指在主函数或全局里),只能通过派生类的对象名访问公有继承情况下基类的公有成员。
以上的所谓“继承原则”是博主学习类时自己总结的
博主b话:
个人觉得要学好继承,一定要理解掌握好以上(在基类和继承类之间)的转化关系,这样我们在读或者编写用到类的继承知识的代码时,可以自己在脑海里做这样的处理:把基类里的成员相应地转化进派生类中(按照上述的转化关系),从而把两个或两个以上的类当作一个类来看,有利于我们快速读懂或正确编写代码。
读者可能想问:那么如何访问到基类的私有数据成员呢?
解决方法:通过基类的公有函数接口。
继承的作用:继承是软件重用的基础。
公有继承(派生):
1.掌握格式:
class A
{
...
};
class B:public A //公有继承A
{
...
};
2.掌握类的成员的访问权限
3.掌握如何初始化基类的私有数据成员:
在派生类的构造函数的成员初始化列表+使用类名
样例代码:
/*定义"点"类 Point,由"点"类公有派生出"圆"类Circle*/
#include <iostream>
#include <iomanip>
using namespace std;
const double pi = 3.14159;
class Point
{
private:
int x, y;
public:
Point(int a = 0, int b = 0) //缺省构造函数
{
x = a;
y = b;
}
//以上的缺省构造函数亦可写成下面的形式:
// Point(int a = 0, int b = 0):x(a), y(b) {} //总结:在初始化列表初始化数据成员//对本类数据成员进行初始化,使用本类数据成员名
// //对对象成员进行初始化,使用对象成员名
void Setxy(int t1, int t2)
{
x = t1;
y = t2;
}
int Getx()
{
return x;
}
int Gety()
{
return y;
}
void Move(int t1, int t2)
{
x += t1;
y += t2;
}
};
class Circle:public Point
{
private:
int r;
public:
Circle(int a, int b, int ra):Point(a, b) //派生类的构造函数:对基类进行初始化+使用基类名
{
r = ra; //+需要初始化派生类的数据成员
}
void Setr(int ra)
{
r = ra;
}
void ShowCircle()
{
double d = r * r * pi;
cout << "展示圆的参数:" << endl;
cout << "Point:(" << Getx() << ',' << Gety() << ')' << endl; //在类内直接访问基类的公有成员函数+通过基类的公有函数接口访问到基类的私有数据成员
cout << "Radius: " << r << '\t' << "Area: " << d << endl;
}
};
int main()
{
Circle c(0, 0, 2);
c.ShowCircle();
c.Move(2, 2); //思考总结:对私有数据成员进行操作的函数,函数应该放在私有数据成员所在的类里。
c.ShowCircle();
c.Setxy(0, 0); //在类外,通过派生类的对象名访问基类的公有成员函数
c.Setr(1);
c.ShowCircle();
return 0;
}
运行结果:
展示圆的参数:
Point:(0,0)
Radius: 2 Area: 12.5664
展示圆的参数:
Point:(2,2)
Radius: 2 Area: 12.5664
展示圆的参数:
Point:(0,0)
Radius: 1 Area: 3.14159
派生类的构造函数需要完成的事
1.初始化基类的数据成员
2.初始化派生类的数据成员
私有继承(派生):
1.掌握格式:
class A
{
...
};
class B:private A //私有继承A
{
...
};
2.掌握在类内类外,类的成员的访问权限
保护继承(派生):
1.掌握格式:
class A
{
...
};
class B:protected A //保护继承A
{
...
};
2.掌握在类内类外,类的成员的访问权限
//私有继承和保护继承用的不多。
private VS protected
1.相同点:private成员和protected成员在类内能直接访问,在类外不可直接访问。
2.不同点:(前提:单一继承的情况下),基类的private成员,无论何种继承方式,都无法在派生类中被直接访问;而 基类的protected成员,无论何种继承方式,在派生类中都可以被直接访问。
//注意:(多重继承的情况下),对于私有继承(派生),基类的protected成员无法在派生类的派生类被直接访问。
//在继承或派生链中,一旦出现私有继承,基类成员的“类内直接访问特性”就无法在派生类中继续传递下去。(基类的“类内直接访问特性”可以传递给派生类,但不能传递给派生类的派生类)
//在类的继承体系中,将类的数据成员的访问权限定义为protected较好。
//protected成员的优点是,既可以在本类中实现数据的隐藏(类内可直接访问、类外不可被直接访问),又可以将类内直接访问特性传递到派生类中。
多重继承
1.格式
class <派生类名>:[<继承方式1>] 基类1名, [<继承方式2>] 基类2名, ..., [<继承方式n>] 基类n名
{
//定义派生类新成员
[[private:]
...]
[protected:
...]
[public:
...]
};
以下代码帮助大家理解protected和private的不同点以及多重继承
/*先定义点类 Point 和半径类 Radius,再由Point类和Radius多重派生出圆类 Circle*/
#include <iostream>
using namespace std;
const double pi = 3.14159;
class Point
{
private:
int x, y; //x、y被定义为private类
public:
Point(int a = 0, int b = 0)
{
x = a;
y = b;
}
void Setxy(int k, int t)
{
x = k;
y = t;
}
void Move(int m1, int m2)
{
x += m1;
y += m2;
}
int Getx()
{
return x;
}
int Gety()
{
return y;
}
};
class Radius
{
protected:
int r; //r被定义为protected类
public:
Radius(int ra = 0)
{
r = ra;
}
void Setr(int k)
{
r = k;
}
int Getr()
{
return r;
}
};
class Circle:public Point, public Radius
{
public:
Circle(int a = 0, int b = 0, int ra = 0):Point(a, b), Radius(ra) {}
void ShowCircle()
{
double ans = r * r * pi; //在基类Radius中,r是protected成员,在派生类Circle中可以直接被访问
cout << "展示一个圆:" << endl;
cout << "Point:(" << Getx() << ',' << Gety() << ')' << endl; //x、y是基类Point的private(私有)成员,在派生类Circle中不可直接被访问
cout << "Radius: " << r << '\t' << "Area: " << ans << endl;
}
};
int main()
{
Circle c(0, 0, 2);
c.ShowCircle();
c.Move(2, 2);
c.ShowCircle();
c.Setxy(0, 0);
c.Setr(1);
c.ShowCircle();
return 0;
}
运行结果:
展示一个圆:
Point:(0,0)
Radius: 2 Area: 12.5664
展示一个圆:
Point:(2,2)
Radius: 2 Area: 12.5664
展示一个圆:
Point:(0,0)
Radius: 1 Area: 3.14159
该例中的各个类的关系图示:
//可能有读者想问:r被定义为protected成员,在派生类中可直接被访问(单一继承时) ,为什么还要定义一个访问r的公有函数接口Getr()呢,是不是有点累赘?答:不累赘的,r被定义为protected成员,虽然在派生类内可以直接被访问,但在类外(如主函数中)依然不能被直接访问,所以定义该公有函数Getr()是为了实现在类外(如主函数中)间接访问到数据成员r 。
//将基类的数据成员定义成保护成员,这样既可以在基类中隐藏数据,又可以在派生类中直接访问这些数据成员。这样处理使程序书写简单,代码执行效率提高。
现在,我们来讨论一下派生类的构造函数和析构函数的执行顺序:
假设派生类的构造函数定义如下:
ClassName(args):Base1(arg_1), Base2(arg_2), ..., Basen(arg_n)
{
<派生类自身构造函数的函数体>
};
派生类的构造函数执行顺序
先依次调用基类构造函数Base1()、Base2()、…、Basen(),再执行派生类自身构造函数的函数体。
派生类析构函数调用顺序:首先执行派生类的析构函数的函数体,再依次执行Basen、…、Base2、Base1。即与派生类构造函数调用顺序相反。
关于继承的其他一些问题:
基类的析构函数和构造函数不会被继承,只能在派生类的构造函数或析构函数中自动被调用,完成对基类数据成员的初始化或清理工作。
/*多重继承时,基类构造函数和析构函数的调用顺序*/
#include <iostream>
using namespace std;
class Base1
{
protected:
int data1;
public:
Base1(int x = 0)
{
data1 = x;
cout << "Base1 Constructor\n";
}
~Base1()
{
cout << "Base1 Destructor\n";
}
};
class Base2
{
protected:
int data2;
public:
Base2(int y = 0)
{
data2 = y;
cout << "Base2 Constructor\n";
}
~Base2()
{
cout << "Base2 Destructor\n";
}
};
class Derived:public Base1, public Base2
{
private:
int data3;
public:
Derived(int x, int y, int z):Base2(y), Base1(x)
{
data3 = z;
cout << "Derived Constructor\n";
}
~Derived()
{
cout << "Derived Destructor\n";
}
void Show()
{
cout << data1 << ',' << data2 << ',' << data3 << endl;
}
};
int main()
{
Derived d(1, 2, 3);
d.Show();
return 0;
}
运行结果:
Base1 Constructor //调用了基类Base1的构造函数
Base2 Constructor //调用了基类Base2的构造函数
Derived Constructor //调用了派生类Derived的构造函数
1,2,3
Derived Destructor //调用了派生类的析构函数
Base2 Destructor //调用了基类Base2的析构函数
Base1 Destructor //调用了基类Base1的析构函数
/*基类成员、对象成员的构造函数和析构函数的调用顺序*/
#include <iostream>
using namespace std;
class Base1
{
protected:
int data1;
public:
Base1(int x = 1)
{
data1 = x;
cout << data1 << ", Base1 Constructor\n";
}
~Base1()
{
cout << data1 << ", Base1 Destructor\n";
}
};
class Base2
{
protected:
int data2;
public:
Base2(int y = 2)
{
data2 = y;
cout << data2 << ", Base2 Constructor\n";
}
~Base2()
{
cout << data2 << ", Base2 Destructor\n";
}
};
class Derived:public Base1, public Base2 //A
{
private:
int data3;
Base1 b1; //B
Base2 b2;
public:
Derived(int x, int y, int z):Base1(x), Base2(y), b1(x + y), b2(x + z)
{
data3 = z;
cout << data3 << ", Derived Constructor\n";
}
~Derived()
{
cout << data3 << ", Derived Destructor\n";
}
void show()
{
cout << data1 << ',' << data2 << ',' << data3 << endl;
}
};
int main()
{
Derived d(1, 4, 9);
d.show();
return 0;
}
运行结果:
1, Base1 Constructor //初始化基类Base1
4, Base2 Constructor //初始化基类Base2
5, Base1 Constructor //初始化对象成员b1
10, Base2 Constructor //初始化对象成员b2
9, Derived Constructor //初始化派生类Derived
1,4,9
9, Derived Destructor //析构派生类Derived
10, Base2 Destructor //析构对象成员b2
5, Base1 Destructor //析构对象成员b1
4, Base2 Destructor //析构基类Base2
1, Base1 Destructor //析构基类Base1
1.先调用基类的构造函数,再调用对象成员的构造函数,最后执行派生类自身构造函数的函数体。(这句话一定要记牢、记死;经常考经常考!)
2.析构函数的调用顺序与构造函数的调用顺序相反。
3.调用基类构造函数的顺序是由A行继承顺序决定;调用对象成员构造函数的顺序是由B行对象定义决定。
class UStudent:public Student
{
private:
string major;
};
UStudent::UStudent(int number1, string name1, float score1, string major1):Student(number1, name1, major1), major(major1)
{
major = major1; //major会被赋值两次
}
//虚基类
class A
{
};
class B:virtual public A
{
};
class C:public virtual A
{
};
class D:public B, public C //D中只有一份A
{
};
class A
{
public:
int x;
A(int a = 0)
{
x = a;
}
};
class B:public A //类的继承
{
int y;
A a1; //类的组合 (组合关系也可以起到代码重用)
public:
};
//类的 组合和 继承:是代码重用的两种重要方法
//sizeof一个B类对象时,值为12
//区分什么时候用组合什么时候用继承
//尽量重用代码