6_2、C++:类与对象基本应用

类与对象基本应用

类的组合

类的组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。简单说,一个类中有若干数据成员是其他类的对象。以前的教程中我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、float类型的或结构体类型的,数据成员也可以是类类型的。
如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化。
组合类构造函数定义(注意不是声明)的一般形式为:

类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{
    类的初始化
}
  • 其中,“内嵌对象1(形参表),内嵌对象2(形参表),…”称为初始化列表,可以用于完成对内嵌对象的初始化。其实,一般的数据成员也可以这样初始化,就是把这里的内嵌对象都换成一般的数据成员,后面的形参表换成用来的初始化一般数据成员的变量形参,比如,Point::Point(int xx, int yy):X(xx),Y(yy) { },这个定义应该怎么理解呢?就是我们在构造Point类的对象时传入实参初始化xx和yy,然后用xx的值初始化Point类的数据成员X,用yy的值初始化数据成员Y

声明一个组合类的对象时,不仅它自身的构造函数会被调用,还会调用其内嵌对象的构造函数。那么,这些构造函数的调用是什么顺序呢?首先,根据前面说的初始化列表,按照内嵌对象在组合类的声明中出现的次序,依次调用内嵌对象的构造函数,然后再执行本类的构造函数的函数体。比如下面例子中对于Distance类中的p1和p2就是先调用p1的构造函数,再调用p2的构造函数。因为Point p1,p2;是先声明的p1后声明的p2。最后才是执行Distance构造函数的函数体。

如果声明组合类的对象时没有指定对象的初始值的话,就会自动调用无形参的构造函数,构造内嵌对象时也会对应的调用内嵌对象的无形参的构造函数。析构函数的执行顺序与构造函数正好相反。

#include <iostream>
using namespace std;

class Point
{
public:
     Point(int xx,int yy)   { X=xx; Y=yy; } //构造函数
     Point(Point &p);
     int GetX(void)     { return X; }        //取X坐标
     int GetY(void)     { return Y; } //取Y坐标
private:
     int X,Y; //点的坐标
};

Point::Point(Point &p)
{
    X = p.X;
    Y = p.Y;
    cout << "Point拷贝构造函数被调用" << endl;
}

class Distance
{
public:
   Distance(Point a,Point b); //构造函数
   double GetDis()   { return dist; }
private:
   Point  p1,p2;
   double dist;               // 距离
};

// 组合类的构造函数
Distance::Distance(Point a, Point b):p1(a),p2(b)
{
   cout << "Distance构造函数被调用" << endl;
   double x = double(p1.GetX() - p2.GetX());
   double y = double(p1.GetY() - p2.GetY());
   dist = sqrt(x*x + y*y);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Point myp1(1,1), myp2(4,5);
    Distance myd(myp1, myp2);
    cout << "The distance is:";
    cout << myd.GetDis() << endl;
    return 0;
}

/*这段程序的运行结果是:
  Point拷贝构造函数被调用
  Point拷贝构造函数被调用
  Point拷贝构造函数被调用
  Point拷贝构造函数被调用
  Distance构造函数被调用
  The distance is:5 */
  • 两个类可能相互包含(两个类交叉半包含)
    类A中有类B类型的内嵌对象,类B中也有A类型的内嵌对象。我们知道,C++中,要使用一个类必须在使用前已经声明了该类,但是两个类互相包含时就肯定有一个类在定义之前就被引用了,这时候怎么办呢?就要用到前向引用声明了。前向引用声明是在引用没有定义的类之前对该类进行声明,这只是为程序声明一个代表该类的标识符,类的具体定义可以在程序的其他地方,简单说,就是声明下这个标识符是个类,它的定义你可以在别的地方找到。

类A的公有成员函数f的形参是类B的对象,同时类B的公有成员函数g的形参是类A的对象,这时就必须使用前向引用声明:

class B;  //前向引用声明
class A
{ 
 public:
       void f(B b);
};
class B
{ 
  public:
      void g(A a);
};

类模板

代码复用是面向对象设计中的重要的软件开发思想,对于软件开发效率很是关键。怎样做好代码复用呢?越是通用的代码越好复用,将类型作为参数,这种程序设计类型就是参数化程序设计。模板就是C++进行参数化设计的工具。利用模板我们可以使用同一段程序处理不同类型的对象。

什么是类模板呢?类模板就是为类声明一种模板,使得类中的某些数据成员,或某些成员函数的参数,又或者是某些成员函数的返回值可以取任意的数据类型,包括基本数据类型和自定义数据类型。

类模板的声明形式如下:

template  <模板参数表>
类声明
  • 在类的声明之前要加上一个模板参数表,模板参数表里的类型名用来说明成员数据和成员函数的类型

模板参数表中可以以下两种模板参数:

  1. class 标识符(指明可以接受一个类型参数,就是说这是个不固定的类型,用它生成类时才会产生真正的类型)
  2. 类型说明符 标识符(指明可以接受一个由“类型说明符”所指定类型的常量作为参数)

模板参数表可以包含一个或多个以上两种参数,多于一个时各个参数之间用逗号分隔。类模板的成员函数必须是函数模板。实际上,类模板并不是有实际意义的代码,它只是一些具有相似功能的类的抽象,就是把这些类的共有部分写成模板,类型作为参数,只有用类模板生成类时才会根据需要生成实际的类的代码。

用类模板建立对象时的声明形式为:

模板<模板参数表>  对象名1,...,对象名n;
  • 此处的模板参数表是用逗号分隔开的若干类型标识符或常量表达式构成。它与上面类模板声明时“模板参数表”中的参数是一一对应的。类型标识符与类模板中的“class 标识符”对应,常量表达式与“类型说明符 标识符”对应。这样声明对象之后系统会根据指定的参数类型和常量值生成一个类,然后建立该类的对象。
 #include <iostream>
using namespace std;

// 定义结构体Student
struct Student
{
   int   id;                 // 学号
   float average;    // 平均分
};

// 类模板,实现对任意类型的数据进行存取
template <class T>
class Store
{
 public:
      Store(void);              // 默认形式(无形参)的构造函数
      T GetElem(void);    // 获取数据
      void PutElem(T x);  // 存入数据
 private:
      T item;                   // item用来存放任意类型的数据
      int haveValue;      // 标识item是否被存入数据
};
// 以下是成员函数的实现,注意,类模板的成员函数都是函数模板
// 构造函数的实现
template <class T>
Store<T>::Store(void):haveValue(0)
{
}
// 获取数据的函数的实现
template <class T>
T Store<T>::GetElem(void)
{    // 若item没有存入数据,则终止程序
   if (haveValue == 0)
   {
      cout << "item没有存入数据!" << endl;
      exit(1);
    }
   return item;
}
// 存入数据的函数的实现
template <class T>
void Store<T>::PutElem(T x)
{
   haveValue = 1;     // 将其置为1,表示item已经存入数据
   item = x;                // 将x的值存入item
}

int _tmain(int argc, _TCHAR* argv[])
{
  // 声明Student结构体类型变量,并赋初值
  Student g = { 103, 93 };
  // 声明两个Store类的对象,数据成员item为int类型
  Store<int> S1, S2;
 // 声明Store类对象S3,数据成员item为Student结构体类型
 Store<Student> S3;
 S1.PutElem(7);    // 向对象S1中存入数值7
 S2.PutElem(-1);   // 向对象S2中存入数值-1
 // 输出S1和S2的数据成员的值
 cout << S1.GetElem() << "  " << S2.GetElem() << endl;

 S3.PutElem(g);    // 向对象S3中存入Student结构体类型变量g
 // 输出对象S3的数据成员
 cout << "The student id is " << S3.GetElem().id << endl;

 return 0;
        }

类是对对象的抽象,类模板是对类的抽象。类模板并非编程开发中必须的内容

UML简介

我们在进行软件开发的时候,如果只靠脑子想,只有一个看不见的软件规划、软件架构,可能写程序时会影响你的思路的清晰,或者中间间断了以后会忘记当初的规划而要重新回忆或重新规划。如果我们可以把程序设计用图形表达出来,就会让我们的思路很清晰,也很容易进行合理的优化,我们和其他的软件开发人员或者用户就能够进行更好的沟通。
面向对象设计图应运而生,它能清楚的描述以下几个问题:

  1. 类,包括数据成员和函数成员。
  2. 对象,类的实例。
  3. 类及对象的关系,继承或者包含。
  4. 类及对象之间的联系,相互作用与消息传递等。

现在国际上标准的面向对象标记方法称为UML,即统一建模语言。这种标记分为两类图形符号:

  1. 表示符号和连接符号。表示符号用来表示类和对象。
  2. 连接符号用来表示类和对象之间的关系和联系。

UML中有9种图:类图、对象图、用例图、顺序图、协作图、状态图、活动图、组件图和实施图。这里只介绍下类图和对象图。

  • 类图:
    类图
  • 对象图:
    对象图

ML中类及对象的关系有以下几种:依赖、关联、聚合、组合、泛化和实现。

  • 依赖关系
    如果类A使用了类B,或者说如果类B的变化会影响类A,则说类A依赖于类B。一般有以下几种情况属于依赖关系:类A调用类B的成员函数;类B的对象是类A的成员变量;类A的成员函数使用了类B类型的参数。依赖关系用带箭头的虚线表示。
    依赖关系

  • 关联关系
    关联关系
    重数A表示类B的每个对象与类A的多少个对象相关联,重数B则表示类A的每个对象与类B的多少个对象发生作用。比如,老师和学生的关联,老师类的重数应该是1,学生类的重数可能是n。

  • 聚合关系
    聚合关系
    聚合表示类之间的关系是整体和部分的关系,但是聚合关系中的整体和部分是可以分开的。比如,我们可以选择某个主板、硬盘、机箱等配件组装一台电脑,以后这台电脑可以随时更换配件,还一样能够组成一台电脑。聚合关系用带空心菱形的实线表示。

  • 组合关系
    在这里插入图片描述
    组合关系也是整体和部分的关系,但是它与聚合关系不同的是,整体和部分是不可以分开的。比如,我们的房子由客厅、卧室、厨房等组合而成,房子不能和别人的房子对换客厅等任何房间,客厅、卧室等不能和房子分开。组合关系用带实心菱形的实线表示。

  • 泛化关系
    如果类A和类B是从类C继承的子类,那么类C就是类A和类B的泛化。泛化关系用带空心三角形的实线表示:
    在这里插入图片描述

  • 实现关系
    在这里插入图片描述
    实现关系用来说明接口和实现接口的类之间的关系。实现关系图用带空心三角形的虚线表示。

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值