C/C++程序设计
1
六、成员函数与 this关键字七、类作用域和成员的访问八、内联成员函数
C/C++程序设计
2
六、成员函数与 this关键字
1,成员函数的定义先给出全局函数的定义 (其中 type,T1,T2,…,Tn 是已声明的类型名 )如下,
type f(T1 v1,T2 v2,…,Tn vn)
{ 根本不含 this的语句序列 ; }
全局函数中语句系列中操作两种来源的数据:一是局部数据,另一是在函数体之前定义的全局数据。
形参和全局变量一道构成函数的接口。
C/C++程序设计
3
成员函数的定义格式为:
type CType::f (T1 v1,T2 v2,…,Tn vn)
{
可隐含 this的语句序列 ; //静态成员函数则不含 this
}
CType::f是成员函数的全限定名,表示 f是隶属于 CType
类的成员函数,双冒号,:是作用域分辨符,CType::为类域分辨符。
CType::f视为一个整体,类名加在成员函数前,
的返回类型加在成员函数的全限定名之前。
C/C++程序设计
4
下面的写法是 错误的:
CType:,type f (T1 v1,T2 v2,…,Tn vn);
成员函数定义可以置于类的声明语句内,由此构成成员函数的内联定义。
在类的外部定义成员函数时,首先在全局范围声明类类型,然后在类声明之后的全局范围定义,此时类域分辨符
CType::是必须的,否则是定义一个名为 f的全局函数。
C/C++程序设计
5
成员函数语句系列中操作的数据来源为三个部分,局部数据、成员数据和全局数据。
编译器优先使用局部名称、成员名称然后使用全局名称。成员数据和形参数据在接口的立场是等量齐观的。
成员函数是类中的普遍方法,对象用于识别具体的数据状态。
非虚成员函数的声明和全局函数原型说明一样可以不提供函数定义如果程序不调用该函数,而被 virtual修饰的成员函数即虚函数必须呈交虚函数代码以便其入口地址进入虚表。
C/C++程序设计
6
2,成员函数的调用成员函数通过外部对象或外部对象指针调用,外部对象调用类的公共成员函数或可访问的成员函数,
其语法格式为,
(&b)->f (实参序列 ); 或 b.f (x,y,..,z);
在成员函数体中隐含调用的调用格式,
f (u,v,..,w); 相当于 this->f(实参序列 );
实参应该与相应的形参的类型位置一一匹配。
函数调用 b.f (x,y,..,z)根据成员函数的返回类型确定是否可参入表达式的各种运算。
C/C++程序设计
7
3,this关键字
this关键字起着横跨结构和类之间起承转合的桥梁作用,仅出现在非静态的成员函数。成员函数语句系列中的成员名有四种并不完全等价的书写格式:
a,完整的明确表示(含义基本同 2),
this->CType::f3(); this->CType::m_t2;
b,类域分辨符表示,访问特定类的成员,
CType::f3(); CType::m_t2;
e,this指针访问 (对象指针访问类的成员 ):
this->f2(); this->m_t1;
d,全缺省的表示 (含义基本同 3),
f2(); m_t1;
C/C++程序设计
8
this是指向当前对象的指针,通常隐含不露如上面全缺省的表现格式。
对象定义语句 {CType b;}为对象分配一段内存,&b代表此段内存空间的起始地址。
b.f1()表示对象从外部调用公共成员函数,this此时等于
&b。 this->f2()表示对象的隐含指针调用成员函数,理解
this->f2()的另一种思路为:
隐含的 this指针使成员函数正确地操作对象的数据。在成员函数内 *this表示当前对象本身即调用该成员函数的对象。
C/C++程序设计
9
全缺省的格式引起歧义时采用带 this的访问格式,下面的成员函数说明这一点:
void CType::SetData (char m_t1,short m_t2,int m_t3)
{ m_t1=m_t1; m_t2=m_t2; m_t3=m_t3; }
上面成员函数 SetData是编译器通过的代码,该函数的原意是设置类的成员数据状态,但实际效果是形参接口变元的自我赋值,因此可改为:
void CType::SetData(char m_t1,short m_t2,int t3)
{ this->m_t1=m_t1; this->m_t2=m_t2; m_t3=t3;
}
C/C++程序设计
10
CType类中存在一个指向该类对象的指针成员 p,
SetP函数设置该指针的初始值,全局指针、形参指针和指针成员都命名为 p,如下方式可以进行名称的唯一界定:
extern CType* p;
//说明一个外部连接属性的全局指针
void CType::SetP(CType* p)
{ if(m_t2==0) this->p=::p;
//采用全局指针初始化指针成员
else CType::p=p;
//采用形参局部指针初始化指针成员
}
C/C++程序设计
11
七、类作用域和成员的访问
1,类作用域类作用域是指 struct,class,union关键字建立的数据描述中引人的成员名或其它名称可有效索引或访问的范围。类作用域简称类域。
在全局范围 (文件作用域 )声明的类类型名具有全局范围,函数体引入的结构名具有局部作用域。可以有类的声明但不一定有类的实例。
类域通过类的实例即对象表现,对象决定非静态成员变量的存储属性。类域与文件作用域概念不同,不宜在两者间比较大小。文件作用域是类声明外和函数定义外的范围。
C/C++程序设计
12
类域一般指全局范围引入的类类型的作用域。静态的成员属于类而不是具体的对象,通过类域分辨符无须类的对象就可操作使用。
类的静态数据成员具有外部连接属性,文件作用域的变量需要加上 extern关键字通知编译器进行外部连接。
类域应从几个方面把握:
a,类的作用域通过类的对象体现,对象有全局对象、
局部对象等作用范围。
b,成员函数和类的静态数据成员为类的对象所共享,
默认具有外部连接属性。但如果成员函数由 inline关键字修饰,则变动为内部连接。
C/C++程序设计
13
c,成员名的类作用域是指成员名通过对象或成员函数或类域分辨符来访问。
d,非静态的成员函数访问类的数据成员隐含地通过对象的 this指针进行。
e,成员名的可访问性受到访问控制属性,
(public,protected 和 private)所制约。
访问类的成员一般通过圆点运算符,.”和箭头运算符
"->"进行。考虑类 CType的声明,类成员 m_t1,m_t2和 m_t3
认为是在类 CType作用域内。
C/C++程序设计
14
类域分辨符 CType::用于界定类域中的标识名称
m_t1,m_t2和 m_t3
为 CType::m_t1,CType::m_t2和 CType::m_t3
CType::m_t1为成员 m_t1的全限定名。
公共的成员,m_t1可以在类 CType声明之后的任何位置由对象 obj访问,格式为 obj.m_t1;若 obj 是 CType类的全局对象,则 obj.m_t1是全局变量。
C/C++程序设计
15
2,对象的访问控制操作关键字 public,protected,private在 C++语言中有两种用途,一种用途用于界定类中成员的访问控制属性,另一种作用是指出继承层次体系间派生类对于基类成员的继承方式。
结构的默认访问控制属性为 public,而类的默认访问控制属性为 private,具体含义为:
class A { int x; protected:int z; };
等价于 class A { private,int x; protected:int z };
struct B { int y; protected:int z; };
等价于 struct B { public,int y; protected:int z; };
C/C++程序设计
16
关键字 public,protected,private对成员访问控制属性的作用为:
a,public关键字表明公共的成员可在该类的成员函数或友员函数中访问,可出现在派生类的成员函数中。
公共的成员可以象传统的 C结构成员一样由外部对象
(如 b)或外部对象指针 (如 pb)访问,这种访问方式称为外部访问。
如下:
b.m_t1; (&b)->m_t1; pb->m_t1; (*pb).m_t1;
b.f1(); (&b)->f1(); pb->f1() ; (*pb).f1();
C/C++程序设计
17
b,protected关键字表明保护的成员可在该类的成员函数或友员函数中访问,保护的成员可出现在派生类的成员函数中,但拒绝外部访问。
例如,b.m_t2,pb->m_t2是不允许的。
c,private关键字表明私有的成员可在该类的成员函数或友员函数中访问,但私有的成员不出现在派生类的成员函数中,拒绝外部访问。
例如,b.m_t3,pb-> m_t3是不允许的。
在类的声明中关键字 public,protected,private可以重复放置,成员的访问控制属性起始于此关键字终止于下个关键字或者类的结尾。
尽量将成员函数和该成员函数要处理的数据放置在邻近的地方,这样便于自己阅读。
C/C++程序设计
18
3,外部访问与内部访问面向对象理论通过如此方式将类中的成员划分为三个部分:
一个对外部敞开为派生类共享的 public部分,这个所谓的外部不是别的归根结底恰是该类的对象本身。公共成员的性能非常卓越既可以通过对象在外部访问,又可由成员函数访问或友元函数访问,亦可以继承出现在派生类的成员函数体中。
一个是对派生类格外垂青的 protected的部分,这个部分的成员可由派生类的成员函数访问,但不允许类的对象进行外部访问,可由成员函数或友元函数访问。
C/C++程序设计
19
最后一个是层层禁锢的 private部分,这个私有部分的成员行为特别隐秘,隐秘到不为派生类访问且禁止对象本身的外部访问,而仅由成员函数访问或友元函数访问。
外部访问是外部对象或外部对象指针仅操作或访问类的公共成员或公共属性的名称。
全局对象或全局对象指针、在全局函数 (或非当前类的成员函数 )形参接口中和它们的函数体中定义的局部对象或局部对象指针,属于外部对象或外部对象指针。
C/C++程序设计
20
内部访问是访问所有的成员包括私有的成员或保护的成员以及私有或保护属性的名称 (例如类声明中的枚举常数名称 )。内部访问在成员函数或友元函数中进行。
在成员函数或友元函数形参接口中和它们的函数体中定义的局部对象或对象指针,可以访问所有的成员包括私有的或保护的成员,这是内部访问。
对象引用是已存在的对象的别名,类似地存在外部与内部对象引用之分。
为了在外部访问类中私有或保护的成员,需要引入公共成员函数。一般地成员最终通过外部对象或外部对象指针操作,因此一般至少有一个公共的成员以便对象在外部切入。
C/C++程序设计
21
[例 ] 内部访问和外部访问。通过指针 p可轻易地间接访问内部的私有成员
#include <stdio.h>
class CType
{ private,int m_n;
public,CType& Set (const CType& r,int n);
int* Initial (int n)
{ this->m_n=n;
return &m_n;
}
};
C/C++程序设计
22
CType& CType::Set (const CType& r,int n)
{ static CType a;
a.m_n=n;
if (n==1)
return a;
m_n= n+r.m_n;
return *this;
//this此时是属性为 CType*const型的指针,间接对象
//*this是 CType型左值
}
C/C++程序设计
23
CType b;
int *p=b.Initial(20);
//b.Initial(20)完成 p=&b.m_n,但 b.m_n访问私有成
//员是错误的。
void main()
{ printf ("%d\t",*p);
b=b.Set(b,2);
printf ("%d\t",*p);
printf ("%d\t",*b.Initial(30));
}
// 输出,20 22 30
C/C++程序设计
24
八、内联成员函数
C++引入内联函数的原因之一是破除精细封装的约束。
定义类的内联函数存在两种方法。
第一种方法是直接在类的内部直接提供内联的函数实现,类内部提供的函数定义是内联函数。
第二种方法是在类声明中给出内联成员函数声明,在类的外部提供内联函数的定义。
定义一般应放置在调用点之前,内联成员函数是内部连接的。内联定义和声明一般放置在头文件。
C/C++程序设计
25
同全局内联函数一样,内联成员函数是否真实展开取决于函数本身的复杂与否。
内联成员函数一般应是一些短小的代码,其语法格式是在函数定义前置关键字 inline:
inline type CType::Funct (T1 v1,T2 v2,...,T2 vn)
{ 语句序列; }
C/C++程序设计
26
[例 ] 内联成元函数破除私有封装
#include<string.h>
#include<stdio.h>
typedef struct tagPOINT { long x; long y; } POINT;
class CPoint
{ public:inline long& X() ;
inline long GetY() ;
private,long x;
long y;
};
C/C++程序设计
27
inline long& CPoint::X() { return x; }
inline long CPoint::GetY() { return y; }
void main ()
{ POINT s= {10,20};
CPoint r;
memcpy (&r,&s,sizeof (r));
printf ("%d,%d\n",r.GetY (),r.X()+=r.GetY());
}
//输出,20,30
//函数调用 r.X()构成左值,而 r.GetY()是右值表达式。
C/C++程序设计
28
私有封装效率偏低,inline关键字解除这一禁锢。现分析内联函数的工作机制,X()是百分之百的成员函数与数据成员的等价映射。系统在编译阶段将函数调用表达式:
r.X() r.GetY() printf ("%d,%d\n",r.GetY(),r.X()+=r.GetY());
分别展开为:
r.x r.y printf ("%d,%d\n",r.y,r.x+=r.y);
对 r.X()的函数调用操作就是对成员变量 r.x的操作。但封装的语法禁止外部对象 r对私有成员 x的访问即书写 r.x是非法的,通过关键字 inline使得这种表面上违反规矩的写法本质上在目标代码级别得以实现。
这既保持了面向对象的精细封装又不失内在的高效。
C/C++程序设计
29
class B;
B* g_pB;
class A
{ public:int m;
B& f(B r);
B m_b;
B* m_pB;
};
class B
{ public,int m_n;
protected,A m_a;
};
C/C++程序设计
30
前置说明用于解决多个类的交叉耦合向导性的介绍问题,英文 (forword reference)的原意是请参阅后面的详细说明。
向导性的介绍是粗线条的,前置说明通知编译器 B类是在后面声明的类,其类的细节构成在 A类的声明处尚是不充足的。
在上面的 A类的声明中声明一个返回 B类对象引用的函数 f,该函数的具体定义需放置在 B类的声明之后。
C/C++程序设计
31
成员声明语句 {B m_b;}在 A类中试图声明一个 B类型的嵌入对象,这种声明形式是错误的,因为编译器到此不具备 B类的细节知识。
这同时剔除前置说明引进嵌入对象潜在的耦合递归声明,B类恰包含一个 A类型的嵌入对象 m_a。
A类中包含一个指向 B类对象的指针 m_pB,这是合法的,因为指针是一个仅占 4字节的变量。
C/C++程序设计
32
十、函数的接口转换
1,成员函数内存分布如下是成员函数代码和对象数据集合的内存映像分布图以及其调用匹配示意,成员函数代码和对象的数据之间的呼应与全局函数和数据结构的交互没有本质的差异:
class CType
{ public,char m_t1;
protected,short m_t2;
private,int m_t3; public,CType* p;
type f (T1 x,T2 y,...,Tn z) { 语句序列; }
}; //(图见下页)
C/C++程序设计
33
数据段图 数据与代码分离的内存映像概念图
m_t1 m_t2 m_t3 p x y,..,Z 函数体中的局部变量g
CType::f成员函数代码指令序列
m_t1 m_t2 m_t3 ps x y,..,z 函数体中的局部变量代码段
CType g; //全局对象堆栈段
g.f (u,v,..,w) 调用建立的动态堆栈空间
s.f (r,t,..,q) 调用建立的动态堆栈空间动态涨落的临时堆栈
CType s; //全局对象
C/C++程序设计
34
一个重要的概念是系统对于类的成员函数只产生一段代码。
一个类可以存在许多实例,每一个实例或对象拥有各自的内存空间。
但成员函数的代码拥有的内存是唯一的,它们被不同的对象所共享。这就是对象具体数据状态与类的普遍方法在内存中的分离。
编译器对于 inline成员函数的内联展开采取复杂的指标,在满足这些指标的情况下为调用它的对象产生一段具体的代码,这段具体的代码占有额外的内存。
C/C++程序设计
35
2,编译器的内部转换函数是最重要的代码重用机制,面向对象的编程是对于全局函数入口形参一个简单的数学平移转换。 this是专供编译器内部操作的隐含的形参。表面上成员函数的定义形式:
type CType::f(T1 x,T2 y,…,Tn z)
{ 隐含 this的语句序列; }
在编译器内部转换为 (其中 C_f是与成员函数对应的全局函数名 ):
type C_f (CType*const this,T1 x,T2 y,…,Tn z)
{显含 this的语句序列; }
编译器将 b.f (x,y,..,z)或 pb-> f (x,y,..,z)处理为,
C_f (&b,x,y,…,z); 或 C_f (pb,x,y,…,z);
C/C++程序设计
36
例如,[例 2]中类内定义的成员函数
void Initial (int x,int y)
{ m=x; n=y;}
编译器添加上 this关键字为
inline void CA::Initial (int x,int y)
{ this->m=x; this->n=y;}
进一步转换为全局函数:
inline void C_Initial (CA *const this,int x,int y)
{ this->m=x; this->n=y;}
根据 cfront全面兼容 C的原则 (通过 C++源代码处理转换为 C源代码 ),成员函数在内部转换为全局函数,类转换为仅包含数据成员的 C结构,变量的引用形参转换为指针的数值形参等。这是高级的代码和数据封装在低一个层次的还原。
C/C++程序设计
37