这一篇我们开启《深入探索c++对象模型》的第3章 Data语义学,第3章开头介绍了对象的大小,这个我们在第一篇的时候已经讲过了,不过第一篇介绍的比较少,只是简单的描述了几种情况下类对象的大小,忘记的可以回去看下第一篇,我也忘记了,刚刚也回去看了下,哈哈哈。
9.1 成员变量绑定时机
我们现在写类变量,经常是在后面写类变量,类方法经常写前面比如:
// 类中再定义一个x
class A
{
public:
float X() const {return x;} // 访问的是类中的x
float GX() const {return ::x;} // 这个访问的是全局的x
private:
float x;
};
这种写法是不是习以为常,下面我们就来谈论谈论一个细思极恐的东西。
9.1.1 类中定义函数访问类变量
我们来看看例子:
#include <iostream>
using namespace std;
//定义一个全局的变量x
float x = 11;
// 可以提到这里试试
// 类中再定义一个x
class A
{
public:
float X() const {return x;} // 访问的是类中的x
float GX() const {return ::x;} // 这个访问的是全局的x
private:
float x;
};
int main(int argc, char **argv)
{
A a;
cout << "x = " << a.X() << " GX = " << a.GX() << endl;
return 0;
}
这个例子专门在全局部分添加了一个float x = 11;以这个来测试一下编译器到底是找那个x。
我们编译,执行一下看看:
[mmog@localhost 09]$ ./9_1
x = -8.02677e-35 GX = 11
A::X() 访问的哪个x,应该都知道吧,就是访问类中的x,编译器对成员函数的解析,是整个类A定义完成之后,才进行的。
A::GX()访问的就是全局的x,要想访问全局的x,需要使用::
9.1.2 类外定义函数访问类变量
我们在尝试一下,把函数体放在类外面试试:
float x = 11;
// 类中再定义一个x
class A
{
public:
float X() const;
float GX() const;
private:
float x;
};
float A::X() const {return x;} // 访问的是类中的x
float A::GX() const {return ::x;} // 这个访问的是全局的x
这样结果也是一样的。
刚刚想到一种是把函数移动到类的前面,这样就编译报错,因为类A还没加载,找不到类A。
9.1.3 内联函数访问类变量
还有一个内联函数:
float x = 11;
class A
{
public:
float X() const;
float GX() const;
inline float XX() const {return x;}
private:
float x;
};
内联函数也是在类A定义完之后,才进行解析的。
float x = 11;
class A
{
public:
float X() const;
float GX() const;
float XX() const;
private:
float x;
};
float A::X() const {return x;}
float A::GX() const {return ::x;}
inline float A::XX() const {return x;}
在外面定义也是如此。
侯捷老师说的以前的c++编译器的事,这里就不提,都是旧时代的东西了,我们现在是新时代了,哈哈哈。
9.2 使用typedef定义变量绑定时机
既然上面是c++后来的标准改变了,那我们就试试使用typedef的方式定义下变量。
9.2.1 类中定义函数访问
我们先来看看类中定义函数的情况:
int _val = 0;
//定义一个全局的变量x
typedef int length;
// 类中再定义一个x
class A
{
public:
void mumble(length val) {_val = val;} // 编译出错,_val是float类型,val是int类型,两个类型不相等
length mumble() {return val;} // 这个返回的是int 类型
private:
typedef float length;
length _val; // 这个是float 类型
};
这样编译会出错,根据就近原则,类的函数应该是样的:
void mumble(int val) {_val = val;} // _val是float 所以出错
int mumble() {return val;}
9.2.2 类外定义函数访问
类中定义有问题,那我们看看类外的,虽然我们直接告诉我们这个也会出错,但还是要分析分析。
int _val = 0;
//定义一个全局的变量x
typedef int length;
// 类中再定义一个x
class A
{
public:
void mumble(length val); // 这个是int类型
length mumble() {return val;} // 这个返回的是int 类型
private:
typedef float length;
length _val; // 这个是float 类型
};
void A::mumble(length val) // 这个也报错,没有定义这个类型,这个length是float类型
{
_val = val;
}
void mumble(length val) // 普通函数是正确的,这是找全局的length,是int类型
{
int _val = val;
}
很明显这种写法也出错了,不过报错的是另一个问题,我们来分析一下,
根据就近原则和类作用域:
// 类中函数,提取出来分析
void mumble(int val); // 这个是int类型
//类外内存,因为类已经定义了,所以这个是float
void A::mumble(float val) // 这个也报错,没有定义这个类型,这个length是float类型
{
_val = val;
}
所以这个报错是类中没有定义这个函数。
9.3.3 正确玩法
经过上面的分析,对于成员函数参数:是在编译器第一次遇到length的时候被决定,所以length第一次被遇到编译器只看到了typedef int length; 所以函数都被翻译成int。
为了类中尽早看到类类型length,所以typedef定义要被提前,这样就不会出现奇奇怪怪的问题了。
int _val = 0;
//定义一个全局的变量x
typedef int length;
// 类中再定义一个x
class A
{
typedef float length; // 提前
public:
void mumble(length val); // 这个是float类型
length mumble() {return _val;} // 这个返回的是float 类型
private:
length _val; // 这个是float 类型
};
void A::mumble(length val)
{
_val = val;
}
void mumble(length val) // 普通函数是正确的,这是找全局的length,是int类型
{
int _val = val;
}
提前能解决很多问题,下次注意,如果用typedef的话,记得提前。