目录
一、友元
1、定义
2、友元函数
3、友元类
二、内部类
1、定义
2、特性:
三、匿名对象
四、拷贝对象时的一些编译器优化
1、传值&传引用返回优化对比
2、匿名对象作为函数返回对象
3、接收返回值方式对比
总结:
一、友元
1、定义
- 友元分为:友元函数和友元类
2、友元函数
问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数,使用形式发生变化。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
// d1 << cout; 或者 d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
ostream & operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, 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;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
3、友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
中的私有成员变量
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 = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_time._hour = hour;
_time._minute = minute;
_time._second = second;
}
private:
int _year;
int _month;
int _day;
Time _time;
}
- 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承,在继承位置再给大家详细介绍。
二、内部类
1、定义
- 内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
- 外部类对内部类没有任何优越的访问权限。
2、特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
我们先来看一下 ”sizeof(外部类)=外部类,和内部类没有任何关系“ 在代码中怎么体现的。
class A{
private:
int h;
public:
class B{
private:
int b;
};
};
int main()
{
A aa;
cout << sizeof(aa) << endl;
return 0;
}
输出结果显示,类A的对象对象只有一个int成员的大小。
在调试中也可以看到类对象aa只有一个成员变量h。
内部类B跟A是独立,只是受A的类域限制。
可以通过下面代码访问到B类
A::B bb;
如果B类的作用域变为私有,则不能访问到。
B天生就是A的友元。
class A{
private:
int h = 0;
static int k;
public:
class B
{
public:
void Print(const A& a)
{
cout << k << endl;// >> OK
cout << a.h << endl;// >> OK
}
};
};
int A::k = 1;
int main()
{
A aa;
A::B bb;
bb.Print(aa);
return 0;
}
通过B类成功访问A类的静态成员变量k和整型成员变量h。
这时我们就可以对使用static成员的这道题使用内部类进行修改。
求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
class Solution {
class Sum {
public:
Sum() {
_sum += _i;
_i++;
}
};
private:
static int _sum;
static int _i;
public:
int Sum_Solution(int n) {
Sum a[n];
return _sum;
}
};
int Solution::_sum = 0;
int Solution::_i = 1;
三、匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用。
Solution().Sum_Solution(10);
return 0;
}
这样定义类对象可以吗?
A aa1();
- 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义。
但是我们可以这么定义匿名对象,匿名对象的特点不用取名字。
A();
但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
匿名对象在这样场景下就很好用。
Solution().Sum_Solution(10);
四、拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
1、传值&传引用返回优化对比
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 func1(A aa)
{
}
void func2(const A& aa)
{
}
int main()
{
A aa1 = 1; // 构造+拷贝构造 -》 优化为直接构造
func1(aa1); // 无优化,不能跨表达式优化
func1(2); // 构造+拷贝构造 -》 优化为直接构造
func1(A(3)); // 构造+拷贝构造 -》 优化为直接构造
cout << "----------------------------------" << endl;
func2(aa1); // 无优化
func2(2); // 无优化
func2(A(3)); // 无优化
return 0;
}
我们看一下main函数中的代码:
A aa1 = 1;
这里首先调用构造函数创建一个临时对象,然后调用拷贝构造函数将临时对象的内容复制到aa1。但是,编译器通常会进行优化,直接调用构造函数创建aa1,避免了不必要的拷贝构造。func1(aa1);
这里调用函数func1,参数是aa1的拷贝,所以会调用拷贝构造函数。这个过程没有优化。函数func1会调用析构函数清理临时变量aa。func1(2);
和func1(A(3));
这两行代码都是先构造一个临时对象,然后调用拷贝构造函数将临时对象的内容复制到函数参数。但是,编译器会进行优化,直接将临时对象作为函数参数,避免了不必要的拷贝构造。
然后是func2的调用:
func2(aa1);
func2(2);
和func2(A(3));
这三行代码都是将一个对象的引用作为函数参数,所以不需要调用拷贝构造函数,也就没有优化的空间。
-
func2(aa1)引用传值,不需要构造和析构。
-
func2(2)构造一个临时对象,然后拷贝构造给aa。
-
func2(A(3))中 A(3)
创建了一个临时对象,调用了构造函数A(int a = 0)
,并输出 "A(int a)"。-
这是因为在函数调用
func2(A(3));
中,临时对象被创建,即A(3)
。const A& aa
表示将这个临时对象通过常引用传递给func2
函数。在这里,没有发生拷贝构造,因为是通过引用传递的。 -
所以在
func2
函数内部,没有额外的构造或拷贝构造的调用。当func2
函数执行完毕,临时对象开始析构。这时调用了析构函数~A()
,并输出 "~A()"。这是因为在函数调用结束后,局部变量(包括通过临时对象构造的aa
)会被销毁。 -
最后,整个程序执行结束,全局的
A(3)
对象也会被销毁,调用析构函数~A()
。因此,总共有两次析构调用。一次是在func2
函数内部的临时对象销毁,另一次是全局的A(3)
对象销毁。 -
-
2、匿名对象作为函数返回对象
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;
};
A func3()
{
A aa;
return aa;
}
A func4()
{
return A();//匿名对象
}
int main()
{
func3();// 构造+拷贝构造
A aa1 = func3();//构造+两个拷贝构造>>>优化为构造+一个拷贝构造
func4(); // 构造+拷贝构造 -- 优化为构造
A aa3 = func4(); // 构造+拷贝构造+拷贝构造 -- 优化为构造
return 0;
}
通过对比,可以发现使用匿名对象在func4()中的好处。
在函数 func4()
中,return A();
创建了一个匿名对象,并且该匿名对象直接作为函数的返回值。这样,调用 func4()
将得到这个匿名对象的拷贝,而不需要额外的临时对象。因此,在 func4()
的调用中,可以直接构造并返回这个匿名对象,避免了多余的对象的创建和拷贝构造。
3、接收返回值方式对比
A func3()
{
A aa;
return aa;
}
int main()
{
A aa1 = func3(); // 拷贝构造+拷贝构造 -- 优化为一个拷贝构造
cout << "****" << endl;
A aa2;
aa2 = func3(); // 声明和定义不在一行,不能优化
return 0;
}
总结:
对象返回:
- 接收返回值对象,尽量拷贝构造方式接收,不要赋值接收。
- 函数中返回对象时,尽量返回匿名对象。
函数传参:
- 尽量使用const &传参。