views:

665

answers:

13

I just took an exam where i was asked the following:

Write the function body of each of the methods GenStrLen, InsertChar and StrReverse for the given code bellow. You must take into consideration the following;

  • How strings are constructed in C++
  • The string must not overflow
  • Insertion of character increases its length by 1
  • An empty string is indicated by 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) {

  };

};

The answer I gave was something like this (see bellow). My one of problem is that used many extra variables and that makes me believe am not doing it the best possible way, and the other thing is that is not working....

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

I would appreciate if anyone could explain me toughly what is the best way to have answered the question and why. Also how come my professor closes each class function like " }; " i thought that was only used for ending classes and constructors only.

Thanks a lot for your help.

+1  A: 

What does index in your code represent? What does it mean? Answer that and some of this should be a little bit clearer.

clahey
This is a comment, not an answer.
danben
index is just to count the string length... i know i was silly, could have used count :S
Carlucho
@Carlucho: You shouldn't have added any members to the class. The member for that purpose already was given.
UncleBens
+5  A: 
int GetStrLen(void){
    for (int i=0 ; str[i]!='\0' ; i++)
        index++;
    return index;   // *** Here am getting a weird value, something like 1829584505306 ***
}

You are getting a weird value because you never initialized index, you just started incrementing it.

dbyrne
ooooooooohhhhh ok
Carlucho
he will get wierd values even if index was initialized, see Kristopher Johnsons Answer
smerlin
+2  A: 

When you init the char array, you should set its first element to 0, and the same for index. Thus you get a weird length in GetStrLen since it is up to the gods when you find the 0 you are looking for.

[Update] In C/C++ if you do not explicitly initialize your variables, you usually get them filled with random garbage (the content of the raw memory allocated to them). There are some exceptions to this rule, but the best practice is to always initialize your variables explicitly. [/Update]

In InsertChar, you should (after checking for overflow) use StrLen to index the array (as the comment specifies "inser a character 'ch' at the end of the string 'str'"), then set the new terminating 0 character and increment StrLen.

Péter Török
but if the array is empty for example, wouldn't str[0] till str[80] be null, therefore having a value of \0?
Carlucho
@Carlucho, no - see my update.
Péter Török
oh ok, thanks a lot for the info Peter
Carlucho
A: 

You clearly have a lot of studying to do before you understand how to program. You should attempt rewriting the getlen and insert functionality for your own study. The later one is too much right now.

Write the two so that there is no 'index' variable, you correctly append (null) - HINT: if you initialize your array correctly there's no need to do this every time in the insert function. Also make sure you do not insert past the end of your buffer (and remember you need that null char).

Of course, you might also consider going a totally different route in your education but since this is a class you probably don't have that option. It doesn't seem to me that you've understood some of the basics that would be necessary to correctly answer this test question. You probably shouldn't be messing with C-style strings at all at this point.

Noah Roberts
I certainly try to learn more everyday, that is the reason why even after the exam i am interested in knowing and learning more about the question. Thanks for your advise swell Noah is well appreciated
Carlucho
This is not a helpful answer.
Steven Sudit
LOL!!! Looks like I got a new fan.
Noah Roberts
If you change your answer to make it helpful instead of judgmental, I will be glad to change the downvote to an upvote.
Steven Sudit
+5  A: 

Your GetStrLen() function doesn't work because the str array is uninitialized. It probably doesn't contain any zero elements.

You don't need the index member. Just use StrLen to keep track of the current string length.

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

This should be something more like

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

You don't need to figure out the length. You already know it it is strLen. Also there was nothing in the original question to indicate that the buffer should contain a null terminated string.

int GetStrLen(void){
    return strLen;
}

Just using an assertion here but another option is to throw an exception.

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

Reversing the string is just a matter of swapping the elements in the str buffer.

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;
    }
}
bmatthews68
Don't use `assert()` for that - you don't know where `InsertChar()` will be called from and have to handle overflows even if `NDEBUG` is defined.
Georg Fritzsche
+15  A: 

First, the trivial }; question is just a matter of style. I do that too when I put function bodies inside class declarations. In that case the ; is just an empty statement and doesn't change the meaning of the program. It can be left out of the end of the functions (but not the end of the class).

Here's some major problems with what you wrote:

  1. You never initialize the contents of str. It's not guaranteed to start out with \0 bytes.
  2. You never initialize index, you only set it within GetStrLen. It could have value -19281281 when the program starts. What if someone calls InsertChar before they call GetStrLen?
  3. You never update index in InsertChar. What if someone calls InsertChar twice in a row?
  4. In StrReverse, you create a reversed string called revStr, but then you never do anything with it. The string in str stays the same afterwords.

The confusing part to me is why you created a new variable called index, presumably to track the index of one-past-the-last character the string, when there was already a variable called StrLen for this purpose, which you totally ignored. The index of of one-past-the-last character is the length of the string, so you should just have kept the length of the string up to date, and used that, e.g.

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

Your algorithm for string reversal, however, is just completely wrong. Think through what that code says (assuming index is initialized and updated correctly elsewhere). It says "for every character in str, overwrite the entirety of revStr, backwards, with this character". If str started out as "Hello World", revStr would end up as "ddddddddddd", since d is the last character in str.

What you should do is something like this:

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

Take note of how that works. Say that StrLen = 10. Then we're copying position 0 of str into position 9 of revStr, and then position 1 of str into position 9 of revStr, etc, etc, until we copy position StrLen - 1 of str into position 0 of revStr.

But then you've got a reversed string in revStr and you're still missing the part where you put that back into str, so the complete method would look like

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

And there are cleverer ways to do this where you don't have to have a temporary string revStr, but the above is perfectly functional and would be a correct answer to the problem.

By the way, you really don't need to worry about NULL bytes (\0s) at all in this code. The fact that you are (or at least you should be) tracking the length of the string with the StrLen variable makes the end sentinel unnecessary since using StrLen you already know the point beyond which the contents of str should be ignored.

Tyler McHenry
Tyles thanks a lot, you managed to pin-point the answer 99%. Thanks for all your explanations, examples, and advice
Carlucho
`InsertChar()` doesn't look like its preventing overflow ;)
Georg Fritzsche
Added the exception for overflow. However, I'll bet that this class hasn't covered throwing exceptions yet given the rudimentary nature of the question, so I'm not exactly sure what the instructor expected the student to do in the case of overflow (maybe just silently refuse to insert the character?)
Tyler McHenry
Carlucho
The reverse algorithm can be done in-place (with a single `char` temporary (or using `swap`): `for ( int i = 0; i < StrLen/2; ++i ) { swap( str[i], str[StrLen-1-i] ); }`
David Rodríguez - dribeas
Very patient, thorough answer. I'd suggest that InsertChar also add the terminating '\0', based on the hint about how C++ stores strings. This way, str will be a proper zero-terminated string, not just a character array.
Steven Sudit
+1  A: 

I would use StrLen to track the length of the string. Since the length also indicates the end of the string, we can use that for inserting:

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;
    }
}
R Samuel Klatchko
+2  A: 

Your teacher gave you very good hints on the question, read it again and try answering yourself. Here's my untested solution:

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;
    }
  };
};
CodexDraco
If you're going to null-terminate the string in the constructor with `str[0]=0;`, then you should also null-terminate it in InsertChar().
indiv
Thanks, that looks pretty much like the answer he might have wanted. However Im pretty sure i was not allowed to modify the constructor (str[0]=0).
Carlucho
You should also use a bound of 79... make sure the last char is NUL.
Michael Aaron Safyan
Or just not use null-terminated strings as knowing the strings length makes it redundant.
Georg Fritzsche
+2  A: 

You don't need index as a member data. You can have it a local variable if you so please in GetStrLen(): just declare it there rather than in the class body. The reason you get a weird value when you return index is because you never initialized it. To fix that, initialize index to zero in GetStrLen().

But there's a better way to do things: when you insert a character via InsertChar() increment the value of StrLen, so that GetStrLen() need only return that value. This will make GetStrLen() much faster: it will run in constant time (the same performance regardless of the length of string).

In InsertChar() you can use StrLen as you index rather than index, which we already determined is redundant. But remember that you must make sure the string terminates with a '\0' value. Also remember to maintain StrLen by incrementing it to make GetStrLen()'s life easier. In addition, you must take the extra step in InsertChar() to avoid a buffer overflow. This happens when the user inserts a character to the string when the length of the string is alreay 79 characters. (Yes, 79: you must spend one character on the terminating null).

I don't see an instruction as to how to behave when that happens, so it must be up to your good judgment call. If the user tries to add the 80th character you might ignore the request and return, or you might set an error flag -- it's up to you.

In your StrReverse() function you have a few mistakes. First, you call GetStrLen() but ignore its return value. Then why call it? Second, you're creating a temporary string and work on that, rather than on the string member of the class. So your function doesn't change the string member, when it should in fact reverse it. And last, you could reverse the string faster by iterating through half of it only.

Work on the member data string. To reverse a string you can swap the first element (character) of the string with its last (not the terminating null, the character just before that!), the second element with the second-to-last and so on. You're done when you arrive at the middle of the string. Don't forget that the string must terminate with a '\0' character.

While you were solving the exam it would also be a good opportunity to teach your instructor a think or two about C++: we don't say f(void) because that belongs to the old days of C89. In C++ we say f(). We also strive in C++ to use class initializer lists whenever we can. Also remind your instructor how important const-correctness is: when a function shouldn't change the object is should be marked as such. int GetStrLen(void) should be int GetStrLen() const.

wilhelmtell
+1  A: 

first of all why on you use String.h for the string length? strlen(char[] array) returns the Lenght or any char array to a int.

Your function return a werid value because you never initialize index, and the array has zero values, first initilize then execute your method.

Gustavo Barrientos
+4  A: 

There are lots of interesting lessons to learn by this exam question. Firstly the examiner is does not appear to a fluent C++ programmer themselves! You might want to look at the style of the code, including whether the variables and method names are meaningful as well as some of the other comments you've been given about usage of (void), const, etc... Do the method names really need "Str" in them? We are operating with a "Strings" class, after all!

For "How strings are constructed in C++", well (like in C) these are null-terminated and don't store the length with them, like Pascal (and this class) does. [@Gustavo, strlen() will not work here, since the string is not a null-terminated one.] In the "real world" we'd use the std::string class.

"The string must not overflow", but how does the user of the class know if they try to overflow the string. @Tyler's suggestion of throwing a std::overflow_exception (perhaps with a message) would work, but if you are writing your own string class (purely as an exercise, you're very unlikely to need to do so in real life) then you should probably provide your own exception class.

"Insertion of character increases its length by 1", this implies that GetStrLen() doesn't calculate the length of the string, but purely returns the value of StrLen initialised at construction and updated with insertion.

You might also want to think about how you're going to test your class. For illustrative purposes, I added a Print() method so that you can look at the contents of the class, but you should probably take a look at something like Cpp Unit Lite.

For what it's worth, I'm including my own implementation. Unlike the other implementations so far, I have chosen to use raw-pointers in the reverse function and its swap helper. I have presumed that using things like std::swap and std::reverse are outside the scope of this examination, but you will want to familiarise yourself with the Standard Library so that you can get on and program without re-inventing wheels.

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

Good luck with the rest of your studies!

Johnsyweb
Good explanation, useful code. As usual, I'll suggest keeping the string zero-terminated.
Steven Sudit
@Steven: Thank you so much for the feedback.I'm interested to know what benefit zero-terminating the string would be in a class where the length of the string is always known.
Johnsyweb
There would be no performance benefit, at least for the operations above, although by the same token, there would be almost no cost. The benefit would come when you need to pass your string to any function that expected C-style terminations. For example, if you're going to eventually print it, you'll likely call puts or something similar. This is a realistic need, which is why std::string has c_str() method that returns the zero-terminated string. Most implementations either add a terminator at that point or kept one all along; the standard leaves it as an implementation choice.
Steven Sudit
@Steven: Thanks. Good explanation!
Johnsyweb