c++(four)
类和对象
面向对象程序设计的基本特点
抽象
抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。
- 先注意问题的本质及描述,其次是实现过程或细节
- 数据抽象:描述某类的属性或状态(对象相互区别的物理量)
- 代码抽象:描述某类对象的共有的行为特征或具有的功能
- 抽象的实现:通过类的声明
实例(钟表)
-
数据抽象
需要三个整数来存储时间,分别表示时、分和秒,这就是对时钟所具有的数据进行抽象。
int Hour,int Minute,int Second
-
代码抽象
时钟要具有显示时间、设置时间等简单的功能,这就是对它的行为的抽象。
SetTime(),ShowTime()
抽象实例
class Clock{
public:
void SetTime(int NewH,int NewM,int NewS);
void ShowTime();
private:
int Hour,Minute,Second;
};
抽象实例(人)
数据抽象:
char *name,char *sex,int age,int id(或:string name,string sex,int age,int id)
代码抽象
生物属性角度:
GetCloth(),Eat(),Step(),..
社会属性角度:
Work(),Promote(),...
封装
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体。也就是将数据与操作数据的函数代码进行有机地结合,形成“类”,其中的数据和函数都是类的成员。
- 目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
- 实现封装:类声明中的{}
实例:
class Clock{
public:
void SetTime(int NewH,int NewM,int NewS);
void ShowTime();
//SetTime(),ShowTime()为外部接口
private:
int Hour,Minute,Second;
//public,private为特定访问权限
};
//{}:边界
继承
C++语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明
是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。
实现:声明派生类——第七章
多态性
-
多态:同一名称,不同的功能实现方式
多态性是指一段程序能够处理多种类型对象的能力。
-
目的:达到行为标识统一,减少程序中标识符的个数
-
实现:在C++中语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态(模板)、包含多态(虚函数)4种形式来实现。
类和对象(c++中的类)
- 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
- 利用类可以实现数据的封装、隐藏、继承与派生
- 利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高。
类实际上相当于一种用户自定义的类型,它和前面学习过的基本类型,如整型、实型,有类似的特征。同样,也可以声明某个类类型的变量,这个变量就称为类的对象(或实例),这个声明的过程也称为类的实例化。类和基本类型的不同之处在于,类这个特殊类型中同时包含了对数据进行操作的函数。
类的定义
以时钟为例:时钟类的定义如下:
类的声明形式
类是一种用户自定义类型,声明形式:
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
};
-
公有类型成员
在关键字public后面声明,它们是类与外部的接口(interface),任何外部函数都可以访问公有类型数据和函数。
-
私有类型成员
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
-
保护类型
与private类似,其差别表现在继承与派生时对派生类的影响不同,第七章讲。
void Clock::SetTime(int NewH,int NewM,int NewS)
{ Hour = NewH;
Minute = NewM;
Second = NewS;
}
void Clock::ShowTime()
{
cout<<Hour<< “:”<<Minute<<“:”<< Second<<endl;
}
类成员的访问控制
-
定义访问控制属性的基本原则
- 将需要隐藏的成员设为私有类型;将提供给外界的接口设为公有类型。
- 设计一个类时,一定要设计必要的外部接口
- 一般情况下,一个类的数据成员都应该声明为私有成员,内部数据结构就不会对类外造成影响,程序模块之间的耦合性降低到最小。
-
类的成员
class Clock{ public: void SetTime(int NewH,int NewM,int NewS); void ShowTime(); //成员函数 (member function)或 函数成员 private: int Hour,Minute,Second; //数据成员 (data member)或 成员数据 }; void Clock :: SetTime(int NewH, int NewM,int NewS) { Hour=NewH; Minute=NewM; Second=NewS; } void Clock :: ShowTime( ) { cout<<Hour<<":"<<Minute<<":"<<Second; }
概念设计中的类的表示
数据成员
与一般的变量声明相同,但需要将它放在类的声明体中。
类的成员函数
一般在类中声明成员函数的原型,在类外定义函数体的实现,并在函数名前使用类名加以限定。也可以直接在类中给出函数体,形成内联成员函数。
类的成员函数
-
成员函数调用中的目的对象
在类中调用成员函数与普通函数是不同,需要使用“对象.成员”,成员函数调用本类中成员不需要使用“.”,成员函数通过其它类的对象调用其它类中成员需要使用“.”。
-
成员函数可以调用本类中公开,私有、保护成员
-
允许声明重载函数和带缺省形参值的函数
-
成员函数重载
class Clock{ public:setTime(int newH,int newM,int newS); setTime(int newH,int newM); ...... }
-
带缺省形参值的成员函数
class Clock{ public:setTime(int newH=0,int newM=0,int newS=0); ...... }
-
内联成员函数(1)
class Clock{ public:setTime(int newH=0,int newM=0,int newS=0); void showTime() { cout<<hour<<”:”<<minute<<”:”<<second; } ...... }
-
内联成员函数(2)
class Clock{ public:setTime(int newH=0,int newM=0,int newS=0); ...... } inline void Clock::showTime() { cout<<hour<<”:”<<minute<<”:”<<second; } //注意:上面两种方式内联的效果是一样的
-
成员函数
-
成员函数的声明和实现
-
成员函数的实现
- 具体是现在类声明之外
- 与普通函数不同的是,实现函数时要指明类的名称。
-
成员函数声明的格式
返回值类型 类名 :: 成员函数名 (参数列表) { 函数体 }
-
-
内联成员函数举例(一)
class Clock { public: void SetTime(int NewH, int NewM,int NewS); void ShowTime( ) {cout<<Hour<<":"<<Minute<<":"<<Second;} private: int Hour, Minute, Second; };
-
内联成员函数举例(二)
class Point { public: void Init ( int initX,int initY); int GetX( ); int GetY( ); void Move ( int xOffset, int yOffset); private: int X,Y; }; inline void Point :: Init(int initX,int initY) { X=initX; Y=initY; } inline int Point :: GetX( ) { return X; } inline int Point :: GetY( ) { return Y; } inline void Point :: Move( int xOffset, int yOffset) { X + = xOffset; Y + = yOffset; }
类的声明和内部实现
- 将类的声明和其成员函数的定义分开,是目前程序开发的通常做法。
- 将类的声明放在头文件中,将类的成员函数的定义放在另外的源程序中
- 把类的声明看作是类的外部接口,成员函数的定义是类的内部实现(implementation)
- 将类拿来编写应用程序时,只需要类的外部接口(头文件)。和使用标准库函数一样,只需要包含函数声明的头文件。因为在类定义中,全部包含了类中成员函数的声明。
//date.h 类的声明 class Date { public : void Set ( int, int,int ); void Print ( ); private: int month, day, year; }; //date.cpp 成员函数定义 // 类的内部实现 #include <iostream.h> #include “date.h” void Date:: Set(int m, int d,int y) { month=m; day=d; year=y; } void Date:: Print( ) { cout<<month<<“/”<<day<<“/”<<year<<endl; }
对象
-
类和对象的关系
- 对象的类型称为类。类代表了一类对象的共性和特征。
- 类是对象的抽象,类的对象是具有该类类型的具体的实例(instance)。
- 每一种数据类型都是对一类对象的抽象,声明的每一个变量都是所属数据类型的一个实例。
- 类是用来定义对象的抽象数据类型,对象是类类型的变量
-
声明一个对象和声明一个一般变量相同
-
例:
Clock myClock; //myClock是对象的名字, Clock 是myClock的类型
-
声明类类型的变量,这个变量就称为类的对象(或实例),这个声明的过程也称为类的实例化(instantiation)。
面向对象的问题求解过程
面向对象语言中的对象用来模拟现实世界中的对象。面向对象强调的是程序的类型化。
类和对象
类和对象的区别
- 类是在程序源代码中存在的实体,声明类的目的是为了建立对象。 对象是在程序运行过程中创建的运行时的实体。
- 程序执行时,一个对象被创建(这一过程称为实例化),即为程序中声明的一个对象分配存储单元。这时对象的生命周期开始。当对象生命期结束时,对象被删除,所占用的存储单元被释放
- 一个类的所有实例具有相同的结构和共享共同的行为,所没有共享的是它们各自的状态值(或属性值,即数据成员的值)。 对象的状态值分别保存在每个对象中。
class Point
{
public:
void Init ( int initX,int initY);
int GetX( );
int GetY( );
void Move ( int xOffset, int yOffset);
private:
int X,Y;
};
在运行时,我们只看到对象,程序的任务是由对象承担的。在程序中,我们只看到类。类定义了它的所有对象共享的代码,它还提供了结构样板,用于创建不同的对象。
每个对象的成员函数的代码并不存储于每个对象中,代码存储于某个公用的存储空间中。只有每个对象的状态值存储于每个对象之中
类的成员的访问方式
- 类中成员互访
- 类的内部,所有成员之间可以通过成员名(公开、私有、保护)直接访问。
- 类外访问
- 类的外部只能访问类的公有成员
- 使用**“对象名.成员名”**方式访问
类的应用举例
#include<iostream.h>
class Clock
{
......//类的声明略
}
//......类的实现略
void main(void)
{ Clock myClock;
myClock.SetTime(8,30,30);
myClock.ShowTime( );
}
类与结构体
在C++中的结构体比C中的结构体具有更强大的功能,它是另外一种形式的类。C++中的结构体也能像类一样包括数据和成员函数。
-
结构体成员
在于缺省情况下,结构体的成员是公有的。除此之外,类与结构体有完全相同的功能。所以结构体又称为全部成员都是公有成员的类。
例如:考虑实现“点”类的定义
//C++中的结构体
Struct SMyPoint
{
int x,y;
void Draw( )
{
cout<<“在坐标(”<<x<<“,”<<y<<“)处画一个点”<<endl;
}
};
类的成员
缺省情况下,类中包括的数据成员和成员函数都是私有的。如果要在类中定义公有成员,必须显示声明public关键字。
例如:我们希望函数Draw是公有的,所以在类CMyPoint的定义中,显示地给出关键字public。
//定义一个“点”类
class CMyPoint
{
int x,y;
public:
void Draw( )
{
cout<<“在坐标(”<<x<<“,”<<y<<“) 处画一个点”<<endl;
}
};
构造函数和析构函数
- 一个类的所有对象都共享该类。每个对象都区别于其他的对象。外在的区别是对象的标识符,即对象名字。内在的区别是对象自身的属性值和所在的存储单元
- 在声明对象时对数据成员赋初值,称为对象的初始化。
- 在声明对象时,给对象分配存储单元,同时调用构造函数将对象初始化为一个特定的状态。
构造函数
-
构造函数是特殊形式的成员函数。
-
编译系统在遇见对象声明语句时,自动生成对构造函数的调用语句。因此构造函数是在对象被创建时由系统自动调用
-
如果程序中未定义构造函数,则编译系统自动生成一个默认形式的构造函数。
-
允许为内联函数、重载函数、带缺省形参值的函数。
-
构造函数的需要性
-
变量初始的方法
int a=1; int *pa=&a;
-
数组初始的方法
int b[]={1,2,3,4};
-
结构初始的方法
struct student{ int semesHours; //总需学时数 float gpa; //平均成绩 } void fn ( ) { student s = { 100 , 3.5 } ; //创建结构变量时,初始化 //… }
但是对类对象来说,如此初始化不行,这是由类的特殊性所决定。
例:下面的代码企图在对象创建时,为其初始化:
class student{ public ; // …公共成员… protected : //此处的数据成员是受保护的 int semesHours ; float gpa ; } ; Void fa( ) { student s = { 100 , 3.5 } // error: 不能访问 // … } }
-
-
如果上面的函数允许那样初始化的话,就意味在函数中,允许:
void fn ( ) { s.semesHours = 0 ; s.gpa = 0 ; }
-
类的封装性,就体现在一部分数据是不能让外界访问的。所以直接在非成员函 数中访问类对象的保护或私有数据是不允许的
-
类对象的初始化任务,自然就落在了类的成员函数身上,因为它们可以访问保护和私有数据成员。
-
初始化构想
class student { public: void init ( ) { semesHours = 100 ; gpa = 3.5 ; } // …其他公共成员 protected: int semesHours ; int gpa } void fn ( ) { student s ; s.init ( ); //类的初始化 //函数的其他部分 }
-
类对象的定义
student ss;
-
构造函数的作用
在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使此对象具有区别于其他对象的特征。
-
定义构造函数的格式:
class 类名 { public : 类名(形参列表);//构造函数声明 ... }; 类名:: 类名(形参列表)// 构造函数的实现 { 函数体 }
构造函数举例:
class Clock { public: Clock (int NewH, int NewM, int NewS); //构造函数 void SetTime(int NewH, int NewM, int NewS); void ShowTime( ); private: int Hour,Minute,Second; };
-
构造函数的实现:
Clock::Clock(int NewH, int NewM, int NewS) { Hour=NewH; Minute=NewM; Second=NewS; }
-
建立对象时构造函数的作用:
void main( ) { Clock c (0,0,0); //隐含调用构造函数,将初始值作为实参 c.ShowTime( ); } Clock c1; //错误的,没有给出必要的实参
-
构造函数可以带默认的形参值,也可以是内联函数
#include < iostream.h> class Clock { public: Clock(int NewH=7,int NewM=30,int NewS=0) //既是构造函数也是内联函数 { hour=NewH; minute=NewM; second=NewS; } void ShowTime() { cout<<hour<<":"<<minute<<":“<<second<<endl; } private: int hour,minute,second; }; void main() { Clock c(6); c.ShowTime(); }
-
构造函数可以无形参值
下面的代码初始化桌子和凳子类对象:
class Desk { public: Desk( ) // 构造函数定义 { weight = 10 ; height = 5 ; width = 5; length = 5 ; } protected : int weight ; int height ; int width ; int length ; }; class Stool { public: Stool( ) // 构造函数定义 { weight = 6 ; height = 3 ; width = 3; length = 3 ; } protected : int weight ; int height ; int width ; int length ; }; void fn( ) { Desk da ; //自动调用Desk( ), 创建对象并初始化 Stool sa ; //自动调用Stool( ) //… }
-
-
构造函数放在类的外部定义:
#include <iostream> class Desk { public: Desk( ); // 构造函数声明 protected: int weight; int height; int width; int length; }; class Stool { public: Stool( ); // 构造函数声明 protected : int weight; int height; int width; int length; }; Desk :: Desk( ) //构造函数定义 { weight = 10; height = 5; width = 5; length = 5; cout <<weight<<“ “<<height<<“ “<<width<<““<<length<<endl ; } Stool :: Stool( ) //构造函数定义 { weight = 6; height = 3; width = 3; length = 3; cout <<weight<<“ “<<height<<“ “<<width <<““<<length<<endl; } void fn( ) { Desk da; //自动调用Desk( ) Stool sa; //自动调用Stool( ) } void main ( ) { fn( ); } //运行结果为: // 10 5 5 5 // 6 3 3 3
默认构造函数
- 若未提供一个类的构造函数(一个都未提供),则C++提供一个默认的构造函数。该默认构造函数无参数,仅负责创建对象,不做任何初始化工作。
- 只要一个类定义了一个或一个以上的构造函数,C++就不再提供默认的构造函数。即如果为类定义了一个带参数的构造函数,还想要无参构造函数,必须自己定义
举例:
Class Student
{
//无构造函数
private:
char name[20];
};
//等价于下面的
Class Student
{
Public:
Student ( ) { }
//一个空的无参构造函数
private:
char name[20];
};
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,其形参是本类的对象的引用。其作用是使用一个已存在的对象(由拷贝构造函数的参数指定的对象)去初始化一个新的同类的对象。
class 类名
{ public :
类名(形参);//构造函数
//形参是本类的对象的引用
类名(类名 &对象名);//拷贝构造函数
...
};
类名:: 类名(类名 &对象名)// 拷贝构造函数的实现
{ 函数体 }
如果程序员没有定义类的拷贝构造函数,则编译系统为程序自动生成一个默认拷贝构造函数。
这个默认拷贝构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。
举例:
class Point
{
public:
Point(int xx=0,int yy=0){X=xx; Y=yy;}
Point(Point& p); //拷贝构造函数
int GetX( ) {return X;}
int GetY( ) {return Y;}
private:
int X,Y;
};
Point::Point (Point& p)
{
X=p.X;
Y=p.Y;
cout<<"拷贝构造函数被调"<<endl;
}
拷贝构造函数被调用的三种情况
-
当用类的一个对象去初始化该类的另一个对象时系统自动调用它实现拷贝赋值。
void main(void) { Point p1 (1, 2); Point p2(p1); //拷贝构造函数被调用 Point p3 = p2; //不同的方法含义一样(拷贝构造函数被调用) }
-
若函数形参的类型为类的对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数
void fn ( Point p) { cout<<p.GetX( )<<endl; } void main( ) { Point mp (1,2); fn (mp); //调用拷贝构造函数 }
- 函数 fn ( )的参数传递方式是值传递(引用传递除外),实参mp传给形参p,形参p是mp的一个拷贝,即对象p用对象mp的值进行初始化
-
当函数的返回值类型是类的对象,系统自动调用拷贝构造函数。
Point fn ( ) { Point tp(1,2); return tp; //调用拷贝构造函数 } void main ( ) { Point p; p=fn( ); }
-
(3的补充)当函数返回一个对象时,要创建一个临时的无名对象。在这里,系统调用拷贝构造函数将tp拷贝到新创建的临时对象中
Point fn( ) { Point tp(1,2); return tp; } void main( ) { Point p; p = fn( ); }
临时对象的生存期只在函数调用的表达式中,即p=f n( )中。当fn( )返回时产生的临时对象拷贝给p后,临时对象就析构
举例:Point类的完整程序。
在程序主函数中,三个部分分别给出拷贝构造函数调用的三种情况。
#include < iostream.h >
class point //point类的声明
{ public: //外部接口
point( int xx=0 , int yy = 0){ X = xx , Y = yy;} // 构造函数
point( point &p); //拷贝构造函数
int GetX( ) { return X;}
int GetY( ) { return Y;}
private : //私有数据
int X ,Y ;
};
//成员函数的实现
point::point( point &p)
{
X = p.X ;
Y = p.Y ;
cout << “拷贝构造函数调用”<< endl ;
}
//形参为point类对象的函数
void fun1( point p )
{
cout << p.GetX( ) << endl ;
}
// 返回值为point类对象的函数
point fun2( )
{
point A(1 , 2 ) ;
return A ;
}
//主函数
void main( )
{
point A( 4 , 5 ) ; //第一个对象A
point B(A) ; //情况1,用A初始化B。//第一次调 用拷贝构造函数。
cout << B.GetX( ) << endl ;
fun1( B ) ; // 情况2,对象B作为fun1的实参。//第二次调 用拷贝构造函数。
B = fun2( ) ; // 情况3,函数返回值是类对象,函数返回时,调 用拷贝构造函数。
cout << B.GetX( ) << endl ;
}
//运行结果:
// 拷贝构造函数被调用
// 4
// 拷贝构造函数被调用
// 4
// 拷贝构造函数被调用
// 1
析构函数(destructor)
-
对象在声明时开始生存期,生存期结束时从内存中删除
-
完成对象被删除前的一些清理工作,例如关闭文件或释放内存
-
在对象的生存期即将结束的时刻由系统自动调用,然后对象消失,释放对象所占内存空间
-
如果程序中未定义析构函数,编译系统将自动生成一个默认的析构函数
-
析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载
-
只是在类对象生命期结束的时候,由系统自动调用
-
析构函数的格式
class 类名 { public : ~ 类名( ) ; //析构函数声明 ... }; 类名:: ~类名() // 析构函数的实现 { 函数体 }
构造函数和析构函数举例
#include <iostream>
class Point
{
public:
Point ( int xx, int yy );
~Point( );
//...其它函数原形
private:
int X, Y;
};
类的应用举例
一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价
#include <iostream>
const float PI = 3.14159;
const float FencePrice = 35;
const float ConcretePrice = 20;
class Circle
{ private:
float radius;
public:
Circle(float r); //构造函数
float Circumference( ); //圆周长
float Area( ); //圆面积 };
// 类的实现
// 构造函数初始化数据成员radius
Circle::Circle(float r)
{radius=r;}
// 计算圆的周长
float Circle::Circumference( )
{ return 2 * PI * radius;}
// 计算圆的面积
float Circle::Area( )
{ return PI * radius * radius; }
void main ( )
{
float radius;
float FenceCost, ConcreteCost;
// 提示用户输入半径
cout<<"Enter the radius of the pool: ";
cin>>radius;
// 声明 Circle 对象
Circle Pool(radius);
Circle PoolRim(radius + 3);
//计算栅栏造价并输出
FenceCost = PoolRim.Circumference() * FencePrice;
cout << "Fencing Cost is ¥" << FenceCost << endl;
// 计算过道造价并输出
ConcreteCost = ( PoolRim.Area())- Pool.Area( ))*ConcretePrice;
cout << "Concrete Cost is ¥" << ConcreteCost << endl;
}
//运行结果
//Enter the radius of the pool: 10
//Fencing Cost is ¥2858.85
//Concrete Cost is ¥4335.39
类的组合
例:
class Circle()
{
public:
Circle(float r);
float Circumference();
float area();
private:
float radius;
};
- 类中的数据成员是另一个类的对象
- 可以在已有的抽象的基础上实现更复杂的抽象
- 在创建组合类的对象时,不仅要对本类中的基本数据类型成员进行初始化,也要对内嵌对象成员初始化
举例:
class Point
{ private:
float x, y; //点的坐标
public:
Point(float h,float v); //构造函数
float GetX(void); //取X坐标
float GetY(void); //取Y坐标
void Draw(void); //在(x,y)处画点
};//...函数的实现略
class Line
{
private:
Point p1,p2; //线段的两个端点
public:
Line(Point a,Point b); //构造函数
void Draw(void); //画出线段
};//...函数的实现略
组合类的构造函数设计
-
组合类构造函数定义的一般形式:
类名::类名(形参表) :内嵌对象1(形参表),内嵌对象2(形参表),…… { 本类的初始化 }
注:初始化列表,可以用于完成内嵌对象的初始化
-
对类中一般的数据成员也可以这样初始化
Point::Point(float h , float v):x(h) , y(v){ }
组合类的构造函数调用
- 声明一个组合类的对象时,不仅它自身的构造函数将被调用,而且还将调用其内嵌对象的构造函数。
构造函数调用顺序
先调用内嵌对象的构造函数(按声明时出现的顺序,先声明者先构造)。然后执行本类构造函数的函数体。(析构函数的调用顺序相反)
- 若调用默认构造函数(即无形参的),则内嵌对象的初始化也将调用相应的默认构造函数
- 如果声明组合类的对象时没有指定对象的初始值,则自动调用无形参的构造函数。这时相应地,也调用内嵌对象的无形参的构造函数。
- 析构函数的调用执行顺序与构造函数刚好相反。
- 内嵌对象的构造顺序与内嵌对象的定义顺序相同
类的组合举例:
class Part
{
public:
Part() { };
Part (int i) { val=i; }
~Part() { };
void Print(){ cout<<val<<endl; }
private:
int val;
};
class Whole
{ public:
Whole() { };
Whole (int i, int j, int k);
~Whole(){ };
void Print();
private:
Part one;
Part two;
int date;
};
Whole::Whole()
{ date=0; }
Whole::Whole (int i, int j, int k): two(i),one(j)
{ date=k; }
void Whole::Print()
{ one.Print();
two.Print();
cout<<date<<endl;
}
void main()
{
Whole w(5, 6, 7);
w.Print();
}
//输出结果:
//6
//5
//7
例:类的组合—-线段距离Distance类
#include < iostream>
#include < cmath>
class point //point类声明
{ public:
point (int xx = 0 , int yy = 0 )
{ X = xx , Y = yy ;}
point( point &p);
int Getx( ) { return X;)
int Gety( ) { return Y;)
private:
int x , y ;
};
point::point(point &p)
{
X = p.x ;
Y = p.y ;
cout<<“Point拷贝构造函数被调用”<<endl;} //类的组合
class Distance //Distance类的声明
{
public: //外部接口
Distance ( Point xp1 , Point xp2 );
double GetDis( ) { return dist ; }
private:
Point p1 , p2 ;
double dist ;
};
// 组合类的构造函数
Distance::Distance( Point xp1 , Point xp2 ):p1(xp1) , p2(xp2)
{
cout<<“Distance构造函数被用”<<endl;
double x = double( p1.Getx( ) – p2.Getx( ) );
double y = double( p1.Gety( ) – p2.Gety( ) );
dist = sqrt( x * x + y * y ) ;
}
// 主函数
void main( )
{
//声明Point类的对象
Point myp1( 1 , 1) , myp2( 4 , 5 );
//声明Distance类的对象
Distance myd ( myp1 , myp2 ) ;
cout<<“The distance is :” ;
cout<<myd.GetDis( ) << endl ;
}
//程序的运行 结果为:
//Point拷贝构造函数被调用
//Point拷贝构造函数被调用
//Point拷贝构造函数被调用
//Point拷贝构造函数被调用
//Distance构造函数被调用
//The distance is : 5
前向引用声明
- 类应该先声明,然后再使用
- 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。
- 前向引用声明只为程序引入一个代表该类的标识符,但具体声明在其它地方。
- 注意:在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联函数中使用该类的对象。
举例:
class B; //前向引用声明
class A
{ public:
void f(B b);
};
class B
{ public:
void g(A a);
};
类模板(class template)
-
通用代码重用
-
类模板
使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括系统预定义的和用户自定义的)。
-
参数化程序设计
在多数情况下,越通用的代码越能够重用
-
模板是C++支持参数化的工具
实现参数化多态性,就是将程序所处理的对象的类型参数化
类模板声明形式
template <形式类型参数表>
class classname
{ //类声明体 };
-
“形式类型参数表”中可包含下列内容
-
class标识符
指明可以接受一个类型参数。
-
类型说明符 标识符
指明可以接受一个由“类型说明符”所规定类型的常量作为参数
-
-
模板类的成员函数必须是函数模板
template <形式类型参数表> 返回类型 classname <类型名表> :: MemberFuncName1 (形式参数表) { //成员函数定义体 } template <形式类型参数表> 返回类型 classname <类型名表> ::MemberFuncName2 (形式参数表) { //成员函数定义体 }
-
类模板(包括成员函数的定义)不是一个实实在在的类,只是对类的描述
-
建立类模板之后,可用以下方式创建类模板的实例:
classname <实际存在类型参数表> object
其中实在类型参数表和类模板中形式类型参数表匹配。
classname <实际存在类型参数表> 是模板类( template class ),object是该模板类的一个对象。
举例:
#include <iostream>
template <class T, int i>
class TestClass
{
public:
T buffer[i];
void setArray( );
void showArray();
};
template <class T, int i>
void TestClass<T,i>::setArray( )
{ for(int j=0;j<i;j++)
cin>>buffer[j];
};
template <class T, int i>
void TestClass<T,i>::showArray()
{ for(int j=0;j<i;j++)
cout<<buffer[j];
cout<<endl;
};
void main(void)
{
TestClass<char, 5> ClassInst;
ClassInst.setArray();
ClassInst.showArray();
}
//运行结果:
//a
//b
//c
//d
//e
//abcde
类模板和模板类
- 类模板和模板类的区别:
- 类模板是模板的定义,不是一个 实实在在的类,定义中用到通用形式 类型参数。
- 模板类是实实在在的类定义,是 类模板的实例化。形式类型参数被实 际类型所代替
例:
Template <class T>
Class Store
{ Private:
T item;
Public:
Store ( );
T GetElem ( );
void Putelem (T x);
};
Void main(void)
{ Store <int> s1, s2;
Store <Student> s3;
}
//其中Store<int> s1,s2;
//模板生成函数为:
//Class Store
//{
//Private:
// int item;
//Public:
// Store ( );
// int GetElem ( );
// void Putelem (int x);
//};
//其中Store <Student> s3;
//模板生成函数为:
//Class Store
//{
//Private:
// Student item;
//Public:
// Store ( );
// Student GetElem ( );
// void Putelem (Student x);
// };
类模板应用举例
#include <iostream>
#include <stdlib>
// 结构体Student
struct Student
{
int id; //学号
float gpa; //平均分
};
template <class T>
//类模板:实现对任意类型数据进行存取
class Store
{ private:
T item; // 用于存放任意类型的数据
int haveValue; // 用于标记item是否已被存入内容
public:
Store(void); // 缺省形式(无形参)的构造函数
T GetElem(void); //提取数据函数
void PutElem(T x); //存入数据函数
};
// 默认形式构造函数的实现
template <class T>
Store<T>::Store(void): haveValue(0) {}
template <class T> // 提取数据函数的实现
T Store<T>::GetElem(void)
{ // 如果试图提取未初始化的数据,则终止程序
if (haveValue == 0)
{ cout << "No item present!" << endl;
exit(1); }
return item; // 返回item中存放的数据
}
template <class T> // 存入数据函数的实现
void Store<T>::PutElem(T x)
{ haveValue++; // 将haveValue 置为 TRUE,
//表示item中已存入数值
item = x; // 将x值存入item
}
void main(void)
{ Student g= {1000, 23};
Store<int> S1, S2;
S1.PutElem(3);
S2.PutElem(-7);
cout << S1.GetElem( ) << " " << S2.GetElem( ) << endl;
S3.PutElem(g);
cout << "The student id is " <<S3.GetElem( ).id << endl; cout << "Retrieving object D " ;
cout << D.GetElem( ) << endl;
//输出对象D的数据成员,由于D未经初始化,
//在执行函数D.GetElement( )时出错
}
//运行结果:
//3 -7
//The student id is 1000
//Retrieving object D No item present!
总结
-
类的组合
-
组合类构造函数定义的一般形式:
类名::类名(形参表) :内嵌对象1(形参表),内嵌对象2(形参表),...... { 本类的初始化 }
-
前向引用声明
-
类模板
template <形式类型参数表> class classname { //类声明体 };
-
使用一个模板类来建立对象时,声明形式
classname <实在类型参数表> object;