【C++基础】面向对象方法(五)

系列文章目录

【C++基础】面向对象方法(一)
【C++基础】面向对象方法(二)
【C++基础】面向对象方法(三)
【C++基础】面向对象方法(四)



一、类的友元

如果普通函数或另一个类中的函数需要经常通过类提供的公有接口来访问类的私有成员或保护成员,为了提高程序运行的效率,可以将他们声明为类的朋友——友元,就可以直接访问类的任何成员了。
友元提供了一个一般函数与类的成员之间、不同类的成员之间进行数据共享的机制。

友元分为3类,用friend关键字来声明。

  1. 友元函数
    将普通函数声明为类的友元函数的形式:
    friend <数据类型><友元函数名>(参数表);

代码如下(示例):

class A
{
	friend int fun(int x);//将普通函数声明为A类的友元函数,则该函数可以访问A类中的任何成员,包括私有成员和保护成员。
};
  1. 友元成员
    将一个类的成员函数声明为另一个类的友元函数,称这个成员函数是友元成员。
    声明友元成员的形式:
    friend <类型><含有友元成员的类名>::<友元成员名>(参数表);

代码如下(示例):

class A
{
	int function(int x);
};
class B
{
	friend int A::function(int x); //将A类中的成员函数声明为B类的友元函数。声明后,A类的成员函数有权访问B类中的任何成员包括私有成员
	//和保护成员。
};
  1. 友类
    将一个类声明为另一个类的友类的语法形式:
    friend<友类名>;

    friend class <友类名>;

代码如下(示例):

/* 将一个类声明为另一个类的友类的语法形式: friend<友类名>; 或 friend class <友类名>; */
class B
{
	friend class A;
};

【例】下面的代码为类的友元实例代码,普通函数getStudentInfo()声明为学生类的友元函数。将教师类的成员函数SetScore()声明为学生类的友元。将管理员类声明为学生类的友类。

代码如下:

//DefineClass.h
//#pragma warning(disable:4996) //不加会报错,第一种解决方案,第二种见cpp
class Student;//类声明
void getStudentInfo(Student& s);//函数声明

class Teacher
{
public:
	void SetScore(Student& s, double sc);
private:
	long m_number;
	char m_name[10];
};

class Manager
{
public:
	void ModifyStudentInfo(Student& s, long, char*, double);
private:
	long m_number;
	char m_name[10];
};

class Student
{
public:
	friend void getStudentInfo(Student& s);//声明友元函数
	friend void Teacher::SetScore(Student& s, double sc);//声明友元成员
	friend class Manager;
	double GetScore()
	{
		return m_score;
	}
private:
	long m_number;
	char m_name[10];
	double m_score;
};
//DefineClass.cpp
#include"DefineClass.h"
#include<iostream>
using namespace std;
void Teacher::SetScore(Student& s, double sc)
{
	s.m_score = sc;
}
void Manager::ModifyStudentInfo(Student& s, long number, char* name, double sc)
{
	s.m_number = number;
	strcpy_s(s.m_name, name); //strcpy()已经被微软认为不安全,两种解决方案,第一种在头文件中,第二种为更改strcpy为strcpy_s
	s.m_score = sc;
}
void getStudentInfo(Student& s)
{
	cout << "学号:" << s.m_number << endl << "姓名:" << s.m_name << endl << "成绩:" << s.m_score << endl;
}
//testFriendMember.cpp
#include<iostream>
#include"DefineClass.h"
using namespace std;
int main()
{
	Teacher t;
	Manager m;
	Student s;
	t.SetScore(s, 85.5);
	m.ModifyStudentInfo(s, 1201201, "周海洋", 95);
	getStudentInfo(s);
	return 0;
}

二、类的对象成员

C++语言为类和对象之间的联系提供了4种方式:

  1. 一个类对象是另一个类的成员。
  2. 一个类的成员函数是另一个类的友元。
  3. 一个类定义在另一个类的说明中,即类嵌套。
  4. 一个类作为另一个类的派生类。

1.对象成员的声明
自定义类的数据成员可以是另一个类的对象,例如类B的对象是类A的一个成员,则该成员就称为类A的对象成员,这就意味着一个A类的"大对象"包含着一个B类的"小对象",也就是说,类B对象属于类A对象。这就是类之间的聚合或组合关系。对象成员的声明与其他成员相同,其语法格式:
类名<对象成员名表>
【例1】Circle类中表示圆心的数据成员m_center是Point类的对象。

代码如下:

//DefineClass.h
class Point //声明点类
{
public:
	Point(double a, double b);
	double GetX();
	double GetY();
private:
	double m_x, m_y;
};
class Circle //声明圆类
{
public:
	Circle(double cx, double cy, double cr);
	void DisplayCircleInfo();
private:
	Point m_center; //对象成员
	double m_radius; //非对象成员
};

2.对象成员的初始化
一个对象数据成员的初始化是通过调用构造函数来完成的,即一个对象成员的初始化是"大对象"被创建时一同被创建的。在定义"大对象"所在类的构造函数时,需要在函数体外通过成员初始化列表将参数传递到对象成员函数的构造函数中。成员初始化列表的格式:
<对象成员1>(<初值表>)[,…,<对象成员n>(<初值表>)]

代码如下:

//DefineClass.cpp
#include"DefineClass.h"
#include<iostream>
using namespace std;
Point::Point(double a, double b)
{
    m_x = a;
    m_y = b;
}
double Point::GetX()
{
    return m_x;
}
double Point::GetY()
{
    return m_y;
}
Circle::Circle(double cx, double cy, double cr) :m_center(cx, cy)
{
    m_radius = cr;
}
void Circle::DisplayCircleInfo()
{
    cout << "圆心为:" << m_center.GetX() << "," << m_center.GetY() << endl;
    cout << "半径为:" << m_radius << endl;
}
//testObjectMember.cpp
#include<iostream>
#include"DefineClass.h"
using namespace std;
int main()
{
	Circle circle(2.3, 4.6, 12.5);
	circle.DisplayCircleInfo();
	return 0;
}

三、自定义类的运算符重载

C++中预定义的运算符的操作对象只能是基本数据类型。对于许多用户自定义类型(例如类)也需要类似的运算操作。这就需要进行运算符重载。运算符重载表现了C++的可扩展性。
运算符函数的定义由关键字operator和其后要重载的运算符符号构成的。如:operator+
运算符重载时需要遵循的规则:

  1. 除了类属关系运算符’’ . “、成员指针运算符” .* “、作用域运算符” :: “、sizeof运算符和三目运算符” ?: "等5种运算符以外,C++中的其他运算符都可以重载。
  2. 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
  3. 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
  4. 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
  5. 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似。

提示:对于自定义类的运算符重载函数,大部分运算符可以将其定义为类的成员函数,也可以将其定义为类的非成员函数,而非成员函数一般采用友元函数形式。
(1)类成员函数形式的运算符重载
成员函数形式的运算符函数定义的一般形式:
<返回类型说明符>operator<运算符符号>(<参数表>)
{
<函数体>
}

提示:当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后增、后减单目运算符除外)。

调用成员函数运算符的形式如:
<对象名><运算符><参数>
它等价于
<对象名>operator<运算符>(<参数>)
【例1】利用成员运算符重载函数实现两个复数对象的加法计算

代码如下:

//Complex.h
class Complex
{
public:
	Complex();
	Complex(double r, double i);
	Complex operator+(Complex& rc);
	void Display();
private:
	double m_real;
	double m_imag;
};

//Complex.cpp
#include"Complex.h"
#include<iostream>
using namespace std;
Complex::Complex()
{
	m_real = 0;
	m_imag = 0;
}
Complex::Complex(double r, double i)
{
	m_real = r;
	m_imag = i;
}
Complex Complex::operator+(Complex& rc)
//运算符重载函数定义(类的成员函数形式的运算符重载)
{
	Complex c;
	c.m_real = m_real + rc.m_real;
	c.m_imag = m_imag + rc.m_imag;
	return c;
}
void Complex::Display()
{
	cout << "(" << m_real << "," << m_imag << "i)" << endl;
}
//testOperatorOverload.cpp
#include<iostream>
#include"Complex.h"
using namespace std;
int main()
{
	Complex c1(1, 2), c2(3, 4), c3;
	c3 = c1 + c2;
	cout << "c1=";
	c1.Display();
	cout << "c2=";
	c2.Display();
	cout << "c3=c1+c2=";
	c3.Display();
	return 0;
}

(2)类友元形式的运算符重载
为了方便,类非成员函数形式的运算符重载函数一般采用友元函数。运算符重载为类的友元函数,需要在类内进行声明。声明类的友元运算符重载的形式:
friend<函数类型>operator<运算符>(<参数表>)

提示:当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左向右一一对应。

调用友元函数运算符的形式如下:
<参数1><运算符><参数2>
等价于
operator<运算符>(<参数1>,<参数2>)

利用友元运算符重载函数实现两个负数对象的加法计算的代码如下:

//Complex.h
class Complex
{
public:
	Complex();
	Complex(double r, double i);
	friend Complex operator+(Complex& rc1, Complex& rc2);
	void Display();
private:
	double m_real;
	double m_imag;
};
//Complex.cpp
#include<iostream>
#include"Complex.h"
using namespace std;
Complex::Complex()
{
	m_real = 0;
	m_imag = 0;
}
Complex::Complex(double r, double i)
{
	m_real = r;
	m_imag = i;
}
Complex operator+(Complex& rc1, Complex& rc2)
{
	Complex c;
	c.m_real = rc1.m_real + rc2.m_real;
	c.m_imag = rc1.m_imag + rc2.m_imag;
	return c;
}
void Complex::Display()
{
	cout << "(" << m_real << "," << m_imag << "i)" << endl;
}
//testOperatorOverload.cpp
#include<iostream>
#include"Complex.h"
using namespace std;
int main()
{
	Complex c1(1, 2), c2(3, 4), c3;
	c3 = c1 + c2;//等价于c3=operator+(c1,c2);
	cout << "c1=";
	c1.Display();
	cout << "c2=";
	c2.Display();
	cout << "c3=c1+c2=";
	c3.Display();
	return 0;
}

在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的,采用何种形式,可参考的规则:

  1. 一般情况下,单目运算符最好重载为类的成员函数;双目运算符最后重载为类的友元函数。
  2. 以下一些双目运算符只能重载为类的成员函数:=、()、[]、->。
  3. 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。
  4. 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,只能选用友元函数。
  5. 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数只能作为一个友元函数来实现。
  6. 当需要重载运算符具有可交换性时,选择重载为友元函数。

总结

以上就是今天记录的内容,本文仅仅简单介绍了类的友元、类的对象成员及自定义类的运算符重载

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值