北大郭炜《程序设计与算法(三)》Mooc笔记:类和对象

从C到C++

c++对c扩展主要是为了实现面向对象,但有一些简单的扩展与面向对象关系不大,在这一节中介绍

引用

int n=4;
int &r=n;//r引用了n,r的类型是int
  • r引用了n,r跟n就是一回事了:改变n,r也会改变,反之亦然

  • 定义引用时,一定要将其初始化

  • 为什么我们要使用”引用“??

    1. 可以更方便的交换两个变量值

    C中的swap函数:

    void swap(int *a,int *b)//参数是两个指针
    {
        int tmp;
        tmp=*a;*a=*b;*b=temp;
    }
    int n1,n2;
    swap(&n1,&n2)//传入的参数是两个变量的地址
    

    C++中的swap函数:

    void swap(int &a,int &b)
    //参数是两个变量的引用(直接在函数声明中创建)
    {
        int tmp;
        tmp=a;a=b;b=tmp;
    }
    int n1,n2;
    swap(n1,n2);
    
    1. 引用作为函数的返回值
    int n=4;
    int &SetValue(){return n;}
    int main()
    {
        SetValue()=40;
        cout<<n;
        return 0;
    }//输出:40
    
  • 常引用

    不能通过常引用去修改他引用的内容,但他引用的内容可以改变

    int n=100;
    const int &r=n;
    r=200;//编译错误
    n=300;//没有问题
    

const关键字

  1. 常量
    const int r=2

  2. 常量指针

    int n,m;
    const int *p=&n;//p是指向n的常量指针
    *p=5;//不能通过常量指针去修改其指向的内容-->编译出错
    n=4;
    p=&m;//常量指针的指向可以变化-->编译通过
    
    const int*p1;
    int *p2;
    p2=p1;//不能把常量指针赋值给非常量指针-->编译错误
    p1=p2;//反过来可以-->编译通过
    p2=(int*)p1;//强制类型转换-->编译通过
    

3.常引用

动态内存分配

c中的malloc可以实现;

c++中的new可以实现:

  1. 分配一个变量

    P=new int

    int:类型名

    P:类型为int*的指针

    new将一片大小为sizeof(int)字节的内存空间的地址赋值给P。

  2. 分配一个数组

    P=new int[N]

    N:要分配的数组元素的个数

注意:new T/new T[N]的返回值都是T*,所以 int *p=new p是成立的

c++用delete释放被new动态分配的内存空间:

  1. 释放一个变量

    delete必须指向new出来的空间;

    一个内存空间不能被delete两次

  2. 释放一个数组

    int* p=new int[20];
    p[0]=1;
    delete[]p;//不要忘记这个中括号
    

练习:神秘的数组初始化

#include <iostream>
using namespace std;

int main()
{
	int * a[] = {NULL,NULL,new int,new int[6]};
	//上面大括号中是初始化的内容
	*a[2] = 123;
	a[3][5] = 456;
	if(! a[0] ) {
		cout << * a[2] << "," << a[3][5];
	}
	return 0;
}

内联函数和重载函数

内联函数

  • 函数调用是有时间开销的。如果函数本身只有几条语 句,执行非常快,而且函数被反复执行很多次,相比 之下调用函数所产生的这个开销就会显得比较大。

  • 为了减少函数调用的开销,引入了内联函数机制。编 译器处理对内联函数的调用语句时,是将整个函数的
    代码插入到调用语句处,而不会产生调用函数的语句。

  • 在函数定义前加关键字inline,即可定义内联函数

重载函数

一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。
以下三个函数是重载关系:

(1)int Max(double f1,double f2){}
(2)int Max(int n1,int n2) {} 
(3)int Max(int n1,int n2,int n3) {}
  • 函数重载使得函数命名变得简单
  • 编译器根据调用语句的中的实参的个数和类型判断应
    该调用哪个函数。
Max(3.4,2.5); //调用 (1) 
Max(2,4); //调用 (2) 
Max(1,2,3); //调用 (3)
Max(3,2.4);//error,二义性

函数的缺省参数

  • C++中,定义函数的时候可以让最右边的连续若干个参 数有缺省值,那么调用函数的时候,若相应位置不写参 数,参数就是缺省值。

    void func( int x1, int x2 = 2, int x3 = 3) { }
    func(10 ) ; //等效于 func(10,2,3) 
    func(10,8) ; //等效于 func(10,8,3)
    func(10, , 8) ; //不行,只能最右边的连续若干个参数缺省
    
  • 函数参数可缺省的目的在于提高程序的可扩充性

  • 即如果某个写好的函数要添加新的参数,而原先那些 调用该函数的语句,未必需要使用新增的参数,那么 为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。

类和对象的基本概念(1)

结构化程序设计

  • c使用:程序=数据结构+算法

  • 不足:结构混乱,没有封装和隐藏的概念,难以debug,不能重用类似的代码。

面向对象的程序设计

  • c++使用:程序=类+类+类+...+类

  • 特点:抽象,封装,继承,多态

    设计方法:

    归纳:将某类客观事物属性归纳出来(用变量描述属性)

    抽象:将这类事物能进行的操作归纳出来(用函数描述操作)

    封装:将属性和函数捆绑在一起,形成一个

  • 通过定义来的变量就叫做对象,C中的struct也是类,他定义出的变量也叫做对象。

  • C++中,可以像使用基本类型intchar一样使用类

类和对象基础

类和对象的基本概念(2)

  1. 类成员的可访问范围

    private:只能在成员函数内访问(缺省定义)

    public:可以在任何地方访问

    protected:以后再说

  2. “隐藏”机制的好处:

    强制对成员变量的访问一定要通过成员函数进行

    class CEmployee{
        private:
               char szName[30];//名字,sz是char字符串前缀
        public:
               int salary;
               void setName(char *name)
               void getName(char *name)
               void averageSalary(CEmployee e1,CEmployee e2)
    };
    
    void CEmployee::setName(char * name){strcpy(szName,name);} void CEmployee::getName(char * name){strcpy(name,szName);} /*在类的成员函数内部,能够访问当前对象的全部变量/函数*/ 
    void CEmployee::averageSalary(CEmployee e1,CEmployee e2){
        cout<<e1.szName;
        salary=(e1.salary+e2.salary)/2;
        //在类的成员函数内部,能够访问同类其他对象的全部变量/函数
    }
    
    int main(){
        CEemployee e;
        strcpy(e.szName,"Tom123456789");❌❌❌
        //在类的成员函数外部,只能访问该类对象的公有变量/函数,szName是私有变量
        e.setName("Tom");🆗
        e.salary=5000;🆗
        //setName是公有函数,salary是公有变量
        return 0;
    }
    

    若将上述程序移植到内存紧张的手持设备上,希望将char szName[30]改为char szName[5]

    由于不存在strcpy(e.szName,"Tom123456789")这种语句,所以只需要在setName中加一个判断语句,当输入多于5个符号时报错即可。

  3. 成员函数的重载,参数缺省

    • 成员函数可以重载,也可以缺省

    • 使用缺省参数时要注意避免函数重载导致的歧义

      class location{
          private:int x,y;
          public:void init(int x=0,int y=0)
                 void valueX(int val=0){x=val;}
                 void valueX(){return x;}
      };
      
      location A;
      A.valueX();❌❌❌
      //编译器无法判断调用哪个valueX
      

构造函数

  1. 什么是构造函数?

    构造函数名字与类的名字相同,可以有参数,不可以有返回值

    class Complex{
        private:double real,imag;
        public:Complex(double r,double i=0);
        //构造函数名字与类名相同,无返回值——连void都没有!!!
    };
    
    Complex::Complex(double r,double i=0){real=r;imag=i;}
    
    Complex c1;❌
    Complex* ptr_c2=new Complex;❌
    Complex c3(2);🆗//i有缺省值0
    Complex* ptr_c4=new Complex(3,4);🆗//c++中变量名可以含有下划线_
    
  2. 为什么需要构造函数?

    对象没被初始化就使用会导致程序出错!

    在用类定义对象时,构造函数可以自动进行初始化操作(就像ios捷径里的自动化!),不必担心忘记初始化。

  3. 如果定义类时没有写构造函数,编译器会自动生成无参数构造函数

    class Complex{
        private:double real,imag;
        public:void Set(double r,double i)
        //编译器自动生成默认构造函数
    };
    Complex c1;🆗
    Complex* pc2=new Complex;🆗//c++中指针前缀:p
    
  4. 可以有多个构造函数,需要参数个数/类型不同。(这些构造函数间是重载的关系)

    class Complex{
        private:double real,imag;
        public:Complex(double r,double i)
               Complex(double r);
               Complex(Complex c1,Complex c2);
               void Set(double r,double i);
    };
    
    Complex::Complex(double r,double i){real=r;imag=i;}
    Complex::Complex(double r){real=r;imag=0;}
    Complex::Complex(Complex c1,Complex c2)
    {
        real=c1.real+c2.real;
        imag=c1.imag+c2.imag;
    }
    
    
    Complex c1(3,1),c2(4),c3(c1,c2);
    //c1={3,1},c2={4,0}c3={7,1}
    
  5. 构造函数在数组中的使用

    class Test{
        public:Test(int n){}//(1)
               Test(int n,int m){}//(2)
               Test(){}//(3)
    };
    
    Test array1[3]={1,Test(1,2)};
    //三个元素分别用(1)(2)(3)初始化
    Test* pArray2[3]={new Test(4),new Test(1,2)};
    //👉两个元素👈分别用(1)(2)初始化
    

复制构造函数

  1. 什么是复制构造函数?

    一种特殊的构造函数,其形式参数必须为引用类型

  2. 如果定义类时没有写,编译器会自动生成无参数复制构造函数

  3. 复制构造函数被调用时,可以不进行复制工作,见4.2的例子

  4. 什么时候用到复制构造函数?

    • 用一个对象去初始化同类的另一个对象时

      Complex c1;
      Complex c2(c1);
      
    • 如果函数有一参数是类A的对象,当该函数被调用时,类A的复制构造函数被调用

      class A{
          public:A(){};//构造函数
                 A(A& a){cout<<"Copy constructor called"<<endl;}//复制构造函数
      };
      
      void Func(A a1){}
      
      int main()
      {
          A a2;
          Func(a2);//此时复制构造函数被调用,但并没有把a1变成a2复制品!
          return 0;
      }
      //输出:Copy constructor called
      
    • 如果函数的返回值是类A的对象,当该函数返回值时,leiA的复制构造函数被调用

      class A{
          public:int v;
                 A(int n){v=n;}
                 A(const A& a){
                     v=a.v;//和之前的代码不同,此处进行了复制工作
                     cout<<"Copy constructor called"<<endl;}
      };
      
      A Func(){
          A b(4);//此处使用类A的构造函数,参数是4
          return b;
      }
      
      int main()
      {
          cout<<Func().v<<endl;
          //此处复制构造函数被调用,且将Func().v变成了b的复制品
          return 0;
      }
      //输出:Copy constructor called 4
      
  5. 对象间赋值并不导致复制构造函数被调用

    假设A 是一个类的名字,下面哪段程序不会调用A的复制构造函数?

    • A. A a1,a2; a1 = a2;
    • B.void func( A a) { cout << “good” << endl; }
    • C. A func( ) { A tmp; return tmp; }
    • D.A a1; A a2(a1);

    正确答案:A你选对了

  6. 为什么我们要在复制构造函数中使用常量引用参数A(const A& a)

    void test(A a){cout<<"Test"<<endl;}
    

    这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大

    👉所以可以考虑使用CMyclass &引用类型作为参数。
    👉为了确保实参的值在函数中不被改变,那么可以加上const关键字:

    class A{
        public:A(){}
               A(const A& a){}//定义时就使用const
    };
    
    void test(const A& a){cout<<"Test"<<endl;}
    //函数中任何试图改变a值的语句都将是变成非法
    
  7. 为什么我们要自己写复制构造函数?????

    👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

类型转换构造函数

  1. 什么是类型转换构造函数?

    只有一个参数,且这参数不是引用类型的一般就是转换构造函数

    此类函数目的是实现类型的自动转换

实例:

#include<iostream>
using namespace std; 

class Complex{
    public:
    double real,imag;
    Complex(int i);//类型转换构造函数
    Complex(double r,double i){real=r;imag=i;}
}; 

Complex::Complex(int i)
{
    cout<<"IntConstructor called"<<endl;
    real=i;imag=0;//此处实现了“将int类型转化为Complex类型”
}

int main()
{
    Complex c1(7,8);
    Complex c2=12;//直接调用类型转换构造函数
    c1=9;//9被自动转换为一个👉临时👈Complex对象
    cout<<c1.real<<","<<c1.imag<<"\n"<<c2.real<<","<<c2.imag<<endl;
    return 0;
}
IntConstructor called
IntConstructor called
9,0
12,0

析构函数

  1. 析构函数是什么?

    class Ctest{
        public:
        ~Ctest(){cout<<"destructor called"<<endl;}
        //析构函数与类名相同,前有一"~"号
        //析构函数没有参数,没有返回值
        //一个类只能有一个析构函数
    };
    
    int main(){
        Ctest array[2];//生成一个含有两个Ctest类的对象的数组
        cout<<"End Main"<<endl;
        return 0;
        //此时main函数结束,array消亡,其中每个元素的析构函数都被调用
    }
    
    End Main 
    destructor called
    destructor called
    
  2. 析构函数与delete

    对象:

    Ctest *pTest;
    pTest=new Ctest;//构造函数调用
    delete Ctest;//此时析构函数被调用!!
    

    对象数组:

    Ctest *pTest;
    pTest= new Ctest[3];
    delete [] Ctest;//👉必须写[]!👈否则只delete一个对象(调用一次析构函数)!!!!
    
  3. 析构函数在对象作为函数返回值返回后被调用

    class Ctest{
        public:
        ~Ctest(){cout<<"destructor called"<<endl;}
    };
    
    Ctest Func(Ctest t){return t;}//作为参数的对象t消亡时,调用析构
    
    int main(){
        Ctest t1;
        t1=Func(t1);//函数返回的临时对象Func(t1)被用过后,调用析构
        return 0;//main函数结束t1消亡,调用析构
    }
    
    destructor called
    destructor called
    destructor called
    

构造/析构函数什么时候被调用?

class Demo{
    int id;
    public:
         Demo(int i);//类型转换构造函数
        ~Demo();//析构函数
};

Demo::Demo(int i){
    id=i;
    cout<<"id="<<id<<"constructed"<<endl;}
Demo::~Demo(){
    cout<<"id="<<id<<"destructed"<<endl;}
Demo d1(1); //d1定义在这里,说明这是一个全局对象

void Func() {
    static Demo d2(2); 
    Demo d3(3); 
    cout << "func" << endl;
} 

int main () {
    Demo d4(4); 
    d4 = 6; //这个语句成立是因为有类型转换构造函数
    cout << "main" << endl;
    {Demo d5(5);}//d5定义在花括号里,说明这是一个局部对象
    Func(); 
    cout << "main ends" << endl; 
    return 0;
}

输出:

id=1constructed
id=4constructed
id=6constructed
id=6destructed
main
id=5constructed
id=5destructed
id=2constructed
id=3constructed
func
id=3destructed
main ends
id=6destructed
id=2destructed
id=1destructed

类和对象提高

This指针

  1. 为什么引入This指针?

    • 刚开始没有c++编译器,需要把c++翻译成c用c编译器编译。

    • 由于c++的成员函数在c中并没有对应概念,因此发明了This指针,该指针可以指向成员函数作用的对象,能将一个全局函数转变为成员函数

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLffftFV-1601986120746)(C:\Users\Abigail\AppData\Roaming\Typora\typora-user-images\image-20201006131212410.png)]

  2. This指针在c++中的作用

    非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针

    class A {
                int i; 
        public: void Hello(){ cout << "hello" << endl; }
    }; //用this翻译:void Hello(A*this){ cout << "hello" << endl; }
    
    int main() {
        A * p = NULL;
        p->Hello(); //用this翻译:Hello(p);
    }//结果会怎样?输出:hello
    
    class A {
                int i; 
        public: void Hello(){ cout << i << "hello" << endl; }
    }; //用this翻译:void Hello(A*this){ cout << this->i << "hello" << endl;}
       
    //this若为NULL,则出错!!
    int main() {
         A * p = NULL; 
         p->Hello();//用this翻译:Hello(p);
    } // 结果会怎样?编译出错!!!!!
    

静态成员变量和静态成员函数

  1. 定义和性质

    • 普通成员变量每个对象各自有一份,静态成员变量一共就一份,所有对象共享

    • sizeof运算符不会计算静态成员变量

      class CMyclass { 
          int n; 
          static int s;
      };
      //sizeof(CMyclass)=4
      
    • 普通成员函数必须具体作用于某个对象,静态成员函数不具体作用于某个对象

    • 静态成员不需要通过对象就能访问

    • 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类 的静态成员变量也存在。

    • 静态成员函数本质上是全局函数。

    • 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

  2. 如何访问静态成员

    • 类名::成员名CRectangle::PrintTotal()

    • 对象名.成员名CRectangle r;r.PrintTotal()

    • 指针->成员名CRectangle* p;p->PrintTotal()

    • 引用.成员名CRectangle& ref;int n=ref.nTotalNumber;

      虽然后三个的形式与普通成员变量相同,但并不意味着该成员是属于那个对象的,要判断静态还是普通需要看成员的定义

  3. 静态成员实例

    考虑一个需要随时知道矩形总数和总面积的图形处理程序
    可以用全局变量来记录总数和总面积
    用静态成员将这两个变量封装进类中,就更容易理解和维护

    class CRectangle
    {
        private:
               int w,h;
               static int nTotalArea;
               static int nTotalNumber;
        public:
              CRectangle(int w_,int h_);
             ~CRectangle();
              static void PrintTotal();
              CRectangle(CRectangle & r )//复制构造函数
    };
    
    CRectangle::CRectangle(int w_,int h_) {
        w = w_; 
        h = h_; 
        nTotalNumber ++; 
        nTotalArea += w * h;
    }
    CRectangle::~CRectangle() {
        nTotalNumber --; 
        nTotalArea -= w * h;
    }
    void CRectangle::PrintTotal() {
        cout << nTotalNumber << "," <<
    }
    CRectangle::CRectangle(CRectangle & r ) {
        w = r.w;
        h = r.h;
        nTotalNumber ++;
        nToTalArea+=w*h;
    }/*💢💢💢不要忘了定义复制构造函数!!!!!
    
    👉在使用CRectangle类时,有时会调用复制构造函数 生成临时的隐藏的CRectangle对象:
    
        调用一个以CRectangle类对象作为参数的函数时, 
        调用一个以CRectangle类对象作为返回值的函数时
        
    👉临时对象在消亡时会调用析构函数,减少nTotalNumber 和 nTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNumber 和 nTotalArea的值。
    
    int CRectangle::nTotalNumber = 0; 
    int CRectangle::nTotalArea = 0; 
    // 必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。 
    int main() {
        CRectangle r1(3,3), r2(2,2); 
        /*cout << CRectangle::nTotalNumber;  👉Wrong , 私有*/
        CRectangle::PrintTotal(); 
        r1.PrintTotal(); //虽然这样写,但不代表PrintTotal是普通成员函数
        return 0;
    }
    

    Output:

    2,13
    2,13
    
  4. 静态成员函数不能访问非静态成员变量,不能调用非静态成员函数

    void CRectangle::PrintTotal() {
         cout << w << "," << nTotalNumber << "," << nTotalArea << endl; //wrong
    }
    CRetangle::PrintTotal(); 
    //解释不通,w 到底是属于那个对象的?
    //同理,非静态成员函数会访问非静态成员变量,调用了非静态函数就等于访问了非静态变量
    

成员对象和封闭类

  • 成员对象的类称之为封闭类(enclosing)

  • 任何生成封闭类对象的语句,都要让编译器知道封闭类中的成员对象如何初始化,所以我们一定要添加封闭类构造函数的初始化列表

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nYWaVCU3-1601986120758)(C:\Users\Abigail\AppData\Roaming\Typora\typora-user-images\image-20201006171353355.png)]

    例子

    class Ctyre{
          private:
                  int radius;
                  int width;
          public:CTyre(int r,int w):radius(r),width(w){}
          //上方的构造函数添加了初始化列表,将 radius 初始化成 r,width 初始化成 w。这种写法比在函数体内用 r 和 w 对 radius 和 width 进行赋值的风格更好。建议对成员变量的初始化都使用这种写法
    };
    
    
    class Cengine{};
    
    class Ccar{
          private:
                  int price;//成员变量
                  Cengine engine;//成员对象
                  Ctyre tyre;//成员对象
          public:
                  CCar(int p,int tr,int tw);//Ccar的初始化函数
    };
    CCar::CCar(int p,int tr,int tw):price(p),tyre(tr,w){};
    //:和{之间的内容即为Ccar的构造函数的初始化列表,如果没有这列表,“CCar car”这句话就会编译出错,因为tyre的构造函数需要参数,但那句话并没有给出参数,因此无法构造tpre,因此无法构造car
    
    int main(){
        Ccar car(20000,17,225);
        return 0;
    }
    

    详细解释看这篇文章:http://c.biancheng.net/view/167.html

  • 封闭类对象生成时,先执行成员对象的构造函数,在执行封闭类的成员函数

    👇👇👇

    对象成员构造函数的顺序与其在封闭类中的说明顺序一致

    👇👇👇

    封闭类对象消亡时,先执行封闭类的析构函数,再执行成员对象的析构函数

    👉👉👉**就像汽车一样,造汽车时先造轮胎和引擎,再组装汽车;拆汽车时先把汽车拆成零件,再去把零件拆散**

  • 封闭类对象如果用默认复制构造函数初始化,那么其成员对象也会用复制构造函数初始化

例子

class A{
public:A(){cout << "default" << endl;}
       A(A& r){cout << "copy" << endl;}
};

class B{A a;};

int main(){
 B b1;
 B b2(b1);
 return 0;
}

Output:

default
copy//说明b2.a是用类A的复制构造函数初始化的,而且调用复制构造函数时的实参就是b1.a

常量成员函数和常量对象

  • 常量对象

    如果不希望某个对象的值被改变,则定义该对象时可以在前面加const关键字

    class Demo{...}
    const Demo Obj;//常量对象
    
  • 常量成员函数

    如果不希望成员函数执行期间修改其作用的对象,则定义该函数时可以在后面加const关键字

    ❗❗❗常量成员函数不能使用普通函数/变量,可以使用静态函数/成员,因为静态函数/成员不具体作用于任何对象

    class Sample
    {
        public:
            int value;
            void GetValue() const;//常量成员函数
            void func(){};
            Sample(){};
    };
    
    void Sample::GetValue()const
    {
        value=0;//❌❌❌不能访问普通成员变量
        func();//❌❌❌不能调用普通成员函数
    }
    
    int main()
    {
        const Sample o;
        o.value=100;//❌常量对象不能被修改
        o.func();//❌常量对象上不能执行非常量成员函数,因为电脑不知道那函数会不会改变这个对象
        o.GetValue();//✅常量对象上可以执行常量成员函数
    }
    

注意:两个成员函数,名字和参数表一样,但一个是const一个不是,算重载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDucKuUl-1601986120760)(C:\Users\Abigail\AppData\Roaming\Typora\typora-user-images\image-20201006165447847.png)]

  • 常引用

    用对象的引用作为参数,不必调用复制构造函数,又比指针做参数好看

    class Sample{...};
    void PrintfObj(const Sample& o){...}
    //这样函数中能确保不会无意间更改o的值
    

    友元(friends)

  • 友元这个机制算是对c语言程序员的一种妥协,为关系相近的类提供了一种调用函数的方便。友元函数不属于该类的成员函数,他是定义在类外的普通函数,只是在类中声明该函数可以直接访问类中的private或者protected成员

  • 友元函数:一个类的友元函数可以访问该类的私有成员

    class CCar//提前声明CCar类,以便后面的CDriver使用
    
    class CDriver
    {
        public:void ModifyCar(CCar* pCar);
        //'改装汽车'函数的参数是一个指向CCar类对象的指针,这里就用到了CCar类
    };
        
    class CCar
    {
        private:
                int price;
        friend void CDriver::ModifyCar(CCar* pCar);
        //可以将一个类A的成员函数(包括构造/析构函数)说明为另一个类B的友元:friend_返回值_A::func()
        friend int MostExpensiveCar(CCar cars[],int total);
        //上面两函数是CCar类的友元函数,可以访问CCar的私有变量price
    };
    //定义第一个友元函数
    void CDriver::ModifyCar(CCar* pCar)
    {
        pCar->price+=1000;//汽车改装后价值增加
    }
    //定义第二个友元函数
    int MostExpensiveCar(CCar cars[],int total)
    {
        int tmpMax=-1;
        for(int i=0;i<total;++i)
            if(cars[i].price>tmpMax)
                tmpMax=cars[i].price;
        return tmpMax;
    }
    
    int main(){return 0;}
    
  • 友元类:一个类的友元类可以访问该类的私有成员

class CCar
{
    private:int price;
    friend class CDriver;//声明CDriver为友元类
};

class CDriver
{
    public:
           CCar myCar;
           void ModifyCar(){myCar.price+=1000;}
           //因CDriver是CCar的友元类,故可以访问CCar的私有成员       
};

int main(){return 0;}
  • 友元类之间的关系不能传递,不能继承
  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值