目录
有元
使用有元就可以突破封装,可以直接对类当中 私有的 成员 成员函数等等进行访问,在某一次上提供了遍历,但是这增大的 耦合性,破坏了封装,所以建议有元不要多用。
所谓耦合性就是 ,某两个 东西的 关系,越紧密,耦合性就越大。其实,我们要实现封装的话,关系紧密不是好的结果。
有元函数
在类当中使用 friend 关键字修饰一个 在类外 的 普通函数(这个函数不属于任何类),也就是用 friend 关键字 在类当中进行声明,在类外面进行定义,那么这个函数就是这个类的友元函数,这个函数就可以访问这个类当中的私有的成员。
如下面这个例子,因为 成员函数的第一个参数是 对应对象的 this 指针,那么如果我们在 类当中定义重载运算符,第一个参数就必须是 这个对象的this指针,那我们在调用这个函数的时候,左边的变量只能是对象,比如我们重载 "<<" ,那么我们就只能 d1 << cout ,这样写, 如果 cout << d1 ,这样写就会报错。
但是第一种方式不是我们平常的书写习惯,所以我们想到在类外定义,然后为了 这个 "<<" 重载函数能访问到类当中的成员,我们就可以把这个重载运算符的函数,声明为有元函数,即代表着,这个函数是这个类的朋友,那么这个函数就可以访问其中 私有的成员。
定义在类中:
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;
};
定义在 类外:
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;
}
【注意】:
- 有元函数可以访问 私有的 受保护的成员,但是不能访问成员函数。
- 有元函数不能用const 修饰, 因为它不能在类当中定义,没有 this 指针。
- 有元函数可以在类当中的任何地方进行声明,不受类访问限定符的限制
- 一个函数可以和多个类进行有元
- 有元函数的调用和普通函数的调用是相同的
有元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
【注意】:
- 有元是单向的,比如 D1类是 D2类的有元类,那么D1类就可以访问其中的成员函数等等,但是 D2就不能访问 D1 当中的成员函数。
- 有元不能传递,比如 C 是 B 的有元,B 是 A的有元,但是 C 不是 A的有元。
- 有元关系不能继承。
class A
{
friend class B;
public:
int Get_a()
{
return _a;
}
protected:
int _a = 1;
};
class B
{
public:
int GetA_a()
{
// 直接访问 A类当中的私有成员
return a._a;
}
protected:
int _b = 0;
A a;
};
内部类
定义在类当中的类的就是这个类的内部类,这个内部类,就是一个类,通过外部的 类是没办法访问内部类中的私有的成员的,也就是说内部类不属于外部类。
我们先来计算一下一个 下列代码当中 A类的 大小:
我们发现是4,这个4只计算了 _a 这个成员的大小,因为 A 当中的 _b 是静态的,我们说类当中只是的成员只是声明,而定义是在 对象当中,但是静态的成员不存储在对象当中,而是在 全局当中进行 定义。那么 类A 当中的 类B 也是如此,A 中没有 B对象,所以不计算 B 的大小。如果我们在 A的成员中 定义了 B 这个对象那么大小就是 8 了:
像上述的 B 类 定义的在 A类的 外面,和 B类 定义在 A类当中的 区别就是,前者是在全局域当中定义,后者是在 A这个类域当中定义。
形象的说,如果 类是图纸,对象是对应建造的房子,那么像上述 B是A的内部类,我们创建A类的对象当中是没有B类的空间的,也此时就是在 A的房子中放了 B的 图纸。
那么既然是在 A的类域当中定义,那么这个 B类就可以访问A类当中的私有成员,也就是内部类就是外部类的友元类。但是外部类不是内部类的友元。
class A
{
public:
class B
{
public:
int GetA_a()
{
return a._a;
}
protected:
int _b = 0;
A a;
};
protected:
int _a = 1;
static int _b;
B b;
};
- 1. 内部类可以定义在外部类的public、protected、private都是可以的。
- 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- 3. sizeof(外部类)=外部类,和内部类没有任何关系
只有是 定义出来的才受到 访问限定符的限制,向上述只是声明,所以是不会限制的。
匿名对象
class A
{
public:
int Get_a()
{
return _a;
}
protected:
int _a;
};
int main()
{
// 匿名创建对象
A();
}
如上述就是使用 匿名对象的创建一个对象。
使用匿名对象来调用成员函数:
int main()
{
// 匿名创建对象
A().Get_a();
}
我们可以使用这样的方式来 一次性调用 类当中的成员函数,如果是多次,那么我们就得使用 普通的创建对象的方式来 调用成员函数了。
之所以这样说,是因为匿名对象是 即用即销毁。
有名对象的生命周期是在 函数局部域,匿名函数的生命周期是在当前行。
【注意】:
我们不能这样去调用成员函数:
A::Get_a();
这样调用的前提是 这个函数是静态的,如果这样调用这个函数就必须没有 this 指针。而成员函数是必须有 this 指针的。
匿名对象和 临时对象一样,具有常性,所以,我们不能创建 匿名对象的普通引用:
所以要加一个 const 才能编译通过:
const A& pa = A();
而且当我们使用 const 引用这个 匿名对象之后,这个匿名对象的 生命周期会被延长,延长的时间是 引用的生命周期。
如下面这个例子:
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
int Get_a()
{
return _a;
}
protected:
int _a;
};
class AAAAAAAAA
{
public:
AAAAAAAAA()
{
cout << "AAAAAAAAA()" << endl;
}
~AAAAAAAAA()
{
cout << "~AAAAAAAAA()" << endl;
}
};
int main()
{
const A& pa = A();
AAAAAAAAA aaaaaaaa;
}
我们把 两个类的 构造函数 和 析构函数 只要已调用就打印一下,表明调用了这个函数,我们来看输出结果:
我们发现, A() 这个匿名函数是在 AAAAAAAAA()这个对象销毁之后才销毁的。
匿名函数的使用场景:
void Print_string(const string& st)
{
cout << st << endl;
}
int main()
{
string str("11111"); // 1
Print_string(str); // 2
return 0;
}
上述的 代码 1 和 2 可以 使用匿名对象 写成一行代码:
Print_string(string("11111"));
此时就使用了 匿名对象 进行传参。