本篇博客来讲述C++类和对象中的最后一些内容,即友元和const的使用方法。
目录
1.友元
1.1引入
1.2内容
1.2.1友元函数
1.2.2友元类
1.3内部类
2.const修饰
2.1内容
2.1.1常数据成员
2.1.2常成员函数
2.1.3常对象
2.2示例
1.友元
1.1引入
在讲述友元之前,我们来简单了解一下友元诞生的原因。当我们封装类后,对于其中私有成员的访问我们需要提供具体接口来返回,这样我们才可以在外部对类中的私有成员进行访问,如下所示代码:
#include<iostream>
#define Pi 3.14
class Circle {
private:
//圆心坐标
int x;
int y;
//半径
int r;
public:
Circle(int x = 0, int y = 0, int r = 0) {
this->x = x;
this->y = y;
this->r = r;
}
int Get_r() {//提供对外接口用于访问类中对应私有成员
return r;
}
};
double SquareMeasure(Circle& c1) {
return c1.Get_r() * c1.Get_r() * Pi;
}
int main() {
Circle c1(0, 0, 1);
std::cout << SquareMeasure(c1);
return 0;
其中Get_r接口便是提供的对外访问类中私有成员的接口,不过在具体的SquareMeasure函数中我们对于Get_r接口调用次数较为重复,降低了程序的执行效率,所以在此基础上,提出友元的概念。
1.2内容
1.2.1友元函数
友元提供了一种突破封装的方式,有时会为我们提供一定程度的便利。但是友元在另一种程度上会增加代码的耦合性,破坏了原有的封装,对于其具体的使用方式我们在上述代码的基础上改变。
#include<iostream>
#define Pi 3.14
class Circle {
private:
//圆心坐标
int x;
int y;
//半径
int r;
public:
Circle(int x = 0, int y = 0, int r = 0) {
this->x = x;
this->y = y;
this->r = r;
}
friend double SquareMeasure(Circle& c1);
};
double SquareMeasure(Circle& c1) {
return c1.r * c1.r * Pi;
}
int main() {
Circle c1(0, 0, 1);
std::cout << SquareMeasure(c1);
return 0;
}
我们在Circle类中声明SquareMeasure函数为友元,则在接下来的SquareMeasure函数中,我们可以直接访问Circle类中的私有数据成员,并且在使用过程中,它存在几种特征:
- 友元函数不是当前类中的成员函数,而是独立于当前类的外部函数,但他可以访问类中的所有成员;
- 友元函数可以在类定义的任何地方声明,不受访问限定符限制;
- 友元函数可以定义在类的内部,也可以定义在类的内部。
- 友元函数不能使用const修饰;
- 一个函数可以是多个类的友元函数;
- 友元函数的调用和普通函数的调用原理相同。
1.2.2友元类
friend关键字除修饰函数之外,还可以用于修饰类,被修饰的类则称之为友元类。友元类中的所有成员函数都可以是另一个类的友元函数,都可以访问一个类中的非共有成员。
同样的,我们借助具体的代码来进行展示和理解:
#include<iostream>
#define Pi 3.14
class Circle {
private:
//圆心坐标
int x;
int y;
//半径
int r;
friend class Size;
public:
Circle(int x = 0, int y = 0, int r = 0) {
this->x = x;
this->y = y;
this->r = r;
}
};
class Size {
private:
double c;//周长
double s;//面积
public:
Size(Circle& c1) {
c = 2 * Pi * c1.r;
s = c1.r * c1.r * Pi;
}
void CircleShow() {
std::cout << "周长:" << c << std::endl;
std::cout << "面积:" << s << std::endl;
}
};
int main() {
Circle c1(0, 0, 1);
Size c2(c1);
c2.CircleShow();
return 0;
}
很直观,Size类是Circle类的友元,我们可以在Size类中直接访问Circle类中的私有成员,在使用过程中,它存在以下几种特征:
- 友元关系是单向的,不具有交换性,即在上述案例中我们可以在Size类中直接访问Circle类中的私有成员,但是无法在Circle类中访问Size类的私有成员;
- 友元关系不具备传递性,即如果B是A的友元,C是B的友元,但是C并不是A的友元;
- 友元关系并不能继承,即B是A的友元,C继承于A,但是B并不是C的友元。
1.3内部类
当存在一个类直接定义在另一个类的范围,则该类被称之为内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类并不具备对内部类的优越访问权限。
不过,内部类是外部类的友元,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但外部类并不是内部类的友元,我们来看代码:
#include<iostream>
class A {
private:
int k;
public:
A(int k = 0) {
this->k = k;
}
class B {//内部类B天生便是外部类A的友元
public:
void Show(const A& a1) {
std::cout << a1.k;
}
};
};
int main() {
A::B a2;
a2.Show(A());
return 0;
}
注意在定义B类对象时,加上具体的作用域限定符。内部类的特征我们可以总结为以下几点:
- 内部类有定义在外部类的任何地方,不受访问限定符限制;
- 内部类可以直接访问外部类中的static成员,不需要外部类的对象或是类名;
- sizeof(外部类),只和外部类有关,和内部类无关。
2.const修饰
2.1内容
对于一些数据我们在进行共享的同时,并不希望它们发生改变,于是我们可以加入const修饰来对这些数据进行保护,使其变为常量,常量在程序运行过程中是不允许改变的。
我们根据const修饰的内容不同,可以将其分为三类,分别是:
- 常数据成员--const修饰数据成员;
- 常成员函数--const修饰成员函数;
- 常对象--const修饰类的对象。
2.1.1常数据成员
在类中定义了常数据成员,则构造函数只能通过初始化列表对其进行初始化,并且其他函数都不能对常数据成员进行修改,只能进行访问。
2.1.2常成员函数
常成员函数的原型进行声明时,我们需要在其函数定义的首部使用关键字从const,并且常成员函数不能修改本类的数据成员,也不能调用普通的成员函数,这样可以保证在常成员函数当中不会修改数据成员的值。(关键字const可以当作函数重载的标志)
2.1.3常对象
常对象必须在定义时进行初始化,并且初始化之后的数据不能被更新,不能被改变,因此通过常对象只能调用常成员函数,而不能调用类中的其他普通成员函数。
2.2示例
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test() {
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.Print();
}
int main() {
Test();
return 0;
}
对于上面代码的执行结果为:
我们可以发现结果的执行结果如函数重载时的模样。
这是因为当我们在使用const修饰成员函数之后,编译器会自动将其优化成如下情况:
void Print(const Date* this) {
}
即默认的this指针加入const修饰,进而形成重载。