C++(四):运算符重载与继承派生

运算符重载

运算符重载的来源

在这里插入图片描述

没有重载运算符的例子
需要用专门的函数负责运算这里是引用
重载运算符的例子在这里插入图片描述
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)继承属性总表

  1. 新类继承旧类,A继承B,则A叫做B的派生类也叫子类,B是A的基类
  2. A类包含了B类的所有成员函数
  3. 定义: class A : public B(以公有方式继承)
  4. 对于从基类继承过来的数据和函数成员,都变成私有成员,若还想用它,则可以通过派生类中(同函数名)重新定义成员达到覆盖基类成员。
  5. 基类的公用成员派生类可以用,私有成员不可以用,且他们都属于基类的成员。想用基类的私有成员,需用基类的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)普通派生类构造函数

  1. 基类构造函数和析构函数没法继承,所以需要在子类中负责对基类成员初始化
  2. 普通基类只能派生一次,不能派生多次(???)
    一个子类只能有一个父类(老虎只能属于一个类,不能既是动物也是爬行类,即主要功能类只能是一个,但可以多继承???这样解释ok?感觉不太行)
  3. 派生类中数据成员不可以含基类对象(好像可以)
  4. 定义:
 派生类名(参数):基类名(参数)
 Student(int n):people(name)
 这里是name参数是实参,是调用构造函数不是定义构造函数,故
 不是形参,不用写成带上类型int
  1. 如果基类中的没有定义构造函数,或者没有参数那么则可以直接省略初始化部分
Student(int n)
  1. 如果派生类和基类构造函数都无参,则可以不定义派生类构造函数,如果基类传参需要,则必须定义派生类构造函数

例:综合例子

#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)构造函数和析构函数的调用顺序

  1. 先调用基类构造函数,再对象成员的构造函数,再子类构造函数
  2. 构造和析构函数两者执行顺序相反
#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)派生类访问基类的私有成员的方法

派生类没法直接访问基类的私有变量,可以通过以下方法访问基类私有成员:

  1. 可以把派生类需要访问基类的私有成员改成保护成员。这样它就可以被派生类访问,但对外界隐蔽(缺点就是当在公有继承下,为外界访问该成员提供了机会)
  2. 将派生类定为基类的友元类,这样派生类就可以访问所有成员
  3. 也可以部分友元,不需要整个类

5) 通过“访问声明”调整访问域

可以让在基类为公有属性的数据成员,被私有继承或者保护继承后,在派生类用调整访问域,就可以使其属性改回公有属性

  1. 访问声明仅仅调整名字的访问,不可以说明任何类型,比如 int A::y 这是不行的,成员函数不能任何参数
  2. 访问声明只能调整基类的保护段和公有段成员,私有是没法改变的
#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. 调用基类构造函数,调用顺序按照他们的继承时声明的顺序。
  2. 调用内嵌成员对象的构造函数,调用顺序按照他们在类中声明的顺序。

多重继承(多继承)

1)定义

  1. 前面已知,一个子类只有一个基类,定义成多重继承后,则可以一个类有多个基类,而基类仍只能只有一个子类(???不太懂,好像是针对抽象方面的,比如:子类只能有一个父类,但是一个父类可以有多个子类,比如“老师”这个类,子类可以是女老师,也可以是男老师,老师的父类可以是“职位”,你可以说老师有男老师,有女老师,但不能说职位有男老师,女老师,也就是一个has a和is a的关系)
  2. 定义
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 Gratepublic Teacher,public Studnent{
	public:
		void show(){
				cout<<name; 错误,这里就有二义性了
				cout<<Teacher::name; 正确
	}
}

2.利用子类覆盖
在这里插入图片描述

class Gratepublic 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.区别

①组合是横向的,是“有”的关系:即教授有生日
②继承是纵向的,是“是”的关系:即教授是老师

所以,不能说教授是生日,也不能教授有老师
是就得用继承,有就得用组合

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值