Link to home
Start Free TrialLog in
Avatar of learn
learn

asked on

object array?

I tried following code with out success:
   Class String
     {...........};
   main()
     {String s1[99];
       .........
     }
Can you tell me how to do that (the length of s1[0] may not be the same as s1[1])?

Thank you in advance.
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of learn
learn

ASKER

Edited text of question
Given what you've shown, that should work.  But I suspected you left out a detail about your class.  

You are creating an array of 99 objects.  C++ will try to intiailize every one of those objects using the class'es default constructor (99 times in a row)  Thus in order to create an array of this class (or any class) you must have a default constructor for the class.  I bet you don't.

Now here is where things get confussing.  If you don't provide any constructors for the class, the C++ compiler will create a constructor for it if possible (If not possible it will produce a compiler  error)  Thus if you have no explicit constructors for the class, it will have a default constructor and the array can be created.   On the other hand, If you specify any constructors for the class, the compile will not try to create a default constructor.  In that case, you must specify a default constructor to make this work.  Otherwise it will not work.
Do you know what a default constructor is?   In case not, it is a constructor that can be called without any parameters.  See the C++ compiler has 99 objects to construct, but no parameters for them, so it needs a constructor that takes no parameters.  There are two ways you can create a default constructor.  It can either take no parameters, or it can take 1 or more parameters, but all the parameters have default values (specified with an = in the parameter list.)

Let me know if you have questions.
Avatar of learn

ASKER

Hi Nietod, you again!

I do put a default constructor:
Class String
{public:
 String();
 ........
}
But, you remaind me that by default, all the object has got length = 0 and later, the program, their lengthes are changed....is that OK? I did the same thing in a single String with success though.
If you need more detail, I can submit the program for you to check.
I thought you were getting a compiler error.  What type of error are you getting?  What is its description.

As to the length issue--that seems suspicious.  You may be on the wrong track.  How are you handling these strings?  Each string object should have a pointer to a dynamically allocated (using new) character array in memory that stores the actual string.  When the string needs to be made longer a new array is allocated and the data is copied to the new array.  Then the old array can be deleted.  

There is a lot that can be done to make this more efficient.  But that is what you need to do to get it to work.  Is that what you were trying?  If you want me to look at the code, post it here if it is not to long.  If it is long, e-mail it to nietod@theshop.net
Avatar of learn

ASKER

Hi nietod

I have reduceed my code and hope it is not too long now :-)

When run the code, I got output:
    s1.GetStr() = ab
    s3[0].GetStr() = ......
    Null pointer assignment
The code:

#include<iostream.h>

class String
{private:
   char* itsStr;
   unsigned short itsLen;
 public:
   String();
   String(const unsigned short len);
   ~String();
   void AddChar(char c1);
   void AddStr(String s1);
   char* GetStr();
};

String::String()
{  itsStr = new char[1];  //in heap, open a room of char
   itsStr[0] = '\0';      //after get room, may move in a null
   itsLen = 0;
}

String::String(const unsigned short len)
{  itsStr = new char[len+1];
   for (unsigned short i = 0; i <= len; i++)
      itsStr[i] = '\0';          //all set to null so that the area is
   itsLen = len;                 //pointed be the pointer not empty and
}                                //don't need to remember to put a null
                         //at the end of a string.
String::~String()
{  delete [] itsStr;
   itsLen = 0;
}

void String::AddChar(char c1)
{  String temp(itsLen+1);
   for (unsigned short i = 0; i < itsLen; i++)
      temp.itsStr[i] = itsStr[i];      //can access private member within class
   temp.itsStr[itsLen] = c1;
   delete [] itsStr;                   //resize string length by delete+new
   itsStr = new char[itsLen+2];
   for (i = 0; i < itsLen+1; i++)
      itsStr[i] = temp.itsStr[i];
   itsStr[itsLen+1] = '\0';
   itsLen++;
}

void String::AddStr(String s1)
{  unsigned short totalL = itsLen + s1.itsLen;
   String temp(totalL);
   for (unsigned short i = 0; i < itsLen; i++)
      temp.itsStr[i] = itsStr[i];
   for (i = itsLen; i < totalL; i++)
{     temp.itsStr[i] = s1.itsStr[i-itsLen];
}
   delete [] itsStr;
   itsStr = new char[totalL+1];
   for (i = 0; i < totalL; i++)
      itsStr[i] = temp.itsStr[i];
   itsStr[totalL] = '\0';
   itsLen = totalL;
}

char* String::GetStr()
{  return itsStr;
}

void main()
{  String s1, s2;   s1.AddChar('a'); s2.AddChar('b'); s1.AddStr(s2);
   cout<<"s1.GetStr() = "<<s1.GetStr()<<endl;
   String s3[5];
   s3[0].AddChar('c');
   s3[0].AddStr(s2);
   cout<<"s3[0].GetStr() = "<<s3[0].GetStr()<<endl;
}


Avatar of learn

ASKER

Adjusted points to 80
Right idea, more or less.  Some points however:

String::String()
     {  itsStr = new char[1];  //in heap, open a room of char
        itsStr[0] = '\0';      //after get room, may move in a null
        itsLen = 0;
     }
There are two systems (more really) for tracking the string length.  You could use a NUL (0) terminator to mark the end of the string or you could us an integer that records the length of the string.  I prefer the integer method because then you can even put NUL characters within the string (Which can be usefull at times.)  It also tends to be more efficient.  There is no need to use both methods.  There is no addvantage to that and there is more work.

On a similar topic, I recommend two lengths.  One is the length allocated in the character array, the other is the length of the string actrualyl stored.  This way, you can allocate extra space in the character array.  Then as the string grows you use the same character array and keep adjusting the string length.  When the string fills the array, you then alocate a new array with additional space.  This will be faster.  This feature can be added later, it is not need to make it work.
****************************
String::String(const unsigned short len)
     {  itsStr = new char[len+1];
        for (unsigned short i = 0; i <= len; i++)
           itsStr[i] = '\0';          //all set to null so that the area is
        itsLen = len;                 //pointed be the pointer not empty and
     }                                //don't
This doesn't make a lot of sense.  How long is the string?  0 characters or is it itsLen characters.  If you go to the method I suggested above with the two lengths, then it could create an array "len" characters long in preperation for storing a string of a specified size.  But it could set "itsLen" to 0 indicating it currently stores no string.  but under the current design, this type of constructor makes no sense, at least not the way you have it.
********************************
void String::AddChar(char c1)
     {  String temp(itsLen+1);
        for (unsigned short i = 0; i < itsLen; i++)
           temp.itsStr[i] = itsStr[i];      //can access private member within class
        temp.itsStr[itsLen] = c1;
        delete [] itsStr;                   //resize string length by delete+new
        itsStr = new char[itsLen+2];
        for (i = 0; i < itsLen+1; i++)
           itsStr[i] = temp.itsStr[i];
        itsStr[itsLen+1] = '\0';
        itsLen++;
     }
Way too much work!  The following will be easier.   Note it assumes that you  want both a NUL at the end and a length.  I really think you want one or the other, but didn't want to force you into one.

void String::AddChar(char c1)
 {  
   char *NewAry = char [new ItsLen+2];  // 1 extra for c1 and 1 extra for NUL.
   memcpy(NewAry,ItsStr,itsLen);  // copy old value.  Okay even if old length is 0.
   NewAry[ItsLen] = c1;
  NewAry[ItsLen+1] = 0;  // NUL terminator.
   ++ItsLen; // Adjust length.
  delete [] ItsStr;
  ItsStr = NewAry;
     }

One more change.  I would do.

String & String::operator +=(char c1)
 {  
    // guts from above.
   return *this;
 }

That allows you to do.

SomeString += 'a';

then I would do.

String String::operator +(char c1)  const
 {  
   String NewStr(*this);  // Assumines you have a copy constructor.  You will have to have one.
   NewStr += c1;  // Add on to new string.
   return NewStr;
 }

This allows you to do

StringA = StringB + 'A';

Both are good to have and it is good to write the + one to use the += one as I did.

continues.
There are a couple of problems with AddStr.  It will cause a crash if you use it.  first of all you pass a String by value.  Since you have no copy constructor, the compiler creates one and that one will cause problems.  (I can elaborate if needed).  You do need to write a copy constructor to prevent these sort of things from happening.  However, in the case of AddStr() you can just pass the string using a reference instead.  Since the string is treated as constant, you would use a cosntant reference, like

void String::AddStr(const String &s1)

Now, for the guts.  Again I have both a NUL and a length.  That is overkill  Also I'm going to write it as operator += instead you could do the same stuff in AddStr

String &String::operator +=(const String &s1)
{
   char *NewStr = char[ItsLen + s1.ItsLen +1]; // extra room for NUL.

   memcpy(NewStr,ItsStr,ItsLen);
   memcpy(NewStr+ItsLen,s1.ItsStr,s1.ItsLen);
   ItsLen += s1.ItsLen;
   NewStr[ItsLen] = 0;
   delete [] ItsStr;
   ItsStr = ItsLen;
   return *this;
}

and also do
String &String::operator + (const String &s1) const
{
   String NewStr(*this);  // again copy constructor needed.

   NewStr += s1;
   return NewStr;
}

Now you can do.

StringA += StringB;
and
StringC = StringA + StringB;

(when you have an "operator =" also).
I suspect that your crash was caused by the fact you used AddStr()  It is possible that something else caused the crash, but AddStr certainly would do it if nothing else did first.  Try the changes I proposed and add a copy constructor.  Make sure you understand why a copy constructor is needed.  That is essential to C++ programming.   I would also do an operator = and a constructor from a "char *"  Those are really nice to have.
Avatar of learn

ASKER

Thank you Nietod.

>Right idea, more or less.
Yes, the code can work for just getting characters from file.
As you point out, my problem is didn't use refernce when passing the String which is resised its length! I have found it and want to tell you before too late, but you point out alweady :-)

>you could us an integer that records the length of the string.
That is very interesting, though not understand.

>
On a similar topic, I recommend two lengths.  One is the length allocated in the character
    array, the other is the length of the string actrualyl stored.  This way, you can allocate extra
    space in the character array.  Then as the string grows you use the same character array
    and keep adjusting the string length.  When the string fills the array, you then alocate a
    new array with additional space.  This will be faster.  This feature can be added later, it is
    not need to make it work.
>
Not very clear...as my username suggested, I am a beginer :-( I have never understan what the book says, but I read thier examples. Did you said we can adrust not only the length of the Strings but also the size of the array? If that is ture, you must give me an example....too good.

>Way too much work!  The following will be easier.
Well, I am a hard worker. Thank you for the easier way you let me know. I even don't know "memcpy(NewAry,ItsStr,itsLen)"...

>Make sure you understand why a copy constructor is needed.
No, why and how...

I can understand the rest of your comments and code. I will change my code. Thank you very much indeed.
>>>you could us an integer that records the length of the string.
>>     That is very interesting, though not understand.
That's what "ItsLen" is doing.  But not always clearly, since you also use NUL to indicate the end of the string.

Out of time for now.  I'll get back to you.  
The way the two length idea would work is as follows.  You store two integers in the class.  One is the current length of the string stored in the array, the other is the length of the array.  The length of the array can be longer than the string.  The extra characters are just ignored, that is, they are not considered part of the string.  If the string needs to be expanded, it can expand into this extra length.  (Until there is not more extra space left.)  It might look like this.

class String
{
   int StrLen;  // Actuall length of string.
   int AryLen;  // Length of string array.  Must be >= StrLen;
   char *StrPtr;
}

// This function can be called to resize the string array to a specified length.  However it will not // necessarily do as requested.  If the string array need to grow, it will grow by an additional
// amount.  If the string array needs to shrink it will not shrink it unless  it needs to shrink a lot.  In // otherwords, the procdure tries to maintain a little extra room at the end of the array.
String::Resize(int NewLen) // Length to size to.
{
   if (NewSiz > AryLen) // If we need to expand the array, then
   {
      NewSiz = NewSiz + NewSiz/16;  // Calculate even more space than requested.
   }
   else if (NewSiz < AryLen) // If we need to shrink the array, then
   {
        NewSiz = min(AryLen,NewSiz + NewSiz/16); // Don't make new size smaller than
                           // current unless it is a lot smaller than current.
   }

   if (NewSiz != ArySiz) // If the array really does need to be changed,
   {
       int CpyLen = min(NewSiz,StrLen);  // Length of old string to copy.
       char *NewStr = new char[NewSiz]; // -> new string buffer.
       memcpy(NewStr,StrPtr,CpyLen);  // Copy in old string.
       delete [] StrPtr;  // Delete old string.
       StrPtr = NewStr;   // Save -> new string.
       ArySiz = NewSiz;  // Save new array length.
   }
}

String &String::operator +=(char &Chr)
{
   Resize(StrLen+1);  // Resize the string if needed (but not if there is already enough room!)
    StrPtr[StrLen] = Chr;
    ++StrLen;
     return *this;
}

String &String::operator +=(const String &s1)
{
   Resize(StrLen+s1.StrLen);  // Resize the string if needed (but not if there is already enough room!)
    memcpy(StrPtr+ItsLen,s1.StrPtr,s1.StrLen);
    StrLen += s1.StrLen;
    return *this;
}

how is that?  make sense?  This resize procedure does all the work.  It makes everything else pretty easy.
Copy constructors and assignment operators:

It you do not write either of these procedures, C++ will do so for you automatically.  They way it does so is clever and works in 99% off the cases.  In other words, it causes a dissaster 1% of the time.  (99% of all statistics are made up.)

For a copy constructor, C++ compiler creates a copy constructor that just calls the copy constructors one each data member in the class  (some of those copy cosntructors it might create the same way, others you might create.)  Thus for a class like

class ClassA
{
   int A;
   char B;
   classC C;
}

it does

ClassA:ClassA(const ClassA& Src) :
   A(Src.A);  
   B(Src.B);  
   C(Src.C);  
{
}

Note the the A and B members have the built-in  int and char copy constructors called.  For C, the classC copy constructor will be called.  This could be a copy constructor provided by the programmer or one provided by the compiler just as the ClassA one is.

The = operator works the same way except it calls the = operator on each data member rather than the copy constructor.

That approach works very well for almost all data members.  That is, it does what you want it to do, for almost all data members.  But it may not work the way you want it to for one type of data member--pointers.  Given the class

String
{
   char *StrPtr;
}

If either the default copy constructor or default assignment operator is used on this class it will copy the StrPtr, so that the two objects each have StrPtr's with the same value, in other words, the two objects both have StrPtr's that point to the same string array.  

That is a mistake (in most classes, not all.  But definitly for your string class).  Each string object should have a unique StrPtr that points to its own "private" string buffer (array).  That is, no two string objects should share (both point to) a single string buffer.  If they did, there would be two problems.  1) if one string object changed the string stored in the string array, the other string object would also be changed, that is, the string it represents would "mysteriously" change.  2) when one string object was destroyed it would delete the string buffer.  The other string object would still try to use the string buffer, however, causing a crash.  (That is the crash you had.)    Make sense?

What you need to do instead is provide "custom" versions of these two functions.  These functions do not copy the StrPtr, instead, they copy the array that the string pointer points to.  Big difference!  Thus afterwords, each stirng object has a unique pointer to its own private array.  the two arrays store the same characters, but they are seperate arrays.

Make sense?