类与对象

更新时间:2023-10-15 15:25:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

课程名称:.面向对象程序设计 信息学院计算机专业课教案

第一章 类与对象

? 本章主要目标

? 类的定义:数据成员和成员函数的概念,类成员的访问权限,类的成员函数的实现,类的

对象的定义与引用。 ? 构造函数与析构函数概念 ? 类的组合概念 ? 对象指针和this指针 ? 常对象与常成员的概念 ? 多文件结构

? 本章重点

? 类的定义

? 构造函数与析构函数概念 ? 类的组合概念 ? 对象指针和this指针

1.1 类的定义

在C++语言中面向对象程序设计占据了核心地位,而类是面向对象程序设计的基础。它将一组数据和对这组数据进行的相关操作(称为方法或成员函数)组合在一起,实现了面向对象中的重要概念---数据封装。而对象是用类类型定义的变量,也称为实例或对象。

1.1.1 结构体与类

在C语言中,有时需要将一些不同类型而关系密切的数据组合成一个有机整体,这就是用户自定义的结构体类型。

例如:定义一个与学生成绩有关的学生类型 struct Student {

int num;

char name[10]; float score; };

将上面struct 改为class,就成为了类定义。虽然两者在形式上相似,但是其内容有了很大的不同,首先类定义中限制了数据成员的访问权限,即在类定义中的数据成员,不再像结构体中的数据成员随意被访问:

class Student

- -

1

课程名称:.面向对象程序设计 信息学院计算机专业课教案

{

int num;

char name[10]; float score; };

其中,与结构体定义不同的是,类中数据成员的权限默认是私有的(private),它们只能够被成员函数访问,因此需要加入访问数据成员的公有(public)成员函数:

class Student {

private: int num;

char name[10]; float score; public:

void input(int,char*,float); //类的公有成员函数 };

其中,第一部分(private)为私有段,它们只能够由成员函数访问。第二部分(public)为公有段,可以由类对象进行访问。

注意:结构体(struct)是一种特殊的类类型。结构体不加访问权限控制符(private,protected,public)说明,默认成员是公有成员,可以直接访问。

1.1.2 类定义

在C++类中可以有两种成员:数据和函数,称为数据成员和成员函数。根据它们被访问的权限,可以分为私有(private)成员、保护(protected)成员和公有(public)成员。类也是一种用户自定义类型,它的一般格式为:

class 类名 {

private: //定义私有段成员 私有数据定义; 私有函数定义;

protected: //定义保护段成员 保护数据定义; 保护函数定义;

public: //定义公有段成员

公有数据定义;

公有函数定义; };

其中,class是定义类的关键字,类名是一个标识符,代表类类型的类型名,用来唯一标识一个类。一对大括号内是类内定义部分,类的成员包括数据成员和成员函数两部分。private (私有的)、protected(保护的)和public(公有的)称为访问权限段控制符,其中private可以省略,即没有段控制符的成员默认私有成员。例如:

class Student {

int num;

- -

2

课程名称:.面向对象程序设计 信息学院计算机专业课教案

char name[10]; float score; public:

void input(int,char*,float); };

1.1.3 类成员的访问权限

类成员中包括数据成员和成员函数,数据成员用来描述问题的属性,而成员函数是反映数据进行的操作,它们是不可分割的两个方面。从访问权限上看两种成员都可以为私有的(private)、保护的(protected)和公有的(public)三种类型。

? 公有成员不仅可以由该类的成员函数访问,还可以在类外的程序中通过对象来访问。公有成员

提供了类与外界程序的接口功能。通常将类中的成员函数全部或大部分定义为公有的。 ? 私有成员只能通过该类的成员函数或友元函数访问(见第六章),在类外的程序中不能通过对象

来访问,通常将类中的数据成员全部或大部分定义为私有的,在类外是通过对象调用公有成员函数对私有成员访问的。

? 保护成员的性质与私有成员性质类似,只是在继承过程的派生类中产生影响。

? 数据封装是一个相对的概念,对类外而言,私有成员和保护成员都是被封装的,不可见的。而

在类内,所有成员都是互相可见,成员函数可以访问任何一种数据成员。另外,访问权限段控制符private,protected,public在类体内出现的顺序和次数是不受限制的。

1.1.1 类的成员函数

一个类的成员函数的实现,可以放在类内完成,称这个函数为内联函数。也可以放在类外实现,但这个成员函数在类内必须用原型说明。函数定义在类外实现,称这个函数为外联函数。 例如,成员函数为内联函数的类定义:

class CDate {

int year,month,day; public:

void setDate(int y,int m,int d) {

year=y;

month=m; day=d; }

void Display() {

cout<

与第三章介绍的普通内联函数相同,内联成员函数的函数体也会在编译时插入到每一个调用它的程序处,这样可以减少调用开销,提高执行效率,但却增加了目标代码长度。因此,一般将代码短,程序简单的成员函数才声明为内联函数。在类中不允许对所定义的数据成员进行初始化。因此,数据成员的初始化和赋值操作一般常用成员函数来实现。Cdate类中的成员函数setDate() 和Display()就是用来实现对封装的私有数据成员year,month,day进行赋值和显示操作。 定义类外成员函数的格式:

- -

3

课程名称:.面向对象程序设计 信息学院计算机专业课教案

返回值类型 类名::成员函数名(参数表) {

函数体 }

其中,作用域限定符::是用来标识某个成员函数是属于哪个类的,函数的原型说明要写在类内,原型说明了函数的参数类型和返回值类型,而函数的具体实现是写在类外的。将上例成员函数声明为外联函数:

class CDate {

int year,month,day; public:

void setDate(int y,int m,int d); // 成员函数的原型说明 void Display(); // 成员函数的原型说明 };

// 成员函数实现部分

void CDate:: setDate(int y,int m,int d) {

year=y;

month=m; day=d; }

void CDate:: Display() {

cout<

1.1.5 类的对象

为了使用类,还必须说明类的对象。类是一种抽象的数据类型,就像在C语言中学过的基本数据类型和自定义类型,如果将类也看作一种自定义数据类型,那么类的对象就可以看成是该类型的一个变量或一个实例。同一般自定义类型一样,一个类可以定义多个对象,每个对象都包含类中定义的各个数据成员的存储空间。在定义类时,系统并不给类分配存储空间,只有定义对象时才会给对象的各个数据成员分配相应的存储空间。而类中定义的成员函数是所有对象共享的。 1.对象的定义

定义类对象的格式如下:

<类名> <对象名表>

其中,<类名>是对象所属类的名字,<对象名表>中可以有一个或多个对象名,多个对象名用逗号分隔。在<对象名表>中,可以是一般对象名,还可以是指向对象的指针名或引用名,也可以是对象数组。

例如:Student st1,st2,*stp,&rst=st1,stu[5];

其中,st1和st2是类Student的对象名,stp是指向Student类对象的指针名,rst是对象st1的引用名,stu是对象数组名,该数组的每个元素都是一个Student类的对象。 2.对象成员的表示

一个对象的成员就是该对象的类所定义的成员,对象成员有数据成员和函数成员两种。与C语言中结构体变量的成员表示相同,用成员运算符.和指向成员运算符-〉表示。格式如下:

- -

4

课程名称:.面向对象程序设计 信息学院计算机专业课教案

<对象名>·<公有数据成员名>

<对象名>·<公有函数成员名>(<参数名>) 指向对象的指针的成员表示如下:

<对象指针名>-〉<公有数据成员名>

<对象指针名>-〉<公有函数成员名>(<参数名>)

3.程序举例

【例1.1】完整的日期类程序 #include class CDate {

int year,month,day; public:

void setDate(int y=0,int m=0,int d=0); void Display()

{

cout<

};

void CDate:: setDate(int y,int m,int d) {

year=y;

month=m; day=d; }

void main() {

Cdate d1,d2; d1.setDate(); d1.Display();

d2.setDate(2005,9,1); d2.Display(); }

执行程序后,输出结果如下: 0,0, 0 2005,9,1

【例1.2】完整的学生类程序

#include #include class Student {

int num;

char name[10]; float score;

- -

5

课程名称:.面向对象程序设计 信息学院计算机专业课教案

return y;

} private: int x,y; };

void main () {

A a(1,2);

A b(a); // 调用拷贝构造函数,等价b(11,12)

cout<<\ }

从【例1.3】中可以看出默认拷贝构造函数可以实现将原对象的数据成员逐一赋给新对象中对应的数据成员。当然这种情况下可以不必要编写拷贝构造函数。但是在进行对象复制时,需要有选择、有变化的复制数据,如【例1.4】中改变新对象的数据成员值。另外,当类的数据成员中有指针类型时,默认拷贝构造函数就不能正确实现数据拷贝。这时就必须编写正确的拷贝构造函数。关于进一步讨论拷贝构造函数问题将在本章第六节继续。

1.2.3 析构函数

与构造函数相对应的是析构函数。一个类可以在构造函数里分配资源。例如动态申请一些内存单元。这样在创建对象时调用构造函数就实现了对象的动态内存分配,当对象的生命期结束时,如函数结束,就要释放动态分配的资源。系统可以自动调用析构函数完成这些清理工作。可以这样说,析构函数的作用与构造函数正好相反。它用来完成对象被删除前的一些内存释放工作。 例如,定义一个析构函数如下: class AA

{

public:

AA() {

name=new char[10]; }

~AA()

{

delete []name; } protected:

char *name; int num;

};

用AA类创建对象时,其构造函数动态分配了10个字节的堆内存。将首地址赋给数据成员name指针。在对象撤消时,就必须归还这一堆内存资源。这一工作是由析构函数完成的。

析构函数也是一个特殊的成员函数。与构造函数相反,当一个对象被撤消时,系统会自动调用析构函数。析构函数有如下特点:

(1) 析构函数是一个特殊的成员函数,析构函数名由类名和波纹号(~)组成。它没有参数并且不指定返回值类型。

- -

11

课程名称:.面向对象程序设计 信息学院计算机专业课教案

(2) 析构函数的作用是进行清除时,释放内存。当对象超出其作用域范围或被释放时,系统会自动调用析构函数。

(3) 每个类只有一个析构函数,即析构函数不能被重载。 (4) 析构函数也是一个可选的成员函数,当类定义中没有析构函数时,系统会自动给这个类提供一个默认的、不做任何事的析构函数。

例如:给CDate类加入一个内联的空析构函数。 class CDate {

public:

CDate(int y=0,int m=0,int d=0);

void setDate(int y,int m,int d); void display(); ~CDate() { } private:

int year,month,day; };

由于没有动态内存分配,析构函数可以是空的,不做任何事情。在类定义中也可以没有析构函数。由系统提供一个空的、不做任何事情的析构函数。析构函数的调用顺序与构造函数调用顺序相反,即先创建的对象后释放,而后创建的对象会先被释放。

【例1.4】构造函数与析构函数的调用顺序 #include #include class student {

public: student() { cout<<\ shours=60; gpa=6; }

~student() { cout<<\ } private:

int shours; int gpa; };

class teacher {

public: teacher() {

- -

12

课程名称:.面向对象程序设计 信息学院计算机专业课教案

cout<<\ }

~teacher() { cout<<\ } };

void main() { student stu1; teacher tea1;

cout<<\}

程序执行结果: constructing student. constructing teacher . end in main

destructing teacher. destructing student.

其中,可以看出析构函数的调用顺序与构造函数调用顺序相反,先创建的student的对象stu1后释放,而后创建的teacher的对象tea1会先被释放。

1.3对象数组

数组元素可以是基本数据类型,也可以是自定义类型。对象数组是指数组元素是某个类的对象的数组。即数组中的元素都是同一个类的对象。每个元素是一个对象,它既有数据成员又有成员函数。创建数组元素对象时,就要多次调用构造函数,释放对象数组也要多次调用析构函数。这些与基本类型数组相比,会有一些不同之处。 声明一维对象数组的格式如下:

<类名><数组名>[<下标表达式>];

数组的每个元素都是对象,引用一个元素就是引用一个对象,通过对象访问其公有成员来实现对象的操作。访问公有成员形式如下:

<数组名>[<下标>].<公有数据成员名>

<数组名>[<下标>].<公有成员函数名>(<参数表>)

对象数组的元素也可以赋初值。对象数组在定义时可以用初始值表进行初始化。例如:

class AA {

public:

AA(int i, int j) {

a=i, b=j; }

AA()

- -

13

课程名称:.面向对象程序设计 信息学院计算机专业课教案

{

a=0,b=0

}

... private:

int a, b; };

AA a[3]={AA(1,2), AA(3,4), AA(5,6)};

在定义数组a时就用初始值表对它的3个元素进行初始化,也就是3次调用形参类型匹配的构造函数。

例如:AA b[3]={AA(1,2)};

定义数组b时,首先调用AA类中带形参的构造函数,初始化b[0],然后两次调用不带参数的构造函数,初始化b[1]和b[2]。

再例如:AA c[3];

定义数组c时,三次调用AA类中不带参数的构造函数,分别将c[0],c[1],c[2]的数据成员a,b初始化为0。所以在定义对象数组时,对类中的构造函数有不同的要求。更常用的定义方式是像c数组,不带有初始值表。这样就要求类中有一个不带参数的构造函数;或者带参数每个参数有默认值的构造函数;或者类中没有构造函数,由系统提供默认构造函数。 【例1.5】对象数组举例

class point {

private:

void init(int x1,int y1) {

x=x1; y=y1; } public:

point() {

init(0,0); }

point(int x1,int y1) {

x=x1; y=y1; }

int x_cord()

{

return x; }

int y_cord()

{

- -

14

课程名称:.面向对象程序设计 信息学院计算机专业课教案

return y;

}

private:

int x,y; };

void main() {

point data1(4,5); point data2[5];

cout<<\ The y _coordinate =\

cout<<\ The x _coordinate of index 2=\ }

执行程序后输出结果如下: The y_corolinate=5

The x_corolinate of index2=0

其中,对象date1和对象数组data2是调用不同的构造函数进行创建的。

point data1(4,5);// 调用带参数的构造函数。 point data2[5]; // 调用不带参数的构造函数。 若将类定义修改为:

#include class point {

public:

void init(int x1,int y1) {

x=x1; y=y1; }

int x_cord() {

return x; }

int y_cord() {

return y; } private:

int x,y; };

void main() {

point data[3];

for(int i=0;i<3;i++) {

data[i].init(i, i+1);

- -

15

课程名称:.面向对象程序设计 信息学院计算机专业课教案

cout<<\ cout<<\ data[\ } }

执行程序后输出结果如下 data[0].x=0 data[0].y=1 data[1].x=1 data[1].y=2 data[2].x=2 data[2].y=3

修改后的类中没有定义构造函数,创建对象数组data时是调用系统提供的默认构造函数,每个元素的数据成员是通过调用init()成员函数完成赋值操作。

在定义对象数组时就用初始值表对它的元素进行初始化,并不是常用方式。所以,定义对象数组时,应注意两点:

1.要求类中有一个不带参数的构造函数;或者带参数每个参数有默认值的构造函数。 2.或者类中没有构造函数,由系统提供默认构造函数。

1.4类的组合

在面向对象程序设计中,通常一个复杂的对象可以化解为若干简单对象的集合。C++中一个类的对象也可以作为另一个类的数据成员,称这种数据成员为对象成员,或称为子对象。这种类的关系也称为类的组合。

在定义一个类时,其数据成员既可以是基本的数据类型,也可以是自定义类型,类也可以看作一种自定义类型。这样,我们可以利用已定义好的类的对象,把它作为新类的成员,来创建新类,从而用若干结构简单的类来构造复杂的类。这种类的组合方法简化了问题,并且提高了软件的开发效率,也是软件复用的一种形式。

【例1.6】 有一个日期类(date)和一个人员类(person),出生日期是每个人员的属性,也是日期类的对象。

#include #include class date {

public:

date(int y, int m,int d) {

day=d; month=m; year=y; }

int getd()

{

return day; }

int getm()

{

return month;

- -

16

课程名称:.面向对象程序设计 信息学院计算机专业课教案

} int gety() {

return year; } private:

int day, month, year; };

class person { public:

person(char *na, int a, int y, int m, int d): birthday(y,m,d) {

strcpy(name,na); age=a; }

void display() {

cout<<\姓名:\ cout<<\年龄:\

cout<<\出生日期:\ <<\ } private:

char name[10]; int age;

date birthday ; };

void main() {

person p1(\张林\ p1.display(); }

程序执行结果: 姓名:张林 年龄:21

出生日期:1984,3,10

在组合类中,当创建对象时,若这个类中具有内嵌的对象成员,那么各个内嵌对象应该首先被创建,因为内部对象是我们要创建的对象的一部分。因此,构造函数既要初始化本类的基本数据成员,也要初始化内嵌对象成员,这时,理解构造函数初始化的顺序是很重要的。 组合类构造函数的一般形式为: 类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),… { 类的初始化程序体 }

- -

17

课程名称:.面向对象程序设计 信息学院计算机专业课教案

其中,内嵌对象1(形参表),内嵌对象2(形参表),?称为初始化列表,其作用是对内嵌对像进行初始化。

在创建一个组合类的对象时,不仅调用它自身的构造函数,还要通过初始化表调用内嵌对象的构造函数。如【例1.6】的构造函数:

//先执行初始化表birthday(y,m,d),后执行构造函数体。 person(char *na, int a, int d, int m, int y): birthday(y,m,d) {

strcpy(name,na); age=a; }

创建对象时,构造函数的调用顺序:

(1) 通过初始化列表调用内嵌对象的构造函数,调用顺序按照内嵌对象在类中的声明顺序。 (2) 执行本类的构造函数的函数体,对本类的基本类型数据成员初始化。

如果组合类构造函数不带参数,则内嵌对象的类的构造函数也应该是无参数形式,这种情况组合类就没有初始化表。在创建组合类对象时,内嵌对象的构造函数也被调用。 【例1.7】组合类构造函数无参数表 #include class A {

public: A() {

a1=a2=10; } void init(int x,int y) { a1=x;a2=y; } void print() { cout<

int a1,a2; };

class B {

public: B() {

b=10; } void init(int x,int y,int z) {

- -

18

课程名称:.面向对象程序设计 信息学院计算机专业课教案

a.A::init(x,y); b=z; } void print() { a.A::print(); cout<<<\ } private:

A a; int b; };

void main() { B obj; obj.print(); obj.init(3,5,7); obj.print(); }

程序执行结果: 10,10,10 3,5,7

分析:主函数创建对象obj,调用B类的构造函数,首先调用obj的内嵌对象a的构造函数执行a1=a2=10,然后执行B类构造函数体b=10。因此,第一次执行obj.print( ),输出的是构造函数赋的初值,即执行a.A::print( ),输出内嵌对象obj.a的成员a1,a2的初值10,然后输出obj.b的初值10。接着执行obj.init(3,5,7),重新给obj.a的成员a1,a2赋值3,5给obj.b赋值为7,第二次执行obj.print( ),输出重新赋值的结果。

1.5 对象指针和this指针

对象指针有两种用法,其一是用它指向一个已创建的对象,然后通过对象指针来访问对象成员。其二,也可以通过对象指针来动态创建对象。而this指针是隐含于每一个类的成员函数中的特殊指针。下面就来讲解对象指针和this指针。

1.5.1 指向对象的指针

每一个对象被创建后,都会分配一定的内存空间。对象的内存空间是用于存放成员的,而函数成员是所有对象共用的。就像可以通过变量指针来访问变量一样,也可以通过对象指针来访问对象。指向对象的指针变量定义格式为:

〈类名〉*〈对象指针名〉

对象指针是用于存放对象地址的变量。定义对象指针时,因为没有创建对象,所以不会调用类的构造函数。对象指针可以定义时被赋初值,也可以定义后再赋值,通常用一个已存在对象的地址值给对象指针赋初值或赋值。 例如: class X

- -

19

课程名称:.面向对象程序设计 信息学院计算机专业课教案

{

… };

X a1; // 定义对象a1 X *pa; // 定义对象指针pa

pa=&a1; // 对象指针pa获得对象a1的地址

这里a1是类X的对象,pa是类X的对象指针,将类X的对象a1的地址&a1赋给pa,则pa指向对象a1。

使用对象指针可以方便地访问对象的成员,其方法同结构变量的指针是相似的,其语法形式为:

〈对象指针名〉—>〈成员名〉

【例1.8】使用对象指针访问cdate类对象数组。 class cdate {

public:

cdate() {

year=2000; month=1; day=1; }

void setdate(int y,int m,int d) {

year=y; month=m; day=d; }

void display() {

cout<

int year,month,day; };

void main() {

cdate ob-day[3],*p; ob-day ob-day[0].setdate(2003,10,1); p->ob-day[0] year=2003 ob-day[1].setdate(2005,10,2); month=10 ob-day[2].setdate(2005,10,3); day=1 p=&ob-day[0]; ob-day[1] year=2004 p->display(); month=10 p++; day=2 p->display(); ob-day[2] year=2005 p++; month=10 p->display(); day=3 }

程序执行结果: 图1.1指向对象数据的指针

- -

20

本文来源:https://www.bwwdw.com/article/lqhf.html

Top