1.友元
1.1 概念
类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
友元是一种定义在类外部的普通函数,但他需要在类内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend
学习友元最终的目的是把友元运用在运算符重载上,其它情况下不要使用友元,因为会破坏面向对象的特性。
友元主要分为以下几种使用情况:
● 友元函数
● 友元类
● 友元成员函数
1.2友元函数
友元函数是一种类外的函数,但是需要在类内结合friend关键字进行说明(非声明),需要注意以下几点:
● 因为友元函数不是类内的函数,因此没有this指针,因此需要给友元函数增加一个传递对象的参数位,用此对象来调用类中的成员。
● 友元函数不属于类,因此不受类中权限修饰符的影响,即友元函数的说明可以放在类中的任意位置。
● 一个友元函数可以访问多个类的成员,只需要在多个类中分别说明。
#include <iostream>
using namespace std;
class Test
{
private:
int a = 1;
// 友元函数的说明,放在类中的任意位置都可以
friend void test_friend(Test& t);
};
// 友元函数在类外
void test_friend(Test& t)
{
t.a++;
cout << t.a << endl;
}
// 不是友元函数
void test(Test& t)
{
// t.a++; 错误
// cout << t.a << endl; 错误
}
int main()
{
Test t;
test_friend(t); // 2
return 0;
}
1.3友元类
当一个类B成为了另一个类A的“朋友”时,类A的所有成员就可以被类B访问,此时类B是类A的友元类。
#include <iostream>
using namespace std;
class A
{
private:
int i = 1;
// 进行友元类的说明
friend class B;
};
class B
{
public:
void func(A& a) // func函数中的this指针是B对象
{
a.i++;
cout << a.i << endl;
}
};
int main()
{
A a;
B b;
b.func(a); // 2
return 0;
}
需要注意的是:
● 友元关系是单向的,不具有交换性。
● 友元关系不具有传递性。
● 友元关系不能被继承。
1.4友元成员函数
可以使类B中的某一个成员函数成为类A的友元成员函数,这样类B中只有这个成员函数可以访问类A的所有成员。
using namespace std;
// 第三步
class A;
// 第二步
class B
{
public:
void func(A& a); // 先只声明
};
// 第一步
class A
{
private:
int i = 1;
// 说明友元成员函数
friend void B::func(A& a);
};
// 第四步,补齐友元函数的定义
void B::func(A &a)
{
a.i++;
cout << a.i << endl;
}
int main()
{
A a;
B b;
b.func(a); // 2
return 0;
}
2.运算符重载
2.1概念
函数可以重载,运算符也是一种特殊的函数,因此运算符也可以重载。
函数的组成部分:
● 名称
● 输入参数
● 函数体
● 返回值
上述的每个组成部分运算符都拥有。
C++中的运算符默认的操作类型只支持基本数据类型,例如+支持整型浮点型等类型的运算,但是对于很多用户自定义的类型(Dog类、Cat类、Test类等)的对象也需要支持运算符,例如 狗+狗。此时可以在代码中重载运算符,赋予这些运算符处理新的类型的功能。
可以被重载的运算符:
算术运算符:+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不被重载的运算符:
成员运算符 .、指针运算符 *、三目运算符 ? :、sizeof、作用域 ::
2.2友元函数运算符重载
可以使用友元函数进行运算符重载。
#include <iostream>
using namespace std;
/**
* @brief The Integer class
* 整型类
*/
class Integer
{
private:
int value;
public:
Integer(int value):value(value){}
int get_value() const
{
return value;
}
// 友元函数的说明
friend Integer operator +(const Integer& i1,
const Integer& i2); // 双目
friend Integer operator ++(Integer& i); // 单目,前置
friend Integer operator ++(Integer& i,int); // 单目,后置
};
Integer operator +(const Integer& i1,
const Integer& i2)
{
return i1.value+i2.value;
}
Integer operator ++(Integer& i)
{
return ++i.value;
}
Integer operator ++(Integer& i,int)
{
return i.value++;
}
int main()
{
Integer i1(1);
Integer i2(2);
Integer i3 = i1+i2;
cout << i3.get_value() << endl; // 3
cout << (i1++).get_value() << endl; // 1
cout << i1.get_value() << endl; // 2
cout << (++i2).get_value() << endl; // 3
return 0;
}
2.3成员函数运算符重载
也可以使用成员函数进行运算符重载,成员函数的运算符函数对应的输入参数比同样使用友元函数实现的友元函数的参数少一个。
using namespace std;
/**
* @brief The Integer class
* 整型类
*/
class Integer
{
private:
int value;
public:
Integer(int value):value(value){}
int get_value() const
{
return value;
}
// 成员函数运算符重载
Integer operator +(const Integer& i);
Integer operator ++(); // 前置
Integer operator ++(int); // 后置
};
Integer Integer::operator +(const Integer& i)
{
// this表示运算数1
return this->value+i.value;
}
Integer Integer::operator ++()
{
return ++this->value;
}
Integer Integer::operator ++(int)
{
return this->value++;
}
int main()
{
Integer i1(1);
Integer i2(2);
Integer i3 = i1+i2;
cout << i3.get_value() << endl; // 3
cout << (i1++).get_value() << endl; // 1
cout << i1.get_value() << endl; // 2
cout << (++i2).get_value() << endl; // 3
return 0;
}
2.4其他运算符重载
2.4.1赋值运算符重载
如果写一个空类,按照目前所学内容,编译器会自动添加:
● 无参构造函数
● 析构函数
● 拷贝构造函数
除了上述三者外,编译器还会增加若干内容,其中包括赋值运算符重载函数。
赋值运算符重载函数只支持成员函数运算符重载,不支持友元函数运算符重载的方式。
using namespace std;
class Teacher
{
public:
string subject;
Teacher(string sub)
{
subject = sub;
}
// 编译器自动添加赋值运算符重载函数
Teacher& operator =(const Teacher& right)
{
this->subject = right.subject;
cout << "赋值运算符:" << subject << endl;
return *this;
}
};
int main()
{
Teacher t1("C++");
Teacher t2("C#");
t2 = t1; // 赋值操作
cout << (t2 = t1).subject << endl; // 支持链式调用
cout << t2.subject << endl; // C++
t2.subject = "C";
Teacher t3 = t2; // 隐式调用拷贝构造函数
cout << t3.subject << endl; // C
return 0;
}
通常无需手动编写赋值运算符重载,以下情况需要手动编写,编译器不在自动添加赋值运算符重载函数:
● 当前类的成员变量出现指针
● 屏蔽赋值运算符的使用(权限为private)
2.4.2类型转换运算符重载
可以使自定义类型的对象自动转换为任意类型,此函数也只能使用成员函数运算符重载。
using namespace std;
class Teacher
{
public:
string subject;
Teacher(string sub)
{
subject = sub;
}
operator string() // 格式比较特殊
{
return subject;
}
};
int main()
{
Teacher t("语文");
string s = t;
cout << s << endl; // 语文
return 0;
}
2.5注意事项
● 运算符重载限制在C++已有的运算符范围内,不允许创建新的运算符。
● 运算符重载也是函数重载,运算符也是函数。
● 重载之后的运算符不能改变优先级和结合性。
● 重载之后的运算符不能改变操作数和语法结构。
● 运算符重载不能改变该运算符用于基本数据类型的含义,但是可以把基本数据类型与自定义类型一起运算,或者都使用自定义类型。
● 运算符重载是针对新类型数据的实际需要对原有运算符的功能进行扩充,因此重载之后的功能应该与原有的功能类似,避免没有目的地使用运算符重载。
● 通常建议单目运算符使用成员函数运算符重载,双目运算符使用友元函数运算符重载。
3.字符串类 std::string
再学习此类,主要涉及此类的一些构造函数和成员函数。
std::string是一种特殊容器的类型,用于操作字符序列。
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
string s; // 创建一个内容为空的字符串对象
// 是否为空
cout << s.empty() << endl; // 1
// 隐式调用构造函数
string s1 = "Abc";
// 相当于
string s2("Abc");
cout << (s1 == s2) << endl; // 1
// 拷贝构造函数
string s3 = s2;
string s4(s3);
cout << s3 << " " << s4 << endl; // Abc Abc
// 参数1:源字符串 char*
// 参数2:从前往后保留的字符数
string s5("ABCDEFG",3);
cout << s5 << endl; // ABC
// 参数1:源字符串 string
// 参数2:从前往后不保留的字符数
s = "ABCDEFG";
string s6(s,3);
cout << s6 << endl; // DEFG
// 参数1:字符数量
// 参数2:字符 char
string s7(5,'A');
cout << s7 << endl; // AAAAA
// 交换
swap(s6,s7);
cout << s6 << " " << s7 << endl; // AAAAA DEFG
s1 = "ABCD";
// 向后追加字符串,支持链式调用
s1.append("EF").append("GHI").append("ZZZ"); // ABCDEFGHIZZZ
cout << s1 << endl;
// 字符串连接
s1 = s6+s7;
cout << s1 << endl; // AAAAADEFG
// 向后追加一个字符
s1.push_back('*');
cout << s1 << endl; // AAAAADEFG*
// 参数1:插入的位置
// 参数2:插入的内容
s1.insert(1,"###");
cout << s1 << endl; // A###AAAADEFG*
// 参数1:替换的起始位置
// 参数2:替换的字符数量
// 参数3:替换的新内容
s1.replace(0,7,"!!!!");
cout << s1 << endl; // !!!!ADEFG*
// 参数1:删除的起始位置
// 参数2:删除的字符数
s1.erase(4,3);
cout << s1 << endl; // !!!!FG*
// 清空
s1.clear();
cout << s1.length() << endl; // 0
// C字符串→C++字符串
char c[20] = "hello";
s = c;
cout << s << endl; // hello
// C++字符串→C字符串
s = "Good afternoon!";
// c = s; 错误
s.copy(c,s.size()); // 全拷贝
cout << c << endl; // Good afternoon!
// 参数1:源字符串
// 参数2:拷贝的数量
// 参数3:拷贝的起始位置
s.copy(c,9,5);
cout << c << endl; // afternoonrnoon!
// 还可以使用下面的函数完成
char d[20];
strcpy(d,s.c_str()); // c_str()返回一个临时的const char*
cout << d << endl; // Good afternoon!
return 0;
}