C/C++程序设计
1
一、构造函数 (constructor)
二、构造函数的调用三、对象数组的初始化
C/C++程序设计
2
一、构造函数 (constructor)
C++语言中有一些成员函数性质是特殊的,这些成员函数负责对象的建立、删除和拷贝。
这些函数的特殊性在于可以由编译器自动地隐含调用,其中一些函数调用格式采用运算符函数重载的语法。
本章对这些特殊的系统默默提供的成员函数进行描述,
附带初步介绍运算符重载的概念。
C++引进一个自动完成对象初始化过程的机制,这就是类的构造函数。
C/C++程序设计
3
1,构造函数的特殊性,
a,构造函数的函数名唯一的,限制为当前类的类名但可以拥有多个形参不同的版本。
b,构造函数不返回具体的数值,不指定函数的返回类型。
c,构造函数是不能继承也不能是 virtual的成员函数。
构造函数是特殊的初始化对象数据状态的成员函数,
编译器在对象的定义点为对象先分配内存,随即自动启动构造函数初始化对象或对象的子集合。
C/C++程序设计
4
构造函数声明语句放置于类的声明内,语法格式为:
class CType class 类名
{ public,成员声明语句 ; { public,成员声明语句 ;
CType (t1 v1,t2 v2,...,tn vn); 构造函数名 (形参列表 );
}; };
C++语言规定构造函数不前置返回类型,void关键字也不能说明构造函数的返回特性。
对象是不同数据类型的集合,构造函数常出现不同的重载版本,每一个特定的构造函数以特定的方式初始化对象特定的数据集合。
C/C++程序设计
5
构造函数可以内置定义也可以在类外定义,构造函数定义的语法格式为:
CType::CType(t1 v1,t2 v2,...,tn vn)
//类名,:构造函数名 (形参列表 )
{
//语句序列中可用 return ;
初始化数据成员的语句序列;
//语句序列中不用 return expre;
}
//类名,:前无返回类型
C/C++程序设计
6
2.拷贝构造函数和无参构造函数
a,无参构造函数无参构造函数是入口没有任何形参的构造函数,如果构造函数中的语句为空,则称为无参空构造函数。无参构造函数定义的格式为:
CType::CType() //类名,:构造函数名 ()
{ //无参构造函数的定义语句序列; //语句序列可以全部为空
}
//形如 CType::CType(){}的构造函数为无参空构造函数
C/C++程序设计
7
b,拷贝构造函数
class CType class 类名
{ public,{ public:
CType (const CType& r ); 类名 (形参类名 &);
}; };
CType::CType(const CType& r)
{
memcpy (this,&r,sizeof (CType));
}
拷贝构造函数的作用是用一个已经存在的对象 (由形参 r
的实参确定 )初始化当前的新的对象 (由 this指针确定 ),新的对象与初始化的源对象具有相同的数据状态。
C/C++程序设计
8
拷贝构造函数中同一名称 CType三位一体地相聚在一起,它们的职责各异分别是类域分辨名、构造函数名和形参类型名,第三个参数是 CType&类型,这个参数是对象的引用,加一个 const前置限定入口形参作为只读数据源。
不允许 CType::CType (CType)形式的构造函数,以避免对象的无穷递归。
无参空构造函数和拷贝构造函数是系统默默提供的构造函数,其访问控制属性是公共的。
C/C++程序设计
9
一旦提供了任一构造函数则系统默认的无参构造函数不再起作用,因此常明显地补充一个公共的无参构造函数以便定义对象时被系统自动调用。
系统提供的拷贝构造函数仅在程序员提交自己的拷贝构造函数时才被覆盖。
在用无参空构造函数构建全局对象或静态对象时,对象的内存数据具有 0状态。
而用无参空构造函数构建非静态的局部对象,局部对象的值是不确定的。
C/C++程序设计
10
c.缺省构造函数无参构造函数是缺省构造函数。构造函数中所有的形参都具有缺省值,也称为缺省构造函数。具有全部缺省值的构造函数的声明格式为:
class CType
{ t1 m_v1; t2 m_v2;,..; tn m_vn;
public,Type ();
CType (t1 v1=const1,t2 v2=const2,...,tn vn=constn);
};
C/C++程序设计
11
缺省构造函数的实现部分位于相应的,cpp文件时,无参构造函数可以直接用常数初始化或者用函数体之前的其值已知的全局变量 g_v1,g_v2,...,g_vn初始化:
CType::CType()
{ m_v1= g_v1; m_v2= g_v2 ;,..; m_vn=g_vn ;
m_v1= const1; m_v2= const2;,..; m_vn= constn ;
}
全部设置缺省值(缺省值通常在声明部分设置)的构造函数可以定义如下:
CType::CType(t1 v1,t2 v2,...,tn vn)
{ m_v1=v1; m_v2=v2 ;,..; m_vn=vn ;}
C/C++程序设计
12
[例 ] 定义对象数组 CA a[2];导致两次调用缺省构造函数。
程序输出,2,3,13
#include <stdio.h>
static int cx=1; const int constn=0;
class CA { public,CA (int m=1)
{x= constn +m+cx++; } long x;
};
void main ()
{ CA a[2],b(10);
printf ("%d,%d,%d",a[0].x,(a+1)->x,b.x);
}
C/C++程序设计
13
对象数组的每一元素 a[i]就是对象,a[i]访问成员的格式如同对象或对象指针访问成员的格式一样。对象数组名实际上是对象指针。格式为:
对象数组名 [下标 ].成员名例如,a[i].x
(对象数组名 +下标 )->成员名例如,(a+i)->x
C/C++程序设计
14
二、构造函数的调用构造函数在对象的定义点被自动调用一次,构造函数的调用采用函数对象名语法隐含调用或者直接显式调用,在对象定义点,相应构造函数应是可访问的。
名称可访问的含义是指或者成员是公共的或者在友员函数和成员函数中访问。
1,对象名调用对象名调用构造函数产生全局对象或局部对象,加上
Static 关键字分为静态全局对象或静态局部对象。
构造函数的对象名调用采用的是函数对象名的语法,构造函数的函数名由对象名替换,形参由相应的实参替换,虚实结合满足函数调用时类型的转换匹配。
C/C++程序设计
15
构造函数的调用
(r1,r2,...,rn是匹配类型 t1,t2,...,tn的实参 )
在对象的定义处进行:
CType a(r1,r2,...,rn)
CType b(a);
上面 a和 b是对象名,调用构造函数时对象名替代函数名
CType,不同的入口形参的构造函数形成不同的函数版本,
编译器在幕后进行名称细分。
无参构造函数显式调用格式是:
CType();
C/C++程序设计
16
基于同 C结构变量定义兼容的考虑,C++语言规定对于缺省构造函数的隐含调用省去其后的一对圆括号,其格式书写为:
CType f,d[6];
这是因为保留圆括号的格式存在另外的语义:
CType f ();
// 等价于 CType f(void);
此处的 f()是一个入口无参的全局函数原型说明,返回
CType数值对象。
而对象定义语句 {CType f;}导致定义一个名称为 f的对象,在定义点调用缺省构造函数。
C/C++程序设计
17
定义静态局部变量仅初始化一次,非静态的局部变量每次都初始化。
定义静态的局部对象时,构造函数也仅调用一次;但如果定义局部静态对象的函数未被调用,相应的构造函数也未被调用。
静态的局部变量和静态的局部对象与全局对象一样安排在全局数据区,可以将静态局部对象视为加上作用域限制的全局对象。
非静态的局部对象随对象的生灭累积地多次调用初始化构造函数。
C/C++程序设计
18
[例 ] 构造函数的调用
#include <stdio.h>
static int n=0;
class CType
{ public,CType () { m_n=n++; }
int m_n;
};
CType d;
void fs () { static CType s; printf (“s.%d\t”,s.m_n); }
void fa () { CType a ; printf (“a.%d\t”,a.m_n); }
void main () { fs(); fs(); fa(); fa(); }
//输出,s.1 s.1 a.2 a.3
C/C++程序设计
19
2,new运算符调用
new运算符调用构造函数产生的是 heap空间的对象。
其格式为:
CType * pa =new CType (r1,r2,...,rn);
这个定义调用构造函数 CType(t1,t2,...,tn),这是构造函数的显式调用。
同样:
CType * pb =new CType ();
pb =new CType;
CType * pd =new CType [5];
C/C++程序设计
20
构造函数的调用都导致对象的诞生。对象名调用产生的对象通过圆点运算符操作类中的成员,new运算符调用产生的堆中对象通过箭头运算符访问类中的成员。
new运算符调用形式其幕后机制是:编译器首先调用
new运算符函数,该函数试图在内存中获取一段内存;如果这一步骤成功,调用构造函数,以构造函数中的入口实参值对指定对象内存的若干成员变量赋值。
new CType[5]要求存在可访问的缺省构造函数。
new运算符定义对象是用户动态产生对象,其对象建立在 Heap空间中。而直接定义对象,编译器根据对象定义点负责构建并仔细安排对象的析构以保证局部对象的动态撤离,
C/C++程序设计
21
new运算符定义的对象需要用户精心调用 delete运算符以清除相关的内存空间。
new运算符表达式定义动态对象数组的语法格式为:
new CType[m]
new 类名 [动态数组大小 ]
该格式要求存在公共的或可访问的缺省构造函数。系统在堆区中开辟了一块大小为 m*sizeof(CType)的空间,表达式的结果是 CType *型的地址。
表达式的结果就指向这块存储空间的开始位置,m是可以动态指定的正数。
C/C++程序设计
22
3,无名对象调用无名对象调用构造函数就是构造函数的直接显式调用,
格式为,CType (r1,r2,...,rn)
直接显式调用构造函数导致一个临时对象的诞生,这个临时对象称为无名对象。主要用于形参类型为 CType时作为实参或函数的返回类型为 CType时出现在 return语句中。
下面的语句直接调用构造函数产生无名对象,无名对象赋值给 a对象。
a= CType (r1,r2,...,rn);
构造函数调用不采用 a.CType (r1,r2,...,rn) 格式。
C/C++程序设计
23
4,函数对象名语法算例
[例 ] 函数对象名语法
#include <stdio.h>
#include<string.h>
class CType
{ int m_n;
public,CType (int n=1) {m_n=n; }
void operator () (const CType* s);
void Show (char*s)
{ printf ("%s,n=%d\t",s,m_n);}
};
C/C++程序设计
24
void CType::operator ()(const CType* s)
{ memcpy (this,s,sizeof (CType)); }
CType d ();
void main ()
{ CType d (1);
d.Show ("d(1)");
CType *p=new CType (2);
d (p); d.Show ("d(p)");
d=CType (3); d.Show ("CType(3)");
CType (4).Show ("CType(4)");
}
//输出,d(1),n=1 d(p),n=2 CType(3),n=3 CType(4),n=4
C/C++程序设计
25
说明:
operator()是函数调用运算符,()”重载,operator()是函数名。
根据函数对象名语法,此函数调用时用对象名 d替代函数名 operator(),实参用相应的对象指针,因此形成 d(p)调用形式。
另外 {CType d();}说明语句中 d是函数名,
{CType d(1);}对象定义语句中的 d是对象名。一个在文件作用域,一个在局部作用域,不构成名称歧义,编译器优先采用局部范围的名称。
如果两个语句在一个作用域则引起名称混乱。
C/C++程序设计
26
三、对象数组的初始化定义数组对象如,
[ CType a[7]; CType *p=new CType[5]; ]
要求存在缺省构造函数。
对象数组的定义牵连到公共的或可访问的缺省构造函数的调用。
可以通过构造函数显式调用的形式构成初始化列表。
定义对象数组包含对于构造函数的多次调用。
对象数组的初始化语法为,
CA d[3]= { CA (r1,r2),CA (s1,s2) };
相当于,CA d[3]= { CA (r1,r2),CA (s1,s2),CA () };
C/C++程序设计
27
[例 ] 构造函数的显式调用构成初始化列表
#include <stdio.h>
static int cnum=1; static int cx=-1,cy=-1;
struct CA
{ CA() { x=cx--; y=cy--;
printf ("%d.CA (%d,%d); \n",cnum++,x,y);}
CA (int m,int n)
{ x=m; y=n;
printf ("%d.CA (+%d,+%d); \n",cnum++,x,y); }
long v() { return x; }
private,long x; long y;
} ;
C/C++程序设计
28
void main ()
{ CA a[2];
CA d[3] = { CA (1,1),CA (2,2)};
d[2]=CA(3,3);
if (d[1].v() ==(d+1)- >v ())
printf ("d[1].v()=(d+1)->v()");
}
C/C++程序设计
29
1
一、构造函数 (constructor)
二、构造函数的调用三、对象数组的初始化
C/C++程序设计
2
一、构造函数 (constructor)
C++语言中有一些成员函数性质是特殊的,这些成员函数负责对象的建立、删除和拷贝。
这些函数的特殊性在于可以由编译器自动地隐含调用,其中一些函数调用格式采用运算符函数重载的语法。
本章对这些特殊的系统默默提供的成员函数进行描述,
附带初步介绍运算符重载的概念。
C++引进一个自动完成对象初始化过程的机制,这就是类的构造函数。
C/C++程序设计
3
1,构造函数的特殊性,
a,构造函数的函数名唯一的,限制为当前类的类名但可以拥有多个形参不同的版本。
b,构造函数不返回具体的数值,不指定函数的返回类型。
c,构造函数是不能继承也不能是 virtual的成员函数。
构造函数是特殊的初始化对象数据状态的成员函数,
编译器在对象的定义点为对象先分配内存,随即自动启动构造函数初始化对象或对象的子集合。
C/C++程序设计
4
构造函数声明语句放置于类的声明内,语法格式为:
class CType class 类名
{ public,成员声明语句 ; { public,成员声明语句 ;
CType (t1 v1,t2 v2,...,tn vn); 构造函数名 (形参列表 );
}; };
C++语言规定构造函数不前置返回类型,void关键字也不能说明构造函数的返回特性。
对象是不同数据类型的集合,构造函数常出现不同的重载版本,每一个特定的构造函数以特定的方式初始化对象特定的数据集合。
C/C++程序设计
5
构造函数可以内置定义也可以在类外定义,构造函数定义的语法格式为:
CType::CType(t1 v1,t2 v2,...,tn vn)
//类名,:构造函数名 (形参列表 )
{
//语句序列中可用 return ;
初始化数据成员的语句序列;
//语句序列中不用 return expre;
}
//类名,:前无返回类型
C/C++程序设计
6
2.拷贝构造函数和无参构造函数
a,无参构造函数无参构造函数是入口没有任何形参的构造函数,如果构造函数中的语句为空,则称为无参空构造函数。无参构造函数定义的格式为:
CType::CType() //类名,:构造函数名 ()
{ //无参构造函数的定义语句序列; //语句序列可以全部为空
}
//形如 CType::CType(){}的构造函数为无参空构造函数
C/C++程序设计
7
b,拷贝构造函数
class CType class 类名
{ public,{ public:
CType (const CType& r ); 类名 (形参类名 &);
}; };
CType::CType(const CType& r)
{
memcpy (this,&r,sizeof (CType));
}
拷贝构造函数的作用是用一个已经存在的对象 (由形参 r
的实参确定 )初始化当前的新的对象 (由 this指针确定 ),新的对象与初始化的源对象具有相同的数据状态。
C/C++程序设计
8
拷贝构造函数中同一名称 CType三位一体地相聚在一起,它们的职责各异分别是类域分辨名、构造函数名和形参类型名,第三个参数是 CType&类型,这个参数是对象的引用,加一个 const前置限定入口形参作为只读数据源。
不允许 CType::CType (CType)形式的构造函数,以避免对象的无穷递归。
无参空构造函数和拷贝构造函数是系统默默提供的构造函数,其访问控制属性是公共的。
C/C++程序设计
9
一旦提供了任一构造函数则系统默认的无参构造函数不再起作用,因此常明显地补充一个公共的无参构造函数以便定义对象时被系统自动调用。
系统提供的拷贝构造函数仅在程序员提交自己的拷贝构造函数时才被覆盖。
在用无参空构造函数构建全局对象或静态对象时,对象的内存数据具有 0状态。
而用无参空构造函数构建非静态的局部对象,局部对象的值是不确定的。
C/C++程序设计
10
c.缺省构造函数无参构造函数是缺省构造函数。构造函数中所有的形参都具有缺省值,也称为缺省构造函数。具有全部缺省值的构造函数的声明格式为:
class CType
{ t1 m_v1; t2 m_v2;,..; tn m_vn;
public,Type ();
CType (t1 v1=const1,t2 v2=const2,...,tn vn=constn);
};
C/C++程序设计
11
缺省构造函数的实现部分位于相应的,cpp文件时,无参构造函数可以直接用常数初始化或者用函数体之前的其值已知的全局变量 g_v1,g_v2,...,g_vn初始化:
CType::CType()
{ m_v1= g_v1; m_v2= g_v2 ;,..; m_vn=g_vn ;
m_v1= const1; m_v2= const2;,..; m_vn= constn ;
}
全部设置缺省值(缺省值通常在声明部分设置)的构造函数可以定义如下:
CType::CType(t1 v1,t2 v2,...,tn vn)
{ m_v1=v1; m_v2=v2 ;,..; m_vn=vn ;}
C/C++程序设计
12
[例 ] 定义对象数组 CA a[2];导致两次调用缺省构造函数。
程序输出,2,3,13
#include <stdio.h>
static int cx=1; const int constn=0;
class CA { public,CA (int m=1)
{x= constn +m+cx++; } long x;
};
void main ()
{ CA a[2],b(10);
printf ("%d,%d,%d",a[0].x,(a+1)->x,b.x);
}
C/C++程序设计
13
对象数组的每一元素 a[i]就是对象,a[i]访问成员的格式如同对象或对象指针访问成员的格式一样。对象数组名实际上是对象指针。格式为:
对象数组名 [下标 ].成员名例如,a[i].x
(对象数组名 +下标 )->成员名例如,(a+i)->x
C/C++程序设计
14
二、构造函数的调用构造函数在对象的定义点被自动调用一次,构造函数的调用采用函数对象名语法隐含调用或者直接显式调用,在对象定义点,相应构造函数应是可访问的。
名称可访问的含义是指或者成员是公共的或者在友员函数和成员函数中访问。
1,对象名调用对象名调用构造函数产生全局对象或局部对象,加上
Static 关键字分为静态全局对象或静态局部对象。
构造函数的对象名调用采用的是函数对象名的语法,构造函数的函数名由对象名替换,形参由相应的实参替换,虚实结合满足函数调用时类型的转换匹配。
C/C++程序设计
15
构造函数的调用
(r1,r2,...,rn是匹配类型 t1,t2,...,tn的实参 )
在对象的定义处进行:
CType a(r1,r2,...,rn)
CType b(a);
上面 a和 b是对象名,调用构造函数时对象名替代函数名
CType,不同的入口形参的构造函数形成不同的函数版本,
编译器在幕后进行名称细分。
无参构造函数显式调用格式是:
CType();
C/C++程序设计
16
基于同 C结构变量定义兼容的考虑,C++语言规定对于缺省构造函数的隐含调用省去其后的一对圆括号,其格式书写为:
CType f,d[6];
这是因为保留圆括号的格式存在另外的语义:
CType f ();
// 等价于 CType f(void);
此处的 f()是一个入口无参的全局函数原型说明,返回
CType数值对象。
而对象定义语句 {CType f;}导致定义一个名称为 f的对象,在定义点调用缺省构造函数。
C/C++程序设计
17
定义静态局部变量仅初始化一次,非静态的局部变量每次都初始化。
定义静态的局部对象时,构造函数也仅调用一次;但如果定义局部静态对象的函数未被调用,相应的构造函数也未被调用。
静态的局部变量和静态的局部对象与全局对象一样安排在全局数据区,可以将静态局部对象视为加上作用域限制的全局对象。
非静态的局部对象随对象的生灭累积地多次调用初始化构造函数。
C/C++程序设计
18
[例 ] 构造函数的调用
#include <stdio.h>
static int n=0;
class CType
{ public,CType () { m_n=n++; }
int m_n;
};
CType d;
void fs () { static CType s; printf (“s.%d\t”,s.m_n); }
void fa () { CType a ; printf (“a.%d\t”,a.m_n); }
void main () { fs(); fs(); fa(); fa(); }
//输出,s.1 s.1 a.2 a.3
C/C++程序设计
19
2,new运算符调用
new运算符调用构造函数产生的是 heap空间的对象。
其格式为:
CType * pa =new CType (r1,r2,...,rn);
这个定义调用构造函数 CType(t1,t2,...,tn),这是构造函数的显式调用。
同样:
CType * pb =new CType ();
pb =new CType;
CType * pd =new CType [5];
C/C++程序设计
20
构造函数的调用都导致对象的诞生。对象名调用产生的对象通过圆点运算符操作类中的成员,new运算符调用产生的堆中对象通过箭头运算符访问类中的成员。
new运算符调用形式其幕后机制是:编译器首先调用
new运算符函数,该函数试图在内存中获取一段内存;如果这一步骤成功,调用构造函数,以构造函数中的入口实参值对指定对象内存的若干成员变量赋值。
new CType[5]要求存在可访问的缺省构造函数。
new运算符定义对象是用户动态产生对象,其对象建立在 Heap空间中。而直接定义对象,编译器根据对象定义点负责构建并仔细安排对象的析构以保证局部对象的动态撤离,
C/C++程序设计
21
new运算符定义的对象需要用户精心调用 delete运算符以清除相关的内存空间。
new运算符表达式定义动态对象数组的语法格式为:
new CType[m]
new 类名 [动态数组大小 ]
该格式要求存在公共的或可访问的缺省构造函数。系统在堆区中开辟了一块大小为 m*sizeof(CType)的空间,表达式的结果是 CType *型的地址。
表达式的结果就指向这块存储空间的开始位置,m是可以动态指定的正数。
C/C++程序设计
22
3,无名对象调用无名对象调用构造函数就是构造函数的直接显式调用,
格式为,CType (r1,r2,...,rn)
直接显式调用构造函数导致一个临时对象的诞生,这个临时对象称为无名对象。主要用于形参类型为 CType时作为实参或函数的返回类型为 CType时出现在 return语句中。
下面的语句直接调用构造函数产生无名对象,无名对象赋值给 a对象。
a= CType (r1,r2,...,rn);
构造函数调用不采用 a.CType (r1,r2,...,rn) 格式。
C/C++程序设计
23
4,函数对象名语法算例
[例 ] 函数对象名语法
#include <stdio.h>
#include<string.h>
class CType
{ int m_n;
public,CType (int n=1) {m_n=n; }
void operator () (const CType* s);
void Show (char*s)
{ printf ("%s,n=%d\t",s,m_n);}
};
C/C++程序设计
24
void CType::operator ()(const CType* s)
{ memcpy (this,s,sizeof (CType)); }
CType d ();
void main ()
{ CType d (1);
d.Show ("d(1)");
CType *p=new CType (2);
d (p); d.Show ("d(p)");
d=CType (3); d.Show ("CType(3)");
CType (4).Show ("CType(4)");
}
//输出,d(1),n=1 d(p),n=2 CType(3),n=3 CType(4),n=4
C/C++程序设计
25
说明:
operator()是函数调用运算符,()”重载,operator()是函数名。
根据函数对象名语法,此函数调用时用对象名 d替代函数名 operator(),实参用相应的对象指针,因此形成 d(p)调用形式。
另外 {CType d();}说明语句中 d是函数名,
{CType d(1);}对象定义语句中的 d是对象名。一个在文件作用域,一个在局部作用域,不构成名称歧义,编译器优先采用局部范围的名称。
如果两个语句在一个作用域则引起名称混乱。
C/C++程序设计
26
三、对象数组的初始化定义数组对象如,
[ CType a[7]; CType *p=new CType[5]; ]
要求存在缺省构造函数。
对象数组的定义牵连到公共的或可访问的缺省构造函数的调用。
可以通过构造函数显式调用的形式构成初始化列表。
定义对象数组包含对于构造函数的多次调用。
对象数组的初始化语法为,
CA d[3]= { CA (r1,r2),CA (s1,s2) };
相当于,CA d[3]= { CA (r1,r2),CA (s1,s2),CA () };
C/C++程序设计
27
[例 ] 构造函数的显式调用构成初始化列表
#include <stdio.h>
static int cnum=1; static int cx=-1,cy=-1;
struct CA
{ CA() { x=cx--; y=cy--;
printf ("%d.CA (%d,%d); \n",cnum++,x,y);}
CA (int m,int n)
{ x=m; y=n;
printf ("%d.CA (+%d,+%d); \n",cnum++,x,y); }
long v() { return x; }
private,long x; long y;
} ;
C/C++程序设计
28
void main ()
{ CA a[2];
CA d[3] = { CA (1,1),CA (2,2)};
d[2]=CA(3,3);
if (d[1].v() ==(d+1)- >v ())
printf ("d[1].v()=(d+1)->v()");
}
C/C++程序设计
29