29
1 第8第 第第第第第第 8.1 第第第第第第第第第 8.2 第第第 8.4 第第第第第第第 第第第第第第第 8.5 第第第 8.6 第第第第第第 8.3 第第第

第 8 章 类和复杂对象

  • Upload
    kipp

  • View
    103

  • Download
    4

Embed Size (px)

DESCRIPTION

第 8 章 类和复杂对象. 8.1 对象指针和对象引用. 8.4 对象数组和指向 对象数组的指针. 8.2 常对象. 8.5 堆对象. 8.6 对象的生存期. 8.3 子对象. 8.1 对象指针和对象引用. 8.1.1 对象指针 对象指针是对象的内存 ( 首 ) 地址,对象指针常常用来做函数的参数和函数的返回值。 【 例 8.1】 分析程序,注意对象指针作函数参数。. #include class M { public: M(int i,int j){ x=i;y=j; } - PowerPoint PPT Presentation

Citation preview

Page 1: 第 8 章  类和复杂对象

1

第 8 章 类和复杂对象

8.1 对象指针和对象引用

8.2 常对象

8.4 对象数组和指向 对象数组的指针

8.5 堆对象

8.6 对象的生存期8.3 子对象

Page 2: 第 8 章  类和复杂对象

2

8.1 对象指针和对象引用 8.1.1 对象指针 对象指针是对象的内存 ( 首 ) 地址,对象

指针常常用来做函数的参数和函数的返回值。

【例 8.1 】分析程序,注意对象指针作函数参数。

Page 3: 第 8 章  类和复杂对象

3

#include<iostream.h>class M{ public: M(int i,int j){ x=i;y=j; } M( ){ x=y=0; } void Setxy( int i,int j) { x=i;y=j; } void Copy(M *m); void Print( ){ cout<<x<<','<<y<<endl; } private: int x,y; };void M::Copy( M *m){x=m->x;y=m->y; }void fun(M m1,M *m2){ m1.Setxy(20,45);m2->Setxy(56,78); }void main( ){ M p(18,25),q,*pq; q.Copy(&p); pq=&q; p.Print( ); pq->Print( ); fun(p,&q); p.Print( ); pq->Print( );} 注意,用对象指针作函数形参,可实现双向传递数的作用。

Page 4: 第 8 章  类和复杂对象

4

8.1.2 对象引用【例 8.2 】将例 8.1 参数传递的指针型改为引用型#include<iostream.h>class M{ public: M(int i,int j){ x=i;y=j; } M( ){ x=y=0; } void Setxy( int i,int j) { x=i;y=j; } void Copy(M &m); void Print( ){ cout<<x<<','<<y<<endl; } private: int x,y; };void M::Copy( M &m){x=m.x;y=m.y; }void fun(M m1,M &m2){ m1.Setxy(20,45);m2.Setxy(56,78); }void main( ){ M p(18,25),q,&pq=q; q.Copy(p); p.Print( ); pq.Print( ); fun(p,q); p.Print( ); pq.Print( );}

Page 5: 第 8 章  类和复杂对象

5

【例 8.3 】对象引用作函数返回值

#include<iostream.h>class N{ public: N(int i,int j){ x=i;y=j; } void Print( ){cout<<x<<','<<y<<endl; } int Getxy( ){ return x+y; } private: int x,y; };N &fun( ){ static N a(23,45); return a; }void main( ){ N p(18,25); p.Print( ); cout<<fun( ).Getxy( )<<endl; fun( )=p; cout<<fun( ).Getxy( )<<endl;} 注意,函数调用(函数返回值)可以接受赋值! 返回值为引用类型的,返回的必须是静态或全局的的,

否则,返回值可能是不确定的。

Page 6: 第 8 章  类和复杂对象

6

8.1.3 this 指针当成员函数被调用时,系统自动生成一个指向所在对象的指针,称为 this 指

针,一般省略不写。【例 8.4 】使用 this 指针的例子。#include<iostream.h>class A{ public:A(int i,int j){ a=i;b=j; } A(){a=b=0;} void Copy(A &); int Returna(){ return a;} // 此处 a 实际是 this->a int Returnb(){ return b;} private:int a,b;};void A::Copy(A &aa){if(this!=&aa)*this=aa;}void main(){ A a1,a2(13,24); a1.Copy(a2);cout<<a1.Returna()+a2.Returna()<<','<<a1.Returnb()+a2.Returnb()<<endl;} 注意,上述程序与书本上的区别。

Page 7: 第 8 章  类和复杂对象

7

8.2 常对象常对象像常量一样,在程序中被使用,但不能被改变,

常对象只能对用常成员函数,注意区分常对象与常成员。

【例 8.5 】使用常对象的例子。#include<iostream.h>class F{ public:F(int i,int j){ f1=i;f2=j; } void Print(){ cout<<f1<<','<<f2<<endl;} void Print()const{cout<<f1<<','<<f2<<endl; } private:int f1,f2; };void main(){ F a1(3,5); const F a2=F(6,7); F const a3(8,2); a1.Print(); a1=a2; a1.Print(); a3.Print();}注意,①常对象使用不同的定义方法 ② 为什么要定义两个 Print 函数?

Page 8: 第 8 章  类和复杂对象

8

【例 8.6 】常对象指针 ( 分两种 : 常指针、指向常对象的指针 ) 例子

#include<iostream.h>

class M

{ public:M(int i){m=i;} int Returnm( )const {return m;}

private:int m; };

int fun(const M *m1,const M *m2)

// 星号不在 const 前面,表明指针指向的对象是常量,对象值不可变{ int mul=m1->Returnm( )/m2->Returnm( ); return mul; }

void main( )

{ M m1(77), m2(9), *const pm1=&m1;

// 星号在 const 前面,表明指针是常量,指针值不可变 cout<<pm1->Returnm( )<<endl;

*pm1=m2; // 指针值不变,实际上是将 m2 赋给了 m1 cout<<pm1->Returnm( )<<endl<<m1.Returnm( )<<endl;

int k=fun(&m1,&m2);// 对象地址对应常对象指针,保证 m1 、 m2 的值不变

cout<<k<<endl;

}

注意,区分指向常对象的指针和指向对象的常指针

Page 9: 第 8 章  类和复杂对象

9

【例 8.7 】常对象引用例子#include<iostream.h>class M{ public:M(int i){ m=i;} int Returnm( ) const {return m; }// 常成员函数 private:int m; };int fun(const M &m1,const M &m2){ int mul=m1.Returnm( )*m2.Returnm( ); return mul; }void main( ){M m1(9),m2(11); M const &rm=m1; // 常对象引用 cout<<rm.Returnm( )<<endl; m2=rm; // 常对象引用调用 int k=fun(m1,m2); cout<<k<<endl;}

Page 10: 第 8 章  类和复杂对象

10

8.3 子对象 一个类的成员是另一个类的对象,这种成员对象称

为子对象。 含有子对象的类的构造函数的参数表后面用冒号分

隔成员初始化列表,成员初始化列表应包含:子对象初始化、常数据成员初始化、引用数据成员初始化,其他数据成员的初始化可放在这里,也可放在函数体中。带有成员初始化列表的构造函数格式为:

构造函数名 ( 参数表 ): 成员初始化列表 { 函数体 }

Page 11: 第 8 章  类和复杂对象

11

【例 8.8 】子对象使用例子。#include<iostream.h>class A{ public:A( ){a=0;cout<<" 隐含构造函数调用。 "<<a<<endl;} A(int i){a=i;cout<<" 构造函数调用。 "<<a<<endl; } ~A( ){cout<<" 析构函数调用。 "<<a<<endl;} int Returna( ){return a;} private: int a; };class B{public: B( ):b1(b),b2(0) { b=0; cout<<" 隐含构造函数调用。 "<<b<<endl;} B(int,int,int,int); ~B( ){cout<<" 析构函数调用。 "<<b<<endl;} void Print( ){cout<<a1.Returna( )<<','<<a2.Returna( )<<endl; cout<<b<<','<<b1<<','<<b2<<endl; } private: A a1,a2;int b;int &b1;const int b2; };

Page 12: 第 8 章  类和复杂对象

12

B::B(int i,int j,int k,int l):a2(i),a1(j),b2(k),b1(b){ b=l;cout<<" 构造函数调用。 "<<b<<endl; }void main ( ){ B x,y(1,2,3,4); y.Print( ); cout<<endl;}注意,① 类 B 的两个构造函数的区别

② 类 B 定义中对类 A 的引用。

③ 程序在什么时候创建了几个对象?何时撤销?

Page 13: 第 8 章  类和复杂对象

13

输出结果:隐含构造函数调用。 0 //1隐含构造函数调用。 0 //2隐含构造函数调用。 0 //3构造函数调用。 2 //4构造函数调用。 1 //5构造函数调用。 4 //62,1 //74,4,3 //8

析构函数调用。 4 //10析构函数调用。 1 //11析构函数调用。 2 //12析构函数调用。 0 //13析构函数调用。 0 //14析构函数调用。 0 //15

//9

Page 14: 第 8 章  类和复杂对象

14

8.4 对象数组和指向对象数组的指针 8.4.1 对象数组对象数组的元素是同一类的对象。如定义类 A : class A{public:A(int i,int j){a1=i;a2=j;}private:int a1,a2;};

则可定义类 A 的对象数组: A a[5]={A(3,1),A(4,2),A(5,3),A(6,4),A(7,5)};

上述定义的数组 a 有 5 个元素: a[0] 、 a[1] 、 a[2] 、 a[3] 、 a[4]

在定义数组时, 5 次调用构造函数初始化作为数组元素的对象。当然也可以先定义数组,再用给数组元素赋值的办法对数组元素(对象)初始化: A a[5];

a[0]=A(3,1);a[1]=A(4,2); a[2]=A(5,3); a[3]=A(6,4); a[4]=A(7,5);

Page 15: 第 8 章  类和复杂对象

15

【例 8.9 】使用对象数组的例子。

#include<iostream.h>class M{ public:M(int i,int j){m=i;n=j;cout<<" 构造函数调用。 \n";} M( ){m=n=0;cout<<" 隐含构造函数调用。 \n";} ~M( ){cout<<" 析构函数调用。 \n";} int Getm( ){return m;} int Getn( ){return n;} private:int m,n;}; M mm1[2]; // 定义全局对象(外部对象)void main( ){ M mm2[4]={M(2,3),M(5,6),M(7,8),M(2,5)}; // 定义局部对象 ( 内部对象 ) mm1[0]=mm2[0]; mm1[1]=M(5,9); cout<<"mm1[0]=("<<mm1[0].Getm( )<<','<<mm1[0].Getn( )<<")\n"; cout<<"mm1[1]=("<<mm1[1].Getm( )<<','<<mm1[1].Getn( )<<")\n"; for(int i=0;i<4;i++) cout<<"mm2["<<i<<"]=("<<mm2[i].Getm( )<<','<<mm2[i].Getn( )<<")\n";}

Page 16: 第 8 章  类和复杂对象

16

8.4.2 对象指针数组 数组元素是指向同类对象的指针。 对象指针数组的定义方式为: 类名 * 数组名 [ 数组元素个数 ]; 注意,此类数组的每一个元素均为对象指针,

指针的初始化应是对象的首地址。使用指针指向的对象的成员,应注意,首先是数组元素,其次是对象指针,即:

数组名 [ 下标 ]-> 数据成员名 数组名 [ 下标 ]-> 成员函数名 ( 参数表 )

Page 17: 第 8 章  类和复杂对象

17

【例 8.10 】使用对象指针数组的例子。#include<iostream.h>class M{ public:M(int i,int j){m=i;n=j;cout<<" 构造函数调用。 \n";} M( ){m=n=0;cout<<" 隐含构造函数调用。 \n";} ~M( ){cout<<" 析构函数调用。 \n"; } void Print(){cout<<'('<<m<<','<<n<<")\n";} private:int m,n;};void main( ){ M m1(2,3),m2(5,6),m3(7,8),m4(2,5),m5(5,9); M *pm[5]={&m1,&m2,&m3,&m4,&m5}; for(int i=0;i<5;i++) pm[i]->Print();}请注意:课本上红色表示的项的位置有错误!请考虑:程序中红色表示的项,等价写法是什么?

(*(pm+i))->Print();

Page 18: 第 8 章  类和复杂对象

18

8.4.3 指向对象数组的指针 注意区分指针指向的是数组元素还是数组本身。 指针指向对象数组元素与指向一般对象没有本质

区别,见例 8.11 (略) 若指针指向的是对象数组,则因为一维数组与元

素指针相当,故指向数组的指针相当于二级指针。 指向数组的指针的定义方法为: 类名 (* 指针名 )[ 数组元素个数 ]; 注意,定义方法与前面定义对象指针数组的区别。 此类指针常常与二维数组相对应,使用二维数组

名对此类指针初始化。

Page 19: 第 8 章  类和复杂对象

19

【例 8.12 】使用对象数组指针的例子。

#include<iostream.h>class A{ public:A(int i,int j){a=i;b=j;} A( ){a=b=0;} void Print(){cout<<'('<<a<<','<<b<<")\n";} private:int a,b;};void main( ){ A aa[3][3]; int m(15),n(10); for(int i=0;i<3;i++) for(int j=0;j<3;j++) aa[i][j]=A(m+=2,n+=5); A (*pAaa)[3](aa); for(i=0;i<3;i++) { for(int j=0;j<3;j++) (*(*(pAaa+i)+j)).Print( ); cout<<endl; } }请考虑:程序中红色表示的项,是否有等价写法? (3 种 )

pAaa[i][j].Print();

(*(pAaa[i]+j)).Print();

(*(pAaa+i))[j].Print();

Page 20: 第 8 章  类和复杂对象

20

内存分配方式 内存分配方式有三种: 1 、从静态存储区域分配。 内存在程序编译的时候就已经分配好,这块内存在程序的整个运行 期间都存在。例如全局变量, static 变量。

2 、在栈上创建。 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,

函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3 、从堆上分配,亦称动态内存分配。 程序在运行的时候用 malloc 或 new申请任意多少的内存,程序员

自己负责在何时用 free 或 delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

Page 21: 第 8 章  类和复杂对象

21

8.5 堆对象8.5.1 运算符 new 和 delete1. 使用 new开辟内存空间,为指针变量赋值例如,定义指针变量: int *p;则可为指针赋值: p=new int; 或 p=new int(8);前者在内存中开辟存储一个 int 型变量空间,并将该空间的首地址赋给指针 p; 后者在前者的基础上,同时用 8 初始化那个 int 型变量。 使用 new 还可同时开辟连续多个同类变量的空间,供数组使用 , 如: int *pa; pa=new int[5];2. 使用 delete释放指针指向变量或数组占用的空间格式为: delete 指针名;或 delete[] 指针名;前者释放单个变量占有的空间,后者释放数组占有的空间。

Page 22: 第 8 章  类和复杂对象

22

【例 8.13 】使用 new 、 delete 的例子。

#include<iostream.h>

#include<stdlib.h>

void main( )

{ int *p,*pa;

p=new int(10);pa=new int[10];

if(!pa){cout<<" 错误! \n";exit(1);}// 当 pa==0 时,终止程序//exit(1) 为关闭文件 ,终止程序的函数 ,该函数定义在 stdlib.h

中 for(int i=0;i<10;i++)pa[i]=i+10;

for(i=0;i<10;i++)cout<<pa[i]+*p<<" ";

cout<<endl;

delete p;delete[]pa;

}

Page 23: 第 8 章  类和复杂对象

23

8.5.2 堆对象、堆对象数组的创建和释放

使用 new 、 delete 可以创建、释放堆对象和堆对象数组。【例 8.14 】创建、释放堆对象的例子。#include<iostream.h>class A{ public:A(int i,int j){a1=i;a2=j;cout<<" 构造函数调用。 \n";} ~A( ){cout<<" 析构函数调用。 \n";} void Print( ){cout<<a1<<','<<a2<<endl;} private:int a1,a2;};void main( ){A *pa1,*pa2; pa1=new A(12,9);pa2=new A(14,6); pa1->Print( ); pa2->Print( ); delete pa1; delete pa2;}

Page 24: 第 8 章  类和复杂对象

24

【例 8.15 】使用堆对象数组指针的例子。

#include<iostream.h>#include<string.h>class B{ public:B( ){cout<<" 隐含构造函数 \n";} B(char *s,double n){strcpy(name,s);b=n;cout<<" 构造函数 \n";} ~B(){cout<<" 析构函数。 "<<name<<endl;} void Getb(char *s,double &n){strcpy(s,name);n=b;} private:char name[80];double b;};void main( ){B *pb;double n;char s[80]; pb=new B[5]; pb[0]=B("wang",4.6); pb[1]=B("zhang",2.9); pb[2]=B("Li",8.2); *(pb+3)=B("Lu",3.5); *(pb+4)=B("Ma",7.1); for(int i=0;i<5;i++){pb[i].Getb(s,n);cout<<s<<','<<n<<endl;} delete[]pb; }

Page 25: 第 8 章  类和复杂对象

25

【例 8.16 】分析程序,思考问题。

#include<iostream.h>#include<string.h>class String{ public:String( ){Length=0;Buffer=0;} String(const char *str); void Setc(int index,char newchar); char Getc(int index)const; int GetLength( )const{return Length;} void Print( )const { if(Buffer==0)cout<<"空 \n";else cout<<Buffer<<endl;} void Append(const char *Tail); ~String( ){ delete[]Buffer; } private:int Length; char *Buffer; }; String::String(const char *str){ Length=strlen(str);Buffer=new char[Length+1];strcpy(Buffer,st

r);}void String::Setc(int index,char newchar){ if(index>0&&index<=Length)Buffer[index-1]=newchar; }

Page 26: 第 8 章  类和复杂对象

26

char String::Getc(int index) const{ if(index>0&&index<=Length)return Buffer[index-1];else return 0;}

void String::Append(const char *Tail){ char *tmp; Length+=strlen(Tail); tmp=new char[Length+1]; strcpy(tmp,Buffer);strcat(tmp,Tail); delete[]Buffer; Buffer=tmp; }void main( ){ String s0,s1("a string."); s0.Print( );s1.Print( );cout<<s1.GetLength( )<<endl; s1.Setc(5,'d'); s1.Print( ); cout<<s1.Getc(6)<<endl; String s2("this is "); s2.Append("a string."); s2.Print( ); cout<<s2.GetLength( )<<endl;}问题:函数 Setc(i,ch) 、 Getc(i) 、 Append(char *) 作用是什么?

Page 27: 第 8 章  类和复杂对象

27

8.6 对象的生存期 对象像变量一样,定义(创建)时分配内存,称为诞生;生

存期结束时,释放占有的内存,称为死亡。 按照生存期的长短不同,分 3 种:局部对象、全局对象、静

态对象。 定义在某函数体内或程序块内的对象称为局部对象,它在其

定义的范围内起作用,进入该范围时诞生,离开该范围时死亡,再次进入该范围时,再次新生。其生命期最短。

不在函数体(或程序块)中定义的对象称为全局对象。它在整个文件中有效,而且其他文件也可引用,但需用 extern进行声明。生命期就是程序的执行期。

定义时用增加关键字 static 的对象,称为静态对象。静态对象的作用域要看其定义的地方,分局部和全局(又称内部和外部);生命期同全局对象为程序的执行期。

Page 28: 第 8 章  类和复杂对象

28

【例 8.17 】分析程序,注意对象的生存期与作用域。

#include<iostream.h>#include<string.h>class A { public: A(char *str); ~A( ); private: char string[80]; }; A::A(char *str){ strcpy(string,str);cout<<" 构造函数被 "<<string<<" 调用。 \n";

}A::~A( ){ cout<<" 析构函数被 "<<string<<" 调用。 \n"; }void fun( ){ A A1(" 函数中的对象 "); static A A2(" 内部静态对象 "); cout<<" 在函数 fun( ) 中。 "; }A A3(" 全局对象 "); static A A4(" 外部静态对象 ");void main( ){ A A5("主函数中的局部对象 "); cout<<" 调用子函数 fun前,在主函数中。 \n"; fun( ); cout<<" 调用子函数 fun 后,回到主函数中。 \n";}

Page 29: 第 8 章  类和复杂对象

29

小结与作业 本章所讲知识实际上是第 7 章与前 6 章的混合 对象指针与对象数组可以统一理解 对象引用作参数时,可以双向传递,此时引用比

指针更方便 常对象与前 6 章讲过的常量类似,须注意使用方

法 堆对象将会经常使用 对象的生存期与变量的的生存期一样 作业:第 8 章所有习题