友元函数/类 :突破访问限定符,允许非同一个类的函数或者类访私有成员变量。
class A
{
public:
A()
:_a(0)
,_b(1)
{
cout << "A()" << endl;
}
private:
int _a = 0;
int _b = 1;
};
void fun(const A& a)
{
cout << a._a << endl;
}
这个代码会报错,原因是_a是a._a的私有成员,fun()是类外函数不能访问。
加上友元就可以突破限制
class A
{
friend void fun(const A& a);
public:
A()
:_a(0)
,_b(1)
{
cout << "A()" << endl;
}
private:
int _a = 0;
int _b = 1;
};
void fun(const A& a)
{
cout << a._a << endl;
}
关于友元的知识
友元函数是一种声明,表明这个函数可以访问私有变量。
友元函数本质上不属于类函数,所以这个声明是不受访问限定符限制的,可以定义在public或者private中。
一个函数可以是多个类的友元函数。
友元函数会破快封装性,所以尽量不要用友元。
友元类:
class A
{
friend class B;
public:
A()
:_a(0)
,_b(1)
{
cout << "A()" << endl;
}
private:
int _a = 0;
int _b = 1;
};
class B
{
public:
void fun(const A& a)
{
cout << a._a << endl;
}
private:
int _c = 1;
};
将fun封装到class B中,在A中用友元声明,使得fun()函数可以访问A中的私有成员变量。
内部类是在类中再嵌套一个类。
class A
{
public:
class B
{
public:
void fun(const A& a)
{
cout << a._a << endl;
}
private:
int _c = 1;
};
A()
:_a(0)
,_b(1)
{
cout << "A()" << endl;
}
private:
int _a = 0;
int _b = 1;
};
如图在class A中定义一个类class B
计算类的大小
cout << sizeof(A) << endl;//结果为8
我们知道计算类的大小是不计算函数的,那么B类为什么不会计算到其中,因为B类在其中相当于声明。当然class A也是一种声明,本身应该不应该有大小,sizeof是计算创建A的实例的大小。sizeof(A)可以看成创建类A的实例后计算大小,如果创建A的实例是不会有类B成员的,只有_a,_b成员。把代码修改成
class A
{
public:
class B
{
public:
void fun(const A& a)
{
cout << a._a << endl;
}
private:
int _c = 1;
};
A()
:_a(0)
,_b(1)
{
cout << "A()" << endl;
}
private:
int _a = 0;
int _b = 1;
B b;
};
结果是12,计算了B类成员的大小。
1.类声明本身是没有大小的,相当于图纸。
2.sizeof(类),计算的是类的实例的大小。如果类创建的成员变量有内部类实例,那么会计算内部类的大小,如果成员变量没有,只有类声明那就不算内部类大小。
3.类大小只算成员变量大小。
内部类的性质
内部类是类的友元类,可以在内部类访问类的私有变量,但是类不是内部类的友元类,所以不能访问内部类的私有成员变量。
B可以访问_a,_b,A不能访问_c
匿名对象
class A
{
public:
A()
:_a(0)
, _b(1)
{
cout << "A()" << endl;
}
void fun()
{
cout << "fun()" << endl;
}
private:
int _a = 0;
int _b = 1;
};
A a;//有名对象
A();//匿名对象
有名对象无匿名对象的区别:
有名对象--生命周期在当前函数局部域。
匿名对象--生命周期在当前行。/这行代码运行结束后就会调用析构函数。*‘
有名对象不能加括号:不知道是对象还是函数名
A a();/无法分辨是对象还是函数,传参初始化是可以加。
匿名对象和临时对象具有常性。
const A&aa=A();
匿名对象如果被引用不会变成野引用 const引用延长了匿名对象的生命周期,生命周期在当前函数局部域,就是函数结束时才调用析构函数。
匿名函数的使用场景:更简单地调用类成员函数
//第一种方式
A a;
a.fun();
//第二种方式
A().fun();
传参时省略创建实例
编译器会对同一条表达式调用多个构造函数时进行优化
如拷贝构造+构造函数可能直接被优化为构造函数。
A a=2;
这发生类型转换,先生成临时变量const A b(2),临时变量再对A拷贝构造A a(const A b),编译会直接优化成A a(2)不调用临时拷贝,但用const A& c=2,可以证明临时变量确实会生成。
接收传值时优化
返回A时有拷贝构造,生成临时对象,生成的临时对象初始化ra也调用拷贝构造。(不是等号运算符重载,因为A ra是被初始化的)
编译器会优化成两次拷贝构造会被合成一次拷贝构造。
//避免的写法
A ra;
A ra=fun5();
//这就不是两次构造了
传参时优化
void fun6(A a)
{
}
//不是同一行表达式,不会优化,构造+拷贝构造
A a(1);
fun6(a);
//匿名,会优化
fun6(A(1));
//最佳写法,隐式类型转换,会优化
fun6(1);