《C++程序设计教程》课件第2章_第1页
《C++程序设计教程》课件第2章_第2页
《C++程序设计教程》课件第2章_第3页
《C++程序设计教程》课件第2章_第4页
《C++程序设计教程》课件第2章_第5页
已阅读5页,还剩181页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2.1对象的创建2.2构造函数

2.3析构函数

2.4构造参数

2.5动态内存分配

2.6常量

2.7const对象

2.8静态成员

本章要点

练习

2.1.1对象创建顺序

在前一章中已介绍了定义对象的方法:先描述类,然后由类来定义对象。描述的类并不占用内存空间,当类定义了对象后才占用内存空间。当我们在一个程序中定义了多个不同作用域的对象时,就存在创建时的先后次序问题。

【例2-1】定义三个Desk类的对象,并说明创建对象的先后次序。2.1对 象 的 创 建运行结果:

weight=0size=0

weight=x…xsize=13 //weight是随机值

weight=0size=0

在本例中按照定义对象的方法分别定义了全局对象、局部对象和静态对象。从定义对象的位置来看,对象c和b都在main中定义,它们是局部对象,仅在main中有效;对象a的定义在函数之外,它是全局对象,从定义点开始到本文件结束都有效。当创建多个同类对象时,按定义的先后次序创建。当同时创建全局对象、局部对象(包括静态局部对象)时,先创建全局对象后创建局部对象。如在例2-1中对象的创建次序为:对象a→对象c→对象b。

本例中我们定义了三个Desk类的对象,即有了a、b、c三张桌子。从运行结果可知,桌子a和b的重量和大小都为0,即静态对象和全局对象不赋初值,则为0。桌子c只设定了大小,并没有给出重量,那么对象的重量是随机值。因weight是被保护成员,所以要设定桌子c的重量,必须添加成员函数。2.1.2对象的赋值

在例2-1中,我们并没有对书桌的保护成员(重量)赋值。对于类中的保护或私有成员,外界不能直接访问,要实现赋值,就要由成员函数来完成。

【例2-2】接例2-1演示如何访问保护成员。运行结果:

weigth=XXsize=13 //XX为随机值

weigth=18size=0

在本例中还有一个成员函数Set,用来完成重量值的设定。在定义对象时自动设定初始值,即初始化的工作,由构造函数来完成。2.2.1构造函数的特点

构造函数是一个特殊的成员函数,它与类同名。构造函数没有类型(void类型也不行),函数体中也不允许有返回值,但可以有无值返回语句return。构造函数由系统在创建对象时自动调用。

【例2-3】演示构造函数的应用。2.2构造函数在本例中有一个成员函数Desk,它与类同名,它就是构造函数,它对数据成员weight进行了初始化,在创建对象a和c时Desk被自动调用。在本例中还有一个成员函数Set,它可对重量进行重新设定或改变。那么两者之间有什么区别呢?Desk与Set的主要区别就是:前者是构造函数,由系统自动调用;后者是成员函数,需人为调用。2.2.2构造函数的需要性

若程序员需要对数据成员进行初始化,就要提供构造函数,并在其中完成赋值。构造函数可以带参数也可以不带参数。带参数的构造函数必须由程序员提供。不带参数的构造函数由系统自动提供,也可以人为提供。一旦程序员提供了构造函数,原来的默认构造函数就被取代。构造函数的主要功能就是初始化类的对象。2.2.3构造函数的调用时机

构造函数在创建类的对象时,由系统自动调用,而且每创建一个对象只能自动调用一次。通过自动调用就创建了对象,因此我们也可以说构造了一个对象。

【例2-4】演示对象构造次序和默认构造函数。Deska('A'); //定义了一个Desk的全局对象a运行结果:

constructA

constructC

constructB

从语句位置来看,对象a是最后申请的,但它却是第一个构造的。对象构造的次序依据的原则是先区分性质,后区分位置。当构造多个不同作用域对象时,先创建全局对象,后创建局部对象;对于相同作用域的对象,由定义的先后次序决定。2.3.1析构函数的特点

析构函数的表示方法是:在构造函数前加波浪线~(所以它也称为逆构造函数),然后将要释放的内容,即要执行的语句放入该函数中。

【例2-5】析构函数的演示。2.3析构函数运行结果:

constructobject

5↙

x=5

destructobject

局部对象在函数返回时生命结束,便被析构。要说明的是,构造函数与析构函数可以实现函数的所有功能,但是往往只让它们实现一些与名称相符的功能。在本例中,在构造函数与析构函数中使用了输出语句,主要是为了方便观察执行次序。以后也有类似的情况,不再说明。

析构函数是特殊的成员函数,它没有返回类型,没有参数,由系统自动调用,不能随意调用。因为析构函数没有参数,也就没有重载。因为析构函数不能被调用,所以也没有递归调用。一个类只能有一个析构函数。2.3.2析构函数的需要性

若在堆中分配了内存,则要在析构函数内用delete释放。若没有需要归还的资源,可以不指定析构函数。析构函数的主要作用就是归还资源。在不提供析构函数时,系统执行默认的析构函数。默认析构函数是函数体为空的函数,它什么也不做。同样地,一旦人为提供了析构函数,该人为提供的析构函数就取代了原来默认的析构函数,则原来默认的析构函数就不存在了。

【例2-6】演示对象构造与析构次序。运行结果:

high=30 //全局对象先构造

width=10 //局部对象按定义次序构造

length=20

destructBench//先析构局部对象,按构造时相反的次序析构

destructDesk

destructChair //程序执行完毕,最后析构全局对象

该例中若不提供析构函数也可以,即在对象取消时调用默认析构函数就可以了。现提供了析构函数并在函数体中添加了输出语句,以便确认析构函数是否被调用以及调用的时机。2.3.3析构函数的调用时机

析构函数是在类对象生命结束的时候,由系统自动调用的。调用的顺序取决于对象生命结束(取消对象)的顺序。对于局部对象,在该局部对象退出作用域或遇到函数结束标记时生命结束。对于全局对象,则在整个程序执行完毕时生命结束。析构的次序与构造函数相反,即后构造先析构。2.4.1带参数的构造函数

构造函数也可以带参数,以使对象的构造按程序员的要求进行。但是程序员一旦提供了构造函数,那么原来系统自动提供的默认构造函数就不复存在了。为了便于以后定义无参对象时不发生语法错误,建议在人为提供了构造函数之后,再提供一个默认的构造函数。

【例2-7】演示带参数的构造函数。2.4构造参数运行结果:

Constructing Jonas

Mynameis Lolee

Mynameis Jones

Destructing Lolee

Destructing Jones用户定义了带参的构造函数后,最好再定义一个无参的构造函数,如上面的Student(){}。这样做的理由是,既可以用带参的构造函数初始化studA对象,也可以用不带参的构造函数初始化studB对象,应用较灵活。后面在讲继承时将进一步说明无参的构造函数的需要性。

本例先构造了studA,名字为Jonas,接着想将它改为Jones,于是就添加了普通成员函数Set,因为只有成员函数才能访问私有成员。然后通过调用Set再来重新设定或改变对象参数,如studA.Set(982002,"Jones",78)。2.4.2带默认参数的构造函数

C++允许给函数定义默认参数值。构造函数的参数也可以用默认方式提供。通过默认参数值,可让编程工作简单,让编译器做更多的检查错误工作,同时也增加了函数的可重用性。在实际应用中,构造函数往往都带有默认参数。若构造函数内的参数都进行了默认,那么该构造函数就成为默认参数构造函数。

1.默认参数的声明

当需要给函数提供默认参数时,有两个途径:在函数的声明中提供默认参数,也可以在函数的实现中提供默认参数。但是不能两者同时提供默认参数。若某函数既有声明也有定义,则默认参数只能出现在函数的声明中,定义中不可以再有默认参数。若某函数只有定义,则默认参数可出现在函数的定义中。例如:

voidfun(int=3,int=4); //声明

voidfun(intx,inty) //定义

{cout<<x<<‘\t’<<y<<endl;}

2.提供默认参数的方法

若函数有多个默认参数,则在形参的分布中,默认参数应当按从右至左的顺序连续提供。调用时,实参则是从左至右连续匹配形参。不论是声明还是匹配,参数的提供都必须是连续的,否则会出现语法错误。声明部分:

voidfun(inta=1,intb,intc=3); //error

voidfun(inta,intb=1,intc=3); //ok

调用部分:

fun(2,4,6) ;//ok,形参以调用时提供的实参为准,a=2,b=4,c=6

fun(3); //ok,3传给a,其余默认

fun(3,4); //ok,3传给a,4传给b,其余默认

fun(); //error,a没有默认值

fun(3,,4); //error,不允许参数中间使用默认值第一句出错的原因是,默认参数没有从右至左连续提供,而是中断了,因为中间一个b没有默认值。最后一句出错的原因是,默认参数没有从左至右连续提供,而是中断了,因为中间一个b没有提供默认值。

【例2-8】说明带默认参数构造函数的使用。运行结果:

high=7 width=8 //构造对象a

high=11 width=8 //构造对象b

high=7 width=8 //构造有2个单元的对象数组c,

因此调用两次构造函数

high=7 width=8

destruct //析构函数对象数组c中后构造的第二个单元

destruct //析构函数对象数组c中先构造的第一个单元

destruct //析构对象b

destruct //析构对象a在本例中用到了Deskc[2],它是构造对象数组c,该对象数组有两个单元长度。在此并没有提供初始值,它用默认参数构造。若要提供参数,可以使用如下语句:

Deskc[2]={22,33};

输出结果为:

high=7 width=8

high=11 width=8

high=22 width=8 //对象c[0]的high是22

high=33 width=8 //对象c[1]的high是33从输出结果可见,在上面的方法中只是改变了第一个参数。Deskc[2]={22,33};实际上相当于Deskc[2]={Desk(22),Desk(33)};。要想改变多个参数,可以使用如下语句:

Deskc[2]={Desk(11,22),Desk(33,44)};

输出结果为:

high=11 width=22 //对象c[0]的high是11,width是22

high=33 width=44 //对象c[1]的high是33,width是44

...3.对默认值的要求

只要在程序编译时就能确定的值,都可以作为默认值。默认值可以是常量、全局变量和一个函数。默认值不可以是局部变量。

【例2-9】给构造函数提供三个默认值a、fun()和d。#include<iostream.h>

intd=5;

intfun()

{intx;cin>>x;returnx;}

voidmain()

{inta=8;}

classDesk

{protected:程序在编译时,Desk(int=a,int=fun(),int=d);出错,因为a是main()函数中的局部变量。将a改为一个可确定的值,如8就可以了。同时要注意,本例中d的定义位置和函数fun的声明或定义位置要放在默认参数声明之前,且fun必须有返回值。2.4.3默认构造函数

默认构造函数最多只能有一个,由系统自动提供或由程序员提供。构造函数可以带参数,若构造函数内的参数皆为默认,则该构造函数就成为默认构造函数,此时程序员不能再提供无参默认构造函数,否则就会出现二义性的语法错误。

例如,下面两个默认构造函数只能出现一个,否则,在执行Deskdesk时就会出现二义性。以默认方式创建对象时无圆括号,如:Deskdesk,而不能用Deskdesk()。因为该形式已被用于函数的声明,如floatdesk();。2.4.4成员初试化表

构造函数主要用来实现成员参数的初试化。其中,既可以在函数体中实现初试化,也可以在成员初试化参数表中实现初试化。方法是在类的构造函数的参数表后加“:成员名(初试化值)”。2.5.1申请堆和释放堆内存

申请堆的一般方法是:

new操作数;

其中,操作数是数据类型,它可以带初始化值表或单元个数。new和操作数之间要有空格。执行new语句后的返回值是一个指向由操作数所规定的数据类型的指针,即具有操作数之数据类型的指针。2.5动态内存分配当某一堆内存不再需要时,要将其释放并归还给系统。释放堆内存的方法是:

deletep;和delete[]q;

其中,p代表指向非数组空间的指针,q代表指向数组空间的指针。

在C++中new和delete是运算符,不是函数,因此执行效率高。执行new时可以直接返回一个指向具体的数据空间的指针,并且具有自动调用构造函数的能力。执行delete具有自动调用析构函数的能力。

1.在堆中申请空间的方法

在堆中申请空间的一般方法是:

new类型名参数;

其中,类型名可以是内部类型,也可以是类类型;参数若是指定数组单元,则用[]包围,否则用()包围。例如:

newint; //开辟一个整型空间,并返回整型指针

newint(100); //开辟一个整型空间,并初始化为100

float*p=newfloat(3.14159);//开辟一个存放实数的空间,并指定该实数的初值为3.14159,

//返回的指向实型数据的指针的指针赋给指针变量p

newchar[10]; //开辟字符数组空间,长度为10

newint[5][4]; //开辟一个二维整型数组的空间

newchar*(p);//开辟一个存放指针p的空间【例2-10】new和delete的使用方法。

#include<iostream.h>

voidmain()

{int*p;p=newint; //在堆内申请一个整型单元

deletep; //释放时delete和操作数之间要有空格

double*q;q=newdouble;

deleteq;

char*r;r=newchardeleter;

intn;cin>>n;

int*p1;p1=newint[n];

//分配一个堆数组

delete[]p1;

//释放一个堆数组

int*p2;p2=(int*)newint[4][5];//分配堆空间,并将int(*)[5]强

制转换为int*

delete[4]p2; //释放一个4×5的二堆数组空间

int*p3;p3=newint(2);

deletep3;

}释放p1所指对象数组时,delete[]p1中的[]p1的语义是告诉C++,p1指针指向的是一个数组,把该数组空间释放掉。对于二维数组的释放要给出行数,如本例的delete[4]p2。若改为deletep2,则释放二维数组第0行所占的存储空间。2.堆中空间的访问

#include<iostream.h>

voidmain()

{int*p;

p=(int*)newint[4][5];

//将二维数组指针强制转换为一维数组指针

p[0]=11;p[1]=22;p[19]=33;//可从0号单元访问到19号单元

cout<<p[0]<<""<<p[1]<<""<<p[19]<<endl;

delete[4]p; //释放一个二堆数组

}2.5.2new和delete的应用

1.分配和释放堆对象

C++中可以使用new和delete在堆中分配和释放对象。例如:

Student*p=newStudent;

deletep;

先自动调用构造函数创建一个Student类的无名对象,并用p指针指向它;然后人为调用析构函数,将p指针指向的对象析构。

【例2-11】演示创建堆对象。在本例中,分别用默认参数和人为提供参数的方法,创建了两个局部堆对象,然后将其释放。本例还演示了delete调用析构函数的能力。

#include<iostream.h>

classDesk

{protected:运行结果:

high=2 width=3.2 //构造堆对象,并被p所指

high=22 width=25.2 //构造堆对象,并被q所指

23.2

destruct //析构被q所指的堆对象

destruct //析构被p所指的堆对象

X…….X //X…….X为随机值运行结果:

France

length=7 width=3 high=4 weight=9.4

本例在main中创建了一个局部对象a,同时在构造函数中申请了堆空间。当函数结束时,自动调用析构函数析构对象a,并将资源归还。对于在构造函数中分配了堆空间的对象,每创建一个对象,都会申请堆空间,因此在析构中要将其释放。

2.关于delete的正确位置

我们比较例2-11和例2-12可以发现,delete的位置是不同的。对于堆对象,可以在程序员需要的地方创建,在不需要处释放。对于在构造函数中分配的堆对象,要将delete放在析构函数中;对于非构造函数内分配的堆对象,一般在该函数的结束符“}”前加入delete,以释放堆空间。

3.分配和释放对象数组

C++用new和delete在堆中分配和释放对象数组。使用方法:

new类型名[数组长度];

【例2-13】分配和释放对象数组。

#include<iostream.h>

#include<string.h>

classStudent

{public:voidmain(){fun(3);}运行结果:

noname

noname

noname

Kaste

Jonas

Randy

Kaste

Jonas

Randy在本例的fun函数体中有Student*ps,意为定义一个Student类型的指针,它可以指向Student类的对象或对象数组。在fun中共申请了三个Student对象,分别存放在数组单元中。这三个对象的姓名都是noname。接着,ps[0].Setname("Kaste");将姓名Kaste通过成员函数Setname赋给数组0号单元中的对象;ps[1].Setname("Jonas")将姓名Jonas赋给数组1号单元中的对象;ps[2].Setname("Randy")将姓名Randy赋给数组2号单元中的对象。最后通过指针运算符“ps->”访问成员函数。

4.用new与定义对象来开辟空间的区别

用new或定义对象来开辟空间时,它们都能自动调用构造函数,但是对象所在的空间位置是不同的。用new开辟空间时是在堆中分配空间;通过类定义对象时,在全局数据区或栈区分配空间。两者的区别除了空间的位置区别外,在析构时也存在差别:全局数据对象是在程序结束时被析构的,栈区对象是在定义该对象的函数结束时被析构的,而堆中的对象必须执行delete才能被析构。运行结果:

constructteacher

constructstudent

constructteacher

deleteteacher //若主函数中去掉deletep,则无此输出

deletestudent //栈对象a在函数结束时自动析构

deleteteacher //全局对象t在整个程序结束时自动析构从运行结果中不难发现,用Studa;创建的对象在生命期结束时自动调用析构函数;用new创建的对象在生命期结束时是不会自动调用析构函数的,而必须使用delete才能释放堆内存和调用析构函数。2.5.3使用堆空间

全局对象和局部对象的生命期是严格定义的,程序员不能以任何方式改变它们的生命期。若要控制对象的生命期,可以定义堆对象。除此以外,通常在以下情况下要使用堆空间。

(1)直到运行时才知道需要多少对象空间。

(2)不知道对象的生成期到底有多长。

(3)直到运行时才知道一个对象需要多少内存空间。尽管动态分配的堆空间具有较大的弹性与灵活性,但它是有限的。过多的使用有可能将它耗尽,此时的请求就会导致失败。在C++中,若返回一个空指针NULL,则表明分配对象失败。这也是分配堆内存空间是否成功的一个标记。例如:

str=newchar[9];

if(!str){cerr<<"Memoryexhausted!\n";exit(0);}2.6.1常量的定义

在C++中一般用关键字const来定义常量。常量是常数或代表固定不变值的代名词。常量是不能作为左值的量。

程序中如果想让变量的内容自初始化后一直保持不变,可以定义一个常量。定义方法:

const类型常量名=常量;

本定义的语义是指定该常量名是常量。2.6常量例如:

constfloatpi=3.141592653f; //指定pi是常量

constintSIZE=6; //指定SIZE是常量

constinta[]={1,2,3}; //指定a[i]是常量

一般将常量定义放在头文件中或放在#define处。常量在定义时必须初始化,如:constfloatpi=3.141592f;。若用constfloatpi;pi=3.141592;定义则出错,错误原因有两条:第一条是pi在定义的同时没有初始化,第二条是将常量作为左值。

【例2-15】演示常量的使用方法。其中pi是常量名,float常量只能存储6位有效数字。

#include<iostream.h>

intmax(){return8;}

voidmain()

{constfloatpi=3.141592653f;

consta[]={1,2,3}; //整型也可以默认

cout<<pi<<'\t'<<a[1]<<endl; //ok,输出3.14159和2intx=8,y;

constintb=x+3; //ok,初始化值可以是一个表达式

cin>>y;

constintc=y; //ok,初始化值可以是变量,但必须有确定的值

y=3; //ok,变量可以改变

constintsize=sizeof(int); //ok

constintnumber=max(); //ok,初始化值可以是具有相同

类型返回值的函数

}该例的运行结果是,pi的输出为3.14159。

常量的初始化值可以是常量,也可以是编译时还不能确定值的变量。对常量的要求是:定义的同时必须初始化,常量不能作为左值。2.6.2常量指针

常量指针也称为指向常量的指针。它指定该指针所指向的对象是常量,不是变量。它保证了不能通过常量指针来改变常量指针所指单元内的值。通过该指针访问单元时,单元内的值只能读出而不能被更改。在指针定义语句的类型前加const,如constint*p;或intconst*p;,都可以定义常量指针p,并将指定p所指向的对象是常量,不是变量。该定义的语义是:它告诉编译器*p是常量,不能将*p作为左值进行操作,即不能通过*p来改变p所指向的对象内的值。

定义常量指针时可以不初始化,以后它还可以改变指向。常量指针可以指向变量,也可以指向常量,而指针变量不能指向常量。

【例2-16】演示常量指针的使用方法。#include<iostream.h>

voidmain()

{constinta=66;

constintb=88;intc=55;

int*r=&c;

intconst*p=&a; //定义常量指针并指向常量a

intconst*q;q=&c; //可以先定义后初始化

cout<<*p<<'\t';p=&b; //ok,常量指针可以变换指向

cout<<*p<<‘\t’;

p=&c; //ok,常量指针可以指向其他同类型的变量

c=20; //ok, c是变量

cout<<*p<<endl;

}

运行结果:

66 88 20从本例中可见,变量的指针不可以指向常量,而常量指针可以指向变量,也可以指向常量,也可以改变指向。例如,p可以初始化为指向变量,也可以初始化为指向常量。常量指针的含意并不是p不能改变指向,而是不能将*p作为左值进行操作。

定义指向常量的指针p只限制*p不能作为左值,而不能限制指针所指向对象本身的操作。如本例中,在进行p=&c;c=20;操作后,c单元内的值就被修改了。2.6.3指针常量

1.定义与引用

定义指针常量的方法是,在指针定义语句的指针名前加const,表示指针本身是常量。定义指针常量时必须初始化,它可以指向常量也可以指向变量,一旦定义以后不能再改变指向。

例如:char*constp="abc";语句告诉编译器p是常量,不能将p作为左值进行操作。但p指向单元内的值可以修改。又例如:

inta=88,b=66;

int*constp=&a; //ok,定义指针常量p,在定义时必须初

始化

*p=77; //ok,指向的内容改为77

char*constq="abc"; //ok,定义指针常量q

*q='A'; //ok,q指向的内容改为:Abc

*(q+1)='B'; //ok,q指向的内容改为:ABc注意:

在定义时不能写成int*constptr=777;,因为ptr是指针常量而不是指针常数,它只能指向单元,不能指向常数。

2.指针常量的用途

指针常量对指针的指向作了限定,使它不能再指向其他对象。它的作用是限定指针只能在某一区间内操作。如果有intfun(char*constp);,那么在fun函数中任何想使p改变指向的语句都将是非法的。2.6.4指向常量的指针常量

1.定义与引用

在指针常量前再加上const,就定义了指向常量的指针常量。定义时必须初始化,它可以指向常量也可以指向变量,一旦指向确定,则既不能改变指向,也不能通过指针改变指针所指单元中的内容。例如,语句constint*constp=&a;告诉编译器p和*p都是常量,它们都不能作为左值进行操作,但p指向的单元内的值有时可以修改。又例如:

constina=8;

intb;

constint*constp=&a; //ok,定义指向常量的指针常量

p,它可以指向一个常量

constint*constq=&b; //ok,定义指向常量的指针常量

q,它可以指向一个变量

//p=q;p=&b;*p=6;//error,指针指

向和指针指向单元内的值不能修改

b=6;

//ok,直接改变变量单元中的内容

2.指向常量的指针常量的用途

指向常量的指针常量限定了指针的指向不能改变,限定了通过指针对指向单元的操作行为只能读,即它保证了只能在限定的单元内读,不能进行修改。2.7.1const数据成员

在类的数据成员中,对于保持不变的值,可以声明为const数据成员。对于带有const数据成员的对象,程序员一定要给构造函数提供初始化该成员的初始值。若有多个const的数据成员,只需将它们列在构造函数的冒号之后,成员之间用逗号分隔即可。

【例2-17】const的数据成员的初始化。2.7const对象2.7.2const成员函数

若想限制某成员函数功能为只读,可以将其声明为const成员函数,即常量成员函数。声明方法是,在函数声明处的分号前加入const关键字,然后在函数实现的圆括号后、花括号前加入const关键字。voidPerson::Display()const

{//high=1.81f; //error,只能读,不能改变

cout<<high<<endl;

}

上面程序中的Display是const成员函数,在该函数中,既能读取对象的const数据成员,也能读取对象的非const数据成员,但是只能读,却不能改变对象的数据成员。若有const对象,则只有const成员函数才能对它进行访问。2.7.3const对象的声明与实现

对于不需要修改的对象或不需要修改数据成员的成员函数,都可以用const指明。试图修改const对象是一个错误的想法。例如:constPersonwom("Jonas");定义了类Person的

一个const对象wom,并初始化姓名为Jonas。const对象是不能被赋值的,所以必须初始化。const对象内的数据是不能改变的,只有const成员函数才能对它的数据成员进行访问。运行结果:

ID name score high

320 Kaste 75 1.81

320 Jonas 0 1.76

本例中列出了三个常见错误。错误1:定义了一个const成员函数,但该函数又修改了对象的数据成员。错误2:定义了一个const成员函数,但该函数又调用了非const成员函数。错误3:const对象调用了非const成员函数,或者说非const成员函数访问了const对象。2.7.4使用const成员

在某对象中,可将不需要修改数据成员的成员函数声明为const成员函数。若对于不需要修改的对象,即所有成员函数都不能修改数据成员时,可声明为const对象。const对象是不能被赋值的,所以必须初始化。同样地,const对象必须由构造函数初始化,由析构函数来取消。const对象的构造函数和析构函数无需用关键字const声明。2.8.1静态成员的需要性

在某些情况下,需要让类的所有对象共享某个数据。例如链表的首指针、链表中结点的总数等若放在类的定义中,则一旦增加结点,修改起来十分不便,因为每个对象都拥有该成员。如果定义为全局变量,那么其他函数也能访问,就不安全了。下面通过一个银行储蓄类进行说明。2.8静态成员从该类中我们可知,对于每一个银行储蓄帐户,个人的姓名和储额不同,而利率是一致的,是共享的,因此将它定义为静态数据成员。对于属于类的一部分,但既不适用于普通成员表示,又不适用于全局变量表示的数据,用静态数据成员表示后,便能在类的范围中共享,称之为静态数据成员。

静态数据成员的引入体现了两个明显的优势:第一,静态数据成员的引入很好地解决了共享与封装之间的矛盾;第二,静态数据成员只属于类,而不属于对象,当存在多个某类的对象时,只有唯一的一份静态数据成员,可节省内存。2.8.2静态成员的访问

静态成员分为公有、私有或保护的,用static声明。静态数据成员是类的一部分,其定义也是类定义的一部分。要将其放在类的内部定义,还必须在类的实现中初始化。初始化时用类名引导,重用时简单地包含头文件便可。

对于类的公有静态成员,既可用类的对象或指针访问,如:对象.公有静态成员或对象指针->公有静态成员,也可用类名加域区分符访问,如:类名::公有静态成员。对于类的私有或保护静态成员,可用静态成员函数访问,如:类名::静态成员函数。2.8.3静态数据成员

公有的静态数据成员可被类的外部访问,保护或私有静态数据成员只可在类的内部被访问。通常,静态数据在类的定义之外初始化。同时也要注意,由于静态成员与对象的无关性,因此静态成员无this指针。静态数据成员可以作为类成员函数的默认参数。要注意,其他数据成员不可以。

1.访问公有的静态数据成员的方法

对象名.数据成员名

类型名::数据成员名

【例2-19】演示静态数据成员的初始化与两种访问方法。运行结果:

1

2 2

在本例中,s.total等价于t.total,因为total是各对象公有的。常用的方法是Student::total。其意义是,静态数据成员是属于Student类的,而不是属于哪个特定对象。即静态数据成员属于类的一部分,不属于对象的一部分。

2.访问保护或私有静态数据成员的方法

类的成员函数可以访问保护或私有静态数据成员,但是一般来说,访问保护或私有静态数据成员要用成员函数。

【例2-20】演示访问保护静态数据成员的方法。

3.静态数据成员的应用

静态数据成员主要应用于以下几方面:用来保存流动变化的对象个数(如上例中的total);作为一种标志,指示一个特定的动作是否发生;作为一个链表的首指针或尾指针;使用静态数据成员,实际上可以不再需要全局变量。2.8.4静态成员函数

1.概念

静态成员属于类,不属于对象,也就是说它不与对象相联系。在没有创建对象的情况下,仍可以直接以类来调用静态成员函数。静态成员函数不能访问类的非静态成员。如果类中的某个成员函数不需要访问类的非静态成员,那么可将该函数声明为静态成员函数。

2.使用中的注意点

(1)静态成员函数的定义属于类定义的一部分,因此它的定义位置与一般的成员函数一样。

(2)静态成员函数不能访问对象成员。静态成员函数与类相联系,不与任何对象相联系,故不能在静态成员函数中对非静态成员进行访问。既不能访问非静态成员函数,也不能访问非静态数据成员。

(3)静态成员函数与非静态成员函数的根本区别是:静态成员函数没有一个指向当前对象的this指针。

(4)不能将静态成员函数声明为const函数。

【例2-21】对上面注意点的说明。

【例2-22】演示静态成员的使用。下面将班级学生总数设置为静态成员total,通过创建对象自动增加,通过析构对象自动减小。

//student.h

#ifndefSTUDENT_H

#defineSTUDENT_H

classStudent

{public://student.cpp //类的实现称之为类的定义

#include<iostream.h>

#include<string.h>

#include"student.h"

intStudent::total=0; //静态数据成员要求初始化Student::Student(char*pName)

{cout<<"createonestudent\n";

strcpy(name,pName);

total++; //自增,成员函数访问静态成员

cout<<total<<endl;

}

Student::~Student()

{cout<<"destructonestudent\n";

total--; //每析构一个对象,学生表减1

cout<<total<<endl;

}intStudent::number() //静态成员函数的实现

{returntotal;}

//stu.cpp

#include<iostream.h>

#include"student.h"

voidfun()

{Students1;Students2;

cout<<Student::number(); //调用静态成员函数用类名引导

cout<<endl;

}

voidmain()

{fun();cout<<Student::number()<<endl;}在构造函数中进行静态成员的自增,这样每创建一个对象,学生数自动加1。在析构函数中进行静态成员的自减,这样每取消一个对象,学生数自动减1。● 与类同名的成员函数称为构造函数。它没有返回类型,它在创建对象时自动被调用,它的功能就是初始化类的对象。

● 让构造函数完成初始化以外的工作不是好方法。

● 构造函数可以带参数,也可以带默认参数。让构造函

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论