【问题标题】:C++ exam on string class implementation字符串类实现的 C++ 考试
【发布时间】:2010-05-07 21:45:54
【问题描述】:

我刚参加了一次考试,被问到以下问题:

为下面给定的代码编写 GenStrLen、InsertChar 和 StrReverse 方法的函数体。您必须考虑以下事项;

  • 如何在 C++ 中构造字符串
  • 字符串不能溢出
  • 插入字符会将其长度增加 1
  • StrLen = 0 表示空字符串
class Strings {
  private:
    char str[80];
    int StrLen;
  public:

  // Constructor
  Strings() {
    StrLen=0;
  };

  // A function for returning the length of the string 'str'
  int GetStrLen(void) {

  };

  // A function to inser a character 'ch' at the end of the string 'str'
  void InsertChar(char ch) {

  };

  // A function to reverse the content of the string 'str'
  void StrReverse(void) {

  };

};

我给出的答案是这样的(见下文)。我的一个问题是使用了许多额外的变量,这让我相信我没有以最好的方式做到这一点,另一件事是这不起作用....

class Strings {
private:
    char str[80];
    int StrLen;
    int index; // *** Had to add this ***
public:

    Strings(){
        StrLen=0;
    }

    int GetStrLen(void){
        for (int i=0 ; str[i]!='\0' ; i++)
            index++;
        return index;   // *** Here am getting a weird value, something like 1829584505306 ***
    }

    void InsertChar(char ch){    
        str[index] = ch;  // *** Not sure if this is correct cuz I was not given int index ***
    }

    void StrRevrse(void){
        GetStrLen();
        char revStr[index+1];
        for (int i=0 ; str[i]!='\0' ; i++){
            for (int r=index ; r>0 ; r--)
                revStr[r] = str[i];
        }
    }
};

如果有人能大致解释一下回答问题的最佳方式和原因,我将不胜感激。还有我的教授怎么会像“};”这样关闭每个类函数,我认为这仅用于结束类和构造函数。

非常感谢您的帮助。

【问题讨论】:

  • 函数后只需要},尽管};在语法上是有效的。 ; 在这种情况下完全没有任何作用
  • 啊,好的,谢谢,因为这也让我有些困惑
  • 我很想把这个归档在作业标签下,但这不是作业。我们需要一个考试标签。
  • 根据你教授的代码,他显然是一个光荣的C程序员,碰巧知道如何创建类,并认为他知道C++。例如,在 C++ 中允许使用 void 参数,但不是惯用的 C++ 或良好的 C++ 风格。此外,他应该将 GetStrLen() 声明为“const”,但没有这样做。我建议您阅读 Neil Gray 的“A Beginnner's C++”、“Learn C++ in 21 Days”和“Parashift C++ FAQ Lite”。它们是熟悉 C++ 的极好资源,而且它们可能会比您的教授做得更好。
  • [继续] 这并不是说您的解决方案是正确的;你的代码有很多问题....但是考虑到我在教授的代码中看到的问题,我认为我提到的资源会更好地向你解释基本概念。

标签: c++ string character


【解决方案1】:

首先,琐碎的}; 问题只是风格问题。当我将函数体放在类声明中时,我也会这样做。在这种情况下,; 只是一个空语句,不会改变程序的含义。它可以放在函数的末尾(但不是类的末尾)。

以下是您所写内容的一些主要问题:

  1. 您永远不会初始化str 的内容。不保证以 \0 字节开头。
  2. 你从不初始化index,你只在GetStrLen内设置它。程序启动时它的值可能为 -19281281。如果有人在拨打GetStrLen 之前先拨打InsertChar 怎么办?
  3. 您永远不会在InsertChar 中更新index。如果有人连续两次拨打InsertChar 怎么办?
  4. StrReverse 中,您创建了一个名为revStr 的反向字符串,但是您从不使用它。 str 中的字符串保持不变。

令我困惑的部分是为什么您创建了一个名为 index 的新变量,大概是为了跟踪字符串中最后一个字符的索引,而此时已经有一个名为 StrLen 的变量用于此目的,你完全忽略了。最后一个字符 的索引是 字符串的长度,所以你应该保持字符串的长度是最新的,并使用它,例如

int GetStrLen(void){
  return StrLen; 
}

void InsertChar(char ch){    
  if (StrLen < 80) {
    str[StrLen] = ch;
    StrLen = StrLen + 1; // Update the length of the string
  } else {
    // Do not allow the string to overflow. Normally, you would throw an exception here
    // but if you don't know what that is, you instructor was probably just expecting
    // you to return without trying to insert the character.
    throw std::overflow_error();
  }
}

但是,您的字符串反转算法是完全错误的。想一想代码说什么(假设index 已在其他地方正确初始化和更新)。它说“对于str 中的每个字符,用这个字符向后覆盖整个revStr”。如果str"Hello World" 开头,则revStr 将以"ddddddddddd" 结尾,因为dstr 中的最后一个字符。

你应该做的是这样的:

void StrReverse() {
   char revStr[80];
   for (int i = 0; i < StrLen; ++i) {
     revStr[(StrLen - 1) - i] = str[i];
   }
}

注意它是如何工作的。说StrLen = 10。然后我们将str 的位置0 复制到revStr 的位置9,然后将str 的位置1 复制到revStr 的位置9,等等,直到我们复制@ 的位置StrLen - 1 987654355@ 进入revStr 的位置0。

但是你在revStr 中有一个反转的字符串,你仍然缺少将它放回str 的部分,所以完整的方法看起来像

void StrReverse() {
   char revStr[80];
   for (int i = 0; i < StrLen; ++i) {
     revStr[(StrLen - 1) - i] = str[i];
   }
   for (int i = 0; i < StrLen; ++i) {
     str[i] = revStr[i];
   }
}

还有一些更聪明的方法可以做到这一点,您不必有一个临时字符串revStr,但上面的功能完美无缺,并且是解决问题的正确答案。

顺便说一句,在这段代码中,您根本不需要担心 NULL 字节 (\0s)。您正在(或至少您应该)使用StrLen 变量跟踪字符串的长度这一事实使得结束标记变得不必要,因为使用StrLen 您已经知道str 的内容应该超过的点忽略。

【讨论】:

  • Tyles 非常感谢,您设法找到了 99% 的答案。感谢您的所有解释、示例和建议
  • InsertChar() 看起来不像是防止溢出 ;)
  • 添加了溢出异常。但是,我敢打赌,考虑到问题的基本性质,这门课还没有涵盖抛出异常,所以我不确定教师希望学生在溢出的情况下做什么(也许只是默默地拒绝插入字符?)
  • 是的,我也这么认为。再次感谢您的回答/更新一百万。像这样的用户和帖子造就了今天的 SO
  • 反向算法可以就地完成(使用单个char 临时(或使用swap):for ( int i = 0; i &lt; StrLen/2; ++i ) { swap( str[i], str[StrLen-1-i] ); }
【解决方案2】:
int GetStrLen(void){
    for (int i=0 ; str[i]!='\0' ; i++)
        index++;
    return index;   // *** Here am getting a weird value, something like 1829584505306 ***
}

你得到了一个奇怪的值,因为你从未初始化索引,你只是开始增加它。

【讨论】:

  • 即使索引已初始化,他也会得到奇怪的值,请参阅 Kristopher Johnsons 答案
【解决方案3】:

您的GetStrLen() 函数不起作用,因为str 数组未初始化。它可能不包含任何零元素。

您不需要index 成员。只需使用StrLen 来跟踪当前字符串长度。

【讨论】:

    【解决方案4】:

    通过这个考试问题可以学到很多有趣的课程。首先,考官本身并不像一个流利的 C++ 程序员!您可能想查看代码的样式,包括变量和方法名称是否有意义,以及您获得的有关 (void)const 等用法的其他一些 cmets... 做方法名称真的需要“Str”吗?毕竟,我们正在使用“字符串”类进行操作!

    对于“如何在 C++ 中构造字符串”,嗯(就像在 C 中一样)这些都是以 null 结尾的,并且不像 Pascal(和这个类)那样存储长度。 [@Gustavo, strlen() 在这里不起作用,因为该字符串不是以 null 结尾的字符串。] 在“现实世界”中,我们将使用 std::string 类。

    “字符串不能溢出”,但是类的用户如何知道他们是否试图溢出字符串。 @Tyler 建议抛出 std::overflow_exception(可能带有消息)会起作用,但是如果您正在编写自己的字符串类(纯粹作为练习,您在现实生活中不太可能需要这样做),那么您应该可能提供您自己的异常类。

    “插入字符将其长度增加 1”,这意味着 GetStrLen() 不计算字符串的长度,而是纯粹返回在构造时初始化并在插入时更新的 StrLen 的值。

    您可能还想考虑如何测试您的班级。出于说明目的,我添加了一个 Print() 方法,以便您可以查看该类的内容,但您可能应该查看类似 Cpp Unit Lite 之类的内容。

    对于它的价值,我包括我自己的实现。与目前为止的其他实现不同,我选择在反向函数及其交换助手中使用原始指针。我假设使用 std::swapstd::reverse 之类的东西超出了本次考试的范围,但您需要熟悉标准库,这样您就可以开始编程而无需重新发明轮子。

    #include <iostream>
    
    void swap_chars(char* left, char* right) {
        char temp = *left;
        *left = *right;
        *right = temp;
    }
    
    class Strings {
    private:
        char m_buffer[80];
        int m_length;
    public:
    
        // Constructor
        Strings() 
            :m_length(0) 
        {
        }
    
        // A function for returning the length of the string 'm_buffer'
        int GetLength() const {
            return m_length;
        }
    
        // A function to inser a character 'ch' at the end of the string 'm_buffer'
        void InsertChar(char ch) {
            if (m_length < sizeof m_buffer) {
                m_buffer[m_length++] = ch;
            }
        }
    
        // A function to reverse the content of the string 'm_buffer'
        void Reverse() {
            char* left = &m_buffer[0];
            char* right = &m_buffer[m_length - 1];
            for (; left < right; ++left, --right) {
                swap_chars(left, right);
            }
        }
    
    
        void Print() const {
            for (int index = 0; index < m_length; ++index) {
                std::cout << m_buffer[index];
            }
            std::cout << std::endl;
        }
    };
    
    int main(int, char**) {
        Strings test_string;
    
        char test[] = "This is a test string!This is a test string!This is a test string!This is a test string!\000";
        for (char* c = test; *c; ++c) {
            test_string.InsertChar(*c);
        }
    
        test_string.Print();
        test_string.Reverse();
        test_string.Print();
    
        // The output of this program should look like this...
        // This is a test string!This is a test string!This is a test string!This is a test
        // tset a si sihT!gnirts tset a si sihT!gnirts tset a si sihT!gnirts tset a si sihT
    
        return 0;
    }
    

    祝你学习顺利!

    【讨论】:

    • 很好的解释,有用的代码。像往常一样,我建议保持字符串以零结尾。
    • @Steven:非常感谢您的反馈。我很想知道在字符串长度总是已知的类中,零终止字符串有什么好处。
    • 不会有任何性能优势,至少对于上述操作而言,尽管出于同样的原因,几乎没有成本。当您需要将字符串传递给任何预期 C 样式终止的函数时,好处就会出现。例如,如果您最终要打印它,您可能会调用 puts 或类似的方法。这是一个现实的需要,这就是为什么 std::string 有 c_str() 方法返回零终止的字符串。大多数实现要么在那个时候添加一个终止符,要么一直保留一个;该标准将其作为实现选择。
    • @Steven:谢谢。很好的解释!
    【解决方案5】:
    void InsertChar(char ch){    
       str[index] = ch;  // *** Not sure if this is correct cuz I was not given int index ***
    }
    

    这应该更像

    str[strlen-1]=ch; //overwrite the null with ch
    str[strlen]='\0'; //re-add the null
    strlen++;
    

    【讨论】:

      【解决方案6】:

      你的老师在这个问题上给了你很好的提示,再读一遍,试着自己回答。这是我未经测试的解决方案:

      class Strings {
        private:
          char str[80];
          int StrLen;
        public:
      
        // Constructor
        Strings() {
          StrLen=0;
          str[0]=0;
        };
      
        // A function for returning the length of the string 'str'
        int GetStrLen(void) {
          return StrLen;
        };
      
        // A function to inser a character 'ch' at the end of the string 'str'
        void InsertChar(char ch) {
          if(StrLen < 80)
            str[StrLen++]=ch;
        };
      
        // A function to reverse the content of the string 'str'
        void StrReverse(void) {
          for(int i=0; i<StrLen / 2; ++i) {
            char aux = str[i];
            str[i] = str[StrLen - i - 1];
            str[StrLen - i - 1] = aux;
          }
        };
      };
      

      【讨论】:

      • 如果你要用str[0]=0;在构造函数中对字符串进行空终止,那么你也应该在InsertChar()中对其进行空终止。
      • 谢谢,这看起来很像他可能想要的答案。但是我很确定我不允许修改构造函数 (str[0]=0)。
      • 您还应该使用 79 的边界...确保最后一个字符为 NUL。
      • 或者只是不使用以空字符结尾的字符串,因为知道字符串长度会使它变得多余。
      【解决方案7】:

      初始化 char 数组时,应将其第一个元素设置为 0,index 也是如此。因此,GetStrLen 中的长度很奇怪,因为当你找到你要找的 0 时,这取决于上帝。

      [更新] 在 C/C++ 中,如果您没有显式初始化变量,通常会用随机垃圾(分配给它们的原始内存的内容)填充它们。此规则有一些例外,但最佳实践是始终显式初始化变量。 [/更新]

      InsertChar 中,您应该(在检查溢出后)使用StrLen 来索引数组(因为注释指定“在字符串'str' 的末尾插入一个字符'ch'”),然后设置新的终止 0 字符并递增 StrLen

      【讨论】:

      • 但如果数组为空,str[0] 直到 str[80] 为空,因此值为 \0?
      【解决方案8】:

      您不需要index 作为会员数据。如果您愿意,可以在GetStrLen() 中将其作为局部变量:只需在此处声明它而不是在类主体中。当你返回 index 时你得到一个奇怪的值的原因是你从来没有初始化它。要解决此问题,请将 index 中的 GetStrLen() 初始化为零。

      但是有一个更好的方法:当你通过InsertChar() 插入一个字符时,增加StrLen 的值,这样GetStrLen() 只需要返回那个值。这将使GetStrLen() 更快:它将在恒定时间内运行(无论字符串长度如何,性能都相同)。

      InsertChar() 中,您可以使用StrLen 作为索引而不是index,我们已经确定这是多余的。但请记住,您必须确保字符串以 '\0' 值结尾。还记得通过增加它来维护StrLen 以使GetStrLen() 的生活更轻松。此外,您必须在InsertChar() 中采取额外的步骤以避免缓冲区溢出。当字符串的长度已经是 79 个字符时,当用户向字符串中插入一个字符时,就会发生这种情况。 (是的,79:您必须在终止的 null 上使用一个字符)。

      我没有看到有关在发生这种情况时如何表现的说明,所以这必须取决于您的良好判断。如果用户尝试添加第 80 个字符,您可能会忽略请求并返回,或者您可能会设置错误标志——这取决于您。

      在您的StrReverse() 函数中,您有一些错误。首先,您调用GetStrLen(),但忽略它的返回值。那为什么叫它?其次,您正在创建一个临时字符串并对其进行处理,而不是对类的字符串成员进行处理。因此,您的函数不会更改字符串成员,而实际上它应该反转它。最后,您可以通过仅迭代一半来更快地反转字符串。

      处理成员数据字符串。要反转字符串,您可以将字符串的第一个元素(字符)与它的最后一个元素(不是终止的 null,之前的字符!)交换,将第二个元素与倒数第二个元素交换,依此类推。当你到达字符串的中间时,你就完成了。不要忘记字符串必须以'\0' 字符结尾。

      在您解决考试的同时,这也是一个很好的机会,让您的导师对 C++ 进行一两个思考:我们不会说 f(void),因为那属于 C89 的旧时代。在 C++ 中,我们说f()。我们还努力在 C++ 中尽可能使用类初始值设定项列表。还要提醒你的导师 const 正确性是多么重要:当一个函数不应该改变对象时,应该这样标记。 int GetStrLen(void) 应该是 int GetStrLen() const

      【讨论】:

        【解决方案9】:

        您无需计算长度。你已经知道它是 strLen。原始问题中也没有任何内容表明缓冲区应包含一个以空字符结尾的字符串。

        int GetStrLen(void){
            return strLen;
        }
        

        这里只使用断言,但另一种选择是抛出异常。

        void InsertChar(char ch){
            assert(strLen < 80);
            str[strLen++] = ch;
        }
        

        反转字符串只是交换 str 缓冲区中的元素。

        void StrRevrse(void){
            int n = strLen >> 1;
            for (int i = 0; i < n; i++) {
                char c = str[i];
                str[i] = str[strLen - i];
                str[strLen - i] = c;
            }
        }
        

        【讨论】:

        • 不要为此使用assert() - 你不知道InsertChar() 将从哪里被调用并且必须处理溢出,即使NDEBUG 已定义。
        【解决方案10】:

        我会使用StrLen 来跟踪字符串的长度。由于长度也表示字符串的结尾,我们可以使用它来插入:

        int GetStrLen(void) {
            return StrLen;
        }
        
        int InsertChar(char ch)
        {
            if (strLen < sizeof(str))
            {
                str[StrLen] = ch;
                ++strLen;
            }
        }
        
        void StrReverse(void) {
            for (int n = 0; n < StrLen / 2; ++n)
            {
                char tmp = str[n];
                str[n] = str[StrLen - n - 1];
                str[StrLen - n - 1] = tmp;
            }
        }
        

        【讨论】:

          【解决方案11】:

          首先为什么要使用 String.h 作为字符串长度? strlen(char[] array) 将 Lenght 或任何 char 数组返回到 int。

          您的函数返回一个奇怪的值,因为您从未初始化索引,并且数组的值为零,首先初始化然后执行您的方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-10-21
            • 1970-01-01
            • 1970-01-01
            • 2012-01-28
            • 2015-08-07
            • 1970-01-01
            • 2012-10-17
            • 2014-12-28
            相关资源
            最近更新 更多