马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
目次
1.继续的概念以及界说
1.1 继续的概念
1.2 继续的界说
编辑
2.继续中的作用域
3.基类和派生类间的转换
4.派生类的默认成员函数
5.实现不被继续的类
6.継承与友元
编辑
7.继续与静态成员
8.多继续及其菱形继续标题
8.2 虚继续
8.3 来看一个小题
9.继续和组合
学完STL后,咱们再转返来看语法,本日要讲的语法是继续,这也是一个算是比较难的语法,做好了?发车喽,来跟博主一起学习吧!
1.继续的概念以及界说
1.1 继续的概念
什么叫继续呢?继续算是属于对代码的复用。即在保持原有的特性上面,增长一些新的方法以及变量,就叫做继续。那么原有的类叫做基类(父类),增长新的方法以及变量之后的类叫做派生类(子类)。继续 出现了⾯向对象步调计划的条理布局,体现了由简朴到复杂的认知过程。从前我们打仗的函数条理的 复用,继续是类计划条理的复用。
来看这一段代码,那么观察可以发现,Student与Teacher这两个类都是属于派生类(因为它们都继续了基类Person的特性),而Person类属于基类。并且,派生类界说的对象也可以调用基类中的成员函数(这个待会讲)。OK,接下来看界说:
1.2 继续的界说
如上图,Person是基类,也称作父类。Student是派生类,也称作子类。
那么到这就会有人问了:限定符有三种,那么继续方式是不是也有三种?没错,很智慧,确实有三种。
那么访问限定符加上继续方式一组合,是不是有9种组合方式呀。
固然,这里,基类的private成员,根本都是不可见的,(是不可见,不是没继续下来,别搞错了)。那么继续方式怎么看呢?
1.基类的其他成员 在派生类的访问方式==Min(成员在基类的访问限定符,继续方式),public >protected>private。
以是通过这个,咱们之前计划一个类,都是不知道啥计划成public,啥计划成protected,现在大概有点端倪了。
2.基类private成员在派生类中是不能被访问,假如基类成员不想在类外直接被访问,但需要在派生类 中能访问,就界说为protected。可以看出掩护成员限定符是因继续才出现的。
但这里涉及到一个标题,既然private,都不能访问,那我要它做什么?大概说我要private継承有什么用吗?以是说,在这,真正用到的也就两个,即public继续下的两个(基类public成员与基类protected成员)。
3.在现实运用中⼀般使用都是public继续,险些很少使用protetced/private继续,也不提倡使用 protetced/private继续,因为protetced/private继续下来的成员都只能在派生类的类内里使用,实 际中扩展维护性不强。
4.使用关键字class时默认的继续方式是private,使用struct时默认的继续方式是public,不外最好显 示的写出继续方式。
2.继续中的作用域
隐蔽规则:
1. 在继续体系中基类和派生类都有独立的作用域。
2. 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种环境叫隐蔽。 (在派生类成员函数中,可以使用基类::基类成员显示访问)
3. 需要注意的是假如是成员函数的隐蔽,只需要函数名类似就构成隐蔽。
4. 注意在现实中在继续体系内里最好不要界说同名的成员。
看上面的代码图片,可以看出,假如只界说派生类的对象,用派生类的对象调用_num,那么默认调用的是派生类中的_num(就近原则)。若是想调用基类中的_num,必须得在调用的谁人函数里指定作用域。另有一些注意事项,都在上面的代码图片中。反正假如说基类与派生类中有同名的成员函数大概成员变量,优先调用派生类中的(就近原则)。
在这里需要夸大一个东西就是:重载:重载是函数名类似,参数范例大概参数个数不同,并且构成重载的函数必须在同一个作用域中。好比:基类跟派生类是两个不同的作用域,以是不构成重载。
隐蔽:函数名类似并且作用域不在同一个作用域就构成了隐蔽。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)" <<i<<endl;
}
};
int main()
{
B b;
b.fun(10);
b.fun();
return 0;
}
那么看上面的代码,A类与B类中的fun()函数构成了隐蔽(注意不是重载)。并且这个步调实验不了,因为固然第一个函数可以调用,调用了派生类中的fun函数,但是第二个函数在派生类中找不到,那么找不到就无法调用,步调就无法运行。
3.基类和派生类间的转换
1.public继续的派生类对象可以赋值给基类的指针/基类的引用。这里有个形象的说法叫切片大概切 割。寓意把派生类中基类那部门切出来,基类指针或引用指向的是派生类中切出来的基类那部门。
2.基类对象不能赋值给派生类对象。
3.基类的指针大概引用可以通过强制范例转换赋值给派生类的指针大概引用。但是必须是基类的指针 是指向派生类对象时才是安全的。
废话少说,上代码:
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年事
};
class Student : public Person
{
public :
int _No ; // 学号
};
int main()
{
Student sobj ; // 1. 派生类对象可以赋值给基类的指针 / 引用
Person* pp = &sobj;
Person& rp = sobj;
// 派生类对象可以赋值给基类的对象是通过调用反面会讲授的基类的拷贝构造完成的
Person pobj = sobj;
//2. 基类对象不能赋值给派生类对象,这里会编译报错
sobj = pobj;
return 0;
}
这个切片的作用就是将派生类中属于基类的那一部门给切出来,怎么切呢,就通过上面所说的方法即可。 这个切片反面代码有作用,再带各人来进一步的明白。
4.派生类的默认成员函数
这个默认成员函数,照旧类的那六个,因为派生类也属于类嘛。
先来看一段代码吧:
class Person
{
public:
Person(const char*name="peter",const char *telephone="1234")
: _name(name)
,_telephone(telephone)
{
cout << " erson()" << endl;
}
Person(const Person& p)
: _name(p._name)
,_telephone(p._telephone)
{
cout << " erson(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << " erson operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
_telephone = p._telephone;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name="kate"; // 姓名//有初始化列表先走初始化列表,无初始化列表就用
//缺省值,反正,末了都类似的要走初始化列表,并且,若派生类中有对基类的初始化
//还必须得看派生类中的。
string _telephone="0000";
};
class Student : public Person
{
public:
Student(const char* name, const char* telephone, int num)
: Person(name,telephone)//如将基类中的初始化列表屏蔽了再去实验这个
//会发现实验不通,是因为这个就是得调用基类的初始化列表,但是基类的初始化
//列表被咱屏蔽了呀。那肯定会报错的
,_num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
: Person(s)//这个地方要把派生类中的基类那一部门给拿出来
//那么切片,就是将派生类对象赋值给基类的引用或指针,叫切片
//并且这里只需要这么写person(s)。因为上面的基类中是&p,
//以是说意思就是将派生类中的基类的那一部门给切下来给基类,交给他们举行
//初始化即可。固然,若基类中只有一个成员变量,大概你说有点多余,但是基类中
//有很多多少个成员变量呢?直接把他们切下来就非常好用了。
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
// 构成隐蔽,以是需要显⽰调⽤
Person: perator =(s);
_num = s._num;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
int main()
{
Student s1("jack","23456", 18);
Student s2(s1);
Student s3("rose","56789" ,17);
s1 = s3;//按理说派生类的拷贝构造与析构都是不需要本身去写的,只有一个构造需要本身写
//一样平常写一个类后,最好照旧本身实现一下构造函数,即初始化
//若其默认构造函数的活动不符合需求(比方需要特定参数),需显式调用其他构造函数
//但一样平常默认构造函数也就会将它们初始化为空(string)
//接下来,我需要确认std::vector的默认构造函数的界说。根据C++尺度,
// 默认构造函数会创建一个空的vector,没有元素,容量为0。
// 这意味着它不会分配任何内存,只是初始化内部指针为nullptr,巨细和容量为0。
//cplusplus网站上的构造的第一个就是默认构造函数的活动
return 0;
}
1.关于构造函数部门:由于派生类中有基类的那一部门,子类继续父类,创建一个子类对象的时间,这个子类对象中有父类的部门的,是需要调用父类的构造来构造这部门内容的。以是说要先调用基类的构造函数,先完成对基类的构造,之后再构造派生类。
2.派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3.派生类的operator=必须要调用基类的operator=完成基类的复制。需要注意的是派生类的 operator=隐蔽了基类的operator=,以是显示调用基类的operator=,需要指定基类作用域。
4.来看它的析构函数:
这是上面谁人代码的实验效果,观察这个我们发现,有三个一样的~student(),~person(),这个是因为有三个对象,需要析构三次。关键的标题是不是只析构一次吗?为什么会出现两个内容?(~student()与~person())缘故原由是:
这里先调用了派生类的析构函数,之后调用了基类的析构函数,那么为什么先调用派生类的析构函数呢?因为,派生类是依靠基类来实现的吧,以是说,假如先析构了基类,那么依靠基类的派生类中的资源是不是就是不安全的了(因为基类都被析构了),以是为了确保派生类的安全,就先析构派生类,如许可以克制资源安全的标题。
另有一个标题就是,我能不能在这里显示的写一个基类的析构函数,不可以的,缘故原由是,你只要显示写了,大概率会先调用基类的析构函数,那么颠末咱们上面的表明,是不是先调用基类的析构函数不安全。并且,子类的析构函数是比较特别的一个函数,我们不需要显示调用父类析构函数,每个子类析构函数反面,会主动调用父类析构函数。以是说析构函数不需要咱们去管就行了。
另有一个标题:基类的析构函数要与派生类的析构函数名字保持同等。因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名类似(这个多态章节会讲 解)。那么编译器会对析构函数名举行特别处置处罚,处置处罚成destructor(),以是基类析构函数不加 virtual的环境下,派生类析构函数和基类析构函数构成隐蔽关系。
5.实现不被继续的类
这里咱们讲C++11的用法,因为C++11的用法较C++98更简朴。
在基类反面加上final关键字,那么这个基类就不可以被继续了。
6.継承与友元
基类的友元是不可以被继续到派生类中的(就像父亲的朋友不是你的朋友)
这里有两个标题:
1.基类中的友元声明,并不知道student类在哪。那有同砚又说了要是把派生类放基类前面不就可以了吗?那叨教你如许的话,这个継承还怎么继续呢?以是最好的办法就是在开头加上class student,让编译器知道有这个类。
2.假如不在student类中加上友元声明,那么下面的友元函数中的第二个就无法打印,因为类外无法访问类中的protected限定的成员变量,除非也在student类中加上友元声明 。
7.继续与静态成员
基类界说了static静态成员,则整个继续体系内里只有⼀个如许的成员。无论派生出多少个派生类,都 只有⼀个static成员实例。
8.多继续及其菱形继续标题
单继续:⼀个派生类只有⼀个直接基类时称这个继续关系为单继续
多继续:⼀个派生类有两个或以上直接基类时称这个继续关系为多继续,多继续对象在内存中的模子 是,先继续的基类在前⾯,后⾯继续的基类在后⾯,派生类成员在放到末了⾯。
菱形继续:菱形继续是多继续的⼀种特别环境。菱形继续的标题,从下⾯的对象成员模子构造,可以 看出菱形继续有数据冗余和二义性的标题,在Assistant的对象中Person成员会有两份。支持多继续就 ⼀定会有菱形继续,像Java就直接不支持多继续,规避掉了这里的标题,以是实践中我们也是不发起 计划出菱形继续如许的模子的。
这种就是菱形继续标题,那么就会导致Assistant中有两个person中的成员变量,会冗余。并且,你要是调用好比student类继续的person变量,不可以,因为编译器不知道你要调用哪个(因为teacher中,person中都有这个成员变量)。
实在这种也是菱形继续:B继续了A,C继续了B,D继续了A,E继续了C和D,那么E就有了两份A
只要派生类中有两份类似的变量时,都叫做菱形继续,那么在B和D的位置放virtual,缘故原由下面有。
解决办法就是:
1.指定作用域是哪个类。
2.接纳虚继续的方式。
8.2 虚继续
在继续谁人双份的成员变量的第一个派生类中加上virtual即可。virtual加在:后,继续方式前。
那么加上这个后,你再去访问谁人双份的成员变量,访问到的就只有最一开始的基类中的成员变量。要是还想访问类中的谁人成员变量,加上作用域即可。
8.3 来看一个小题
class Base1
{
public:
int _b1;
};
class Base2
{
public:
int _b2;
};
class Derive : public Base1, public Base2
{
public: int _d;
};
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
看这个题,main函数里的看着认识不,没错,就是咱们说的切片,那么这个标题的本质就是,这个切片在内存中是怎么存储的,就是先先继续的内存更靠上 。
p3是指向派生类的指针,自然指向最上面,地点的起始处。而p1是开始继续的,以是也在地点最高处,而p3与p1指向同一个地方,纯属偶合。自然p2的位置就相对于靠后点。
以是说它们几个的关系是: p3==p1!=p2
9.继续和组合
1.public继续是⼀种is-a的关系。也就是说每个派生类对象都是⼀个基类对象。
2.组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
3.继续答应你根据基类的实现来界说派生类的实现。这种通过生成派生类的复用通常被称为白箱复⽤ (white-box reuse)。术语“白箱”是相对可视性而言:在继续方式中,基类的内部细节对派生类可 见。继续⼀定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依 赖关系很强,耦合度高。
4.对象组合是类继续之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来得到。对 象组合要求被组合的对象具有精良界说的接口。这种复⽤风格被称为黑箱复用(black-boxreuse), 因为对象的内部细节是不可见的。对象只以“黑箱”的情势出现。组合类之间没有很强的依靠关 系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
5.优先使用组合,而不是继续。现实只管多去用组合,组合的耦合度低,代码维护性好。不外也不太 那么绝对,类之间的关系就适当继续(is-a)那就⽤继续,另外要实现多态,也必须要继续。类之间的 关系既适适用继续(is-a)也适当组合(has-a),就用组合。
继续的写法
template<class T>
class stack :public vector<T>
{};
组合的写法
template<class T>
class stack
{
public:
vector<T> _v;
};
这里有个耦合度的概念:可以把耦合度明白为接洽,耦合度高就是接洽紧密,耦合度低就是接洽疏松。那么咱们写一个项目,有几百个类,那么使用耦合度低的,照旧耦合度高的?肯定是耦合度低的,你想想,假如说,你在写项目的时间,一个类写错了,那么入过这个项目的耦合度高,会牵连到其他的几个类,乃至说整个项目,导致整个项目会出现修不完的bug,严肃的时间,大概要重写。这个代价资本可黑白常大的。以是说有耦合度低的肯定都会选择耦合度低的。
而上文提到的白箱与黑箱,就是白箱就是可视化程度很高,即 继续,因为继续的话,基类中的全部派生类都可以看到,也就是耦合度高。而黑箱,就是可视化很低,也就说明每个类之间耦合度低,以是说,没有啥接洽,相互之间可看到的代码也就很少,符合类的封装。
OK,可算是讲完了,各人若是发现错误,还请指出,谢谢啦!
本篇完.........................
|