200 likes | 377 Views
第8章 静态成员、友员与成员指针. 8.1 静态成员. 一个类的属性或方法可以采用 static 进行修饰,这样的成员称为静态成员。. 8.1.1 静态属性. 1. 静态属性是只在对象之间公用的“全局变量” 函数 共用一份信息,采用全局变量来实现。 类的对象 共用一份信息,采取的方法是将其定义为类的静态属性。. 2. 静态属性的定义与初始化 static int no; // 类型任意 与普通属性不同的是,静态属性必须在 类定义之外 进行定义性声明,包括初始化,如: int PointX:: no = 0; // 仍使用“ 类名 :: ”作为限定.
E N D
8.1 静态成员 • 一个类的属性或方法可以采用static进行修饰,这样的成员称为静态成员。
8.1.1 静态属性 • 1. 静态属性是只在对象之间公用的“全局变量” • 函数共用一份信息,采用全局变量来实现。 • 类的对象共用一份信息,采取的方法是将其定义为类的静态属性。
2. 静态属性的定义与初始化 • static int no; //类型任意 • 与普通属性不同的是,静态属性必须在类定义之外进行定义性声明,包括初始化,如: • int PointX::no = 0; //仍使用“类名::”作为限定 声明不会将no的私有属性变成公开 静态属性声明也是对该属性初始化的唯一场所和时机,在没有提供初始值时,系统将其清0。 类定义外的静态属性声明不能再加static关键字。 在.h和.cpp文件中,静态属性的定义性声明语句应置于.cpp文件中,所有的类方法实现之外。 静态属性是属于类而不是对象的。(sizeof(PointX)的值可验证) 静态属性在程序运行开始时初始化
3. 静态属性的表示方法 • 类名::静态成员名 • 如PointX::no class PointX { int x, y; public: static int no; PointX(int x1, int y1):x(x1), y(y1){ no++; } }; int PointX::no = 0; int main( ) { PointX p1(1,1), p2(2,3), p3(3,1); cout << PointX::no; }
8.1.2 静态方法 class PointX { int x, y; static int no; //私有静态成员 public: PointX(int x1, int y1):x(x1), y(y1){ no++; } static int getNo(){ return no; } //静态方法 static void increaseNo() { no++; }//静态方法 }; int PointX::no = 0; int main( ) { PointX p1(1,1), p2(2,3), p3(3,1); cout << PointX::getNo(); PointX::increaseNo(); cout << PointX::getNo(); } ⑴静态方法中不能出现任何非静态成员。 这是因为非静态成员要依赖对象才能存在和使用。 但非静态方法中可以使用静态属性。 ⑵静态方法没有缺省的this指针 因为仅借助类名调用静态方法时显然不会有对象地址传给this。 这是静态方法与普通方法的最重要的内在区别。
8.2 友员 • 类是经过封装的“黑盒”,普通函数不能访问类的私有成员。 • 不妨将一个对象看成是一本私人的日记,通常是不允许外人去翻看的,但朋友就可能享有一定的特权,看到日记的内容。如果希望一个普通函数能够直接访问类的成员,也可以将其定义为类的“朋友”。因为这个普通函数并不是类的成员,故称为类的友员。
1. 使用普通函数作为类的友员 • 如果要将某个普通函数作为类的友员,必须在类定义中进行声明,形式为: class X { ... public: friend type 友员函数名(形式参数表); };
#include <iostream> using namespace std; class A { int m; //类的私有成员 public: friend void setm(A& to, int m1); }; void setm(A& to, int m1) //普通函数,不属于类 { to.m = m1; //类的私有成员对友员也是公开的 } int main() { A to; setm(to, 10); }
2. 使用友员时应注意的问题 • 将一个普通函数定义为类的友员的目的是使其能直接访问类的非公开成员,因此,友员一般要有类对象、类对象引用或类指针为参数(通常是引用),以使友员能借助它们引用类的成员。 • 那么,究竟是什么使我们甘冒破坏类的封装性的危险而将普通函数定义成类的友员呢?最主要的原因是效率。 • 使用友员还有一些原因,如可以使运算符重载更加灵活等,但友员并不是一种必需的技术。
3. 向前引用类定义 • 如果一个类使用在前而定义在后,需要先进行类声明,语法形式如下: • class 类名; //类声明语句 • 这种声明称为类定义的向前引用,也称为超前声明。
class B; class A { ... public: friend void set(A& oa, B& ob); }; class B { ... public: friend void set(A& oa, B& ob); };
4. 类方法作为友员和友类 • 一个类的方法可以定义为另一个类的友员。 • 例如,要将类B的方法int fn(int, A&)定义成类A的友员,应在类A的定义中加入如下声明: • friend int B::fn(int, A&); • 这使B的fn方法可以直接访问类A的所有成员。此时需要对类A做超前声明。 • 一个类定义为另一个类的友员,称为友类。 • 例如,若要将类B定义为类A的友类,必须在类A的定义中加入如下声明: • friend class B; //在A的定义中声明B是A的友类 • 这相当于将类B的所有方法都定义成类A的友员。
8.3 指向类成员的指针 • 这里所说的指向成员的指针并不是指普通的指针。例如: class XYZ { public: int x, y, z; }; void fn() { XYZ a; int* p = &a.x; } • 这里的a.x是一个普通的整型变量,p也是一个普通指针,不能称为指向类成员的指针。
8.3.1 利用普通指针访问静态成员 • 由于类的静态成员是类属的,不与任何对象发生联系。因此,借助普通指针就可以实现静态成员的访问。 class X { public: static int x; X(int x1){ x = x1; } static int getx(){ return x; }; }; int X::x = 10; int main() { int* p = &X::x; //X::x是普通变量 (*p)++; //相当于X::x++; cout << *p << '=' << X::x; //显示11=11 int (*fp)() = X::getx; //X::getx是普通函数指针 cout << fp() << '=' << X::getx(); //显示11=11 }
8.3.2 指向非静态数据成员的指针 • 使用一种特殊的指向数据成员的指针访问非静态属性: • type 类名::*指针变量名; • 这样的指针应该与对象(或引用)和指针联系在一起,并使用“.*”或“->*”运算符访问一个成员,形式为: • 对象名.*指向对象成员的指针 • 对象指针名->*指向对象成员的指针 double ABC::*px; //指向double类型数据成员的指针变量 px = &ABC::a; //指向类的成员a ABC x; //类对象 x.*px = 1; //相当于x.a = 1; px = &ABC::b; //指向类的成员b x.*px = 2; //相当于x.b = 2; px = &ABC::c; //指向类的成员c x.*px = 3; //相当于x.c = 3; cout << x.*px; //相当于cout<<x.c;
如果使用对象指针来引用对象成员时,应将.*换成->*,如:如果使用对象指针来引用对象成员时,应将.*换成->*,如: ABC* p = &x; px = &ABC::c; cout << p->*px; //相当于cout<<p->c 应该说,使用指向数据成员指针的场合并不多见。因为如果一个成员是公开的,使用普通指针就可以实现对它的引用,如: double* px; //指向double类型数据成员的指针变量 ABC x; //类对象 px = &x.a; //指向类的成员a *px = 1; //相当于x.a = 1;
8.3.3 指向非静态函数成员的指针 • 一个指向普通函数的指针变量只与函数的原型有关。 例如,对于如下函数: double fn(double, int); 与指针fn相吻合的指针变量应按如下形式定义: double (*fp)(double, int); fp = fn; //将指针fn赋给变量fp 如果fn是一个类A的方法,则指针变量中必须反映出这种所属关系: class A { public: double fn(double x, int y){ cout<<x<<’,’<<y; } }; double (A::*fpA)(double, int); //指向方法的指针变量
利用指向方法的指针变量fpA访问一个对象或对象指针的方法与访问其属性的过程一致:利用指向方法的指针变量fpA访问一个对象或对象指针的方法与访问其属性的过程一致: ⑴将指向方法的指针变量指向一个方法,做预定位; ⑵如果是对象,采用“(对象.*fpA)”作为方法名,如果是对象指针,采用“(对象指针->*fpA)”作为方法名,进而调用相应的方法。例如: double (A::*fpA)(double, int); //定义指针变量 A a; //定义A类的对象a fpA = A::fn; //指向方法fn的预定位 (a.*fpA)(2,3); //与a.fn(2,3)相同,显示2,3 (&a->*fpA)(5,6) //与a.fn(5,6)相同,显示5,6