1、友元的两种形式(friend)
功能上,这两种形式,都是相同,应用场合不同。
一个是,使用普通的全局函数,作为自己的朋友,实现特殊功能。
一个是,使用其他类的成员函数,作为自己的朋友,实现特殊功能
1、友元函数
friend void upgrade(Computer* computer);
--------------------------------------------------------------
void upgrade(Computer* computer) {
computer->cpu = "i9"; //直接访问对象的私有数据成员!!!
}
2、友元类
作用:如果A类是B的的友元类
那么A类的所有成员函数【在A类的成员函数内】,可以直接访问【使用】B类的私有成员,
即,友元类可以直接访问对应类的成员
// 友元类
friend class ComputerService;
-------------------------------------
void ComputerService::upgrade(Computer* computer) {
computer->cpu = "i9";
}
注意:友元类和友元函数,使用friend声明即可,与访问权限无关,所以放在public、private,protected任意区域。
2、运算符重载+
1、使用成员函数重载运算符
class Pork;
class Goat;
class Cow
{
Pork operator+(const Cow& cow); //同类型进行运算,很频繁
Pork operator+(const Goat& goat); //不同类型进行运算,比较少见
}
---------------------------------------------------------------
Cow c1(100);
Cow c2(200);
Pork p = c1 + c2;
Goat g1(100);
p = c1 + g1;
2、使用友元函数重载运算符
class Pork;
class Goat;
class Cow
{
friend Pork operator+(const Cow& cow1, const Cow& cow2);
friend Pork operator+(const Cow& cow1, const Goat& goat);
}
两种方式区别:
使用成员函数实现运算符时少些一个参数,第一个参数就是this指针;
两种方式的选择:
1、一般情况下,单目运算符重载,使用成员函数重载更方便(不用写参数);
2、一般情况下,双目运算符使用友元函数更直观,方面试验A+B或B+A,成员函数无法实现。
特殊情况:
1、=、()、[]、->不能重载为友元函数(可能与C++其他规则冲突),只能使用成员函数重载;
2、如果运算符的第一个操作数使用隐式类型转化,则必须使用友元函数(成员函数第一个是this指针)。
注意:同一个运算符重载,不能使用两种方式重载,否则编译器不知道使用哪种方式(二义性)。
3、运算符重载禁区与规则
- 为了防止对标准类型进行运算符重载,
C++规定重载运算符的操作对象至少有一个不是标准类型,而是用户自定义的类型
比如不能重载 1+2
但是可以重载 cow + 2 和 2 + cow // cow是自定义的对象
2.不能改变原运算符的语法规则, 比如不能把双目运算符重载为单目运算
-
不能改变原运算符的优先级
-
不能创建新的运算符,比如 operator**就是非法的, operator*是可以的
-
不能对以下这四种运算符,使用友元函数进行重载
= 赋值运算符,()函数调用运算符,[ ]下标运算符,->通过指针访问类成员
- 不能对禁止重载的运算符进行重载
不能被重载的运算符
成员访问 | . |
---|---|
域运算 | :: |
内存长度运算 | sizeof |
三目运算 | ? : : |
预处理 | # |
可以被重载的运算符
双目运算符 | + - * / % |
---|---|
关系运算符 | == != < <= > >= |
逻辑运算符 | && || ! |
单目运算符 | +(正号) -(负号) *(指针) &(取地址) ++ – |
位运算 | & | ~ ^ <<(左移) >>(右移) |
赋值运算符 | = += -= *= /= %= &= |= ^= <<= >>= |
内存分配 | new delete new[ ] delete[ ] |
其他 | ( ) 函数调用-> 成员访问 [ ] 下标, 逗号 |
4、赋值运算符重载=
Boy& operator=(const Boy& boy);
--------------------------------------
Boy& Boy::operator=(const Boy& boy)//返回引用类型便于连续赋值,const保护实参不被破坏
{
if (name) {
delete name; //释放原来的内存
}
name = new char[strlen(boy.name) + 1]; //分配新的内存、使用深拷贝
strcpy_s(name, strlen(boy.name)+1, boy.name);
this->age = boy.age;
this->salary = boy.salary;
this->darkHorse = boy.darkHorse;
//this->id = boy.id; //根据需求来确定是否要拷贝id
return *this;
}
5、重载运算符<、>、==
class Boy{
bool operator>(const Boy& boy);
bool operator<(const Boy& boy);
bool operator==(const Boy& boy);
}
----------------------------------------
Boy boy1("老王", 38, 7890);
Boy boy2("老李", 45, 60000);
//***********逻辑省略
if (boy1 > boy2) {
std::cout << "选择boy1" << std::endl;
}
else if (boy1 == boy2) {
std::cout << "难以选择" << std::endl;
}
else {
std::cout << "选择boy2" << std::endl;
}
6、重载运算符[]
class Boy{
public:
int operator[](std::string index);
private:
string name;
int age;
}
----------------------------------
int Boy::operator[](std::string index)
{
if (index == "name") {//参数多可使用宏定义防止写错#define
return name;
}
else if (index == "age") {
return age;
}
=========main=======================
Boy boy1("老王",18);
std::cout << "name:" << boy1["name"] << std::endl;
std::cout << "age:" << boy1["age"] << std::endl;
class Boy{
public:
int operator[](int index);
private:
string name;
int age;
}
----------------------------------
int Boy::operator[](int index)
{
if (index == 0) {//参数多可使用枚举
return name;
}
else if (index == 1) {
return age;
}
=========main=======================
Boy boy1("老王",18);
std::cout << "name:" << boy1[0] << std::endl;
std::cout << "age:" << boy1[1] << std::endl;
7、重载<<和>>
1、方式1使用成员函数
ostream& operator<<(ostream& os) const;
----------------------------------------------
ostream& Boy::operator<<(ostream& os) const
{
os << "ID:" << id << "\t姓名:" << name << "\t年龄:" << age;
return os;
}
-------------main-----------------------------
Boy boy1("老王", 38);
Boy boy1("老王", 38);
// 调用: boy1.operator<<(cout);
boy1 << cout;
// 先调用 boy1.operator<<(cout)
// 再调用 boy2.operator<<(cout)使用不方便
boy2 << (boy1 << cout)
2、方式2使用友元函数
friend ostream& operator<<(ostream& os, const Boy& boy);
friend istream& operator>>(istream& is, Boy& boy);
-----------------------------------
ostream& operator<<(ostream& os, const Boy& boy) {
os << "ID:" << boy.id << "\t姓名:" << boy.name << "\t年龄:" << boy.age;
return os;
}
istream& operator>>(istream& is, Boy& boy)
{
string name2;//假如Boy.name为char类型
is >> name2 >> boy.age;
boy.name = (char*)malloc((name2.length()+1) * sizeof(char));
strcpy_s(boy.name, name2.length() + 1, name2.c_str());
return is;
}
--------------------------------------
Boy boy1("老王", 38)
cin >> boy1;
cout << boy1;
8、普通类转类类型
调用对应的只有一个参数【参数的类型就是这个普通类型】的构造函数
如:Boy boy1 = 10; // 年龄 构造函数Boy(int);
Boy(int age);
-------------------------
Boy::Boy(int age)
{
const char *defaultName = "未命名";
name = new char[strlen(defaultName) + 1];
strcpy_s(name, strlen(defaultName) + 1, defaultName);//如果有名字
this->age=age;
}
-------main----------------------------
Boy boy1 = 10;
cout << boy1 << endl;
9、类类型转普通类
调用特殊的运算符重载函数,类型转换函数,不需要写返回类型
类型转换函数:operator 普通类型 ( )
operator char* () const;
----------------------------------
Boy::operator char* () const
{
return name;
}
Boy boy1("王师傅");
char* name = boy1;
cout << name << endl;
10、类类型转类类型
调用对应的只有一个参数【参数的类型就是类类型A】的构造函数
也可以使用类型转换函数,但是使用对应的构造函数更合适
Man(const Boy& boy);
--------------------
Man::Man(const Boy& boy)
{
int len = strlen((char*)boy) + 1;
name = new char[len];
strcpy_s(name, len, (char*)boy);
age = boy["age"];
}
--------------main----------------------
Boy boy("小王", 28);
Man man = boy;
cout << boy << endl;
cout << man << endl;//需要输出运算符重载<<CV
11、operator=的参数问题(借鉴)
赋值运算符的重载,应该使用这种方式:
Boy& operator=(const Boy &boy);
就是:参数要使用引用!
如果定义成:
Boy& operator=(const Boy *boy);
将会没有效果,编译器不会识别为赋值运算符的重载,
也就是:boy2 = boy1时不会调用这个函数
如果定义:
Boy& operator=(const Boy boy);
有效果,但是在调用时,会执行参数的传递:
比如:boy2 = boy1;
就会执行: boy2.operator=(boy1);
就会执行: const Boy boy = boy1;
就会执行: Boy类的赋值构造函数
有两个影响:
1) 浪费性能
2) 如果没有自定义的拷贝构造函数,而且这个类又有指针成员时,就会调用自动生成的拷贝构造函数,导致浅拷贝
如果析构函数中,对这个指针指向的内存做了释放,那就导致数据损坏或崩溃!
小结:
1)赋值运算符的重载,一定要使用引用参数
2)如果一个类有指针成员,而且使用了动态内存分配,那么一定要定义自己的拷贝构造函数【要使用深拷贝】,避免调用自动生成的拷贝构造函数
因为自动生成的拷贝构造函数,是浅拷贝!