运算符重载
运算符重载的来源
没有重载运算符的例子
需要用专门的函数负责运算
重载运算符的例子
a2作为参数传送了进去
1.本身编译器没有这种 “+”运算规则,所以运算符重载目的就是实现这种功能,且是在类中实现的
2. 运算符的重载从另一个方面体现了OOP技术的多态性,且同一运算符根据不同的运算对象可以完成不同的操作。
重载的参数要求
1.定义的重载函数必须具有不同的参数个数,或不同的参数类型或顺序。只有这样编译系统才有可能根据不同的参数去调用不同的重载函数。
2.仅返回值不同时,不能定义为重载函数。
运算符重载的方法
运算符重载实质就是函数重载
1.运算符重载的函数不仅有新功能,还保留原有的功能
2.运算符重载是通过定义函数实现的
3.运算符重载实质上是函数的重载
4.格式
int operator+(int a,int b){return(a+b);}
函数名就是:operator+
5.运算符重载在类中的应用,是C++功能强大和最吸引人的一个特点;因为原本没有: ”对象+对象“,只是“整型+整型”,而引入运算符重载与类的结合,进而可以实现“对象+对象”
class Complex{
public:
int a;
Complex(int b){a=b;}
Complex operator+(Complex&c2)声明重载运算符函数
};
Complex Complex::operator+(Complex&c2){
Complex c;用来返回相加后的对象
c.a=a+c2.a;本对象a加对象c2的a并赋值到临时对象c的a
return c;
}
int main(){
Comlpex c1(11),c2(22),c3;
c3=c1+c2;
相当于:c3=c1.operator+(c2);
}
重载运算符函数还可以再写得简单点
Complex Complex::operator+(Complex&c2){
return Comlex(a+c2.a);改成这样简便很多
作为构造函数创建对象并返回
}
6.运算符重载仍然保留原有功能,是因为编译系统根据操作数的数据类型和决定实现什么功能
运算符重载规则
1.不能定义新的运算符,只能是C++规定的运算符
2.不能重载的5个运算符
. 保证访问成员的功能不能被改变
* 保证访问成员的功能不能被改变
:: 操作对象是类型,不是变量,不具备重载特征
sizeof 操作对象是类型,不是变量,不具备重载特征
?:
3.重载不能改变操作数个数
4.重载不能改变结合性:左结合,右结合
5.重载不能改变优先级
6.不能有默认参数:否则改变了运算符参数的个数
7.重载运算符与用户定义类的对象一起使用,其参数至少有一个是类对象;也就是说不能全部是标准类型:int等
Complex operator+(int a,int b){
return Complex(a-b);
}
加法运算符试图实现减法运算,如果表达式4+3
那结果是7还是1
所以证明参数不能全部是标准类型
8.两个可以不需要重载就可以在类中应用的运算符:=、&
因为系统默认会提供对象的赋值,和对象地址的重载功能,但是功能比较单一,不满足我们的需求,所以可以自行重载,也可以重载;
9.不能重载运算符的函数是:构造函数
运算符重载作为:类成员函数
1.作为类成员函数,故可以用this指针
在C++中不允许重载有三个操作数的运算符
class Complex{
public:
int a; 作为操作数之一
Complex(int b){a=b;}
Complex operator+(Complex&c2)声明重载运算符函数
{return Complex(a=this.a+c2.a);}
};
2.此项情况下作为类成员函数,即传入参数是数据类型情况下,虽然可以省略一个类对象参数,但必须要求第一个参数是类对象,即左侧操作数
Complex operator+(int&i)参数是数据类型
c3=c2+i 正确 c2.operator+(i)
c3=i+c2 非法 i是标准数据没有成员函数
3.必须作为类成员函数的重载运算符
=
[]
()
->
4.运算符重载函数不能定义为静态的成员函数,因为静态的成员函数中没有this指针。
运算符重载作为:友元函数
1.作为友元函数:友元函数没有this指针,所以这里没法隐藏参数了,双目就得双参数;友元会破坏封装性,原则上不用
class Complex{
public:
int a;
Complex(int b){a=b;}
friend Complex operator+(Complex&c1,Complex&c2)声明重载运算符函数
{return Complex(c1.a+c2.a);}
};
A a,b,c;
c=a+b;
5.必须作为友元函数的运算符
<<
>>
>
6.不能作为友元函数的是
=
()
[]
->
6.一般双目运算符作为友元函数
单目运算符重载:++与- -
1.注意前置还是后置,他们的重载函数的样子是不一样的
A a, b;
b=++a;
b=a++;
2.可以看出,虽然运算后对象a的值一致,但先自加或后自加,虽然都是++,但其重载运算符函数的不一样的,必须在重载时予以区分
赋值运算符重载 “=”
1,同类型的对象间可以相互赋值,等同于对象的各个成员一一赋值。
A a(2,3), b;
b=a;
2.但当对象的成员中使用了动态的数据类型时即用new开辟空间,就不能直接相互赋值,否则在程序的执行期间会出现运行错误。
class A{
char *ps;
public: A( ){ ps=0;}
A(char *s )
{ps =new char [strlen(s)+1]; strcpy(ps,s);}
~A( ){ if (ps) delete ps;}
};
void main(void )
{ A s1("China!"),s2("Computer!");
s2=s1;
}
这时,利用编译系统的默认赋值无法正确运行程序,必须重载赋值运算符“=”,即重新定义“=”。
3.定义格式:赋值运算符重载
A A:: operator = (A &a)
b=a;
b.operator=(a);
例:
class Sample{
int x;
public: Sample(int i=0){x=i;}
void disp(void){ cout<<“x=“<<x<<endl;}
void operator=(Sample &p) { x=p.x; }
};
void main(void)
{
Sample A(20),B;
Sample C(A);//使用缺省的拷贝构造函数
B=A; //使用赋值运算符重载
B.disp();
A.disp();
}
4.一般来说,单目运算符最好被重载为成员函数;对双目运算符最好被重载友元函数
A operator + (A &a,int b) 与
A operator + (int a, A &b) 是不一样的!
需要分别重载,且因为第二个的第一个参数为int,如果重载为
成员函数会默认为本对象的this,不符合,所以最好都重载为
友元函数。
转换函数
1.转换函数就是在类中定义一个成员函数,其作用是将类转换为某种数据类型
2.转换函数必须是类的成员函数
3.转换函数的调用是隐含的,没有参数
4.定义
A :: operator float ( )// A-->float
{ return x+y; }//转换算法
例:
class A
{ int i;
public:public:
A(int a=0) { i=a; }
void Show(void)
{ cout<<"i="<<i<<endl; }
operator int( ){ return i; } 转换函数
};
void main(void)
{ A a1(10),a2(20);
cout<<a1<<endl;
cout<<a2<<endl;
}
5.转换函数只能是成员函数,不能是友元函数。转换函数的操作数是对象。转换函数可以被派生类继承,也可以被说明为虚函数。
继承与派生
1)继承属性总表
- 新类继承旧类,A继承B,则A叫做B的派生类也叫子类,B是A的基类
- A类包含了B类的所有成员函数
- 定义: class A : public B(以公有方式继承)
- 对于从基类继承过来的数据和函数成员,都变成私有成员,若还想用它,则可以通过派生类中(同函数名)重新定义成员达到覆盖基类成员。
- 基类的公用成员派生类可以用,私有成员不可以用,且他们都属于基类的成员。想用基类的私有成员,需用基类的public函数,而公用和保护型则可以直接在类用,注意他们还是属于基类的成员
基类成员继承过来在派生类中的属性表:比如A以 protected 继承B类中的 public 成员,在派生类中则该基类成员数据在派生类中变成 protected成员
7.私有继承基类,若想用子类对象直接访问基类的成员函数,那么子类成员部分必须含有对基类部分的直接调用;
#include<iostream>
#include <cstring>
using namespace std;
class A{
public:
int i;
int fun(int j){cout<<j<<endl;}
};
class B:private A{
public:
A::fun;
这句话不加的话,就是非法的,这个只是点是访问声明的内容
};
int main(){
B d;
d.fun(13);//此fun是A类继承过来的
}
例:车的例子
#include<iostream>
using namespace std;
//交通工具类
class veh
{
int weigth,wheel;
public:
veh(int i,int j)
{
weigth=i;
wheel=j;
}
get_weigth()
{
cout<<weigth<<endl;
}
};
//轿车类
class car:public veh //当改为protected 时 ,下面的get_weigth必须重新定义,因为变成私有属性了不能直接访问了,重新定义会覆盖掉原来的
{
int passagers;
public:
car(int k,int i,int j ):veh(i,j)
{
passagers=k;
}
get_passagers()
{
return passagers;
}
/* get_weigth()
{
cout<<veh::get_weigth()<<"新的";//必须带上原类的作用域
}*/
};
int main()
{
car A(4,20,30);
cout<<A.get_passagers()<<endl;//记得函数加括号
cout<<A.get_weigth()<<endl;//这里要是写成A.weigth就是错的,不能直接访问私有属性,必须通过公有成员作为接口
}
8.在基类定义的protected成员在私有继承的派生类中可见。基类的protect在子类中变成private,对于子类还是可见的,所以基类定义private,并私有继承,那才对于子类不可见
2)继承性概念
1.作用:①提供了无限重复利用程序资源的一种途径。②可以扩充和完善旧的程序设计以适应新的需求
派生类的构造函数
1)普通派生类构造函数
- 基类构造函数和析构函数没法继承,所以需要在子类中负责对基类成员初始化
- 普通基类只能派生一次,不能派生多次(???)
一个子类只能有一个父类(老虎只能属于一个类,不能既是动物也是爬行类,即主要功能类只能是一个,但可以多继承???这样解释ok?感觉不太行)- 派生类中数据成员不可以含基类对象(好像可以)
- 定义:
派生类名(参数):基类名(参数)
Student(int n):people(name)
这里是name参数是实参,是调用构造函数不是定义构造函数,故
不是形参,不用写成带上类型int
- 如果基类中的没有定义构造函数,或者没有参数那么则可以直接省略初始化部分
Student(int n)
- 如果派生类和基类构造函数都无参,则可以不定义派生类构造函数,如果基类传参需要,则必须定义派生类构造函数
例:综合例子
#include<iostream>
using namespace std;
class A
{
int i;
public:
A(int j)
{
i=j;
}
get()
{
return i;
}
};
class B:public A
{
int k;
public:
B(int i,int x):A(i)//子类构造函数初始化基类成员
{
k=x;
cout<<"k= "<<k<<endl;
}
};
int main()
{
B c(4,5);
cout<<"i= "<<c.get()<<endl;
}
2)带子类对象的派生类构造函数
1.用法
Student(int a,int b):people(a),person(b)
// person 是一个people类的对象,并做Student类的成员
3)构造函数和析构函数的调用顺序
- 先调用基类构造函数,再对象成员的构造函数,再子类构造函数
- 构造和析构函数两者执行顺序相反
#include<iostream>
using namespace std;
class data
{
public:
data()
{
cout<<"调用了data构造函数"<<endl;
}
};
class A
{
data v; // a 类中含有data的对象
public:
A()
{
cout<<"调用了 A 构造函数"<<endl;
}
};
class B:public A
{
public:
B():A()
{
cout<<"调用了B 构造函数"<<endl;
}
};
int main()
{
B c;//先执行class A 但A中有data对象所以再从data构造,完了继续A 再到 B
}
调用了data构造函数
调用了 A 构造函数
调用了B 构造函数
4)派生类访问基类的私有成员的方法
派生类没法直接访问基类的私有变量,可以通过以下方法访问基类私有成员:
- 可以把派生类需要访问基类的私有成员改成保护成员。这样它就可以被派生类访问,但对外界隐蔽(缺点就是当在公有继承下,为外界访问该成员提供了机会)
- 将派生类定为基类的友元类,这样派生类就可以访问所有成员
- 也可以部分友元,不需要整个类
5) 通过“访问声明”调整访问域
可以让在基类为公有属性的数据成员,被私有继承或者保护继承后,在派生类用调整访问域,就可以使其属性改回公有属性
- 访问声明仅仅调整名字的访问,不可以说明任何类型,比如 int A::y 这是不行的,成员函数不能任何参数
- 访问声明只能调整基类的保护段和公有段成员,私有是没法改变的
#include<iostream>
using namespace std;
class A
{
int x;
public:
int y;
A(int i)
{
cout<<"调用了 A 构造函数"<<endl;
y=i;
}
};
class B:private A
{ public:
B(int i):A(i)
{ cout<<"调用了B 构造函数"<<endl;
}
A::y;
将私有继承过来变成私有成员的 y,经过访问声明,
变成了公有成员
};
int main()
{
B c(4);
cout<<c.y<<endl;;所以这里才可以通过对象直接访问 y
}
3.对重载函数访问声明将调整基类中具有该名的所有函数的访问域,若重载函数在不同访问域,那么派生类没法调整期访问域
class base
{
public;
void x();
void x(int a);
void x(char *p);
};
class derive::base//没写继承方式默认私有
{
public:
base::x;//基类中所有x 函数都将变成公有
}
4.若派生类中的函数名跟基类的成员函数相同,则基类中该函数不能再派生类中 访问声明,此时基类的同名函数在派生类的作用域中不可见
class base
{
public;
f();
f(int a);
f(char *p);
};
class derive::base
{
public:
void f(int x);
base::f;//作用域不可见
}
int main(){
derive d;
d.f("abcd");
}
/*
注意:此时编译不会报错,但这样代码不健全;若定义derive类的对象d,
此语句d.f("abcd"); 在编译时会出错;d只能访问derive类中的f成员,
而不能访问base类中的f成员,及时写成base::f("abcd");也是错误的
*/
练习:
1.访问声明可以在公有继承派生类中把基类的public成员声明
为private成员(判读:错误,是声明为private成员)
6)派生类调用基类和成员对象构造函数顺序
- 调用基类构造函数,调用顺序按照他们的继承时声明的顺序。
- 调用内嵌成员对象的构造函数,调用顺序按照他们在类中声明的顺序。
多重继承(多继承)
1)定义
- 前面已知,一个子类只有一个基类,定义成多重继承后,则可以一个类有多个基类,而基类仍只能只有一个子类(???不太懂,好像是针对抽象方面的,比如:子类只能有一个父类,但是一个父类可以有多个子类,比如“老师”这个类,子类可以是女老师,也可以是男老师,老师的父类可以是“职位”,你可以说老师有男老师,有女老师,但不能说职位有男老师,女老师,也就是一个has a和is a的关系)
- 定义
class D:public A,private B,protected C;
2)多重继承派生类的构造函数
1.定义:需要对一些成分初始化
class D{
public:D(int a,int b,int c):A(a),D(b),C(c)
}
可以参数形式初始化,也可以{}花括号里面赋值化
3)多重继承的二义性
1.解决二义性的方法:指明作用域,如:Teacher::name
class Teacher{
public:
//这里省略,写构造函数初始化 name
string name;
}
class Student{
public:
//这里省略,写构造函数初始化 name
string name;
}
class Grate:public Teacher,public Studnent{
public:
void show(){
cout<<name; 错误,这里就有二义性了
cout<<Teacher::name; 正确
}
}
2.利用子类覆盖
class Grate:public Teacher,public Studnent{
public:
string name; 那么子类覆盖掉基类的同名成员
void show(){
cout<<name; 正确,访问的是子类的成员
cout<<Teacher::name; 正确
}
}
练习:
1.所谓的二义性,就是指基类与派生类中存在同名成员
(判断:错误)是调用出现二义性
基类与对象成员
1.任一基类在派生类中只能继承一次,否则,会造成成员名的冲突 (如:class B:public A,private A 非法)
2.若在派生类中,确实要有二个以上基类的成员,则可用基类的对象作为派生类的成员。
虚基类
1)定义
派生类A 继承了BCD 类,而BCD类都继承了E类,那么E的成员会在A类中备份多份,各自占用空间,虽然是一样的但他们各自属于B、C、D类,访问只能指定作用域。冗余,所以虚基类应运而生。根本目的是为了消除:二义性
虚基类:就是一个类被定义为虚基类,那么它被多个类直接或间接继承,则只会保留一份,不会备份多份
定义:在派生类的定义中,只要在基类的类名前加上关键字virtual,就可以将基类说明为虚基类。
class E{};
class B:virtual public E{};
class C:virtual public E{};
class D: public E{};
两虚基类只共同保留一份E的数据成员
而D是普通继承,则也保留一份E的数据成员
2)初始化
1.普通类继承是,派生类为基类初始化;而虚基类则是被间接派生类初始化;
2.虚基类构造函数只被调用一次,只在最后派生类中调用,而虚基类直接派生类的调用则被忽略
class E{
E(int i){}
};
class B:virtual public E{
B(int i):E(i){} 并不会调用虚基类构造函数
};
class C:virtual public E{
C(int i):E(i){} 并不会调用虚基类构造函数
};
class A:public B,public C{
A(i):B(i),C(i),E(i){} 会调用虚基类E构造函数
};
例:
#include<iostream>
using namespace std;
class base
{
public:
base()
{
cout<<"这是base"<<endl;
}
};
class base1:virtual public base
{
public:
base1()
{
cout<<"这是base 1"<<endl;
}
};
class base2:virtual public base
{
public:
base2()
{
cout<<"这是base 2"<<endl;
}
};
class level1:virtual public base1
{
public:
level1()
{
cout<<"这是level 1"<<endl;
}
};
class level2:virtual public base2
{
public:
level2()
{
cout<<"这是level 2"<<endl;
}
};
class top:public level1,virtual public level2
{
public:
top()
{
cout<<"这是 top"<<endl;
}
};
int main()
{
top c;
}
结果:
这是base
这是base 1
这是base 2 // 从输出的结果可以看出来,其公共基类的构造函数只调用了一次,并且优先于非虚基类的构造函数调用;
这是level 2 //当公共基类 base 被声明为虚基类,虽然它成为 base1 和 base2 的公共基类,但子派生类只生成一个虚基类的对象
这是level 1//level2先于输出level1是因为top对level2是虚继承
这是 top
3.同个层次的基类,一起执行
将level虚继承去掉
结果
3)多重继承的调用顺序
虚基类 > 一般继承 > 对象 > 本身构造函数 (基于定义顺序同时)
例,
假设有 class A,B, base ,base2, base3,base4, top
class top:virtual public base2,public base1,virtual base3
{//看继承顺序和继承方式
public:
top():base3(),base2(),base1(),obj2(),obj1();
{ //对象看定义顺序
cout<<"top"<<endl;
}
protected:
A obj1; //这是聚合类的对象成员
B obj2;
}
结果
base2
base3
base1
obj1
obj2
top
4)基类与派生类的转换
1.子类型:即public 继承的派生类称为基类的子类型
2.子类对象可以赋值给基类对象,反之不行
3.且该子类对象赋值给基类对象后,会屏蔽掉子类中的成员
class A{ public: int a};
class B:public A{public int b};
int main(){
A a;父类对象
B b;子类对象
a=b;子类给父类对象,正确
b=a;父类给子类对象,非法
a.a=30;父类对象调用父类成员,正确
a.b=30;父类对象调用子类成员,非法,被屏蔽了
b.b=30;子类对象调用子类成员,正确
}
4.基类引用可以被子类对象赋值,但该引用不是子类对象
A a;
B b;
A &r=a;r是父类对象a的别名,与a公用存储空间
A &r=b; 或 r=b;
r不是a也不是b的别名,也不与b共用空间,而是b对象中父类的成员
部分的别名;
5.基类指针可以指向子类对象:即可以将一个派生类对象的地址赋给基类的指针变量
Student a;
People *p;
p=&a;
6.
练习:
5.当不同的类具有相同的间接基类时,()。
(a) 各派生类无法按继承路线产生自己的基类版本
(b) 为了建立惟一的间接基类版本,应该声明间接基类为虚基类
(c) 为了建立惟一的间接基类版本,应该声明派生类虚继承基类
(d) 一旦声明虚继承,基类的性质就改变了,不能再定义新的派生类
组合
1.在一个类中,以另一个类的对象作为数据成员,称为组合
class Teacher{};
Class Birthday();
class Professor:public Teacher{//有组合有基础
public:
Birthday b;//数据成员是对象
}
2.组合和继承都发挥了软件复用性
3.区别①组合是横向的,是“有”的关系:即教授有生日
②继承是纵向的,是“是”的关系:即教授是老师所以,不能说教授是生日,也不能教授有老师
是就得用继承,有就得用组合