Я просто сдал экзамен, где меня спросили следующее:
Запишите тело функции каждого из методов 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];
}
}
};
Я ценил бы, если кто-либо мог бы объяснить меня примерно, что лучший способ состоит в том, чтобы ответить на вопрос и почему. Также, каким образом мой преподаватель закрывает каждую функцию класса как "}"; я думал, что это только использовалось для конечных классов и конструкторов только.
Большое спасибо за Вашу справку.
Во-первых, тривиальный вопрос };
- это просто вопрос стиля. Я тоже так делаю, когда помещаю тела функций внутри объявлений классов. В этом случае ;
является пустым утверждением и не меняет смысла программы. Его можно не ставить в конце функций (но не в конце класса).
Вот несколько основных проблем с тем, что вы написали:
str
. Не гарантируется, что оно начнется с \0
байт. index
, вы только устанавливаете его в GetStrLen
. При запуске программы он может иметь значение -19281281. Что если кто-то вызовет InsertChar
до вызова GetStrLen
?index
в InsertChar
. Что если кто-то вызовет InsertChar
дважды подряд?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 байтах (\0
s). Тот факт, что вы отслеживаете (или, по крайней мере, должны отслеживать) длину строки с помощью переменной StrLen
, делает концевой дозор ненужным, поскольку с помощью StrLen
вы уже знаете точку, за которой содержимое str
должно быть проигнорировано.
Я бы использовал 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;
}
}
в первую очередь, почему вы используете String.h для длины строки? strlen (char [] array) возвращает Lenght или любой массив char в int.
Ваша функция возвращает значение werid, потому что вы никогда не инициализируете индекс, а массив имеет нулевые значения, сначала инициализируйте, а затем выполните свой метод.
Из этого экзаменационного вопроса можно извлечь много интересных уроков. Во-первых, экзаменатор, похоже, сам не является программистом, свободно владеющим 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;
}
Удачи в дальнейшей учебе!
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++;
Вам не нужен индекс
в качестве данных-членов. Вы можете сделать его локальной переменной, если хотите, в 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
.
Вам не нужно вычислять длину. Вы уже знаете, что она равна 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;
}
}
Ваш учитель дал вам очень хорошие подсказки по вопросу, прочитайте его еще раз и попробуйте ответить сами. Вот мое непроверенное решение:
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;
}
};
};
int GetStrLen(void){
for (int i=0 ; str[i]!='\0' ; i++)
index++;
return index; // *** Here am getting a weird value, something like 1829584505306 ***
}
Вы получаете странное значение, потому что вы никогда не инициализировали индекс, вы просто начали его увеличивать.
Когда вы инициализируете массив char, вы должны установить его первый элемент в 0, и то же самое для index
. Таким образом, вы получите странную длину в GetStrLen
, поскольку от богов зависит, когда вы найдете искомый 0.
[Update] В C/C++ если вы явно не инициализируете свои переменные, то обычно они заполняются случайным мусором (содержимым выделенной им необработанной памяти). Есть некоторые исключения из этого правила, но лучшая практика - всегда инициализировать переменные явно. [/Update]
В InsertChar
вы должны (после проверки на переполнение) использовать StrLen
для индексации массива (как указано в комментарии "вставить символ 'ch' в конец строки 'str'"), затем установить новый завершающий 0 символ и увеличить StrLen
.
Ваша функция GetStrLen ()
не работает, потому что str
Массив не инициализирован. Вероятно, он не содержит нулевых элементов.
Вам не нужен элемент index
. Просто используйте StrLen
, чтобы отслеживать текущую длину строки.