Author Topic: An example of coding a C++ class MyString ...  (Read 26120 times)

Offline David

  • Hero Member
  • *****
  • Posts: 647
    • View Profile
An example of coding a C++ class MyString ...
« on: February 05, 2016, 04:26:22 PM »



An other fairly often student problem ...

is to code a class to take in and handle dynamic strings in C++,

perhaps, (under the hood),  using arrays of char that are terminated by '\0' ( i.e. C strings ) ...


The following example may give you some ideas how to start ...


Firstly the .h file:

Code: [Select]
// MyString.h //  // 2016-02-05 //


#ifndef MYSTRING_H
#define MYSTRING_H


#include <iostream>
#include <cstring> // re. strlen, etc...
#include <cctype> // re. tolower
#include <cmath> // re. log10
#include <vector>


// a start to a NULL terminated C++ string class //
struct MyString // You can, the way coded here, just replace struct with class ... MAKES NO difference HERE !!! //
{
public:
    // if neg num's stored as 'twos complement' ...
    // this next value is the largest size_t possible //
    static const size_t npos = -1;
   
    static const size_t chunk_size = 255;

    // 4 ctor's follow ...

    // default ...
    MyString() : len(0), str( new char[1] )
    {
        str[0] = 0;
    }

    // from C string ...
    MyString( const char* s )
    {
        len = strlen(s);
        str = new char[len+1];
        strncpy( str, s, len );
        str[len] = 0; // terminate with  the (NULL C) '\0' char //
    }

    //
    MyString( const size_t num, const char ch )
    {
        len = num;
        str = new char[len+1];
        for( size_t i = 0; i < len; ++ i ) str[i] = ch;
        str[len] = 0; // terminate with  the (NULL C) '\0' char //
    }

    // Copy ctor
    MyString( const MyString& s )
    {
        len = s.len;
        str = new char[len+1];
        strncpy( str, s.str, len );
        str[len] = 0; // terminate with  the (NULL C) '\0' char //
    }

    // overloaded assignment i.e. overloaded operator = ...
    MyString& operator = ( const MyString& s )
    {
        if( this != &s )
        {
            delete [] str; // firstly delete (free up) OLD memopry

            len = s.len;
            str = new char[len+1];
            strncpy( str, s.str, len );
            str[len] = 0; // terminate with  the (NULL C) '\0' char //
        }
        return *this;
    }

    // overloaded operator += ...
    MyString& operator += ( const MyString& s )
    {
        size_t n_len = len + s.len;
        char* tmp = new char[n_len+1];
        strncpy( tmp, str, len );
        strncpy( tmp+len, s.str, s.len );
        tmp[n_len] = 0; // terminate with '\0' char //

        delete str; // delete old memory //
        str = tmp; // update
        len = n_len;

        return *this;
    }
   
    // overloaded operator += ...
    MyString& operator += ( const char ch )
    {
        char* tmp = new char[len+2];
        strncpy( tmp, str, len );
        tmp[len] = ch;
        ++len;
        tmp[len] = 0; // terminate with '\0' char //

        delete str; // delete old memory //
        str = tmp; // update

        return *this;
    }

    // overloaded operator +...
    friend MyString operator + ( const MyString& a, const MyString& b )
    {
        MyString tmp(a);
        return  tmp += b;
    }
   
    friend MyString operator + ( const char ch, const MyString& b )
    {
        MyString tmp( 1, ch );
        return  tmp + b;
    }
    friend MyString operator + ( const MyString& a, const char ch )
    {
        MyString tmp( 1, ch );
        return  a + tmp;
    }

    // overloaded operator []
    char operator [] ( const size_t i ) const
    {
        return str[i];
    }

    // overloaded operator []
    char& operator [] ( const size_t i )
    {
        return str[i];
    }

    char* c_str() const
    {
        return str;
    }
    char*& c_str()
    {
        return str;
    }

    size_t find( const char c, const size_t start = 0 )
    {
        for( size_t i = start; i < len; ++ i )
        {
            if( str[i] == c ) return i;
        }
        return npos;
    }

    MyString& erase( const size_t start, const size_t num = npos )
    {
        if( start > len )
            return *this;

        size_t numErase = num;
        if( numErase > len - start )
            numErase = len - start;

        size_t n_len = len - numErase;
        char* tmp = new char[n_len+1];

        if( start ) // if ( start > 0 ) //
        {
            strncpy( tmp, str, start );
            tmp[start] = 0; // 'NULL terminate' //
            if( numErase < len - start )
                strcat( tmp, str+start+numErase );
        }
        else // here ... start is 0 //
        {
            strncpy( tmp, str + numErase, len - numErase );
            tmp[n_len] = 0;
        }

        delete [] str;
        str = tmp;
        len = n_len;

        return *this;
    }
   
   
    MyString substr( const size_t start, const size_t num = npos )
    {
        MyString tmp;
       
        if( start > len )
            return tmp;

        tmp.len = num;
        if( tmp.len > len - start )
            tmp.len = len - start;
           
        delete [] tmp.str;
        tmp.str = new char[tmp.len+1];

        strncpy( tmp.str, str+start, tmp.len );
        tmp.str[tmp.len] = 0; // 'NULL terminate' //

        return tmp;
    }
   
   
    ~MyString() // destructor
    {
        delete [] str;
        str = 0;
        len = 0;
    }

    void clear()
    {
        delete [] str;
        str = new char[1];
        str[0] = 0;
        len = 0;
    }

    size_t size() const
    {
        return len;
    }

private:
    size_t len;
    char* str;

    friend std::ostream& operator << ( std::ostream& os, const MyString& ms )
    {
        return os << ms.str;
    }

    friend std::istream& operator >> ( std::istream& is, MyString& ms )
    {
        size_t cap = MyString::chunk_size, size = 0; // c to hold each char
        char c;
        char* strData = new char[cap+1];
       
       
        while( is.get(c)  &&  strchr(" \t\n", c) )
            ; // advance over all leading (ws) delimit char's //

        if( !is.eof() ) // ok ... get this first char ... //
            strData[size++] = c;
        else
        {
            delete [] ms.str;
            delete [] strData;
            ms.str = new char[1];
            ms.str[0] = 0;
            ms.len = 0;

            return is;
        }


        while( is.get(c)  &&  !strchr(" \t\n", c) )
        {
            if( size == cap )
            {
                cap += cap; // double memory capacity
                char* tmp = new char[cap+1];
                for( size_t i = 0; i < size; ++ i )
                    tmp[i] = strData[i];
                tmp[size] = 0;
                delete [] strData;
                strData = tmp;
            }

            // now ... since reached here ...
            strData[size++] = c; // append this char //
        }
       
        strData[size] = 0; // ensure terminated with 0 //

        if( cap > size ) // 'right' size string //
        {
            char* tmp = new char[size+1];
            for( size_t i = 0; i < size; ++ i )
                tmp[i] = strData[i];
            tmp[size] = 0;
            delete [] strData;
            strData = tmp;
        }
        delete [] ms.str;
        ms.str = strData;
        ms.len = size;

        return is;
    }

    friend std::istream& getline( std::istream& is, MyString& ms, const char* delimits = "\n" )
    {
size_t cap = MyString::chunk_size, size = 0; // c to hold each char
        char c;
        char* strData = new char[cap+1];

        while( is.get(c)  &&  !strchr( delimits, c ) )
        {
            if( size == cap )
            {
                cap += cap; // double memory capacity
                char* tmp = new char[cap+1];
                for( size_t i = 0; i < size; ++ i )
                    tmp[i] = strData[i];
                tmp[size] = 0;
                delete [] strData;
                strData = tmp;
            }

            // now ... since reached here ...
            strData[size++] = c; // append this char //
        }
       
        strData[size] = 0; // ensure terminated with 0 //

        if( cap > size ) // 'right' size string //
        {
            char* tmp = new char[size+1];
            for( size_t i = 0; i < size; ++ i )
                tmp[i] = strData[i];
            tmp[size] = 0;
            delete [] strData;
            strData = tmp;
        }
        delete [] ms.str;
        ms.str = strData;
        ms.len = size;

        return is;
    }
   
    void friend split( std::vector< MyString >& vms, const MyString& ms, const char* delimits = " \t" )
    {
        size_t len = ms.len, i = 0, j = 0;
       
        while( i < len )
        {
            // skip over any leading delimits char's ...
            while( strchr( delimits, ms.str[i] ) )
                ++ i;
            if( i == len ) return; // ALL delimits //

            j = i;

            // find j 'one past' endof 'word' ...
            while( j < len && !strchr( delimits, ms.str[j] ) )
                ++ j;

            MyString tmp( j-i, ' ' ); // get right sixed empty MyString
            strncpy( tmp.str, ms.str+i, j-i );
           
            vms.push_back( tmp ); // add this word to end of vector //

            i = j+1;
        }
    }
} ;



// EIGHT utilities for the above MyString type ... //

MyString takeInMyString( const MyString& prompt = "" )
{
    std::cout << prompt << std::flush;
    MyString s;
    getline( std::cin, s );
    return s;
}

int takeInChr( const MyString& prompt = "" )
{
    return takeInMyString( prompt )[0];
}
// defaukts to 'y' i.e. YES //
bool more( const MyString& msg_part = "" )
{
    std::cout << "More " + msg_part;
    return tolower( takeInChr( " (y/n) ? " )) != 'n';
}

MyString& toCapsOnAllFirstLetters( MyString& str )
{
    // to get CAPS on first char ...
    int prev_was_ws = 1; // start with previous was white space
    size_t len = str.size();
    for( size_t i = 0; i < len; ++ i )
    {
        if( prev_was_ws )
            str[i] = toupper( str[i] );
        prev_was_ws = isspace( str[i] );
    }
    return str;
}

MyString& toUpper( MyString& str )
{
    size_t len = str.size();
    for( size_t i = 0; i < len; ++ i )
        str[i] = toupper( str[i] );
    return str;
}
MyString& toLower( MyString& str )
{
    size_t len = str.size();
    for( size_t i = 0; i < len; ++ i )
        str[i] = tolower( str[i] );
    return str;
}

// NO ERROR checking done here !!!
// The assumption here IS that the string passed in ...
// IS VALID ... for the type requested !!! //

// Note!!! Does NOT handle scientific notation !!! //
template< typename T >
T toNumber( const MyString& val )
{
    MyString str( val );
    char sign = '+';
    if( str[0] == '-' )
    {
        sign = '-';
        str.erase(0, 1);
    }
    else if(str[0] == '+')
        str.erase(0, 1);
       
    MyString dec_part;
    double dec = 0.0;
   
    size_t pos = str.find('.');
    if( pos != MyString::npos )
    {
        dec_part = str.substr(pos+1);
        str = str.erase(pos);
        //std::cin.get();
        double divisor = 1;
        size_t len = dec_part.size();
        for( size_t i = 0; i < len; ++ i )
        {
            divisor *= 10;
            dec += (dec_part[i] - '0')/divisor;
        }
    }

    size_t len = str.size();
    T num = 0;
    for( size_t i = 0; i < len; ++ i )
        num = 10*num + str[i] - '0';
       
       
    if( dec_part.size() )
        num += dec;
       
    if( sign == '-' )
        num *= -1;
       
    // else ...
    return num;
}



// this handles conversion from NON-NEGATIVE INTEGER NUMBERS
// to a comma formatted string //
template< typename T >
MyString commasAdded( T val ) // val is a local copy //
{
    int num_digits = (int) log10((double)val) +1;
    MyString tmp( num_digits, '0' ); // get string correct size //

    //std::cout << "Num digits is " << num_digits << '\n';

    int i = num_digits-1; // start from end and work to front //
    do
    {
        int digit = val % 10;
        tmp[i] = '0' + digit;
        val /= 10;
        --i;
    }
    while( val );


    int comma = 0,
    len = tmp.size();

    // note integer division here: 2/3 = 0 & 3/3 = 1
    int len2 = len + (len-1)/3;

    // construct right size and filled with commas
    MyString tmp2( len2, ',' );

    while( len2 ) // start at end of string of commas ... then ...
    {
        --len, --len2;
        ++comma;         // this tracks where we are //

        if( comma != 4 ) // keep copying until reach the next comma to skip over //
            tmp2[len2] = tmp[len];

        else // ha ... we want to LEAVE this comma here //
        {
            comma = 1; // reset counter
            //tmp2[len2] = ','; // this comma STAYs NOT over-ridden //
            --len2; // so skip over //
            tmp2[len2] = tmp[len]; // ok ... copy in/(and over commas) this digit
        }
    }

    return tmp2; // return it //
}


#endif
« Last Edit: February 05, 2016, 07:00:22 PM by David »

Offline David

  • Hero Member
  • *****
  • Posts: 647
    • View Profile
Re: An example of coding a C++ class MyString ...
« Reply #1 on: February 05, 2016, 04:29:39 PM »
Now here is a little test program to start to test out the class MyString ...


Code: [Select]
// testing_MyString.cpp //  // 2016-02-05 //

// a demo of loops, arrays of char, new and delete (for arrays of char)

// recall that C strings ARE arrays of char terminated by a 'NULL char'
// i.e. arrays of char ... terminated by '\0'


#include "MyString.h"


#include <fstream>
#include <iomanip> // re. precision ...

////////////////////////////////////////////////////////////
// only used here to see AND to compare
// sizeof C++ string object
// with ...
// sizeof MyString object
#include <string>
using std::string;
////////////////////////////////////////////////////////////

const char* FNAME = "words.txt";
// just a few words as per below to test reading from file //
/*
sam joe bill harry ann jill bonnie
*/


int main()
{
    using std::cout;
    using std::endl;
    using std::vector;

    const size_t num_bytes = sizeof(size_t);

    cout << "Number of bytes in type size_t is " << num_bytes  << '\n';

    // form the largest number that fits into a memory block with size of type size_t ...
    size_t t = 1;
    for( size_t i = 1; i < num_bytes*8; ++ i )
        t = (t << 1) + 1;  // shift left 1 byte and then add in 1 //

    cout << "Largest size_t = " << commasAdded( t ) << '\n';
    cout << "MyString::npos = " << commasAdded( MyString::npos ) << "\n\n";
   
   
    cout << "sizeof(MyString)  = " << sizeof(MyString) << '\n'
         << "sizeof(MyString*) = " << sizeof(MyString*) << '\n'
         << "sizeof(string)    = " << sizeof(string) << '\n'
         << "sizeof(string*)   = " << sizeof(string*)
         << "\n\n";
         
    cout << "\nTesting toNumber(12345.67890) ...\n";
   
    cout << std::setprecision(16);
   
    double nVal = toNumber< double >( "12345.67890" );
    cout << nVal<< "\n\n";

    MyString s1, s2(" is working very diligently at U!" );
    cout << "'" << s1 << "'" << " has len " << s1.size() << '\n';
    cout << "'" << s2 << "'" << " has len " << s2.size() << '\n';

    s1 = "Sam";
    cout << "'" << s1 << "'" << " has len " << s1.size() << '\n';

    s1 += s2;
    cout << "'" << s1 << "'" << " has len " << s1.size() << '\n';

    MyString s3( s1 + " True!!!" );
    cout << "'" << s3 << "'" << " has len " << s3.size() << '\n';

    for( size_t i = 0; i < s3.size(); ++ i ) cout << s3[i];

    cout << endl;

    size_t i = s3.find( 'w' );
    if( i != MyString::npos )
    {
        s3[i]   = 'l';
        s3[i+1] = 'u';
        s3.erase( s3.size() - 7 );
        s3 += "Ha! Haaahhh!!!";
        for( size_t i = 0; i < s3.size(); ++ i ) cout << s3[i];
    }
   
    cout << "\nTesting using getline with split (splitting up line) ...\n";
    MyString test_line = takeInMyString( "Enter a line of text: " );
    vector< MyString > vms;
    split( vms, test_line );

    for( size_t i = 0; i < vms.size(); ++ i )
        cout << "'" << vms[i] << "' ";
    cout << endl;

   
    cout << "\n\nTesting reading file with 7 word using fin >> MyStringVar in a loop ... \n";
    std::ifstream fin( FNAME );
    int count = 0;
    if( fin )
    {
        MyString ms;
        while( fin >> ms )
        {
            ++count;
            cout << " (" << count << ") " << ms;
        }
           
        fin.close();
        cout << endl;
    }
else cout << "Ther was a problem opening file " << FNAME << '\n';

    cout << "\n\n";
    do
    {
        MyString name = takeInMyString( "Enter ALL your names (include middle name) : " );
        cout << "You entered: \"" << name << "\"\n";
       
        toCapsOnAllFirstLetters( name );
       
        MyString mid;

        // erase 2nd word of more than 2 words ... IFF exist //
        size_t i = name.find( ' ' );
        if( i != MyString::npos )
        {
            size_t j = name.find( ' ', i+1 ); // start at i+1 //
            if( j != MyString::npos )
            {
                mid = name.substr( i+1, j-i );
                name.erase( i, j-i );
            }
        }

        name = "Hi there, " + name + '!';
        cout << name << '\n';
        if( mid.size() )
            cout << "Your middle name is " << mid << '\n';

        // print after erasing 1st 3 char's ...
        cout << name.erase(0, 3) << '\n';

        // erase next middle word ... IFF exists //
        i = name.find( ',' );
        if( i != MyString::npos )
        {
            size_t j = name.find( ' ', i+2 );
            if( j != MyString::npos )
                name.erase( i, j-i);
        }
        cout << name << '\n';
    }
    while( more( "names" ) );

}
« Last Edit: February 05, 2016, 04:40:46 PM by David »