关于类和对象的进一步讨论.docx
《关于类和对象的进一步讨论.docx》由会员分享,可在线阅读,更多相关《关于类和对象的进一步讨论.docx(59页珍藏版)》请在冰豆网上搜索。
![关于类和对象的进一步讨论.docx](https://file1.bdocx.com/fileroot1/2023-1/29/702a08ae-ae68-469c-8455-5011df2131f8/702a08ae-ae68-469c-8455-5011df2131f81.gif)
关于类和对象的进一步讨论
第3章关于类和对象的进一步讨论
请注意,这一章内容是C++的重点,要特别注意!
第2章我们介绍了关于类的一些基本内容,对于类对象的数据成员的初始化我们始终是通过建立成员函数来实现的。
如:
类Time就是通过set_time这个成员函数来实现的。
而且一般都是用赋值语句给数据成员赋值,或通过cin来为数据成员从键盘输入值来实现。
然后手工调用该函数对数据成员进行赋值的。
//自编,这是一个由cin的输入或用赋值语句来为数据成员赋值的程序
#include
usingnamespacestd;
classPoint{
intx,y;
public:
voidset(){cin>>x>>y;}
voidset(intx,inty){Point:
:
x=x;Point:
:
y=y;}
voidprint(){cout<<"("<};//===============================
intmain(){
Pointa,b;
a.set();
b.set(4,5);
a.print();
b.print();
}//================================
那么在C++中对于类来说有没有更方便的方式能够在对象创建的时候就自动初始化数据成员呢?
答案是肯定的。
关于C++类成员的初始化,有专门的构造函数来进行自动操作而无需要手工调用。
3.1构造函数
3.1.1对象的初始化
对象和变量一样,不赋初值是没有意义的。
根据变量定义,全局变量和静态变量在定义(分配空间)时,初值为0,局部变量在定义时才分配内存空间,其内容为随机数。
对象定义和变量定义一样,当创建全局对象时如果没有初始化,初值为0,当创建局部对象时,如果没有初始化则以随机值表示对象。
下面来看一个实例
//自编,全局对象,局部对象在不赋初值情况下的输出
#include
usingnamespacestd;
//-------------------------------------
classPoint{
intx,y;
public:
voidset(inta,intb)
{x=a,y=b;
}//------------------------------------
voidprint()
{cout<<"("<};//===================================
Pointt;//全局对象
intmain(){
Points;//局部对象
s.print();
t.print();
}//====================================
//运行结果如下:
(-858993460,-858993460)
(0,0)
从运行结果可以知道,末设置初始时,全局对象值为0,局部对象值为随机值。
没有初值的对象是毫无意义的,例如创建一个桌子对象,桌子就应有长、宽、高和重量。
因此,在桌子对象建立时,就该赋予一组值给该桌子对象。
对象是一个实体,它反映了客观事物的属性,是应该有确定的值的。
类的数据成员是不能在声明类时初始化的。
也就是说在类体中不允许对所定义的数据成员进行初始化。
classPoint{
intx=10,y=20;//这是不允许的
public:
voidset(){cin>>x>>y;}
voidprint()const{cout<<"("<};//===============================
如果一个类中所有的成员都是公有的,则与结构体一样是可以在定义对象时对数据成员进行初始化。
如:
#include
usingnamespacestd;
classPoint{
public:
intx,y;
};
intmain()
{Pointp={1,1};
cout<<"("<
return0;
};//===============================
因为对象中的数据成员一般都是私有的,不能随意改动,如果改成公有的破坏了封装性。
C++建立和初始化对象的过程专门由该类的构造函数来完成。
这个构造函数很特殊,只要对象建立,它马上被调用,给对象分配空间和初始化。
如果一个类没有专门定义构造函数,那么C++就仅仅创建对象而不做任何初始化。
3.1.2构造函数的作用
C++规定与类同名的成员函数是构造函数,与其他成员函数不同的是它不需用户来调用,在该类的对象创建时,会自动被调用。
下面来看一个Desk类的定义
classDesk
{intweight,height,width,length;
public:
Desk()//构造函数
{weight=10;height=5;
width=4;length=4;
}
};
在类中定义的与类名相同的成员函数,并且没有任何返回类型的Desk()就是构造函数,这是一个无参数的构造函数,他在对象创建的时候自动调用,如果去掉Desk()函数体内的代码那么它和c++的默认提供的构造函数等价的。
构造函数是没有返回类型的,函数体中也不允许返回值,但可以有无值返回语句return;。
因为构造函数专门用于创建对象和为其初始化,所以它不能随意被调用。
没有返回类型,正显得它与众不同。
例如,下面的代码是在类定义的外部定义一个构造函数,但是却错误地加上了返回类型:
classDesk
{
intweight,height,width,length;
public:
voidDesk()//这样是错误的。
构造函数不应有返回值,也不是无类型void函数。
{
weight=10;height=5;
width=4;length=4;
}
};
下面来看书中的例3.1
//例3.1
#include
usingnamespacestd;
classTime
{
inthour,minute,second;
public:
Time()//构造函数的定义,在类内定义,当然也可以在类外定义
{
hour=0;minute=0;second=0;
}
voidset_time();//函数声明,在类外定义
voidshow_time();//函数声明,在类外定义
};
voidTime:
:
set_time()
{
cin>>hour>>minute>>second;
}
voidTime:
:
show_time()
{
cout<"<"<}
intmain()
{
Timet1;
t1.set_time();
t1.show_time();
Timet2;//会自动调用构造函数
t2.show_time();
/*Timet3;
T3.Time();*///用户是不能调用构造函数的
return0;
}
有关构造函数再一次说明如下:
(1)构造函数是在建立对象时自动调用的。
(2)构造函数没有返回值,即使是void也不行。
(3)构造函数不需用户调用,也不能被用户调用。
(4)构造函数并不公是对数据成员赋初值,也可以有其它的操作,但一般情况下构造函数不要有与初始化无关的内容。
(5)用户没有定义构造函数,系统会自动生成一个构造函数,只是这个构造函数是空的,也没有参数,也没有初始化的操作。
3.1.3带参数的构造函数
前面介绍的构造函数是无参的,不能完全满足初始化的要求。
应该让构造函数可以带参数,否则,程序员只能先将对象构造成千篇一律的对象值,甚至一个随机值对象,然后再调用一个初始化成员函数(set)将数据存到该对象中去。
这样建立一个对象要调用两个成员函数才行,一个是构造函数,一个是set函数。
如果采用带参数的构造函数,在调用不同对象的构造函数,可以将不同的数据传递给构造函数,以实现不同的初始化。
构造函数的一般格式为:
构造函数名(类型1形参1,类型2形参2,…)
下面的程序定义了一个带参数的Box类:
//例3.2有两个长方柱,其长宽高分别为
(1)12,25,30;
(2)15,30,21。
分别求它佼的体积
#include
usingnamespacestd;
classBox
{public:
Box(int,int,int);
intvolume();
private:
intheight,width,length;
};
Box:
:
Box(inth,intw,intl)
{height=h;width=w;length=l;
}
intBox:
:
volume()
{returnheight*width*length;
}
intmain()
{Boxbox1(12,25,30);
cout<Boxbox2(15,30,21);
cout<}
可以看到,在建立对象时,实现了对不同的对象进行不同的初始化。
下面我们将第2章中定义的类改成由构造函数来设置对象值(学生课堂练习)
#include
#include
usingnamespacestd;
//----------------------------------
classDate{
intyear,month,day;
public:
Date(inty,intm,intd);
boolisLeapYear();
voidprint();
};//-------------------------------
Date:
:
Date(inty,intm,intd){
year=y;month=m;day=d;
}//--------------------------------
boolDate:
:
isLeapYear(){
return(year%4==0&&year%100!
=0)||(year%400==0);
}//--------------------------------
voidDate:
:
print(){
cout<cout<(2)<(2)<cout<}//--------------------------------
intmain(){
Dated(2000,12,6);
d.print();
Date*dp=&d;
if(dp->isLeapYear())
(*dp).print();
}//================================
创建对象如果不给出对象名,也就是直接以类名调用构造函数,则产生一个无名对象。
无名对象经常在参数传递时用到。
例如:
cout<3.1.4用参数初始化表对数据成员初始化
在前面的实例中是在构造函数的函数体内通过赋值语句对数据成员实现初始化。
C++还提供了另一种初始化数据成员的方法:
参数初始化表。
上例可改写为:
Box:
:
Box(inth,intw,intl):
height(h),width(w),length(l){}
这种写法方便、简练。
3.1.5构造函数的重载
是函数就可以重载。
构造函数也可以重载,还可以设置默认参数。
//例3.3,在上例的基础上,定义两个构造函数,其中一个无参数,一个有参数。
#include
usingnamespacestd;
classBox
{
public:
Box();//构造函数1,无参数的构造函数,称为默认构造函数。
如果
//用户不定义系统会自动提供一个无任何操作的默认构造函数。
Box(inth,intw,intl):
height(h),width(w),length(l){}//构造函数2,有2个参数
intvolume();
private:
intheight,width,length;
};
Box:
:
Box()
{height=0;width=0;length=0;
}
intBox:
:
volume()
{returnheight*width*length;
}
intmain()
{Boxbox1(12,25,30);
cout<Boxbox2;//注意:
不能写成Boxbox2();不能有括号。
如果有括号的形式就是
//在声明一个普通函数,其返回类型为Box类型。
cout<}
虽然可以有多个构造函数,但对于每一个对象来说,建立对象时只执行一个构造函数,并非每个构造函数都执行。
3.1.6使用默认参数的构造函数
构造函数中参数的值也可以指定默认值。
例如,下面的代码在创建对象时,将自动调用系统提供的默认构造函数。
//例3.4将上例中构造函数改用含默认值均为10.
#include
usingnamespacestd;
classBox
{
public:
Box(inth=10,intw=10,intl=10):
height(h),width(w),length(l){}
intvolume();
private:
intheight,width,length;
};
intBox:
:
volume()
{
returnheight*width*length;
}
intmain()
{
Boxbox1;
cout<Boxbox2(15);
cout<Boxbox3(15,20);
cout<Boxbox4(15,20,30);
cout<}
在构造函数中使用默认参数是方便有效的,这相当于提供了多个重载的构造函数。
说明:
(1)构造函数的默认值要在声明中指定。
(2)声明时可以省略形参名,如:
Box(int=10,int=10,int=10);
(3)当构造函数的参数全部指定为默认值时,可以给出一个或多个参数,也可以不给参数,这时就不能再定义重载构造函数了。
//补充实例
#include
#include
usingnamespacestd;
//----------------------------------
classDate{
intyear,month,day;
public:
Date(inty=2000,intm=1,intd=1);//构造函数1,有三个参数,设置默认参数
Date(conststring&s);//构造函数2,有一个参数,无默认参数
boolisLeapYear()const;
voidprint()const;
};//-------------------------------
Date:
:
Date(inty,intm,intd){
year=y;month=m;day=d;
}//--------------------------------
Date:
:
Date(conststring&s){
year=atoi(s.substr(0,4).c_str());//取出子串转换为字符串再转换为整型
month=atoi(s.substr(5,2).c_str());
day=atoi(s.substr(8,2).c_str());
}//--------------------------------
inlineboolDate:
:
isLeapYear()const{
return(year%4==0&&year%100!
=0)||(year%400==0);
}//--------------------------------
inlinevoidDate:
:
print()const{
cout<cout<(2)<(2)<cout<}//--------------------------------
intmain()
{
Datec("2005-12-28");
Dated(2003,12,6);
Datee(2002);//默认两个参数
Datef(2002,12);//默认一个参数
Dateg;//默认三个参数,注意这里g后没有括号。
表示无初始化的对象的定义
//如果有括号,表示是无参函数。
c.print();
d.print();
e.print();
f.print();
g.print();
}
#include
usingnamespacestd;
classStudent
{
//无构造函数
protected:
charname[20];
};
voidmain()
{
StudentnoName;//ok:
noName的内容为随机值
}
上例中的类定义等价于下面的类定义:
#include
usingnamespacestd;
classStudent
{
public:
Student(){}//一个空的无参构造函数
protected:
charname[20];
};
voidmain()
{
StudentnoName;
}
又例如,下面的代码定义了一个带参数的构造函数,面对创建无参对象,将不能正确地编译:
#include
usingnamespacestd;
classStudent
{
public:
Student(char*pName)
{
strncpy(name,pName,sizeof(name));
}
protected:
charname[20];
};
voidmain()
{
StudentnoName;//error:
无匹配的构造函数
}
以上程序会出现如下的错误信息:
errorC2512:
'Student':
noappropriatedefaultconstructoravailable
如果增加一个无参的构造函数,就可解决这个问题:
#include
usingnamespacestd;
classStudent
{
public:
Student(){}
Student(char*pName)
{
strncpy(name,pName,sizeof(name));
}
protected:
charname[20];
};
voidmain()
{
StudentnoName;//正确的,有了匹配的构造函数
Students("li");
}
3.2析构函数
C++另有一种析构函数,它也是类的成员函数,当对象撤消时,就会马上被调用,其作用是善后处理。
例如,一张桌子要扔掉,须将桌子里面的东西拿出来,这些东西可能有用,不能随桌子一起扔。
类似这些事就由析构函数来完成。
析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载。
只是在类对象生命期结束的时候,由系统自动调用。
析构函数不同于构造函数,不可以有有参数,也不可以重载。
作为一个类,可能有许多对象,每当对象生命期结束时,都要调用析构函数,每个对象一次。
析构函数名,就在构造函数名前加上一个逻辑非运算符“~”,表示“逆构造函数”。
对象析构的顺序与对象创建的顺序正好相反。
先构造的后析构,后构造的先析构。
它相当于一个栈,先进后出。
但并不是在任何情况下都是这一原则处理的。
下面归纳一下什么时候调用构造函数和析构函数。
●在一个函数中定义的对象,它是局部自动对象,则在建立对象时调用其构造函数。
如果函数被多次调用,则在每次建立对象时都要调用构造函数。
在函数调用结束、对象释放时先调用析构函数。
●全局对象,它的构造函数在文件中的所有函数(包括main函数)执行之前调用。
但如果一个程序有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序不确定。
当main函数执行完毕或调用exit函数是(此时程序终止),调用析构函数。
●静态局部对象则在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此与不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
析构函数的作用是撤销对象占用的空间,而不是删除对象。
析构函数由于没有函数参数,所以不能重载。
一个类可以有多个构造函数,但只能有一个析构函数。
//例3.5包含构造函数和析构函数的程序
#include
#include
usingnamespacestd;
classStudent
{public:
Student(intn,stringnam,chars)
{num=n;
name=nam;
sex=s;
cout<<"Constructorcalled."<}
~Student()
{cout<<"Destructorcalled."<voiddisplay()
{cout<<"num:
"<cout<<"name:
"<cout<<"sex:
"<}
private:
intnum;
stringname;
char