• Welcome to Jose's Read Only Forum 2023.
 

ProgEx30 - Project Organization In A C++ Project With Classes

Started by Frederick J. Harris, November 09, 2009, 05:08:58 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris


//Main.cpp

/*
  ProgEx30  -  Customary Project Organization Of A C++ Project
 
  Before continuing with our development of a String Class which we started on
  in ProgEx29, lets examine how C++ projects are organized in terms of source
  and header files.
 
  The basic idea is to put equates, function/class prototypes/declarations, and
  user defined types in a header file.  These things don't require storage and
  are used by the compiler at compile time.
 
  Code and variables, that is, the implementations of ordinary functions or
  class member functions go in *.cpp or *.c files.  These entities require
  storage at run time.
 
  Its typical to make a seperate *.h / *.cpp file set for each class.  Since we
  have the beginnings of a String Class we can place the String Class
  Declaration in a file named Strings.h  Note the plural since one of the
  Standard C Runtime Libraries is named string.h.  It looks like this...
 
  class String
  {
   public:
   String();                 //Uninitialized Constructor - Does nothing
   String& operator=(char*); //Operator= for assigning char* to String
   char* lpStr(void);        //Returns Address of String data Member
   ~String();                //String Destructor
 
   private:
   char* pStrBuffer;         //This char* member holds the address of Str buffer
  };
     
  The String.cpp file contains the implementations of the above String members.
  In terms of syntax within that file the scope resolution operator is used to
  identify the function as being a member of the class in question - here our
  String Class.  For example, our lpStr() member will look like so...
 
  char* String::lpStr()
  {
   return this->pStrBuffer;
  }
 
  First comes the return value of the function which is pointer to char.  Then
  comes the name of a class which the compiler should have in its symbol table
  by this time.  And seperating the name of the class and the name of the member
  is the scope resolution operator '::'.  At this time you should examine the
  String.h and String.cpp files to see the pattern.
 
  Typically a program will have a file that contains the program's entry point
  function which is Main.cpp in this example.  If any instances of any classes
  are going to be instantiated in Main.cpp the header for the class needs to be
  included there.  In this example we create an instance of our String Class in
  Main.cpp so that header is included there.
 
  In whatever programming environment you are using you no doubt have some kind
  of class browser/file viewer associated with it.  When you create the project
  you need to include all the source and header files you need.  Typically you
  can do this several ways but the easiest is usually to place the source and
  header files in the project directory then right click in the project viewer
  to bring up a context sensitive menu to add various files.  In this project
  you'll have a Main.cpp, a Strings.cpp and a Strings.h
 
  The way C/C++ compilers work is somewhat different from the PowerBASIC
  compiler in that the compiler first creates *.obj files from the *.cpp files.
  At that point the program's instructions have been converted to binary, but
  the linker needs to run to finally resolve the various addresses and
  references necessary to produce the executable.  If there are any syntax
  errors in the program the compiler will catch them, and the linker will not
  even run.  If the program compiles OK but the linker can't resolve some
  reference then you'll get a linker error.  These are oftentimes more troubling
  than compilation errors.
*/

#include <stdio.h>
#include "Strings.h"

int main(void)
{
String s1;

printf("\n&s1 = %u\n",&s1);
s1="Fred";
printf("s1.lpStr() = %s\n",s1.lpStr());
printf("s1.lpStr() = %u\n",s1.lpStr());
getchar();

return 0;
}

/*
Entering Uninitialized Constructor!
  &this->pStrBuffer = 2293584
  this->pStrBuffer  = 0
Leaving Uninitialized Constructor!

&s1 = 2293584

Entering String& operator=(const char* pStr)
  pStr        = Fred
  pStrBuffer  = 4079448
  this        = 2293584
Leaving String& operator=(const char* pStr)

s1.lpStr() = Fred
s1.lpStr() = 4079448
*/



//Strings.h

/*
  Class Declaration For String Class
*/


class String
{
  public:
  String();                             //Uninitialized Constructor - Does nothing
  String& operator=(char*);             //Operator= for assigning literal or char* to String
  char* lpStr(void);                    //Returns Address of String data Member  >> this->lpStr()
  ~String();                            //String Destructor
 
  private:
  char* pStrBuffer;                     //This char* member holds the address of an allocated String
};




//String.cpp

/*
  Implementation Of String Class
*/

#include  <stdio.h>
#include  <string.h>
#include  "Strings.h"

/*
   *****************************************************************************
   This is an uninitialized String Constructor.  It is called when a declaration
   of a String such as this occurs...
 
   String s1;
 
   All it does assign NULL to the pStrBuffer member.
   *****************************************************************************
*/
String::String()
{
printf("Entering Uninitialized Constructor!\n");
pStrBuffer=NULL;
printf("  &this->pStrBuffer = %u\n",(unsigned int)&this->pStrBuffer);
printf("  this->pStrBuffer  = %u\n",(unsigned int)this->pStrBuffer);
printf("Leaving Uninitialized Constructor!\n");
}


/*
  ******************************************************************************
  This overloaded operator= is called when a String is assigned a char*.  In
  other words, something like this...
 
  String s1;
 
  s1="Hello, World!" 
 
  or
 
  char* pStr="Hello, World!";
  s1=pStr;
 
  The 1st thing the function does is check to see if this->pStrBuffer isn't
  NULL.  If it isn't then the String is holding some previous String the user
  doesn't want anymore.  In that case delete [] is called on the String.  In any
  case a new buffer is then allocated of a length equal to the length of the
  char* pStr passed in plus an extra byte for the null terminator.  Then the
  passed in parameter string is copied to the newly acquired buffer.  While this
  points to a String, *this is a String, and now since the char* has been
  transfered to the String's storage, we simply return *this.
  ******************************************************************************
*/ 

String& String::operator=(char* pStr)
{
printf("\nEntering String& operator=(const char* pStr)\n");
printf("  pStr        = %s\n",pStr);
if(this->pStrBuffer)
    delete pStrBuffer;
pStrBuffer=new char[strlen(pStr)+1];
printf("  pStrBuffer  = %u\n",pStrBuffer);
strcpy(pStrBuffer,pStr);
printf("  this        = %u\n",this);
printf("Leaving String& operator=(const char* pStr)\n\n");
   
return *this;
}

/*
   *****************************************************************************
   This function simply returns the pStrBuffer private char* address of the
   String.  It is useful for example in outputing the string to a file or
   display with the printf function.
   *****************************************************************************
*/
char* String::lpStr()
{
return this->pStrBuffer;
}

/*
   *****************************************************************************
   This is the String Destructor.  It is called when a String is being
   destroyed.  In such cases it is considered good practice to set any class
   buffers to 0 so the same address does't accidentally get deleted twice. 
   Calling delete on a NULL pointer is OK; calling it twice on a non null
   pointer isn't OK, because there may be something else at that address.
   *****************************************************************************
*/
String::~String()    //String Destructor
{
delete pStrBuffer;
pStrBuffer=NULL;
}