青岛理工大学实验5 类和对象的应用

实验5 类和对象的应用

这个很重要想要改行JAVA的注意了要理解好类和对象,到后期做项目它可能就变成了1+1的事了

一、实验目的

  1. 掌握类定义的基本格式和类成员的访问控制。
  2. 掌握对象定义和利用构造函数对对象的数据成员进行初始化的方法。
  3. 掌握成员函数的定义与使用。
  4. 理解并掌握静态数据成员和静态成员函数的使用方法。
  5. 理解友元函数的使用方法。

二、知识要点

1. 类的定义

在面向对象程序设计中,类(class)就是对现实世界中抽象出的“类”的模拟描述,是用户自己定义的数据类型,它的一般定义格式如下:

class <类名>

{

     private:

        <私有数据成员和成员函数>;

     protected:

         <保护数据成员和成员函数>;

     public:

        <公有数据成员和成员函数>;

};

<类中各个成员函数的实现>

说明:protected访问控制符主要用在类的继承中,后续课继承中会讲到

  1.  类成员的访问控制

类的成员访问权限总体上可分为:公有的(public)、私有的(private)和保护的(protected)三类。若某个成员未作任何访问权限声明,则默认为私有的。

公有的成员用public来说明,这部分通常是一些操作(即成员函数),作为类与外界的接口,所有公有的成员可以在程序中的任何位置被访问。

私有的成员用private来说明,这部分通常是一些数据成员,这些成员用来描叙该类中对象的属性的,只有成员函数或经过特殊说明的函数(如友元函数)才可以引用它们,它们是特意被用户隐藏起来的部分,用户在类外其他地方是无法访问它们的。

保护的成员用protected来说明,它的限定能力介于私有和公有之间,除了类本身的成员函数、友元函数可以访问成员外,只有该类的派生类(子类)可以访问。

关键字public、private和protected被统称为访问权限修饰符或访问控制修饰符。它们在类体(即一对花括号内)出现的先后顺序没有要求,并且允许多次出现。

  1.  对象的定义

C++规定:必须先定义类,然后定义对象,用类来定义对象在格式上与普通类型定义变量是完全相同的.

定义对象的一般形式:

    <类名> <对象名表>;

  1. 对象成员的访问方式

对象成员访问的一般形式是:

    <对象名>.<数据成员名>

    或者

    <对象名>.<成员函数名>([<实参表>])

  1. 构造函数的定义和分类

构造函数是类的一种特殊的成员函数,它的主要作用于是为对象分配空间和进行初始化工作。除了具有一般成员函数的特征外,还具有以下一些特殊的性质:

(1) 构造函数的名字必须与类名字相同;

(2) 构造函数可以有任意类型和任意个数的参数,所以构造函数可以重载,但不能指定返回值类型;

(3) 构造函数的函数体可以写在类体内,也可以写在类体外;

(4)  构造函数被声明为公有函数,但它不能像其他成员函数那样被显示的调用,而是在用类声明对象的同时被系统自动调用。

调用构造函数的一般形式是:

    类名 对象名(参数表);

    构造函数分为4类,分别是普通构造函数、默认构造函数、有缺省参数的构造函数和复制(拷贝)构造函数。

  1. 默认构造函数

默认构造函数是指没有任何参数的构造函数。如果在设计类时没有定义构造函数,C++编译程序会自动为该类建立一个默认的构造函数。这个默认构造函数没有任何形式参数,并且函数体为空。其格式如下:

<类名>::<默认构造函数名>()  {  }

按构造函数规定,默认构造函数名与类名相同。默认构造函数也可以由程序员直接定义在类体中。另外,如果构造函数的参数具有默认值时,这样的构造函数被称为有缺省参数的构造函数。

  1. 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,用于依据已存在的对象建立一个新对象。其一般形式为:

class T

{

 public:

     T(const  T & 对象名);

……

}

T::T(const  T &对象名 )

{ 函数体 }

其中,T代表任何一个类的名字。const是一个类型修饰符,被它修饰的对象是不能被改变的常量.

拷贝构造函数的拷贝分为浅拷贝和深拷贝。一般来说,只需浅拷贝时最好利用系统自动生成的拷贝函数,这样效率高。若需要在构造函数中开辟新的内存空间,则需要我们自己编写这样的构造函数以完成深拷贝。

拷贝构造函数主要在下面3种情况中起到初始化作用。

(1) 声明语句中用一个对象初始化另外一个对象,例如:

Person student2 (student1);

(2) 函数的参数是值参数时,若用对象作为函数实参传递给函数形参,这时需要调用拷贝构造函数。

(3) 当对象作为函数返回值时,如执行return R 时,系统将用对象R来初始化一个匿名对象,这时需要调用拷贝构造函数。

  1. 析构函数

析构函数是类的一种特殊成员函数,其功能是用来释放一个对象的内存空间。它的功能与构造函数正好相反.

析构函数的特点如下:

(1)析构函数是成员函数,函数体可以写在类体内,也可以写在类体外;

(2)析构函数是一个特殊的函数,它的名字是在类名的前面加“~”字符;

(3)析构函数没有参数,没有返回值,所以不能重载。

在下面两种情况下,析构函数会被自动调用:

A.当一个对象的作用域结束时,该对象的析构函数被自动调用。

B.当一个对象是使用new运算符被动态创建的,那么在使用delete运算符释放它时,delete将会自动调用析构函数。

  1. 重载成员函数

构造函数可以重载,而析构函数不能重载,原因是构造函数可以有参数,析构函数不带任何参数,所以无法重载。可以说,带有不同类型和数量参数的成员函数都可以进行重载。

  1. 子对象

将一个类的对象作为另一个类的成员,该对象就被称为子对象。

在一个类中出现了子对象时,该类的构造函数就要考虑子对象的初始化问题。在C++中,通常采用初始化列表的方法来初始化子对象。在初始化列表中可以包括对子对象的初始化及对类中其他成员的初始化。

  1. 指向对象的指针

每个对象在声明后都会在内存中占有一定的空间,就会有地址。因此,可以用指针变量来保存这个地址。指向对象的指针(变量)就是用于存放对象地址的变量。声明对象指针的一般形式为:

    <类名>*<对象指针名>;

声明对象指针的语法和声明其他数据类型指针的语法相同。使用对象指针时,首先要把它指向一个已创建的对象,然后才能以“间接方式”访问该对象。

  1. this指针

this指针是一个隐含于每个类的成员函数中的特殊指针。该指针是一个指向正在操作的成员函数的对象。当一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数。

  1. 对象数组

对象数组是指将对象作为数组元素的数组。该数组中若干个元素必须是同一个类的若干个对象。对象数组的定义、赋值和引用与普通数组一样,只是数组的元素与普通数组不同,它是具有相同类的若干个对象。

定义一个对象数组的一般形式是:

<类名><数组名>[<数组长度>];

定义二维对象数组的一般形式是:

<类名><数组名>[<第一维数组长度>][<第二维数组长度>];

  1. 常对象

如果在说明对象时用const修饰,则被说明的对象为常对象。常对象的数据成员值在对象的整个生命期内不能改变,常对象的声明形式如下:

<类名>const<对象名>[(初值)];    或者    

const<类名><对象名>[(初值)];

在定义常对象时必须进行初始化,而且常对象不能被更新。

  1. 常数据成员

在类中使用关键字const来说明某个数据成员为常数据成员。如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该数据成员赋值。

类的常数据成员即可以是常量也可以是常引用,由于必须初始化,因此,类中这些常数据成员必须也只能通过构造函数的成员初始化列表来实现初始化工作。

  1. 常成员函数

在类中使用关键字const说明的函数称为常成员函数,它的一般说明形式是:

<类型><成员函数名><([参数表])>const;

const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。

关于常成员函数的说明:

(1)常成员函数不能调用该类中的普通成员函数,因而也不能更新对象的数据成员;

(2)如果将一个对象设为常对象,则该对象只能调用它的常成员函数,而不能调用普通的成员函数,这是C++在安全机制上的考虑。

  1. 常类型的函数参数传递

将形参设置为const引用形参或const地址(指针)形参,这样就可以保障安全快捷的传递对象了。将函数形参设为const型引用和指针的一般形式是:

const<类型说明符>&<引用名>

const<类型说明符>*<指针变量名>

18.静态数据成员

C++中同一个类定义多个对象时,每个对象都拥有各自的成员,而静态数据成员是类的所有对象中共享的成员,它不因对象的建立而产生,也不因对象的消失而删除,它是类定义的一部分,属于整个类,即属于所有对象。

由于静态数据成员不专属于任何一个具体对象,但任何一个对象在声明前都需要它提前拥有一个值,因此C++规定:必须对类的静态数据成员初始化,并且它的初始化不能在构造函数中进行。

静态数据成员初始化的方法一般采用如下形式:

<类型><类名>::<静态数据成员>=<值>;

关于静态数据成员初始化的进一步说明:

(1) 初始化在类体外进行,其前面不加static;

(2) 初始化时不加该成员的访问权限控制符private、public或protected;

(3) 即使静态数据成员是私有的,也可以在类外有文件作用域的地方直接初始化,一般在类的定义之后马上初始化。

在引用公有的静态数据成员时采用下面的形式:<类名>::<静态数据成员>

19.静态成员函数

静态成员函数的定义和其他成员函数相同。但它的特点与静态数据成员类似,不专属于任何一个对象,为整个类所共享。静态成员函数的定义方法是在一般成员函数的定义前面加上static关键字。

关于静态成员函数的说明如下:

(1) 调用静态成员函数的格式一般采用如下形式:

<类名>::<静态成员函数名>(<参数表>);

(2) 静态成员函数只能访问静态数据成员、其他静态成员函数和类以外的函数与数据,不能访问类中的非静态数据成员,因为非静态数据成员只有对象存在时才有意义。

(3) 静态成员函数不能声明为虚函数。

20.友元

友元是一种定义在类外部的普通函数或类,但它需要在类体内声明为“朋友”。友元的作用在于提高程序的运行效率,但是从某种程度上讲,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员,因此在程序设计时应该严格限制使用。

友元可以是一个函数,称之为友元函数,它也可以是一个类,该类被称为友元类。下面分别介绍这两种友元。

(1) 友元函数

为了与该类的成员函数加以区别,在类内说明友元函数时需要在前面加上关键字friend。需要注意的是友元函数不是成员函数,但是它可以访问类中的私有成员。

定义友元函数的方式是在类定义中用关键字friend说明该函数,其格式如下:

friend<类型><友元函数名>([<参数表>]);

说明:友元函数说明的位置可以在类中的任何位置,意义完全一样。友元函数定义则必须在类的外部,一般与类的成员函数定义在一起。声明类的友元函数的目的就是为普通函数提供直接方便的访问该类的所有成员的权限。

(2) 友元类

友元也可以是一个类,即将一个类作为另一个类的友元。当一个类作为另一个类的友元时,意味着这个类的所有成员函数都是另一个类的友元函数。

三、实验内容和步骤

【实例1】定义一个关于日期的类,然后声明对象,判断该日期是否为闰年并输出。

#include <iostream>

using namespace std;

class TDate

{

public :

void SetDate(int y,int m,int d);

int isLeapYear();

void Print();

private:

int year,month,day;

};

void TDate::SetDate(int y,int m,int d)

{

year = y;

month = m;

day = d;

}

int TDate::isLeapYear()

{

return (year%4==0&&year%100!=0) || (year%400==0);

}

void TDate::Print()

{

cout<<year<<"."<<month<<"."<<day<<endl;

}

void main()

{

TDate date1,date2;

date1.SetDate(2004,5,4);

date2.SetDate(2005,4,9);

int leap = date1.isLeapYear();

date1.Print();

if (leap == 1)

cout<<"是闰年!"<<endl;

else

cout<<"不是闰年!"<<endl;

date2.Print();

}

注意:

(1)类定义的末尾有一个分号。

(2)成员函数常采用在类内声明,在类外定义的方法。

(3)体会类的定义方法以及类的成员函数的调用方法。

(4)采用多文件的方式来实现该程序。将类定义放在头文件中,将类的实现单独放在一个实现文件中,并将主程序单独放在一个实现文件中,如:

//Lab5_1.h

#include <iostream>

class TDate

{

public :

void SetDate(int y,int m,int d);

int isLeapYear();

void Print();

private:

int year,month,day;

};

//Lab5_1.cpp

#include"Lab6_1.h"

using namespace std;

void TDate::SetDate(int y,int m,int d)

{

year = y;

month = m;

day = d;

}

int TDate::isLeapYear()

{

return (year%4==0&&year%100!=0) || (year%400==0);

}

void TDate::Print()

{

cout<<year<<"."<<month<<"."<<day<<endl;

}

//main.cpp

#include"Lab8_1.h"

void main()

{

TDate date1,date2;

date1.SetDate(2004,5,4);

date2.SetDate(2005,4,9);

int leap = date1.isLeapYear();

date1.Print();

if (leap == 1)

cout<<"是闰年!"<<endl;

else

cout<<"不是闰年!"<<endl;

date2.Print();

}

【实例2】编写一个程序,定义一个日期时间类,并在类中定义构造函数和析构函数。

#include <iostream>

using namespace std;

class TDateTime

{

public:

TDateTime(int y,int m,int d,int h,int mi,int s);

~TDateTime();

void Print();

private:

int year,month,day,hour,minite,second;

};

TDateTime::TDateTime(int y,int m,int d,int h,int mi,int s)

{

year = y;

month = m;

day = d;

hour = h;

minite = mi;

second = s;

cout<<"Constructor called."<<endl;

}

TDateTime::~TDateTime()

{

cout<<"Destructor called."<<endl;

}

void TDateTime::Print()

{

cout<<year<<"/"<<month<<"/"<<day<<"/"<<hour<<":"<<minite<<":"<<second<<endl;

}

void main()

{

TDateTime Now(2007,10,31,9,30,35);

cout<<"Now is ";

Now.Print();

}

注意:

(1)构造函数和析构函数都没有返回值。

(2)构造函数可以有参数,析构函数不可以有参数。

(3)构造函数和析构函数都是系统自动调用的。

(4)为类TDateTime增加一个无参构造函数,并在主函数中进行测试。

(5)在(4)的基础上,将有参构造函数

TDateTime(int y,int m,int d,int h,int mi,int s);

改为有缺省参数的构造函数

TDateTime(int y=2008,int m=8,int d=8,int h=24,int mi=0,int s=0);

编译程序,看一下有什么错误提示信息?错误的原因是什么?

【实例3】拷贝构造函数的使用。

#include <iostream>

#include <string>

using namespace std;

class Person

{

public:

Person(char* pN);

Person(Person &p);

~Person();

private:

char * pName;

};

Person::Person(char *pN)

{

cout<<"构造函数:   "<<pN<<endl;

pName = new char[strlen(pN)+1];

strcpy(pName,pN);

}

Person::Person(Person &p)

{

cout<<"拷贝"<<p.pName<<"到新的堆空间\n";

pName = new char[strlen(p.pName)+1+8]; //8是字符串"Copy of "的长度

strcpy(pName,"Copy of ");

strcat(pName,p.pName);

}

Person::~Person()

{

cout<<"析构函数:  "<<pName<<endl;

delete pName;

}

void main()

{

Person p1("Jack");

Person p2(p1); //或者Person p2=p1;

}

【实例4】使用静态数据成员。

#include <iostream>

using namespace std;

class Static_Myclass    

{

public :

   static int a; //静态数据成员a

};

int Static_Myclass::a = 5; //初始化静态数据成员a

void main()

{

Static_Myclass obj1,obj2;

obj1.a = 10; //静态数据成员的引用方法一

Static_Myclass::a = 20; //静态数据成员的引用方法二

cout<<obj1.a<<endl;

cout<<obj2.a<<endl;

cout<<Static_Myclass::a<<endl;

}

本程序的运行结果为:

20

20

20

【实例5】使用静态成员函数。

#include <iostream>

using namespace std;

class Myclass

{

private :

static int b;

public :

int a;

static void fun()

{

//a=5; //静态成员函数不能直接使用类内的一般数据成员a

int a = 5;  //这是在该函数体内的局部变量

b = 7; //可直接使用类的静态数据成员b

cout<<"a="<<a<<" 类的静态数据成员 b="<<b<<endl;

}

};

int Myclass::b; //静态数据成员的初始化

void main()

{

Myclass::fun(); //不必通过对象操作静态成员函数即可使用

Myclass obj;

obj.a = 30;

obj.fun(); //通过对象操作静态成员函数

}

本程序的运行结果为:

a=5 类的静态数据成员 b=7

a=5 类的静态数据成员 b=7

【实例6】使用友元函数。

#include <iostream>

#include <math>

using namespace std;

class Point

{

public:

Point(double i,double j)

{

x = i;

y = j;

}

void Getxy()

{

cout<<"("<<x<<","<<y<<")"<<endl;

}

friend double Distance(Point &a,Point &b);

private:

double x,y;

};

double Distance(Point &a,Point &b)

{

double dx = a.x - b.x;

double dy = a.y - b.y;

return sqrt(dx*dx + dy * dy);

}

void main()

{

Point p1(3.0,4.0),p2(6.0,8.0);

p1.Getxy();

p2.Getxy();

double d = Distance(p1,p2);

cout<<"Distance:"<<d<<endl;

}

本程序的运行结果为:

(3,4)

(6,8)

Distance:5

四、思考与练习

1. 对于【实例1】,定义指向对象的指针,体会用指针来间接访问类的成员的方法。

2. 若类的构造函数中有new命令,则该类必须有析构函数吗?且在析构函数内使用delete命令吗?

3. 拷贝构造函数的应用场合是什么?

4. 在类的构造函数中可以对类的静态数据成员初始化吗?

5. 静态成员函数中可以直接使用类的所有的数据成员吗?

6. 使用友元的目的是什么?

7. 仿照【实例1】编写程序,设计一个学生类,完成输入/输出的功能,要求:

A.数据成员包括:学号、姓名、性别、成绩等;

B.成员函数要实现信息的录入和显示。

8. 仿照【实例2】改造7中的程序,要求:

A.增加构造函数和析构函数;

B.构造函数中除了需要完成正常的初始化外,还要用new为姓名数据成员开辟空间;

C.析构函数要释放己开辟的空间。

 

猜你喜欢

转载自blog.csdn.net/CSDNwbdream/article/details/82192015
今日推荐