构造函数
构造函数的作用
C++为类设计了构造函数(constructor)机制,它可以达到初始化数据成员的目的。
类的构造函数是类的一个特殊成员函数,它没有返回类型(void也不行),可以有参数,函数名和类名一样。
当创建类的一个新对象时,自动调用构造函数,完成初始化工作(需要注意构造函数是否有参数,以及参数的个数、类型)。
构造函数的作用为:
(1)分配一个对象的数据成员的存储空间;
(该功能由系统自动完成。)
(2) 执行构造函数(体),一般是初始化一个对象的部分或全体数据成员。
构造函数的定义
1. 构造函数的定义
class Date {
private:
int year, month, day;
public:
Date(int y, int m, int d);
… //其它成员
};
构造函数的参数可以是缺省的。
用户定义的类类型中,可以没有构造函数。
编译器会自动给该类类型生成一个没有参数的构造函数,该函数不作任何初始化工作。
这种构造函数称为缺省的构造函数。
注意: 一个类如果有显式定义的构造函数,编译器就不会生成缺省构造函数了。
重载构造函数
一个类可以提供多个构造函数,用于在不同场合进行类对象的初始化工作。
构造函数的重载,它们的参数表必须互不相同。
class Date { int year, month, day;
public:
Date(int d, int m, int y);
Date(int d, int m);
Date(int d);
Date();
Date(const char * dateStr); … };
析构函数
与构造函数对应的是析构函数。C++通过析构函数来处理对象的善后工作。
析构函数没有返回类型,没有参数,函数名是类名前加“~”。
析构函数的作用为:
(1) 执行析构函数(一般没有具体的工作);
(2) 释放对象的存储空间。(该功能由系统自动完成。)
注意:析构函数需要负责释放new申请的空间。
可以使用完全限定名方式显式地调用析构函数;若没有显式调用,则在一个对象的作用域结束时,系统自动调用析构函数。
系统自动调用构造函数和自动调用析构函数的顺序是相反的。
拷贝构造函数
构造函数的参数可以是任何类型参数,甚至可以将自己类对象的(常量)引用作为参数,称它为拷贝构造函数。
拷贝构造函数有两个含义:
首先,它是一个构造函数,当创建一个新对象时,系统自动调用它;
其次,它将一个已经定义过的对象(参数代表的对象)的数据成员逐一对应地拷贝给新对象。
如果一个类没有显式定义拷贝构造函数,C++编译器可以为该类产生一个缺省的拷贝构造函数。
缺省的拷贝构造函数,也将拷贝对象的各个数据成员拷贝给被拷贝对象的各个数据成员。
这样一来,两个对象的内存映像是一模一样的。
拷贝构造函数的作用是:
1.创建一个新对象,并将一个已存在的对象拷贝到这个新对象
2.对象本身做参数(将实参对象拷贝给形参对象)
3.函数返回对象(拷贝返回的对象给一个临时对象)
对象的创建、释放和初始化
对象的创建和释放
可以创建不同形式的各类对象:
1.命名的自动对象
每次进入该对象的作用域,都调用构造函数;
每次退出该对象的作用域,都调用析构函数
2.由对象(动态对象)
使用new创建对象(实际上调用构造函数),使用delete释放对象(实际上调用析构函数);
当delete释放对象后,该对象就不能再被使用。
如果构造函数有参数,也必须给出实参
C++没有自动垃圾回收机制。
程序员必须清醒地注意曾经动态创建过对象,同时在合适的地方要销毁那些对象。
否则,在程序结束后将会产生一个不引人注意的错误:内存泄露(memory leaks)。
要将类方法声明为const,可在方法声明中将所有参数括起的括号和分号之间放置关键字const,例如:void SomeFunction() const;这声明了一个名为SomeFunction()的const成员方法,它不接受任何参数,返回类型为void,由于它被声明为const,因此不会修改其所属类的任何数据成员。
尽可能将方法声明为const的。这样让编译器捕获错误,而不致于成为等到程序运行时才出现的bug
对象和指针
this指针
C++为所有非静态成员函数提供了一个称为
this的指针,因此,常常称成员函数拥有this指针。
this是一个
隐含的指针,
不能被显式声明
它只是一个
形参,一个
局部变量
在任何一个
非静态成员函数里都存在
它局部于某一对象。
this指针是一个
常指针,可以表示为(但不能显式声明):
X * const this;
可以使用
const说明符将this声明为
指向常量的常指针。
在类中,一个成员函数的原型
后跟一个const,该函数称为const成员函数,它的特点是该函数
不能修改this所指的对象的成员。
例
int GetInt() const;
this 指针主要用在运算符重载、自引用等场合
指向类的成员的指针
1. 指向类的数据成员的指针
指向类的数据成员的指针定义格式为:
类型名 类名::* 指针;
这种说明
不是说指针是属于类的
而是说明指针
只能指向指定类的指定类型的成员
指向类的成员函数的指针定义的格式为:
类型名 (类名::*指针)(参数表);
函数指针
并不属于类,
而是
只能指向类的指定原型的函数
友元关系
一个对象的私有数据,
只能通过
成员函数进行访问,这是一堵不透明的墙。
这种限制性的用法给两个类必须共享同一函数的情形带来了较大的开销。
出于
效率(而非技术上必须)考虑,C++提供了一种辅助手段,允许外面的类或函数去访问一个类的私有数据。
友元函数
友元函数
不属于类,友元函数
没有this 指针
这是友元函数与成员函数的主要区别。
友元函数的声明可以放在类的
任何段里。
class INTEGER {
private:
int num1;
friend Printer;
… };
类Printer的成员函数全都是类INTEGER的
友元函数
;
可以访问类INTEGER的
任何成员
通过友元函数,可以访问类的所有成员。
友元函数不是类的成员函数,是类外的函数,所以,它的声明可以放在类的私有段或公有段。
友元函数的调用也不需要通过对象或指向对象的指针进行,直接调用友元函数即可。
友元函数没有this指针
友元机制的重要性在于两个方面。
某个函数可以是多个类的友元,使用友元函数能提高效率,使得表达简洁、清晰。
在运算符重载的某些场合需要使用友元。
友元类提供了不同类的对象之间合作的一种方式。
类A是类B的友元类,类A的对象访问类A的成员函数,这些函数又是类B的友元函数,它们可以访问(类A和)类B的所有成员。
友元关系的总结
友元具有如下的特性:
非传递性。即A是B的友元,B是C的友元,但A不一定是C的友元(除非将A声明 为C的友元);
非对称性。即A是B的友元,但B不一定是A的友元(除非将B声明为A的友元)。
与类和对象相关的问题
可以像创建任何其他数据类型的数组一样的方式来创建一个类的对象数组。
创建对象数组,每个数组元素都是一个对象,所以需要多次调用构造函数;释放对象数组,也需要多次调用析构函数。
One_Class_Name obj_array[10];
C++语言不允许初始化对象数组,所以,要创建一个类的对象数组,该类的构造函数必须满足下列三个条件之一:
①
没有构造函数;
②有构造函数,但要有一个构造函数
不带参数;
③有构造函数,但要有一个构造函数具有的参数
全是缺省参数。
由于类是一个数据类型,也可以将对象作为参数传递给函数
参数传递遵循
传值(或传地址)的方式,这同所有其他的数据类型是相同的。
类类型做形参类型,一般有3种方式:
①对象
本身做参数(传值)
②对象
引用做参数(传地址)
③对象
指针做参数(传值)
对象
本身做参数,对形参的任何修改都
不影响用作实参的对象;
对象
引用做参数,对形参的任何修改就是对实参的对象的修改;
对象
指针做参数,对它指向的对象作任何修改就是对实参对象的修改。
一个类的对象作为另一个类的成员
一个复合类在构造的时候,
首先调用的是
内部类对象的构造函数。
如果内部类对象多于一个,则它们的构造函数的调用顺序依照它们的定义顺序。
此后,再调用外部类的构造函数。
而在复合类对象析构的时候,析构函数的调用顺序正好与构造是
相反。
考虑点和圆的关系。圆的属性包括圆心和半径,而圆心就是一个点。
class Point { int x, y;
public:
Point(int x1,int y1)
{ x=x1; y=y1 ; }
void setxy(…);
…};
class Circle { float r; Point circle_point;
public:
Circle(float r1,int a,int b) :circle_point(a,b)
{r=r1;}
void set_point(…)
{ circle_point.setxy(…);}
… };
void main()
{Circle cobj(5.6,1,2);cobj. set_point(…);…}
非局部环境
在函数体外定义的变量(如全局变量、类中的静态成员等)称为
非局部变量。
它们在main函数执行
前进行初始化(如果是对象,则在main函数执行前调用构造函数),在main函数执行
结束时才释放对应的存储空间
当函数的返回类型为类类型时,将调用
拷贝构造函数将返回的对象保存到那个
临时对象中。
另外,在类对象的运算中,也可能会产生临时对象。
临时对象也可以由显示构造函数的调用来创建。