友元函数this

关于C++中的友元函数的总结

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 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。



========================================================

=============================================================


 

详解c++中类的六个默认的成员函数

标签: c++中类的默认的成员函数
  1212人阅读  评论(0)  收藏  举报
  分类:
c++(13) 

类的6个默认的成员函数包括:

构造函数、析构函数、拷贝构造函数、赋值运算符重载函数、取地址操作符重载、const

修饰的取地址操作符重载。

这篇文章重点解释前四个。

(一)构造函数

构造函数,顾名思义,为对象分配空间,进行初始化。它是一种特殊的成员函数,具有

下特点:

1.函数名与类名相同。

2.无返回值。

3.构造对象的时候系统会自动调用构造函数。

4.可以重载。

5.可以在类中定义,也可以在类外定义。

6.如果类中没有给出构造函数,编译器会自动产生一个缺省的构造函数,如果类中有构

造函数,编译器就不会产生缺省构造函数。

7.全缺省的构造函数和无参的构造函数只能有一个,否则调用的时候就会产生冲突。

8.没有this指针。因为构造函数才是创建对象的,没有创建对象就不会有对象的首地址。

构造函数,说来就是给成员变量进行初始化。而初始化却有两种方法:

初始化列表、构造函数函数体内赋值。

举例:依然使用日期类来说明:

[cpp]  view plain  copy
 
  1. #define _CRT_SECURE_NO_WARNINGS 1  
  2.   
  3. #include  
  4. using namespace std;  
  5.   
  6. class Date   
  7. {  
  8. public:  
  9.     Date()//无参构造函数  
  10.     {  
  11.         m_year = 2016;  
  12.         m_month = 7;  
  13.         m_day = 6;  
  14.     }  
  15.     Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数  
  16.     {  
  17.         m_year = year;  
  18.         m_month = month;  
  19.         m_day = day;  
  20.     }  
  21.     Date(int year, int month, int day) :m_year(year), m_month(month), m_day(day)  
  22.         //初始化列表初始化成员变量  
  23.     {  
  24.     }  
  25.     void print()  
  26.     {  
  27.         cout << m_year << "-" << m_month << "-" << m_day << endl;  
  28.     }  
  29. private:  
  30.     int m_year;  
  31.     int m_month;  
  32.     int m_day;  
  33. };  
  34. int main()  
  35. {  
  36.     Date date(2016,7,4);  
  37.     date.print();  
  38.     system("pause");  
  39.     return 0;  
  40. }  


上边这段代码只是为了解释初始化列表初始化成员变量和在构造函数体内初始化,也解

释了无参构造函数和全缺省的构造函数。声明:由于上边的代码同时给出无参和全缺省

的构造函数,产生调用冲突,编译不通过。

既然有两种初始化的方法,我们究竟该怎样选择呢??

尽量使用初始化列表,因为它更高效。下边用代码说明它是怎么个高效法。

[cpp]  view plain  copy
 
  1. "code" class="cpp">class Time  
  2. {  
  3. public:  
  4.     Time(int hour= 1, int minute=1, int second=1)  
  5.     {  
  6.         m_hour = hour;  
  7.         m_minute = minute;  
  8.         m_second = second;  
  9.         cout << "构造时间类对象中..." << endl;  
  10.     }  
  11. private:  
  12.     int m_hour;  
  13.     int m_minute;  
  14.     int m_second;  
  15. };  
  16. class Date  
  17. {  
  18. public:  
  19.     Date(int year, int month, int day,Time t)  
  20.         /*:m_year(year),  
  21.         m_month(month), 
  22.         m_day(day) 
  23.         */  
  24.     {  
  25.         m_year = year;  
  26.         m_month = month;  
  27.         m_day = day;  
  28.         m_t = t;  
  29.     }  
  30.     void print()  
  31.     {  
  32.         cout << m_year << "-" << m_month << "-" << m_day << endl;  
  33.     }  
  34. private:  
  35.     int m_year;  
  36.     int m_month;  
  37.     int m_day;  
  38.     Time m_t;  
  39. };  
  40. int main()  
  41. {  
  42.     Time t(10,36,20);  
  43.     Date d(2016,7,6,t);  
  44.     system("pause");  
  45.     return 0;  
  46. }  

 
     

上边给出不使用初始化列表初始化日期类中的时间类对象的 办法,会导致时间类构造两

次,一次在主函数中定义时间类对象时,一次在参数列表中调用。而如果我们将所有

的成员变量都用初始化列表初始化,时间类构造函数只会被调用一次,这就是提高效率

所在。

有些成员变量必须再初始化列表中初始化,比如:

1. 常量成员变量。(常量创建时必须初始化,因为对于一个常量,我们给它赋值,是不

                              对的)2. 引用类型成员变量。(引用创建时必须初始化)

3. 没有缺省构造函数的类成员变量。(如果构造函数的参数列表中有一个类的对象,并

且该对象的类里没有缺省参数的构造函数时,要是不使用初始化列表,参数中会调用无

参或者全缺省的构造函数,而那个类中又没有。)

注意:在上边的main函数中要是有这样一句:Date d2();这不是定义一个对象,而是声

明了一个函数名为d2,无参,返回值为Date的函数。

(二)析构函数

析构函数是一种特殊的成员函数,具有以下特点:

1. 析构函数函数名是在类名加上字符~。2. 无参数无返回值(但有this指针)。3. 一个类有且只有一个析构函数,所以肯定不能重载。若未显示定义,系统会自动生成

    缺省的析构函数。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

5. 注意析构函数体内并不是删除对象,而是做一些清理工作。(比如我们在构造函数

   中动态开辟过一段空间,函数结束后需要释放,而系统自动生成的析构函数才不管内

  存释放呢,所以需要人为地写出析构函数)

注意:对象生命周期结束后,后构造的对象先释放。

(三)拷贝构造函数:用已有的对象创建一个新的对象。仍然使用上边的日期类举例:

[cpp]  view plain  copy
 
  1. int main()  
  2. {  
  3.        Date d1(2016,7,6);  
  4.        Date d2(d1);  
  5.         system("pause");  
  6.         return 0;  
  7. }  

上边是用d1创建一个d2,系统会给出默认的拷贝构造函数,并且该函数的参数是一个常

引用,我们想象为什么必须是引用呢,如果不是又会发生什么。

如果不是引用,形参是实参的一份临时拷贝,由于两者都是对象,此时就会调用自己的

拷贝构造函数,陷入无限递归中.......

上边的代码,我们用默认的拷贝构造函数可以得到正确的结果,有时就不会。实例:

[cpp]  view plain  copy
 
  1. class Person  
  2. {  
  3. public:  
  4.     Person(char *name,int age)  
  5.     {  
  6.         m_name = (char *)malloc(sizeof(char)*10);  
  7.         if (NULL == m_name)  
  8.         {  
  9.             cout << "out of memory" << endl;  
  10.             exit(1);  
  11.         }  
  12.         strcpy(m_name,name);  
  13.         m_age = age;  
  14.     }  
  15.     ~Person()  
  16.     {  
  17.         free(m_name);  
  18.         m_name = 0;  
  19.     }  
  20. private:  
  21.     char *m_name;  
  22.     int m_age;  
  23. };  
  24. int main()  
  25. {  
  26.     Person p1("yang",20);  
  27.     Person p2= p1;  
  28.     system("pause");  
  29.     return 0;  
  30. }  

上边的代码会出错,原因见图片。

在析构时,同一块空间释放两次就会有问题。

这种仅仅只是值的拷贝的拷贝方式就是浅拷贝。

深拷贝就是为对象重新分配空间之后,然后将值拷贝的拷贝方式。

下边自己给出拷贝构造函数。

[cpp]  view plain  copy
 
  1. Person(const Person &p)  
  2. {  
  3.     m_name = new char[strlen(p.m_name)+1];  
  4.     if (m_name != 0)  
  5.     {  
  6.         strcpy(m_name,p.m_name);  
  7.         m_age = p.m_age;  
  8.     }  
  9. }  

下边用图给出实现机理。

调用拷贝构造函数的3种情况:

1.当用类的一个对象去初始化该类的另一个对象时。

2.当函数的形参是类的对象,调用函数时进行形参和实参的结合时。

3.当函数的返回值是对象,函数执行完返回调用者时。(函数运行结束后,返回的对象

会复制到一个无名对象中,然后返回的对象会消失,当调用语句执行完之后,无名对

象就消失了)

调用拷贝构造函数的两种方法:

1.代入法:

Person p2(p1);

2.赋值法:

Person p2 = p1;

(四)赋值运算符重载函数

它是两个已有对象一个给另一个赋值的过程。它不同于拷贝构造函数,拷贝构造函数是

用已有对象给新生成的对象赋初值的过程。

默认的赋值运算符重载函数实现的数据成员的逐一赋值的方法是一种浅层拷贝。

浅层拷贝会导致的指针悬挂的问题:

[cpp]  view plain  copy
 
  1. class Person  
  2. {  
  3. public:  
  4.     Person(char *name,int age)  
  5.     {  
  6.         m_name = (char *)malloc(sizeof(char)*10);  
  7.         if (NULL == m_name)  
  8.         {  
  9.             cout << "out of memory" << endl;  
  10.             exit(1);  
  11.         }  
  12.         strcpy(m_name,name);  
  13.         m_age = age;  
  14.     }  
  15.     ~Person()  
  16.     {  
  17.         free(m_name);  
  18.         m_name = 0;  
  19.     }  
  20. private:  
  21.     char *m_name;  
  22.     int m_age;  
  23. };  
  24. int main()  
  25. {  
  26.     Person p1("yang",20);  
  27.     Person p2("yao",20);  
  28.     p2 = p1;  
  29.     system("pause");  
  30.     return 0;  
  31. }  

看图:

使用深层拷贝来解决指针悬挂的问题:

[cpp]  view plain  copy
 
  1. "code" class="cpp">Person & operator=(const Person &p)  
  2.     {  
  3.         if (this == &p)  
  4.             return *this;  
  5.         delete[]m_name;  
  6.         m_name = new char[strlen(p.m_name) + 1];  
  7.         strcpy(m_name,p.m_name);  
  8.                 m_age= p.m_age;  
  9.         return *this;  
  10.     }  

 
     

这样先将p2对象里指针指向的旧区域,然后再分配新的空间,再拷贝内容。当然,对于

那些成员变量里没有指针变量就不会涉及到指针悬挂问题。

(五)取地址操作符重载

[cpp]  view plain  copy
 
  1. Person * operator&()  
  2. {  
  3.         return this;  
  4. }  


(六)const修饰的取地址操作符的重载

[cpp]  view plain  copy
 
  1. const Person * operator&()  const  
  2. {  
  3.         return this;  
  4. }  


函数后边的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.尽量用类的非成员函数以及友元函数替换类的成员函数
例如一个类来模拟人People
复制代码
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         }
复制代码
那么是选择类的成员函数还是类的非成员函数呢?
        面向对象则要求是,将操作数据的函数与数据放在一起。但这不意味着要选择成员函数。从封装的角度看,成员函数的moringAction封装性比非成员函数要低。如果某些东西被封装,它就不再可见。越多东西被封装,越少人可以看到它。所以使用非成员函数的类,封装性较低。而越少人看到它,我们就有越大弹性去变化它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,越多东西被封装,改变哪些东西能力越大。
        在考虑对象内的数据。越少的代码可以看到数据(访问它),越多的数据可以被封装,而我们也就越能自由改变对象数据。现在如果一个成员函数、非成员函数都能提供相同的机能,我们选择非成员函数。
 
在说下面内容之前我们先谈谈C++中的类型转换分显示类型转换和隐式类型转换。
 
2.显示类型转换
 
C++有显示类型转换操作符分别为:static_cast,const_cast,dynamic_cast和reinterpret_cast
 
2.1static_cast
转换功能与C风格类型转换一样,含义也一样。通过使用static_cast可以使没有继承关系的类型进行转换。但是要注意不能将内置类型转化为自定义类型,或者将自定义类型转化为内置类型,并且不能将cosnt类型去掉,但能将non-cosnt类型转换为const类型。例如:
        char a='a';
        int b=static_cast(a);
两个类型之间的转换,其实也是要自己实现支持,原理和内置类型之间转换相似。
复制代码
 1 #include 
 2 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
2.2const_cast
    主要用来去掉const和volatileness属性,大多数情况下是用来去掉const限制。
 
2.3dynamic_cast
    它用于安全地沿着继承关系向下进行类型转换。一般使用dynamic_cast把指向基类指针或者引用转换成其派生类的指针或者引用,并且当转换失败时候,会返回空指针。
 
2.4reinterprit_cast
    该转换最普通用途就是在函数指针类型之间进行转换。
1         typedef void (*fun) ( )   //一个指向空函数的指针
2         fun funArray[10];         //含有10个函数指针的数据。
3         int function( );              //一个返回值为int类型函数
4         funArray[0] = &function( )  //错误!类型不匹配
5         funArray[0] = reinterpret_cast(&function); //ok
注意转换函数指针的代码是不可移植的。一般应该避免去转换函数指针类型。
 
3.使用非成员函数可以发生隐式转换
C++是支持隐式类型转换的,例如在做运算的时候,或者传递参数给函数的时候常常会发生隐式类型转换。
假设你设计一个class用来表现有理数。其实令类支持隐式类型转换是一个槽糕的决定。当然在建立数值类型时就是例外。下面定义一个有理数类型:
复制代码
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
        这里发生了什么?其实在第一行式子里发生了所谓隐式类型转换。哪里发生了隐式类型转换呢?看上面的代码operator*函数参数是const Rational类型,而2是一个cosnt int类型,。编译器发现有non-explicit型的单参数类为int类型的构造函数,可以造出Rational类型。所以编译器那样做了,发生了隐式类型转换。所以第一个式子可以正常运行,但是第二个是没有将Rational类型转换为int类型的。
        设计出上面两个式子正常运行才算合理的运行。
复制代码
 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         }
复制代码
       按上面代码设计成非成员函数,那么在调用int类型整数的时候会发生隐式类型转换。
        operaotr* 是否应该称为Rational class的一个友元函数呢?对本例子而言,完全没有必要。
如果你需要为某个函数所有参数进行类型转换,那么这个函数必须是个非成员函数。
 
4.谨慎使用隐式类型转换
       我们对一些类型隐式转换无能为力,因为它们是语言本身的特性。不过当编写自定义类时,我们可以选择是否提供函数让编译器进行隐式类型转换。
        有两种函数允许编译器进行隐式类型转换:单参数构造函数与隐式类型转换运算符。
复制代码
 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         }
复制代码
        C++是支持隐式类型转换的,例如在做运算的时候,或者传递参数给函数的时候常常会发生隐式类型转换。有两种函数允许编译器进行隐式转换。单参数构造函数和隐式类型转换运算符。
        也许前面说道了一些隐式类型转换带来的方便之处,下面说说一些麻烦之处。
复制代码
 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                 }
复制代码
    如果这里不小心将数组a的下标忘记写了,这里编译器应该报出警告信息,但是其实是没有的。因为编译器将a看成Array类型,b为 int,根据上面的经验,编译器看到一个non-explicit单参数构造函数其参数类型为int,而且operator需要一个Array类型,那么编译器就这样做了,发生了隐式类型转换。相信如果真的发生这样,会很麻烦。
    怎么样避免呢?将构造函数声明为explicit。避免隐式类型转换。

你可能感兴趣的:(友元函数this)