C++语言是对C语言的一种增强,而其主要贡献在于,为C语言增加了类和模板等功能,可以帮助实现面向对象编程和代码复用等更方便的功能。
C++语言的类,一般都包含两种成员,分别是成员变量和成员函数。成员变量可以用来表示该类的某些状态,而成员函数则可以用来对这些成员变量进行操作。而对于这些成员而言,最核心的概念当属类的封装和继承的概念。
1. 封装
所谓封装,就是编写一个类对象,只留出用户接口,不需要用户去关心里面的东西是如何构造的,拿来就可以直接使用。这就像一台手机,卖家卖给用户,用户只需要使用手机而不需要知道手机是怎样制造的,同时也不可能拆开看个究竟再自己仿造一个,可以说是一种“黑箱概念”。这既方便了用户的使用,让用户不再挂念内部实现的细节,同时也避免了用户有意地(比如想仿制一个相同的,可能导致知识产权侵权)或无意地(可能导致类的损坏而影响使用)对原有的封装进行修改。
可以通过一个生活中的简单的例子来理解C++实现封装的方法。手机厂商不允许用户擅自篡改手机系统,但手机厂商必须允许用户对手机在可控的范围内进行操作,比如增加或删除一个软件或电话号码。而这样的增加、删除或修改数据一定是不可以篡改系统的。若要实现这样的效果,唯一的办法就是厂商自己来制定一套手机操作的规则,让用户只能使用他提供的规则去操作,这样就避免了所有的对原封装的篡改。与此原理相似地,为了实现封装,C++提供了在类中的两种操作范围(这个用词可能不太妥当):public和private,中文意思可以称为“公有”和“私有”。public的成员函数都是可以通过类对象来直接访问的,可以通过"class_name.class_method(para1,para2)"的语法格式或者使用类指针以"p_class_name->method(para1.para2)"的形式来实现的。如果将成员变量放在public里边,同样可以利用类似的方法访问,即"class_name.class_member"或"p_class_name->class_member"。但根据C++对类封装的原则,不希望提供给用户随意修改修改类内成员变量的权限,因此,C++语言为类提供了private,就是希望类的编写者把该类所涉及的所有数据都放进这个部分,然后在public中编写一些用户接口(公有成员函数),让用户只能通过这些接口来进行操作。
2.继承
我之前写过的一篇讲“派生类的重新定义”的文章简要地介绍过“继承”的概念。由于学习进度,我在这里主要想讲一下protected关键字在public继承中的效果(其他类型的继承我还没有学到,后面会继续补充相关的内容)。
为了更加简明易懂,这里举一个简单的例子来进行说明。
首先定义一个基类“shape”,它包含"private"、"protected"和"public"类型的成员变量和成员函数,并在main函数中创建一个“shape”类的对象s1并调用成员函数来演示各种类型成员之间的访问权限。
之后定义一个从基类“shape”派生出来的“rectangle”类,它在基类的基础上,又增加了自己独有的一些public和private成员变量和成员函数,并试图让这些成员函数去访问其基类中自带的"private"、"protected"和"public"类型的成员变量和成员函数。之后再在main函数中创建一个“rectangle”对象r1,再尝试用它可以调用哪些成员函数和访问哪些成员变量。
下面是测试代码。由于时间关系,这里的代码并没有调用<iostream>库文件的控制台显示的功能,只是通过编译器Visual Studio2017的编译来测试对类内各种成员是否可以访问(直接或间接)。有兴趣的话可以在里面的各个成员函数中添加"cout << "来辅助观察效果。
//本程序可直接粘贴在.cpp文件中运行,
//仅用于了解private和public在类继承中如何对类成员赋予权限,
//编译可以通过即可,运行也不会在屏幕上显示任何信息。
//主要的讲解都在注释当中。
#include <iostream>
using namespace std;
//定义一个基类,名为“图形”
class shape
{
//基类私有成员(包括私有成员变量和私有成员函数)
private:
//基类私有成员变量
double x; //横坐标
double y; //纵坐标
double scale; //尺度
//基类私有成员函数
void reset() //重置横纵坐标和尺度坐标
{
x = y = 0.0;
scale = 1.0;
}
//基类保护成员(包括保护成员变量和保护成员函数)
protected:
//基类保护成员变量
int color; //颜色
//基类保护成员函数
void show_color(){} //显示颜色(空函数)
//基类公有成员(均为成员函数)
public:
shape(double _x, double _y, double _sacle,int _color) //构造函数
:x(_x), y(_y), scale(_sacle),color(_color) {}
virtual ~shape() {} //析构函数
void move_x(double _x) { x = _x; } //移动横坐标
void move_y(double _y) { y = _y; } //移动纵坐标
void resize(double _scale) { scale = _scale; }; //放缩尺度
void normalize() { reset(); } //标准化
void call_show_color(){ show_color(); } //仅用来调用基类保护成员函数
void access_color() { color = 255; } //仅用来访问基类保护成员变量
};
//从“图形”基类生成一个派生类,名为“长方形”
class rectangule
:public shape //继承“图形”基类
{
//派生类私有成员
private:
//派生类私有成员变量
double angle; //长方形朝向角度
double ratio; //长方形的长宽比
//派生类私有成员函数
void set_ratio(double _ratio) { ratio = _ratio; } //设置长宽比
//派生类上共有成员(均为成员函数)
public:
rectangule //构造函数
(double _x,double _y,double _scale,double _angle,int _color)
:shape(_x,_y,_scale,_color),angle(_angle),ratio(1.0){}
virtual ~rectangule(){} //析构函数
void set_angle(double _angle) //设置长方形朝向角度
{
angle = _angle;
}
void set_square() { set_ratio(1.0); } //设置为正方形
void set_long() { set_ratio(2.0); } //设置为2:1长方形
//以下操作为示例操作,在此仅为说明继承中基类的private访问权限。
//void amplify1(){ scale = 10.0;} //将长方形尺度改为10
//由于派生类的public成员函数不可访问scale,失效
void amplify2() { resize(10.0); } //将长方形尺度改为10
//void reset_rect1() { reset(); } //重置长方形
//由于派生类的public成员函数不可访问reset(),失效
void reset_rect2() { normalize(); } //重置长方形
//可见,派生类在基类之上新增加的成员函数,只能直接访问其新增加的
//私有成员函数和私有成员变量,而无法访问其从基类继承来的私有成员
//变量和私有成员函数。
/*此处着重强调一下*/
//很多教材或者博客常用的说法都是“派生类只能继承基类的public部分,
//而无法继承其private部分”。其实这样的说法过于笼统和模糊,容易对
//初学者造成误导。
//更恰当的说法应该是“派生类继承了基类所有的内容,但它在此基础上
//新增加的成员函数无权直接访问它从基类继承来的private部分,它只能
//通过它所继承的基类的public成员函数来间接访问它从基类继承来的
//private成员变量和成员函数。
//为派生类增加public成员函数,试图直接访问它从基类继承来的
//protected的成员变量和成员函数。结果发现,在派生类中新增加的public
//成员函数确实有直接访问基类中protected成员变量和成员函数的权限。
void d_call_show_color() { show_color(); } //
void d_access_color() { color = 244; }
};
//主函数,测试两种类对象对其内部各变量的操作权限
int main()
{
//1. 测试“图形”对象以说明
//一个普通基类对象对其private成员、protected成员和public成员的访问权限。
//类对象s1仅可直接访问以下属于public的成员函数。
shape s1(10, 20, 1.0,127);
s1.move_x(15);
s1.move_y(25);
s1.normalize();
s1.resize(2.0);
s1.~shape();
s1.access_color();
s1.call_show_color();
//s1.x = 22; //成员变量x位于private当中,类对象s1无法直接访问它。
//但s1.move_x(15)这个类对象的public成员函数却可以访问x。
//s1.reset(); //成员函数reset()位于private当中,类对象s1无法直接访问它。
//但s1.normalize()这个类对象的public成员函数却可以访问reset()。
//s1.color = 255; //成员变量color位于protected当中,类对象s1无法直接访问它。
//但s1.access_color()这个类对象的public成员函数却可以访问color。
//s1.show_color(); //成员函数show_color()位于protected当中,
//类对象s1无法直接访问它。
//但s1.call_show_color()这个类对象的public成员函数却可以访问show_color()。
//综上,类对象不可以直接访问private的成员变量和成员函数,
//只能通过类内部的其他成员函数访问它们,
//这就是封装应有的效果。
//2. 测试“长方形”对象以说明
//一个派生类对象对其所继承的基类的private成员、protected成员和public成员,以及
//其新增添的private成员与public成员的访问权限。
//类对象r1仅可以直接访问以下属于public的成员函数。
rectangule r1(11, 21, 1.0, 5.0,127);
//所继承的基类自带的public成员函数
r1.move_x(1.0);
r1.move_y(1.2);
r1.normalize();
r1.resize(2.0);
//派生过程中新添加的public成员函数
r1.amplify2();
r1.reset_rect2();
r1.set_angle(5.1);
r1.set_long();
r1.set_square();
r1.~rectangule();
r1.d_call_show_color();
r1.d_access_color();
system("pause");
return 0;
}
最后,我总结了一张图来表示public继承中的private、protected和public成员之间的访问关系。
以上就是我目前学习到的public继承中所涉及的知识。如果图中有错误,请留言告诉我,谢谢!
至于protected成员的具体用法,我还不是很清楚,会在后面的博客中继续记录下去。