Chap3 类和对象800Word文档格式.docx
《Chap3 类和对象800Word文档格式.docx》由会员分享,可在线阅读,更多相关《Chap3 类和对象800Word文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
intSet_Content(char*);
//设置字符串
intGet_Length();
//取字符串长度
char*Get_Content();
//取字符串内容
};
4.巩固练习——1(类声明)
3.2成员函数的定义(MemberFunction)
1.两种定义方式:
1在类体中声明,而在类外定义。
声明时,可以只指出函数所带参数的类型;
在类外定义时,必须在函数名前缀上类名,以标明此函数所属的类。
如,上例中的String类中的三个成员函数可定义如下:
//string_1.cpp
//功能:
设置私有变量contents的值,同时计算其长度
intString:
:
Set_Content(char*conts)
{
inti=0;
contents=conts;
//将字符串赋给私有变量contents
while(*conts++!
=‘\0’)
i++;
//求字符串长度
length=i;
//将长度赋给私有变量length
return1;
}
获取字符串的长度
Get_Length()
returnlength;
//返回私有变量length
获取字符串的值
char*String:
Get_Content()
returncontents;
//返回私有变量contents
注意:
Ø
在所定义的成员函数名之前应缀上“类名:
”,如“String:
”
函数的返回类型一定要与函数声明时的类型相匹配
2在类体内定义。
对于一些简单的成员函数,可以在类体中定义。
在类体内定义的成员函数被当作内联函数处理
2.内联函数的两种定义方式
1隐式定义——类体内定义
//point.h
intx,y;
voidSetPoint(intvx,intvy)
intGetX(){returnx;
}
intGetY(){returny;
//string_1.h
intGet_Length(){returnlength;
char*Get_Content(){returncontent;
2显式定义——类体外定义,函数最前面冠以关键字“inline”
//point.cpp
inlinevoidPoint:
SetPoint(intvx,intvy)
x=vx;
y=vy;
【小结】
①所有成员函数都必须在类体内用函数原型加以声明,而其定义可在体外定义,也可在体内定义。
在体外定义时,成员函数名前应缀上“类名:
②成员函数与普通函数一样,可设置缺省参数。
3.3类与对象
1.类与对象的关系——整型int和整型变量i之间的关系
●类在概念上是一种抽象机制,它抽象了具有相同特征的一类对象的存储特性和操作特性;
在系统实现中,类是一种共享机制,它提供了本类对象共享的操作实现。
●对象和实例表达的是一个意思:
对象的创建过程就是类实例化的过程
●创建对象的两种方法
①在定义类的同时创建对象——全局对象(弊端:
)
//date.h
classDate{
intmonth,day,year;
voidSet(int,int,int);
voidPrint();
intGetYear();
intGetMonth();
intGetDay();
}tt;
//同时创建对象tt
②在使用时定义对象——定义格式与一般变量的定义相同
如,Datett;
//定义了tt是Date类的一个对象
2.类的使用
创建一个此类的对象,然后通过此对象访问类的公有成员函数,以便操作此对象的数据成员。
classPoint{
voidSetPoint(intvx,intvy){x=vx;
……
//test.cpp
#include“iostream.h”
voidmain()
{
Pointpt;
//创建Point类的对象pt
pt.SetPoint(10,10);
//给Point类对象pt的私有成员赋值
cout<
<
”x=”<
pt.GetX();
//输出pt的坐标
”,y=”<
pt.GetY();
}
使用“.”操作符访问类的成员。
若定义的是指向此类对象的指针,则使用“->
”操作符。
如:
Pointpt,*pt1=&
pt;
pt1->
SetPoint(10,10);
pt1->
GetX();
GetY();
3.名字解析
在调用成员函数时,通常使用缩写的形式,如
pt.SetPoint(10,10)pt.Point:
SetPoint(10,10);
因此,可以在不同的类中定义名字相同的成员而不会产生二义性。
(函数重载?
如,//real_int_set.h
classRealSet{//定义一个实数集合类
intcard;
floatelems[16];
voidPrint(){//…}
classIntSet{//定义一个整数集合类
intelems[16];
//test.cpp
voidmain()
IntSetis;
RealSetrs;
//...
is.Print();
//调用的是InSet类中的Print()
rs.Print();
//调用的是RealSet类中的Print()
4.巩固练习——2(成员的调用)
3.4构造函数与析构函数
目的:
构造函数初始化对象
析构函数删除对象
3.4.1基本用法
①函数名与类名相同,且没有返回类型。
classString{
char*contents;
intlength;
String();
//声明构造函数,其名必须与类名相同,无类型说明
~String();
//声明析构函数,在函数名前冠以“~”符号
voidSet_Content(char*);
intGet_Length(){returnlength;
char*Get_Content(){returncontent;
//构造函数的定义
String:
String()
contents=0;
//对其私有变量赋初值
length=0;
//析构函数的定义
~String()
{
//设置私有变量contents的值,同时计算其长度
②在构造函数中一般只对数据成员做初始化工作,而不做赋初值以外的事情。
③构造函数不可显式地调用,当创建一个对象时,系统自动地调用。
在对象超出作用域范围之前,系统自动调用析构函数,释放对象的成员所占的空间。
//对私有变量赋初值
cout<
”StringInitialized\n”;
//在屏幕上输出对象已初始化的信息
~String()
”~Stringobject”<
contents<
endl;
Stringstr1,str2;
//定义String的两个对象str1和str2
str1.Set_Content(“Hello”);
//给两个对象的字串赋值
str2.Set_Content(“Welcome”);
//在屏幕上输出两个对象的字串值
cout<
”\nstr1=”<
str1.Get_Content();
”\nstr2=”<
str2.Get_Content();
显示结果:
StringInitialized
StringInitialized
str1=Hello
str2=Welcome
~StringobjectWelcome
~StringobjectHello
创建一个对象时,系统先为对象分配内存,然后调用构造函数初始化此对象的各数据成员(即:
设置对象的初始状态)
释放一个对象时,系统先调析构函数,然后回收对象所占的内存。
如deletestr1;
//①str1.~String()②回收内存
析构对象的顺序与构造的顺序相反。
若类中没有显式定义构造函数,在创建对象时,系统只分配内存,而不执行初始化工作,对象的各个数据将是随机值。
此时若对对象的数据进行操作,将导致结果错误!
(缺省的构造函数)
若类中没有显式定义析构函数,系统会生成一个缺省的析构函数;
但缺省的析构函数只回收对象所占有的空间,并不回收通过构造函数动态分配的内存(内存泄漏!
)(后讲!
④一个类有且只有一个析构函数。
3.5动态存储(new与delete)
newmalloc(sizeof());
deletefree();
1.优越性:
①new自动计算要分配的空间(根据数据类型)
②自动返回正确的指针类型,不必对返回指针进行类型转换
③可以用new将分配的对象初始化
2.new和delete的语法
基本形式:
名字指针=newDataType(初始化值);
delete名字指针;
//被释放的存储空间的首址
①动态创建和释放一个对象
int*pi=newint(3);
/*int*pi=(int*)malloc(sizeof(int));
*pi=3;
*/
deletepi;
//free(pi);
②创建和释放一组对象(数组)
int*pi=newint[10];
delete[]pi;
//释放整个数组所占用的空间
③创建和释放结构体对象
3.实例(1~4)
4.使用注意:
delete前,必须判断指针是否为空,即if(p==NULL)orif(!
p)
以免同一空间进行多次撤消工作,导致非法操作!
new和delete一般配对使用,即:
用new分配的存储,最好用delete释放
动态分配的存储,必须显式删除(delete/free),系统不会自动回收,否则造成内存泄漏!
3.4.2构造函数的类型
1.带参数的构造函数
①在定义对象时,根据对象的初始状态不同,传递不同的状态参数给构造函数,从而实现不同对象的初始化状态不同。
如,
//test.cpp
Point(intvx,intvy);
//声明带参数的构造函数
voidoffset(intax,intay);
};
Point:
Point(intvx,intvy)
{
//用传递进来的参数对私有变量x,y赋初值
voidPoint:
offset(intax,intay)
x+=ax;
//对私有变量增值x=x+ax;
y+=ay;
voidmain()
//定义对象pt1和pt2,并给构造函数传递实参
Pointpt1(5,5),pt2(3,3);
pt1.offset(10,15);
pt2.offset(2,3);
//......
2.缺省参数的构造函数
Point(intvx=0,intvy=0){x=vx;
//.......
Pointp1;
//不传递参数,全部用缺省值,即x=y=0
Point(10);
//只传递一个参数,vy用缺省值,即x=10,y=0
Point(10,20);
//传递两个参数,全部用实参,即x=10,y=20
构造函数、一般的成员函数、一般的全局函数,都可以使用缺省参数。
3.拷贝构造函数
功能:
用同类的一个已存在的对象去初始化新创建的对象
两种形式:
①系统自动产生——缺省的拷贝构造函数,如
Pointp1(10,20);
//调用Point(int,int)
Pointp2=p1;
/*用已知对象初始化新对象
Pointp2(p1);
调用缺省的拷贝构造函数*/
实现机制:
将p1(已知对象)的每个数据成员的值,按照它们在类中说明的顺序,依次拷贝给新对象p2相应的数据成员,每次只拷贝一个数据,即“位模式拷贝”。
“位模式拷贝”产生的隐患:
#include<
string.h>
#include<
iostream.h>
intlength;
char*contents;
public:
String(char*s);
//声明构造函数
~String();
//
//…
String:
String(char*s)//定义构造函数
{
if(s)
{
length=strlen(s);
contents=newchar[length+1];
//为字符串分配存储
strcpy(contents,s);
//字符串之间赋值
else
length=0;
contents=0;
~String()//定义析构函数
if(content)
deletecontents;
//释放字符串contents所占空间
contents=0;
//将指针置空
voidmain()
Stringa(“Hust”);
Stringb=a;
}//同一存储空间contents被delete两次
当类中声明有指针数据成员时,为避免程序隐患,最好自行编写拷贝构造函数。
②用户定义
格式:
A:
A(constA&
)//A为类名
注:
只有一个参量,且参量是同类对象的引用变量(如A&
);
多采用只读引用变量(如constA&
),以免被引用对象被修改。
如
intx,y;
Point(intvx,intvy){x=vx;
Point(constPoint&
rp)//定义一个拷贝构造函数
{x=rp.x;
y=rp.y;
String(constString&
rs);
//定义拷贝构造函数
String(constString&
rs)//定义拷贝构造函数
length=rs.length;
strcpy(contents,rs.content);
Stringa(“Hust”);
Ab=a;
4.多构造函数(构造函数的重载)
在一个类中同时声明几个构造函数,以适应不同对象初始化目的。
classA{
A();
//不带参数的构造函数
A(int);
//只带一个int参数的~
A(int,char);
//带两个参数的~,一个整数,一个字符
A(float,char);
//带两个参数的~,一个浮点数,一个字符
A(constA&
);
//拷贝构造函数
main()
Aa;
Ab
(1);
Ac(1,’c’);
Ad(3.5,’d’);
Ae=a;
注1:
多个构造函数之间,在参数的个数或类型上必须有所差别,否则系统调用时就会出现二义性
注2:
若在定义多个构造函数时,使用了缺省参数,要防止二义性问题
如,classx
x();
x(inti=0);
main()
xone(10);
//
xtwo;
//?
?
3.4.3动态存储类的对象(new与delete)
1.创建和释放一个对象
Point*pt;
//对象指针
pt=newPoint(10,10);
//...
deletept;
实例5>
2.创建和释放一组对象(数组)
Point*pt=newPoint[2];
//对象数组
delete[]pt;
对数组动态分配存储时,不能同时对数组中的元素进行初始化。
若要实现对象数组中元素的初始化,可按下面两种方法:
①在类中定义不带参数的构造函数或全部带缺省参数的构造函数(实例6)
②在类中定义一个成员函数专门用来完成初始化功能;
对象数组被创建后,通过调用此函数来对数组中的对象元素进行初始化
对象数组的初始化方法同基本类型的数组
如,inta[2];
a[0]=1;
Aa[2];
a[0]=A
(1);
inta[3]={1,2,3};
Aa[3]={A
(1),A(1,’a’),a[0]};
若创建多维数组,必须提供所有维的大小。
int*qi=newint[2][3][5];
int*qi=newint[][3][5];
//×
参看教材【例3.18】>
3.使用注意:
对简单的数据类型,以及没有显示定义构造函数和析构函数的类,两种管理内存的方式可以混合使用,即new分配的内存可用free释放,malloc分配的内存可用delete释放。
但若一个类显示定义了构造和析构函数,则最好用new和delete分配和释放内存。
动态分配的存储,必须显式删除(delete/free),否则造成内存泄漏!
3.4.4小结
构造函数和析构函数是一种特殊的成员函数,其个性:
①都没有返回类型,即在定义时不需指出类型
②构造函数可以有缺省参数,但要注意避免二义性
③构造函数可重载,但析构函数不可重载(唯一性)
④构造函数不可显式调用,但析构函数可以
⑤当创建(定义)对象时,系统自动调用构造函数;
当删除对象时,系统自动地调用析构函数(C++新特点:
提供了自动回收和显式回收两种内存管理方式)
⑥都不能被继承
⑦析构函数可以是虚的(virtual),但构造函数不行
3.4.5巩固练习
练习1——改错(1-6)
练习2——成员的访问和对象的定义:
2
练习3——写出输出结果:
1
看懂教材第3章所有例子!
重点: