6_1、C++:类与对象基本介绍

面对对象设计概念

  1. 抽象
    面向对象方法中的抽象是指对具体问题即对象进行概括,抽出一类对象的共性并加以描述的过程。面向对象的软件开发中,首先应该对要解决的问题抽象成类,然后才是解决问题的过程。抽象有两个方面:数据抽象和行为抽象。数据抽象是描述某类对象的属性或状态,行为抽象是描述某类对象的共同行为或共同功能。比如:
时钟(Clock):
数据抽象:
   int Hout; int Minute; int Second;
行为抽象:
   ShowTime(); SetTime();
  1. 封装
    把抽象出来的数据成员和函数成员结合形成一个整体,就是封装。封装的时候,我们可以把一些成员作为类和外界的接口,把其他的成员隐藏起来,以达到对数据访问权限的控制,这样可以使程序的各个部分改变时最低程度的影响其他部分,程序会更安全。
    把数据和函数封装为一个可复用的模块,开发时可以利用已有的成果而不必每次都重复编写。我们只需要通过类提供的外部接口访问模块,并不需要知道内部的细节。C++中就是利用类的形式来实现封装的。
class  Clock                                                                       // class是关键字 Clock是类名
{
public:                                            //提示下面是外部接口
   void SetTime(int NewH,int NewM,int NewS);       // 行为,函数成员
   void ShowTime();                                 // 行为,函数成员
private:                                           // 特定的访问权限
   int Hour,Minute,Second;                        // 属性,数据成员          
};
  • 这是一个完整的类的声明。它声明了一个名为Clock的类,其中的数据成员和函数成员是前面分析得到的抽象结果。关键字public和private是用来指定成员的不同访问权限的,至于具体访问权限的问题后面课程中会讲到。声明为public的两个函数为类提供了外部接口,外界只能通过这两个接口跟Clock类联系。声明为private的三个整型数据是类的私有数据,外部无法直接访问。我们可以看到,这种访问权限的机制有效实现了对数据的隐藏。
  1. 继承
    我们在软件开发过程中,可能已经有了前人的一些现有的成果,我们没有必要再重新去编写,那么我们怎样利用这些已有的模块呢?还有可能我们对以前写的程序有了更新的认识,需要融入一些新的认识,那怎么办呢?
    这些都可以通过继承来实现,C++语言提供了类的继承机制,让我们软件开发者可以在保持原有特性的基础上,进行更具体、更详细的说明。通过继承我们可以利用之前已经有的程序模块,还可以添加一些新的数据和行为,这在很大程度上提高了程序的复用性,大大节约开发成本。
  2. 多态
    多态就是类中具有相似功能的不同函数使用同一个名称。上一讲中讲的重载函数就实现了多态。利用多态可以对类的行为再抽象,抽象成同一个名称的功能相似的函数,减少程序中标识符的个数。多态是通过重载函数和虚函数等技术来实现的。

类的声明、成员的访问控制和对象

在面向过程的设计中,程序的模块是函数构成的,而面向对象设计中程序模块是类构成的。函数只是语句和数据的封装,而类是函数与数据的封装,对比下肯定是面向对象设计更重量级了,更适合大型程序的开发。

类的声明

可以理解为类就是一种自定义数据类型,跟一般的类型如int、char等有很多相似之处。
类的声明:

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

//示例
class  Clock                                                                      
{
public:                                                                              
   void SetTime(int NewH,int NewM,int NewS);      
   void ShowTime();                                                  
private:                                                              
   int  Hour,Minute,Second;                                           
};
  • 这里的public、protected和private关键字可以任意换顺序,比如先声明私有成员再声明其他的也可以,每个关键字也可以出现多次,比如声明一些public的成员,后面又出了个public声明了另一些成员,也是可以的,但是一般我们还是按照上面的形式来声明类。

类中函数的实现:类Clock封装了时钟的数据和行为,分别叫Clock类的数据成员和函数成员。在类的声明中只声明函数的原型,函数的实现也就是函数体可以在类外定义,当然也可以写在类声明里,那样就成为隐式声明的内联函数

void Clock::SetTime(int NewH, int NewM, int NewS)
{
     Hour=NewH;
     Minute=NewM;
     Second=NewS;
}
void Clock::ShowTime()
{
    cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
}
//函数名前面要加上它所属的类,用来说明它属于哪个类.

类成员的访问控制

类成员访问权限的控制是通过设置成员的访问控制属性来实现的。访问控制属性有三种:公有类型(public)、私有类型(private)和保护类型(protected)。

  • 公有类型声明了类的外部接口。公有类型成员用public关键字声明。外部访问必须通过外部接口进行。比如,对于Clock类,外部想查看或改变时间只能通过SetTime和ShowTime两个公有类型的函数实现。
    1. 公有成员可以被类的对象、类的成员函数和类的友元访问.
    2. 公有成员在类的外部是可见的,可以通过对象来访问.
    3. 公有成员通常用于表示类的接口,即外部可以访问和使用的部分
  • private后面声明的是类的私有类型成员。如果没有标明访问控制属性则默认为private。比如,类Clock声明中,如果那个public没有的话,那么SetTime和ShowTime函数就都默认是private的。私有类型成员只能由本类中的成员函数访问,外部不能访问。Clock类中Hour、Minute、Second都是私有类型成员。
    1. 私有成员只能被类的成员函数和类的友元访问,外部无法访问。
    2. 私有成员在类的外部是不可见的,只能在类的成员函数内部和友元函数内部访问。
    3. 私有成员通常用于隐藏类的实现细节,只让类的成员函数访问。
  • 保护类型的成员和私有类型成员权限相似,差别就是某个类派生的子类中的函数能够访问它的保护成员。
    1. 保护成员可以被类的成员函数、类的派生类的成员函数和类的友元访问。
    2. 保护成员在类的外部不可见,只能在类的成员函数内部和派生类的成员函数内部访问。
    3. 保护成员通常用于在继承中表示基类和派生类之间的接口。

类的成员函数

函数原型的声明要写在类主体中,原型说明了函数的参数类型和个数及返回值类型。而函数的具体实现是类声明之外的,但是跟普通函数不同的是,写函数实现时要在前面加上类名,指明所属的类。具体的形式为:

返回值类型  类名::函数成员名(参数表)
{
   函数体
}
  • 类的成员函数也可以有默认参数值,它的调用规则跟之前讲的普通函数相同。

类的内联函数

类的比较简单的成员函数也可以声明为内联函数,和普通内联函数一样,编译时也会将内联函数的函数体插入到每个调用它的地方。内联函数的声明有两种方式:隐式声明和显式声明。

  1. 把函数体直接放到类主体内,这种方式就是隐式声明。
class  Clock                                                                       
{
public:                                                                             
     void SetTime(int NewH,int NewM,int NewS);       
     void ShowTime()
        { cout<<Hour<<":"<<Minute<<":"<<Second<<endl; }                                                  
private:                                                              
     int  Hour,Minute,Second;                                           
        };
  1. 为了程序的可读性,让大家一看就知道是内联函数,一般还是用关键字inline显式声明。就像普通内联函数那样,在函数实现时在函数返回值类型前加上inline,声明中不加入函数体。
inline void Clock::ShowTime()
{
    cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
}

对象

类的对象就是具有该类类型的特定实体。就像一般类型的变量一样,类是自定义数据类型,对象就是该类类型的变量。

  • 声明一个对象和声明变量的方式是一样的: 类名 对象名;。比如,声明一个时钟类的对象:Clock myClock;。
  • 声明了对象后就可以访问对象的公有成员,对于私有和保护对象不能直接访问,这种访问需要采用“.”操作符,调用公有成员函数的一般形式是:对象名.公有成员函数名(参数表),访问公有数据成员的形式是:对象名.公有数据成员。
#include<iostream>
using namespace std;
// 第一部分
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<<endl;
}
// 第三部分
int _tmain(int argc, _TCHAR* argv[])
{   
     Clock  myClock;
     myClock.SetTime(8,30,30);
     myClock.ShowTime();
     return 0;
}

构造函数和析构函数

某个类的对象之间都有哪些不同呢?首先是对象名不同,其次就是对象的数据成员的值不同。我们在声明一个对象时,也可以同时给它的数据成员赋初值,称为对象的初始化。

构造函数

在声明一个变量时,如果对它进行了初始化,那么在为此变量分配内存空间时还会向内存单元中写入变量的初始化。声明类的对象有相似的过程,程序执行时遇到对象声明语句时会向操作系统申请一定的内存空间来存放这个对象,但是它能像一般变量那样初始化时写入指定的初始值吗?
类的对象太复杂了,要实现这一点不太容易,这就需要构造函数来实现。

  • 构造函数的作用就是在对象被创建时利用特定的初始值构造对象,把对象置于某一个初始状态,它在对象被创建的时候由系统自动调用,我们只需要使用默认的构造函数或者自己定义构造函数,而不用管怎么调用的。
  • 构造函数也是类的成员函数,除了有成员函数的所有特征外,还有一些不同之处:构造函数的函数名跟类名一样,而且没有返回值。构造函数一般被声明为公有函数,除非我们不允许某个类生成对象则将它声明为private或protected属性。编译器碰到对象声明语句时,会自动生成对构造函数的调用语句,所以我们常说构造函数是在对象声明时由系统自动调用的。

类中必须带有构造函数,,没有时编译器将自动生成。

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;
};

//构造函数的实现
Clock::Clock(int NewH, int NewM, int NewS)
{
     Hour=NewH;
     Minute=NewM;
     Second=NewS;
}

//建立对象时构造函数的作用
int main()
{
   Clock c(0,0,0); //隐含调用构造函数,将初始值作为实参。
   c.ShowTime();
   return 0;
}
  • main函数中,创建对象c时,其实隐含了构造函数的调用,将初始值0,0,0作为构造函数的实参传入。因为上面Clock类定义了构造函数,那么编译器就不会再为它生成默认构造函数了。这里的构造函数有三个形参,那么建立对象就必须给出初始值了。

因为构造函数也是一个成员函数,所以它可以直接访问类的所有数据成员,可以是内联函数,可以带有形参表,可以带默认的形参值,也可以重载,就是有若干个名字相同但形参个数或者类型不同的构造函数。

拷贝构造函数

我们可以将一个变量的值赋给另一个同类型的变量,那么可以将一个对象的内容拷贝给相同类的另一个对象吗?可以,我们可以将第一个对象的数据变量的值分别赋给另一个对象的数据变量,但是,如果数据变量数很多的话那将是很麻烦的,这时候我们就需要有拷贝构造函数。
拷贝构造函数是一种特殊的构造函数,因为它也是用来构造对象的。它具有构造函数的所有特性。拷贝构造函数的作用是用一个已经存在的对象去初始化另一个对象,这两个对象的类类型应该是一样的。定义拷贝构造函数的形式是:

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;
}

拷贝构造函数在以下三种情况下会被调用:

  1. 当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。
Point A(1,2);
Point B(A); //拷贝构造函数被调用
  1. 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。
void fun1(Point p)
{  
    cout<<p.GetX()<<endl;
}
int main()
{  
    Point A(1,2);
    fun1(A); //调用拷贝构造函数
    return 0;
} 
  1. 当函数的返回值是类对象时,系统自动调用拷贝构造函数。
Point fun2()
{   
    Point A(1,2);
    return A; //调用拷贝构造函数
}
int main()
{
    Point B;
    B=fun2();
    return 0;
}

析构函数

如果在函数中声明了一个对象,那么在这个函数运行完返回调用函数时,声明的对象也会释放.
在对象释放时都有什么工作要做呢?我们经常遇到的情况就是:构造函数时动态申请了一些内存单元,在对象释放时就要同时释放这些内存单元。
析构函数和构造函数的作用是相反的,它会在对象被删除之前做一些清理工作。析构函数是在对象要被删除时由系统自动调用的,它执行完后对象就消失了,分配的内存空间也释放了。

  • 析构函数是类的一个公有函数成员,它的名称是在类名前加“~”形成,不能有返回值,大家注意下,它和构造函数不同的是它不能有任何形参。如果没有定义析构函数系统也会自动生成一个默认析构函数,默认析构函数也不会做任何工作。一般如果我们想在对象被删除之前做什么工作就可以把它写到析构函数里。
class Point
{    
  public:
        Point(int xx, int yy);
        ~Point();
//...其他函数原型
  private:
        int X, int Y;
        char *p;
};

//下面是构造函数和析构函数的实现
Point::Point(int xx,int yy)
{     
   X=xx;   
   Y=yy;
   p=new char[20];     // 构造函数中动态分配char型内存
}
Point::~Point()
{
   delete []char;      // 在类析构时释放之前动态分配的内存
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值