这几天在培训新招的程序员。下面是这道题,我足足教了两个星期才过关。

大家可以试试,如果自信做得好的,可以发到 support@winmount.com

附上简历,我们继续招C++程序员

-----------------------
题目:用C++写一个类,读取一个文本文件,实现类成员函数如下:

GetOneWord 实现功能:当前位置获取一个单词,同时位置指针指向单词后。具体:
  * 自动跳过空白
  * 遇空白或换行视为单词结束
  * 本函数不跨行。如果到行尾,失败返回
  * 如果遇引号,引号中的部分视为一个单词
  * 如果遇 \ 号,视为下一行紧跟不换行

isLineEnd 实现功能:检查当前位置是否为换行或文件尾,自动跳过空白

skipLineEnd 实现功能:自动跳过空白,跳过行尾的换行符

以上所讲空白是指space空格和tab制表符。如果文件格式自动支持unicode,utf-8,ansi,且得到的单词是unicode,有加分

  • 标 题:答复
  • 作 者:dlmu
  • 时 间:2010-12-07 10:56:16

ascii版本,时间比较仓促,代码很粗糙,得到的是ascii,见笑。

上传的附件 Ascii版本.doc

C++测试题简评一下

前天我发了一道测试题
  http://bbs.pediy.com/showthread.php?t=126118
收到了三份答案,都写得很差

* 当我们说要解析一下文本文件时,通常这个文件是不大的,你第一感觉就应该是把整个文件都读到内存,然后用 char c = *p++ 之类代码就可以简单处理了。或者用 CreateFileMapping 转化成可以直接操作的内存。而这几份答案都是直接文件操作,满篇的 fread fgetc fseek ftell 看起来很乱。

* 要求中,有一个函数IsLineEnd,要得到当前是不是在一个行尾。既然是检查什么东西是不是,那么这个函数一定应该是const的,不能改变任何状态。你用fgetc得到下一个字母,发现不是行尾,这取出来的一个字母怎么还回去?即使能还回去这代码也是不好的。全读出来后用 *p++ 就不存在这个问题了。

* 要求中,多次提到要跳过空白,你应该有一个函数SkipWhiteSpace,而不要满篇代码,到处都在比较空格,很乱

* 重点戏 GetOneWord,因为需要考虑三种情况
  * 跳过开头的空白
  * 遇非单词字符要停下来,非单词字符有多种可能
  * 以引号开头要特殊处理
于是初学者就乱了方寸,不知道怎么写了,搞得代码极乱。

这里为什么我不提 \ 的行结束这种情况呢?其实可以把 \ 当作是一个空格,是个单词结束符就可以了。这个符号仅仅是 IsLineEnd 和 SkipLineEnd 要考虑的,并没有增加 GetOneWord 的复杂度。

优秀的程序员总是能把复杂的事情条理化。既然GetOneWord需要跳过开头的空白,那么在函数开始调用一下 SkipWhiteSpace 就解决了。这就少了一种情况。

以引号开头需要特殊处理,那么接下来判断一下第一个字符,如果是引号,就转到另一段例程,找到下一个引号就可以return 了

总之,函数的流程应该是这样的:
  if (IsLineEnd) return;
  SkipWhiteSpace();
  if (*p = '\"')
  {
    ...;
    return;
  }
  while (IsWordChar(*p))
  {
    ...;
    p++;
  }
整个流程清晰明了。如果
  * 你不是用*p而是用fread或fgetc来得到下一个字母,
  * 你没有用 SkipWhiteSpace 来跳过前头空白,而是到处比较,
  * 你用一个flag来表示是在单词中还是单词前,还是单词后,
  * 你没有把引号开头的情况独立处理,而是用一个flag来区分,
那么,每一种情况都会让这个简单的函数变得复杂。如果这些错误你都犯了,这个函数会复杂得不堪入目,一个小时也看不懂,而且极可能有BUG。

GetOneWord的原型定义,可能是
  void GetOneWord(OUT stl:string & s);
  bool GetOneWord(OUT char s[]);
  CString GetOneWord();
等等。如果你写成 char * GetOneWord() 那是不好的。

当你拿到一个模块工作任务时,你应该安静地完成。你不能因为文件打开失败就
  cout << "file open error";
你怎么知道我有cout ? 你怎么知道我是一个命令行console的工程而不是GUI甚至驱动?当你要失败时,你只能return false,或者抛出异常。

实际应用中,我们还考虑了以下这些情况,
  * 单词中出现\
  * 单词中出现引号
  * 引号中出现\换行
  * 引号不配对
  * 自动支持多种格式的文本文件unicode,utf-8,ansi

  • 标 题:答复
  • 作 者:回忆罐头
  • 时 间:2010-12-09 11:12:51

:( 很不想回帖说说你的问题,但觉得你的思路有问题。
*尽善的话把 file 或 buffer 作成一个接口, ReadWord 只接受IO接口,当然文件不大比较快的方式还是直接操作buffer,故而可以做一个inline的buffer接口
*匹配有问题,为了速度为何不做张assii字符转码表呢?
*SkipWhiteSpace -> SkipSpace
*多种编码格式的文件用适配器来平衡,不应该再集合到这个小程序中。如按您所言,该类的职责已不单一了,如何增量编码

btw:
*有这份时间还不如好好写写WinMount的测试代码,以下是 WinMTExt.dll(3.3.1.20),在获取目录长度为1时直接抛异常了?给谁抓?

.text:1000D389                 mov     ebx, 1
.text:1000D38E                 mov     [esp+240h+var_230], ebx
.text:1000D392                 test    edi, edi
.text:1000D394                 jnz     short loc_1000D3C2
.text:1000D396                 call    __invalid_parameter_noinfo
.text:1000D39B                 xor     eax, eax
.text:1000D39D
.text:1000D39D loc_1000D39D:                           ; CODE XREF: sub_1000D290+134j
.text:1000D39D                 mov     esi, [esp+240h+var_224]
.text:1000D3A1                 lea     ecx, [esi-4]
.text:1000D3A4                 cmp     ecx, [eax+10h]
.text:1000D3A7                 jb      short loc_1000D3AE
.text:1000D3A9                 call    __invalid_parameter_noinfo
.text:1000D3AE
.text:1000D3AE loc_1000D3AE:                           ; CODE XREF: sub_1000D290+117j
.text:1000D3AE                 cmp     [esi+10h], ebx
.text:1000D3B1                 ja      short loc_1000D3B8
.text:1000D3B3                 call    sub_10014656

*多线程的同步也有问题,压缩文件很多时候点取消都直接死锁了,和7z模块的同步有问题,感觉对7z模块改的不兼容,个人倒觉得可以效仿haozip完全修正7z源码并发布份修改版出来,很多地方都作得不如haozip,也许您把大部分时间都花到mount这一功能上了。(感谢您提供的WinMount!)

  • 标 题:答复
  • 作 者:cntrump
  • 时 间:2010-12-09 13:51:52

我猜都是没画流程图导致的编码乱。

引用:
GetOneWord的原型定义,可能是
  void GetOneWord(OUT stl:string & s);
  bool GetOneWord(OUT char s[]);
  CString GetOneWord();
等等。如果你写成 char * GetOneWord() 那是不好的。
前两种的话,和C没有区别,写个类只是为了简单的把三个可分离的函数拼在一起,就写不出类的优势了。

我认为这样比较好:
string GetOneWord();
bool IsLineEnd();
void skipWhiteSpace();
private:
char *m_pos;
能加强内聚,实际使用的时候也方便。

  • 标 题:答复
  • 作 者:LiuTaoTao
  • 时 间:2010-12-09 14:48:19

回罐头:
*file 或 buffer 作成一个接口。。。我没明白你的意思
*匹配有问题。。。我没明白你的意思
*whitespace这个说法不是我提出来的,我经常见到这个说法,我理解它的意思是不只是空格,空格加制表符一起就叫 whitespace 了
*多种编码格式的文件用适配器来平衡。。。我没明白你的意思

最后,谢谢你提的WinMount的BUG。第一个BUG目录长度为1的问题已经解决。第二个多线程的同步问题,我们也会测试。以后再发现BUG欢迎报给我。

你不能说“有这份时间还不如好好写写WinMount”,我这几天在招聘新人,培训新人。今天高兴了,就写了这么个东西,希望对菜鸟能有所帮助

  • 标 题:答复
  • 作 者:回忆罐头
  • 时 间:2010-12-09 15:14:32

回LiuTaoTao:
*file 接口,我指的意思是把这部分的操作分开,对接口编码,比如7z源码里很多单独的buffer操作函数,更正规的就是以输入流的代价进来,但这显然很不合算
*Space字符的匹配如果转成一张表, 比如 

const unsigned char kTransTableForSpace[] = 
{
  4,   //0,   \00
  0,   //1, 
  0,   //2, 
  0,   //3, 
  0,   //4, 
  0,   //5, 
  0,   //6,
  0,   //7,
  0,   //8,
  4,   //9, \t
  4,   //10, \0a
...
  0   //255, ''
}

上面的\t 和0a都可以直接查表获取,如此速度总比多个 if 或者 switch 强吧(这局限assii)

*whitespace是我看得少 :)
*适配器,我说的是设计模式里的适配器模式,为的是统一编码接口

//////////////////////////////////////////////////////////////////////////////////////////////////////
你不能说“有这份时间还不如好好写写WinMount”,我这几天在招聘新人,培训新人。今天高兴了,就写了这么个东西,希望对菜鸟能有所帮助 
//////////////////////////////////////////////////////////////////////////////////////////////////////
:) 还望多见谅! 其实这题我做了,但发现要做到完善要作的功夫很多,最后放弃了。
刚好今天见您发帖了,说了下我的观点。那2个bug也是这些天作了压缩的工作,看了几家的产品,顺便提上来的!

第二个我这里的调试信息如下,希望对您有帮助: 

0:004> ~*kv

   0  Id: bf8.a50 Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f74c 7c821c8d 000001ac ffffffff 00000000 ntdll!KiFastSystemCallRet
0012f760 025155f9 000001ac ffffffff 00000111 kernel32!WaitForSingleObject+0x12
0012f77c 02516be9 00000002 00000111 02574640 MouCoreUI!GetIWM_MouCoreUI+0xea9
0012f814 77e25f82 02590ff0 00710150 00000111 MouCoreUI!GetIWM_MouCoreUI+0x2499
0012f854 77e25f38 00000000 00000000 77e2b8b8 USER32!IsDialogMessageW+0x2e1
0012f860 77e2b8b8 00000000 00000000 00000000 USER32!IsDialogMessageW+0x297
00000000 00000000 00000000 00000000 00000000 USER32!LoadCursorW+0x4eca

   1  Id: bf8.8d0 Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
01daff48 7c93e4a2 00000002 01daff70 00000000 ntdll!KiFastSystemCallRet
01daffb8 7c824829 00000000 00000000 00000000 ntdll!RtlSetLastWin32ErrorAndNtStatusFromNtStatus+0x301
01daffec 00000000 7c93e1fa 00000000 00000000 kernel32!GetModuleHandleA+0xdf

   2  Id: bf8.b78 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
026cfef4 77e2bbd1 00000002 026cff1c 00000000 ntdll!KiFastSystemCallRet
026cff50 77e2ce36 00000001 026cffb0 ffffffff USER32!MsgWaitForMultipleObjectsEx+0xd7
026cff6c 4c6268ab 00000001 026cffb0 00000000 USER32!MsgWaitForMultipleObjects+0x1f
026cffb8 7c824829 00000000 00000000 00000000 gdiplus+0x68ab
026cffec 00000000 4c629605 00000000 00000000 kernel32!GetModuleHandleA+0xdf

   3  Id: bf8.614 Suspend: 1 Teb: 7ffdc000 Unfrozen
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
027ce398 77e24f0d 0087eea8 0000000c 00000000 ntdll!KiFastSystemCallRet
027ce3b8 77e17892 00400070 02ccd728 027ce3f4 USER32!SetWindowTextW+0x2d
027ce3c8 025163e8 00710150 000003f1 02ccd728 USER32!SetDlgItemTextW+0x21
027ce3f4 02b9e4d2 02ccd6d0 02e60020 000a5b05 MouCoreUI!GetIWM_MouCoreUI+0x1c98
00000000 00000000 00000000 00000000 00000000 7z!CreateObject+0x3df62

#  4  Id: bf8.914 Suspend: 1 Teb: 7ffdb000 Unfrozen
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
02aefff4 00000000 00000000 00000000 00000000 ntdll!DbgBreakPoint

  • 标 题:答复
  • 作 者:accessd
  • 时 间:2010-12-09 15:21:59

刘涛涛一直是我的偶像,最近准备做编译器了吗?

发一段我写的编译器的代码,不过只处理ascii,不解析unicode



#define CHAR_ISDIGIT(c)  ((c)>='0' && (c)<='9')
#define CHAR_ISCHAR(c)  (((c)>='a' && (c)<='z') || ((c)>='A' && (c)<='Z'))
#define CHAR_ISHEXCHAR(c) (CHAR_ISDIGIT(c) || ((c)>='a' && (c)<='f') || ((c)>='A' && (c)<='F'))
#define CHAR_ISBLANK(c) ((c)==' '||(c)=='\t')
#define CHAR_ISEND(c) ((c)=='\0'||(c)=='\r'||(c)=='\n')
#define CHAR_ISMATHOPR(c)  ((c)=='+'||(c)=='-'||(c)=='*'||(c)=='/'||(c)=='^'||(c)=='&'||(c)=='|')

#define ISFULLWORDCHAR(x) (((x)>='a'&&(x)<='z') || ((x)>='A'&&(x)<='Z') || CHAR_ISDIGIT(x) || (x) == '_' || (x) == '$')
#define SKIPBLANK(x)    while((*x) == ' ' || (*x) == '\t'){(x)++;}    //跳过空格和tab



/*
判断语句是否结尾

  ;
*/
BOOL CScript::IsEndOfLine(const char *str)
{
  char* p = (char*)str;
  SKIPBLANK(p);
  if(CHAR_ISEND(*p))
    return TRUE;

  if(*p != ';')
    return FALSE;
  //只能是一个 ';'
  p++;
  SKIPBLANK(p);
  if(CHAR_ISEND(*p))
    return TRUE;

  return FALSE;
}