Print service provided by iDogiCat: http://www.idogicat.com/
home logo





Home > IT > Programming > C++ Programming > iDog's Interview C++ > iDog's Interview C++ Part 7

iDog's Interview C++ Part 7

Exercise - String class

Exercise: write a String class.

Write header file:


// File:   String.h
// Author: iDog
// Date:   Oct 10, 2005

#ifndef _MY_STRING_H
#define _MY_STRING_H

#include <iostream>
#include <string>

class String;

std::ostream& operator<<(std::ostream&, const String&);
std::istream& operator>>(std::istream&, String&);

String operator+(const String&, const String&);
bool operator==(const String&, const String&);
bool operator==(const String&, const char*);
bool operator==(const char*, const String&);

class String
{
private:
    size_t m_nSize;
    char* m_pszData;

public:
    String(const String&);
    String(const char* = NULL);
    ~String();

    String& operator=(const String&);
    String& operator=(const char*);

    const char& operator[](size_t) const;
    char& operator[](size_t);

    String& operator+=(const String&);
    String& operator+=(const char*);

    operator std::string() const;

    size_t size() const {return m_nSize;}
    const char* c_str() const {return m_pszData;}
};

#endif // _MY_STRING_H

When an overloaded operator is a class member, the class member operator is only invoked when the operator is used with a left operand that is an object of class type. If the operator must be used with a left operand of another type, then the overloaded operator must be a namespace function, in which case, compiler does the conversion.

So following "==" operators


bool String::operator==(const char*) const;
bool String::operator==(const String&) const;
can handle following cases

if(str1 == "mystring") {} // call the first "==" operator
if(str1 == str2) {} // call the second "==" operator
but they cannot handle following case:

if("mystring" == str1) {}

To solve this, we can replace these member functions by following global function:


bool operator==(const String&, const String&);
It handles both of the above cases in following way:

operator==(str1, String("mystring"))
operator==(String("mystring"), str1)

Note that this kind of type conversion is only done for global functions, so memeber functions won't work this way.

But converting const char* to const String& has run-time costs. If we need to make it much faster, we should add following two overloaded == operator functions:


bool operator==(const String&, const char*);
bool operator==(const char*, const String&);

Implementation:


bool operator==(const String& str1, const String& str2)
{
    if(str1.size() != str2.size())  return false;
    return strcmp(str1.c_str(), str2.c_str())? false : true;
}

inline bool operator==(const String& str, const char* pszStr)
{
    return strcmp(str.c_str(), pszStr)? false : true;
}

inline bool operator==(const char* pszStr, const String& str)
{
    return (str == pszStr);
}

The >> and << operators cannot be writen as member functions of String class, because they can only be member functions of istream and ostream. Thus we declare them as global functions, too.

Whole implementation of this class:


// File:   String.cpp
// Author: iDog
// Date:   Oct 10, 2005

#include &lt;cstring>
#include "String.h"


/////////////////////////////////////////
//// Global functions
/////////////////////////////////////////


std::ostream& operator&lt;&lt;(std::ostream& os, const String& str)
{
    os &lt;&lt; str.c_str();
    return os;
}

std::istream& operator>>(std::istream& is, String& str)
{
    const int BUFFER_SIZE = 256;
    int nCurrSize = BUFFER_SIZE;
    int nCounter = 0;
    char* pszBuffer = new char[nCurrSize + 1];
    *pszBuffer = '\0';
    char c = is.get();

    while(c != EOF && c != '\n') {
        if(nCurrSize &lt;= nCounter) {
            nCurrSize += BUFFER_SIZE; // increase size of buffer
            char* pszNewBuffer = new char[nCurrSize + 1];

            if(pszBuffer) { // not empty, then copy original contents
                pszBuffer[nCurrSize] = '\0';
                strcpy(pszNewBuffer, pszBuffer);
                delete[] pszBuffer;
            }

            pszBuffer = pszNewBuffer;
        }

        pszBuffer[nCounter++] = c;
        c = is.get();
    }

    // overwrite existing contents with chars read
    // if append, then call str += pszBuffer
    pszBuffer[nCounter] = '\0';
    str = pszBuffer;
    delete[] pszBuffer;

    return is;
}


bool operator==(const String& str1, const String& str2)
{
    if(str1.size() != str2.size())  return false;

    return strcmp(str1.c_str(), str2.c_str()) ? false : true;
}

inline bool operator==(const String& str, const char* pszStr)
{
    return strcmp(str.c_str(), pszStr)? false : true;
}

inline bool operator==(const char* pszStr, const String& str)
{
    return (str == pszStr);
}


String operator+(const String& str1, const String& str2)
{
    size_t size = str1.size() + str2.size();
    char* pszResult = new char[size + 1];
    strcpy(pszResult, str1.c_str());
    strcat(pszResult, str2.c_str());

    // instead of doing it in the way shown in following two lines
    // we'd better do it in another way to improve performance
    //String strResult(pszResult);
    //delete[] pszResult;
    String strResult;
    strResult.m_pszData = pszResult;
    strResult.m_nSize = size;

    return strResult;
}

/////////////////////////////////////////
//// Implementation of class
/////////////////////////////////////////

String::String(const String& str)
{
    m_nSize = str.m_nSize;
    m_pszData = new char[m_nSize + 1];
    strcpy(m_pszData, str.m_pszData);
}

String::String(const char* pszData)
{
    if(pszData == NULL) {
        m_nSize = 0;
        m_pszData = new char[1];
        *m_pszData = '\0';
    } else {
        m_nSize = strlen(pszData);
        m_pszData = new char[m_nSize + 1];
        strcpy(m_pszData, pszData);
    }
}


String::~String()
{
    assert(m_pszData != NULL);
    delete[] m_pszData;
}

String& String::operator=(const String& str)
{
    if(this != &str) {
        // in order to make the code exception-safe (refer to the next chapter), we should allocate memory first
        // (think of the case that 'new' operation can fail)
        char* pszData = new char[str.m_nSize + 1];
        strcpy(pszData, str.m_pszData);        

        delete[] m_pszData;
        m_nSize = str.m_nSize;
        m_pszData = pszData;
    }
    return *this;
}

String& String::operator=(const char* pszData)
{
    if(m_pszData != pszData) {
        // in order to make the code exception-safe (refer to the next chapter), we should allocate memory first
        // (think of the case that 'new' operation can fail)
        size_t size = strlen(pszData);
        char* pszCopiedData = new char[size + 1];
        strcpy(pszCopiedData, pszData);

        delete[] m_pszData;
        m_nSize = size;
        m_pszData = pszCopiedData;
    }
    return *this;
}


char& String::operator[](size_t i)
{
    assert(i >= 0 && i < m_nSize);

    return m_pszData[i];
}

const char& String::operator[](size_t i) const
{
    assert(i >= 0 && i < m_nSize);

    return m_pszData[i];
}

String& String::operator+=(const String& str)
{
    size_t size = m_nSize + str.m_nSize;
    char* pszResult = new char[size + 1];
    strcpy(pszResult, m_pszData);
    strcat(pszResult, str.m_pszData);
    delete[] m_pszData;
    m_pszData = pszResult;
    m_nSize = size;

    return *this;
}

String& String::operator+=(const char* pszData)
{
    return operator+=(String(pszData));
}

String::operator std::string() const
{
    return std::string(m_pszData);
}