•A friend class must be previously declared in an enclosing scope or defined in the non-class scope immediately enclosing the class that is declaring it a friend
class X { … };
namespace N {
class Y {
friend class X;
friend class Z;
friend class AE;
};
class Z { … };
}
class AE { … }; // not a friend of Y
友元函数可以像友元一样声明,也可以通过其自定义参数的作用域找到。
例子:
namespace N{
class X;
void f();
void h(const X&);
class X {
friend void f();
friend void h(const X&);
};
}
void g(const N::X& x)
{ f(); // ERROR
h(x);//CORRECT
}
什么时候用友元?什么时候用成员?
面向对象设计的特点应该是封装性,我们需要思考这个函数是否需要访问私有数据成员?如果确实需要访问,还涉及成员状态的属性,我们就需要友元函数。
某些操作必须是成员函数: 构造函数、析构函数、= 、 [ ] 、 ( ) 、 ->
修改类状态的操作应该是成员函数或采用非 const 引用(或指针)参数的全局函数。
隐式类型转换
先给例子:
class X {
…
public:
X(int);
int m1();
int m2() const;
friend int f1(X&);
friend int f2(const X&);
friend int f3(X);
};
99.m1() or 99.m2() //error
f1(99) //error
f2(99) and f3(99) //OK
为什么99.m1()是错的?是因为点运算符和指针的指向运算分左侧的对象是不会帮你做隐式类型转换的。
f1(99)错误的原因在于f1(x&)的参数是引用类型,只能引用左值,所以不能引用临时对象,想要引用临时对象只能通过const &,f3的参数是X,99就会调用构造函数,创建临时对象。
我们都知道,运算符函数参数的数据类型用指针就不合适,效率慢。
当我们用用临时对象返回,涉及到拷贝操作,会比较耗时。为了提高效率,我们就会采用move。
move的操作其实就是接管临时对象的存储空间(转移控制权),这样就不需要创建另外的存储空间。
&&是右值引用,为什么右值引用不能加const,因为在move constructor和assignment中会修改这个对象的状态(原来的对象的指针会修改指向NULL,让operator接管原来的空间)
//move版本
void swap(matrix& a, matrix& b)
{ matrix t{move(a)};
a=move(b);
b=move(t);
}
//copy版本
void swap(matrix& a, matrix& b)
{ matrix t;
t=a;
a=b;
b=t;
}
string g(string arg)
{
return arg;
}
string s{"Newton"};
s=g(s);
/* string arg=s; string temp=arg;
destroy arg;
s=temp; destroy temp. */
上面是实参初始化临参的例子,内部的操作中包含了建立了一个temp拷贝了返回的arg,当s被赋值完后就会destroy temp。(注意释放的先后顺序)
这里的Mystring s=’a其实调用的是MyString(unsigned int=100);这就造成了隐式的混淆。
因此出现了关键词explicit,要求定义的函数为显式函数。
class String{
explicit String(int n);
String(const char*);
……
};
String s1 = 'a'; // Error
String s2(10);
String s3 = String(10);
String s4 = "xxxx";
String s5("xxxx");
比较条件可以是一个比较对象类,重载()符号。
重载++符号的注意事项
递增和递减运算符在C++运算符中是唯一的,因为它们可以用作前缀和后缀运算符。 后缀运算符需要一个 int 参数来将其与前缀运算符区分开来。 这个参数没有意义——所以叫假参数,它不能用在算子函数定义中。