Экзамен C++ на реализации строкового класса

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

Запишите тело функции каждого из методов 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];
        }
    }
};

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

Большое спасибо за Вашу справку.

8
задан Michael0x2a 15 May 2014 в 22:58
поделиться

11 ответов

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

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

  1. Вы никогда не инициализируете содержимое str. Не гарантируется, что оно начнется с \0 байт.
  2. Вы никогда не инициализируете index, вы только устанавливаете его в GetStrLen. При запуске программы он может иметь значение -19281281. Что если кто-то вызовет InsertChar до вызова GetStrLen?
  3. Вы никогда не обновляете index в InsertChar. Что если кто-то вызовет 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 (при условии, что index инициализирован и обновлен правильно в другом месте). Он говорит "для каждого символа в str, перезапишите весь revStr, в обратном порядке, этим символом". Если str начиналась как "Hello World", то revStr закончится как "ddddddddddddd", поскольку 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 из revStr, и так далее, и так далее, пока мы не скопируем позицию StrLen - 1 из 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, но приведенный выше вполне работоспособен и будет правильным ответом на проблему.

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

17
ответ дан 5 December 2019 в 04:49
поделиться

Я бы использовал 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;
    }
}
1
ответ дан 5 December 2019 в 04:49
поделиться

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

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

1
ответ дан 5 December 2019 в 04:49
поделиться

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

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

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

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

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

Для большей пользы, я включаю свою собственную реализацию. В отличие от других реализаций, я решил использовать raw-пойнтеры в обратной функции и ее помощнике 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!\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;
}

Удачи в дальнейшей учебе!

4
ответ дан 5 December 2019 в 04:49
поделиться
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++;
2
ответ дан 5 December 2019 в 04:49
поделиться

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

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

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

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

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

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

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

2
ответ дан 5 December 2019 в 04:49
поделиться

Вам не нужно вычислять длину. Вы уже знаете, что она равна 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;
    }
}
1
ответ дан 5 December 2019 в 04:49
поделиться

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

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;
    }
  };
};
2
ответ дан 5 December 2019 в 04:49
поделиться
int GetStrLen(void){
    for (int i=0 ; str[i]!='\0' ; i++)
        index++;
    return index;   // *** Here am getting a weird value, something like 1829584505306 ***
}

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

4
ответ дан 5 December 2019 в 04:49
поделиться

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

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

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

2
ответ дан 5 December 2019 в 04:49
поделиться

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

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

4
ответ дан 5 December 2019 в 04:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: