6.4 对象的生存期
1全局对象、静态对象与局部对象
对象的生存期是指对象从被创建开始到被释放为止的时间。对象按生存期可分为3类:
(1)局部对象:当程序执行到局部对象的定义之处时,调用构造函数创建该对象;当程序退出定义该对象所在的函数体或程序块时,调用析构函数释放该对象。
(2)静态对象:当程序第一次执行到静态对象的定义之处时,调用构造函数创建该对象;当程序结束时调用析构函数释放该对象。
(3)全局对象:当程序开始执行时,调用构造函数创建该对象;当程序结束时调用析构函数释放该对象。
2自由存储对象
动态内存分配技术可以保证在程序运行过程中按照实际需要申请适量的内存,使用结束后进行释放。这种在程序运行过程中根据需要可以随时建立或删除的对象称为自由存储对象。建立和删除工作分别由堆运算符new和delete完成。
6.5 this 指针
C+ +提供了一个特殊的对象指针——this指针,它是成员函数所属对象的指针,它指向类对象的地址。成员函数通过这个指针可以知道自己属于哪一个对象。
this指针是一个隐含的指针,它隐含于每个类的非静态成员函数中,它明确地表示出了成员函数当前操作的数据所属的对象。当对一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后调用成员函数,每次成员函数存取数据成员时,则隐含使用this指针。
6.6 静态成员
对于类中的非静态数据成员,每一个类对象都拥有一个拷贝(副本),即每个对象的同名数据成员可以分别存储不同的数值,这是保证每个对象拥有区别于其他对象的特征的需要。而类中的静态成员则是解决同一个类的不同对象之间的数据和函数共享问题的。静态成员的特性是不管这个类创建了多少个对象,它的静态成员都只有一个拷贝(副本),这个副本被所有属于这个类的对象共享。这种共享与全局变量或全局函数相比,既没有破坏数据隐藏的原则,又保证了安全性。
静态成员表示整个类范围的信息,其声明以static关键字开始,包括静态数据成员和静态成员函数。
1静态数据成员
静态数据成员声明时要使用关键字static。
静态数据成员在每个类对象中并不占有存储空间,它只是在每个类中分配有存储空间,供所有对象公用。静态数据成员的值对每个对象都是一样的,但它的值可以被任何一个对象更新,从而实现了同一类的不同对象之间的数据共享。
静态数据成员具有静态生存期,必须对它进行初始化。静态数据成员初始化的一般格式如下:
<数据类型><类名>::<静态数据成员名>=<初始值>;
在对静态数据成员初始化时应注意:
(1)由于在类的声明中仅仅是对静态数据成员进行了引用性声明,因此必须在文件作用域的某个地方对静态数据成员进行定义并初始化,即应在类体外对静态数据成员进行初始化(静态数据成员的初始化与它的访问控制权限无关)。
(2)静态数据成员初始化时前面不加static关键字,以免与一般静态变量或对象混淆。
(3)由于静态数据成员是类的成员,因此在初始化时必须使用作用域运算符(::)限定它所属的类。
2静态成员函数
公有的静态数据成员可以直接访问,但私有的或保护的静态数据成员却必须通过公有的接口进行访问,一般将这个公有的接口定义为静态成员函数。
使用static关键字声明的成员函数就是静态成员函数,静态成员函数也属于整个类而不属于类中的某个对象,它是该类的所有对象共享的成员函数。
静态成员函数可以在类体内定义,也可以在类外定义。当在类外定义时,要注意不能使用static关键字作为前缀。
由于静态成员函数在类中只有一个拷贝(副本),因此它访问对象的成员时要受到一些限制:静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,再通过对象来访问。
6.7 常成员
虽然数据隐藏保证了数据的安全性,但各种形式的数据共享却又不同程度地破坏了数据的安全性。因此,对于既需要共享又需要防止改变的数据应该定义为常量进行保护,以保证它在整个程序运行期间是不可改变的。这些常量需要使用const修饰符进行定义。const关键字不仅可以修饰类对象本身,也可以修饰类对象的成员函数和数据成员,分别称为常对象、常成员函数和常数据成员。
1常对象
使用const关键字修饰的对象称为常对象,它的定义格式如下:
<类名>const<对象名>
或
const<类名><对象名>
常对象在定义时必须进行初始化,而且不能被更新。
2常成员函数
使用const关键字说明的成员函数称为常成员函数,常成员函数的说明格式如下:
<返回类型><成员函数名>(<参数表>)const;
3常数据成员
使用const说明的数据成员称为常数据成员。常数据成员的定义与一般常量的定义方式相同,只是它的定义必须出现在类体中。
常数据成员同样也必须进行初始化,并且不能被更新。但常数据成员的初始化只能通过构造函数的成员初始化列表进行。
常数据成员的初始化只能在成员初始化列表中进行,但对于大多数数据成员而言,既可以使用成员初始化列表的方式,也可以使用赋值,即在构造函数体中使用赋值语句将表达式的值赋值给数据成员。这两种方式中,成员初始化列表方式使初始化情况更加明显,并且可能带来效率上的优势。
6.8 友元
类具有数据封装和隐藏的特性,只有类的成员函数才能访问类的私有成员,外部函数只能访问类的公有成员。但在某些情况下,需要在类的外部访问类的私有成员。这时,如果通过公有成员函数进行访问,由于参数传递、类型检查和安全性检查等需要时间上的开销,将影响程序的运行效率。为了解决整个问题,引入了友元。友元可以在类外部直接访问类的私有成员,提高了程序的运行效率。
友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。对于一个类,可以利用friend关键字将一般函数、其他类的成员函数或者是其他类声明为该类的友元,使得这个类中本来隐藏的信息(包括私有成员和保护成员)可以被友元所访问。如果友元是一般成员函数或是类的成员函数,称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都成为友元函数。
1友元函数
友元函数不是当前类的成员函数,而是独立于当前类的外部函数(包括普通函数和其他类的成员函数),但它可以访问该类的所有对象的成员,包括私有成员、保护成员和公有成员。
友元函数要在类定义时声明,声明时要在其函数名前加上关键字friend。该声明可以放在公有部分,也可以放在私有部分。友元函数的定主既可以在类内部进行,也可以在类外部进行。
2友元类
友元除了可以是函数外,还可以是类,即一个类可以作为另一个类的友元,称为友元类。友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
友元类可以在另一个类的公有部分或私有部分进行说明,说明方法如下:
friend<类名>;//友元类类名
6.9 对象数组
对象数组是指数组元素为对象的数组,该数组中的每一个元素都是同一个类的对象。
对象数组的定义格式如下:
<类名><数组名>[<大小>]……
使用对象数组成员的一般格式是:
<数组名>[<下标>].<成员名>
6.10 成员对象
类的数据成员可以是简单类型或自定义类型,也可以是类类型的对象。因此,可以利用已定义的类来构成新的类,使得一些复杂的类可以由一些简单类组合而成。类的聚集,描述的就是一个类内嵌其他类的对象作为成员的情况。
当一个类的成员是另外一个类的对象时,该对象就称为成员对象。当类中出现了成员对象时,该类的构造函数要包含对成员对象的初始化,通常采用成员初始化列表的方法来初始化成员对象。定义的一般格式如下:
<类名>::<类名>(<总形参表>):<成员对象1>(<形参表1>),<成员对象2>(<形参表2<),…
{
//类成员的初始化
建立一个类的对象时,要调用它的构造函数。如果这个类有成员对象,要首先执行所有的成员对象的构造函数,当全部成员对象的初始化都完成之后,再执行当前类的构造函数体。析构函数的执行顺序与构造函数的执行顺序相反。
当类中有多个成员对象时,要按照定义成员对象的顺序建立各个子对象,即成员对象构造函数的执行顺序仅与成员对象在类中声明的顺序有关,而与成员初始化列表中给出的成员对象的顺序无关。
如果在构造函数的成员初始化列表中没有给出对成员对象的初始化,则表示使用成员对象的缺省构造函数。如果成员对象所在的类没有缺省构造函数,将产生错误。如果所有的成员对象都是调用缺省构造函数建立的,那么该类的构造函数的成员初始化列表可以省略。