第四章 对象的初始化与销毁:构造函数与析构函数本章主要内容:
1,构造函数、拷贝构造函数 —— 重点
2,构造函数的初始化列表 —— 难点
3,析构函数 —— 重点总体要求:
理解构造函数与析构函数的必要性与特殊性,掌握它们的使用方法
16:29:32
§ 1 对象的初始化
1.成员变量全部公有时的初始化类的成员变量都是公有的,此时对该类对象的初始化可以与 C中对结构体变量的初始化一样
class person{
public:
char name[15];
int age;
address addr;
};
class address{
public:
long telenum;
char addr[30];
};
person p1={,张三,,23,
{8911114,“LanZhou University”} };
缺点:
数据完全外露,没有实现信息隐藏
– 实质是对象中成员变量的初始化,有 3种常见方法
16:29:32
2.使用公有的成员函数完成对象的初始化在类中提供公有的成员函数,对象通过调用这样的成员函数对其内部的成员进行初始化例:银行帐户类
class Account{
char sName[32];
char sID[20];
float fBalance;
public:
void Initialze(char *,
char *,float);
};
void Account::Initialize(
char *name,char *id,
float amount )
{
strcpy(sName,name);
strcpy(sID,id);
fBalance=amount;
}
void main(){
Account acc;
acc.Initialize(“张三,,“s9801”,1000);
}
缺点:
客户程序员容易忘记调用这样的函数,对象的初始化得不到保障
16:29:32
3.构造函数 (constructor)
– 构造函数是类中特殊的成员函数,其函数名与类名相同
– 创建对象时系统 自动调用 构造函数创建对象的两种方法:
.定义类类型的变量
.用 new运算符动态产生对象无论用那种方法,都会 自动调用 构造函数
—— 不是由客户程序员人为调用,而是 自动调用例,,银行帐户类,日期类
– 把给成员变量赋值的操作写在构造函数中,就能初始化成员变量:对象一旦被创建,马上调用构造函数,在能对对象进行其它操作之前,对象的成员变量已有确定的值了
16:29:32
构造函数的必要性与特殊性
– 函数名与类名相同
– 在定义和声明时,不能说明构造函数的返回值类型,而且构造函数根本不返回任何值
– 一个类的构造函数可以有多个,构造函数允许重载,一个构造函数对应一种创建对象的方法
– 构造函数可以带有形参,也可以不带形参,创建对象时 自动调用参数类型,数目均能匹配的一个 。 实参通过在创建对象时用括号的形式,在括号中传递给形参 。 若调用的是不带参的构造函数,则括号可省略 。 无论带参与否,程序中都不能通过对象名或对象指针显式调用构造函数例,Date dtObj(2004,10,11);
Date *pDtObj=new Date(2004,10,12);
Date dtObjDft;
Date *pDt=new Date;
16:29:32
构造函数的必要性与特殊性(续)
– 类中未显式提供构造函数时,C++会自动添加一个默认构造函数,该构造函数不带有形参,且函数体为空,在后台起作用 ;
若类中显式提供了构造函数,则不再添加默认构造函数
– 使用默认构造函数的情况下,若创建的对象是全局对象或静态对象,则成员变量的值全部被置为相应类型的 0值,否则成员变量的值不确定
– 应尽可能提供自己的构造函数,而不使用默认的构造函数
16:29:32
§ 2 拷贝构造函数 (copy-constructor)
– 一种特殊的构造函数
– 创建对象时,用一个已经存在的对象对新创建的同类对象进行初始化,此时要求构造函数的参数为自身类类型的引用,这种构造函数称为 拷贝构造函数
– 拷贝构造函数的一般形式为类名,:类名 (const 类名 & 引用名,… );
一般情况下,多使用如下形式类名,:类名 (const 类名 & 引用名 );
其中 const是可选的例:拷贝构造函数
class A{
public:
A(int){cout << "A()" << endl;}
A(const A&){cout << "A(A&,int)" << endl;}
};
void main(){A a(1);A b(a);A c=b;}
16:29:32
拷贝构造函数调用的时机
– 用已经存在的对象初始化新创建的对象时,调用拷贝构造函数例,Location类
– 对象做形参,调用函数时用实参对象初始化形参,调用拷贝构造函数
void f(Location p)
{cout<<“Functin:”<<p.getX()<<“,”<<p.getY()<<endl;}
推荐使用对象的引用做形参,可降低函数调用过程中的开销
– 函数返回对象,此时要通过拷贝构造函数创建临时对象
Location g()
{Location A(1,2);return A;}
16:29:32
浅拷贝与深拷贝
– 类中未显式提供拷贝构造函数时,C++会自动添加一个默认拷贝构造函数,该拷贝构造函数完成的功能是位对位的拷贝,亦即将已存在对象中的每一位复制到新创建的对象中对应的位 (亦即 将已存在对象中的每个成员拷贝到新建对象中对应的成员 ),此时称完成的拷贝是 浅拷贝
– 默认的拷贝构造函数在一般情况下可以很好地工作,但一些特殊情况下,仅使用默认的拷贝构造函数可能会引起问题,
此时必须提供自己的拷贝构造函数并按需要自己完成拷贝工作才能解决问题,称这时完成的拷贝是 深拷贝例:内部包含指针成员变量的类
.浅拷贝,深拷贝例:自动记录本类对象数目的类
.浅拷贝,深拷贝
16:29:32
§ 3 构造函数的初始化列表
– 类的成员变量是 const的或是另外一个类的对象时,对其进行初始化,需要在定义构造函数时使用 初始化列表
– 初始化列表的一般形式类名,:类名 (形参列表 ):成员变量名 1(初始值列表 ),…,成员变量名 n(初始值列表)
{ /*函数体 */ }
16:29:32
一,const的成员变量
– 对简单类型的非静态成员变量初始化,既可以在构造函数的函数体中对其赋值,也可以使用初始化列表
//Fred.cpp
Fred::Fred():size(0)
{x=0;}
Fred::Fred(int sz,int xx)
:size(sz)
{x=xx;}
void Fred::Print(){
cout << size << endl;
}
//Fred.h
class Fred{
const int size;
int x;
public:
Fred();
Fred(int sz,int xx);
void Print();
};
– 对 const成员变量初始化,只能通过构造函数的初始化列表进行,而不能在函数体中赋值
Fred::Fred(int sz,int xx):size(sz),x(xx)
{ }
16:29:32
二,成员变量是另一个类的对象把作为成员变量的对象称为 成员对象或内嵌对象,而把包含该成员对象的类称为 外围类例,Circle类其中 Circle类是外围类,而成员变量 ptCenter是成员对象或内嵌对象
– 成员对象的初始化只能通过外围类的构造函数的初始化列表,调用成员对象本身的构造函数来进行
– 若成员对象所属的类有多个构造函数,则通过外围类构造函数的初始化列表调用参数类型,数目均能匹配的一个 。
若调用成员对象的不带参的构造函数,则初始化列表中可以省略对该构造函数的显式调用,此时由系统自动调用
– 有多个成员对象时,每个成员对象都必须出现在初始化列表中,且它们的构造函数的调用次序与在初始化列表中出现的次序无关,只取决于在类中定义它们时的次序
– 先调用成员对象的构造函数,然后执行外围类的构造函数的函数体
class Circle{
Point ptCenter;
int nRadius;
public:
Circle();
Circle(int x,int y,int r);
Circle(Point &pt,int r);
Circle(const Circle & c);
void Print();
int getRadius()const;
};
class Point
{
int x,y;
public:
Point();
Point(int xx,int yy);
void moveTo(
int xx,int yy);
int getX()const;
int getY()const;
};
16:29:32
§ 4 对象的撤销,析构函数
– 对象的生命期结束时系统要撤销对象,除了回收对象本身占据的存储空间以外,还需做一些清理工作
– 如撤销一个链表对象,除了释放链表对象本身占据的存储空间外,还需要释放链表上结点的存储空间
– C++中,类或对象的 析构函数 用来完成这个任务
– 析构函数析构函数是类的一个特殊成员函数,其函数名是类名前冠以,~,,其一般形式为:
类名,:~类名();
16:29:32
例:自动记录本类对象数目的类
class CCountObj{
static int count;
int num;
public:
CCountObj();
CCountObj(const
CCountObj & c);
~ CCountObj();
static int getCount();
};
int CCountObj::count=0;
CCountObj::CCountObj()
{count++;num=count;}
CCountObj::CCountObj(
const CCountObj & c);
{count++;num=count;}
CCountObj::~ CCountObj()
{count--;}
int CCountObj::getCount()
{return count;}
16:29:32
例:链表类
//LinkList.h
class LinkList{
private:
struct NODE{
char ch;
struct NODE *next;
}*head;
public:
LinkList();
~ LinkList();
bool Insert(int,char);
bool Delete(int);
bool Delete(char);
void Display();
};
//LinkList.cpp
//… 此处省略其它函数定义
LinkList::~ LinkList(){
NODE *pWork;
while(head!=NULL)
{
pWork=head;
head=head->next;
delete pWork;
}
}
16:29:32
说明:
– 定义和声明析构函数时,不能说明析构函数的返回值类型,
析构函数不返回任何值
– 析构函数不带有任何参数,一个类只能有一个析构函数:
析构函数不能被重载
– 撤销对象时系统 自动调用 析构函数撤销对象的两种情况:
1.对象的生存期结束
2.使用 delete运算符释放 new动态生成的对象两种情况下都会 自动调用 对象的析构函数
– 析构函数不能由客户程序员显式调用
16:29:32
本章总结
– 各种对象的构造函数与析构函数的调用时机
.全局对象程序流程进入 main()函数之前按定义对象的顺序调用构造函数,程序运行结束时按相反的顺序调用析构函数
.局部对象调用包含该局部对象的函数时按定义对象的顺序调用构造函数,生命期结束时调用析构函数 ;静态局部对象直到程序运行结束时调用析构函数 。
析构函数的调用顺序与构造函数的顺序正好相反
.成员对象创建外围对象时使用初始化列表调用成员对象的构造函数,
且先调用成员对象的构造函数,再执行外围对象的构造函数体;外围对象撤销时调用成员对象的析构函数,但先执行外围对象的析构函数体,再调用成员对象的析构函数析构函数的调用顺序与构造函数的调用顺序完全相反
16:29:32