14.1 C++类-成员函数、对象复制与私有成员
14.2 C++类-构造函数详解、explicit与初始化列表
14.3 C++类-inline、const、mutable、this与static
14.4 C++类-类内初始化、默认构造函数、“=default;”和“=delete;”
14.5 C++类-拷贝构造函数
14.6 C++类-重载运算符、拷贝赋值运算符与析构函数
14.7 C++类-子类、调用顺序、访问等级与函数遮蔽
14.8 C++类-父类指针、虚/纯虚函数、多态性与析构函数
14.9 C++类-友元函数、友元类与友元成员函数
14.10 C++类-RTTI、dynamic_cast、typeid、type-info与虚函数表
14.11 C++类-基类与派生类关系的详细再探讨
14.12 C++类-左值、右值、左值引用、右值引用与move
14.13 C++类-临时对象深入探讨、解析与提高性能手段
14.14 C++类-对象移动、移动构造函数与移动赋值运算符
14.15 C++类-继承的构造函数、多重继承、类型转换与虚继承
14.16 C++类-类型转换构造函数、运算符与类成员指针
文章目录
3.inline、const、mutable、this与static
3.1 在类定义中实现成员函数inline
接着上一节的Time.h和Time.cpp说,在Time.h中增加addhour成员函数。注意,整个成员函数的定义都写在其中:
public:
void addhour(int tmphour)
{
Hour += tmphour;
}
这种直接在类的定义中实现的成员函数(14.2.1节所称的“成员函数的定义”)会被当作inline内联函数来处理。回忆一下内联函数:系统将尝试用函数体内的代码直接取代函数调用代码,以提高程序运行效率。但还是老话:内联函数只是对编译器的建议,能不能inline成功,依旧取决于编译器,所以,成员函数的定义体尽量写得简单,以增加被inline成功的概率。
3.2 成员函数末尾的const
读者对许多const的用法已经不陌生。const是“常量”的概念。这里再介绍一种const的常用用法,就是在成员函数的末尾增加一个const。请注意,对于成员函数的声明和实现代码分开的情形下,不但要在成员函数的声明中增加const,也要在成员函数的实现中增加const。
那么这个成员函数末尾的const起什么作用呢?告诉系统,这个成员函数不会修改该对象里面的任何成员变量的值等,也就是说,这个成员函数不会修改类对象的任何状态。
这种在末尾缀了一个const的成员函数也称为“常量成员函数”。在Time.h中增加一个新的成员函数,定义如下:
void noone() const
{
Hour += 10; //错误,常量成员函数不可以修改成员变量值
}
从上面的代码可以看到,如果在noone成员函数中修改成员变量Hour的值,是不被允许的。看看如下范例:
{
const Time abc; //定义const对象,这种对象有限制
//abc.addhour(12); //不可以,addhour成员函数是非const的,只能被非const对象调用
abc.noone(); //可以,因为noone成员函数是const的
Time def;
def.noone(); //const成员函数,则不管const对象,还是非const对象都可以调用const成员函数(万人迷,性格好)
//而非const成员函数呢?不能被const对象调用,只能被非const对象调用。
}
普通函数(非成员函数)末尾是不能加const的。编译都无法通过,因为const在函数末尾的意思是“成员函数不会修改该对象里面任何成员变量值”,普通函数没有对象这个概念,所以自然不能把const放在普通函数末尾。
3.3 mutable
mutable,表示不稳定的、容易改变的意思。与const正好是反义词。而且mutable的引入也正是为了突破const的限制。
刚刚已经看到,在末尾有const修饰的成员函数中,是不允许修改成员变量值的。那在设计类成员变量的时候,假如确实遇到了需要在const结尾的成员函数中希望修改成员变量值的需求,怎么办呢?
也许有人会说,那就把函数末尾的const去掉,变成一个不以const结尾的成员函数。那这个时候可能面临上面曾提到过的另外一个问题——如果这个成员函数从const变成非const了,那么就不能被const对象调用了。
所以,引入了mutable修饰符(关键字)来修饰一个成员变量。一个成员变量一旦被mutable所修饰,就表示这个成员变量永远处于可变状态,即使是在以const结尾的成员函数中。
在Time类定义中增加一个mutable成员变量的定义:
mutable int myHour;
现在可以在noone成员函数中增加修改myHour成员函数的代码了:
void noone() const
{
myHour += 3; //已经正确了
}
这里先简单认识mutable,以后随着看到的程序越来越多,会对它具体有什么实用价值有更深刻的理解和体会。
3.4 返回自身对象的引用——this
在Time.h的Time类定义中增加如下代码来进行一个新成员函数rtnhour的声明:
public:
Time& rtnhour(int tmphour); //返回自身的引用
在Time.cpp中,增加成员函数rtnhour的实现:
Time& Time::rtnhour(int tmphour)
{
Hour += tmphour;
return *this; //把对象自身返回了
}
在main主函数中书写如下代码:
{
Time mytime;
mytime.rtnhour(3);//设置断点,跳入该函数查看this和*this值
}
现在要谈一谈this了。
this用在成员函数中是一个隐藏起来的函数参数,表示的是指向本对象的指针。那么,*this表示该指针指向的对象也就是本对象。换句话说,*this表示调用这个成员函数的那个对象。
如何具体理解this呢?调用成员函数时,编译器负责把调用这个成员函数的对象的地址传递给这个成员函数中一个隐藏的this形参中。例如上面mytime.rtnhour(3);这种调用,编译器在内部实际是重写了这个rtnhour成员函数的。编译器写成这样了:
Time& Time::rhnhour(Time * const this, int tmphour){...}
调用的时候编译器实际是这样调用的:
mytime.rtnhour(&mytime, 3);
注意,上面的代码传入的第一个实参是mytime对象的地址。
这也解释了为什么在rtnhour成员函数体中可以直接使用诸如Hour成员变量,就是因为在系统的角度看来,任何对类成员的直接访问都被看作通过this做隐式调用。所以在rtnhour中的代码“Hour+=tmphour;”等价于“this->Hour+=tmphour;”。
this是系统保留字,所以参数名、变量名等都不能起名叫this,系统不允许。
其实,this本身是一个指针常量,总是指向这个对象本身,不可以让this再指向其他地方。
关于this有一些说法,请注意:
(1)this指针只能在成员函数(普通成员函数)中使用,全局函数、静态函数等都不能使用this指针。
(2)在普通成员函数中,this是一个指向非const对象的指针常量。例如,类类型为Time,那么this就是Time*const类型的指针(指针常量),表示this只能指向Time对象。
(3)在const成员函数中,this指针是一个指向const对象的const指针。例如,类类型为Time,那么this就是constTime*const类型。
既然能返回对象本身,那么如果再写一个类似rtnhour这样的函数,这两个函数就能串起来调用。
在Time.h的Time类定义中增加如下代码来进行一个新成员函数rtnminute的声明:
public:
Time & rtnminute(int tmpminute);
// .cpp文件
Time& Time::rtnminute(int tmpminute)
{
Minute += tmpminute; //如果传递进来的形参也叫Minute,那么成员变量Minute可以写成this.Minute以和形参做区别,代码就变成:this.Minute += Minute
return *this; //把对象自身返回了
}
在main主函数中书写如下代码:
{
Time mytime;
mytime.rtnhour(3).rtnminute(5); //可以设置加断点调试,会发现:先调用rtnhour,再调用rtnminute
}
另外值得强调的是:this一般不写在代码中。当然,读者以后也许能见到一些使用this的代码,但一般来讲,还是很少直接使用this的。换句话说,代码中其实是在隐式使用this,并没有写到明面来使用,因为多数情况下摆到明面使用this属于画蛇添足。
//构造函数
Time::Time()
{
this->Hour = 12; //前面加this是可以的,但没必要
Minute = 59;
Second = 59;
initMillTime(59);
}
3.5 static成员
现在看一看static关键字在类中的能力。看如下代码:
{
Time mytime1; //调用的是无参构造函数
mytime1.Minute = 15;
Time mytime2;
mytime2.Minute = 30;
}
设置断点并跟踪调试可以看到,mytime1对象的Minute成员变量值是15,而mytime2对象的Minute成员变量值是30。不同对象的成员变量Minute有不同的值,彼此互不影响。为什么互不影响?因为Minute这种成员变量是属于对象的(跟着对象走),mytime1和mytime2是两个不同的对象,所以它们的Minute成员变量自然可以理解成是两个不同的变量——各自占用不同的内存地址。
那有没有这样一种成员变量,不属于某个对象的,而是属于整个类的(跟着类走)?有。这种成员变量就叫static成员变量(静态成员变量),其特点是:不属于某个对象,而是属于整个类,这种成员变量可以通过对象名来修改(也可以通过类名来修改),但一旦通过该对象名修改了这个成员变量的值,则在其他该类对象中也能够直接看到修改后的结果。
例如上面的Minute成员变量,每个对象针对该成员变量都有个副本,可以保存不同的值,而static成员变量是所有该类的对象共享同一个副本。也就是说,这种成员变量只有一个副本。同时,可以用“类名::成员变量名”的方式对这种成员变量进行引用。
当然,不仅对于成员变量,成员函数也可以在其前面增加static关键字,增加了这种关键字的成员函数同样不隶属于某个对象,而是隶属于整个类,调用的时候可以用“类名::成员函数名(……)”这种调用方式。当然,在static成员函数中,一般也只能操作和类相关的成员变量(static成员变量),不能操作和对象相关的成员变量,如Minute。
看如下代码,在Time.h中的Time类内部,声明一个静态成员变量和一个静态函数:
public:
static int mystatic; //声明但没有定义
static void mstafunc(int testvalue);
静态成员变量和普通成员变量不同,普通成员变量在定义一个类对象时,就已经被分配内存了。那静态成员变量什么时候分配内存呢?
其实,上面的“staticintmystatic;”这行代码是对静态成员变量的声明,这代表着还没有给该静态成员变量分配内存,这个静态成员变量还不能使用。为了能够使用,必须定义这个静态成员变量,也就是给静态成员变量分配内存。
那一般怎样定义这个静态成员变量呢?一般会在某一个.cpp源文件的开头来定义这个静态成员函数,这样能够保证在调用任何函数之前这个静态成员变量已经被成功初始化,从而保证这个静态成员变量能够被正常使用。
在main.cpp最上面写如下代码
int Time::mystatic = 5; //可以不给初值,那么系统默认会给0,定义时这里不需要用static了
{
cout << Time::mystatic << endl; //5
}
虽然上面的代码是用“类名::成员变量名”的方式来访问静态成员变量,但也可以使用“对象名.成员变量名”的方式来访问静态成员变量。看如下代码:
{
Time mytime1;
Time mytime2;
mytime1.mystatic = 12; //可以用对象名.引用静态成员变量
cout << Time::mystatic << endl; //12
cout << mytime1.mystatic << endl; //12
cout << mytime2.mystatic << endl; //12
}
回头再说一说静态成员函数的实现。在Time.cpp源文件中来书写,注意,静态成员函数实现时就不需要在前面加static关键字了:
void Time::mstafunc(int testvalue)
{
//Minute = testvalue; //错误,和对象相关的成员变量,不能出现在静态成员函数中
mystatic = testvalue; //这个可以
}
再来看一看如何调用。看如下代码:
{
Time::mstafunc(1288);
cout << Time::mystatic << endl; //1288
}
当然,依旧可以用“对象名.静态成员函数名”的方式来调用静态成员函数。看如下代码:
{
Time mytime1;
mytime1.mstafunc(2000);
cout << Time::mystatic << endl; //2000
}