Posts Tagged C/C++
Member functions with the const constraint in C++
Posted by kingsamchen in PROGRAMMING on 2011 年 05 月 24 日
一、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 »
Effective C++笔记:C++组成及类内部常量的实现
Posted by kingsamchen in PROGRAMMING on 2011 年 05 月 20 日
一、为什么C++这么复杂
自从C++发展成熟至今,对其过于复杂的抱怨不绝于耳。C++为什么如此复杂?
在很久之前,为了应对OO思想带来的冲击,C++作为C在OO特性上的改进版出现在人们视野之中。而C++最初的名字,也恰好是C with classes。
渐渐发展和成熟后的C++,吸收和借鉴了众多新式的编程思想和特性,也变得愈加复杂,最终成为不同于C的另一种语言。
Scott Meyers认为,现在的C++应看作多种语言的集合,而不是单独的某一种语言。所以C++大致上可以分为四种子语言:
1.C:归根究底,C++仍然以C为基础。二者具有近乎一致的语法结构,预处理功能,内建数据类型,数组,指针 .etc,C几乎是作为一个完全被兼容支持的子集存在于C++之中。但C++在这个基础之上,也增加了诸如重载、异常处理等特性。
2.Object-Oriented C++:这可以说是C++最初的雏形。支持类,封装,继承,多态,虚函数等来自面向对象思想的语言特性。
3.Template C++:这部分通常称为泛型编程(generic programming)或者模板元编程(template metaprogramming)。模板的引入几乎使旧有的C++体系发生了根本性的变化,并且产生了和旧有截然不同的编程思维和方式。
4.STL:准确的说,STL是一个标准模板库。但它是一个很特殊的模板库(标准化的思想最初来自于一个法国的数学流派)。STL定义了自己特有的容器,迭代器,算法和函数对象,并且推荐使用这些东西来替代旧式的工具。而且这些由STL定义的东西,在STL的标准协议约束下,啮合的超乎想象。
C++之所以复杂,是因为你需要时常在几个子语言之间来回切换。而每个子语言,都自己的一套规则和约定,规则和约定间不可避免的会存在一些鸿沟。因而,你会看到许许多多游离在规则之间的特例。
不单单是程序员,有时候连编译器都不知道应该使用哪一套方法来处理代码。因而,当你发现编译器的做法和你想象的大相径庭时,不必感到奇怪,因为这并不罕见。
但是在事实上,很不幸的是,有许多号称自己通晓C++的C++程序员,往往停留在只知道1和2的水平上。他们不会使用甚至几乎不知道template和STL。更有甚者,完全把C++当作C来使用。
如果某家公司聘用这些人来开发产品,那将是一件很恐怖的事情。
Read the rest of this entry »
表达式求解的一个简单实现
Posted by kingsamchen in PROGRAMMING on 2011 年 05 月 20 日
RT
好久没写Blog,写一篇凑凑数。
来自某个很无聊的“作业”,最初是让写一个简易科学计算器,为了避免做UI的麻烦,直接选择表达式求解的方式实现。
算法原型来自Lieo,经过部分改进后,支持负数和多重括号。
因为只是expression-evaluate的一个demo,所以没有写得太详细。
不支持小数,没有异常处理,包括除数为零。
这里说下算法:
首先需要有两个堆栈或者支持FILO性质的线性表(我用的是vector,便于调试时观察内部元素是否正确),分别保存操作数和操作符。
负数通过额外的符号位实现,并规定,操作符后面跟着的“-”表示负号,符号位逻辑求反。
遍历表达式时,如果碰到操作数,直接入操作数栈。碰到操作符op,先和操作符栈顶的元素比较优先级,如果优先级大于栈顶元素,入栈(如果操作符栈为空,直接入栈),否则取出操作符栈顶元素和操作数进行计算,一直到操作符栈空或者栈顶元素优先级小于操作符op。
对于括号特殊处理:
碰到“(”,直接入栈,碰到“)”,取出操作符栈和操作数栈计算,直到操作符栈中弹出“(”。
为了简化算法,表达式尾部需要有#符号充当哨兵元素,并规定#符号的优先级最小。
提供核心代码,全部代码请翻看SRC
#define pop(x) x.back(); \
x.pop_back();
struct NumData
{
NumData() : signal(false), pos(0)
{
wmemset(sz, L'\0', NUM_LEN);
}
enum{NUM_LEN = 10};
wchar_t sz[NUM_LEN];
bool signal;
unsigned int pos;
};
int EvaluateExpression(wchar_t* pszExpression)
{
stackOperand oprand;
stackOperator oprator;
bool isNumbericMode = true;
NumData data;
for (wchar_t* p = pszExpression; *p != NULL; ++p)
{
if (*p >= L'0' && *p <= L'9')
{
data.sz[data.pos++] = *p;
isNumbericMode = false;
}
else if (L'-' == *p && isNumbericMode)
{
data.signal = !data.signal;
}
else // pure operators
{
isNumbericMode = true;
if (L'(' == *p)
{
oprator.push_back(*p);
}
else if (L')' == *p)
{
int num;
if (data.pos)
{
num = FormatNumtoInt(data);
}
else
{
num = pop(oprand);
}
do
{
wchar_t op = pop(oprator);
if (L'(' == op)
{
oprand.push_back(num);
break;
}
else
{
int num2 = pop(oprand);
num = Calculate(num2, num, op);
}
} while (true);
}
else // operators except ( and )
{
int num;
if (data.pos)
{
num = FormatNumtoInt(data);
}
else
{
num = pop(oprand);
}
do
{
if (oprator.empty() || (GetPriority(*p) > GetPriority(oprator.back())))
{
oprand.push_back(num);
oprator.push_back(*p);
break;
}
else
{
int num2 = pop(oprand);
wchar_t op = pop(oprator);
num = Calculate(num2, num, op);
}
} while (true);
}
//DumpStack(oprand);
}
}
return oprand.back();
}
int FormatNumtoInt(NumData& data)
{
assert(data.pos);
int num = _wtoi(data.sz);
num = data.signal ? -num : num;
wmemset(data.sz, L'\0', NumData::NUM_LEN);
data.pos = 0;
data.signal = false;
return num;
}
经过重写的托盘图标CTray类
Posted by kingsamchen in PROGRAMMING on 2011 年 05 月 03 日
重写CTray缘于最近在写的某个Project。此Project需要显示托盘,所以我自然而然的想到了重用以前写的CTray。
但是当我把CTray文件添加到工程时,发现之前在编写CTray时,考虑得不够周到,整个类设计的也有问题
比如:强制使用带参数的constructor、成员函数命名不规范和异常情况考虑不周等问题
于是打算将CTray重写一遍,由于前后风格(命名风格请参考:匈牙利命名法的衰落和建议)和设计思维的改变,原有的代码几乎被推倒重写了一遍。
嗯,于是写了一天,终于完成。
现在的头文件大概是酱紫的
/**************************************************
** Project:CTray Design
** File:Tray.h
** Edition:v1.0.0 Demo
** Coder:KingsamChen [MDSA Group]
** Last Modify:2010-5-3
**************************************************/
#pragma once
#define WM_MY_TRAY (WM_USER + 10)
class CTray : public CObject
{
public:
CTray();
CTray(UINT trayId, UINT message);
virtual ~CTray();
public:
LRESULT OnTrayProc(WPARAM wParam, LPARAM lParam);
BOOL Create(CWnd* pWnd, HICON hIcon);
BOOL Create(UINT trayId, UINT message, CWnd* pWnd, HICON hIcon);
BOOL Show(LPCTSTR pszTipText);
void Delete();
BOOL Change(HICON hIcon, LPCTSTR pszTipText);
BOOL ShowBalloonTip(LPCTSTR pszInfoTitle, LPCTSTR pszInfo,
UINT timeout, DWORD infoFlags);
virtual void AssertValid() const;
public:
static UINT ms_taskbarCreateMsg;
protected:
NOTIFYICONDATA m_Nid;
};
唔,做几点说明吧:
1.显然,这个类还是为MFC设计的(WTL似乎也可以经过少量修改后直接使用),纯SDK的话需要大规模重写( ̄ε(# ̄)
2.支持原来的功能:显示/修改/删除图标,集成Message Proc,支持任务栏重建时恢复,气泡风格
3.原有接口几乎全被破坏- -||,所以就有代码需要更新。
4.另外,重写时翻文档发现了一点东西:
WinVista后,由于Tray Icon的UI做了大量修改,很多属性都不推荐使用,比如NOTIFYICONDATA.uTimeout。并且新增了一些成员(包括之前文档标识为Reserved)。
气泡弹出的效果也做了改进(不使用uTimeout,转而使用其他新增加成员)
但是考虑到功能统一,并没有使用新增加的效果,有兴趣的自己修改CTray
PS:新文档参考:http://msdn.microsoft.com/en-us/library/bb762159%28VS.85%29.aspx
新版本的CTray下载地址:http://cid-cb24c6c6cbd98991.office.live.com/self.aspx/Blog%20File%20Storage/CTray%20Design.zip
非常规排序问题
Posted by kingsamchen in PROGRAMMING on 2011 年 04 月 08 日
让问题飞一会儿1
考虑如下问题:
描述:假设有一个包含最多n个正整数的文件,每个数均小于n,此处n=10^7.且每个数字至多只出现一次。
目标:设计一个算法,将文件中的正整数按升序排列输出。
条件:RAM大约在1MB,最大占用不超过1.5MB2,运行时间在10S内,无磁盘空间约束。
这个问题抽象自某个数据库系统对美国800免费电话的排序问题:
当时美国免费电话由区号800加7位数字构成。系统在某些时候需要对这些号码进行排序。
对这个问题认真思考后,我们来考虑一些由这个原问题得到的扩展问题。
扩展问题
Q1:为了解决原问题,需要相应测试数据。我们需要一个算法,该算法能够产生[0,n)间不重复的含有K个(0<K<=n)元素的整数序列.
Q2:原问题中,RAM最大的空间是1.5MB,若提供的空间严格的只有1MB,对应的算法应该怎样修改?
Q3:如果数字允许最多重复出现10次,算法又该如何改进?
Q4:现在美国免费电话的区号有:800、877和888。而且还有不断增加的趋势,如何用1MB内存对上述号码进行排序?
Q5:在Q4的条件下(不包含1MB内存约束条件),如何将这些号码保存在一个集合中,实现对给定号码的快速查找?
Q6:在大空间中,初始化需要消耗大量时间。设计算法避免初始化。新算法的访问时间复杂度应该是O(1),且额外空间大小正比于排序数据大小。
原题分析
1.如果不考虑内存约束条件,即可以将10^7个整数全部读到内存中,在进行排序(比如QuickSort)。但是这个需要10^7 / 1024 / 1024 × 4 = 38MB。这个和允许的内存空间差距太大了。(感谢LittleHorse的debug
)
2.如果采用基于磁盘文件的归并排序,则可以有效降低内存占用。
我们设一次排序K个数,那么有 K * 4 / 20^20 = 1.5 => K = 393216
我们可以一次读入35W个数据进行排序(内存中可以用QuickSort),得到输出文件后再利用MergeSort进行合并。
但是很不幸的,我们需要N = 10^7 / K = 29次文件I/O。
是否有算法既能降低内存要求又能避免大量的磁盘I/O?
3.回忆线性排序中的计数排序(CountSort),结合实际情况,我们考虑采用位向量(bitset)来表示数据。
bitset表示法即:假设v是一个位向量,用于表示非负整数。那么若第K个数存在,则v[k]为true,否则为false。
易知,1000W个数需要1000W bits = 1.2MB3,内存符合要求。
Read the rest of this entry »
二叉查找树及一个封装拙劣的BST类
Posted by kingsamchen in PROGRAMMING on 2011 年 02 月 27 日
二叉查找树(Binary Search Tree, BST)是一种特殊的二叉树,常常用来作为进行数据查找的存储结构。
对于长度为n的线性表来说,搜索一个数据的时间是O(n),而对于BST,搜索的运行时间复杂度和高度成正比。
由二叉树的性质可以知道,当二叉树比较平衡时(比如完全二叉树),其高度h近似等于logn,n为结点数。所以,较为平衡的BST的搜索时间复杂度可以达到O(logn)。
1. 二叉查找树(BST)
BST通常用链表表示,每个结点至少会包含:parent,left,right和key等附加的数据域。如果孩子结点或者父结点不存在,则用NULL表示。
易知,树根结点是唯一一个父结点为NULL的结点。
BST有一个如下所述的重要存储性质:
在一个BST中,对任何结点x,其左子树中的key不大于x的key,其右子树中的key不小于x的key。即
如图为一棵BST
根据BST的性质可以知道,如果对BST进行中序遍历(Inorder Traverse),可以将BST中所有元素按有序输出
遍历一棵含有n个结点的BST所需要的时间复杂度是O(n),因为每个结点都会被访问。
2. BST类的设计
可以利用C++将BST封装成一个类,便于以后调用。且为了使得这个类通用,我们将类写成模板类
我们需要一个Node结构和一个控制结构的类CBinSearch。
为了尽可能地遵循封装思想,我们将Node类写在CBinSearch类的protected成员中,这样Node结构对外界是透明的。
考虑到某些函数要求客户传入一个指向Node结点的指针,我们仿照STL的iterator,定义出两个公共的结点指针类型
typedef Node* pos; typedef const Node* const_pos;
所以,最后我们的类声明大概是这样的
Read the rest of this entry »
The process in windows
Posted by kingsamchen in PROGRAMMING on 2011 年 02 月 08 日
1.进程属性
1.1.进程概述
在Win中,进程是正在运行程序的一个实例,包括一个内核对象和一个地址空间。
操作系统(Operating System,OS)用内核对象来管理进程及保存进程的统计信息。而在地址空间内,包含所有可执行文件或DLL模块的代码和数据及线程堆栈和分配的堆。
在Win中,进程本身没有执行代码的能力,需要依靠进程内的线程来完成。当OS创建一个进程时,会自动为进程创建一个线程,成为主线程(Primary-thread)。主线程可以创建更多的线程。每个线程都有自己的寄存器和堆栈。所有的线程都在进程的地址空间中“同时”执行代码。
如果进程中没有线程执行代码,OS将自动销毁进程并进行相关清理工作。
系统会轮流为每个要运行的线程调度一些CPU时间,使得看上去所有线程都在并发运行。
1.2.进程实例句柄
每个被加载到进程地址空间的EXE或DLL文件都被分配了一个唯一的实例句柄(HINSTANCE)。EXE文件的实例句柄以入口函数(w)WinMain的参数hInstanceExe传入。
PS:在非16Bit的Win中,HINSTANCE和HMODULE完全相同,可以互相代替使用。个人猜测和长短指针有关。
hInstanceExe参数表示OS将可执行文件映像加载到进程地址空间的某个内存基地址。基地址的选择(默认)由连接器决定。
为了得到一个EXE或者DLL文件的内存基地址,可以通过调用GetModuleHandle函数。
如果在DLL中想要得到调用DLL进程的基地址,可以调用GetModuleHandleEx,并传入GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
1.3.进程命令行
OS在创建一个进程时,会传递一个命令行,包含附加启动信息(程序启动参数)
在一个WinGUI程序中,可以调用GetCommandLine来获取进程完整的命令行。CUI程序的命令行大多保存在argv[]中。
对于WinGUI程序,调用CommandLineToArgvW函数可以将完整的命令行分解为单独的token,但是此API仅支持UNICODE编码,且调用API后,需要用HeapFree来释放相关内存空间。
1.4.进程环境变量
每个进程都有一个关联的环境块(environment block)分配在进程地址空间内。
其包含的字符串大致如下:
:::\…
VarName1 = VarValue1\0
VarName2 = VarValue2\0
\0
对环境变量的操作可以通过GetEnvironmentStrings、FreeEnvironmentStrings、GetEnvironmentVariable、ExpandEnvrionmentStrings及SetEnvrionmentVariable完成
Read the rest of this entry »
LogToFile 1.1.0 Release
Posted by kingsamchen in PROGRAMMING on 2011 年 01 月 26 日
为http://blog.kingsamchen.com/archives/546的新版
新版较之旧版改变如下:
1、在保留原有接口的基础上,增加了GetErrorMessage接口。此函数可以由GetLastError得到的错误代码获取错误文本说明,适合Win32平台上的开发
2、考虑到易用性,利用预处理解决掉了不同编译器之间的问题。即是说,在非Win32上,新增加GetErrorMessage接口不会被编译
值得注意的是,由于VC编译器的特殊性,预编译头无法用预处理解决,如果你不使用预编译,则直接删除相应预处理代码即可
3、由于接口函数DeleteFile与Win32 API发生名字重叠,所以新版函数接口改为DeleteLogFile,为了统一,相关函数也做出了修改
4、删去了仅VC支持的Error Stream处理的相关代码
新版函数接口如下
/************************************************** ** Project:LogToFile ** File:logfile.h ** Edition:v1.1.0 Demo ** Coder:KingsamChen [MDSA Group] ** Last Modify:2011-01-26 **************************************************/ #if _MSC_VER > 1000 #pragma once #endif #ifndef _LOGFILE_C6A57B5F_C91D_4fc0_9B38_10524313FAE1 #define _LOGFILE_C6A57B5F_C91D_4fc0_9B38_10524313FAE1 const wchar_t ERRLOG_FILE_NAME[] = L"ErrorLog.log"; void LogErrorToFile(const wchar_t* pszFileName, wchar_t* pszFormat, ...); void ClearLogFile(const wchar_t* pszFileName); void DeleteLogFile(const wchar_t* pszFileName); #ifdef _WIN32 typedef unsigned long ulong; int GetErrorMessage(ulong ulErrorId, wchar_t* pszMessage, ulong ulBufLen); #endif static int WriteFile(const wchar_t* pszFileName, wchar_t* pszStr); #endif
Download LogToFile_SRC
Algorithms Exercise 1
Posted by kingsamchen in PROGRAMMING on 2011 年 01 月 21 日
回家前在图书馆碰到了刘汝佳的《算法竞赛入门经典》,于是便借回来随手翻翻
个人实在不太喜欢这类风格的书,但是觉得某些题目不错,就随手做做,权当练习
1、位数
描述:输入一个不超过10^9的正整数(由输入者负责保证),输出它的位数。例如12735的位数是5.不要使用任何数学函数,只使用四则运算。
思路1:设接受的的n位整数为N,让i从1取到9(事实上,只要不发生溢出,可以取到上限),并令 k = N / 10^i,当k == 0时,则所求位数为i。算法复杂度为O(n)
int QueryDigit(int nNum)
{
int nDigit, nDiv;
for (nDigit = 1, nDiv = 10; nNum / nDiv; ++nDigit)
{
nDiv *= 10;
}
return nDigit;
}
思路2:查表法。很容易想到,每个数所在的位数区间都是确定的。比如12735在10000~99999这个区间范围内。而这个区间范围内的数都是5位。查表法需要写较多的代码,但是运行起来速度会快得多,不过只有在要查询的数字比较多的情况下才能体现。易知,复杂度为O(1)
int QueryDigit(int nNum)
{
int DigitTable[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int nDigit;
// a long long if statement
if (0 <= nNum && nNum < 10)
{
nDigit = DigitTable[0];
}
else if (10 <= nNum && nNum < 100)
{
nDigit = DigitTable[1];
}
else if (100 <= nNum && nNum < 1000)
{
nDigit = DigitTable[2];
}
else if (1000 <= nNum && nNum < 10000)
{
nDigit = DigitTable[3];
}
else if (10000 <= nNum && nNum < 100000)
{
nDigit = DigitTable[4];
}
else if (100000 <= nNum && nNum < 1000000)
{
nDigit = DigitTable[5];
}
else if (1000000 <= nNum && nNum < 10000000)
{
nDigit = DigitTable[6];
}
else if (10000000 <= nNum && nNum < 100000000)
{
nDigit = DigitTable[7];
}
else
{
nDigit = DigitTable[8];
}
return nDigit;
}
2、韩信点兵
描述:相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只要掠一眼排尾的人数就知道总人数。输入3个非负整数a,b,c,表示每种队形排尾人数(a<3,b<5,c<7),输出总人数的最小值。
思路1:题目实质是求x=3*k1+a = 5*k2 + b = 7*k3 + c。可以通过令x从1开始进行尝试,第一个符合的数就是所求的数。分析可知,算法的复杂度是O(n),这里的n只与所求数有关。
但事实上,存在复杂度为O(1)的算法。因为此题可以通过公式计算得到。
思路2:这道题在数论上颇有名气,其本质是求线性同余方程组的解。解法最早由宋朝秦九韶提出,并被国外数学家冠以“中国剩余定理”
ps:这本号称入门级的算竞赛辅导书(似乎还是面向中学生)居然会出现线性同余方程组求解的问题,表示这让我很诧异。
关于“中国剩余定理”的详细说明和分析将在未来几天内(起码得等我到家)完成。
由“中国剩余定理”可以得到韩信点兵问题的通解公式
知道了公式,代码就显得极其简单
Read the rest of this entry »
散列表
Posted by kingsamchen in PROGRAMMING on 2011 年 01 月 05 日
散列表(Hash Table)又称哈希表,是一种仅支持少数几种字典操作的表。其支持的操作有:Insert、Delete和Find
使用散列表的原因在于,对散列表中元素进行查找的期望效率很高,通常复杂度在O(1)
一、直接寻址表
在介绍散列表前,我们先介绍一种更高效的表:直接寻址表(Direct Addressing Table,DAT)
DAT查找的高效性在于采用的直接寻址技术(Direct Addressing),可以看作散列表的前身
设存在一些取自全域U的关键字元素k,即
此时n是一个不大的数,且规定
可以建立一个直接寻址表T[0...n-1](可利用数组实现)。若关键字k在表中,则必有T[k]=k,否则T[k]=NIL
易知,DAT的各种操作效率很高,时间复杂度可以达到O(1),但是局限性比较大
二、散列表
对于DAT来说,如果关键字全域U很大,那么实现一个大小为|U|的DAT就不太现实,且如果用到的关键字域|K|<<|U|,就会造成巨大的空间浪费
而利用散列表(Hash Table,HT),则可以保持查找期望复杂度为O(1)的情况下,是空间复杂度降低至O(|K|)
在DAT中,关键字k被存在T[k]中,而在HT中,关键字k保存在T[h[k]]中。其中,h(k)被称为散列函数(hash function),h(k)称为散列值(hash code)
散列函数h(k)利用关键字k,计算出k在HT中的位置,进而存储k
在HT中,有时会发生不同的关键字k得到相同的散列值的情况,即
这一情况称为碰撞(collision),后文对碰撞和解决碰撞的方法进行分析
三、散列函数
事实上,散列函数的好坏直接决定了散列表的性能,因为好的散列函数可以很大程度上降低碰撞发生的概率
在理想情况下,一个好的散列函数应满足每个关键字元素k映射到表的n个位置中是等可能发生的,且关键字之间各自独立,这个称为一致散列假设
但在实际情况中,这个假设很难成立,我们只能构造某些近似满足的情况
(1)求模取余散列法
使用求模取余散列法可能首先需要利用准备函数p(k),将关键字转化为自然数k
通过对关键字k进行模m操作来计算散列值(m为散列表大小),表达式为
此处h(k)应注意对m值的选取,由众多科学家及数学家的分析推导,m应取与2的整数次幂不太接近的一个素数
Read the rest of this entry »


COMMENTS