Posts Tagged C/C++

NULL和nullptr

0.NULL的前世今生

对于C和C++程序员来说,一定不会对NULL感到陌生。但是C和C++中的NULL却不等价(别惊讶,这是真的)。

NULL表示指针不指向任何对象,但是问题在于,NULL不是关键字,而只是一个宏定义(macro)。

在C中,习惯将NULL定义为void*指针值0:

#define NULL (void*)0

但同时,也允许将NULL定义为整常数0

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.55) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

在C++中,NULL却被明确定义为整常数0:

#define NULL 0

A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.

导致不同定义的根本原因是:隐式类型转换(implicit conversion)。

C的定义使得存在void*到0的隐式类型转换,而宏观表现则是,void*指针可以直接赋值给其他的指针类型

int* ptr = 0;
int* pptr = (void*)0; // both are legal in c

但是C++却不支持void*到其他类型的隐式转换,所以上面的代码在C++中并不能全部通过编译。

1.C++的解释

为什么C++在NULL上选择不完全兼容C?原因和C++的重载函数有关。

C++通过搜索匹配参数的机制,试图找到最佳匹配(best-match)的函数,而如果继续支持void*的隐式类型转换,则会带来语义二义性(syntax ambiguous)的问题。

考虑下面两个重载函数:

void foo(int i);
void foo(char* p)

foo(NULL); // which is called?

编译器同时找到了两个最佳匹配,以至于连编译器都不知道选择哪个。

为了消除混乱,C++才会将NULL定义为整常数0。

但是,这么做又带来了另一个问题:NULL和整型发生了重叠。

2.C++的选择

为了彻底解决NULL带来的问题,C++0x标准引入了一个新的关键字:nullptr。

nullptr是一个很神奇的关键字,他被定义为std::nullptr_t类型,这是一个泛指针类型(这个术语是我自己加的-.-),支持到其他指针类型的自动转换。并且不支持到除了bool类型以外的整型的转换。

由于nullptr具有类型,且支持到任何兼容的指针类型的转换,所以对于上述重载函数的调用,编译器会很明确的选择第二个函数。

不过,引入新关键字并不代表解决了一切问题。其代价也很明显:向后兼容性/习惯性和编译器的支持程度。

C++0x中保留了原来的NULL,所以旧的代码也可以通过编译。并且为了和C保持最大程度的兼容,在一段时间内,NULL都应该是支持的。

至于程序员对于nullptr的接受程度,那只能另当别论。

对于编译器,Visual Studio 2010已经开始支持C++0x中的大部分特性,自然包括nullptr。而VS2010之前的版本,都不支持此关键字。

Codeblocks10.5附带的G++ 4.4.1不支持nullptr,升级为4.6.1后可支持nullptr(需开启-std=c++0x编译选项)

其余编译器对于nullptr的支持就不是太清楚了。有需要的可自行翻阅Wiki。

考虑到目前的编译环境,用nullptr全面代替NULL不太现实,不过可以在文件头加入说明或使用预编译。

, , , ,

4 Comments

C++中的私有继承

和大多数OO语言不同C++允许私有继承(private inheritance)。

对于公有继承而言,子类和父类间的关系是is-a。即,每一个子对象都是父对象。

因而父类中的所有实现和接口对于子类来说,可见性都是维持不变的。

但是私有继承会改变父类中接口和实现在子类中的可见性(均变为private),故私有继承不是is-a的模型。

事实上,私有继承对应has-ais-implemented-in-terms-of的继承模型。

而has-a或is-implemented-in-terms-of模型大部分情况下,可以使用包含所需类的对象为成员的策略解决,这让私有继承的存在显得有点多余。

不过私有继承在绝大多数情况下,有两个主要的用途。

(1)在避免产生不符合逻辑的is-a关系的前提下改写某个类的虚函数

比如有一个在桌面显示小东西的类Widget,需要定时处理一些事件,因此它需要改写类Timer的虚函数。但是因为Timer和Widget没有逻辑联系,所以不能使用公有继承。

此时,私有继承则可以很优雅的解决问题

class Timer
{
	public:
		explicit Timer(int tickFrequency);

		// automatically called for each tick
		virtual void onTick() const;
};

class Widget: private Timer
{
	private:
	  virtual void onTick() const;

};

当然,如果执意不用私有继承也可以解决。所用策略一般是,使用嵌套一个继承自(公有继承)自目标类的方式:

class Widget
{
	private:
		class WidgetTimer: public Timer
		{
		   	public:
			    virtual void onTick() const;

		};

	WidgetTimer timer;
};

虽然能达到同样的效果,但是和使用私有继承相比,显得笨拙不少。

故私有继承常用于管理钩子或者回调函数。


使用私有继承的另外一种情况是:当你需要继承一个什么数据都没有的类。例如,只用于存放typedef定义和函数声明的类(这种类在STL等库中很常见)。

之所以采用私有继承,和C++的处理机制有关:C++规定对象大小必须至少为一

所以C++会让那些“空类”对象在内存中占有一定的空间,即

class Empty{}; // sizeof(Empty) > 0

对于大多数编译器,他们会在你毫不知情的情况下偷偷插入一个char数据类型。

因此,如果采用组合(composition)的实现方式,会引起不必要的内存开销,这可能还要算上内存对齐产生的间隙。而私有继承则后,子类不会再是空数据,编译器不会再偷插入数据,可以避免这一浪费。

这种优化成为Empty Base Optimization(EBO)。且EBO只能在Single Inheritance下起作用。

一个很有意思的事实是,私有继承常和另一个受众人嗤之以鼻的继承模型–多重继承一块儿使用。

总而言之,仅在证明使用私有继承比所有可选方法更有效时,才采取这一做法。

, ,

6 Comments

C++中的接口继承和实现继承

很多人认为,C++中是不存在接口继承的,只有Java、C#这类语言才提供了相应的语法支持。

但是,如同鲁迅说过的某句名言:世上本没有接口继承,用的人多了,才有了接口继承。C++中依然可以实现接口继承,只是形式上稍有不同罢了。

C++中的继承基于一个事实:父类定义的成员函数会一直被子类继承(包括被子类隐藏的部分)。

而父类中提供的函数可以有三种:1)普通成员函数 2)普通虚函数 3)纯虚函数。这三种函数类型代表了三种继承设计模式。

一个简单的实例代码如下:

class Shape
{
	public:
		virtual void Draw() = 0;
		virtual int GetError();
		int GetId();
};

class Rectangular : public Shape
{
	//...
};

class Circle : public Shape
{
	//...
};

普通成员函数由父类声明且实现,子类应继承接口以及强制性的实现

这几乎是最常见的一种函数类型,代表了典型的”is-a”继承设计模式。

ps:所谓的”is-a”设计模式,指的是”everything that applies to base classes must also apply to derived classes

示例中,函数GetId严格遵守”is-a”模式。因为每个子类本质都是一个Shape对象,都有一个唯一的ID

普通虚函数可以在父类中有默认的实现,而这个默认实现可以由子类继承。
Read the rest of this entry »

, , ,

2 Comments

堆与优先级队列

优先级队列(Priority Queue)是一种广泛使用的数据结构。其内部元素按照设定的关键字大小(亦可称为优先级别)出队。

使用原始的队列来实现优先级队列,虽然入队效率可以达到Θ(1),但是出队需要对整个队列进行遍历,复杂度需要Θ(N)。

因而优先级队列一般不采用原始队列实现,转而使用堆结构(heap),尤其是最小堆实现。

关于堆数据结构,请参考http://blog.kingsamchen.com/archives/547以及http://en.wikipedia.org/wiki/Heap_%28data_structure%29

基本操作

元素的插入-Insert

和堆排序不同,优先级队列是一个容器,元素需要依次插入。

插入算法比较简单:先将元素插入到下一个可用空间。此时在堆结构中,对应为一个叶子结点。由于元素的插入可能破坏堆特性,即:插入的元素key小于其父结点。此时从结点处向上调整,过程称为PercolateUp,上滤。

PercolateUp操作停止的断言为:{到达根结点 || 已符合堆结构}

另外,如果堆内部数组从下标1开始存放数据,除了使得PARENT/LEFTCHILD/RIGHTCHILD的公式更简洁之外,还可以往首元素填入一个哨兵元素(元素值永远最小)用以简化PercolateUp操作,避免对是否到达根结点进行判断。

Insert操作的复杂度是O(logN),这是最坏情况,但是实际应用中情形往往稍好。

元素获取-ExtractMin

首先,最小堆性质保证了根结点元素具有最大的优先级,所以我们只需要返回根结点的一个copy,并且删除根结点。

但是直接删除根结点会破坏整个堆,而且删起来也蛋疼(想想,的确够蛋疼的)。所以通常会用最后一个叶子的元素去取代根结点,以保持整个堆看起来还存在。

但是这种取代十有八九会破坏堆性质(除非叶子结点优先级和原先根结点相同),所以需要从根结点出向下调整,过程成为PercolateDown,下滤。

PercolateDown基本上都会将元素重新调整到最底层,所以需要临界条件的判断。

另外,PercolateDown和PercolateUp稍有不同:每一次向下一层递进,都一定要确保上浮的元素是几个元素中最小的。而这又会引发另外一个问题,父结点可能有一个孩子,可能有两个孩子。

所以Percolate实现相对较麻烦,而且也难以用哨兵。

ExtractMin的复杂度是O(logN),而且由于下滤的特殊性,一般都会达到这个上界

扩展操作

元素查找-Find

对一种数据结构来说,查找要求是经常会发生的。比如在进程表中查找某个进程。

但是堆不像BinarySearch,并没有表现出太强的顺序性,所以查找通常之能遍历整个堆。复杂度为Θ(N)
Read the rest of this entry »

, , , ,

No Comments

C++中用qsort排序字符串

下午写代码时需要对一个由字符串指针构成的数组进行排序。

为了节约时间和代码量,提高重用性(嗯,你可以看作是我偷懒),决定用crt库函数提供的qsort。

为此,我写了如下的一个比较函数供qsort调用

int cmp(const void* key1, const void* key2)
{
	return wcscmp(reinterpret_cast<wchar_t*> key1,
					reinterpret_cast<wchar_t*>key2);
}

但是调试的结果让我大吃一惊,数组并没有被排序。

排除了其他代码出现问题的可能性之后,我把焦点放在了自己写的cmp函数上。

qsort为了通用性,需要调用一个客户端指定的函数用以比较大小。因为某些类型,主要是结构或者C++中的类(当然,qsort作为C的库函数,不会考虑类)不支持内建的比较。

而为了提高效率,元素的比较是通过指针完成。相应的,这个函数的参数是指向待排序数组元素的指针

字符串数组中的元素是字符指针wchar_t*,而指向这个元素的指针就是wchar_t**。

所以我们需要做的,是将key1和key2都从const void*显式的转换为wchar_t**,因为此时传给函数的参数类型实际上就是这个。

在C中,函数可以如下改写

int cmp(const void* key1, const void* key2)
{
	return wcscmp(*(wchar_t**)key1, *(wchar_t**)key2);
}

如果希望使用C++类型转换风格(C++的类型转换较之要安全但是繁琐),可以如下完成

int cmp(const void* key1, const void* key2)
{
	return wcscmp(*reinterpret_cast<wchar_t**>(const_cast<void*>(key1)),
					*reinterpret_cast<wchar_t**>(const_cast<void*>(key2)));
}

关键的一步是首先要去掉参数的const属性,然后才能使用reinterpret_cast转换为wchar_t**。

直接的参数转换是没法通过编译的

, , ,

1 Comment

CRegistry V2

从7月6日开始重构CRegistry,一直到昨天通过测试正式结束,小小的感慨下~~

期间碰到实习大作业展示和考试,以及Dave造访、回家等琐事,deadline被一拖再拖。还好,现在总算结束了。

CRegistry第一版来自个人写ARV(AutorunLoadViewer)时的封装。由于水平和时间的原因(V1是在高二时写的…),当时设计的CRegistry不是很合理,存在一些比较大的缺点。

于是便想到重构CRegistry,使其变得更具通用性和易用性。

经过重构的CRegistry以下优点:

完美支持不同编译器的Win32项目(最初只支持VC的MFC)

更强大的操作支持

重新设计的函数接口,更加符合面向对象

新版CRegistry的头文件如下:

/**************************************************

** Project:CRegistry Design

** File:Registry.h

** Edition:v2.0.0 Demo

** Coder:KingsamChen [Code Think]

** Last Modify:2011-7-15

**************************************************/

#if _MSC_VER > 1000
#pragma once
#endif

#ifndef _CREGISTRY_38037F33_2602_44f7_8C0F_EBB82B1408F3
#define _CREGISTRY_38037F33_2602_44f7_8C0F_EBB82B1408F3

#include <Windows.h>
#include <cstdio>

class CRegistry
{
	public:
		CRegistry();
		CRegistry(HKEY key);
		CRegistry(const CRegistry& reg);
		~CRegistry();

	public:
		CRegistry& operator=(const CRegistry& reg);
		inline void Key(HKEY key);
		inline HKEY Key() const;
		inline DWORD GetErrorCode() const;

	public:
		BOOL Open(HKEY keyParent, LPCTSTR pszKeyName, REGSAM samDesired = KEY_READ | KEY_WRITE);
		inline void Close();
		BOOL Create(HKEY keyParent, LPCTSTR pszKeyName,
					DWORD keyOptions = REG_OPTION_NON_VOLATILE,
					REGSAM samDesired = KEY_READ | KEY_WRITE,
					LPSECURITY_ATTRIBUTES pSecurityAttributes = NULL);
		BOOL IsKeyExist(HKEY keyParent, LPCTSTR pszKeyName);
		BOOL DeleteKey(LPCTSTR pszSubKey);
		BOOL DeleteTree(LPCTSTR pszSubKey);
		BOOL DeleteValue(LPCTSTR pszValueName);
		BOOL EnumKey(DWORD index, LPTSTR szKeyName, LPDWORD pNameLength);
		BOOL EnumValue(DWORD index, LPTSTR szValueName, LPDWORD pNameLength,
						LPDWORD pDataType = NULL,
						LPBYTE pData = NULL, LPDWORD pDataSize = NULL);
		BOOL QueryBinaryValue(LPCTSTR pszValueName, LPVOID pData, LPDWORD pDataSize);
		BOOL QueryDwordValue(LPCTSTR pszValueName, DWORD& data);
		BOOL QueryStringValue(LPCTSTR pszValueName, LPTSTR szData, LPDWORD pLength);
		BOOL SetBinaryValue(LPCTSTR pszValueName, LPCVOID pData, DWORD dataSize);
		BOOL SetDwordValue(LPCTSTR pszValueName, DWORD data);
		BOOL SetStringValue(LPCTSTR pszValueName, LPCTSTR pszData, DWORD type = REG_SZ);

	public:
		enum{OPERATION_ERROR = -1, NAME_MAX = 255};		

	private:
		HKEY m_hKey;
		DWORD m_errorCode;
};

inline void CRegistry::Key(HKEY key)
{
	if (key != NULL)
	{
		m_hKey = key;
	}
}

inline HKEY CRegistry::Key() const
{
	return m_hKey;
}

/*
	Description:
		Closes a handle to the specified registry key
	Parameters:
		none
	Return Value:
		none
*/
inline void CRegistry::Close()
{
	if (m_hKey != NULL)
	{
		::RegCloseKey(m_hKey);
		m_hKey = NULL;
		m_errorCode = 0L;
	}
}

/*
	Description:
		Returns last-error code value. The code value is maintained on a
		per-object basis. Note that last-error code saved in object will be
		updated after any operations that are revelant to registry.
	Parameters:
		none
	Return Value:
		The return value is last-error code.
*/
inline DWORD CRegistry::GetErrorCode() const
{
	return m_errorCode;
}

#endif

需要注意的是,考虑到实际的操作情况,对于注册表Value的Query/Set暂时只有最常用的三种数据类型。如果你需要对其他数据类型进行操作,请自行添加。

本项目遵从Apache License,请合理使用源代码。

Download CRegistry V2 SRC

, , , , ,

2 Comments

逆波兰式与表达式求解

一、历史

逆波兰式表示法(Reverse Polish Notation, RPN)可以看作波兰表示法(PN)的一种逆向表示。后者最初由波兰数学家Jan Lukasiewicz在1920年提出,由此得名。

在波兰表示法中,每个操作符均在相应的操作数之前,这也是牛逼的Lisp采用的表达式风格。与此相对的,逆波兰式中,每个操作符都在其操作数之后,由此也得名“后缀表示法(Postfix)”

ps:还记得C/C++中的 op += opr么?

例如表达式(3-4)*5的逆波兰式为:3 4 – 5 *

而表达式3-4*5的逆波兰式为:3 4 5 * -

由此可以发现,逆波兰式中无需用括号来指定优先级,这是RPN一个非常有用的特点。

基于此特点,逆波兰式被提出用于解决表达式求值的问题。

ps:关于逆波兰式的更详细信息,请参看http://en.wikipedia.org/wiki/Reverse_Polish_notation

二、表达式预处理

应用RPN求解表达式的难点在于,如何将一个标准的表达式转换成RPN,即Infix to Postfix

对于词法分析器来说,生成RPN可以通过构造一个词法树,然后对其后序遍历。

但是对于简单的应用来说,使用堆栈就可以解决这个问题。

首先我们需要一个堆栈用于保存操作符,假设为s,那么相应算法描述如下:

1.碰到操作数,直接输出
2.碰到操作符op
如果op是( 则直接入栈s
如果op是) 那么s弹栈并输出,一直到弹出(
如果op是+-*/,那么
如果s为空 或者 top[s] == ( 或者 op的优先级高于top[s] 则 op入栈
否则 s弹栈并输出,接着重复上一步
如果表达式分析完毕后栈s不为空,将剩下的操作符全部输出

如此,Infix已经被转换成了Postfix

这里需要注意的是,为了能正确的应用此算法,一般将()的优先级设置为最高

Read the rest of this entry »

, , ,

1 Comment

C++非局部静态对象分离编译问题

在讨论整个问题前,首先要说明下什么是“非局部静态对象(non-local static object)”.

所谓非局部静态对象,指的是具有和程序相同生命周期的变量(并非单纯的静态变量)。包括全局变量、namespace中的变量以及class中和代码文件中声明为static的成员变量。

当几个非局部静态对象分布在不同的源文件中,且彼此之间存在联系(某个对象需要引用另外一个对象),那么在编译上会产生一个问题:无法确定哪个变量先进行初始化。

也就是说,C++编译器几乎是以一种随机的方式对这几个非局部静态对象进行初始化。

原因和实现的复杂度以及效率有关。

那么考虑如下的代码:

代码来自Effective C++ 3rd edition,下同

// in a file
class FileSystem
{                    

    public:

      ...

      std::size_t numDisks() const;       

      ...

};

extern FileSystem tfs;               

// in another file

class Directory
{                       

    public:

       Directory( params );

      ...

};

Directory::Directory( params )

{

  ...

  std::size_t disks = tfs.numDisks();  

  ...

}

Directory tempDir( params );

如果要tempDir对象要能够正确的初始化,那么必须保证全局的tfs对象已经被初始化完成。
Read the rest of this entry »

, , ,

6 Comments

大整数类:CBigInt v1.0.0

个人写的第二个大整数类,和郁磊童鞋合作的第一个Proj,也是CodeThink小组的第一个Proj。

本来想拉上晨晨的,但是考虑到晨晨要准备下半年的日语一级,就没有打扰他。于是两个人分配完成了整个proj。

就如上面说的,这是我第二个大整数类。第一个类由于设计和架构上的错误,早早的宣布失败,被迫烂尾。

而在写这个Proj时,吸取了前面的经验,首先确保设计和架构上的可行性以及实现的简易程度。

在整个Proj的进展中,郁磊童鞋的学习效率超出我的想象。虽然之前没有太多编码的经验,但是他在整个proj开发中的表现完全超出新手的水准,这也使得proj能够基本按照计划进行。

扯了这么多,现在简单介绍下CBigInt的功能和用法。

features:

*接受wchar_t*表示的大整数,并且兼容内建的int类型
*支持基本的+ – * / += -= *= /=的运算
*支持前置自增/自减和后置自增自减
*支持基本的比较运算符,诸如== != > < >= <=
*支持格式化输出。包括返回一个wstring类型字符串和接受一个wchar_t[]缓冲区

features:

*允许动态调整内部数据区大小SetCapcity
*比较字符串 Compare
*返回内部元素个数 Length
*自定义错误输出 ErrorOutput

说明:由于CBigInt默认是为VC编译器设计,所以用了部分vc-only的库函数,如果需要在GCC下编译,请修改以下:

*将_ASSERT替换为assert或其他GCC支持的断言宏
*将memcpy_s修改为memcpy或者其他GCC支持的内存操作函数

具体的头文件如下:

/***************************************

** Project:BigInt Class Design

** File:bigint.h

** Edition:v1.0.0 Demo

** Coder:Kingsam Chen、Lei Yu [CodeThink]

** Last Modify:2011-6-2

****************************************/

#if _MSC_VER > 1000
#pragma once
#endif

#ifndef _CBIGINT_89208EB6_3829_40ec_9569_7258E249101D
#define _CBIGINT_89208EB6_3829_40ec_9569_7258E249101D

#include <iostream>
#include <string>

using std::wstring;
using std::wcout;
typedef unsigned char byte;

class CBigInt
{
	public:
		CBigInt();
		CBigInt(int num);
		CBigInt(const wchar_t* pszNum);
		CBigInt(const CBigInt& num);
		virtual ~CBigInt();

	public:
		CBigInt& operator =(int num);
		CBigInt& operator =(const CBigInt& num);
		CBigInt& operator =(const wchar_t* pszNum);
		friend CBigInt operator +(const CBigInt& lhs, const CBigInt& rhs);
		friend CBigInt operator -(const CBigInt& lhs, const CBigInt& rhs);
		friend CBigInt operator *(const CBigInt& lhs, const CBigInt& rhs);
		friend CBigInt operator /(const CBigInt& lhs, const CBigInt& rhs);
		CBigInt& operator +=(const CBigInt& rhs);
		CBigInt& operator +=(int rhs);
		CBigInt& operator -=(const CBigInt& rhs);
		CBigInt& operator -=(int rhs);
		CBigInt& operator *=(const CBigInt& rhs);
		CBigInt& operator /=(const CBigInt& rhs);
		CBigInt& operator ++();
		CBigInt operator ++(int);
		CBigInt& operator --();
		CBigInt operator --(int);
		friend bool operator ==(const CBigInt& lhs, const CBigInt& rhs);
		friend bool operator !=(const CBigInt& lhs, const CBigInt& rhs);
		friend bool operator >(const CBigInt& lhs, const CBigInt& rhs);
		friend bool operator >=(const CBigInt& lhs, const CBigInt& rhs);
		friend bool operator <(const CBigInt& lhs, const CBigInt& rhs);
		friend bool operator <=(const CBigInt& lhs, const CBigInt& rhs);

	public:
		int Compare(const CBigInt& rhs) const;
		wstring ToString() const;
		void ToString(wchar_t* szBuffer, int bufferSize) const;
		inline const size_t Length() const;
		bool SetCapcity(int newSize = DATALEN);

	protected:
		void Clear();
		void FormatIntoInternalData(int num);
		void FormatIntoInternalData(const wchar_t* pszNum);
		void CopyInternalData(const CBigInt& num);
		wstring DealWithNum(const wchar_t* p);
		int UnsignedCmp(const CBigInt& rhs) const;
		CBigInt& UnsignedlInc(const CBigInt& rhs);
		CBigInt& UnsignedDec(const CBigInt& rhs);
		inline void AdjustSignal();

	protected:
		inline void ErrorOutput(wchar_t* pszErrorDescription);

	protected:
		enum{DATALEN = 20, DATALEN_GROWING_RATE = 150};  // rate is in percentage
		byte* m_pData;
		int m_dataLen;
		int m_eleCount;	 // m_eleCount is off-by-one to index
		bool m_sign;
};

// no null-terminated included
inline const size_t CBigInt::Length() const
{
	// negative sign will take up one
	_ASSERT(m_eleCount >= 0);
	return m_sign ? m_eleCount + 1 : m_eleCount;
}

/*
	Description:
		Sets the sign to positiveness if the data is equal to 0
	Parameters:
		none
	Return Value:
		none
*/
inline void CBigInt::AdjustSignal()
{
	if (1 == m_eleCount && 0 == m_pData[0])
	{
		m_sign = false;
	}
}

// error output interface
inline void CBigInt::ErrorOutput(wchar_t* pszErrorDescription)
{
	wcout<<pszErrorDescription;
}

#endif

CBigInt完全开放源代码,但是如果要修改或在其他项目中引用,请保留原作者信息。

Download SRC

, , , ,

1 Comment

Member functions with the const constraint in C++

一、const成员函数的限制

在设计类时,有时为了保证某个成员函数不会修改成员变量,将其标志为const。

	public:
		void set() const;

比如这里的set函数不能修改成员变量的值,否则会产生一个编译错误。

但是有一种情况除外:目标成员变量是static

即,const成员函数可以不受限制的修改static成员变量的值

	public:
		void set() const
		{
			n = 1024;
		}

	int test::n;

	private:
		static int n;

这样的代码是合法的。

这种做法看上去很好,因为他解决了const成员函数有可能需要修改极个别变量的问题。但是在更多的时候,把成员变量标记为static不是一种值得推荐的行为。

为了使某些成员变量能够被const成员函数修改且避免把自己标记为static,C++提供了mutable关键字。

mutable修饰的变量表示可变变量,该变量允许const成员函数修改它的值。

	public:
		void set() const
		{
			n = 23;
		}

	private:
		mutable int n;

二、成员函数的const重载规则

一个经常被人忽略的规则是:即使两个成员函数除了const之外没有任何区别(即一个是const成员函数,另一个不是),这两个成员函数的重载也是合法的

一个存在极其拙劣的设计但是仅用于说理的例子如下:
Read the rest of this entry »

, , ,

1 Comment