一、友元
友元提供了一种突破封装的方式,有时提供了方便,但是友元会增加耦合度,破坏了封装,所以不建议经常使用友元。
友元分为: 友元函数和友元类。
1.1 友元函数
我们在尝试去重载operator<<时发现无法将operator<<重载成成员函数,因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了,但是实际使用中cout需要时第一个形参对象,才能正常使用,所以将operator<<重载成了全局函数,但是又会导致类外没办法访问成员,此时就需要友元来解决,operator>>也是同理。
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}ostream& operator<<(ostream& _cout){_cout << _year << " " << _month << " " << _day << endl;return _cout;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 1);d1 << cout;//形式不符合常规调用return 0;
}
友元函数可以直接访问类中的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要再类的内部声明,声明时需要加上friend关键字。
下面我们来看下友元函数的应用:
class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 2004, int month = 1, int day = 1):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << " " << d._month << " " << d._day << endl;return _cout;
}istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year >> d._month >> d._day;return _cin;
}int main()
{Date d1(2022, 1, 1);//d1 << cout;//形式不符合常规调用Date d;cin >> d;cout << d << endl;return 0;
}
注意:
1.友元函数可访问类的私有成员和保护成员 ,但它并不是类的成员函数
2.友元函数不能用const修饰
3.友元函数可以在类定义的任何地方声明,不受类的访问限定符限制
4.一个函数可以是多个类的友元函数
5.友元函数的调用原理和普通函数的调用原理相同
2.2 友元类
友元类中的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的非公有成员。
注意:
1.友元的关系是单向的,不具有双向性
例如:下面的代码实例中Time类和Date类,在Time类中声明date类为其友元类,那么可以在Date类中直接访问Time类的私有成员函数,但想在Time类中访问Date类中私有的成员变量则不行。
2.友元关系不可传递
3.友元关系不能继承(关于继承方面以后再来进行介绍)
#include<iostream>
using namespace std;
class Time
{//将Date声明为Time的友元类friend class Date;
public:Time(int hour = 0, int minute = 0, int second = 0):_hour(hour),_minute(minute),_second(second){}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 2004, int month = 1, int day = 1):_year(year), _month(month), _day(day){}void SetTimeofDate(int hour, int minute, int second){//声明成友元后,Date可以直接访问Time类内的私有和保护成员_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};
二、内部类
概念:如果一个类定义在另一个类的内部,那么这个定义在类的内部的类就叫做内部类。
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象来访问内部类的成员。外部类对于内部类没有任何优越的访问权限。但是内部类是其外部类的友元类,内部类可以通过外部类的对象参数来访问外部类的所有成员,但是外部类不是内部类的友元。
也就是说内部类和外部类,它们两个类时独立的,平行的,内部类是个独立的类,放在外部类里面,仅仅受到类域的印象。而且内部类可以放在外部类的私有区,那样的话内部类就是外部类的专属类。
特性:
1.内部类可以定义在外部类的public,protected,private都是可以的
2.注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
3.sizeof(外部类) = 外部类,和内部类没有任何关系
class A
{
private:static int k;int h;
public://B就是A的内部类,并且B是A的友元class B{public:void foo(const A& a){cout << k << endl;cout << a.h << endl;}};
};int A::k = 1;int main()
{A::B b;b.foo(A());return 0;
}
三、匿名对象
关于匿名对象我们先看下面的一段代码来感受一下:
class N
{
public://构造函数N(int a = 0):_a(a){cout << "N(int a)" << endl;}//析构函数~N(){cout << "~N()" << endl;}private:int _a;
};class Solution
{
public:int Sum_solution(int n){return n;}
};int main()
{/*A::B b;b.foo(A());*/N aa1;//不能像下面这样去定义对象,这样的话编译器会无法识别到底是函数声明还是对象定义//A aa1();//我们可以定义匿名对象,匿名对象特点就是不需要名字//但是它的声明周期就只要一行,过了定义的这一行就会直接调用析构函数销毁N();//匿名对象N aa2(2);//匿名对象在如下场景很好用,当然还有一些其他的使用场景Solution().Sum_solution(10);return 0;
}
注意:
1.匿名对象和拷贝时的临时变量都是具有常性的
2.匿名对象生命周期仅在这一行,执行之后就会直接调用析构函数销毁
四、拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,也是减少了一些时间上的消耗。
在传值传参的时候,先要进行拷贝构造。
对于优化的具体实现这里不做解释,我们只需要记住一般编译器的优化规则:
f(1);
隐式类型,连续构造+拷贝构造->优化为直接构造
f1(A(2));
一个表达式中,连续构造+拷贝构造->优化为一个构造
A aa2 = f2();
一个表达式,连续拷贝构造+拷贝构造->优化为一个拷贝构造
aa1 = f2()
一个表达式中,连续拷贝构造+赋值重载->无法优化
这里的等号是赋值重载,拷贝构造和赋值重载在一起时不能优化的,建议尽量不要用以上写法。
对于优化这部分到这里就介绍完了。
下面我再来对构造和析构的细节进行一些补充:
五、关于构造和析构的一些补充
1.构造和析构是在栈帧里面的满足先进后出,后进先出的规则,因此后定义的对象先析构,先定义的对象后析构(构造是先定义的先构造,后定义的后构造,构造就相当于入栈,析构是出栈)。
2.若为全局变量,main函数之前就构造了。
3.局部的静态变量在第一次调用的时候构造,第二次调用不会构造,只会初始化一次。
(静态变量只能初始化一次)
4.先构造全局,再构造静态,然后再试普通对象
结语:
关于类和对象的内容,至此就结束了,希望大家能从这几篇博客中对类和对象有更加深刻的认识,接下来会继续更新C++的内容。