文章目录
- 一. 友元(friend)
- 1 友元函数
- 1.1 全局函数作为友元函数
- 1.2 类的成员函数作为友元函数(了解)
- 2. 友元类
- 3. 使用友元的注意事项
- 二. 常成员函数和常对象(const)
- 1. 常成员函数
- 2. 常对象
- 3. mutable关键字
- 三. 运算符重载
- 1. 定义
- 2. 重载的方法
- 3. 运算符重载要求
- 4. 调用时机及调用原则
- 5. 运算符重载的格式
- 5.1 算术类运算符重载(双目运算符)
- 5.2 赋值类运算符重载
- 5.3 关系运算符重载
- 5.4 单目运算符重载
- 5.4 单目运算符重载
- 5.5 自增、自减运算符重载
- 5.6 插入、提取运算符重载
- 5.6 不能重载的运算符
- 5.7 运算符重载注意事项
- 5.6 不能重载的运算符
- 5.7 运算符重载注意事项
一. 友元(friend)
友元是一种定义在类外部的普通函数或类
1 友元函数
1.1 全局函数作为友元函数
声明一个全局函数作为类的友元函数,则允许该全局函数,访问类中各个权限下的成员
在类中要将该函数进行声明:friend 全局函数头;
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu() {}
Stu(string n, int a) : name(n), age(a) {}
void show()
{
cout << "name = " << name << " age = " << age << endl;
}
friend void display(Stu s); // 在类中将全局函数声明成类的友元函数
};
// 定义全局函数
void display(Stu s)
{
cout << "name = " << s.name << " age = " << s.age << endl; // 正常的全局函数不能访问类中私有属性
}
int main()
{
display(Stu("zhangpp", 18));
return 0;
}
1.2 类的成员函数作为友元函数(了解)
-
声明一个其他类的成员函数作为自己类的友元函数,则允许让该函数访问自己类中的所有成员
-
要求该函数必须类内声明类外定义
2. 友元类
-
在一个类中声明另一个类当做友元类,则允许友元类中所有成员访问自己的所有权限下的成员
-
声明格式:friend class 类名;
#include <iostream>
using namespace std;
class Stu; // 类的前置声明
class Teacher // 老师类
{
private:
string sub;
public:
Teacher() {}
Teacher(string s) : sub(s) {}
void show(Stu s); // 类内声明
};
class Stu // 学生类
{
private:
string name;
int age;
public:
Stu() {}
Stu(string n, int a) : name(n), age(a) {}
void show()
{
cout << "name = " << name << " age = " << age << endl;
}
friend void display(Stu s); // 在类中将全局函数声明成类的友元函数
// friend void Teacher::show(Stu s); //声明老师类中的成员函数作为友元函数
// 将整个Teacher类当做友元类
friend class Teacher;
};
// 定义全局函数
void display(Stu s)
{
cout << "name = " << s.name << " age = " << s.age << endl; // 正常的全局函数不能访问类中私有属性
}
// 老师类中的成员函数的定义
void Teacher::show(Stu s)
{
cout << "Teacher::sub = " << sub << endl;
cout << "Stu::name = " << s.name << endl; // 其他类不能访问类中私有成员
}
int main()
{
display(Stu("zhangpp", 18));
Teacher t1("C++");
t1.show(Stu("zhangpp", 18));
return 0;
}
3. 使用友元的注意事项
-
友元具有方向性,A把B当做朋友,允许B访问A的所有权限,但是A不一定能访问B的所有权限
-
友元不具有交换性:A是B的朋友,但B不一定是A的朋友
-
友元不具有传递性:A是B的朋友,B是C的朋友,则A不一定是C的朋友
-
友元不具有继承性:父类的朋友不一定是子类的朋友
-
由于友元的出现,破坏了了类的封装性,使得访问权限形同虚设。所以不在万不得已的情况下,尽可能少用友元
-
必须使用友元的情况:插入和提取运算符重载。(后期讲)
二. 常成员函数和常对象(const)
1. 常成员函数
由const修饰的成员函数叫常成员函数
格式:
-
返回类型 成员函数名(参数表) const;
例如:int function(int x) const
特点:
-
在常成员函数中,不能修改成员变量的值(保护成员变量不被修改)
-
类中同名的常成员函数和非常成员函数构成重载关系,原因是隐藏的this指针的类型不同
非常成员函数中: Stu * const this 常成员函数中: const Stu * const this
-
非常对象,优先调用非常成员函数,如果没有非常成员函数,则去调用同名的常成员函数
-
常对象只能调用常成员函数,没有常成员函数会报错
2. 常对象
const修饰的对象为常对象
-
常对象只能调用常成员函数,不能调用非常成员函数
-
当一个常引用的目标为非常对象,则通过引用只能调用常成员函数,通过对象优先调用非常成员函数
3. mutable关键字
功能:
取消成员常属性
使用方式:
定义变量前加mutable,那么,该变量就能在常成员函数中被修改
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
mutable int age; // 由mutable关键字修饰的成员变量,能在常成员函数中被修改
double score;
public:
Stu() {}
Stu(string n, int a, double s) : name(n), age(a), score(s) {}
// 有const修饰的成员函数就是常成员函数
void show() const // const Stu * const this;
{
// this = nullptr;
// this->score = 50; // 在常成员函数中不能修改成员变量的值
this->age = 100; // 可以更改,因为有关键字修饰
cout << "name = " << name << endl;
cout << "age = " << age << endl;
cout << "score = " << score << endl;
cout << "AAAAAAAAAAAAAAAAAAAAAA" << endl;
}
void show() // Stu * const this;
{
this->score = 50; // 在常成员函数中不能修改成员变量的值
cout << "name = " << name << endl;
cout << "age = " << age << endl;
cout << "score = " << score << endl;
cout << "BBBBBBBBBBBBBBBBBBBBBBBB" << endl;
}
};
int main()
{
Stu s1("张三", 20, 99);
s1.show(); // 50
const Stu &r = s1;
r.show(); // 常成员函数
s1.show(); // 优先调用非常成员函数
return 0;
}
三. 运算符重载
单、算、关、逻、条、赋、逗
1. 定义
所谓运算符重载,就是给运算符新的含义,能够实现“一符多用”,也是属于静态多态的一种,
他能够实现将原本加载到基本数据类型的运算符,在自定义类对象减使用。
好处:能够使得代码更加简洁、易懂,优雅好看
2. 重载的方法
统一的名称:
operator# //#表示运算符
3. 运算符重载要求
功能:
实现运算符对应的操作
参数:
由运算符本身决定
返回值:
由用户自己决定
4. 调用时机及调用原则
调用时机:当使用该运算符时,系统自动调用,无需手动调用
调用原则:左调右参 //a = b; a.operator=(b)
5. 运算符重载的格式
每种运算符都有两个版本的格式:
-
成员函数版本,类对象本身就是一个参数,形参个数是操作数个数-1
-
全局函数版,需要使用友元完成,此时参数个数等于操作数个数
以上两个版本的重载函数,只能实现一个,否则调用时会混乱报错
5.1 算术类运算符重载(双目运算符)
种类:
+、-、*、/、%。。。
表达式:
L#R //L表示左操作数,#表示运算符,R表示右操作数
-
左操作数:既可以是左值也可以是右值
-
右操作数:既可以是左值也可以是右值
-
结果:右值
定义格式:
- 成员函数版:const 类名 operator#( const 类名 &R ) const
第一个const:保护返回结果不被改变
第二个const:保护右操作数不被修改
第三个const:保护左操作数不被修改
- 全局函数版:const 类名 operator#(const 类名 &L, const 类名 &R)
5.2 赋值类运算符重载
种类:
=、-=、*=、/=、%=、+=。。。
表达式:
L#R //L表示左操作数,#表示运算符,R表示右操作数
-
左操作数:只能是左值
-
右操作数:既可以是左值也可以是右值
-
结果:右值
定义格式:
-
成员函数版: 类名 & operator#( const 类名 &R )
-
全局函数版: 类名 & operator#(类名 &L, const 类名 &R)
5.3 关系运算符重载
种类:
>=、<=、!=、>、<、==。。。
表达式:
L#R //L表示左操作数,#表示运算符,R表示右操作数
-
左操作数:既可以是左值也可以是右值
-
右操作数:既可以是左值也可以是右值
-
结果:bool类型的右值
定义格式:
-
成员函数版: const bool operator#( const 类名 &R ) const
-
==全局函数版: const bool operator#( const 类名 &L,const 类名 &R ) ==
5.4 单目运算符重载
种类:
&、!、-(负号)。。。
表达式:
#O //#表示运算符 O表示操作数
-
操作数:既可以是左值也可以是右值
-
结果:右值
定义格式:
-
成员函数版: const 类名 operator#( void ) const
-
全局函数版: const 类名 operator#( conts 类名 &O)
5.4 单目运算符重载
种类:
&、!、-(负号)。。。
表达式:
#O //#表示运算符 O表示操作数
-
操作数:既可以是左值也可以是右值
-
结果:右值
定义格式:
-
成员函数版: const 类名 operator#( void ) const
-
全局函数版: const 类名 operator#( conts 类名 &O)
5.5 自增、自减运算符重载
前置自增:++a
表达式:
#O //#表示运算符 O表示操作数
操作数:只能是左值
结果:左值,自身的引用
定义格式:
成员函数版: 类名 & operator#( )
全局函数版: 类名 & operator#( 类名 &O )
后置自增:a++
表达式:
O# //#表示运算符 O表示操作数
操作数:只能是左值
结果:右值
定义格式:
成员函数版: 类名 operator#( int )
全局函数版: 类名 operator#( 类名 &O , int)
5.6 插入、提取运算符重载
- cin和cout的来源
namespace std
{
extern istream cin; /// Linked to standard input
extern ostream cout; /// Linked to standard output
}
从这两个类对象可以看出,cin和cout来自于内置对象,想要对<<和>>进行重载时,如果想要实现成员函数版,则需要多istream和ostream类进行修改,难度较大。
此时,我们可以使用全局函数版实现这两个运算符的重载,将该全局函数设置成友元函数即可
因为要用到istream和ostream的类对象和自定义类对象,原则上来说需要在两个类中都要设置成友元函数
但是,在运算符重载函数中,只会对自定义的类对象访问私有成员,而不会对istream和ostream的类访问私有成员
所以,最终只需要在自定义类中,将全局函数设置成友元函数即可
表达式:
L#R (L是cin或cout #是<<或者>> R是自定义的类对象)
-
左操作数:istream和ostream的类对象
-
右操作数:自定义的类对象
-
结果:左操作数自身的引用
格式:
-
ostream &operator<<(ostream &L, const 类名 &O);
-
istream &operator>>(istream &L, const 类名 &O);
5.6 不能重载的运算符
-
成员运算符 .
-
成员指针运算符 .*
-
条件表达式 ?:
-
求字节运算 sizeof
-
作用域限定符 ::
5.7 运算符重载注意事项
-
运算符重载只能在已有的运算符基础上进行重载,不能凭空捏造一个运算符
-
运算符重载不能更改运算符的本质:如不能在加法运算符重载中实现减法
-
运算符重载不能改变运算符的优先级
-
运算符重载不能改变运算符的结合律
-
运算符重载函数不能设置默认参数
L#R (L是cin或cout #是<<或者>> R是自定义的类对象)
-
左操作数:istream和ostream的类对象
-
右操作数:自定义的类对象
-
结果:左操作数自身的引用
格式:
-
ostream &operator<<(ostream &L, const 类名 &O);
-
istream &operator>>(istream &L, const 类名 &O);
5.6 不能重载的运算符
-
成员运算符 .
-
成员指针运算符 .*
-
条件表达式 ?:
-
求字节运算 sizeof
-
作用域限定符 ::
5.7 运算符重载注意事项
-
运算符重载只能在已有的运算符基础上进行重载,不能凭空捏造一个运算符
-
运算符重载不能更改运算符的本质:如不能在加法运算符重载中实现减法
-
运算符重载不能改变运算符的优先级
-
运算符重载不能改变运算符的结合律
-
运算符重载函数不能设置默认参数
-
成员函数版的参数要比全局函数版的参数少一个,原因是对象本身就是一个参数