第 7章 类与数据抽象
7.1 类 的 定 义
7.2 对 象 的 定 义
7.3 构造函数和析构函数
7.4 成员函数的特征
7.5 静 态 成 员
7.6 友 元
7.7 对象的指针和对象的引用
7.8 对 象 数 组
7.9 常 类 型类的基本特征有:封装性、继承性、
多态性。
类中的成员,根据访问权限分为三类:
私有,保护和公有 。
7.1 类 的 定 义类的一般定义格式如下:
class<类名 >
{
public:
<数据成员或成员函数的说明 >
private:
<数据成员或成员函数的说明 >
};
<各个成员函数的实现 >
其中,class是定义类的关键字,<类名 >是符合 C++规定的标识符 。 花括号内部是类的说明部分 ( 包括前面的类头 ),用来说明该类的成员 。 类的成员包含数据成员和成员函数两部分 。 从访问权限上来分,类的成员又分为:公有的 ( public),私有的 ( private) 和保护的 ( protected) 三类 。
公有的成员用 public来说明,公有部分往往是一些操作 ( 即成员函数 ),它是提供给用户的接口功能 。
这部分成员可以在程序中引用 。 私有的成员用 private来说明,私有部分通常是一些数据成员,这些成员用来描述该类中的对象的属性,用户是无法访问它们的,只有成员函数或经特殊说明的函数才可以引用它们,它们是被用来隐藏的部分 。
<各个成员函数的实现 >是类定义中成员函数具体功能的实现部分,这部分包含所有在类体内说明函数的具体功能 。
【 例 7.1】 下面给出一个关于时间的类的定义,该类是对时间的抽象,该类的对象将是一个具体的时间 。
//类的说明部分:
class Time{
private:
int hour;
int minute;
int second;
public:
Time();
void SetTime(int,int,int);
void printMilitary();
void printStandard();
};
//类的实现部分
Time::Time(){ hour= minute = second =0;}
void Time::SetTime(int h,int m,int s)
{
hour = (h>=0&&h<24)?h:0;
minute = (m>=0&&m<60)?m:0;
second = (s>=0&&m<60)?s:0;
}
void Time::PrintMilitary()
{
cout<<(hour<10? "0":
"")<<hour<<":"<<(minute<10? "0":
"")<< minute <<endl;
}
void Time::PrintStandard()
{
cout<<(hour==0|| hour==12? 12:
hour%12)<< ":"<<(minute<10? "0":
"")<< minute
<< ":"<<(second<10? "0","")<<
second<<(hour<12? " AM"," PM")<<endl;
}
如果成员函数定义在类体外,则在函数头的前面必须加上作用域运算符,,:”,以表明该函数所属类的标识 。
在定义类时应注意以下几点:
( 1) 在类体中不允许对所定义的数据成员进行初始化 。
( 2) 类中的数据成员的类型可以是任意的,
包括基本类型,数组,指针和引用等 。 也可以是对象 。
( 3) 经常习惯地将类定义的说明部分或者整个定义部分 (包含实现部分 )放到一个头文件中 。
7.2 对 象 的 定 义
7.2.1 对象 的 定义对象定义的一般格式为:
<类名 > <对象名表 >
例如,
Time t1,*t2,t3[3];
7.2.2 对象成员的表示方法对象成员有数据成员和成员函数,其表示方式如下:
<对象名 >.<成员名 >
或者
<对象名 >.<成员名 >(<参数表 >)
7.3 构造函数和析构函数
7.3.1 构造函数和析构函数
7.3.1.1 构造函数构造函数是一个特殊的成员函数,构造函数的功能是在创建对象时,使用给定的值将对象初始化 。 该函数的名字与类名相同,
该函数不指定类型说明,它有隐含的返回值,该值由系统内部使用 。
该函数可以有一个参数,也可以有多个参数,即构造函数可以重载 。 函数体可以写在类体内,也可以写在类体外 。 程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数 。
【 例 7.3】 构造函数应用举例。
class Date
{
public:
Date(int y);
Date(int y,int m) {year=
y;month=m;day=0;}
Date(int y,int m,int d);
int IsLeapYear();
void Print();
private:
int year,month,day;
};
//类的实现部分
Date::Date(int y)
{
year= y;month=day=0;
cout<<"1个参数的构造函数已被调用。 \n";
}
Date::Date(int y,int m)
{
year= y;month=m;day=0;
cout<<"2个参数的构造函数已被调用。 \n";
}
Date:,Date(int y,int m,int d)
{
year = y;
month = m;
day = d;
cout<<"3个参数的构造函数已被调用。 \n";
}
int Date::IsLeapYear()
{
return(year%4==0 && year%100!=0) ||
(year%400==0);
}
void Date::Print()
{
cout<<year<<"."<<month<<"."<<day<<endl;
}
7.3.1.2 析构函数析构函数也是一个特殊的函数,其功能与构造函数的功能正好相反,是用来释放一个对象,在对象删除前,用它来做一些清理工作 。 析构函数的名字同类名,并在前面加上,~”字符,用来与构造函数加以区别 。
析构函数不指定数据类型,也没有参数 。
一个类中只能定义一个析构函数 。 析构函数是成员函数,函数体可写在类体内,也可写在类体外 。 析构函数可以被调用,也可以被系统调用 。 在下面两种情况下,析构函数会被系统自动调用 。
( 1) 如果一个对象被定义在一个函数体内,
则当这个函数结束时,该对象的析构函数被自动调用 。
( 2) 当一个对象是使用 new运算符动态创建的,在使用 delete运算符释放它时,
delete将会自动调用析构函数 。
【 例 7.4】 下面程序说明构造函数和析构函数的应用 。
#include "iostream.h"
class Date
{
public:
Date(int y=0,int m=0,int d=0);
~Date();
int IsLeapYear();
void Print();
private:
int year,month,day;
};
//类的实现部分
Date:,Date(int y,int m,int d)
{
year = y;
month = m;
day = d;
cout<<"构造函数已被调用 。 \n";
}
Date::~ Date()
{
cout<<"析构函数被调用 。 \n";
}
int Date::IsLeapYear()
{
return(year%4==0 && year%100!=0) ||
(year%400==0);
}
void Date::Print()
{
cout<<year<<"."<<month<<"."<<day<<en
dl;
}
void main()
{
Date today(2004,3,15),tomorrow(2004,3,
16);
cout<<"Today is:";
today.Print();
cout<<"Tomorrow is:";
tomorrow.Print();
}
运行结果为,
构造函数已被调用 。
构造函数已被调用 。
Today is:2004.3.15
Tomorrow is:2004.3.16
析构函数被调用 。
析构函数被调用 。
7.3.2 缺省构造函数和缺省析构函数在类定义时没有定义任何构造函数时,则编译器会自动生成一个不带参数的缺省构造函数,其格式如下:
<类名 >::<缺省构造函数名 >()
{
}
在程序中定义一个对象而没有进行初始化时,则编译器便按缺省构造函数来初始化该对象 。 用缺省构造函数初始化对象时,
对象的所有数据成员都初始化为零或空 。
如果一个类中没有定义析构函数时,则编译系统也生成一个缺省析构函数,其格式如下:
<类名 >::~<缺省析构函数名 >
{
}
缺省析构函数是一个空函数 。
7.3.3 拷贝初始化构造函数拷贝初始化构造函数是一种特殊的成员函数,它的功能是用一个已知的对象来初始化一个被创建的同类的对象 。
拷贝初始化构造函数的特点如下:
( 1) 该函数名同类名,因为它也是一种构造函数,并且该函数不被指定返回类型 。
( 2) 该函数只有一个参数,并且是对某个对象的引用 。
( 3) 每个类都必须有一个拷贝初始化构造函数 。
拷贝初始化构造函数格式如下:
<类名 >::<拷贝初始化构造函数名 >(const <
类名 >& <引用名 >)
在下述三种情况下,需要用拷贝初始化构造函数来用一个对象初始化另一个对象 。
( 1) 明确表示由一个对象初始化另一个对象时,如 TPoint P2(P1)。
( 2) 当对象作为函数实参传递给函数形参时,如上例中的 P = f(N)。
( 3) 当对象用为函数返回值时,如上例中的 return R。
【 例 7.5】 拷贝初始化构造函数应用举例 。
#include "iostream.h"
class Tpoint
{
public:
TPoint(int x,int y) {X=x; Y=y;}
TPoint(TPoint & p);
~TPoint() {cout<<"析构函数被调用 。
\n";}
int Xcoord() {return X;}
int Ycoord() {return Y;}
private:
int X,Y;
};
TPoint::TPoint(TPoint & p)
{
X = p.X;
Y = p.Y;
cout<<"拷贝初始化构造函数被调用 。 \n";
}
void main()
{
TPoint P1(5,7);
TPoint P2(P1);
cout<<"P2="<<P2.Xcoord()<<","<<P
2.Ycoord()<<endl;
}
运行程序,输出结果为:
拷贝初始化构造函数被调用 。
P2=5,7
析构函数被调用 。
析构函数被调用 。
【 例 7.6】 关于拷贝初始化构造函数的其他用法 。
#include "iostream.h"
class TPoint
{
public:
TPoint(int x,int y) {X=x;Y=y;}
TPoint(TPoint &p);
~TPoint() {cout<<"析构函数被调用 。 \n";}
int Xcoord() {return X;}
int Ycoord() {return Y;}
private:
int X,Y;
};
TPoint::TPoint(TPoint & p)
{
X = p.X;
Y = p.Y;
cout<<"拷贝初始化构造函数被调用 。 \n";
}
TPoint f(TPoint Q);
void main()
{
TPoint M(20,35),P(0,0);
TPoint N(M);
P = f(N);
cout<<"P="<<P.Xcoord()<<","<<P.Ycoor
d()<<endl;
}
TPoint f(TPoint Q)
{
cout<<"ok\n";
int x,y;
x = Q.Xcoord()+25;
y= Q.Ycoord()+30;
TPoint R(x,y);
return R;
}
运行程序,输出结果如下:
拷贝初始化构造函数被调用 。
拷贝初始化构造函数被调用 。
ok
拷贝初始化构造函数被调用 。
析构函数被调用 。
析构函数被调用 。
析构函数被调用 。
P=45,65
析构函数被调用 。
析构函数被调用 。
析构函数被调用 。
7.4 成员函数的特征
7.4.1 成员函数的重载
【 例 7.7】 成员函数重载 。
#include "iostream.h"
class myclass
{
private:
int x;
int y;
int z;
public:
void set(){x=10;y=20;z=30;}
void set(int xx){x=10;y=20;z=30;}
void set(int xx,int yy ){x=xx;y=yy;z=30;}
void set(int xx,int yy,int zz)
{x=xx;y=yy;z=zz;}
void print(){cout<< x<<'\t'<< y<<'\t'<<
z<<'\n';}
};
void main()
{
myclass b1;
cout <<"x y z\n";
b1.set ();
b1.print ();
b1.set (11);
b1.print ();
b1.set (11,22);
b1.print ();
b1.set (11,22,33);
b1.print ();
}
运行结果:
x y z
10 20 30
11 20 30
11 22 30
11 22 33
7.4.2 参数的缺省值对于没有在函数调用中指定的参数,函数声明时必须设定其默认值 。
【 例 7.8】 计算盒子的体积 。
#include<iostream.h>
class Box
{
private:
int length;
int width;
int height;
public:
int get_volume(int length,int width = 2,int
height = 3);
};
int Box::get_volume(int l,int w,int h)
{
length = l;
width = w;
height = h;
cout<< length<<'\t'<< width<<'\t'<<
height<<'\t';
return length,width,height;
}
void main()
{
Box b1;
int x = 6,y = 8,z = 10;
cout <<"Length Width Height Volume\n";
cout << b1.get_volume(x,y,z) << "\n";
cout << b1.get_volume(x,y) << "\n";
cout << b1.get_volume(x) << "\n";
cout << b1.get_volume(x,9) << "\n";
cout << b1.get_volume(6,6,6) << "\n";
}
运行程序,输出结果为:
Length Width Height Volume
6 8 10 480
6 8 3 144
6 2 3 36
6 9 3 162
6 6 6 216
7.5 静 态 成 员
7.5.1 静态数据成员在定义一个类时,可以使用 static关键字指定静态成员 。 包括静态数据成员和静态成员函数 。
【 例 7.9】 静态数据成员使用举例。此程序用于计算并显示矩形的面积,还将显示每次由对象递增的静态数据成员的值。
#include<iostream.h>
class rectangle
{
private:
int length;
int width;
static int extra_data; //声明静态成员
extra_data
public:
rectangle();
void set(int new_length,int new_width);
int get_area();
int get_extra();
};
int rectangle::extra_data; //定义静态成员变量 extra_data
rectangle::rectangle()
{
length = 8;
width = 8;
extra_data = 1;
}
void rectangle::set(int new_length,int
new_width)
{
length = new_length;
width = new_width;
}
int rectangle::get_area()
{
return (length * width);
}
int rectangle::get_extra()
{
return extra_data++;
}
void main()
{
rectangle small,medium,large;
small.set(5,7);
large.set(15,20);
cout<<"Small rectangle area is
"<<small.get_area()<<"\n";
cout<<"Medium rectangle area is
"<<medium.get_area()<<"\n";
cout<<"Large rectangle area is
"<<large.get_area()<<"\n";
cout <<"The static data value is
"<<small.get_extra()<<"\n";
cout <<"The static data value is
"<<medium.get_extra()<<"\n";
cout <<"The static data value is
"<<large.get_extra()<<"\n";
}
运行程序,输出结果为:
Small rectangle area is 35
Medium rectangle area is 64
Large rectangle area is 300
The static data value is 1
The static data value is 2
The static data value is 3
静态数据成员能在类说明符中声明,但不能在其中定义。
对于静态数据成员的初始化不能在构造函数中进行。
7.5.2 静态成员函数当一个函数不需要访问类中除静态数据成员之外的数据时,我们可以将其定义为静态成员函数 。 一般情况下,静态成员函数只能访问类中的静态数据成员 。 静态成员函数是被一个类中所有对象共享的成员函数,不属于哪个特定的对象 。
【 例 7.10】 在下面的程序中,将使用类 Car
为自己的每一个对象提供了对象 ID号。创建或销毁对象时,将有一静态数据成员记录程序中的对象数。执行结果是显示对象
ID及对象数。
#include<iostream.h>
class Car
{
private:
static int counter; //静态数据成员
int obj_id;
public:
Car(); //构造函数
static void display_total();
//静态成员函数
void display();
~Car();
//析构函数
};
int Car::counter;
//定义静态数据成员
Car::Car()
{
counter++;
obj_id = counter;
}
Car::~Car()
{
counter--;
cout<<"Object number "<<obj_id<<"
being destroyed\n";
}
void Car::display_total() //static function
{
cout <<"Number of objects created is =
"<<counter<<endl;
}
void Car::display()
{
cout << "Object ID is "<<obj_id<<endl;
}
void main()
{
Car a1;
Car::display_total();
Car a2,a3;
Car::display_total();
a1.display();
a2.display();
a3.display();
}
输出结果:
Number of objects created is = 1
Number of objects created is = 3
Object ID is 1
Object ID is 2
Object ID is 3
Object number 3 being destroyed
Object number 2 being destroyed
Object number 1 being destroyed
7.6 友 元
7.6.1 友元函数友元函数是一种定义在类外面的普通函数,
它需要在类体内进行说明,为区别友元与类成员函数,在说明友元函数时前面加关键字 friend。 友元函数不是成员函数,但是它可以访问类私有成员 。
函数通过一个类中的友元声明成为该类的友元函数 。
例如,
class person
{
public:
void getdata();
friend void display(person p);
//友元函数的声明
};
void display(person p)
//友元函数没有,:运算符
{
……
//一些代码
}
7.6.2 友元类可以将一个成员函数或几个成员函数或整个类声明为另一个类的友元。
【 例 7.12】 一个类的成员函数是另一个类的友元。
class beta; //前向声明
class alpha
{
private:
int a_data;
public:
alpha() {a.data = 10;}
void display(beta);
};
class beta
{
private:
int b_data;
public:
beta() {b_data = 20; }
friend void alpha::display(beta bb);
// alpha类的成员函数为 beta类的友元函数
};
void alpha::display(beta bb)
{
cout<<"\n data of beta ="<<bb.b_data;
cout<<"\n data of alpha ="<<a_data;
}
void main()
{
alpha a1;
beta b1;
a1.display(b1);
}
7.7 对象的指针和对象的引用
7.7.1 对象的指针
7.7.1.1 对象的指针在创建一个类的对象时,系统会自动在内存中为该对象分配一个确定的存储空间,
该存储空间在内存中存储的起始地址,如果用一个指针来保存,那么这个指针就是指向对象的指针,简称对象的指针 。
对象的指针的声明形式如下:
类名 * 对象的指针名而通过对象的指针间接访问对象成员的方式相应地表示为:
( * 对象的指针名 ),数据成员名;
//访问数据成员
( *对象的指针名 ),成员函数名 ( 参数表 ) ; //访问成员函数注意,因为间接访问运算符,*” 的优先级低于成员选择运算符,,,,所以表达式中对象的指针名两边的圆括号不能省略 。
另外,C++语言提供了另一个更为常用的方法 。 表述形式如下:
对象的指针名 ->数据成员名;
//访问数据成员对象的指针名 ->成员函数名 ( 参数表 ) ;
//访问成员函数其中的,->”也叫做成员选择运算符,该运算符可用于通过对象的指针或结构变量的指针来访问其中的成员 。
在使用对象的指针之前一定要给指针赋一个合法的初值。
【 例 7.14】 对象指针的使用。
#include "iostream.h"
#include "string.h"
class Student
{
private:
char name[20];
char ID[20];
int age;
double score[5];
public:
Student (char *str1="",char *str2="",int
a=0,double s0=0,double s1=0,double
s2=0,double s3=0,double s4=0)
{
strcpy(name,str1);
strcpy(ID,str2);
age=a;
score[0]=s0;
score[1]=s1;
score[2]=s2;
score[3]=s3;
score[4]=s4;
}
void display();
};
void Student::display()
{
cout<<"Name,"<<name<<endl;
cout<<"ID,"<<ID<<endl;
cout<<"Age,"<<age<<endl;
for(int i=0;i<5;i++)
cout<<"Score "<<i<<",
"<<score[i]<<endl;
}
void main()
{
Student
p1("zhangsan","123456789",19,98,79,78,87
,99);
Student *pt=&p1;
//定义指向对象的指针 pt
Pt->display();
}
程序执行结果为:
Name,zhangsan
ID,123456789
Age,19
Score 0,98
Score 1,79
Score 2,78
Score 3,87
Score 4,99
7.7.1.2 对象的指针作为函数的参数
【 例 7.15】 分析下面程序执行结果 。
#include "iostream.h"
#include "string.h"
class person
{
private:
char name[20];
char ID[20];
int age;
double account;
public:
person(char *str1="",char *str2="",int
a=0,double ac=0 )
{
strcpy(name,str1);
strcpy(ID,str2);
age=a;
account=ac;
}
double GetAccount(){return account;}
void display();
};
void person::display()
{
cout<<"Name,"<<name<<endl;
cout<<"ID,"<<ID<<endl;
cout<<"Age,"<<age<<endl;
cout<<"Account,"<<account<<endl;
}
double fun(person *p)
//对象的指针作为函数的参数
{
double x;
x=p->GetAccount()*0.15;
return x;
}
void main()
{
person p1("张三
","232402197804232227",22,1000000);
person *pt=&p1;
pt->display();
cout<<"Personal tax is,"<<fun(pt)<<endl;
}
程序运行结果为:
Name,张三
ID,132402197804232227
Age,22
Account,1e+006
Personal tax is,150000
7.7.2 this 指针
this指针也是一个指向对象的指针,
它隐含在类的成员函数中,用来指向成员函数所属类的正在被操作的对象 。
this指针是 C++实现封装的一种机制,它将数据成员和成员函数连接在一起,当一个成员函数对数据成员进行操作时,用 this指针来表示数据成员所在的对象 。 当程序操作不同对象的成员函数时,this指针也指向不同的对象 。
注意,静态成员函数没有 this指针 。
【 例 7.16】 this指针的使用。
#include "iostream.h"
class Person
{
private:
int num;
int age;
public:
void display();
};
void Person,,display()
{
this->num=1001; //与 num=1001相同
this->age = 20; // 与 age=20 相同
cout<<this->num<<" "<<this->age<<endl;
//与 cout<<num<<" "<<age<<endl;相同
}
void main()
{
Person Lili;
Lili.display();
}
程序运行结果为:
1001 20
this指针的有用之处在于它能从成员函数中返回值 。
7.7.3 对象引用
【 例 7.17】 对象的引用作为函数的参数,
分析程序执行情况。
#include "iostream.h"
#include "string.h"
class P
{
private:
char num[20];
int grade;
public:
P(char *str="",int g=20){strcpy(num,str);
grade = g; }
void set(int gg=0){grade=gg;}
void display();
};
void P,,display()
{
cout<< num<<" "<< grade<<endl;
}
void fun(P &p1)
//对象的引用作为函数的参数
{
p1.set(9);
}
void main()
{
P p1("WeiFeng",12);
fun(p1);
p1.display();
}
程序运行结果为:
WeiFeng 9
7.8 对 象 数 组对象数组是指数组元素为对象的数组。
对象数组定义的一般格式为:
<类名 > <对象数组名 >[<长度 >]…
例如:
person p[10];
person pp[2][3];
【 例 7.18】 分析下面程序执行结果。
#include "iostream.h"
class A
{
private:
int x;
public:
A(int xx=100){x=xx; cout<< "Constructor
called.\n";}
~A(){cout<<"Destructor called,
"<<x<<endl;}
void set(int xx=100){x=xx;}
void display();
};
void A:,display()
{
cout<<x<<endl;
}
void main()
{
A a[3];
for(int i=0;i<3;i++)
a[i].set(100+i*100);
for(int j=0;j<3;j++)
a[j].display();
}
程序运行结果为:
Constructor called.
Constructor called.
Constructor called.
100
200
300
Destructor called,300
Destructor called,200
Destructor called,100
7.9 常 类 型
7.9.1 常指针和常引用
7.9.1.1 常量和常指针常量是一个在程序执行过程中其值不能改变的量。 C++中的关键字 const可以加到对象的声明中使该对象成为一个常量。
常量不能改变,因此必须对常量进行初始化。
例如,
const int Maxsize=100;
const也可以修饰指针变量,当使用带有指针的 const时,有两种含义:一是指针本身是一个变量,二是指针指向的内容是一个变量。当把 const置于指针声明之前可以使指针指向的内容是一个变量,而不是使指针变成一个常量。
例如,
int i =10;
const int *iptr = &i;
将 const置于指针声明之后,是将指针本身声明为常量,而不是指针指向的内容,
例如:
double d1 = 99.9;
double *const dptr = &d1; // 常量指针指向 double型变量
*dptr = 100.87; //正确
7.9.1.2 常引用使用 const修饰符说明引用时,则该引用为常引用 。 该引用所引用的对象不能被更改 。
常引用的定义格式为:
const <类型说明 > &<引用名 >;
例如,
int y=100;
const int & x=y;
7.9.2 常成员函数使用 const说明的成员函数,称为常成员函数 。 只有常成员函数才有权使用常量或常对象,没有使用 const说明的成员函数不能使用常对象 。
常成员函数说明的格式为:
<类型说明 > <函数名 > (<参数表 >) const;
【 例 7.20】 分析下面程序执行结果 。
#include "iostream.h"
class Point
{
private:
int x,y;
public:
Point(int xx=0,int yy=0){x=xx; y=yy;}
void display()const;
void display();
};
void Point::display()
{
cout<<x<<" ***** "<<y<<endl;
}
void Point::display()const
{
cout<<x<<" const "<<y<<endl;
}
void main()
{
Point p(100,89);
p.display();
const Point pc(200,98);
pc.display();
}
程序运行结果为:
100 ***** 89
200 const 98
7.9.3 常数据成员关键字 const不仅可以修饰成员函数,也可以修饰数据成员 。 由于 const修饰的对象不能更改,所以必须进行初始化 。
【 例 7.21】 常数据成员的使用 。
#include "iostream.h"
class Circle
{
private:
int x,y;
const double PI;
double r;
public:
Circle(int xx=0,int yy=0,double
rr=0):PI(3.14159){x=xx; y=yy;r=rr;}
double area(){return PI*r*r;}
void display();
};
void Circle::display()
{
cout<<" 圆心位置,
("<<x<<","<<y<<")"<<endl;
cout<<"半径大小为,"<<r<<endl;
cout<<"圆的面积为,"<<area()<<endl;
}
void main()
{
Circle c(100,200,10);
c.display();
}
程序运行结果为:
圆心位置,(100,200)
半径大小为,10
圆的面积为,314.159