五、类和对象

1、结构化程序设计

1)C语言使用结构化程序设计:

程序 = 数据结构 + 算法 

  • 程序由全局变量以及众多相互调用的函数组成 。 
  • 算法以函数的形式实现,用于对数据结构进行 操作。

2)结构化程序设计的不足

  • 结构化程序设计中,函数和其所操作的数据结构, 没有直观的联系。 
  • 随着程序规模的增加,程序逐渐难以理解,很难一 下子看出来:

    某个数据结构到底有哪些函数可以对它进行操作?

    某个函数到底是用来操作哪些数据结构的?

    任何两个函数之间存在怎样的调用关系?

  • 结构化程序设计没有“封装”和“隐藏”的概念。 要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序 的维护、扩充。
  • 难以查错,当某个数据结构的值不正确时,难以找出到底是那个函数导致的。
  • 重用:在编写某个程序时,发现其需要的某项功 能,在现有的某个程序里已经有了相同或类似的实现,那么自然希望能够将那部分代码抽取出来, 在新程序中使用。
  • 在结构化程序设计中,随着程序规模的增大,由于程序大量函数、变量之间的关系错综复杂,要抽取这部分代码,会变得十分困难。
  • 总之,结构化的程序,在规模庞大时,会变得难 以理解,难以扩充(增加新功能),难以查错, 难以重用。
  • 软件业的目标是更快、更正确、更经济地建立软件。

    如何更高效地实现函数的复用?

    如何更清晰的实现变量和函数的关系?使得程序 更清晰更易于修改和维护

2、面向对象程序设计

1)面向对象的程序设计方法,能够较好解决上述 问题。

  面向对象的程序 = 类 + 类 + …+ 类

设计程序的过程,就是设计类的过程。

2)面向对象的程序设计方法: 

  • 将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性); 
  • 将这类事物所能进行的行为也归纳出来,形成一个个函数, 这些函数可以用来操作数据结构(这一步叫“抽象”)。
  • 然后,通过某种语法形式,将数据结构和操作该数据结构的函 数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封 装”。
  • 面向对象的程序设计具有“抽象”,“封装”“继承”“多态” 四个基本特点。

3、类和对象

1)从客观事物抽象出类

  • 比如: 写一个程序,输入矩形的长和宽,输出面积和周长。

  a.于“矩形”这种东西,要用一个类来表示,该如何做 “抽象”呢?

    矩形的属性就是长和宽。因此需要两个变量,分别代表长 和宽。

  b.一个矩形,可以有哪些行为呢(或可以对矩形进行哪些操 作)? 

    矩形可以有设置长和宽,算面积,和算周长这三种行为 (当然也可以有其他行为)。

    这三种行为,可以各用一个函数来实现,他们都需要用 到长和宽这两个变量。

  c.将长、宽变量和设置长,宽,求面积,以及求周长的 三个函数“封装”在一起,就能形成一个“矩形类”。

  d.长、宽变量成为该“矩形类”的“成员变量”,三个 函数成为该类的“成员函数” 。 成员变量和成员函 数统称为类的成员。

  e.实际上,“类”看上去就像“带函数的结构”。    

实现上述描述的类:

class CRectangle {
     public: int w, h; 
    int Area() {
         return w * h; 
    } 
    int Perimeter(){
     return 2 * ( w + h); 
    } 
    void Init( int w_,int h_ ) { 
    w = w_; h = h_; 
    } 
 }; //必须有分号    

int main( ) 
{ 
  int w,h; 
  CRectangle r; //r是一个对象 
  cin >> w >> h; 
  r.Init( w,h); 
  cout << r.Area() << endl << r.Perimeter(); 
  return 0; 
}
  • 通过类,可以定义变量。

  类定义出来的变量,也称为类的实例,就是我们所说的“对象” 。

  C++中,类的名字就是用户自定义的类型的名字。可以象使用基本类型那样来使用它。CRectangle 就是一种用户自定义的类型

  和结构变量一样,对象所占用的内存空间的大小, 等于所有成员变量的大小之和。 对于上面的CRectangle类,sizeof(CRectangle) = 8 

  每个对象各有自己的存储空间。一个对象的某个成 员变量被改变了,不会影响到另一个对象

 2)对象间的运算

和结构变量一样,对象之间可以用 “=”进行赋值,但是不能用 “==”,“!=”,“>”,“<”“>=”“<=” 进行比较,除非这些运算符经过了“重载”。

使用类的成员变量和成员函数

  • 用法1:  对象名.成员名
CRectangle r1,r2;
  r1.w = 5;
  r2.Init(5,4);

 

Init函数作用在 r2 上,即Init函数执行期间访问的 w 和 h是属于r2 这个对象的, 执行r2.Init 不会影响到 r1。 

  • 用法2: 指针->成员名
CRectangle r1,r2;
CRectangle * p1 = & r1; 
CRectangle * p2 = & r2;
p1->w = 5; 
p2->Init(5,4); //Init作用在p2指向的对象上使用类的成员变量和成员函数 
  • 用法3: 引用名.成员名
CRectangle r2; 
CRectangle & rr = r2; 
rr.w = 5; 
rr.Init(5,4); //rr的值变了,r2的值也变 
void PrintRectangle(CRectangle & r) { 
cout << r.Area() << ","<< r.Perimeter(); 
} 
CRectangle r3; 
r3.Init(5,4); 
PrintRectangle(r3)

3)类成员的可访问范围

  • 在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围:

    – private: 私有成员,只能在成员函数内访问

    – public : 公有成员,可以在任何地方访问

    – protected: 保护成员,以后再说

  以上三种关键字出现的次数和先后次序都没有限制。

定义一个类格式: 

class className {
      private: 私有属性和函数
      public: 公有属性和函数
      protected: 保护属性和函数
    }; 

如过某个成员前面没有上述关键字,则缺省地被认为是私有成员。

 class Man {
       int nAge; //私有成员
       char szName[20]; // 私有成员
      public:
         void SetName(char * szName){
        strcpy( Man::szName,szName);
        }
    }; 
  • 在类的成员函数内部,能够访问:

    – 当前对象的全部属性、函数;

    – 同类其它对象的全部属性、函数。

在类的成员函数以外的地方,只能够访问该类对象的公有成员。

class CEmployee {
          private: char szName[30]; //名字
      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); //ok
    }

    void CEmployee::getName( char * name) {
       strcpy( name,szName); //ok
     }

   void CEmployee::averageSalary(CEmployee e1, CEmployee e2){
     cout << e1.szName; //ok,访问同类其他对象私有成员
     salary = (e1.salary + e2.salary )/2;
    }

    int main() {
      CEmployee e;
      strcpy(e.szName,"Tom1234567889"); //编译错,不能访 问私有成员
      e.setName( "Tom"); // ok
      e.salary = 5000; //ok
      return 0;
   }            
  • 设置私有成员的机制,叫“隐藏”

   隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员 函数即可。否则,所有直接访问成员变量的语句都需要修改。

如果将上面的程序移植到内存空间紧张的手持设备上,希望将 szName 改为 char szName[5],若szName不是私有,那么就要找出所有类似 strcpy(e.szName,"Tom1234567889"); 这样的语句进行修改,以防止数组越界。这样做很麻烦。

“隐藏”的作用:

    --如果将szName变为私有,那么程序中就不可能出现(除非在类的内部) strcpy(e.szName,"Tom1234567889"); 这样的语句,所有对 szName的访问都是通过成员函数来进行,比如: e.setName( “Tom12345678909887”);

    --那么,就算szName改短了,上面的语句也不需要找出来修改,只 要改 setName成员函数,在里面确保不越界就可以了。

2)用struct定义类

 struct CEmployee { 
    char szName[30]; //公有!! 
    public : int salary; //工资 
    void setName(char * name); 
    void getName(char * name); 
    void averageSalary(CEmployee e1,CEmployee e2); 
}; 

和用"class"的唯一区别,就是未说明是公有还是私有的成员,就是公有

3)成员函数的重载及参数缺省
  -- 成员函数也可以重载

  -- 成员函数可以带缺省参数。

#include <iostream>
using namespace std;
class Location {
private :
int x, y;
public:
void init( int x=0 , int y = 0 );
void valueX( int val ) { x = val ;}
int valueX() { return x; }
};
void Location::init( int X, int Y)
{
x = X;
y = Y;
}
int main() {
Location A,B;
A.init(5);
A.valueX(5);
cout << A.valueX();
return 0;
}
输出:
5
  --使用缺省参数要注意避免有函数重载时的二义性
class Location {
    private :
    int x, y;
    public:
    void init( int x =0, int y = 0 );
    void valueX( int val = 0) { x = val; }
    int valueX() { return x; }
};
    Location A;
    A.valueX(); //错误,编译器无法判断调用哪个valueX

 

posted @ 2022-05-14 15:41  Grit_L。  阅读(37)  评论(0编辑  收藏  举报