C/C++程序设计
1
四、析构函数 (destructor)
五、赋值运算符函数 operator =
六、成员函数重载七、缺省参量的成员函数八、编译器默默提供的成员函数
C/C++程序设计
2
四、析构函数 (destructor)
析构函数是类中的特殊成员函数。析构函数与构造函数的作用相反,构造函数在对象的定义点由编译器自动调用,
析构函数则是在对象生存期结束时自动执行的成员函数。
析构函数的语法格式是在无参构造函数的构造函数名前加以一个波浪符 ~也即按位求反运算符,由此形成特殊的析构函数名,表示“反构造函数”。
若没有提交析构函数,编译器暗中生成一个公共的缺省析构函数,它是空函数。
C/C++程序设计
3
析构函数在类中的声明和定义格式为:
~CType(); 析构函数名 (); ~类名 (); //声明格式
CType::~CType () 类名,:~类名 () //定义格式
{ {
析构函数语句序列; //语句为空时对应缺省的析构函数
} }
上面析构函数的定义放置在实现文件中,放在类的声明中时写为:
~CType(){ 语句序列; } ~类名 (){ 语句序列; }
C/C++程序设计
4
构造、析构函数和赋值运算符函数都不参入继承机制。
下面是析构函数的特殊之处:
1),析构函数是唯一的,既没有入口参数也无返回类型,
因此不能重载。
2),析构函数不能继承但可以是虚函数。
3),析构函数体中不能使用 return语句返回值。
4),new运算符定义的对象,delete运算符清除时导致析构函数的隐含调用。
C/C++程序设计
5
new运算符定义对象时 new运算符先申请内存然后调用构造函数。
由 delete清除 new运算符定义的对象时,则是先调用析构函数,然后再收回原来由 new运算符为对象分配的内存。
new CType[n]运算分配内存大小是 n*sizeof(CType),
并不包含其成员指针其后所间接在构造函数中进一步获得的内存,这片内存应由析构函数先行释放。
new CType[n]运算符与 delete [ ]匹配,new CType运算符与 delete 匹配,其余的搭配都是不稳健的,应避免。
匹配关系为 [CType *pk=new CType[2];..;delete [ ] pk;]
与 [CType *p=new CType;...;delete p;]。
delete运算符隐含调用析构函数时,在调用点相应的析构函数应是可访问的。
C/C++程序设计
6
[例 ] 对象定义与析构函数
#include <stdio.h>
static int numc=0;
class CType
{ public,CType (int=3 ); ~CType();
private,long * m_p ;
int m_n;
};
CType::CType (int n)
{ m_n = n;
printf ("%d,CType (); ++%d\n",m_n,++numc);
m_p = new long [m_n];
}
C/C++程序设计
7
CType::~CType ()
{ printf ("%d,~CType (); %d--; ",m_n,numc- - );
delete [ ] m_p;
}
CType g (1);
CType *q = new CType (9);
void main () //因此相应的析构函数未被调用
{ printf ("Enter into main() {\n");
CType d (2);
CType *pk= new CType [2];
delete [ ] pk;
printf ("\nGo outof main () }\n");
}
C/C++程序设计
8
//程序运行输出结果,
1,CType();++1
9,CType();++2
Enter into main(){
2,CType();++3
3,CType();++4
3,CType();++5
3,~CType();5--; 3,~CType();4--;
Go outof main() }
2,~CType();3-- 1,~CType();2–
C/C++程序设计
9
说明:
全局对象的构造函数在进入主函数之前就被系统优先调用,这是 C++程序相对于 C程序一个重要的突破。
析构函数按照与构造函数相反的次序隐含地被系统调用,最先登陆构造的对象最后经过析构函数撤离现场。
程序 5次调用构造函数,4次调用析构函数。
C/C++程序设计
10
五、赋值运算符函数 operator=
赋值运算符函数也称为等号运算符函数,赋值运算符的函数原型为:
B& B::operator=(const B& r);
函数原型说明一个函数名为 operator=的运算符函数,B::operator = 是这个函数的全限定名,表示,
operator = 函数是属于类 B的成员函数,圆括号包括的是
const B&型的入口形参。
B&是这个函数的返回类型,这是一个返回左值对象的函数。因此该函数可以用于连续的赋值运算如 a=b=c或
(a=b)=c。
C/C++程序设计
11
[例 ] 赋值运算符函数与拷贝构造函数
#include <stdio.h>
#include<string.h>
class B
{ private:int m_n;
public,B (int v=1) {Set (v); }
B (const B& r);
B& operator= (const B& r);
void Set (int n){m_n=n; }
void Show () {printf ("n=%d;",m_n);}
};
B::B (const B& r)
{ printf ("call B::B (const B& r); ");
memcpy (this,&r,sizeof (B));
}
C/C++程序设计
12
B& B::operator= (const B& r)
{ printf ("call B& B::operator = (const B& r);");
if (this==&r) return *this;
memcpy (this,&r,sizeof (B));
return *this ; }
void main()
{ B d; d.Show ();
B a=d; a.Show ();
a.Set (100); d=a;
d.Show ();
} //输出,n=1;call B::B(const B& r);
n=1; call B&B::operator=(const B& r);n=100
C/C++程序设计
13
拷贝构造函数和等号赋值函数其内在机制是一样的,
memcpy函数完成幕后数据拷贝。
等号语法 a=d,d=a派生出两种语义,编译器根据周边氛围确定调用合适的函数,在对象的定义点调用与初始化过程相关的拷贝构造函数,在其余的环境调用等号赋值运算符函数。
C/C++程序设计
14
[例 ] 拷贝构造函数的隐含作用
class A
{ public,A(int n) { m_n=n; }
~A() { }
private,A (const A& r) { m_n=r.m_n; }
int m_n;
};
void f (A a) { }
A g (int n) { return A (n); }
void main()
{ A d(2); A b=10;
A a=d; f (b);
}
C/C++程序设计
15
A类中拷贝构造函数声明为私有的,对象定义语句
[A a=d;]
等价于 [A a(d);],a(d)
调用拷贝构造函数。
拷贝构造函数是私有的,定义语句导致外部访问私有的成员,因而弹出错误提示。
在函数对象数值形参入口 f (A a)和对象数值返回
return A(n)
时要求一个可访问的拷贝构造函数。
函数 f (A a)是对象数值形参,调用 f (b)引起拷贝构造函数的调用,如果在虚实结合处不存在可访问的拷贝构造函数,则导致错误提示。
C/C++程序设计
16
六、成员函数重载类中的成员函数如全局函数一样可以重载。析构函数是唯一的,不能重载的函数是析构函数。
成员函数允许重载 (overloading)其含义以两个重载函数为例,
type CType::funct (t1 v1,t2 v2,tn vn)
{ 语句序列; }
type CType::funct (type3 v1,type4 v2,typem vn)
{ 语句序列; }
C/C++程序设计
17
重载的成员函数是全限定名 CType::funct相同形参列表类型、个数、位置有所不同的成员函数。
成员函数重载在函数的调用点根据实参的类型和位置差异在编译阶段确定。
CType::用于函数名的名称界定,因此另一个类中同名的成员函数 funct是绝然不同的函数,正是类域分辨符减少了同名函数的混乱,这就是封装的优点。
C/C++程序设计
18
[例 ] CRect类中的重载函数 SetRect
#include<string.h>
#include<stdio.h>
class CRect
{ public,long left; long top;
long right; long bottom;
CRect (int l=1,int t=2,int r=3,int b=4);
void SetRect (int x1,int y1,int x2,int y2);
void SetRect (const CRect& r) ;
void Show();
};
C/C++程序设计
19
void CRect::Show ()
{ printf ("left=%d,top=%d,right=%d,bottom=%d;",
left,top,right,bottom) ;
}
void CRect::SetRect (const CRect& r)
{ left =r.left; top = r.top;
right = r.right; bottom = r.bottom;
}
CRect::CRect (int l,int t,int r,int b)
{ SetRect (l,t,r,b) ;
}
C/C++程序设计
20
void CRect::SetRect (int l,int t,int right,int bottom)
{ left = l; top = t; this->right = right;
this->bottom = bottom;
}
void main()
{ CRect r,s;
r.Show(); r.SetRect(5,6,7,8);
s.SetRect(r); s.Show();
}
//输出:
left=1,top=2,right=3,bottom=4;
left=5,top=6,right=7,bottom=8
C/C++程序设计
21
七、缺省参量的成员函数缺省参量的函数是形参列表参量具有事先预定的值,这给编程带来的好处是,
首先提醒程序员合适的实参值大抵在什么范围 ;
其次缺省参量对于函数的使用提供了方便,只需将缺省值作为实参带入函数就能得到一致的结果。
对于带有缺省值的如下函数原型:
long CType::Set (int n,long m=10,short s=5);
CType::CType (int n,long m=10,short s=5);
在不引起重载调用歧义的情况下存在如下等价的调用格式,
this-> Set (7); this-> Set (7,10); this-> Set (7,10,5);
CType a (7); CType a (7,10); CType a (7,10,5);
C/C++程序设计
22
[例 ] 缺省参量的构造函数和成员函数
#include<stdio.h>
class CType
{ long m; int n; short s;
public,void Set(int n,long m=10,short s=5);
CType (int n=1,long m=10,short s=5);
void Show (); CType () {}
};
void CType::Set (int n,long m,short s)
{ this->n=n; this->m=m; this->s=s; }
CType::CType (int n,long m,short s) { Set (n,m,s) ;}
void CType::Show()
{ printf ("%d,%d,%d \n",n,m,s); }
C/C++程序设计
23
void main ()
{ CType* pthis =new CType(1);
pthis->Set (7,10,5); pthis->Show ();
pthis->Set (7,10); pthis->Show ();
CType a (7,10,5); a.Show ();
CType c (7,10); c.Show ();
}
为避免重载的歧义性,无参构造函数和全部设置缺省值的构造函数只提交一个版本。虽然两者都提供并不算错,但编译器提出警告。
对象定义 {CType b;}既可以调用无参构造函数也可以调用全部设置缺省值的构造函数;两者都是缺省构造函数。
C/C++程序设计
24
八、编译器默默提供的成员函数对一个简单的类声明
[class CType{long m_tData;};],
编译器暗中提交无参构造函数、析构函数、拷贝构造函数、
赋值运算符函数以及一对取地址运算符函数。
取地址运算函数存在一个只读成员函数,该函数用
const关键此后置限定,用于取只读对象的地址。
这些成员函数是公共访问性质的,仅在程序调用时才隐含生成不露声色。
C/C++程序设计
25
编译器默默奉献的成员函数清单如下:
#include <stdio.h>
#include<string.h>
class CType
{ long m_tData;
public,CType (){ }
~CType (){}
CType (const CType& r) ;
CType& operator= (const CType& r);
inline CType* operator&();
inline const CType* operator& ()const;
};
C/C++程序设计
26
int and=0;
CType::CType(const CType& r)
{memcpy(this,&r,sizeof (CType)); }
CType& CType::operator= (const CType& r)
{ if (this==&r) return *this;
memcpy (this,&r,sizeof (CType));
return *this;
}
inline CType* CType::operator&()
{ and=1; return this; }
inline const CType* CType::operator&() const
{and=2; return this; }
C/C++程序设计
27
void main()
{ CType v;
CType* pv=&v;
printf ("%d\t",and);
const CType c;
const CType* pc=&c;
printf ("%d\t",and);
and=0;
pc=v.operator& () ;
printf ("%d\t",and);
}
//输出,1 2 1
C/C++程序设计
28
默认的拷贝构造函数和赋值运算符函数不能胜任类中具有指针成员的复制。
可以废弃两者的默认功能。废弃拷贝构造函数和赋值运算符函数默认功能的方法是将它们归入私有的访问控制属性,仅有函数的声明而无实际的函数定义。 如下所示:
class CType
{ long m_tData;
private:
CType (const CType& ) ;
CType& operator= (const CType& );
};
C/C++程序设计
29