首页
社区
课程
招聘
[讨论]C++测试题简评一下
发表于: 2010-12-9 10:30 8688

[讨论]C++测试题简评一下

2010-12-9 10:30
8688

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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (12)
雪    币: 278
活跃值: (29)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
:( 很不想回帖说说你的问题,但觉得你的思路有问题。
*尽善的话把 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!)
2010-12-9 11:12
0
雪    币: 278
活跃值: (29)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
如按您所言,该类的职责已不单一了,如何增量编码

->

如按您所言,该类的职责已不单一了,已经不能谈上最优设计了
2010-12-9 11:17
0
雪    币: 239
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
看两位大牛简评,受教了
2010-12-9 11:20
0
雪    币: 278
活跃值: (29)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
*SkipWhiteSpace -> SkipSpace 自己孤陋了~~呜呼
2010-12-9 11:59
0
雪    币: 678
活跃值: (101)
能力值: ( LV2,RANK:150 )
在线值:
发帖
回帖
粉丝
6
其实指出错误的同时,也应该给一些建议,觉得看看《Effective C++》这类书籍是很不错的。觉得这种方式更好。
2010-12-9 12:21
0
雪    币: 1708
活跃值: (586)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
7
我猜都是没画流程图导致的编码乱。


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;
能加强内聚,实际使用的时候也方便。
2010-12-9 13:51
0
雪    币: 468
活跃值: (340)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
8
回罐头:
*file 或 buffer 作成一个接口。。。我没明白你的意思
*匹配有问题。。。我没明白你的意思
*whitespace这个说法不是我提出来的,我经常见到这个说法,我理解它的意思是不只是空格,空格加制表符一起就叫 whitespace 了
*多种编码格式的文件用适配器来平衡。。。我没明白你的意思

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

你不能说“有这份时间还不如好好写写WinMount”,我这几天在招聘新人,培训新人。今天高兴了,就写了这么个东西,希望对菜鸟能有所帮助
2010-12-9 14:48
0
雪    币: 278
活跃值: (29)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
回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
2010-12-9 15:14
0
雪    币: 1115
活跃值: (122)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
10
刘涛涛一直是我的偶像,最近准备做编译器了吗?

发一段我写的编译器的代码,不过只处理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;
}
2010-12-9 15:21
0
雪    币: 217
活跃值: (68)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
11
优秀的程序员总是能把复杂的事情条理化
赞一个
再复杂的事情只要条理化 都变得很简单
2010-12-9 15:59
0
雪    币: 1708
活跃值: (586)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
13
居家旅行必备。
上传的附件:
2010-12-11 13:26
0
雪    币: 437
活跃值: (110)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
14
受教了。
2011-3-7 10:46
0
游客
登录 | 注册 方可回帖
返回
//