1.友元函数的简单介绍
1.1为什么要使用友元函数
在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了
使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
1.2使用友元函数的优缺点
1.2.1优点:能够提高效率,表达简单、清晰。
1.2.2缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。
2.友元函数的使用
2.1友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
2.1.1 要访问非static成员时,需要对象做参数;
2.1.2 要访问static成员或全局变量时,则不需要对象做参数;
2.1.3 如果做参数的对象是全局对象,则不需要对象做参数;
2.2友元函数的位置
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
2.3友元函数的调用
可以直接调用友元函数,不需要通过对象或指针
2.4友元函数的分类:
根据这个函数的来源不同,可以分为三种方法:
2.4.1普通函数友元函数
2.4.1.1 目的:使普通函数能够访问类的友元
2.4.1.2 语法:
声明: friend + 普通函数声明
实现位置:可以在类外或类中
实现代码:与普通函数相同
调用:类似普通函数,直接调用
2.4.1.3代码:
class INTEGER
{
friend void Print(const INTEGER& obj);//声明友元函数
};
void Print(const INTEGER& obj)
{
//函数体
}
void main()
{
INTEGER obj;
Print(obj);//直接调用
}
2.4.2类Y的所有成员函数都为类X友元函数—友元类
2.4.2.1目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
2.4.2.2语法:
声明位置:公有私有均可,常写为私有(把类看成一个变量)
声明: friend + 类名(不是对象哦)
2.4.2.3代码:
class girl;
class boy
{
public:
void disp(girl &);
};
void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数
{
cout<<"girl's name is:"<
}
class girl
{
private:
char *name;
int age;
friend boy; //声明类boy是类girl的友元
};
main函数就不写了和普通调用时一样的。
2.4.3类Y的一个成员函数为类X的友元函数
2.4.3.1目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量
2.4.3.2语法:
声明位置:声明在公有中 (本身为函数)
声明:friend + 成员函数的声明
调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制
2.4.3.3代码:
实现代码和2.4.2.3中的实现及其相似只是设置友元的时候变为friend void boy::disp(girl &);自己解决喽……
小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。
4.友元函数和类的成员函数的区别
4.1 成员函数有this指针,而友元函数没有this指针。
4.2 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。
========================================================
=============================================================
版权声明:本文为博主原创文章,未经博主允许不得转载。
类的6个默认的成员函数包括:
构造函数、析构函数、拷贝构造函数、赋值运算符重载函数、取地址操作符重载、const
修饰的取地址操作符重载。
这篇文章重点解释前四个。
(一)构造函数
构造函数,顾名思义,为对象分配空间,进行初始化。它是一种特殊的成员函数,具有
以下特点:
1.函数名与类名相同。
2.无返回值。
3.构造对象的时候系统会自动调用构造函数。
4.可以重载。
5.可以在类中定义,也可以在类外定义。
6.如果类中没有给出构造函数,编译器会自动产生一个缺省的构造函数,如果类中有构
造函数,编译器就不会产生缺省构造函数。
7.全缺省的构造函数和无参的构造函数只能有一个,否则调用的时候就会产生冲突。
8.没有this指针。因为构造函数才是创建对象的,没有创建对象就不会有对象的首地址。
构造函数,说来就是给成员变量进行初始化。而初始化却有两种方法:
初始化列表、构造函数函数体内赋值。
举例:依然使用日期类来说明:
上边这段代码只是为了解释初始化列表初始化成员变量和在构造函数体内初始化,也解
释了无参构造函数和全缺省的构造函数。声明:由于上边的代码同时给出无参和全缺省
的构造函数,产生调用冲突,编译不通过。
既然有两种初始化的方法,我们究竟该怎样选择呢??
尽量使用初始化列表,因为它更高效。下边用代码说明它是怎么个高效法。
上边给出不使用初始化列表初始化日期类中的时间类对象的 办法,会导致时间类构造两
次,一次在主函数中定义时间类对象时,一次在参数列表中调用。而如果我们将所有
的成员变量都用初始化列表初始化,时间类构造函数只会被调用一次,这就是提高效率
所在。
有些成员变量必须再初始化列表中初始化,比如:
1. 常量成员变量。(常量创建时必须初始化,因为对于一个常量,我们给它赋值,是不
对的)2. 引用类型成员变量。(引用创建时必须初始化)
3. 没有缺省构造函数的类成员变量。(如果构造函数的参数列表中有一个类的对象,并
且该对象的类里没有缺省参数的构造函数时,要是不使用初始化列表,参数中会调用无
参或者全缺省的构造函数,而那个类中又没有。)
注意:在上边的main函数中要是有这样一句:Date d2();这不是定义一个对象,而是声
明了一个函数名为d2,无参,返回值为Date的函数。
(二)析构函数
析构函数是一种特殊的成员函数,具有以下特点:
1. 析构函数函数名是在类名加上字符~。2. 无参数无返回值(但有this指针)。3. 一个类有且只有一个析构函数,所以肯定不能重载。若未显示定义,系统会自动生成
缺省的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 注意析构函数体内并不是删除对象,而是做一些清理工作。(比如我们在构造函数
中动态开辟过一段空间,函数结束后需要释放,而系统自动生成的析构函数才不管内
存释放呢,所以需要人为地写出析构函数)
注意:对象生命周期结束后,后构造的对象先释放。
(三)拷贝构造函数:用已有的对象创建一个新的对象。仍然使用上边的日期类举例:
上边是用d1创建一个d2,系统会给出默认的拷贝构造函数,并且该函数的参数是一个常
引用,我们想象为什么必须是引用呢,如果不是又会发生什么。
如果不是引用,形参是实参的一份临时拷贝,由于两者都是对象,此时就会调用自己的
拷贝构造函数,陷入无限递归中.......
上边的代码,我们用默认的拷贝构造函数可以得到正确的结果,有时就不会。实例:
上边的代码会出错,原因见图片。
在析构时,同一块空间释放两次就会有问题。
这种仅仅只是值的拷贝的拷贝方式就是浅拷贝。
深拷贝就是为对象重新分配空间之后,然后将值拷贝的拷贝方式。
下边自己给出拷贝构造函数。
下边用图给出实现机理。
调用拷贝构造函数的3种情况:
1.当用类的一个对象去初始化该类的另一个对象时。
2.当函数的形参是类的对象,调用函数时进行形参和实参的结合时。
3.当函数的返回值是对象,函数执行完返回调用者时。(函数运行结束后,返回的对象
会复制到一个无名对象中,然后返回的对象会消失,当调用语句执行完之后,无名对
象就消失了)
调用拷贝构造函数的两种方法:
1.代入法:
Person p2(p1);
2.赋值法:
Person p2 = p1;
(四)赋值运算符重载函数
它是两个已有对象一个给另一个赋值的过程。它不同于拷贝构造函数,拷贝构造函数是
用已有对象给新生成的对象赋初值的过程。
默认的赋值运算符重载函数实现的数据成员的逐一赋值的方法是一种浅层拷贝。
浅层拷贝会导致的指针悬挂的问题:
看图:
使用深层拷贝来解决指针悬挂的问题:
这样先将p2对象里指针指向的旧区域,然后再分配新的空间,再拷贝内容。当然,对于
那些成员变量里没有指针变量就不会涉及到指针悬挂问题。
(五)取地址操作符重载
(六)const修饰的取地址操作符的重载
函数后边的const表明在函数体中不能改变对象的成员,当然可以改变mutable变量。函
数的返回值是指向常对象的指针。
一个静态成员函数不与任何对象相联系,故不能对非静态成员进行默认访问。
它们的根本区别在于静态成员函数没有this指针,而非静态成员函数有一个指向当前对象的指针this。
例如:
1 class Sc 2 { 3 public: 4 void nsfn(int a); //像声明Sc::nsfn(Sc *this , int a); 5 static void sfn(int a); // 无this指针 6 //.... 7 }; 8 9 void f(Sc &s) 10 { 11 s.nsfn(10); // 转换为Sc::nsfn(&s , 10) 12 s.sfn(10); // 转换为Sc::sfn(10) 13 }
函数nsfn()可被认为它声明为void Sc::nsfn(Sc* this , int a)。对nsfn()的调用,编译像注解的那样进行转换,s的地址作为第一个传递的参数。(你并不实际写该调用,由编译来实现。)
在函数内部,Sc::nsfn()对非静态成员的访问将自动把this参数作为指向当前对象的指针。而当Sc::sfn()被调用时,没有任何对象的地址被传递。因此,当访问非静态成员时,无this指针出错。这就是为什么一个静态成员函数与任何当前对象都无联系的原因。
1 class People{ 2 public: 3 ... 4 void Getup( ); 5 void Washing( ); 6 void eating( ); 7 ... 8 }
1 class People 2 { 3 ... 4 void morningAction( ) 5 { 6 Getup( ); 7 Washing( ); 8 eating( ); 9 } 10 }
1 void moringAction(People& p) 2 { 3 p.Getup( ); 4 p.Washing( ); 5 p.eating( ); 6 }
1 #include2 class Car; 3 4 class People 5 { 6 public: 7 People(int a,int h) 8 :age(a),height(h) 9 {} 10 11 inline int getAge() const 12 { 13 return age; 14 } 15 16 inline int getHeight() const 17 { 18 return height; 19 } 20 21 People & operator=(const Car& c); 22 private: 23 int age; 24 int height; 25 }; 26 27 class Car 28 { 29 public: 30 Car(double c, double w) 31 :cost(c),weight(w) 32 {} 33 34 inline double getCost() const 35 { 36 return cost; 37 } 38 39 inline double getWeight() const 40 { 41 return weight; 42 } 43 44 Car & operator=(const People& c); 45 private: 46 double cost; 47 double weight; 48 }; 49 50 People & People::operator=(const Car& c) 51 { 52 age = static_cast<int>(c.getCost()); 53 height = static_cast<int>(c.getWeight()); 54 return *this; 55 } 56 57 Car & Car::operator=(const People& c) 58 { 59 cost = static_cast<double>(c.getAge()); 60 weight = static_cast<double>(c.getHeight()); 61 return *this; 62 } 63 64 int main(int argc,char * argv[]) 65 { 66 Car c(1000.87,287.65); 67 People p(20,66); 68 People p2(0,0); 69 Car c2(0.00,0.00); 70 p2=c; 71 c2=p; 72 std::cout<< "car'info: cost is " << c2.getCost() << ". weight is " << c2.getWeight() <<std::endl; 73 std::cout<< "people'info: age is " << p2.getAge() <<". height is " << p2.getHeight() <<std::endl; 74 return 0; 75 }
car'info: cost is 20. weight is 66 people'info: age is 1000. height is 287
1 typedef void (*fun) ( ) //一个指向空函数的指针 2 fun funArray[10]; //含有10个函数指针的数据。 3 int function( ); //一个返回值为int类型函数 4 funArray[0] = &function( ) //错误!类型不匹配 5 funArray[0] = reinterpret_cast(&function); //ok
1 class Rational { 2 public: 3 Rational( int numerator = 0,int denominator =1 ); 4 int numerator( ) const; 5 int denominator ( ) const ; 6 private: 7 ... 8 }
1 class Rational { 2 public: 3 ... 4 const Rational operator* (const Rational& rhs) const; 5 ... 6 } 7 Rational testOne(1,4); 8 Rational testTwo(1,1); 9 //做算术运算 10 Rational result = testOne * testTwo; 11 //与常量做运算 12 result = testOne * 2; //ok 13 //乘法满足交换定律 14 result = 2 * testOne //error!!
1 result = testOne.operator*(2); //ok 2 result =2.operator*(oneHalf); //error
1 class Rational{ 2 ... 3 }; 4 const Rational operator*(const Rational & lhs, const Rational & rhs) 5 { 6 return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator() ); 7 } 8 Rational testOne(1, 4); 9 Rational result; 10 result = oneFourth *2; 11 result = 2 * oneFourth; 通过 12 }
1 public Name{ 2 public: 3 Name(const string& s); //转换string到Name 4 5 ... 6 }; 7 8 class Rational { //有理数类 9 public: 10 //转换从int到有理数类 11 Rational(int numerator=0,int denominatior =1); 12 ... 13 }
1 template<class T> 2 class Array{ 3 Array(int lowBound,int highBound); 4 Array(int size); 5 T& operator[](int index); 6 ... 7 }; 8 bool oerpator==(const Array<int>& lhs,const Array<int>& rhs); 9 Array<int> a(10); 10 Array<int> b(10); 11 ... 12 for(int i=0;i < 10; ++i) 13 if(a == b[i]) { 14 ... } 15 else 16 { 17 ... 18 }