C++ 类和对象

类及其实例化

对象是一类物体的实例,将一组对象的共同特征抽象出来,从而形成"类"的概念。

定义类

像C语言构造结构一样,类也是一种用户自己构造的数据类型并遵循C++的规定。

类要先声明后使用,不管声明内容是否相同,声明同一个名字的两个类是错误的,类是具有唯一标识符的实体;在类中声明的任何成员不能使用extern ,auto和register关键字进行修饰;类中声明的变量属于该类,在某些情况下,变量也可以被该类的不同实例所共享。

类和其他数据类型不同的是,组成这种类型的不仅可以有数据,而且可以有对数据进行操作的函数,他们分别叫做类的数据成员和类的成员函数,而且不能在类声明中对数据成员使用表达式进行初始化。

类声明

类声明以关键字class开始,其后跟类名。类所声明的内容用花括号括起来,右花括号后的分号作为类关键字声明语句的结束标志。这一对花括号之间的内容称为类体;

访问权限用于控制对象的某个成员在程序中的可访问性,如果没有使用关键字,则所有成员默认声明为private权限。

定义成员函数

类声明的成员函数用来对数据成员进行操作,还必须要在程序中实现这些成员函数。

定义成员函数的一般形式如下:
返回类型 类名::成员函数名(参数列表)
{
成员函数的函数体 //内部体现
}

其中“::”是作用域运算符,“类名”是成员函数所属类的名字,“::”用于表明其后的成员函数是属于这个特定的类。换言之,“类名::成员函数名” 的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值的类型。

也可以使用关键字inline将成员函数定义为内联函数

如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。

数据成员的赋值

不能在类体内给数据成员赋值。在类体外就更不允许。

数据成员的具体值是用来描述对象的属性的。只有产生了一个具体的对象,这些数据才有意义,如果在产生对象是就使对象的数据成员具有指定值,则称为对象的初始化。

使用类的对象

对象和引用都使用运算符”.“访问对象的成员,指针则使用”->“运算符。
暂不涉及还没有介绍的保护成员,可以归纳出如下规律:

(1)类的成员函数可以直接使用自己类的私有成员(数据成员和成员函数)

(2)类外面的函数不能直接访问类的私有成员(数据成员和成员函数)

(3)类外面的函数只能通过类的对象使用该类的公有成员函数。

在程序运行时,通过为对象分配内存来创建对象。在创建对象时,使用类作为样板。故称对象为类的实例。

定义类对象指针的语法如下:

类名* 对象指针名;

对象指针名=对象的地址;
也可以直接进行初始化。

类目 *对象指针名=对象的地址;
类对象的指针可以通过“->”运算符访问对象的成员,即:
对象指针名->对象成员名

数据封装
面向对象的程序设计是通过为数据和代码建立分块内存区域,以便提供对程序进行模块化的一种程序设计方法,这些模块可以被用作样板,在需要时再建立其副本。根据这个定义,对象是计算机内存中的一块区域,通过将内存分块,每个模块(即对象)在功能上保持相对独立。

对象被视为能做出动作的实体,对象使用这些动作完成相互之间的作用。换句话说,对象就像在宿主计算机上拥有数据和代码并能相互通信的具有特定功能的一台较小的计算机。

构造函数

C++有称为构造函数的特殊成员函数,它可以自动进行对象的初始化。

初始化和赋值时不同的操作,当C++语言预先定义的初始化和赋值操作不能满足程序的要求时,程序员可以定义自己的初始化和赋值操作。

默认构造函数

当没有为一个类定义任何构造函数的情况下,C++编译器总要自动建立一个不带参数的构造函数。

定义构造函数

构造函数的定义和使用方法

构造函数的名字应与类名同名。并在定义构造函数时不能指定返回类型,即void类型也不可以。

当声明一个外部对象时,外部对象只是引用在其他地方声明的对象,程序并不为外部对象说明调用构造函数。如果是全局对象,在main函数指向之前要调用构造函数,

自动调用构造函数

构造函数的定义和使用方法

构造函数的名字应与类名同在。并在定义构造函数时不能指定返回类型,即使void类型也不可以。

当声明一个外部对象时,外部对象只是引用在其他地方声明的对象,程序并不为外部对象说明调用构造函数。如果时全局对象,在main函数执行之前要调用他们的构造函数。

自动调用构造函数

程序员不能在程序中显式的调用构造函数,构造函数是自己调用的。例如构造一个point对象的类a,不能写错“point a.point(x,y);” , 只能写成“point a(x,y); ” . 编译系统会自动调用point(x,y)产生对象a并使用x和y将其正确的初始化。

可以设计多个构造函数,编译系统根据对象产树的方法调用相应的构造函数,构造函数是在产树对象的同时初始化对象的。

构造函数和预算符new

运算符new 用于建立生存期可控的对象, new 返回这个对象的指针。由于类名被视为一个类型名。因此,使用new建立动态对象的语法和建立动态变量的语法类似,其不同点是new和构造函数一起使用。

使用new建立的动态对象只能用delete 删除,以便释放所占空间。 应养成及时释放不再使用的内存空间的习惯。

构造函数的默认参数

如果程序定义自己的有参数构造函数,又想使用无参数形式的构造函数,解决的方法时间相应的构造函数全部使用默认的参数设计。

复制构造函数

引用在类中一个很重要的用途是用在复制构造函数中。一是一类特殊而且重要的函数,
通常用于使用已有的对象来建立一个新对象。

在通常情况下,编译器建立一个默认复制构造函数,默认复制构造函数采用拷贝方式用已有的对象来建立新对象,所以又直译为拷贝构造函数。程序员可以自己定义复制构造函数,对类A而言,复制构造函数的原型如下:
A::A(A&)

从这个函数原型来看,首先它是一个构造函数,因为这毕竟是在创造一个新对象。其次他的参数有些特别,是引用类自己的对象,即用一个已有的对象来建立新对象。使用引用是从程序的执行效率角度考虑的。为了不改变原有的对象,更普通的形式是像下面这样使用const限定:

A::A(const A &)

像调用构造函数一样,如果自己定义了复制构造函数,编译器只调用程序员为它设计的赋值构造函数。

在C++中,在一个类中定义成员函数可以访问该类任何对象的私有成员。

析构函数

在对象消失时,应使用析构函数释放由构造函数分配的内存。构造函数,赋值构造函数和析构函数时构造型成员函数的基本成员,应深刻理解他们的作用并熟练掌握其设计方法。

定义构造函数

因为调用析构函数也是由编译器来完成的,所以编译器必现总能知道应调用哪个函数。最容易,也最符合逻辑的方法是指定这个函数的名称与类名一样。为了与析构函数区分,在析构函数的前面加上一个 ~ 号。在定义析构函数时,不能知道任何返回类型,即使指定void类型返回类型也不行。析构函数也不能指定参数,但是可以显示地说明参数为void,即形如 A::~A(void) 。从函数重载的角度分析,一个类也只能定义一个析构函数且不能指明参数,以便编译系统自动调用。

析构函数在对象的生存期结束时被自动调用。当对象的生存期结束时,程序为这个对象调用析构函数,然后挥手这个对象占用的内存。全局对象和静态对象的析构函数在程序运行结束之前被调用。

析构函数与运算符delete

运算符delete与析构函数一起工作。当使用运算符delete删除一个动态对象时。他首先为这个动态对象调用析构函数。然后再释放这个动态对象占用的内存,这和使用new建立动态对象的过程正好相反。

当使用delete释放动态对象数组时,必须告诉这个动态对象数组有几个元素对象,C++使用“[]”来实现。即语句。

delete[] ptr; //注意不要写错为delete ptr[];

当程序先后创建几个对象时,系统按后建先析构的原则析构对象。当使用delete调用析构函数时,则按delete的顺序析构。

默认析构函数

如果在定义类时没有定义析构函数,C++编译器也为他产生一个函数体为空的默认析构函数。

调用复制构造函数的综合实例

成员函数重载及默认参数

this 指针

使用this 指针,保证了每个对象可以拥有自己的数据成员,但处理这些数据成员的代码可以被所有的对象共享。

C++ 规定,当一个成员函数被调用时,系统自动向他穿第一个隐含的参数,该参数是一个指向调用该函数的对象的指针,从而使成员函数知道该对那个对象进行操作。在程序中,可以使用关键字this来引用该指针。this 指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数链接在一起,在外部看来,每一个对象都拥有自己的成员函数。

除非特殊需要,一般情况下都省略符号“this ->”,而让系统进行默认设置。

一个类的对象作为另一个类的成员

类和对象的性质

对象的性质
(1)同一个类的对象之间可以相互赋值
(2)可以使用对象数组
(3)可以使用指向对象的指针,使用取地址运算符&将一个对象的地址置于该指针中。

注意,指向对象的指针的算术运算规则与C语言一样,但指向对象的指针不能取数据成员的地址,也不能取成员函数的地址。

(4)对象可以用作函数参数
(5)对象作为函数参数时,可以使用对象,对象引用和对象指针
(6)一个对象可以做为另一个类的成员

类的性质

使用类的权限

为了简单具体,讨论数据成员为私有,成员函数为共有的情况。

(1)类本身的成员函数可以使用类的所有成员(私有和共有成员)
(2)类的对象只能访问共有成员函数
(3)其他函数不能使用类的私有成员,也不能使用公有成员函数,它们只能通过类的对象使用类的公有成员函数。

(4)虽然一个可以包含另一个类的对象,但这个类也只能通过被包含类的对象使用那个类的成员函数,通过成员函数使用数据成员。

不完全的类声明

类不是内存中的物理实体,只有当使用类产生对象时,才能进行内存分配,这种对象建立的过程称为实例化。

应当注意的是: 类必须要在其成员使用之前先进行声明。

class MembersOnly; //不完全的类声明
MenbersOnly *club; //定义一个全局变量类指针

第一条语句称为不完全类声明。它用于在类没有完全定义之前就引用该类的情况。

不完全声明的类不能实例化,否则会出现编译错误;不完全声明仅用于类和结构,企图存取没有完全声明的类成员,也会引起编译错误。

空类

尽管类的目的是封装代码和数据,它也可以不包括任何声明

类的作用域

声明类时所使用的一对花括号形成所谓的类的作用域。在类作用域中声明的标识符只在类中可见。

如果该成员函数的实现是在类定义之外给出的,则类作用域也包含类成员函数的作用域。

类中的一个成员名可以使用类名和作用域运算符来显式地指定,这称为成员名限定。

面向对象的标记图

类和对象的UML标记图

对象的结构与连接

只有定义和描述对象之间的关系,各个对象才能构成一个整体的、有机的系统模型,这就是对象的结构和连接关系。对象结构是指对象之间的分类继承关系和组成关系,统称为关联关系。对象之间的静态关系是通过对象属性之间的链接反应的,称为实例链接。对象行为之间的动态关系是通过对象行为之间的依赖关系表现的,称为消息链接,实例链接和消息链接统称为连接。

分类关系及其表示
C++中的分类结构是继承(基类、派生类)结构,UML使用一个空三角形表示继承关系,三角形指向基类。

对象组成关系及其表示

组成关系说明的结构是整体与部分关系。C++中最简单的是包含关系。C++语言中的“聚合”隐含了两种实现方式,第一种方式是独立定义的,可以属于多个整体对象,并鞠永不同生存期。这种所属关系是可以动态变化的,称之为聚集。使用空心菱形表示他们之间的关系。第二种方式是用一个类的对象作为一种广义的数据类型来定义整体对象的一个属性,构成一个嵌套对象。在这种情况下,这个类的对象只能隶属于唯一的整体对象并与它同生同灭,称这种情况为组合,他们之间的关联关系比第一种强,具有管理组成部分的责任,使用实心菱形表示。

实例连接及其表示

实例练级反映对象之间的静态关系,例如车和驾驶员的关系,这种双边关系在实现中可以通过对象的数学表达出来。实例连接有一对一,一对多,多对多三种连接方式。

消息链接及其表示

消息链接描述对象之间的动态关系。即若一个对象在执行自己的操作时,需要通过消息请求另一个对象为它完成某种服务,则说一个对象与第二个对象之间存在着消息链接。消息链接时有方向的,即使用一条带箭头的实线表示,从消息的发送者执行消息的接收者。

使用实例

对象,类和消息

对象的属性时指描述对象的数据成员。数据成员可以是系统活程序员定义的数据类型。对象属性的集合称为对象的状态。

对象的行为是定义在对象属性上的一组操作的集合。操作是响应消息而完成的算法,表示对象内部实现的细节。对象的操作集合体现了对象的行为能力。

对象的属性和行为是对象定义的组成要素,分别带比例对象的静态和动态特征。由以上分析的例子可见,无论对象是简单的或是复杂的,一般具有以下特征:

(1) 有一个状态,由与其相关的属性集合所表征
(2)有唯一标识名,可以区别与其他对象
(3)有一组操作方法,每个操作决定对象的一种行为
(4)对象的状态只能被自己的行为所改变
(5)对象的操作包括自身操作和施加于其他对象的操作
(6)对象之间以消息传递的方式进行通信
(7)一个对象的成员仍可以是一个对象

面向对象编程的文件规范

一般要求将类的声明放在头文件中,非常简单的成员函数可以在声明中定义,实现放在.cpp文件中。在.cpp文件中,将头文件包含进去。主程序单独使用一个文件,这就是多文件编程规范。

编译指令
C++的源程序可包含各种编译指令,以指示编译器对源代码进行编译之前先对其进行预处理。所有的编译之类都以#开始,每条编译指令单独占用一行,同一行不能有其他编译之类和C++语句。编译之类不是C++的一部分,但扩展C++编程环境的使用范围,从而改善程序的组织和管理。

嵌入之类
嵌入指令#include 只是编译器将一个源文件嵌入到带有#include指令的源文件中指令放在的位置处。尖括号或双引号中的文件名可包含路径信息。例如:

#include<\user\prog.h>

注意:由于编译指令不是C++的一部分,因此,在这里标识反斜杠时只使用一个反斜杠。

宏定义

#define 指令定义一个标识符及串,在源程序中每次遇到该标识符时,编译器均用定义的串代替之。该标识符称为宏名,而将替换过程称之为宏替换。#define指令用以进行宏定义,其一般形式如下:

#define 宏名 替换正文

“宏名”必须是一个有效的C++标识符,“替换正文”可为任意字符组成的字符序列。“宏名”和“替换正文”之间至少有一个空格。注意,宏定义由新行结束,而不已分号结束。如果给出了分号,则它也被视作替换正文的一部分。当替换正文要书写在多行上时,除最后一行外,每行的行尾要加上一个反斜杠,标识宏定义继续到下一行。

因宏定义有许多不安全因素,对需要使用无参数宏的场合,应该尽量使用const代替宏定义。

在程序的一个地方定义的宏名,如果不想使其影响到程序的其他地方,可以在不使用时用#undef删除。

条件编译指令

条边编译指令是#if 、#elif 和#endif,它们构成类似于C++的if选择结构,其中#endif 表示一条指令结束。

编译指令#if用于控制编译器对源程序的某部分有选择地进行编译。该部分从#if开始,到#endif结束。如果#if后的常量表达式为真,则编译这部分,否则就不编译该部分,这时,这部分代码相当于被从源文件中删除。

编译指令#else 在#if 测试失效的情况下建立另外一个选择,可以在#else分支钟使用编译指令#error输出错误信息。#error 使用的形式如下:

#error 出错信息

“出差信息” 是一个字符序列。当遇到“#error” 指令时,编译器显示其后面的“出错信息”,并中止对程序的编译。

编译指令可嵌套,签到规则和编译器对其处理方式与C++的if 预处理嵌套情况类似。

#define 操作符

关键字 defined不是指令,而是一个预处理操作符,用于判断一个标识符是否已经被#define定义。如果标识符identifier 已经被#define 定义,则define(identifier)为真。否则为假。

条件编译指令#ifdef 和#ifndef 用于测试其后的标识符是否被#define定义,如果已经被定义,则#ifdef测试为真,#ifndef测试为假;如果没有被定义,则#ifdef 测试为假。#ifndef 测试为真。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值