Desktop softwares and information
Posts tagged c++
十进制整数到字符串的转换
Mar 4th
今天被问到了这个问题,一下子卡壳,没有回答上来。
十进制比如12345如何转换成字符串?基本思路是要把每一位整数单独提取出来。
一万二千三百四十五,如何提取每一位?其实也简单:
12345 / 10000 = 1;
12345 % 10000 = 2345;
取出的模再来一遍:
2345 / 1000 = 2;
2345 % 1000 = 345;
以此类推。
这算法在我自己的函数库中都有过实现,关键时刻却不记得了。老了吗?sigh~
Windows命令行窗口与Unicode
Feb 23rd
一.
还记得刚开始学习C语言的时候,最早接触的runtime函数,就是printf,这应该是一个最基础的函数了。
但是这样的一个函数,即使在VC2005和Windows 7下面,对输出Unicode字符这样的任务,依然会给程序员带了麻烦。
下面的代码是一个Unicode版本的测试程序:
int wmain(int argc, wchar_t **argv)
{
wprintf(L"测试");
return 0;
}
看着完全没有问题,但是在console中就是没有任何的输出。
二.
单步进入wprintf,发现如下代码:
retval = _woutput_l(stdout,format,NULL,arglist);
其中,宏stdout为标准输出,定义如下:
#define stdout (&__iob_func()[1])
__iob_func的代码如下:
/* These functions are for enabling STATIC_CPPLIB functionality */
_CRTIMP FILE * __cdecl __iob_func(void)
{
return _iob;
}
_iob是一个FILE类型的数组,第0个元素为stdin,第1个元素为stdout,第3个为stderr。
而FILE类型是一个结构体:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
从图中可以看出,std本身就不支持UNICODE。
三.
我们还可以更深入一点,进入_woutput_l函数,会发现该函数调用
WRITE_CHAR(ch, &charsout);
宏逐个输出字符。
该宏的定义和_woutput_l在同一个文件(output.c)中找到:
#define WRITE_CHAR(ch, pnw) write_char(ch, stream, pnw)
其中ch为第一个字符,“测”;stream为_woutput_l的第一个参数,就是stdout。
四.
write_char的代码很简单:
LOCAL(void) write_char (
_TCHAR ch,
FILE *f,
int *pnumwritten
)
{
if ( (f->_flag & _IOSTRG) && f->_base == NULL)
{
++(*pnumwritten);
return;
}
#ifdef _UNICODE
if (_putwc_nolock(ch, f) == WEOF)
#else /* _UNICODE */
if (_putc_nolock(ch, f) == EOF)
#endif /* _UNICODE */
*pnumwritten = -1;
else
++(*pnumwritten);
}
五.
进入_fputwc_nolock(即_putwc_nolock)。该函数的声明为:
wint_t __cdecl _fputwc_nolock (
wchar_t ch,
FILE *str
)
函数中对str有三个判断,
if (_textmode_safe(_fileno(str)) == __IOINFO_TM_UTF16LE)
{
...
}
else if (_textmode_safe(_fileno(str)) == __IOINFO_TM_UTF8)
{
...
}
else if ((_osfile_safe(_fileno(str)) & FTEXT))
{
...
}
第一第二个判断很明显,判断目标文件是否为UTF16 little endian编码和UTF8编码。
对stdout,这里会进入第三个判断,其中FTEXT表示文件句柄为文本模式。
在该条件下,函数接着调用wctomb_s:
wctomb_s(&size, mbc, MB_LEN_MAX, ch)
做编码转换,即试图把wide char的ch转换为multi-byte的mbc。
六.
wctomb_s调用_wctomb_s_l,仅仅把locale参数设为NULL:
extern "C" errno_t __cdecl wctomb_s (
int *pRetValue,
char *dst,
size_t sizeInBytes,
wchar_t wchar
)
{
return _wctomb_s_l(pRetValue, dst, sizeInBytes, wchar, NULL);
}
七.
_wctomb_s_l函数是wctomb_s的locale版,根据locale做不同的工作。声明如下:
extern "C" int __cdecl _wctomb_s_l (
int *pRetValue,
char *dst,
size_t sizeInBytes,
wchar_t wchar,
_locale_t plocinfo
)
函数对locale有一个判断,
if ( _loc_update.GetLocaleT()->locinfo->lc_handle[LC_CTYPE] == _CLOCALEHANDLE )
{
if ( wchar > 255 ) /* validate high byte */
{
if (dst != NULL && sizeInBytes > 0)
{
memset(dst, 0, sizeInBytes);
}
errno = EILSEQ;
return errno;
}
if (dst != NULL)
{
_VALIDATE_RETURN_ERRCODE(sizeInBytes > 0, ERANGE);
*dst = (char) wchar;
}
if (pRetValue != NULL)
{
*pRetValue = 1;
}
return 0;
}
else
{
...
}
如果locale是C locale的话,那么当wchar大于255,即超出ANSI范围时,不做转换,直接返回;
小于255,即为ANSI字符时,则转换为char类型。
如果不是c locale,则调用WideCharToMultiByte:
if ( ((size = WideCharToMultiByte( _loc_update.GetLocaleT()->locinfo->lc_codepage,
0,
&wchar,
1,
dst,
(int)sizeInBytes,
NULL,
&defused) ) == 0) ||
(defused) )
测试代码没有设置locale,所以判断wchar,自然大于255,函数直接返回。层层返回,没有调用任何实际的输出。
八.
稍稍修改一下测试程序:
int wmain(int argc, wchar_t **argv)
{
wprintf(L"%s", L"测试");
return 0;
}
输出的是两个问号。单步跟踪,我们可以发现,在_woutput_l中,没有直接调用WRITE_CHAR,而是调用WRITE_STRING:
WRITE_STRING(text.wz, textlen, &charsout);
WRITE_STRING的代码:
LOCAL(void) write_string (
_TCHAR *string,
int len,
FILE *f,
int *pnumwritten
)
{
if ( (f->_flag & _IOSTRG) && f->_base == NULL)
{
(*pnumwritten) += len;
return;
}
while (len-- > 0) {
write_char(*string++, f, pnumwritten);
if (*pnumwritten == -1)
{
if (errno == EILSEQ)
write_char(_T('?'), f, pnumwritten);
else
break;
}
}
}
在write_char写入Unicode字符失败后,就会写入一个”?”,所以输出的会是问号。
九.
如何输出Unicode呢?有两个方法:
1. 设置正确的locale。
我们看到整个过程最后会调用WideCharToMultiByte把Unicode根据locale转换成multiple byte。
这可以解决一点问题,但不完美。除了绕了个圈,又把wide char转换成multiple byte之外,还无法解决多种语言混合输出的问题。
比如,无法同时输出俄文和中文。
2. 使用WriteConsole。
如下代码可以完美的解决Unicode的问题:
wchar_t szText[] = L"测试";
HANDLE hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hStdOutput, szText, (DWORD)wcslen(szText), NULL, NULL);
如何设计类
Feb 20th
一.
类是面向对象的基础。面向对象有三大特点:封装,继承和多态。
我们学习面向对象,不可回避几部经典,比如C++编程,编程思想等。
无论Java还是C++,只要讲解面向对象,都会有一些例子用来说明什么是封装,继承和多态。
大部分都会用shape来举例。
shape作为抽象的基类,circle,square和triangle继承自shape。shape会有个draw的函数,多半还是个虚函数。
这样的例子对初学者来说也是简单易懂的,很能说明白面向对象是什么。
但这却是一个非常糟糕的开端,就像小时候父母告诉你是从胳肢窝里出来的一样,从一开始就给了你一个错误的概念。
二.
面向对象的产生,不仅仅是为了解决问题,还应该要站在待解决问题的角度来描述问题,从而让问题本身更容易被理解和解决。
换个说法,就是我们应该如实的描述问题,不夸张不过分。
什么是夸张?你从胳肢窝里出来就是夸张。shape有个draw的函数,也是夸张。你见过一个会draw自己的shape?
有人认为这无伤大雅,把draw放在shape中,能更简洁的解决问题。
但我们应该从一开始就建立一个正确的概念。
三.
然而把draw放在shape中并不能简洁的解决问题。
我们需要在真实的环境中使用这些类,比如Microsoft Windows。我们使用GDI来画这些shape。我们需要一个DC。
怎么表示?在shape中聚合一个HDC,还是把HDC作为参数传递给draw?(应该没有人会让shape从DC继承吧?)
我们还需要一个画笔,来表达这个shape的其他属性,比如颜色,粗细等。
我们还需要知道位置,这个shape在DC上的位置。
问题开始变得复杂了,shape类以及它的子类与GDI对象耦合在一起。
四.
shape类以及它的子类本应该是独立的。
cycle在在Windows下是cycle,在Linux下也应该是cycle,不管有没有GDI对象。
问题出在什么地方?
在这个例子中显而易见:只有在真正开始draw的时候,才需要GDI对象,而shape本身,并不能做draw这件事情。
把一件不能做的事情,或是不适合做的事情硬塞给对象来实现,迟早是会出问题的。
最糟糕的不是我们发现不了问题,而恰恰是问题迟早会被发现。
五.
如何从一开始就避免这样的情况,而不是在第二次的时候才做正确?
我们如何才能够设计出一个正确的类?或者更确切的说,如何才能够正确的设计出一个类?
从一开始就如实的描述问题,不夸张不过分。不给它不拥有的属性,更不给它不应该有的父类。
这是最基本的设计原则。
六.
对于设计现实中不存在对应的类,同样需要如实描述——如实描述想法,设计。不要想的是孙悟空,出来的却是二郎神。
计算机只识别0和1,这是个讲究精确的行业。

Recent Comments