Upload
leah-oneill
View
155
Download
12
Embed Size (px)
DESCRIPTION
程序设计实习 第十一讲 类和对象 http://ai.pku.edu.cn/cpp2010/gw/shiyanban/. 面向对象语言的历史. Simula :1967 年, Ole-Johan Dahl 和 Kristen Nygaard 。发布 Simula 67 提出了类( class) 和子类( subclass) 的概念 , 虽然实现并不是很完整。但这是语言发展史上的一个重要的里程碑。 - PowerPoint PPT Presentation
Citation preview
程序设计实习
第十一讲类和对象
http://ai.pku.edu.cn/cpp2010/gw/shiyanban/
面向对象语言的历史Simula :1967 年, Ole-Johan Dahl 和
Kristen Nygaard。发布 Simula 67 提出了类( class) 和子类( subclass) 的概念 , 虽然实现并不是很完整。但这是语言发展史上的一个重要的里程碑。
Smalltalk: 1971 年, Xerox Palo Alto Research Center 的 Alan Kay 发明 Smalltalk 。是公认的完整实现的面向对象的程序设计语言
C++: 1979 年, Bell 实验室 Bjarne Stroustrup 开始发明,刚开始的版本叫: C with Classes 。 1983 年10 月份的时候正式命名为 C++ 。
Java: 1995 年, Sun 公司发布C#: 2002 年 1 月,微软公司公布 .NET Framework
1.0 正式版。与此同时, Visual Studio.NET 2002也同步发行。
C++语言的历史 1983 年 8 月, 第一个 C++ 实现投入使用 1983 年 12 月, Rick Mascitti 建议命名为 CPlusPlus ,即 C++ 。 1985 年 10 月, Bjarne 博士完成了经典巨著 The C++ Programming Language 第一版 1991 年 6 月, The C++ Programming Language 第二版完成。 1994 年 8 月, ANSI/ISO 委员会草案登记。 1997 年 7 月, The C++ Programming Language 第三版完成。 10 月, ISO 标准通过表决被接受 1998 年 11 月, ISO 标准被批准。 接下来让我们一起看一下 C++ 编译器的“第一个”: 1985 年 10 月, Cfront Release 1.0 发布。 1987 年 12 月, GNU C++ 发布。 1988 年 1 月,第一个 Oregon Software C++ 发布。 6 月,第一个 Zortech C++ 发布。 1990 年 5 月,第一个 Borland C++ 发布。 1992 年 2 月,第一个 Dec C++ 发布。 3 月,第一个 Microsoft C++ 发布。 5 月,第一个 IBM C++ 发布。
结构化程序设计:
程序 = 数据结构 + 算法
程序由全局变量以及众多相互调用的函数组成 .
算法以函数的形式实现,用于对数据结构进行操作。
为什么需要面向对象?
main()
Sub1() Sub2() Sub3()
Sub1_1() Sub3_1() Sub3_2()…………………
var1 var2 var3 …………………变量:
结构化程序设计的程序模式:
结构化程序设计导致数据和操作被分离开,容易导致对一种数据的操作分布在整个程序的各个角落,而一个操作也可能会用到很多种数据,在这种情况下,对数据和操作的任何一部分进行修改都会变得很困难。
结构化程序设计的程序模式:
结构化程序设计中,函数和其所操作的数据结构,没有直观的联系,即难以一下子看出来,某个数据结构到底有哪些函数可以对它进行操作,以及某个函数到底是用来操作哪些数据结构的
这样,当某个数据结构的值不正确时,难以找出到底是那个函数导致的。
要掌握每个数据结构到底是如何构成的,也十分费力
为什么需要面向对象?
而且,结构化程序设计中,任何两个函数之间都有可能存在调用关系,要搞清楚函数之间的调用关系变得十分困难
结构化程序设计没有“封装”和“隐藏”的概念,要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序的维护、扩充
为什么需要面向对象?
总之,结构化的程序,在规模庞大时,会变得难以理解,难以扩充(增加新功能),查错困难,而且难以重用。
( 重用:在编写某个程序时,发现其需要的某项功能,在现有的某个程序里已经有了相同或类似的实现,那么自然希望能够将那部分代码抽取出来,在新程序中使用。但是由于老程序大量函数、变量之间的关系错综复杂,要抽取这部分代码,会变得十分困难。 )
为什么需要面向对象?
面向对象的程序设计方法,能够较好解决上述问题
面向对象的程序 = 类 + 类 + …+ 类
设计程序的过程,就是设计类的过程
为什么需要面向对象?
面向对象的程序设计方法,以一种更符合自然的方式,将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性),并且将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构 ( 这一步叫“抽象”)。然后,通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。
为什么需要面向对象?
main()
面向对象的程序模式:
class1
var1
Sub1()Sub1_1()
class2
var2
Sub2()
class3
var3
Sub3()Sub3_1()Sub3_2()
写一个程序,输入矩形的长和宽,输出面积和周长比如对于“矩形”这种东西,要用一个类来表示,
该如何做“抽象”呢? 矩形的属性就是长和宽。因此需要两个变量,
分别代表长和宽。一个矩形,可以有哪些行为呢(或可以对矩形进
行哪些操作)?矩形可以有设置长和宽,算面积,和算周长这三种行为(当然也可以有其他行为)。这三种行为,可以各用一个函数来实现,他们都需要用到长和宽这两个变量。
从客观事物抽象出类的例子
将长、宽变量和设置长,宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。长、宽变量成为该“矩形类”的“成员变量”,三个函数成为该类的“成员函数”
成员变量和成员函数统称为类的成员
实际上,“类”看上去就像“带函数的结构”,成员变量就相当于结构的域
从客观事物抽象出类的例子
从客观事物抽象出类的例子
class CRectangle{
public:int w,h;int Area() {
return w * h; }int Perimeter() {
return 2 * ( w + h); }void Init( int w_,int h_ ) {
w = w_; h = h_;}
}; //必须有分号
从客观事物抽象出类的例子int main( ) {
int w,h; CRectangle r; //r 是一个对象 cin >> w >> h; r.Init( w,h); cout << r.Area() << endl << r. Perimeter(); return 0;
}
通过类,可以定义变量。类定义出来的变量,也称为类的实例,就是我们所说的“对象” C++ 中,类的名字就是用户自定义的类型的名字。可以象使用基本类型那样来使用它。CRectangle 就是一种用户自定义的类型
对象的内存分配和结构变量一样, 对象所占用的内存空间的大小,等于所有成员变量的大小之和。
每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会影响到另一个对象。
如何使用类的成员变量和成员函数
用法 1 :对象名 . 成员名CRectangle r1,r2;r1.w = 5;r2.Init(5,4);
Init 函数作用在 r2 上,即 Init 函数执行期间访问的 w 和 h 是属于 r2 这个对象的 ,执行 r2.Init 不会影响到 r1
用法 2. 指针 -> 成员名CRectangle r1,r2;CRectangle * p1 = & r1;CRectangle * p2 = & r2;p1->w = 5;p2->Init(5,4); //Init 作用在 p2指向的对象上
如何使用类的成员变量和成员函数
用法 3 :引用名 . 成员名CRectangle r3;
CRectangle & rr = r3;rr.w = 5;rr.Init(5,4); //rr 的值变了, r3 的值也变
void PrintRectangle(CRectangle & r){
cout << r.Area() << ","<< r.Perimeter();}
引用的概念类型名 & 引用名 = 某变量名 ; 上面的写法就定义了一个引用,并将其初始化为引用某个变量。
某个变量的引用,和这个变量是一回事,相当于该变量的一个别名
int n = 4;int & r = n;r = 4; cout << r; //输出 4;cout << n; //输出 4;n = 5;cout << r; //输出 5
定义引用时一定要将其初始化成引用某个变量,不初始化编译不过。
引用只能引用变量,不能引用常量和表达式
C 语言中,要写交换两个整型变量值的函数,只能通过指针void swap( int * a, int * b){
int tmp;tmp = * a; * a = * b; * b = tmp;
}int n1, n2;swap(& n1,& n2) ; // n1,n2的值被交换
引用的作用
有了引用后:void swap( int & a, int & b){
int tmp;tmp = a; a = b; b = tmp;
}int n1, n2;swap(n1,n2) ; // n1,n2的值被交换
引用的作用
函数的返回值可以是引用 ,如:
#include <iostream>using namespace std;int n = 4;int & SetValue() { return n; }main() {
SetValue() = 40;cout << n;
}该程序输出结果是 40
引用作为函数的返回值
类的成员函数的另一种写法
成员函数体和类的定义分开写class CRectangle{
public:int w,h;int Area(); // 成员函数仅在此处声明
int Perimeter() ; void Init( int w_,int h_ );
};
int CRectangle::Area() { return w * h;
}int CRectangle::Perimeter() {
return 2 * ( w + h); }void CRectangle::Init( int w_,int h_ ) {
w = w_; h = h_;}
CRectangle::说明后面的函数是 CRectangle 类的成员函数,而非普通函数。那么,一定要通过对象或对象的指针才能调用。
类的成员函数的另一种写法
对象成员的访问权限
在类的定义中,用下列权限关键字来说明对象成员的访问权限 private: 私有成员,只能在成员函数内访问 public : 公有成员,可以在任何地方访问 protected: 保护成员,以后再说
以上三种关键字出现的次数和先后次序都没有限制
定义一个类class className {private:
私有属性和函数public:
公有属性和函数protected:
保护属性和函数};
说明类成员的可见性
如过某个成员前面没有上述关键字,则缺省地被认为是私有成员class Man {int nAge; //私有成员char szName[20]; // 私有成员
public:SetName(char * szName){strcpy( szName); } ;
};
对象成员的访问权限
在类的成员函数内部,能够访问: 当前对象的全部属性、函数 同类其它对象的全部属性、函数。
在类的成员函数以外的地方,只能够访问该类对象的公有成员
对象成员的访问权限
#include <string.h>class CHero {private:
char szMenpai[10]; //侠客的门派public :
int nStrength, nForce; //生命值和攻击力void SetMenpai(char * menpai);void Attack( CHero & hero);
};void CHero::SetMenpai( char * menpai){
strcpy( szMenpai,menpai); //ok}void CHero::Attack( CHero & hero) {
hero.nStrength -= nForce; // 在成员函数内部可以访// 问同类对象的私有成员
}
int main(){
CHero Hero1,Hero2;strcpy(Hero1.szMenpai,“Shaolin”); // 错 , 不能访问私有成
员Hero1.SetMenpai( "Shaolin"); // okHero1.nStrength = 100; //okreturn 0;
}设置私有成员的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
设置私有成员的机制,叫“隐藏”
例如,如果将上面的程序移植到内存空间紧张的手持设备上,希望将 szMenpai 改为 int nMenpai, 即门派编号,然后根据门派编号能查到门派名字,如果 szMenpai 是私有,那么就要找出所有类似
strcpy(Hero1.szMenpai,”Shaolin”);
这样的语句进行修改,这样做很麻烦。
如果将 szMenpai 为私有,那么程序中就不可能出现(除非在类的内部)strcpy(Hero1.szMenpai,”Shaolin”);这样的语句,所有对 szMenpai 的访问都是通过成员函数来进行,比如:
Hero1.SetMenpai(“Shaolin”); 那么,就算 szPenpai 变成 int nMenpai 了,上面的语句也不需要找出来修改,只要改 SetMenpai 成员函数,在里面根据门派名查到门派编号再赋值给 nMenpai 就可以了
函数重载:
一个或多个函数,名字相同,然而参数个数或参数类型互不相同,这叫做函数的重载
如 :int Max(double f1,double f2) {
}int Max(int n1,int n2) {
}int Max(int n1,int n2,int n3) {
}
函数重载使得函数命名变得简单
函数的缺省参数:C++中,写函数的时候可以让参数有缺省值,那么调用函
数的时候,若不写参数,参数就是缺省值。
void func( int x1 = 2, int x2 = 3) { }
func( ) ; //等效于 func(2,3)func(8) ; //等效于 func(8,3)func(, 8) ; //不行
函数参数可缺省的目的在于提高程序的可扩充性。即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数
成员函数也可以重载 (普通函数也可以) 成员函数和构造函数可以带缺省参数 (普通函数也可
以)
#include <iostream.h>class Location { private :
int x, y;public:
void init( int x=0 , int y = 0 ); void valueX( int val ) { x = val ;} int valueX() { return x; }
};void Location::init( int X, int Y){x = X;y = Y;
}
成员函数的重载及参数缺省
int main() {Location A,B;A.init(5);A.valueX(5);cout << A.ValueX();return 0;
}
使用缺省参数要注意避免有函数重载时的二义性#include <iostream.h>class Location {
private : int x, y;
public: void init( int x =0, int y = 0 );
void valueX( int val = 0) { x = val } int valueX() { return X; }
};
Location A;A.valueX(); // 错误,编译器无法判断调用哪个 valueX
构造函数 成员函数的一种,名字与类名相同,可以有参数,不能有返回值 (void 也不行 ) ,作用是对对象进行初始化,如给成员变量赋初值。 如果定义类时没写构造函数,则编译器生成一个缺省的无参数的构造函数。缺省构造函数无参数,什么也不做。 如果定义了构造函数,则编译器不生成缺省的无参数的构造函数。 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。 为类编写构造函数是好的习惯,能够保证对象生成的时候总是有合理的值。 一个类可以有多个构造函数
class Complex {private :
double real,imag;
public:void Set( double r,double i);
}; // 构造函数为编译器自动生成的无参数构造函数。
Complex c1; // 构造函数被调用Complex * pc = new Complex; // 构造函数被调用
class Complex {private :
double real,imag;public:
Complex( double r, double i = 0);}; // 定义了一个构造函数。Complex::Complex( double r,double i){
real = r; imag = i;}Complex c1; // error, 没有参数Complex * pc = new Complex ; // error, 没有参数Complex c1( 2); // OKComplex c1(2,4), c2(3,5);Complex * pc = new Complex(3,4)
可以有多个构造函数,参数个数或类型不同 class Complex {
private : double real,imag;
public:void Set( double r,double i);Complex( double r,double i );Complex ( double r);Complex ( Complex c1, Complex c2);
}; Complex::Complex( double r,double i){
real = r; imag = i;}
Complex::Complex( double r){
real = r; imag = r;}Complex::Complex ( Complex c1, Complex c2); {
real = c1.real+c2.real;imag = c1.imag+c2.imag;
}Complex c1( 3) , c2 (1,0), c3( c1,c2);
// c1 = { 3,3}, c2 = {1,0}, c3 = { 4, 3};
析构函数 ( destructors )
成员函数的一种,名字与类名相同,在前面加 ‘ ~’ 没有参数和返回值,一个类最多只能有一个析构函数。析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。 如果定义了析构函数,则编译器不生成缺省析构函数。
class chunk {private :
char * p;public:
chunk () {p = new char[10];
}~chunk () ;
};chunk::~chunk(){
delete [] p;}
静态成员变量和静态成员函数在说明前面加了 static 关键字的成员普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
如果是 public 的话,那么静态成员在没有对象生成的时候也能直接访问
class Apple {private :
int nWeight;static int nTotalWeight;static int nTotalNumber;
public:Apple( int w) ;~Apple( ) ;static void PrintTotal();
};
Apple::Apple( int w) {
nWeight = w;nTotalWeight += w;nTotalNumber ++;
}Apple::~Apple() {
nTotalWeight -= nWeight;nTotalNumber --;
}void Apple::PrintTotal () { printf( “%d,%d”, nTotalWeight, nTotalNumber); }
int Apple::nTotalWeight = 0;int Apple::nTotalNumber = 0;
// 必须在定义类的文件中对静态成员变量进行一次说明 // 或初始化。否则编译能通过,链接不能通过
int main (){
Apple p1(3), p2( 5);Apple::nTotalWeight = 6; // Wrong , 私有Apple::PrintTotal();p1.PrintTotal();
}
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
void Apple::PrintTotal () { printf( “ % d%d,%d”, nWeight, nTotalWeight, nTotalNumber); }
Apple a;a.PrintTotal(); // 解释得通Apple::PrintTotal(); 解释不通, nWeight 到底是属于那个对象的?
sizeof 运算符不会计算静态成员变量 class CMyclass { int n;
static s; };则 sizeof( CMyclass ) 等于 4
访问静态成员,可以通过 类名 :: 成员名 的方式 : CMan:: GetTotal();
也可以和普通成员一样采取
对象名 . 成员名 Jimmy.GetTotal();
指针 -> 成员名 CMan * p = & Jimmy; p->GetTotal();
引用 . 成员名 CMan & r = Jimmy; r.GetTotal();
但上面这三种方式,效果和类名 :: 成员名 没区别,静态成员变量不会属于某个特定对象,静态成员函数不会作用于某个特定对象。
静态成员
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
静态成员函数本质上是全局函数。
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解