实例成员指针是指向实例成员的指针,可分为实例数据成员指针和实例函数成员指针。
实例成员指针必须直接或间接同.*或->*左边的实例(对象)结合,以便访问该对象的实例数据成员或函数成员。
构造函数不能被显式调用,故不能有指向构造函数的实例成员指针。
实例成员指针是成员相对于对象首地址的偏移,不是真正的代表地址的指针。
实例成员指针不能移动:
#include
using namespace std;
struct A {
int i; //公有的成员i
private:
long j;
public:
int f() {
cout << "f()";
return 1;
}
private:
void g() {
cout << "g() ";
}
} a;
void main(void) {
int A::* pi = &A::i; //普通数据成员指针pi指向public成员A::i
int (A:: * pf)() = &A::f; //普通函数成员指针pf指向函数成员A::f
int x = a.*pi; //等价于 x = a.*(&A::i) = a.A::i = a.i
x = (a.*pf)(); //.*的优先级低,故用(a.*pf)
pi++; //错误, pi不能移动,否则指向私有成员j
pf += 1; //错误, pf不能移动
long y = (long)pi; //错误,pi不能转换为长整型
x = x + sizeof(int) //对
pi = (int A::*)x; //错误,x不能转换为成员指针
}
const 只读,volatile 易变,mutable 机动。
const 和 volatile 可以定义变量、类的数据成员、函数成员及普通函数的参数和返回类型。
mutable 只能用来定义类的实例数据成员。
含const实例数据成员的类必须定义构造函数(如果const实例数据成员没有设定缺省值),且数据成员必须在构造函数参数表之后,函数体之前初始化。
含 volatile、mutable 数据成员的类则不一定需要定义构造函数。
#include
#include
class TUTOR {
char name[20];
const char sex; //性别为只读成员
int salary;
public:
TUTOR(const char* name, const TUTOR* t);
TUTOR(const char* name, char gender, int salary);
const char* getname() { return name; }
char* setname(const char* name);
};
TUTOR::TUTOR(const char* n, const TUTOR* t) : sex(t->sex) {
strcpy(name, n); salary = t->salary;
} //只读成员sex必须在构造函数体之前初始化
TUTOR::TUTOR(const char* n, char g, int s) : sex(g), sarlary(s) {
strcpy(name, n);
} //非只读成员sarlary可在函数体前初始化,也可在体内再次赋值
char* TUTOR::setname(const char* n) {
return strcpy(name, n); //strcpy的返回值为name
}
void main(void) {
TUTOR wang("wang", 'F', 2000);
TUTOR yang("yang", &wang);
*wang.getname() = ‘W’; //错误:不能改wang.getname()指的字符
*yang.setname("Zang") = 'Y';
}
普通函数成员参数表后出现const或volatile,修饰this指向的对象。
出现const表示this指向的对象(其非静态数据成员)不能被函数修改,但可以修改this指向对象的非只读类型的静态数据成员。
构造或析构函数的this不能被说明为 const 或 volatile 的(即要构造或析构的对象应该能被修改,且状态要稳定不易变)。
对隐含参数的修饰还会会影响函数成员的重载:
普通对象应调用参数表后不带 const 和 volatile 的函数成员;
const 和 volatile 对象应分别调用参数表后出现const和volatile的函数成员,否则编译程序会对函数调用发出警告。
#include
class A {
int a;
const int b; //b只能在构造函数初始化
public:
int f() {
a++; //this类型为 A * const this, 指向的
return a; //对象可修改(故其普通成员a可修改),只读成员b不可改
}
int f() const { //a++; //this类型为const A * const this,指向的对象
return a; //不可改,其普通成员a不可改。同上, b不可改
}
int f() volatile { //this类型为volatile A * const this,指向的对象可修改,
a++; //其普通成员a可修改。只读成员b不可改
return a;
}
int f() const volatile { //this类型为const volatile A* const this,
//a++; //不能修改普通成员a。同上,只读成员b不可改
return a;
}
A(int x) :b(x) { a = x; }
} x(3); //等价于A x(3), x可修改,
const A y(6); // y、z不可改
const volatile A z(8); // x、y、z由开工函数构造、收工函数析构
void main(void) {
x.f(); //普通对象x调用int f( ): 指向的对象可修改
y.f(); //只读对象y调用int f( ) const:指向的对象不可修改
z.f(); //只读易变对象z调用int f( ) const volatile
}
函数成员参数表后出现volatile,常表示调用该函数成员的对象是挥发对象,这通常意味着存在并发执行的进程。
C++编译程序几乎都支持编写并发进程,编译时不对挥发对象作任何访问优化,即不利用寄存器存放中间计算结果,而是直接访问对象内存以便获得对象的最新值。
关于mutable
函数成员参数表后出现 const 时,不能修改调用对象的非静态数据成员,但如果该数据成员的存储类为 mutable,则该数据成员就可以被修改。
- mutable 用于说明实例数据成员,mutable 不能与 const、static 连用,但可以与 volatile 连用。
- mutable 仅用于说明实例数据成员为机动成员,不能用于静态数据成员的。
所谓机动是指在整个对象为只读状态时,其每个成员理论上都是不可写的,但若某个成员是mutable成员,则该成员在此状态是可写的。
例如,产品对象的信息在查询时应处于只读状态,但是其成员“查询次数”应在此状态可写,故可以定义为“机动”成员。
保留字mutable还可用于定义Lambda表达式的参数列表是否允许在Lambda的表达式内修改捕获的外部的参数列表的值。
如果A类型的有址引用变量 r 引用了new生成的(一定有址的)对象x,则应使用 delete& r 析构 x,同时释放其所占内存。r.~A()仅析构 x 而不释放其所占内存(由new分配),造成内存泄漏。应该用 delete& r;
引用变量必须在定义的同时初始化,引用参数则在调用函数时初始化。有址传统左值引用变量和参数必须用同类型的左值表达式初始化。
class PRODUCT {
char* name; //产品名称
int price; //产品价格
int quantity; //产品数量
mutable int count; //产品查询次数
public:
PRODUCT(const char* n, int m, int p);
int buy(int money);
void get(int& p, int& q)const;
~PRODUCT(void);
};
void PRODUCT::get(int& p, int& q) const { //const PRODUCT *const this
p = price; q = quantity; //当前对象为const对象,故其成员不能被修改
count++; //但count为mutable成员,可以修改
}
静态数据成员是使用static说明或定义的类的数据成员。
静态数据成员通常在类的里面说明,在类的外面唯一定义一次。
静态数据成员一般用来描述类的总体信息,例如对象总个数。
实例数据成员可以定义默认值,但非const静态数据成员不能定义默认值。
静态数据成员在类中初始化只能定义为 inline static、const static、const inline 类型(保留字顺序可变)。
静态数据成员不管是否用inline、const说明,在所有代码文件只有一个副本。
函数中的局部类不能定义静态数据成员,容易造成生命期矛盾。
静态数据成员不能定义为位段成员。
int x = 3;
union S { //定义局部类T
const static int b = 0; //全局类中可用const定义同时初始化静态成员, 必须用常量
inline static int c = x; //全局类可用inline定义同时初始化静态成员, 可用任意表达式
inline const static int d = x; //可用任意表达式
};
void f(void) {
class T { //定义函数中的局部类T
int d; //static int d; //错误:函数中的局部类不能定义静态数据成员
};
T a; //局部自动变量 a
static T s; //局部静态变量 s
}
void main() { f(); f(); } //第一个函数调用f()返回后 a.d ⇔ s.d ⇔ T::d 产生生成矛盾
静态函数成员通常在类里以 static 说明或定义,它没有this参数。
有this的构造和析构函数、虚函数及纯虚函数都不能定义为静态函数成员。
静态函数成员一般用来访问类的总体信息,例如对象总个数。
静态函数成员可以重载、内联、定义默认值参数。
静态函数成员同实例成员的继承、访问规则没有没有太大区别。
静态函数成员的参数表后不能出现 const、volatile、const volatile 等修饰符。
静态函数成员的返回类型可以同时使用 inline、const、volatile 等修饰。
class A {
double i;
const static int j=3;
public:
static A &inc(A &); //说明静态函数成员
static A &dec(A &a) { //在内体内定义静态函数成员:默认使用 inline
a.i = a.i - A::j;
return a;
}
};
A &A::inc(A &a) { //不能 static A &A::inc(A &a), why ?
a.i += A::j; //静态函数可访问静态数据成员
return a;
}
#include
using namespace std;
class CROWD {
int age;
char name[20];
public:
static int num;
static int getn( ) { return num; }
CROWD(char *n, int a) {
strcpy(name, n);
age = a; num++;
}
~CROWD( ) { num – –; }
};
int CROWD::num = 0;
void main(void) {
int *d = &CROWD::num; //普通指针指向静态数据成员
int (*f)( ) = &CROWD::getn; //普通函数指针指向静态函数成员
cout << "\nCrowd num=" << *d;
//类CROWD无对象时访问静态成员
CROWD zan("zan", 20);
//d = &zan.num; 等价于如下
//d = &CROWD::num;
cout << "\nCrowd num=" << *d;
CROWD tan("tan", 21);
cout << "\nCrowd num=" << (*f)( );
}
静态成员指针与普通成员指针有很大区别。
struct A {
int a, *b;
int A::*u; int A::*A::*x;
int A::**y; int *A::*z;
static int c, A::*d;
} z;
int A::c = 0;
int A::*A::d = &A::a;
void main(void) {
int i, A::**m;
z.a = 5; z.u = &A::a; i = z.*z.u;
z.x = &A::u; i = z.*(z.*z.x);
m = &A::d;
m = &z.u; i = z.**m;
z.y = &z.u; i = z.**z.y;
z.b = &z.a;
z.z = &A::b; i = *(z.*z.z);
}