目录
一. 友元 friend
1.1 概念
1.2 友元函数
1.3 友元类
1.4 友元成员函数
二. 运算符重载
2.1 概念
2.2成员函数运算符重载
2.3 成员函数运算符重载
2.4 特殊运算符重载
2.4.1 赋值运算符重载
2.4.2 类型转换运算符重载
2.5 注意事项
三、std::string 字符串类(熟悉)
一. 友元 friend
1.1 概念
定义:
类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的所有成员。
作用:
在于提高程序的运行效率,但是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。导致程序维护性变差,因此使用友元要慎用。
友元较为实际的应用是在运算符重载,这种应用可以提高软件系统的灵活性。
分类:
● 友元函数
● 友元类
● 友元成员函数
1.2 友元函数
友元函数是一种“声明”在类内,实际在类外的普通函数
#include <iostream>
using namespace std;
class Girl
{
private:
int age;
public:
Girl(int age):age(age){}
int get_age() const
{
cout << &age << endl;
return 18;
}
// 1. "声明"友元函数
friend void access_true_age(Girl&);
};
// 2. 定义友元函数
void access_true_age(Girl& g)
{
// 突破权限
cout << &g.age << endl;
cout << "真实年龄:" << g.age << endl;
// 修改
g.age = 18;
cout << "修改后年龄:" << g.age << endl;
}
int main()
{
Girl g(45);
cout << g.get_age() << endl;
// 通过友元函数访问Girl的年龄
access_true_age(g);
return 0;
}
● 由于不属于类的成员函数,因此友元函数没有this指针,访问类的成员只能通过对象。
● 友元函数在类中的“声明”可以写在类的任何部分,不受权限修饰符的影响。
● 理论上一个友元函数可以是多个类的友元函数,只需要在各个类中分别“声明”。
1.3 友元类
当一个类B成为了另一个类A的友元类时,类B可以访问类A的所有成员。
需要注意的是:
● 友元关系是单向的,不具有交换性。
如果类B是类A的友元类,类A不一定是类B的友元类。
● 友元关系不具有传递性。
如果类C是类B的友元类,类B是类A的友元类,类C不一定是类A的友元类。
● 友元关系不能被继承。
#include <iostream>
using namespace std;
class A
{
private:
string str = "A私有";
// “声明”友元类
friend class B;
};
class B
{
public:
void func(A& a)
{
// cout << this->str << endl; 错误:this是B对象不是A对象
cout << a.str << endl;
a.str = "我改了";
cout << a.str << endl;
}
};
int main()
{
A a;
// cout << a.str << endl; 错误
B b;
b.func(a);
return 0;
}
1.4 友元成员函数
在友元类的任何成员函数中都可以访问其他类的成员,但是友元成员函数把友元范围限制在一个成员函数中。
例如,类B的某个成员函数称为了类A的友元成员函数,这样类B的该成员函数就可以访问类A的所有成员了。
#include <iostream>
using namespace std;
// 3. 因为第二步中用到了类A,提前声明类A
class A;
// 2. 编写类B,并真正声明友元成员函数
class B
{
public:
void func(A&);
};
class A
{
private:
string str = "A私有";
// 1. 确定友元的函数格式并“声明”
friend void B::func(A&);
};
// 4. 类外定义友元成员函数
void B::func(A & a)
{
// cout << this->str << endl; 错误:this是B对象不是A对象
cout << a.str << endl;
a.str = "我改了";
cout << a.str << endl;
}
int main()
{
A a;
// cout << a.str << endl; 错误
B b;
b.func(a);
return 0;
}
二. 运算符重载
2.1 概念
如果把运算符看做是一个函数,则运算符也可以像函数一样重载。
C++中预定义的运算符的操作对象只能是基本数据类型。但实际上对于很多用户的自定义类型,也需要类似的运算操作、这时可以在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型,执行特定的操作。
可以被重载的运算符:
算术运算符:+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不被重载的运算符:
成员运算符“.”、指针运算符“*”、三目运算符“? :”、sizeof、作用域“::”
2.2成员函数运算符重载
#include <iostream>
using namespace std;
class MyInt
{
private:
int a;
public:
MyInt(int a):a(a){}
int get_int()
{
return a;
}
// + 运算符重载
friend MyInt operator +(MyInt &i,MyInt &i2);
friend MyInt operator ++(MyInt &i); // 前置自增
friend MyInt operator ++(MyInt &i, int); // 后置自增
};
// 友元函数 实现
MyInt operator +(MyInt &i,MyInt &i2)
{
// int → MyInt 触发构造函数隐式调用
return i.a + i2.a;
}
// 前置自增
MyInt operator ++(MyInt &i)
{
return ++i.a;
}
// 后置自增
MyInt operator ++(MyInt &i, int)
{
return i.a++;
}
int main()
{
MyInt int1(2);
MyInt int2(int1); // 拷贝构造函数
MyInt int3 = int1 + int2;
cout << (int3++).get_int() << endl; // 4
cout << int3.get_int() << endl; // 5
return 0;
}
2.3 成员函数运算符重载
成员函数运算符重载相比于友元函数重载,最主要的区别在于,友元函数的第一个输出参数,在成员函数运算符重载中使用this指针代替。因此相同的运算符重载,成员函数运算符重载比友元函数运算符重载参数少一个。
#include <iostream>
using namespace std;
class MyInt
{
private:
int a;
public:
MyInt(int a):a(a){}
int get_int()
{
return a;
}
MyInt operator +(MyInt &i2);
MyInt operator ++();
MyInt operator ++(int);
};
// 成员函数 类外实现
MyInt MyInt::operator +(MyInt &i2)
{
// int → MyInt 触发构造函数隐式调用
return this->a + i2.a;
}
// 前置自增
MyInt MyInt::operator ++()
{
return ++this->a;
}
// 后置自增
MyInt MyInt::operator ++(int)
{
return this->a++;
}
int main()
{
MyInt int1(2);
MyInt int2(int1); // 拷贝构造函数
MyInt int3 = int1 + int2;
cout << (++int3).get_int() << endl;
cout << int3.get_int() << endl;
return 0;
}
2.4 特殊运算符重载
2.4.1 赋值运算符重载
除了之前学习的无参构造函数、拷贝构造函数、析构函数以外,如果程序员不手写,编译器就会给一个类添加赋值运算符重载函数。
#include <iostream>
using namespace std;
class MyInt
{
private:
int a;
public:
MyInt(int a):a(a){}
int get_int()
{
return a;
}
// 编译器会自动添加赋值运算符重载函数
MyInt & operator =(MyInt &i)
{
cout << "赋值运算符被调用了" << endl; // 编译器自动添加的赋值运算符重载函数不会打印这句话
this->a = i.a;
return *this;
}
};
int main()
{
MyInt int1(2);
MyInt int4(3);
cout << int4.get_int() << endl;
int4 = int1; // 赋值运算符重载
cout << int4.get_int() << endl;
return 0;
}
当类中出现指针成员变量时,默认的赋值运算符重载函数会出现类似于浅拷贝构造函数的问题,因此也需要手动编写解决“浅拷贝”的问题。
【面试题】一个类什么都不写,编译器添加了那些代码?
无参构造函数、拷贝构造函数、析构函数、赋值运算符重载函数
2.4.2 类型转换运算符重载
必须使用成员函数运算符重载,且格式比较特殊。
#include <iostream>
using namespace std;
class MyInt
{
private:
int a;
string str = "hello";
public:
MyInt(int a):a(a){}
int get_int()
{
return a;
}
// 编译器会自动添加赋值运算符重载函数
MyInt & operator =(MyInt &i)
{
cout << "赋值运算符被调用了" << endl; // 编译器自动添加的赋值运算符重载函数不会打印这句话
this->a = i.a;
return *this;
}
// 类型转换运算符重载
operator int()
{
return a;
}
operator string()
{
return str;
}
};
int main()
{
MyInt int1(2);
int a = int1;
string str = int1;
cout << a << endl;
cout << str << endl;
return 0;
}
2.5 注意事项
● 重载的运算符限制在C++语言中已有的运算符范围,不能创建新的运算符。
● 运算符重载的本质也是函数重载,但是不支持函数参数默认值设定。
● 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数和语法结构。
● 运算符重载必须基于或包含自定义类型,即不能改变基本数据类型的运算符规则。
● 重载功能应该与原有功能类似,避免没有目的的滥用运算符重载。
● 一般情况下,双目运算符建议使用友元函数进行重载,单目运算符建议使用成员函数进行重载。
三、std::string 字符串类(熟悉)
字符串对象是一个特殊类型的容器,专门设计用于操作字符串。
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
string s; // 创建一个空字符串
// 判断是否为空
cout << s.empty() << endl; // 1
// 隐式调用构造函数
string s1 = "hello";
cout << s1 << endl; // hello
// 显式调用构造函数,等同于上面写法
string s2("world");
cout << s2 << endl;
// ==、!=、<、> 都是判断编码
cout << (s1 == s2) << endl; // 0
cout << (s1 != s2) << endl; // 1
cout << (s1 > s2) << endl; // 0
cout << (s1 < s2) << endl; // 1
// 拷贝构造函数
string s3(s2); // string s3 = s2;
cout << s3 << endl;
// 参数1:char *源字符串
// 参数2:保留的字符数
string s4("ABCDEFG",3);
cout << s4 << endl; // ABC
// 参数1:std::string 源字符串
// 参数2:不保留的字符数
string s5(s2,3);
cout << s5 << endl; // ld
// 参数1:字符的数量
// 参数2:字符的内容char
string s6(5,'a');
cout << s6 << endl; // aaaaa
// 交换
cout << "原s5=" << s5 << " " << "原s6=" << s6 << endl; // 原s5=ld 原s6=aaaaa
swap(s5,s6);
cout << "s5=" << s5 << " " << "s6=" << s6 << endl; // s5=aaaaa s6=ld
// 字符串拼接、连接
string s7 = s5 + s6;
cout << s7 << endl; // aaaaald
// 向后追加字符串
s7.append("jiajia");
cout << s7 << endl; // aaaaaldjiajia
// 向后追加单字符
s7.push_back('s');
cout << s7 << endl; // aaaaaldjiajias
// 插入
// 参数1:插入的位置
// 参数2:插入的内容
s7.insert(1,"234");
cout << s7 << endl; // a234aaaaldjiajias
// 删除字符串
// 参数1:起始位置
// 参数2:删除的字符数量
s7.erase(2,5);
cout << s7 << endl; // a2aldjiajias
// 替换
// 参数1:起始位置
// 参数2:被替换的字符数
// 参数3:替换的新内容
s7.replace(0,3,"***");
cout << s7 << endl; // ***ldjiajias
// 清空
s7.clear();
cout << s7.length() << endl; // 0
// 直接赋值初始化(隐式调用构造函数)
string s8 = "hahaha";
cout << s8 << endl;
// 重新赋值
s8 = "ABCDEFGH";
cout << s8 << endl; // ABCDEFGH
// 参数1:拷贝的目标
// 参数2:拷贝的字符数量
// 参数3:拷贝的起始位置
char arr[20] = {0};
s8.copy(arr,6,1);
cout << arr << endl; // BCDEFG
// C++string 到 c string 用到了C语言的strcpy
// c_str C++的字符串转换成C语言的字符数组
// c_str返回值类型是一个const char*
char c[20] = {0};
strcpy(c,s8.c_str());
cout << c <<endl; // ABCDEFGH
return 0;
}