运算符重载为非成员函数(友元)
规则:
• 函数的形参代表依自左至右次序排列的各操作数(所有操作数)。
• 重载为非成员函数时:
○ 参数个数=原操作数个数(后置++、--除外)
○ 至少应该有一个自定义类型的参数。
• 后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
• 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元(比通过类的公有接口访问私有数据成员效率高)。
双目运算符(双目运算符有两个操作数,通常在运算符的左右两侧,)
双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数,因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。
<对象名>.operator <运算符>(<参数>) //obj1.operator<运算符>(OBJ obj2)
等价于
<对象名><运算符><参数> //obj1<运算符>obj2
例如 a+b 等价于 a.operator+(b)
单目运算符重载:
逻辑非运算符:!、按位取反运算符:~、自增自减运算符:++, - - 等
前置单目:
<运算符> obj
相当于
obj.operator<运算符>();
例如:
++a 相当于 a.operator++();
后置单目:运算符重载为类的成员函数时,函数要带有一个整型形参。
obj <运算符>
相当于
obj.operator<运算符>(int);
例如:
a++ 相当于 a.operator++(int);
非成员函数:
friend 函数类型 operator 运算符(参数表){
函数体;
}
双目运算符 B重载后,
表达式 oprd1 B oprd2
等同于 operator B(oprd1,oprd2 )
前置单目运算符 B重载后,
表达式 B oprd
等同于 operator B(oprd )
后置单目运算符 ++和--重载后,
表达式 oprd B
等同于operator B(oprd,0 )
举例:
//前置++运算符重载
friend 函数类型 & operator++(类类型 &);
//后置++运算符重载
friend 函数类型 & operator++(类类型 &,int);
初识虚函数:
一般成员函数可以是虚函数
构造函数不能是
析构函数可以是
作用:
实现多态,父类指针指向子类对象,让函数结果实现在编译时或者运行时决定。大体就是让指向父类型的指针指向继承它的子类的函数,从而达到多态性,声明为虚函数,在派生类中要重新定义,如果父类指针单指函数,就表现为子类的特征,如果加入限定符,就表现为某一类中的虚函数特性。
虚函数成员:
声明:
virtual 函数类型 函数名(形参表);
虚函数声明只能出现在类定义的函数原型声明中,不能在函数定义的时候
在派生类中可以对基类的成员函数(添加virtual关键字的虚函数)进行覆盖
虚函数一般不声明内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的
虚表:
每一个多态类有一个虚表(virtual table)
虚表中有当前类的各个虚函数的入口地址
每一个对象有一个指向当前类的虚表的指针(虚指针vfptr)
动态绑定的实现:
构造函数中为对象的虚指针赋值
通过多态类型的指针或引用调用成员函数时
通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
通过该入口地址调用虚函数
抽象类:
• 纯虚类纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:
• virtual 函数类型 函数名(参数表) = 0;//不表示函数运行结果为0,表示它没有函数体
• 带有纯虚函数的类称为抽象类
class 类名
{
virtual 类型 函数名(参数表)=0;
//其他成员……
}
抽象类只能作为基类来使用
override:
多态行为的基础:基类声明虚函数,继承类声明一个函数覆盖该虚函数
覆盖要求: 函数签名(signature)完全一致
函数签名包括:函数名 参数列表 const
final:
C++11提供的final,用来避免类被继承,或是基类的函数被改写
格式:
virtual 函数类型 函数名(形参列表)final ;
避免被派生类继承
虚基类是解决类成员标识二义性和信息冗余问题的类,是实现动态多态性的基础
第九章 模板与群体数据(相同类型数据)
如果只是处理数据类型不同,推荐使用模板
例子:
template<typename或者class T>//函数模板
T abs(T x) {
return x<0?-x:x;
}
注意:
一个函数模板并非自动可以处理所有类型的数据
只有能够进行函数模板中运算的类型,可以作为类型实参
自定义的类,需要重载模板中的运算符,才能作为类型实参
类模板:
主要目的就是能够处理不同类型的数据
声明:
template <class T>
class 类名{类内声明}
在类模板以外定义其他成员函数,采取:
template <class T>
T 类名<模板参数 标识符列表>::函数名(参数表)
群体概念:
• 群体是指由多个数据元素组成的集合体。群体可以分为两个大类:线性群体和非线性群体。
• 线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。
• 非线性群体不用位置顺序来标识元素。
• 线性群体中的元素次序与其逻辑位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为直接访问、顺序访问和索引访问。
直接访问:线性群体——数组类
静态数组具有固定元素个数,通过下标直接访问
缺点:编译时大小确定,运行时不能更改大下
动态数组可以扩充位置,由任意数量相同类型元素组成
优点:程序运行时可以更改
为什么有的函数返回引用?
如果一个函数返回值是一个对象的值,就是右值,不能成为左值,类似于交换函数
如果返回的是引用,引用是对象的别名, 通过返回引用可以更改对象的值,因此是左值
顺序访问的线性群体——链表类
• 链表是一种动态数据结构,可以表示此类群体
• 链表是由一系列节点组成,节点可以在运行时动态生成
• 每一个节点包括数据域和指向链表下一个结点的指针(),如果链表每一个节点中只有一个指向后继节点的指针,则该链表为单链表
栈是只能从一端访问的线性群体,是一种后进先出的数据结构
栈的三种状态:栈空、栈满、一般状态
栈的基本操作:初始化、入栈、出栈、清空栈、访问栈顶元素、检测栈的状态
队列是只能向一端添加元素,从另一端删除元素的线性群体
队列的三种状态:队空、队满、一般状态
循环队列:在想象中将数组弯曲成环形,元素出队时,后续元素不移动,每当队尾达到数组最后一个元素时,便再回到数组开头,将存储空间的第一个位置作为队尾。可以防止伪溢出的发生。
数据元素:
关键字:
排序:
两种操作:
○ 比较两个数据的大小
○ 调整元素在序列中的位置
插入排序思想:每一步将待排序元素按关键字值的大小插入到已排序的适当位置上
选择排序思想:每次从待排序序列中选择关键序最小的元素,(当需要按照关键字升序排列时)顺序排在已排序的最后,直到全部排完
交换排序思想:两两比较待排序中的元素,并交换不满足排序顺序要求的各对元素,直到全部满足顺序要求为止