Not stuck and no real question, which is unusual for me. Just a comment. The 2nd term in a for loop establishes the condition under which the loop terminates. For example, this loop terminates when i increments to 5...

for(i=0; i<=5; i++)
{
...
...
}

It'll execute its contents 6 times.

The terminating condition, i.e., i=5, seems to be determined one time. However, if a function call is placed in the terminating condition, such as...

for(i=0; i<=this->LenStr(); i++)
{
...
...
}

...then that function call (a class member function, in the above case), is evaluated every iteration of the loop! I simply didn't know that and it amazed me. Any comments???

Yup! glad you are finally figuring that out! Never put a function call there which will always return the same value -- its better and faster to store the value in a variable and use that variable in the loop.

I simply didn't know that and it amazed me.

Why did it amaze you? There are two good options for how to manage function calls in the condition:

  1. Call the function every time the condition is tested.
  2. Call the function once and capture the result, then use the captured result every time the condition is tested.

Both are equally good, but I think the first follows the rule of least surprise. Then again, C++ is good at surprising everybody from day 1. ;)

The way I discovered it was that my code was working too good! I'm not happy when things work too good. I prefer things to work badly at first. Then I gradually find out what's wrong and fix it. I'm more comfortable with that. When things work too good I know I'm in for trouble somewhere!

Anyway, what I was doing was working on my 'own' asciiz string class. I wanted a member function to trim whitespace from the front of a String. What I decided to do was start at the beginning of the asciiz buffer at offset 0 and count white space chars up to the first printable char. Then run a for loop starting at the first printable char and offsetting the characters back to the beginning. So if I have the 1st letter of my name with 5 spaces in front of it like so...

s1="     F"

...I'd have a String with a length of 6 and there would be a NULL after the 6th char, i.e., the 'F'. In the String class the only member variable is a char* pStrBuffer. There is a member function to return its length...

int String::LenStr()
{
 return strlen(this->pStrBuffer);
}

And my LTrim() looked like this...

String& String::LTrim()
{
 unsigned int i,iCt=0;

 for(i=0;i<this->LenStr();i++)                //Start at beginning of String
 {                                            //and count # of white space
     if(pStrBuffer[i]==32||pStrBuffer[i]==9)  //chars up to 1st printable
        iCt++;                                //char.
     else
        break;
 }
 for(i=iCt;i<=this->LenStr();i++)             //bump printable chars up iCt
     pStrBuffer[i-iCt]=pStrBuffer[i];         //bytes.
 
 return *this;
}

The thing that had me worried about this was that I figgurred the for loop would run beyond the NULL at the 7th position in the 6 char array, and I'd have to figure out how to stop it sooner. The extra characters copied beyond the null wouldn't be a part of the string (because they would be on the other side of the null), but nontheless, I didn't think it would be a good idea to 'read' them. To my extreme surprise I found out that the above code in LTrim() was only copying the 'F' to offset 0 and the NULL to offset 1 and stopping - exactly what you would want to happen. The question remained though; "Why wasn't it running for 6 iterations - this->LenStr()?"

The answer of course is that it was evaluating the length of the string at every iteration, and after putting the 'F' at offset zero and the NULL (which was at offset 7) at offset 1, the new length of the string ( LenStr() ) was now only one so it stopped! So I had a condition where my various machinations within the loop were affecting the terminating condition. It sure worked neat but gave me a bad feeling.

If I make an int such as...

int iLenStr;

and do this...

iLenStr = this->LenStr();

and do the for loop this way...

for(i=iCt;i<=iLenStr;i++)
        pStrBuffer[i-iCt]=pStrBuffer[i];

...then the LenStr() isn't called every time the loop iterates, and the function will indeed iterate beyond the point where the NULL is copied to offset 1. It doesn't seem to hurt anything - but it still gives me a bad feeling. I'm not sure if I ought to leave the function call in the for loop, or complicate the function further by establishing logic to prevent it from copying anymore characters beyond the null at the end of the original string.

Both are equally good ...

Sorry, Tom, but they are NOT both equally good. That is one reason so many programs are sluggish or slow today because coders are careless about program optimization.

In many cases of calling inline functions, you are right that it may not really matter because the compiler's optimizer will probably toss out the function call and replace it with a variable.

Edited 7 Years Ago by Ancient Dragon: n/a

No, I take that back. Just ran a test. Either way it only iterates twice. Here is the output without the function call in the for loop...

s1.lpStr()  =      F
  Called String::LenStr()
s1.LenStr() = 6

Entering String::LTrim()
  Called String::LenStr()
  iCt = 5
  Called String::LenStr()
  5     F       F
  6
  pStfBuffer = F
  F     70
  &pStrBuffer[0] = 4260080
Leaving String::LTrim()

s1.lpStr()  = F
  Called String::LenStr()
s1.LenStr() = 1

Could you enumerate the reasons why you think so?

It should be quite obvious why. Example I have seen in working production code.

char buf[] = "Hello World";
// This calls strlen() multiple times
for(int i = 0; i < strlen(buf); i++)
{

}

// This calls strlen() only once
int len = strlen(buf);
for()int i = 0; i < len; i++)
{

}

I'm glad you guys are enjoying the topic as much as I am! Here is the actual code I'm working with if you want to play with it. Its just enough of my string class to test out my LTrim() member. In my real class I have all the 'Basic Like' String handling functions such as Mid(), Right(), Left(), InStr(), etc., etc.

I've commented out several usable versions of the LTrim() member that could be easily played around with...

//Main.cpp
#include  <stdlib.h>
#include  <stdio.h>
#include  <string.h>

class String
{
 public:
 String();                       //Uninitialized Constructor - Does nothing
 ~String();                      //Destructor
 String& operator=(const char*); //For assigning a literal or null terminated character string to object
 String(char* pStr);             //Constructor with initialization asciiz character array
 unsigned int LenStr();          //String Length
 char* lpStr();                  //Pointer To Beginning Of Null Terminated String
 String& LTrim();                //Trims leading spaces or tabs from beginning of String

 private:
 char*         pStrBuffer;
};


String::String()                 //Uninitialized Constructor - Does nothing
{
 pStrBuffer=NULL;
}


String::String(char* pStr)       //Constructor with initialization asciiz character array
{
 pStrBuffer=new char[strlen(pStr)+1];
 strcpy(pStrBuffer,pStr);
}


String& String::operator=(const char* pStr)
{
 if(this->pStrBuffer)
    delete [] pStrBuffer;
 pStrBuffer=new char[strlen(pStr)+1];
 strcpy(pStrBuffer,pStr);

 return *this;
}


String::~String()             //Destructor
{
 if(pStrBuffer)               //Don't try to [] delete a NULL pointer!
    delete [] pStrBuffer;
}


unsigned int String::LenStr(void)
{
 puts("  Called String::LenStr()");
 return strlen(this->pStrBuffer);
}


char* String::lpStr()
{
 return pStrBuffer;
}


String& String::LTrim()
{
 unsigned int i,iCt=0,iLenStr=0;
 
 printf("\nEntering String::LTrim()\n");
 iLenStr=this->LenStr();
 for(i=0;i<iLenStr;i++)
 {
     if(pStrBuffer[i]==32||pStrBuffer[i]==9)
        iCt++;
     else
        break;
 }
 printf("  iCt = %u\n",iCt);
 iLenStr=this->LenStr();
 for(i=iCt;i<=iLenStr;i++)
 {
     pStrBuffer[i-iCt]=pStrBuffer[i];
     printf("  %u\t%c\t%c\n",i,pStrBuffer[i-iCt],pStrBuffer[i]);
 }
 printf("  pStfBuffer = %s\n",pStrBuffer);
 printf("  %c\t%d\n",pStrBuffer[0],pStrBuffer[0]);
 printf("  &pStrBuffer[0] = %u\n",&pStrBuffer[0]);
 printf("Leaving String::LTrim()\n\n");

 return *this;
}

/*
String& String::LTrim()
{
 unsigned int i,iCt=0;

 printf("\nEntering String::LTrim()\n");
 for(i=0;i<this->LenStr();i++)
 {
     if(pStrBuffer[i]==32||pStrBuffer[i]==9)
        iCt++;
     else
        break;
 }
 printf("  iCt = %u\n",iCt);
 for(i=iCt;i<=this->LenStr();i++)
 {
     pStrBuffer[i-iCt]=pStrBuffer[i];
     printf("  %u\t%c\t%c\n",i,pStrBuffer[i-iCt],pStrBuffer[i]);
 }
 printf("  pStfBuffer = %s\n",pStrBuffer);
 printf("  %c\t%d\n",pStrBuffer[0],pStrBuffer[0]);
 printf("  &pStrBuffer[0] = %p\n",&pStrBuffer[0]);
 printf("Leaving String::LTrim()\n\n");

 return *this;
}
*/

/*
String& String::LTrim()
{
 unsigned int i,iCt=0;

 for(i=0;i<this->LenStr();i++)                //Start at beginning of String
 {                                            //and count # of white space
     if(pStrBuffer[i]==32||pStrBuffer[i]==9)  //chars up to 1st printable
        iCt++;                                //char.
     else
        break;
 }
 for(i=iCt;i<=this->LenStr();i++)             //bump printable chars up iCt
     pStrBuffer[i-iCt]=pStrBuffer[i];         //bytes.
 
 return *this;
}
*/


int main(void)
{
 String s1;

 s1="     F";
 printf("s1.lpStr()  = %s\n",s1.lpStr());
 printf("s1.LenStr() = %u\n",s1.LenStr());
 s1.LTrim();
 printf("s1.lpStr()  = %s\n",s1.lpStr());
 printf("s1.LenStr() = %u\n",s1.LenStr());
 getchar();

 return 0;
}

It should be quite obvious why. Example I have seen in working production code.

I think we might be talking about something different. I was describing the possible ways function calls in a loop condition might be interpreted by C++ if the language were designed differently. The point was that the way C++ works now is not necessarily obvious, and a beginner could be surprised with the behavior of function calls in loops if he assumed C++ worked a different, but equally viable way.

Edited 7 Years Ago by Tom Gunn: n/a

Frederick: In the destructor it is not necessary to check for a NULL pointer -- the delete[] operator works correctly when given a null pointer.

In the constructor: suggest you add the const keyword to the parameter, e.g. String::String(const char* pStr) LenStr: Suggest this be an inline function so that the compiler can optimize it better.

lpStr: ditto as above

Frederick: In the destructor it is not necessary to check for a NULL pointer -- the delete[] operator works correctly when given a null pointer.

In the constructor: suggest you add the const keyword to the parameter, e.g. String::String(const char* pStr) LenStr: Suggest this be an inline function so that the compiler can optimize it better.

lpStr: ditto as above

Thanks AncientDragon. I'll make especial note of that. I wasn't aware about the delete [] not having a problem with null pointers.

Fred

This article has been dead for over six months. Start a new discussion instead.