由一道面试题引发的思考

来源:互联网 发布:安装网络电视需要什么 编辑:程序博客网 时间:2024/04/28 09:40

                           由一道面试题引发的思考

                                                                 created by jsjwql

在网上碰到这样的一个题目,据说是微软面试的题目:

将一个字符串的句子翻转(有空格)

"   you welcome,  GTSC  Microsoft         "

->"         Microsoft  GTSC,  welcome you   "

一直习惯用标准 库里面的容器和算法, 也尝试了下一下这方面的代码,发现中间还出现不少问题,这里并不是为要要展示算法有多么好,而是从中总结一些平时容易犯的一些错误。

 

inline bool isLetter(char* p)

{     

        if (!*p)

        return false;

 

        if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') )

                return true;

        else

                return false;

}

 

//divide a string into words and space-punctuation sectors

char* GetWord(char** p)       //problem 1

{

        char* tempStr = new char[20];   //problem 2

        //char* tempStr = (char *)malloc(sizeof(char) * 20);

        char* q= tempStr;

        //handle the word and put it into tempStr

        while ((**p != '/0')&&isLetter(*p))

        {

                *q = **p;

                 q++;

                (*p)++;

        }

       

        //check if it is a world

        if (isLetter(tempStr))

        {

                *q = '/0'; //add an end for using strlen() later //problem 3

                return tempStr;

        }

       

        //if it is space-punctuation sector

        while ((**p != '/0')&&!isLetter(*p))

        {

                *q = **p;

                q++;

                (*p)++;

        }     

        *q = '/0';

        return tempStr;

}

 

char* StringReverse(char* str)

{

        if (!str)

                return NULL;

 

        char *p = str;

        int size = strlen(str) + 1; //contains '/0'  //problem 3

        char *container = new char[size];        //problem 2

        //char *container = (char *)(malloc(sizeof(char) * size));

        char *tail = container + size -1;

        *tail = '/0'; //add an end

        //tail--;                            //problem 4

        while (*p != '/0')

        {

                //get a sector

                char *q = GetWord(&p);

 

                if (!q)

                 break;

               

                int len = strlen(q);

                tail -= len;

                char *r = tail;

                char *t = q;

                //put it into container by the tail

                for (int i = 0; i < len; i ++)

                {     

                        *r = *t;

                         r++;

                         t++;

                }

                delete q;               //problem 2

                //free(q);

        }

 

        return container;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

 

        char* originalStr = "   you welcome,  GTSC  Microsoft         ";

        char* reverseStr = StringReverse(originalStr);

        if (reverseStr)

                delete reverseStr;   //problem 2

                //free(reverseStr);

 

        return 0;

}

 

处理步骤是:

1.   提供一个化词的方法,把单词和非单词区(空格和标点看成一个整体)分开来

2.   申请一片空间,把这些划分出来的对象从改空间的尾部安插下去。

 

问题1

char* GetWord(char** p)  不能被定义成char* GetWord(char*p)    

我的想法是随着GetWord()函数的处理,p的指针会随之变化,当第一次调用GetWord处理完,p"   you welcome,  GTSC  Microsoft         "变成了

"you welcome,  GTSC  Microsoft         "前面的空格都没有了,但是如果定义成char* GetWord(char*p) ,则p仍然是"   you welcome,  GTSC  Microsoft         ",问题原因就像我们要在函数里面分配内存时一样,传入指针的指针。

如果直接传入指针的话,如下图所示,在调用函数的时候回创建一个临时指针,虽然我们可以通过这个指针来操作实际对象object的作用,但是它不会影响原始指针p

如果传入指针的指针的话,相当于我们传入的是指针p的指针&p,然后生成了&p的一个临时指针,这样的话,临时指针就能影响指针p了。

问题2

之前在调试代码的时候出现了一个很奇怪的错误,在释放空间的时候出现了问题,我第一反应是不是因为我new了数组,没有用delete[]的方式来释放空间,结果并不是这里出先问题,而是由下面的问题4引起来的。

这就引起了我的兴趣,学c++的都知道,用new创建一个数组的时候,记得用delete[]来删除,否则的话会造成内存泄露。

我就做了一个测试发现的结果确不是这样的,我用的开发工具是VS2005

int *p = new int[10];

int *head = p; //record the address of this array

for (int i = 0; i<5; i++)

{

*p = i;

p++;

}

delete head; //replace delete[] head;

我用vs自带的工具memory观察了内存分配过程,以及释放过程,发现居然不是我想象的只释放第一个数据对象*head,而是整个数组空间都被释放了。为了确认,我还用DoundsChecker 检查了一遍,没内存泄露。在网上和网友讨论了一翻,原来在effective c++ item5Scott就告诉过我们,new/delete new[]/delete[] 是必须要配套使用的,否则后果是无法预期的,所以说上面的代码虽然没有内存泄露,但是不稳定。

这样的话我们如果在一个函数中返回new出来的数组的话,

int* test()

{

int *p = new int[10];

int *head = p; //record the address of this array

for (int i = 0; i<5; i++)

{

*p = i;

p++;

}

 

         return head;

}

client端:

int* array = test();

调用这应该用delete[] array来删除new出来的空间,如果使用者不知道函数内部是怎么分配内存的话,很容易直接用delete array,这样可能造成无法预期的问题,也就是可能内存泄露。 这样的函数设计是不是不合理? 个人认为还可以用malloc/free的方式替代。

 

问题3

strlensizeof的区别,借用上面的例子

char *p = new char[10];

char *head = p; //record the address of this array

for (int i = 0; i<5; i++)

{

*p = ‘a’;

p++;

}

*p = ‘/0’;

int len = strlen(head);

int size = sizeof(head);

delete head; //replace delete[] head;

a

a

a

a

a

/0

 

 

 

 

 

Strlen表示字符串的长度,不包括/0字符,因为它不属于字符串的一部分,只是起了表示字符结束了的作用。但是你要安排空间来存放这个字符/0。所以一个有5个字符的字符串至少要6个字节的空间来存放。如果没有/0的话,strlen就从首地址一直往后找知道找到有/0存在才结束。所以这样的话strlen的长度就无法预期了。

Sizeof 的指的是分配空间的长度,但是sizeof(head)只有4,因为head是一个指针,拥有存放地址的。如果把代码改成

        char p[10];

        char *q = p;

        for (int i = 0; i<5; i++)

        {

                *q= 'a';

                 q++;

        }

        *q = '/0';

        int len = strlen(p);

        int size = sizeof(p);

size10,有一点容易混淆的是以为size6就是字符串的长度加上字符/0。这只是针对数组的空间刚好容纳字符串和/0的时候。

问题4

就是容易犯的一个低级数学错误。

假设一个int数组的首地址为p, 数组的长度为10,最后一个元素的地址是

p+10-1 而不是p+10. 相反如果末尾元素的地址是p,首地址就是p-10+1了。

否则的话就越位,容易出现不可预期的错误。

 

原创粉丝点击