对象(变量)的作用域、可见性与生存期 类的静态成员
类的友元
常引用、常对象和常成员
对象(变量)的作⽤用域、可⻅见性和⽣生存期
• •
•
对象(变量)的作用域:指对象的有效范围。
-一般情况下,对象在其作用域内是可见的和存在的,对象的作用域、可见性和生存期三者是一致的。
对象(变量)的可见性:指不同作用域的同名对象在其作用域范围内的某一处是否可被引用。
对象(变量)的生存期:指对象从诞生(占据内存)到结束(不占内存)的这段时间。在生存期内,对 象或变量将保持它的值不变,直到它们被更新为 止。
-对象(变量)的生存期分为动态生存期和静态生存期两 种。
• •
• •
对象(变量)的作⽤用域
局部对象(变量):(函数内)块作用域
-从声明处开始到块结束的花括号为止
形参对象(变量):函数作用域
-在整个函数内有效
成员对象(变量):类作用域
-在整个类内有效,即在所有成员函数内有效
全局对象(变量):文件作用域
-在整个文件内有效
• •
• •
对象(变量)的可⻅见性一般情况下对象(变量)在其作用域范围内的任一处
是可见的,即可被引用。 在有不同作用域的多个同名对象(变量)的情况下,外层对象(变量)在内层不可见,被隐藏起来。
不同作用域的同名对象的 包含关系如图所示。
被隐藏的成员对象(变量)可加“类名::”来显式地引用,被隐藏的全局对象(变量)可加“::”来显式地引用。
文件作用域
类作用域
函数作用域
块作用域
作用域关系 图
对象(变量)的⽣生存期
对象(变量)的生存期:动态生存期、静态生存 期。
动态生存期:对象(变量)在程序运行期间随时诞 生和消失,如局部变量、形参变量、成员变量。
静态生存期:对象(变量)在程序运行期间一直存 在,如全局变量。
C语言中静态局部变量:作用域与生存期不一 致。
C++在类的成员函数中定义一个静态局部变量: 该类的所有对象在调用这个成员函数时将共享这 个变量。
C++类的成员为静态成员:静态成员为该类所有 对象所共享,它不属于某个对象的,见下节。
程序实例
静态成员(static)在C语言中,当把函数的一个局部变量说明为
静态的,则该变量在函数调用结束后其值仍然存 在,但其它函数不能引用。若下次再调用该函 数,则该变量的值不再重新初始化为0,函数可以 使用上次调用所保留的值。
在C++中,对于某个类所定义的每一个对象, 都有其属于自身的数据成员与成员函数,不同对 象之间的成员是互不相干的,这类似于C中函数 内的局部变量,不同时间函数调用的局部变量是 互不相干的。但它们都同名。
因此,在C++中,当把类的某个成员用关 键字static说明为静态成员时,就是把该成员定 义为在该类范围内的全局成员,即无论这个类 建立了多少个对象,所有对象都共用这个成 员。因此,静态成员的主要用途是定义类的各个 对象所共用的数据成员或成员函数,其中尤其 是数据成员。
下面对静态数据成员和静态成员函数分别 进行讨论。
静态数据成员
在一个类中,若将一个数据成员说明为static,则该数据成员称为静态数据成员,无论建 立多少个该类的对象,都只有一个共同的数据成 员。static数据成员在编译时按全局变量方式被分 配存储单元并初始化,默认的初始值为零。
欲声明一个成员为静态的,只要在其前方加 上关键字static即可,如在Point类定义一个静态数 据成员countP:
staticintcountP;
静态数据成员的使用与普通数据成员的使用 并无差异,但必须注意两点:
a.由于静态数据成员在该类范围内是一个全局 变量,因此必须在类定义外的全局范围中给它赋 初值,然后才能使用。如下所示:
intPoint::countP=0;
给静态数据成员赋初值时必须同时指名数据 类型与所属的类,用类名加作用域运算符来限 定。这里的数据类型必须与在类中声明的数据类 型一致。
由于静态数据成员并不特别属于某一个对象, 上述这样的赋初值语句只能在整个程序中出现一 次。因此,程序员最好不要把静态成员的初始化定 义在头文件中,因为头文件可能在程序中多次被引 入使用,否则将发生错误。
b.当静态数据成员声明于类的public区时,由 于它并不特别属于某一个对象,在类范围内是全局 的,因此可以在程序的任何地方直接引用该静态数 据成员,但使用时必须加上类范围的限制,如:
cout<<"对象个数为:"<<Point::countP;
由于静态数据成员是所有同类对象所共用的
数据,所以
A.getC();B.getC();
这两个输出语句的结果是相同的,即: 对象个数为:2
对象个数为:2
从程序的最后两句可以看出,类的public数据 成员有两种使用方法:
对象名.数据成员 或类名::数据成员
第一种方式主要用于非静态数据成员,第 二种主要用于静态数据成员。
静态成员函数
成员函数也可以被声明为静态的。当一个成 员函数被声明为static时,表明该静态成员函数只 属于一个类,而不属于该类的任何对象。因此, 在访问静态成员函数时,最好用类名加作用域运 算符来调用该静态成员函数。
实际上,当某成员函数只使用静态数据成员 时,应该把该成员函数定义为静态成员函数。这是 因为,一个仅使用到静态数据成员的成员函数通过 某类的对象来调用是没有任何意义的,且容易造成 阅读上的混淆,阅读者无法从程序中直接看出该成 员函数引用了静态数据成员。
因此
A.getC();B.getC();
改为:
Point::getC();Point::getC();
更为妥当。
另一方面,静态成员函数不属于某个对象的,因 此,在静态成员函数访问非静态成员时,必须要指 明所要访问的对象。例如,若在getC中要访问非静 态成员X,则getC必须修改为:
staticvoidgetC(Point&p)
{cout<<"对象个数为:"<<countP<<endl;
cout<<“X:”<<X<<endl;//错误
cout<<“X:"<<p.X<<endl; }
则main函数中的调用语句改为:
Point::getC(A); Point::getC(B);
友元(friend)前面已讲过,一个对象的私有数据只能通过成员
函数进行访问。这种数据封装的方法虽有许多优点,但在某些情况下也带来许多不便,如某个函数需要 使用多个类的多个相关数据,或类Y的所有成员函数 要访问类X的私有数据。出于效率而非技术的考虑, C++提供了友元(friend)这样一种辅助手段,允许外 面的类或函数去访问一个类的私有数据。
友元分为两种情况:将一个普通函数声明为某 类的友元和将一个类声明为另一个类的友元。
友元函数
要声明一个普通函数为某类的友元,只要在该 类定义中任何一处(不管是private、protected或是public区)提供对该函数的声明,并在其最前方加 上关键字friend即可。下面是计算两点距离的实 例:
classPoint {intX,Y;
friendfloatfdist(Point&p1,Point&p2);//友元函数public:
Point(intxx=0,intyy=0){X=xx;Y=yy;} };
与成员函数一样,友元函数可以在类Point的 内部定义,也可以在类Point的外部定义。当在类 外定义时,友元函数不需要加“类::”。如上述fDist函数在类Point外定义:
doublefDist(Point&p1,Point&p2) {
doublex=double(p1.X-p2.X); doubley=double(p1.Y-p2.Y); returnsqrt(x*x+y*y);
}
请注意友元函数与成员函数在定义和使用上 的区别。成员函数与友元函数的主要区别是成员 函数是属于某个类的,而友元函数不属于某个 类。
在友元函数中,必须指定所要访问的对象, 这在三个地方体现出来:
1在参数中有2个Point类的引用;
(a)在实现代码中通过引用对象p1和“.”来使用数据
成员;
(b)使用友元函数(即调用该友元函数)时必须有该
类对象作为实参。
友元类
我们可以将类A声明为类B的友元,这样类A的所有成员函数就可以访问类B的私有数据。例 如:
classA;//一个类声明语句classB
{
friendA;//声明A为B的友元类inti;
voidm_func();
};
classA
{public:
voidf_f1(B&); voidf_f2(B&);
...... };
对于友元类的成员函数的使用和定义方式, 由于它兼具有类和友元函数的双重特点,所以 它的使用和定义方式颇具特色,具体如下:
a.由于它是属于某个类(A),所以在类外定义 时必须加作用域运算符来限定它属于某一类(A)的,在使用时必须加对象名和成员运算符来指定 哪一个对象调用它;
b.由于它是另一个类(B)的友元,所以在其参 数中必须用该类(B)的对象和引用作为形参。
c.在实现代码上,必须用该类(B)的对象名加 成员运算符来使用该类(B)的私有数据。
友元说明只有在确实必要时才应使用,即在 没有它时必须建立一个复杂的类等级的时候才使 用。从性质上来说,友元是一种数据共享,它破 坏了类的封装性,因此要尽量少用。
在使用友元时还要注意:(1)友元不具有可传 递性,如B是A的一个友元,A是C的一个友元, 并不能得出B是C的友元的结论。(2)友元关系是 单向的,如果B类是A类的友元,B类的成员函数 可以访问A类的私有数据,但A类的成员函数不能 访问B类的私有数据。(3)友元关系不能被继承, 如果B类是A类的友元,B类的派生类不能自动成 为A类的友元。
常对象和常成员(const)虽然数据隐藏保证了数据的安全性,但各种
形式的数据共享却又不同程度地破坏了数据的安 全。因此,对于既需要共享、又需要防止改变的 数据应该声明为常量。因为常量在程序运行期间 是不可改变的,所以可以有效地保护数据。
本节介绍用const定义的常对象、常成员和常 引用。
常对象
常对象是指常类型对象,即对象常量,定义 的语法形式为:
类名const对象名(初值);或
const类名对象名(初值);
定义常对象同样要赋初值,并且该对象不得再更 新。另外,常对象只能调用它的常成员函数,而 不能调用其他成员函数。参见下面的常成员函 数。
常成员1.常成员函
数
类型说明符函数名(参数表)const;注意:
(1)const是函数类型的一个组成部分,因此在实 现部分也要带const关键字,并且可用于对重载函数 的区分。
使用const关键字说明的函数为常成员函数,常 成员函数据说明格式如下:
(2)常成员函数不更新对象的数据成员,也不能调 用该类中没有用const修饰的成员函数。
(3)常对象只能调用它的常成员函数,而不能调用 其他成员函数。
2.常数据成
员
用const说明的数据成员称为常数据成员。常数 据成员的值在任何函数中都不能被更改,必须编 写构造函数并采用初始化列表的方式进行初始 化。
常引用
函数的参数可以被说明为常引用(用const修 饰),则在该函数内不能修改该形参的值。
•
•
• •
小结
一般情况下,对象在其作用域内是可见的和存在 的,对象的作用域、可见性和生存期三者是一致 的,但在同名和加了static后会出现不一致的情 况。
静态成员是类中所有对象共有的,不属于某一对 象的。
友元函数和友元类可以访问类的私有成员,破坏 了封装性,应慎用。
常对象、常成员和常引用一般用于保护对象或变 量的值不被改变。