1. 友元
友元是一种突破封装性的方式,对于解决一些问题很方便,但是增加了耦合度,破坏了封装,所以不宜多用。
1.1 友元函数
之前我们重载了很多运算符,现在我们来重载一下<<运算符,看看会有什么问题。
class Date
{
public:
Date(int year = 2023, int month = 11, int day = 5)
:_year(year)
,_month(month)
,_day(day)
{
}
ostream& operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day;
return out;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
cout << d;
return 0;
}
因为我们在类内的成员函数的第一个参数默认是this指针(左操作对象),抢占了cout的位置,实际用法是,cout为左操作对象,Date为右操作对象,所以如果按照上面写法写的话就需要写成 d << cout的形式 ,看起来会很别扭,所以为了解决上面的问题,我们需要显示的传参,所以我们这个函数不能定义在类内,但这也引出了另一个问题,类外不能访问类的成员变量。
class Date
{
public:
Date(int year = 2023, int month = 11, int day = 5)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
int main()
{
Date d;
cout << d;
return 0;
}
这里我们就需要用友元函数来解决这个问题,友元函数不属于类,它是定义在类外的普通函数,但是它的声明需要再类内,并且要加上friend关键字
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year = 2023, int month = 11, int day = 5)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
int main()
{
Date d;
cout << d;
return 0;
}
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数友元函数的调用与普通函数的调用原理相同
1.2 友元类
了解了友元函数之后,对友元就很容易理解了,形式和友元函数类似
class Date
{
friend class A;
public:
Date(int year = 2023, int month = 11, int day = 5)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
class A
{
public:
void Print(Date& d)
{
cout << d._year << "-" << d._month << "-" << d._day << endl;
}
};
int main()
{
Date d;
A a;
a.Print(d);
return 0;
}
注:
- 友元关系是单向的,不具有交换性。
比如上述Date类和A类,在Date类中声明A类为其友元类,那么可以在A类中直接访问Date类的私有成员变量,但想在Dtae类中访问A类中私有的成员变量则不行。- 友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承
2. 内部类
概念:如果一个类定义在一个类的内部,那么这个类就是内部类,外部类不能访问内部类的成员,反而内部类天生就是外部类的友元类,可以访问外部类的成员。
- 内部类可以定义在任何的访问限定修饰符里
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
- sizeof(外部类)= 外部类,和内部类没有关系,进一步说明了内部类和普通的类差不多,只是被外部类限定了范围。
class Date
{
public:
Date(int year = 2023, int month = 11, int day = 5)
:_year(year)
,_month(month)
,_day(day)
{
}
class A
{
public:
A(int a = 1,int b = 2,int c = 3,int d = 4)
:_a(a)
,_b(b)
,_c(c)
,_d(d)
{}
private:
int _a;
int _b;
int _c;
int _d;
};
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
cout << sizeof(Date) << endl;
cout << sizeof(Date::A) << endl;
return 0;
}
3. 匿名对象
匿名对象顾名思义就是没有名字的对象,和我们C语言的匿名结构体很相似。
注:匿名对象的生命周期就在它定义的这一行,过了就会调用它的析构函数。
class A
{
public:
A(int a = 1)
:_a(a)
{}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A();
cout << "这是匿名函数的下一行" << endl;
return 0;
}
在一些特定的场景下,匿名函数会很好用,等写的代码多了,我们就会有很好的体会。
4. 拷贝对象时的一些编译器优化
我们在值传参和值返回的时候都会发生拷贝构造,在一些情况下,编译器会减少对象的拷贝,提高程序的效率。
先说结论:
- 构造 + 构造 优化成 构造
- 构造 + 拷贝构造 优化成 构造
- 拷贝构造 + 拷贝构造 优化成 拷贝构造
注:只有连续的情况下才会发生。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void fun(A a)
{
}
A fun1()
{
A aa;
return aa;
}
int main()
{
//这里我们先用构造函数构造了A,然后在值传参的过程中会发生拷贝构造(构造 + 拷贝构造)
fun(A(2));
//这里我们值返回发生了拷贝构造,然后再拷贝构造a(拷贝构造 + 拷贝构造)
//注意这里不是赋值拷贝,因为赋值拷贝是对于两个存在的对象
A a = fun1();
}