• C++ 第4章 类与对象


    4.1 面向对象程序设计的基本特点

    4.4.1 抽象

    抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。
    首先注意的是问题的本质及描述,其次是解决问题的具体过程
    对一个问题的抽象应该包括两个方面:数据抽象行为抽象(或称为功能抽象、代码抽象)

    • 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。
    • 代码抽象:描述某类对象的共有的行为特征或具有的功能。

    抽象的实现:通过类的声明
    抽象实例——钟表

    数据抽象:
    int Hour,  int Minute,  int Second
    代码抽象:
    SetTime(),  ShowTime()
    class  Clock
    {
            public: 
                 void SetTime(int NewH,int NewM,int NewS);
                 void ShowTime();
            private: 
                  int Hour,Minute,Second;
    };

    抽象实例——人

    数据抽象:
    char *name,char *gender,int age,int id
    代码抽象:
    生物属性角度:
    GetCloth(),  Eat(),  Step(),…
    社会属性角度:
    Work(), Promote() ,…

    4.4.2 封装

    将抽象出的数据成员、代码成员相结合,将它们视为一个整体。

    • 目的是曾强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
    • 实现封装:类声明中的{}

    4.4.3 继承
    是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。
    实现:声明派生类——第七章
    4.4.4 多态
    从广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。

    • 多态:同一名称,不同的功能实现方式。
    • 目的:达到行为标识统一,减少程序中标识符的个数。
    • 实现:重载函数和虚函数——第八章

    4.2 类和对象

    • 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
    • 利用类可以实现数据的封装、隐藏、继承与派生。
    • 利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高。

    4.2.1 类的定义

      类是一种用户自定义类型,声明形式:

    class 类名称
    {
           public:
                     公有成员(外部接口)
           private:
                     私有成员
           protected:
                     保护型成员
    }

    4.2.2 类成员的访问控制
    公有类型成员:在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
    私有类型成员:在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。
    保护类型:与private类似,其差别表现在继承与派生时对派生类的影响不同,见第七章。

    4.2.3 对象

    类的对象是该类的某一特定实体,即类类型的变量。

    声明形式:
    类名     对象名;
    例:
    Clock  myClock;

    类中成员的访问方式

    • 类中成员互访-----直接使用成员名
    • 类外访问--------使用“对象名.成员名”方式访问 public 属性的成员

    4.2.4 类的成员函数

    成员函数:

    • 在类中说明原形,可以在类外给出函数体实现,并在函数名前使用类名加以限定。也可以直接在类中给出函数体,形成内联成员函数。
    • 允许声明重载函数和带默认形参值的函数

    1、成员函数的实现
    成员数据:与一般的变量声明相同,但需要将它放在类的声明体中。
    原型说明了函数的参数表和返回值类型,而函数的具体实现是写在类定义之外的。与普通函数不同的是,实现成员函数时要指明类的名称,具体形式为:

    返回值类型 类名::函数成员名(参数表)
    {
       函数体
    }

    class  Clock
    {
      public:
          void SetTime(int NewH, int NewM,int NewS);
          void ShowTime();
      private: 
          int Hour, Minute, Second;
    };
    
    void Clock::SetTime(int NewH, int NewM,int NewS)
    {
            Hour=NewH;
            Minute=NewM;
            Second=NewS;
    }
    void Clock::ShowTime()  //类的成员函数名需要用类名来限制
    {
            cout<<Hour<<":"<<Minute<<":"<<Second;
    }

    2、成员函数中的目的对象

    调用一个成员函数与调用普通函数的差异在于,需要使用“."操作符指出调用所针对的对象,这一对象在本次调用中称为目的对象。
    例如使用myClock.showTime()调用showTime函数时,myClock就是这一调用过程中的目的对象。
    3、带默认形参值的成员函数

    class Clock {
    public:
    void setTime (int newH=0 , int newM=0 , int newS=0);
    ……
    };

    如果调用这个函数时没有给出实参,就会按照默认形参值将时钟设置到午夜零点。
    4、内联成员函数:

    • 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
    • 内联函数体中不要有复杂结构(如循环语句和switch语句)。
    • 在类中声明内联成员函数的方式:
      • 隐式声明:将函数体放在类的声明中。
      • 显式声明。:使用inline关键字

    例:内联成员函数

    class Point
    {
     public:
        void Init(int initX,int initY)
        {
          X=initX;
          Y=initY;
        }
        int GetX() {return X;}
        int GetY() {return Y;}
     private:
        int X,Y;
    };
    class Point
    {
     public:
        void Init(int initX,int initY);
        int GetX(); 
        int GetY();
     private:
        int X,Y;
    };
    inline void Point::Init(int initX,int initY)
    {
        X=initX;
        Y=initY;
    }
    
    inline int Point::GetX() 
    {
        return X;
    }
    
    inline int Point::GetY() 
    {
        return Y;
    }

    4.2.5 程序实例
    例:时钟类的完整程序

    #include<iostream>
    using namespace std;
    
    class Clock{  //时钟类的定义
    public:   //外部接口,公有成员函数
        void setTime(int newH=0,int newM=0,int newS=0);
        void showTime();
    private:  //私有数据成员
        int hour,minute,second;
    };
    
    //时钟类成员函数的具体实现
    void Clock::setTime(int newH,int newM,int newS){
      hour=newH;
      minute=newM;
      second=newS;
    }
    
    inline void Clock::showTime(){   //显式声明内联成员函数
       cout<< hour<<": "<< minute<<": "<< second<< endl;
    }
    
    //主函数
    int main(){
      Clock myClock;     //定义对象myClock
      cout<<"First time set and output:"<<endl;
      myClock.setTime();  //设置时间为默认值
      myClock.showTime();  //显示时间
      cout<<"Second time set and output:"<<endl;
      myClock.setTime(8,30,30);   //设置时间为8:30:30
      myClock.showTime();   //显示时间
      return 0;
    }

    4.3 构造函数和析构函数

    4.3.1 构造函数

    • 构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。
    • 对象创建时由系统自动调用
    • 如果程序中未声明,则系统自动产生出一个默认形式的构造函数
    • 允许为内联函数、重载函数、带默认形参值的函数

    例:

    class Clock
    {
    public:
        Clock (int NewH, int NewM, int NewS);//构造函数
        void SetTime(int NewH, int NewM, int NewS);
        void ShowTime();
    private:
        int Hour,Minute,Second;
    };
    
    //构造函数的实现:
    class Clock
    {
    public:
        Clock (int NewH, int NewM, int NewS);//构造函数
        void SetTime(int NewH, int NewM, int NewS);
        void ShowTime();
    private:
        int Hour,Minute,Second;
    };
    
    //建立对象时构造函数的作用:
    void main()
    {
        Clock  c(0,0,0); //隐含调用构造函数,将初始值作为实参。
        c.ShowTime();
    }

    作为类的成员函数,构造函数可以直接访问类的所有数据成员,可以是内联函数,可以带有参数表,可以带默认的形参值,也可以重载.

    class Clock
    {
      public:
        Clock (int newH, int newM, int newS);//构造函数
        Clock (){   //无参数构造函数(默认构造函数)
             hour=0;
             minute=0;
             second=0;
        }
        void setTime(int newH, int newM, int newS);
         void showTime();
      private:
        int hour,minute,second;
    };
    
    //其他函数实现略
    
    void main()
    {
        Clock  c1(0,0,0); //调用有参数构造函数
        Clock c2;  //调用无参数构造函数
    }

    4.3.2 复制构造函数(拷贝构造函数)

    拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。
    其作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。
    如果程序员没有定义类的复制构造函数,系统就会在必要时自动生成一个隐含的复制构造函数。这个隐含的复制构造函数的功能是,把初始值对象的每个数据成员的值都复制到新建立的对象中。

    class 类名
    {  public:
           类名(形参);//构造函数
           类名(类名 &对象名);//拷贝构造函数
               ...
    };
    类名::类名(类名 &对象名)//拷贝构造函数的实现
    {    函数体    }
    

    例:

    class Point
    {
       public:
           Point(int xx=0,int yy=0){//构造函数
              X=xx; Y=yy;
           }
           Point(Point &p);   //复制构造函数
           int GetX() {return X;}
           int GetY() {return Y;}
       private:
           int  X,Y;
    };
    //复制构造函数的实现
    Point::Point(Point &p)
    {
          X=p.X;
          Y=p.Y;
          cout<<"拷贝构造函数被调用"<<endl;
    }

    普通构造函数是在对象创建时被调用,而复制构造函数在以下3种情况下都会被调用。
    (1)当用类的一个对象去初始化该类的另一个对象时。例如:

    int main(){
        Point a(1,2);
        Point b(a);  //用对象a初始化对象b,复制构造函数被调用
        Point c=a;   //用对象a初始化对象c,复制构造函数被调用
        cout<<b.getX()<<endl;
        return 0;
    }

    细节:以上对b和c的初始化都能够调用复制构造函数,两种写法只是形式上有所不同,执行的操作完全相同。

    (2)如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。例如:

    void fun1(Point p)
    {   cout<<p.GetX()<<endl;
    } 
    void main()
    {   Point A(1,2);
        fun1(A); ////函数的形参为类的对象,当调用函数时,复制构造函数被调用
    }     

    提示:只有把对象用值传递时,才会调用复制构造函数,如果传递引用,则不会调用复制构造函数。

    由于这一原因,传递比较大的对象时,传递引用会比传值的效率高很多。
    (3)如果函数的返回值是类的对象,函数执行完成返回调用者时。例如:

    Point g()
    {   
        Point a(1,2);
        return a;  //函数的返回值是类对象,返回函数值时,调用复制构造函数
    } 
    void main()
    {   Point b;
        b=g();
    }     

    为什么在这种情况下,返回函数值时,会调用复制构造函数呢?表面上函数g将a返回给了主函数,但是a是g()的局部对象,离开建立它的函数g以后就消亡了,不可能在返回主函数后继续生存,所以在处理这种情况时编译系统会在主函数中创建一个无名临时对象,该临时对象的生存期只在函数调用所处的表达式中,也就是表达式"b=g()"中。执行语句"return a,”时,实际上是调用复制构造函数将a的值复制到临时对象中。函数g运行结束时对象a消失,但临时对象会存在于表达式"b=g()"中。计算完这个表达式后,临时对象的使命也就完成了,该临时对象便自动消失。

    例:完整程序

    #include <iostream>
    using namespace std;
    
    class Point{//Point 类的定义
    public:
        Point(int xx=0 , int yy=0) {//构造函数
        x=xx;
        y=yy;
        }
        Point(Point &p);//复制构造函数
        int getX(){return x};
        int getY(){return y};
    private://私有数据
        int x,y;
    };
    
    //成员函数的实现
    Point::Point(Point &p){
        x=p.x;
        y=p.y;
        cout<< "Calling the copy co且structor"<<endl;
    }
    //形参为Point类对象的函数
    void fun1(Point p){
        cout<<p.getX()<<endl;
    }
    //返回值为Point 类对象的函数
    Point fun2(){
        Point a(1,2);
        return a;
    }
    //主程序
    void main(){
        Point a(4,5);//第一个对象a
        Point b=a;//情况一,用a初始化b。第一次调用复制构造函数
        cout<<b.getX()<<endl;
        funl(b);//情况二,对象b作为fun1的实参。第二次调用复制构造函数
        b= fun2();//情况三,函数的返回值是类对象,函数返回时,调用复制构造函数
        cout<<b.getX()<<endl;
    }

    4.3.3 析构函数

    简单来说,析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作,也就是专门做扫尾工作的。
    析构函数是在对象的生存期即将结束的时刻被自动调用的。它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
    与构造函数一样,析构函数通常也是类的一个公有函数成员,它的名称是由类名前面加“~”构成,没有返回值。
    和构造函数不同的是析构函数不接收任何参数,但可以是虚函数(第8章)。
    如果不进行显式说明,系统也会生成一个函数体为空的隐含析构函数
    例:

    #include<iostream>
    using namespace std;
    class Point
    {     
      public:
        Point(int xx,int yy);
        ~Point();
        //...其它函数原形
      private:
        int X,int Y;
    };
    
    Point::Point(int xx,int yy)
    {     X=xx;   Y=yy;
    }
    Point::~Point()
    {
    }
    //...其它函数的实现略

    如果希望程序在对象被删除之前的时刻自动(不需要人为进行函数调用)完成某些事情,就可以把它们写到析构函数中。


    4.3.4 程序实例
    例题:游泳池改造预算,Circle类
    一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。

     1 #include <iostream>
     2 using namespace std;
     3 const float PI = 3.14159;
     4 const float FencePrice = 35;
     5 const float ConcretePrice = 20;
     6 
     7 //声明类Circle 及其数据和方法
     8 class Circle
     9 {
    10   private:
    11     float   radius;
    12        
    13   public:
    14     Circle(float r);  //构造函数
    15         
    16     float Circumference() const; //圆周长
    17     float Area() const;  //圆面积
    18 };
    19 // 类的实现
    20 // 构造函数初始化数据成员radius
    21 Circle::Circle(float r)
    22 {
    23     radius=r;
    24 }
    25 
    26 // 计算圆的周长
    27 float Circle::Circumference() const
    28 {
    29     return 2 * PI * radius;
    30 }
    31         
    32 // 计算圆的面积 
    33 float Circle::Area() const
    34 {
    35     return PI * radius * radius;
    36 }
    37 void main ()
    38 {
    39   float radius;
    40   float FenceCost, ConcreteCost; 
    41   
    42   // 提示用户输入半径
    43   cout<<"Enter the radius of the pool: ";
    44   cin>>radius;
    45 
    46   // 声明 Circle 对象
    47   Circle Pool(radius);
    48   Circle PoolRim(radius + 3);
    49   // 计算栅栏造价并输出
    50   FenceCost = PoolRim.Circumference() * FencePrice;
    51   cout << "Fencing Cost is ¥" << FenceCost << endl;
    52     
    53   //  计算过道造价并输出
    54   ConcreteCost = (PoolRim.Area()-Pool.Area())*ConcretePrice;
    55   cout << "Concrete Cost is ¥" << ConcreteCost << endl;
    56 }

    4.4 类的组合

    4.4.1 组合

    举例:

    class Point
    {   private:
               float x,y; //点的坐标
         public:
               Point(float h,float v); //构造函数
               float GetX(void); //取X坐标
               float GetY(void); //取Y坐标
               void Draw(void); //在(x,y)处画点
    };
    //...函数的实现略
    
    class Line
    {
          private:
                Point  p1,p2; //线段的两个端点
          public:
                Line(Point a,Point b); //构造函数
                Void Draw(void); //画出线段
    };
    //...函数的实现略

    类的组合描述的就是一个类内嵌其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。

    当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。因为部件对象是复杂对象的一部分,因此,在创建对象时既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化
    声明形式:

    类名::类名(对象成员所需的形参,本类成员形参):对象1(参数),对象2(参数),......
    {  本类初始化  }
    • 构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相反)
    • 若调用默认构造函数(即无形参的),则内嵌对象的初始化也将调用相应的默认构造函数。
    class Part  //部件类
    {
        public:
            Part();
            Part(int i);
            ~Part();
            void Print();
        private:
            int val;
    };
    class Whole 
    {
        public:
           Whole();
           Whole(int i,int j,int k);
           ~Whole();
           void Print();
        private:
           Part one;
           Part two;
           int date;
    };
    Whole::Whole()
    {
      date=0;
    }
    Whole::Whole(int i,int j,int k):
                two(i),one(j),date(k)
    {}
    
    //...其它函数的实现略

    4.4.2 向前引用声明

    • 类应该先声明,后使用
    • 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。
    • 前向引用声明只为程序引入一个标识符,但具体声明在其它地方。
    class B;  //前向引用声明
    class A
    {  public:
          void f(B b);
    };
    class B
    {  public:
          void g(A a);
    };
    

    使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。请看下面的程序段:

    class Fred;    //前向引用声明
    class Barney {
       Fred x;    //错误:类Fred的声明尚不完善
     };
    class Fred {
       Barney y;
     };
    class Fred;    //前向引用声明
     
     class Barney {
     public:
       void method()
       {
         x->yabbaDabbaDo();    //错误:Fred类的对象在定义之前被使用
       }
     private:
       Fred* x;   //正确,经过前向引用声明,可以声明Fred类的对象指针
     };
     
     class Fred {
     public:
       void yabbaDabbaDo();
     private:
       Barney* y;
     }; 

    该记住:当你使用前向引用声明时,你只能使用被声明的符号,而不能涉及类的任何细节。

    4.5 UML图形标识

    4.5.1 UML简介
    4.5.2 UML类图

    4.6 结构体和联合体

    4.6.1 结构体
    结构:是由不同数据类型的数据组成的集合体

     struct  结构名
      {
          数据类型   成员名 1;
          数据类型   成员名 2;
           ……
          数据类型   成员名 n; 
      };​​​​​​​

    举例:

    struct student   //学生信息结构体
    {
        int num;  //学号
        char name[20];  //姓名
        char gender;  //性别
        int age;  //年龄
        float score;  //成绩
        char addr[30];  //住址
    };

    结构体变量形式: 
       结构名  结构变量名;

    注意:

    • 结构变量的存储类型概念、它的寿命、可见性及使用范围与普通变量完全一致。
    • 结构变量说明在结构类型声明之后,二者也可同时进行。
    • 结构变量占内存大小可用 sizeof 运算求出: sizeof(运算量)

    初始化:    说明结构变量的同时可以直接设置初值。
    使用:   结构体成员的引用形式:      结构变量名.成员名
    例:

    #include <iostream>
    #include <iomanip>
    using namespace std;
    struct student   //学生信息结构体
    {  int num;  //学号
        char name[20];  //姓名
        char gender;  //性别
        int age;  //年龄
    }stu={97001,"Lin Lin",'F',19};
    void main()
    {  cout<<setw(7)<<stu.num<<setw(20)<<stu.name       <<setw(3)<<stu.sex<<setw(3)<<stu.age;
    }

    4.6.2 联合体

    union 联合名
      {
          数据类型   成员名 1;
          数据类型   成员名 2;
          ……
          数据类型   成员名 n; 
      };

    例子:

    union uarea
    {  char   c_data;
       short  s_data;
       long   l_data;
    }

    无名联合体
    无名联合没有标记名,只是声明一个成员项的集合,这些成员项具有相同的内存地址,可以由成员项的名字直接访问。
    例:

    union{
       int    i;
       float   f;
    }

    在程序中可以这样使用:

    i=10;
    f=2.2;

    4.7 综合实例--个人银行账户管理程序

    4.7.1 类的设计
    4.7.2 源程序及说明

    4.8 深度探索

  • 相关阅读:
    jumpserver的安装
    安装iostat 命令
    zabbix配置server,proxy,agent架构
    RGB颜色对照表
    【ZYNQ-7000开发之九】使用VDMA在PL和PS之间传输视频流数据
    基于AXI VDMA的图像采集系统
    图像采集调试总结
    DDR3调试总结
    内存系列二:深入理解硬件原理
    在嵌入式设计中使用MicroBlaze(Vivado版本)
  • 原文地址:https://www.cnblogs.com/alec7015/p/12393096.html
Copyright © 2020-2023  润新知