Экзамен C++ по реализации класса string

Я только что сдал экзамен, где меня спросили следующее:

напишите тело функции каждого из методов 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]!='' ; 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]!='' ; i++){
            for (int r=index ; r>0 ; r--)
                revStr[r] = str[i];
        }
    }
};

Я был бы признателен, если бы кто-нибудь мог объяснить мне примерно, что является лучшим способом ответить на вопрос и почему. Также Почему мой профессор закрывает каждую функцию класса, как " }; ", Я думал, что это используется только для завершения классов и конструкторов.

большое спасибо за вашу помощь.

11 ответов


во-первых, тривиальные }; вопрос просто вопрос стиля. Я тоже, когда я поместил тело функции внутри объявления класса. В этом случае ; - это просто пустое утверждение и не меняет значения программы. Его можно оставить вне конца функций (но не конец урока).

вот некоторые основные проблемы с тем, что вы написали:

  1. вы никогда не инициализировать содержимое str. Это не гарантированно начать с байт.
  2. вы никогда не инициализировать index, вы устанавливаете его только внутри GetStrLen. Он может иметь значение -19281281 при запуске программы. Что, если кто-нибудь позвонит InsertChar прежде чем они называют GetStrLen?
  3. вы никогда не обновляйте index на InsertChar. Что, если кто-нибудь позвонит InsertChar два раза подряд?
  4. на StrReverse, вы создаете обратную строку с именем revStr, но тогда вы никогда ничего не делаете с ним. Строка str остается те же послесловия.

запутанная часть для меня-это то, почему вы создали новую переменную под названием index, по-видимому, для отслеживания индекса последнего символа строки, когда уже была переменная с именем StrLen для этой цели, которую вы полностью проигнорировали. Показатель один-прошлое-в-последний символ is длина строки, поэтому вы должны были просто сохранить длину строки в актуальном состоянии и использовать это, например,

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" С d является последним символом в str.

что вы должны сделать что-то вроде этого:

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

обратите внимание, как это работает. Скажи это StrLen = 10. Тогда мы копируем позицию 0 из str в позицию 9 revStr, а затем Позиция 1 из str в должности 9 of revStr и т. д., и т. д., Пока мы не копируйте положение StrLen - 1 of str в положение 0 revStr.

но тогда у вас есть обратная строка в 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, но вышеуказанное совершенно функционально и было бы правильным ответом на проблему.

кстати, вам действительно не нужно беспокоиться о нулевых байтах (s) вообще в этом коде. Тот факт, что вы (или, по крайней мере, вы должны быть) отслеживать длину строки с StrLen переменная делает end sentinel ненужным с использованием StrLen вы уже знаете, за содержание str следует игнорировать.


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

вы получаете странное значение, потому что вы никогда не инициализировали индекс, вы просто начали увеличивать его.


код GetStrLen() функция не работает, потому что str массив не инициализирован. Вероятно, он не содержит нулевых элементов.

вам не нужно index член. Просто используйте StrLen отслеживать текущую длину строки.


есть много интересных уроков на этот экзаменационный вопрос. Во-первых, экзаменатор не кажется беглым программистом на C++! Возможно, вы захотите взглянуть на стиль кода, в том числе на то, имеют ли значения переменные и имена методов, а также некоторые другие комментарии, которые вам дали об использовании (void), const, etc... Действительно ли имена методов нуждаются в " Str " в них? В конце концов, мы работаем с классом "Strings"!

"Как строки построены на C++", ну (как и в C), они заканчиваются нулем и не сохраняют длину с ними, как это делает Pascal (и этот класс). [@Gustavo,strlen() не будет работать здесь, так как строка не является нулевой.] В "реальном мире" мы бы использовали std::string класса.

"строка не должна переполняться", но как пользователь класса знает, пытаются ли они переполнить строку. @ Предложение Тайлера бросить std::overflow_exception (возможно, с сообщением) будет работать, но если вы пишете свой собственный класс string (чисто как упражнение, вам вряд ли понадобится делать это в реальной жизни), то вы, вероятно, должны предоставить свой собственный класс исключений.

"вставка символа увеличивает его длину на 1", это означает, что GetStrLen() не вычисляет длину строки, но чисто возвращает значение StrLen, инициализированное при построении и обновленное вставкой.

вы также можете подумать о том, как вы собираетесь тест свой класс. Для иллюстрации я добавил Print() метод, чтобы вы могли посмотреть содержимое класса, но вы, вероятно, должны взглянуть на что-то вроде Cpp Unit Lite.

для чего это стоит, я включаю свою собственную реализацию. В отличие от других реализаций, я решил использовать raw-указатели в функции reverse и ее помощнике swap. Я предположил, что использование таких вещей, как std::swap и std::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!0";
    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;
}

удачи с остальными вашими исследованиями!


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]=''; //re-add the null
strlen++;

ваш учитель дал вам очень хорошие подсказки по вопросу, прочитайте его снова и попробуйте ответить сами. Вот мое непроверенное решение:

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;
    }
  };
};

когда вы вводите массив символов, вы должны установить его первый элемент в 0, и то же самое для index. Таким образом, вы получаете странную длину в GetStrLen так как это зависит от богов, когда вы найдете 0, который вы ищете.

[обновление] в C / C++ если вы явно не инициализируете свои переменные, вы обычно заполняете их случайным мусором (содержимое выделенной им необработанной памяти). Есть некоторые исключения из этого правила, но лучше всегда инициализируйте переменные явно. [/Update]

на InsertChar, вы должны (после проверки переполнения) использовать StrLen чтобы индексировать массив (поскольку комментарий указывает "inser символ 'ch' в конце строки 'str'"), затем установите новый завершающий символ 0 и инкремент StrLen.


не нужно index как данные участника. Вы можете иметь локальную переменную, если вам так нравится в GetStrLen(): просто объявить его там, а не в теле класса. Причина, по которой вы получаете странное значение при возврате index - Это потому, что вы никогда не инициализировать его. Чтобы исправить это, инициализируйте index к нулю в GetStrLen().

но есть лучший способ сделать вещи: когда вы вставляете символ через InsertChar() увеличить значение StrLen, так что GetStrLen() нужно только вернуть это значение. Это сделает GetStrLen() гораздо быстрее: он будет работать в постоянное время (та же производительность независимо от длины строки).

на InsertChar() можно использовать StrLen как вы указываете, а не index, который мы уже определили, является избыточным. Но помните, что вы должны убедиться, что строка завершается '' значение. Также не забудьте сохранить StrLen увеличивая его, чтобы сделать GetStrLen()легче жизнь. Кроме того, вы должны сделать дополнительный шаг в InsertChar() чтобы избежать буфера переполнение. Это происходит, когда пользователь вставляет символ в строку, когда длина строки составляет 79 символов. (Да, 79: вы должны потратить один символ на завершение null).

в своем StrReverse() функция у вас есть несколько ошибок. Во-первых, вы звоните GetStrLen() но игнорировать возвращаемое значение. Тогда зачем звонить? Во-вторых, вы создаете временную строку и работаете над ней, а не над строковым членом класса. Таким образом, ваша функция не изменяет строковый элемент, когда он должен фактически отменить его. И, наконец,вы можете изменить строку быстрее, повторяя только половину ее.

работа над строкой данных члена. Для реверса строки можно поменять местами первый элемент (символ) строки с ее последним (не завершающим нулем, символом непосредственно перед этим!), второй элемент с предпоследним и так далее. Вы закончите, когда доберетесь до середины строки. Не забывайте, что строка должна заканчиваться на '' символ.

пока вы решали экзамен, это также была бы хорошая возможность научить вашего инструктора думать или два о C++: мы не говорим f(void) потому что это относится к старым временам C89. В C++ мы говорим f(). Мы также стремимся в C++ использовать списки инициализаторов классов, когда можем. Также напомните своему инструктору, насколько важна const-корректность: когда функция не должна изменять объект, он должен быть отмечен как таковой. int GetStrLen(void) должно быть int GetStrLen() const.


вам не нужно вычислять длину. Вы уже знаете, что это стрлен. Также в исходном вопросе не было ничего, что указывало бы, что буфер должен содержать строку с нулевым завершением.

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;
    }
}

Я хотел бы использовать 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;
    }
}

прежде всего, почему вы используете строку.h для длины строки? strlen (char [] array) возвращает длину или любой массив char в int.

ваша функция возвращает значение werid, потому что вы никогда не инициализируете индекс, а массив имеет нулевые значения, сначала инициализируйте, а затем выполните свой метод.