继承与派生
派生类的构成:
(1)继承基类的成员
- 默认情况派生类包含除构造和析构函数外的所有成员。
- C++11可以使用using语句继承基类构造函数,但是只能初始化从基类继承的成员,导致派生类新数据只能类内初始化不能使用构造函数定制,因此一般不使用该继承,一般当派生不定义新数据时使用。语法:using 基类构造函数::基类构造函数;
(2)改造基类的成员 - 派生类中可以定义与基类成员同名新成员,从而将外层同名成员隐藏,但是可以基类名和作用域限定符可以访问隐藏的同名函数(基类的)例:c1.B:: fun1(); c1.A::fun1()。
(3)添加新的成员
不同继承方式的影响
- 派生类成员对基类的访问权限
- 通过派生类对象对基类成员的访问权限
派生类初始化
- 单继承时构造函数语法:
派生类名::派生类名(基类形参,本类成员形参):基类名(参数化列表),本类成员初始化表 { //其他 }; - 多继承派生类构造函数语法:
派生类名::派生类明(参数表):基类名1(初始化参数),基类2(初始化参数)… { //其他 }; - 有继承和组合的派生类的构造函数语法:
派生类名::派生类名(形参表):基类1(参数),基类…,本类成员包括对象成员的初始化表 { //其他 }; 以上不不清晰,如果有连个point对象,那形参里int x ,int y写两次? 还是不清楚呀
派生类的复制构造函数
- 一般编译器会在需要的时候生成隐含的复制构造函数,它先调用基类的复制构造函数,再为派生类新增的成员进行复制。
- 如果自己在派生类定义构造函数,语法如下:
C::C(const C &c1):B(c1){…} 复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造爱函数。基类复制构造函数的形参类型是基类对象的引用,实参可以是派生类对象的引用。
派生类析构
- 构造时先构造基类再构造派生类成员,析构的时候相反,先执行派生类析构函数体再调用基类析构函数。
类型转换
有点懵懂,函数参数是基类类型的指针,可以用派生类对象传入实参:理解来就是说,可以使用派生对对象初始化基类类类型的指针,把派生类对象转化为基类对象,然后因为基类对象除了自己共有接口外,也不知道派生类新定义的接口,固然不会调用,反过来基类对象就不能传给派生类指针,因为派生类印象中是有派生时的特有接口的,如果一旦调用就会在基类对象找不到,简而言之,可以以多充少富余不用,不能以少充多要用没有。
虚基类
概念引出:当派生类从多个基类派生时,而多个基类又又共同的基类,则共同基类成员将在二次派生时冗余等问题。
- 语法:以virtual 说明继承方式
class B: virtual public A 注意:在第一级继承时就要将共同基类设计成虚基类。 - 虚基类及其派生类构造函数
建立对象时所指定的类称最远派生类,虚基类成员由最远派生类构造函数调用虚基类构造函数初始化,中间其他类对虚基类的构造函数调用会被忽略。
多态性
运算符重载
- 重载函数属于静态的多态性,而虚函数、指针、引用与运行时多态性有关。
- 不能重载的运算符有:“?:”、“::”、“.*”、"."。
- 重载后运算符优先级和结合性不会改变。
- 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。
双目运算符重载为成员函数
语法: 函数类型 operator (空格)运算符(形参) { 参数个数比原操作数个数少1(后置++和–除外) } A+B相当于A.operator +(B),operator +是A对象所属类的成员函数,操作数B作为为函数实参。
单目运算符重载为成员函数
如果要重载++或者–等单目运算符,使它可以实现oper++和++oper等表达式,其中oper为A类的对象,则++或–应该被重载为A类的成员函数。由于++oper和oper++都是对++重载,函数均为 operator ++,这样为了区分函数,只能用参数列表区分了,但参数列表也一样,没有参数。故,规定后置++多一个参数即:opetator ++(int)。
#include"stdafx.h"
#include<iostream>
using namespace std;
class Clock
{
private:
int hour, minute, second;
public:
Clock(int hour = 0, int minute = 0, int second = 0);
void showtime();
Clock &operator ++();
Clock operator ++(int);
};
Clock::Clock(int hour , int minute , int second )
{
if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60)
{this->hour = hour;
this->minute = minute;
this->second = second;}
else cout << "Time error" << endl;
}
void Clock::showtime(){
cout << hour << ":" << minute << ":" << second<<endl;}
Clock& Clock::operator ++()
{ second++;
if (second == 60)
{ second -= 60; minute++; if (minute == 60)
{minute -= 60; hour = (hour + 1) % 24 } }
return *this;}
Clock Clock::operator ++(int)
{ Clock old= *this; ++(*this); return old; }
int main()
{ Clock myclock(23, 59, 59);
(myclock++).showtime();
(++myclock).showtime();
return 0;}
运行结果: 23:59:59 换行 0:0:1
运算符重载为非成员函数
- 当运算符的左操作数不是类对象时,就无法将该运算符在类中进行重载,当运算符左操作数是类库对象等我们无法修改的类对象时,也不能修改类库添加操作符重载函数,为了解决以上问题,需要在类外对操作符进行重载,为了让重载函数具有访问类的权限,可以将它在类内声明为友元函数。
- 函数参数个数与原操作数个数相同(后置++、–除外)
- 后置单目运算符++、–形参列表中增加一个int,但不需要写形参名。
虚函数
虚函数、虚析构函数与虚基类
虚表与动态绑定
模板与群体数据
如果重载的函数解决问题的逻辑是一致的、函数体语句相同,只是处理的数据类型不同,那么写多个相同的函数体,是重复劳动,而且还可能因为代码的冗余造成不一致性,为此使用函数模板可以用同种方法处理不同类型的数据。
模板语法
语法: template<class/typename T>
- 只有能够进行函数模板中运算的类型,可以作为类型实参,自定义的类,需要重载模板中的运算符,才能作为类型实参。
类模板
语法(代码为例):
#include <iostream>
#include <cstdlib>
using namespace std;
struct Student {
int id;
float gpa;
};
template <class T>
class Store {
private:
T item;
bool haveValue;
public:
Store();
T &getElem();
void putElem(const T &x);
};
template <class T>
Store<T>::Store(): haveValue(false) { }
template <class T>
T &Store<T>::getElem() {
if (!haveValue) {
cout << "No item present!" << endl;
exit(1);
}
return item;
}
template <class T>
void Store<T>::putElem(const T &x) {
haveValue = true;
item = x;
}
int main() {
Store<int> s1, s2;
s1.putElem(3);
s2.putElem(-7);
cout << s1.getElem() << " " << s2.getElem() << endl;
Student g = { 1000, 23 };
Store<Student> s3;
s3.putElem(g);
cout << "The student id is " << s3.getElem().id << endl;
Store<double> d;
cout << "Retrieving object D... ";
cout << d.getElem() << endl;
return 0;
}
|