C++ 类和对象

在这里插入图片描述

类与对象

1. 类的定义
类类型的形式化定义为:

class className
{
	属性列表;
	行为列表;
};  //Java?--无此;

类和对象的概念
与其它类型一样,类只是一种形式化的规格说明。要使用类提供的功能,必须使用类的实例(类的静态成员例外)。类的实例称为“对象”。一个类可以定义多个对象
实例化
定义对象的过程称为“实例化(instantiation)”,而一个对象也称为类的“实(instance)
定义一个类对象就像定义一个整型变量一样。例如:
Rectangle rect;
int a;
•类类型Rectangle和整数类型int代表的是一般的概念,对象rect和整型变量a代表的是具体的实例,而每一个实例都要在内存中占据一定的存贮空间
与结构体类似,我们使用成员选择运算符.来访问对象的成员,例如:
c.x
c.area()
注:c的成员函数没有包含在它的内存布局中(思考:why)
这与C的结构体有很大不同:C结构体不能包含成员函数

2.类和对象的关系
• 类代表了一组对象的共同性;对象被赋予了具体的性质
• 类在概念上是一种抽象机制,它抽象了一类对象的存储和操作特性;对象是类的一个实现,占据了物理存储器
• 类是一种共享机制,它提供了一类对象共享其类的操作实现。这些操作通过类的实例(对象)来完成
• 类是一种
封装
机制,它将一组数据和对该组数据的操作封装在一起;对象是这种封转机制的具体实现
• 类是对象的模型,对象承袭了类中的数据和方法(操作)。只是各实例对象的数据初始化状态和各个数据成员的值不同

3.访问控制
• 一组成员刻画了类的外部接口,是类与外部进行联系的纽带,因此必须是在类外可以被访问到的
• 另一组成员描述了类的内部结构,记录了类对象的运行状态,是不应该也不需要被外部了解的,因此只能在类内访问,而在类外是不可访问的(inaccessible),或者说,是不可见的(invisible)

class 类名
{
private:    	  //定义私有段成员
	私有段数据和函数定义;
protected:  //定义保护段成员
	保护段数据和函数定义;
public:       //定义公有段成员
	公有段数据和函数定义;
};

访问控制描述符(access specifier),俗称“段描述符”。private和protected成员仅在类内可见,public成员在类内和类外均可见。

数据封装:
• 使用私有数据这一语言特性来隐藏由类对象操纵的数据
• 类对数据的描述和类提供给外部世界来处理数据的接口这两件事互相独立
• 各个对象通过该接口完成各项功能。这就是“数据封装”的概念。

访问控制保护的是类而非类对象,在类的作用域中,可以自由地访问该类对象的所有成员而没有任何限制。

类的成员

一个类类型中可以有两类成员:数据(data)和操作(operation)
C++:
数据-〉数据成员(data member)
操作-〉成员函数(member function)
面向对象的术语:
“属性(attribute/property)”“方法(method)”

1.数据成员
• 类的数据成员往往描述了该类对象的某种特性,或者目前所处的状态,因此它们被称为“属性”
• 从理论上讲,类的数据成员的类型可以是任意已经定义的类型,包括编译器内建类型用户自定义类型

class List
{
	private:
			Node *head, *tail;
			//other members
};

一个类还可以包含另一个或多个类的对象作为成员。例如:

class Lynx {}; 
class Alligator {};
class Hippopotamus {};
 
class Zoo
{
public:
     Lynx lynx;
     Alligator gator;
     Hippopotamus hippo;
};

Zoo类包含了Lynx等类的对象作为数据成员。这是一种类与类合作的方式。
在Zoo中对lynx的使用受到了Lynx类访问控制的限制。这就要求后者能够提供足够的公共接口

在一般情况下,一个类不能包含该类类型的对象作为成员,因为这样做会使一个类的存在依赖于该类自己。很明显,这是一种递归定义方式,而C++编译器不支持这样的方式。例如:

class Zoo
{
private:
	Zoo *p;  //ok
	Zoo &r; //ok 这个引用成员必须被初始化。
	//Zoo o; //错误,类定义依赖于自身。
};

2.成员函数
函数作为成员是面向对象的一种标志之一。这表明了,操作是隶属于对象的。此外,类的公有成员函数提供了该类的功能接口,使得其它类能够调用这些接口,从而获得该类提供的服务

  1. 在类中定义成员函数
class circle //简化版
	{
		public:
			double area() { return; }
			//other members
		private:
			//other members
			double r;
	};
  1. 在类中声明成员函数,在类外定义成员函数
    在类型定义外再给出函数的定义的格式为:
    返回类型 类名::函数名(参数列表) {…}
class circle //简化版
{
Public:
	double area(); //类中声明成员函数
Private:};
double circle::area() //类外定义成员函数
{
	return;
}
::称为“作用域解析运算符”,它指明了其右面的元素隶属于左面的作用域。这个作用域可以是一个类,也可以是一个名字空间。
成员函数“知道”它正在操作的数据是属于激活该函数的那个对象的。

this指针

3.静态成员
在类作用域中,关键字static也可以用于修饰一些成员(包括数据成员和成员函数),这样的成员被称为“静态成员(static member)”

  1. 静态数据成员
    定义静态数据成员的语法可以用下例演示:
class circle //简化版
{
public:
	static int counter; //all objects share this static data member
    //other members
};

前面的例子只是声明了静态成员,而它的存储需要在类定义外额外分配
int circle::counter = 0;
其中,初始化是可选项。但在一般情况下都是需要的。
假设定义了三个对象:
circle c1, c2, c3;
下图形象地说明了类的静态数据成员和普通数据成员的关系。
在这里插入图片描述
静态数据成员属于类,而不属于对象。可以这样来理解这句话的含义:静态数据成员的存储定义是独立于类的,所以在所有类对象被创建之间它就已经存在了。
• 在这个意义上,在类对象不存在的情况下,也可以访问到类的静态成员。其访问方式为:
类名::静态公有数据成员
• 这种访问方式不需要类对象的参与。而在类的内部,静态数据成员可以被所有成员直接访问而没有任何限制。
• 但是,静态数据成员仍是类的一部分,所有受到了访问控制的严格约束,只有具有公共访问属性的静态数据成员才能在类外被访问到。

  1. 静态成员函数
    • 与静态数据成员一样,静态成员函数属于类而不是某个类对象
    • 在类外调用一个公有静态成员函数:
    类名::静态公有成员函数名(参数列表)
    • 一般使用静态成员函数来访问静态数据成员
    • 与其它非静态成员函数一样,所有类对象共享静态成员函数的代码
    • 静态成员函数是不属于类对象的,因此它“不知道”自己在操作哪个对象

类对象的初始化

与简单对象一样,新生成的对象没有初始状态,即这个对象的数据成员的值都是未定的。因此,为了避免运行时错误,应该对其进行初始化工作。从理论上讲,类的所有数据成员都应该被初始化

解决方法:为类提供一个初始化函数来完成操作。
• 在定义了一个对象后,应立即调用它的初始化函数。
• 如果类拥有静态数据成员,那么这些成员的初始化工作最好是在定义静态成员的存储时完成,而不应该在初始化函数中进行
• 如果类还拥有一些特殊的成员,例如常量和引用成员,那么它们的初始化只能借助于类的构造函数完成。

注:实际上,初始化函数并非是一种好的模式

C++中的类

除了class外,C++还将从C继承过来的struct和union视为类,不过,它们拥有特别的特性

  1. 结构 struct
    结构体和类的差别在于缺省访问控制描述时,类的成员都是私有的,而结构体的成员则都是公有的。
  2. 联合 union
    联合体的所有成员只能为公有成员

一般情况下,C++程序员都用class来定义对象的抽象形式,而用C的方式来使用结构体
如果仅需C式的联合体,最好以C的方式使用它。

数据封装和信息隐藏的意义

一个类的定义实现了数据封装,它具有如下可见性:

• 类外不可见:(即封装起来的部分)
– 私有段数据
– 私有段函数原型
– 私有段函数和公有段函数的实现

• 类外可见:(即一个类向外提供的部分)
– 公有段数据

公有段函数原型
• 类类型符合抽象原则。抽象是指对于一个系统的简化的描述
抽象的原则,运用在计算机领域,称之为“信息隐藏”原则;在面向对象的程序设计语言中,使用数据封装机制实现信息隐蔽
• 将对象的描述、对象操作的实现与对象提供的接口分开,使得类的使用者(委托方)对类的实现部分的依赖程度大大减小
• 在面向对象方法中,一个类的用户唯一需要做的事情是访问类的接口

用面向对象的方式思考

第一,确定解决问题的流程
提问:问题中的流程是怎样的?
输入
计算
输出

第二,在流程中发现对象/类。
提问:问题中可能的对象有哪些?
两步流程中出现了三个场景,每个场景都有潜在的对象存在:

  1. 输入:键盘
  2. 计算:(这个可以成为对象吗?)
  3. 输出:显示器

为什么还需要单独分离出输入和输出设备这两个概念呢?
在回答上述问题之前,我们先试着回答如下两个相关的问题?
① 在我们的设计中,计算设备在承担了计算任务之外,是否还应该承担输入和输出的责任?
② 计算设备用的输入输出设备是否只限于键盘和显示器?

第一个问题的答案为否,因为输入和输出显然是另外一些设备的任务。面向对象设计原则之一就是:只让一个类承担最少/小的责任。这项原则称为“单一责任原则(SRP, The Single Responsibility Principle”。
第二个问题的答案也为否,因为我们可能用到触屏或者打印机完成输入和输出。

基于上述两个问题的答案,那么将任务分解到三个不同的类上是非常有道理的。
根据上述分析,所有场景中的对象及其功能可以归纳为:
• 输入设备:keyboard,只完成键盘输入;
• 输出设备:monitor,只完成屏幕显示;
• 计算器:calculator,主要完成计算,并整合输入和输出的功能;
数据:简单类型。

第三,设计类的接口,以及类之间的关系
尽可能多地列出对象的属性和操作,再从中筛选与问题直接相关的信息,同时理清属性和相关操作的关系。这是在建立对象内部数据和操作之间的联系。
提问: 几个对象各有哪些

第四,编码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值