C/C++程序设计
1
四、静态成员五、指向成员的指针
C/C++程序设计
2
四、静态成员
extern在不同的模块中沟通全局变量的外部连接,
static则充当内部屏蔽作用。
C++语言中静态成员属于类内部的合法成员但凌驾于对象之上。模块之间静态成员具有 extern的外部连接属性。
a,静态数据成员
static变量具有特别惹人垂青的性能:
1),生命与程序等长的持久性。
2),维持其原先位置的隐蔽性,禁止外部直接访问。
静态全局变量是内部连接的,但静态成员变量具有外部连接属性。
C/C++程序设计
3
[例 ] 静态成员变量的声明、定义和使用
#include <stdio.h>
static long* pa;
class CType
{ public,static int data;
CType (int n=1) { m_n=n; data++; }
static void f()
{ pa=sa; }
protected,static long sa[ ];
private,int m_n;
};
静态成员声明 static long sa[ ]; 相当于原先的外部全局变量连接说明 extern long sa[ ];
C/C++程序设计
4
静态成员变量的定义和初始化设置:
1,定义和初始化不管是公共的还是私有的一律采用相同的格式。
2,定义时不再加 static关键字但类域分辨符则是静态成员变量定义的整体部分。
3,定义是唯一的且在文件范围内进行,定义时可以不赋初始值,如同定义全局变量。
4,最好在,cpp部分同时指定初始值,特别定义私有的静态成员变量时。放在头文件的全局范围定义静态成员变量容易导致定义的重复,因此避免这样做是上策。
C/C++程序设计
5
5,不完备的声明语句如 [static long sa[];]在定义点需补充足够的信息。
6,在构造函数中对静态成员变量赋值不牵涉静态成员变量的内存分配,高度警惕对静态成员变量 data=const合法的简单赋值,这导致对象构造一次静态成员变量又回归原值。
下面是静态成员变量在文件范围定义的格式:
数据类型 类名,:静态成员变量名;
C/C++程序设计
6
[例 ]
int CType::data;
long CType::sa [ ]={20,30,40};
void main ()
{ CType::data=0;
printf ("%d,static data=%d\t",sizeof
(CType),CType::data);
CType::f ();
printf ("pa[0]=%d,pa[1]=%d,
pa[2]=%d\n",pa[0],pa[1],pa[2]);
}
//输出,4,static data=0 pa[0]=20,pa[1]=30,pa[2]=40
C/C++程序设计
7
上面例题中并无具体对象,静态成员变量明显地独立于对象而存在。可以当作全局变量一样在类外通过全限定名操作公共的静态成员如 CType::data,也可以通过该类的实例以对象名 obj.data方式进行处理。
对象名 obj.data方式的访问与对象本身的具体属性毫不相干。根据内存数据状态唯一性原则系统最终将其归约为
CType::data的形式。
私有的保护的静态成员变量由成员函数和友元访问。
静态成员变量可以用于监控一组成员函数,纪录它们被调用的次数,根据调用次数进行适当的恢复或前进操作。
静态成员变量实质上是平移到类中再加上访问控制规则制约的全局变量。
C/C++程序设计
8
b,静态成员函数静态成员函数没有隐含的 this指针,因而不操作类中的非静态成员,因而不能是虚函数。只读成员函数本质上是限定 this指针为只读指针。 静态成员函数不含当前类的 this指针因而不能是只读成员函数。
静态成员函数操作的是静态成员变量、类中的梅举常数和全局变量以及自身入口中的局部变量。
非静态的成员函数可以直接操作静态成员函数或全局函数,静态成员函数需要通过对象或对象指针间接操作非静态的成员。
C/C++程序设计
9
下面是使用静态成员函数的三个步骤,
1,在类中通过关键字 static声明类中的静态成员函数
class CType
{ protected,
static double function(int,float,...,long);
tatic 返回类型 函数名 (形参列表 );
};
2,在类的外部给出静态成员函数的定义,定义时不再加
static关键字但类域分辨符则是静态成员函数定义的整体部分。
静态成员的连接属性根据语言约定是 extern性质,但
inline定义的静态成员函数是内部连接的。
C/C++程序设计
10
返回类型 类名,,函数名 (形参列表 )
{不含 this的语句序列 ;}
double CType::function (int n,float f,...,long l)
{不含 this的语句序列 ;}
3,公共的静态成员函数可直接全限定方式调用,就象调用全局函数:
double v=CType::function (n,f,...,l);
对象名的访问方式也是可行的如,
CType a; double v= a.function (n,f,...,l);
对象或对象指针调用静态成员函数,与对象的数据状态无关。换言之 a.function (n,f,...,l)是全限定访问形式的
CType::function (n,f,...,l)的一个间接别名。
C/C++程序设计
11
[例 ] 静态成员对象实质上是全局对象,静态成员函数实质上是全局函数
# include<stdio.h>
class A
{ private,static A a; int m_n;
A(int n)
{ m_n=n; printf ("call A::A ();\t"); }
public,static A& GetStatic ();
int GetNum () {return this->m_n; }
};
inlineA& A::GetStatic ()
{return a;}
A A::a(10);
C/C++程序设计
12
void main()
{ printf ("Here is in main scope\t");
printf ("m_n=%d\t",A::GetStatic().GetNum());
A& (*pf) ()=A::GetStatic;
printf ("m_n=%d\n",pf ().GetNum ());
}
//输出:
call A::A(); Here is in main scope m_n=10 m_n=10
C/C++程序设计
13
类中的声明中自身的对象是不许作为成员的。
上面 A类中具有一个私有静态成员正好是 A类的对象,
但由于 static修饰的结果这个对象实际上是一个加上类域分辨符的全局对象,这个静态成员对象 A::a是独立于类的具体实例的,是唯一的。
静态成员函数实质上是加上类域分辨符和访问控制规则制约的全局函数。
因此可以由类型属性为 A& (*)()的常规函数指针 pf间接调用,而不是 A& (A::*)()型的成员函数指针。
C/C++程序设计
14
五、指向成员的指针指针是用于管理内存数据的变量,指向成员的指针分两种:一种是指向成员函数的指针,另一种是指向成员数据的指针。用 type,t1,t2等表示类名。
type,t1,t2等可以是 int,long,float等也可是结构名等,
用 CType表示类类型名
1,指向数据成员的指针指向成员的指针是 C++新引进的,C语言意义上的指针称为常规指针或简称指针 ;指向成员的指针简称为成员指针,
C/C++程序设计
15
静态数据成员是独立于对象的类的全局变量,因而指向静态数据成员的指针是一个常规的数据指针。
定义指向数据成员的指针的格式为:
type CType::* pm;
类名 类类型名,:* 指向数据成员的指针指向数据成员的指针 pm的类型为 type CType::*,而常规指针的类型为 type*,两者的差别在于类域分辨符
CType:,
如果 CType类的声明里存在两个 type型的成员变量,
这个指向数据成员的指针 pm可以指向其中之一。
class CType { type m_t1; type m_t2; };
C/C++程序设计
16
指向成员的指针可不在类的声明中,访问控制属性仅在该指针与其成员关联时起作用。
公共的成员可在当前类的外部如全局函数中赋给指向成员的指针。
私有的或保护的成员可在成员函数或友元函数中与指向成员的指针关联,而不在外部环境赋值。
一经关联编译器就难以限制指向成员的指针对成员访问控制属性的挑战。
对于 CType类特定的对象 obj,type*型的指针 p指向
type型成员的格式是:
p=&obj,m_t1;
在成员函数内部 p的关联格式是,
p=&this->m_t1;
C/C++程序设计
17
type*型的指针指向特定对象的数据成员,指向成员的指针指向待定对象的相对偏移。
如下语句进行了指向数据成员的指针和类的成员变量的关联:
成员指针名 =&类名,:数据成员名
pm=&CType::m_t2
//此关联式中不含对象
C++引进两个运算符访问指向成员的指针,一个运算符,.*,是对象访问成员指针运算符,另一个运算符,->*,
是对象指针访问成员指针运算符。
星号 "*"右边是成员指针。
圆点 "."左边是对象名,箭头 "->"左边是对象指针。
C/C++程序设计
18
下面是索引指向数据成员的指针的格式:
对象,*成员指针
obj.* pm
//对象 obj访问成员指针 pm
对象指针 ->*成员指针
pObj->*pm
//对象指针 pObj访问成员指针 pm
this->*成员指针
this->*pm
//this指针访问成员指针 pm
C/C++程序设计
19
对于类型为 type CType::*的指针 pm,表达式
obj.*pm(或 pObj->*pm,this->*pm)
是 type型的左值;
如果 type表示 int,则 obj.*pm是 int型的左值,如果 type
表示 double*型,则 obj.*pm是 double*型的左值。
对于对象 a,b和对象指针 p,q;
当 [pm=&CType::m_t2;]时,则 [a.*pm;b.*pm;]等价于
[a.m_t2; b.m_t2;]
当 [pm=&CType::m_t1;]时,则 [p->*pm;q->*pm;]等价于
[p->m_t1;q->m_t1;]
C/C++程序设计
20
2,指向成员函数的指针指向成员函数的指针简为成员函数指针。
成员函数指针是将常规函数指针平移到类内的产物。不失一般性以两个参数为例,常规函数指针的定义格式为:
type (*pf)(t1,t2 );
类型 (*函数指针名 )(类型 1,类型 2);
pf是具有类型属性为 type (*)(t1,t2 )的函数指针。
在常规函数指针前冠上类域分辨符 CType::就得到成员函数指针的定义格式:
类型 (类类型名,:*成员函数指针名 )(类型 1,类型 2);
type (CType::*pfm)(t1,t2 );
C/C++程序设计
21
如上语句定义一个类型属性为 type (CType::*)(t1,t2 )
的普通又奇异的指针 pfm。
成员函数指针可独立于对象而存在,但同时又必须指向成员函数,成员函数具有访问控制属性,而超乎其外的成员函数指针并不全然理会私有禁锢的制约。
在初始化成员函数指针时编译器设置一道防线维持内部访问控制的约束机制:
如果成员函数指针指向私有或保护的成员函数,初始化可在友元或成员函数中进行。
如果成员函数指针指向公共的成员函数,初始化可在全局范围或全局函数中进行。
C/C++程序设计
22
指向静态成员函数的指针是常规的函数指针。
指向静态成员和变量如 obj.m_t的常规指针和指向成员的指针初始关联时存在相同的约束,在可访问的环境获得初始值后,常规指针和指向成员的指针就可以在外部实施对成员的间接访问。
成员函数指针一经赋值,该指针则可以在外部实施对成员函数的间接调用。
因此指针是解除内部封装的有力手段。
C/C++程序设计
23
成员函数指针的初始化语法格式为:
成员函数指针名 =类名,:同类型的成员函数名 ;
pfm= &CType::f;
成员函数 f具有与成员函数指针相同的类型属性即函数原型的全限定形式为,
返回类型 类名,:函数名 (类型 1,类型 2);
type CType::f (t1,t2 );
同全局函数一样取地址运算符 &是可选的,亦可在定义成员函数指针时就赋初值,
类型 (类名,:*成员函数指针 )(类型 1,类型 2)=
类名,:同类型的成员函数名 ;
type (CType::*pfm)(t1,t2 )= &CType::f;
C/C++程序设计
24
成员函数通过对象名或对象指针来调用,成员函数指针的使用格式为,
(对象名,*成员函数指针 )(实参 1,实参 2)
(obj.*pfm)(v1,v2)
(对象指针 ->*成员函数指针 )(实参 1,实参 2)
(pobj->*pfm)(v1,v2)
(this->*成员函数指针 )(实参 1,实参 2)
(this->*pfm)(v1,v2)
当 [pfm= &CType::f;]时,间接调用 (obj.*pfm)(v1,v2)
相当于直接调用 obj.f(v1,v2)。实参 v1,v2应与类型 t1,t2匹配。
返回类型确定函数调用的使用方式。
规定成员函数指针不指向构造函数和析构函数,即
pfm=&CType::CType是非法的。
C/C++程序设计
25
由于成员函数指针的类型书写起来过于冗长,因此指定一个成员函数指针的别名就是自然的事情,从定义成员函数指针的格式前在冠一个 typedef关键字就得到类型的别名:
type (CType::*pfm)(t1,t2);
typedef type (CType::*PFM)(t1,t2);
PFM是类型 type (CType::*)(t1,t2)的一个别名。
由此可以定义成员函数指针或数组:
PFM pfm,pfmn[5];
C/C++程序设计
26
[例 ] 指向数据成员的指针,静态成员函数的地址属性同等于全局函数的地址属性
#include <stdio.h>
class CType
{ public:static void Setpm (int CType::* &,int k)
void Set (int CType::*& pm,int k)
{ Setpm (pm,k); this->*pm+=k; }
CType (int x=10,int y=20)
{ m_t1=x; m_t2=y; }
void Show()
{ printf ("m_t1=%d,m_t2=%d\t",m_t1,m_t2); }
public,int m_t1;
private,int m_t2;
};
C/C++程序设计
27
void CType::Setpm ( int CType::*& pm,int k)
{ if (k==1) pm=&CType::m_t1;
else pm=&CType::m_t2;
}
void (*Setpm)( int CType::*&,int)= CType::Setpm;
void main ()
{ int CType::* pm;
CType::Setpm(pm,1); CType a;
a.Show(); a.*pm+=10;
Setpm(pm,2);
a.*pm+=20; a.Set (pm,1); a.Show ();
} // 输出,m_t1=10,m_t2=20 m_t1=21,m_t2=40
C/C++程序设计
28
[例 ] 指向成员函数的指针
#include <stdio.h>
class CType;
typedef int (CType::*PFM)(int);
class CType
{ public,static void Setpfm (PFM & pfm,int);
CType (int x=0) { pfm(x); }
void Show() { printf ("m_x=%d\t",m_x); }
int pfm (int x) { m_x=x; return 2; }
private,int Add (int x){m_x+=x; return 1; }
int m_x;
};
C/C++程序设计
29
void CType::Setpfm (PFM & pfm,int num)
{ switch (num)
{ case 1,pfm=&CType::Add; break
default,pfm=&CType::pfm; break;
}
}
int (CType::*gpfm) (int)=&CType::pfm;
C/C++程序设计
30
void main ()
{ CType* pthis=new CType ();
(pthis->*gpfm)(10); pthis->Show ();
PFM pfm;
CType::Setpfm (pfm,1);
(pthis->*pfm)(20);
pthis->Show ();
CType::Setpfm (pfm,2);
(pthis->*pfm) (3);
pthis->Show ();
pthis->pfm (4);
pthis->Show ();
}
C/C++程序设计
31
//输出,m_x=10 m_x=30 m_x=3,m_x=4
说明:
上面的程序定义一个名为 pfm的指向成员函数的局部指针,在类中存在一个名为 pfm的成员函数。对于成员函数指针 pfm的访问格式为:
(pthis->*pfm)(3)
pfm右边紧贴右圆括号左圆括号对,)(”是必不可少的。
(pthis->*pfm)(3)是对于指向成员函数指针的间接调用,pthis->pfm (4)是直接调用成员函数 pfm。
C/C++程序设计
32