友元

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问

目的:让一个类中私有成员对类外特定函数或者类访问

关键字:friend

友元的三种实现:全局函数做友元、类做友元、成员函数做友元

全局函数做友元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Building
{
//告诉编译器goodFriend全局函数是Building类的好朋友,可以访问类中的私有内容
friend void goodFriend(Building * building);
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
};
void goodFriend(Building * building)
{
cout << "好友访问" << building->m_SittingRoom << endl;
cout << "好友访问" << building->m_BedRoom<< endl;
}
int main()
{
Building building;
goodFriend(&building);
return 0;
}

类做友元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Building
{
//类做友元
friend class GoodFriend;
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
};
class GoodFriend
{
public:
void visit(Building* building)
{
cout << "好友访问" << building->m_SittingRoom << endl;
cout << "好友访问" << building->m_BedRoom<< endl;
}

};
int main()
{
Building building;
GoodFriend goodFriend;
goodFriend.visit(&building);
return 0;
}

成员函数做友元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Building;
class GoodFriend
{
public:
void visit1(Building* building);
void visit2(Building* building);
};
class Building
{
//类做友元
friend void GoodFriend::visit1(Building* building);
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
};
void GoodFriend::visit1(Building* building)
{
cout << "好友visit1访问" << building->m_SittingRoom << endl;
cout << "好友visit1访问" << building->m_BedRoom<< endl;
}
void GoodFriend::visit2(Building* building)
{
cout << "好友visit2访问" << building->m_SittingRoom << endl;
//cout << "好友visit2访问" << building->m_BedRoom<< endl;
}
int main()
{
Building building;
GoodFriend goodFriend;
goodFriend.visit1(&building);
goodFriend.visit2(&building);
return 0;
}

运算符重载

通过定义类的成员函数或全局函数,重新赋予运算符新的功能,以适应不同的数据类型

加号运算符重载

成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Student
{
public:
int chineseScore;
int mathScore;
public:
//定义一个函数,实现两个对象相加属性后返回新的对象
Student operator+(Student &s)//通用名称
{
//定义一个临时学生
Student temp;
//对临时学生属性进行复制,this:当前对象,s:参数对象
temp.chineseScore = this->chineseScore + s.chineseScore;
temp.mathScore = this->mathScore + s.mathScore;
return temp;
}

};
int main()
{
Student xz1;
xz1.chineseScore = 40;
xz1.mathScore = 40;
Student xz2;
xz2.chineseScore = 40;
xz2.mathScore = 40;
//学霸的成绩,是两个学渣的和
Student xb = xz1 + xz2;
cout << xb.chineseScore << xb.mathScore << endl;
return 0;
}
全局函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Student
{
public:
int chineseScore;
int mathScore;
};
//全局函数
//定义一个函数,实现两个对象相加属性后返回新的对象
Student operator+(Student &s1,Student &s2)
{
Student temp;
temp.chineseScore = s1.chineseScore + s2.chineseScore;
temp.mathScore = s1.mathScore + s1.mathScore;
return temp;
}
int main()
{
Student xz1;
xz1.chineseScore = 40;
xz1.mathScore = 40;
Student xz2;
xz2.chineseScore = 40;
xz2.mathScore = 40;
//学霸的成绩,是两个学渣的和
Student xb = xz1 + xz2;
cout << xb.chineseScore << xb.mathScore << endl;
return 0;
}

左移(输出)运算符重载

作用:可以输出自定义数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student
{
friend ostream & operator<<(ostream &cout,Student &s);
private:
int chineseScore;
int mathScore;
public:
Student(int chineseScore,int mathScore):chineseScore{chineseScore},mathScore{mathScore}{}
};
//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout,Student &s)
{
cout << s.chineseScore << s.mathScore;
return cout;
}
int main()
{
int a = 10;
cout << a << endl;
Student s{40,40};
cout << s <<endl;
return 0;
}

递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class MyInt
{
friend ostream & operator<<(ostream &cout,MyInt myint);
private:
int num;
public:
MyInt():num{0}{}
//重载前置++运算符
//返回引用,是为了一直对一个数据进行递增操作
MyInt & operator++()
{
//先++
num++;
//再返回
return *this;
}
//重载后置++运算符
//函数离参数int表示占位参数,用于区分前后置递增
MyInt operator++(int)
{
//先记录当时结果
MyInt temp = *this;
//后 递增
num++;
//最后将结果进行返回
return temp;
}
};
ostream & operator<<(ostream &cout,MyInt myint)
{
cout << "myint:" <<myint.num;
return cout;
}
int main()
{
MyInt myint;
cout << myint <<endl;
//前置递增
cout << ++myint <<endl;
//后置递增
cout << myint++ <<endl;

cout << myint <<endl;
return 0;
}

递减运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class MyInt
{
friend ostream & operator<<(ostream &cout,MyInt myint);
private:
int num;
public:
MyInt():num{0}{}
//重载前置--运算符
//返回引用,是为了一直对一个数据进行递增操作
MyInt & operator--()
{
//先--
num--;
//再返回
return *this;
}
//重载后置--运算符
//函数离参数int表示占位参数,用于区分前后置递减
MyInt operator--(int)
{
//先记录当时结果
MyInt temp = *this;
//后 递增
num--;
//最后将结果进行返回
return temp;
}
};
ostream & operator<<(ostream &cout,MyInt myint)
{
cout << "myint:" <<myint.num;
return cout;
}
int main()
{
MyInt myint;
cout << myint <<endl;
//前置递减
cout << --myint <<endl;
//后置递减
cout << myint-- <<endl;
cout << myint <<endl;
return 0;
}

赋值运算符重载

C++编译器默认给一个类提供的函数:
(1)默认构造函数(无参,函数体为空)
(2)默认析构函数(无参,函数体为空)
(3)默认拷贝构造函数,对属性进行值拷贝(初始化)
(4)赋值运算符 operator=, 对属性进行值拷贝(赋值)

浅拷贝

简单的赋值拷贝操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class MyClass {
public:
int data;
string name;

// 默认构造函数
MyClass() : data(0), name("") {}

// 默认拷贝构造函数
MyClass(const MyClass& other) : data(other.data), name(other.name) {}

// 默认赋值运算符重载
MyClass& operator=(const MyClass& other) {
if (this != &other) {
data = other.data;
name = other.name;
}
return *this;
}
//默认析构函数
~MyClass(){}
};
int main() {
MyClass myc1;// 使用默认构造函数
myc1.data = 10;
myc1.name = "myc1";

MyClass myc2 = myc1;// 使用默认拷贝构造函数

MyClass myc3;
myc3 = myc1;// 使用默认赋值运算符重载

// 默认析构函数将在程序结束时自动调用
return 0;
}

调用默认的拷贝构造函数和赋值运算符重载函数,面对成员变量开辟含有堆空间,会导致默认析构函数,多次释放这个堆空间,导致程序异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Person
{
public:
int *age;
Person(int age)
{
this->age = new int(age);
}
~Person()
{
if (age!=nullptr)
{
delete age;
age = nullptr;
}

}
};
int main()
{
Person p1{18};
cout << "p1.age = " <<*p1.age <<endl;
Person p2{20};
cout << "p2.age = " <<*p2.age <<endl;

p2 = p1;
cout << "<------>" << endl;
cout << "p1.age = " <<*p1.age <<endl;
cout << "p2.age = " <<*p2.age <<endl;
//堆区内存重复释放,造成程序崩溃,还会涉及到堆内存的泄露
return 0;
}

深拷贝

在堆区重新申请空间,进行拷贝操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//利用深拷贝解决浅拷贝带来的问题
class Person
{
public:
int *age;
Person(int age)
{
this->age = new int(age);
}
~Person()
{
if (age!=nullptr)
{
delete age;
age = nullptr;
}

}
//拷贝构造函数
Person(const Person &p)
{
age = new int(*p.age);
}
//重载复制运算符
Person & operator=(const Person &p)
{
//防止自己赋值自己
if (this != &p)
{
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(age!=nullptr)
{
delete age;
age = nullptr;
}
age = new int(*p.age);
}
return *this;
}

};
int main()
{
Person p1{18};
cout << "p1.age = " <<*p1.age <<endl;
Person p2{20};
cout << "p2.age = " <<*p2.age <<endl;

p2 = p1;
cout << "<------>" << endl;
cout << "p1.age = " <<*p1.age <<endl;
cout << "p2.age = " <<*p2.age <<endl;
Person p3{p2};
return 0;
}

关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Person
{
public:
string name;
int age;
Person():name{},age{}{}
Person(string name,int age):name{name},age{age}{}
bool operator==(const Person &p)
{
if(this->name == p.name && this->age == p.age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(const Person &p)
{
if(this->name == p.name && this->age == p.age)
{
return false;
}
else
{
return true;
}
}

};
int main()
{
Person p1{"Ethaniel",22};
Person p2{"Ethaniel",22};
if (p1 == p2)
{
cout << "p1和p2相同" << endl;
}
if (p1 != p2)
{
cout << "p1和p2不相同" << endl;
}
return 0;
}

函数调用运算符重载

基本概念
函数调用运算符: ()
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyClass
{
public:
//由于函数调用运算符使用起来非常类似函数调用,因此称为仿函数
void operator()(string str)
{
cout << str << endl;
}
void printf1(string str)
{
cout << str << endl;
}
};
void printf2(string str)
{
cout << str << endl;
}
int main()
{
MyClass myc;
myc.printf1("e");
myc("t");
printf("h");
return 0;
}

类对象初始化的方式

默认初始化

使用默认的构造函数进行初始化

1
2
3
4
5
6
class MyClass {
public:
int num;
std::string str;
MyClass(){} // 默认构造函数
};

值初始化

将对象的每个成员初始化为其对应类型的默认值。

1
2
3
4
5
6
7
8
9
10
11
class MyClass {
public:
int num;
std::string str;
MyClass():num(),str(){} // 值初始化
};
int main()
{
MyClass obj{};// 值初始化对象,num和str的值为默认值
return 0;
}

初始化列表

在构造函数中使用冒号 : 后跟成员变量列表进行初始化。

1
2
3
4
5
6
7
8
9
10
11
class MyClass {
public:
int num;
std::string str;
MyClass(int n, const std::string& s):num(n),str(s){} // 带参数的构造函数
};
int main()
{
MyClass obj(10, "Hello"); // 初始化列表直接初始化对象,使用参数值进行初始化
return 0;
}

直接初始化

使用圆括号 () 或花括号 {} 提供初始值进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass {
public:
int num;
std::string str;
MyClass(int n, const std::string& s):num(n),str(s){} // 带参数的构造函数
};
int main()
{
MyClass obj1(10, "Hello"); //直接初始化对象,使用参数值进行初始化
MyClass obj2{10, "Hello"}; //也可以使用花括号进行直接初始化
return 0;
}

拷贝初始化

使用等号 = 或圆括号 () 进行初始化,涉及到拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass {
public:
int num;
std::string str;
MyClass(int n, const std::string& s) : num(n), str(s) {} // 带参数的构造函数
MyClass(const MyClass& other) : num(other.num), str(other.str) {
std::cout << "拷贝构造函数被调用" << std::endl;
}
};
int main()
{
MyClass obj = obj2; // 调用拷贝构造函数来创建一个新的对象,并将obj2的值复制给obj
MyClass obj(obj2); // 调用拷贝构造函数来创建一个新的对象,并将obj2的值复制给obj
return 0;
}

拷贝构造函数是通过值来拷贝对象的,因此如果成员变量中包含指针等动态分配的资源,需要特别注意拷贝构造函数的实现,以避免浅拷贝带来的问题

列表初始化

使用花括号 {} 进行初始化,提供了更严格的类型匹配和防止窄化转换的特性。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass {
public:
int num;
std::string str;
MyClass(int n, const std::string& s):num(n),str(s){} // 带参数的构造函数
};
int main()
{
MyClass obj{10, "Hello"}; //列表初始化对象,使用参数值进行初始化

return 0;
}

类对象的拷贝

C++中类对象的拷贝操作可以通过拷贝构造函数和拷贝赋值运算符来实现

拷贝构造函数:拷贝构造函数用于创建一个新对象,并将已存在的对象的值复制到新对象中。
语法形式为:class_name(const class_name& other);
默认情况下,编译器会为类生成一个默认的拷贝构造函数,按照逐个成员的方式进行拷贝。

拷贝赋值运算符:拷贝赋值运算符用于将一个已存在的对象的值赋给另一个已存在的对象。
语法形式为:class_name& operator=(const class_name& other);
默认情况下,编译器会为类生成一个默认的拷贝赋值运算符,按照逐个成员的方式进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Person {
private:
std::string name;
int age;

public:
//构造函数
Person(const std::string& name, int age) {
this->name = name;
this->age = age;
}
//拷贝构造函数
Person(const Person& other) {
this->name = other.name;
this->age = other.age;
}
//拷贝赋值运算符
Person& operator=(const Person& other) {
if (this != &other) {
name = other.name;
age = other.age;
}
return *this;
}
std::string getName() const {
return name;
}
int getAge() const {
return age;
}
};
int main() {
Person p1("A", 20);
Person p2 = p1; // 使用拷贝构造函数进行对象的拷贝
//Person p2(p1);// 使用拷贝构造函数进行初始化
Person p3("B", 25);
p3 = p1; // 使用拷贝赋值运算符进行对象的拷贝

std::cout << p1.getName() << ", " << p1.getAge() << std::endl;
std::cout << p2.getName() << ", " << p2.getAge() << std::endl;
std::cout << p3.getName() << ", " << p3.getAge() << std::endl;
return 0;
}

注意事项:

(1)深拷贝 vs 浅拷贝:默认情况下,C++会自动生成浅拷贝的拷贝构造函数和拷贝赋值运算符。浅拷贝只是简单地将原对象的成员变量的值复制给新对象,如果成员变量中有指针等动态内存分配的资源,会导致多个对象共享同一块内存,造成悬垂指针或者重复释放的问题。为了避免这个问题,需要手动实现深拷贝,即对动态分配的资源进行独立复制。

(2)参数传递方式:拷贝构造函数和拷贝赋值运算符的参数都是常量引用(const&)。这是为了确保在调用拷贝构造函数和拷贝赋值运算符时不会修改原对象。

(3)自赋值检查:在拷贝赋值运算符的实现中,需要注意对自赋值的检查。即在执行复制操作之前,检查目标对象是否与源对象是同一个对象,如果是,则不进行复制操作,直接返回。

(4)返回类型:拷贝赋值运算符应该返回一个引用,通常是返回*this,以便实现链式连续赋值。

(5)对象成员的拷贝:在拷贝构造函数和拷贝赋值运算符中,需要手动复制每个成员变量的值。对于指针成员变量,需要进行深拷贝。

需要注意的是,拷贝构造函数和拷贝赋值运算符可以根据具体的业务需求进行适当的定义和实现,确保对象的拷贝和赋值操作是正确且安全的。