友元旨在让函数或类访问另一个类中的成员, 下面根据友元的类型简单做一下整理.
1. 普通函数作友元
如果一个函数想要访问一个类中的(私有)成员, 则必须将它声明为该类的友元函数. 这种是最简单的情形, 例如下面的代码:
class BaseClass{
private:
int value;
public:
BaseClass(int v) : value(v) {
}
int countOnes() const ;
friend std::ostream& operator<<(std::ostream& os, BaseClass& bc);
};
int BaseClass::countOnes() const {
int v = this->value;
int num = 0;
while (v){
++num;
v &= (v - 1);
}
return num;
}
std::ostream& operator<<(std::ostream& os, BaseClass& bc){
os << bc.value << " " << bc.countOnes() << "\n";
return os;
}
int main(){
BaseClass bc (5);
std::cout << bc;
system("pause");
return 0;
}
输出:
5 2
以上代码中重载了<<运算符, 并输出成员value的值和二进制的value中包含多少个1. 这样就可以在类外调cout << .
2. 一个类作为另一个类的友元
如果两个类之间不是继承关系, 也不是一个包含另一个的关系, 而是只是共享某些特征, 可以考虑将一个类作为另一个类的友元. 例如, 我们希望B类能访问A类的成员, 那么B把A当朋友. 当然这种不是双向的(比如大多数感情一样), A并不能访问B的成员. 这种情形, 要在A类的声明中加入friend class B(声明的位置无关紧要), 说明B是A的友元.
例如下面的代码:
class FriendA {
private:
int money;
friend class FriendB;
public:
FriendA(int m) : money(m) {
}
};
class FriendB{
private:
int money;
public:
FriendB(int m) : money(m) {
}
bool AmIRich(FriendA& fa) {
return this->money > fa.money;
}
};
int main(){
FriendA a (100);
FriendB b (30);
std::cout << b.AmIRich(a);
system("pause");
return 0;
}
输出:
0
这种情况下, FriendB能访问FriendA的所有成员. 但有时候我们不希望这样, 而是规定B的某些函数才能访问A的成员, 这样就有了成员函数作友元.
3. 成员函数作友元
这种情况略微复杂一些. 复杂在我们必须要弄明白类声明的顺序. 例如, 如果FriendB类的一个函数想要获取到FriendA类的一个成员, 如果这么写:
class FriendA {
private:
int money;
std::string address;
friend std::string FriendB::arriveA(FriendA&);
public:
FriendA(int m, std::string add) : money(m), address(add) {
}
};
class FriendB{
private:
int money;
public:
FriendB(int m) : money(m) {
}
std::string arriveA(FriendA& fa) {
return fa.address;
}
};
就会报错:
为什么呢? 因为编译器看到FriendB::arriveA()函数的时候, 它不认识, 它不知道FriendB类里面有没有这个函数. 那我们如果把FriendB类放到FriendA类前面呢? 也不行, 因为FriendB类里函数定义用到了FriendA. 打破这种僵局的方法是,
- B的函数定义有A的参数, 则A的声明必须在B前面, 也即在B之前加上
class A;
声明 - A中声明B的函数为A的友元, 则B的完整定义必须在A之前
- 在A的友元函数的定义中, 编译器必须知道该函数已经是A的友元, 因此要在类定义之后定义. 如果放在B中, 这时候编译器还不知道它是A的友元函数.
总之, 声明定义的原则是: 编译器到这里的时候知道不知道.
综上, 代码修改如下:
class FriendA; // 首先声明 B中的函数知道有A这么个东西
class FriendB{
private:
int money;
public:
FriendB(int m) : money(m) {
}
std::string arriveA(FriendA& fa); // 不能在此定义函数, 因为编译器不知道fa是可访问的
};
class FriendA {
private:
int money;
std::string address;
friend std::string FriendB::arriveA(FriendA&); // 声明友元关系, 这时候编译器知道arriveA是B的一个成员了
public:
FriendA(int m, std::string add) : money(m), address(add) {
}
};
std::string FriendB::arriveA(FriendA& fa){
// 在类外定义函数才行
return fa.address;
}
int main(){
std::string str = "Sdasdjw";
FriendA fa (2, str);
FriendB fb (2);
std::cout << fb.arriveA(fa);
system("pause");
return 0;
}
输出:
Sdasdjw
4. 两个类互为友元类或者共有友元函数
感情当然可以是双向的. 如果A是B的友元类, B也是A的友元类, 这种情况直接声明即可.但需要注意的是, 若先声明A后声明B, 则需要将A的涉及到B的函数放在B后面类外定义. 这样编译器才能知道B中的信息. 例如:
class B;
class A{
private:
int num;
friend class B;
public:
A(int n) : num(n) {
}
void showBName(B& b);
};
class B{
private:
char* name;
friend class A;
public:
B(char* n) {
this->name = new char[strlen(n)];
strcpy(this->name, n);
}
virtual ~B() {
delete[] this->name; }
void showANum(A& a){
std::cout << a.num;
}
};
void A::showBName(B& b){
std::cout << b.name;
}
int main(){
A a (233);
B b ("sdajw");
a.showBName(b);
b.showANum(a);
system("pause");
return 0;
}
共有友元函数情形相同, 最好也是在两个类后再定义.
5. 模板类的友元函数
模板类的友元函数大概分三类, 第一类是非模板友元函数. 也即当作最普通的函数看待, 我们需要对每个可能的模板类型都重载一个友元函数.
第二类是约束模板友元函数, 也就是模板类的类型要与类的类型保持一致.
第三类是非约束模板友元函数, 也即友元函数比较独立, 友元函数的类型与类的类型是独立(不同)的.
5.1 非约束模板友元函数
这样的友元在声明时, 需要额外再写一个template, 因为和类的template是不同的.
template<typename T>
class TestClass{
private:
T name;
public:
TestClass(T t) {
this->name = t;}
template<typename T1> friend void showName(T1&);
};
template<typename T1>
void showName(T1& t){
std::cout << t.name << "\n";
}
int main(){
TestClass<std::string> tc ("asdwqdw");
showName(tc);
system("pause");
return 0;
}
输出:
asdwqdw
5.2 约束模板友元函数
约束, 即友元函数的类型与模板的类型相同, 这时要在类中将模板具体化, 而且要提前声明, 如下:
template<typename T> void showName(T&); // 提前声明
template<typename T>
class TestClass{
private:
T name;
public:
TestClass(T t) {
this->name = t;}
friend void showName<>(TestClass<T>&); // <>是模板具体化, 将函数参数类型与模板绑定
};
template<typename T>
void showName(T& cls){
// 正常声明即可 不要声明成TestClass<T>&
std::cout << cls.name << "\n";
}
int main(){
TestClass<std::string> tc ("asdwqdw");
showName(tc);
system("pause");
return 0;
}
输出:
asdwqdw