CString Extension


There are innumerable MFC functions that need a CString object as an argument or return a CString object. Often, even if a function needs a char pointer, we still use a CString to hold the string. Although, the CString class has lots of features, it does lack a few very useful string manipulation functions. A case insensitive search funciton is one, a search and replace function is another. Fortunately, implementing these functionality is not very difficult. The CStringEx class given below derives from CString and adds a few useful functions. The CStringEx class does not add any member variables of its own, so that it can be freely interchanged with a CString object.

The constructors in the CStringEx class parallel the constructor in CString and in fact simply chains into the corresponding CString constructor. Some of the CStringEx functions use knowledge of the internal structure of the CString object so there is a small chance that these functions might break if the CString implementation changes.

The functions provided by CStringEx are quite easy to use and fairly simple to understand. The Insert() functions inserts a character or a sub-string within a string. The result is similar to inserting text in an edit control, the string is expanded to accommodate the sub-string. The Delete() function removes a segment from the string and shortens it. The Replace() function removes a sub-string and replaces it with another. Again, the string size is adjusted depending on the size of the sub-string that was removed and the size of the sub-string that was inserted.

The find family of functions, finds a sub-string in the forward or the reverse direction. The NoCase version of these functions are case insensitive. The FindReplace() and FindReplaceNoCase() functions searches for a sub-string and replaces the matching sub-string with another string. The GetField() and GetDelimitedField() functions find a token in the string. The table below exemplifies the uses of these functions.

String

Function

Result

xyz

Insert(1, a)

xayz

xyz

Insert(1, "ab")

xabyz

xyz

Insert(4, "ab")

xyz ab

Abcde

Delete(2,2)

Abe

Abcde

Replace(2,2,"xyz")

Abxyze

Abc

Find(b)

1

Abcabc

Find(b, 2)

4

AbcAbc

FindNoCase("aBC", 1)

3

AbcAbc

FindReplace("Ab","Xy")

XycXyc

AbcAbc

FindReplace("Ab","Xy", FALSE)

XycAbc

AbcAbc

FindReplaceNoCase("ab","Xy")

XycXyc

AbcAbc

FindReplaceNoCase("ab","Xy", FALSE)

XycAbc

AbcAbc

ReverseFind("bc")

4

AbcAbc

ReverseFindNoCase("abc")

3

AbcAbc

ReverseFindNoCase("abc", 2)

0

AbcAbc

GetField( :, 0 )

AbcAbc

AbcAbc

GetField( :, 1 )

(Blank string)

Abc:Def

GetField( :, 0 )

Abc

Abc:Def

GetField( :, 1 )

Def

Abc:Def

GetField( :, 2 )

(Blank string)

Name: Zafir

GetField("Name: ", 1)

Zafir

(415) 555-7777

GetDelimitedField("(", ")", 0 )

415

//////////////////////////////////////////////////////////////////////
// StringEx.h
//

#ifndef __STRINGEX_H_
#define __STRINGEX_H_

class CStringEx : public CString
{
public:
	CStringEx() : CString( ){};
	CStringEx( const CString& stringSrc) : CString( stringSrc ){};
	CStringEx( const CStringEx& stringSrc) : CString( stringSrc ){};
	CStringEx( TCHAR ch, int nRepeat = 1 ) : CString( ch, nRepeat ){};
	CStringEx( LPCTSTR lpch, int nLength ) : CString( lpch, nLength ){};
	CStringEx( const unsigned char* psz ) : CString( psz ){};
	CStringEx( LPCWSTR lpsz ) : CString( lpsz ){};
	CStringEx( LPCSTR lpsz ) : CString( lpsz ){};

	CStringEx& Insert(int pos, LPCTSTR s);
	CStringEx& Insert(int pos, TCHAR c);

	CStringEx& Delete(int pos, int len);
	CStringEx& Replace(int pos, int len, LPCTSTR s);

	int Find( TCHAR ch, int startpos = 0 ) const;
	int Find( LPCTSTR lpszSub, int startpos = 0 ) const;
	int FindNoCase( TCHAR ch, int startpos = 0 ) const;
	int FindNoCase( LPCTSTR lpszSub, int startpos = 0 ) const;

	int FindReplace( LPCTSTR lpszSub, LPCTSTR lpszReplaceWith, BOOL bGlobal = TRUE );
	int FindReplaceNoCase( LPCTSTR lpszSub, LPCTSTR lpszReplaceWith, 
				BOOL bGlobal = TRUE );

	int ReverseFind( TCHAR ch ) const{ return CString::ReverseFind(ch);};
	int ReverseFind( LPCTSTR lpszSub, int startpos = -1 ) const;
	int ReverseFindNoCase( TCHAR ch, int startpos = -1  ) const;
	int ReverseFindNoCase( LPCTSTR lpszSub, int startpos = -1 ) const;

	CStringEx GetField( LPCTSTR delim, int fieldnum);
	CStringEx GetField( TCHAR delim, int fieldnum);
	int GetFieldCount( LPCTSTR delim );
	int GetFieldCount( TCHAR delim );

	CStringEx GetDelimitedField( LPCTSTR delimStart, LPCTSTR delimEnd, 
				int fieldnum = 0);
};


#endif
/////////////////////////////////////////////////////////////////////





//////////////////////////////////////////////////////////////////////
// StringEx.cpp
//

#include "stdafx.h"
#include "StringEx.h"


// Insert	- Inserts a sub string into the string
// Returns	- Reference to the same string object
// pos		- Position to insert at. Extends the string with spaces if needed
// s		- Sub string to insert
CStringEx& CStringEx::Insert(int pos, LPCTSTR s)
{
	int len = lstrlen(s);
	if ( len == 0 )
		return *this;

	int oldlen = GetLength();
	int newlen = oldlen + len;
	LPTSTR str;
	if ( pos >= oldlen ) 
	{			
		// insert after end of string
		newlen += pos - oldlen ;
		str = GetBuffer( newlen );
		_tcsnset( str+oldlen, _T(' '), pos-oldlen );
		_tcsncpy( str+pos, s, len );
	} 
	else 
	{	
		// normal insert
		str = GetBuffer( newlen );
		memmove( str+pos+len, str+pos, sizeof(_T(' ')) *(oldlen-pos) );
		_tcsncpy( str+pos, s, len );
	}
	ReleaseBuffer( newlen );

	return *this;
}


// Insert	- Inserts a character into the string
// Returns	- Reference to the same string object
// pos		- Position to insert at. Extends the string with spaces if needed
// c		- Char to insert
CStringEx& CStringEx::Insert(int pos, TCHAR c)
{
	TCHAR buf[2];
	buf[0] = c;
	buf[1] = _T('\0');
	return Insert( pos, buf );
}


// Delete	- Deletes a segment of the string and resizes it
// Returns	- Reference to the same string object
// pos		- Position of the string segment to remove
// len		- Number of characters to remove
CStringEx& CStringEx::Delete(int pos, int len)
{
	int strLen = GetLength();

	if( pos >= strLen)
		return *this;
	if(len < 0 ||len > strLen - pos)
		len = strLen - pos;

	LPTSTR str = GetBuffer( strLen );
	memmove(str+pos, str+pos+len, sizeof(_T(' ')) *(strLen-pos));
	ReleaseBuffer( strLen - len );

	return *this;
}


// Replace	- Replaces a substring with another
// Returns	- Reference to the same string object
// pos		- Position of the substring
// len		- Length of substring to be replaced
// s		- New substring
CStringEx& CStringEx::Replace(int pos, int len, LPCTSTR s)
{
	Delete(pos, len);
	Insert(pos, s);

	return *this;
}


// Find 	- Finds the position of a character in a string
// Returns	- A zero-based index
// ch		- Character to look for
// startpos	- Position to start looking from
int CStringEx::Find( TCHAR ch, int startpos /*= 0*/ ) const
{
	// find first single character
	LPTSTR lpsz = _tcschr(m_pchData + startpos, (_TUCHAR)ch);

	// return -1 if not found and index otherwise
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}


// Find 	- Finds the position of a substring in a string
// Returns	- A zero-based index
// lpszSub	- Substring to look for
// startpos	- Position to start looking from
int CStringEx::Find( LPCTSTR lpszSub, int startpos /*= 0*/ ) const
{
	ASSERT(AfxIsValidString(lpszSub, FALSE));

	// find first matching substring
	LPTSTR lpsz = _tcsstr(m_pchData+startpos, lpszSub);

	// return -1 for not found, distance from beginning otherwise
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}


// FindNoCase	- Case insensitive find
// Returns	- A zero-based index
// ch		- Char to search for
// startpos 	- Position to start looking from
int CStringEx::FindNoCase( TCHAR ch, int startpos /*= 0*/ ) const
{
	unsigned int locase = Find( tolower( ch ), startpos );
	unsigned int upcase = Find( toupper( ch ), startpos );

	return locase < upcase ? locase : upcase;
}


// FindNoCase	- Case insensitive find
// Returns	- A zero-based index
// lpszSub	- Substring to search for
// startpos 	- Position to start looking from
int CStringEx::FindNoCase( LPCTSTR lpszSub, int startpos /*= 0*/ ) const
{
	CStringEx sLowerThis = *this;
	sLowerThis.MakeLower();

	CStringEx sLowerSub = lpszSub;
	sLowerSub.MakeLower();

	return sLowerThis.Find( sLowerSub, startpos );
}


// FindReplace		- Find a substring and replace with another
// Returns		- Number of instances replaced
// lpszSub		- Substring to look for
// lpszReplaceWith	- Substring to replace with
// bGlobal		- Flag to indicate whether all occurances 
//					  should be replaced
int CStringEx::FindReplace( LPCTSTR lpszSub, LPCTSTR lpszReplaceWith, 
					BOOL bGlobal /*= TRUE*/ )
{
	int iReplaced = 0;

	// find first matching substring
	LPTSTR lpsz;
	
	int pos = 0;
	int lenSub = lstrlen( lpszSub );
	int lenReplaceWith = lstrlen( lpszReplaceWith );
	while( (lpsz = _tcsstr(m_pchData + pos, lpszSub)) != NULL )
	{
		pos = (int)(lpsz - m_pchData);
		Replace( pos, lenSub, lpszReplaceWith );
		pos += lenReplaceWith;
		iReplaced++;
		if( !bGlobal ) break;
	}

	return iReplaced;
}


// FindReplaceNoCase	- Find a substring and replace with another
//			  Does case insensitive search
// Returns		- Number of instances replaced
// lpszSub		- Substring to look for
// lpszReplaceWith	- Substring to replace with
// bGlobal		- Flag to indicate whether all occurances 
//			  should be replaced
int CStringEx::FindReplaceNoCase( LPCTSTR lpszSub, LPCTSTR lpszReplaceWith, 
					 BOOL bGlobal /*= TRUE*/ )
{
	CStringEx sLowerThis = *this;
	sLowerThis.MakeLower();

	CStringEx sLowerSub = lpszSub;
	sLowerSub.MakeLower();

	int iReplaced = 0;

	// find first matching substring
	LPTSTR lpsz;
	
	int pos = 0, offset = 0;
	int lenSub = lstrlen( lpszSub );
	int lenReplaceWith = lstrlen( lpszReplaceWith );
	while( (lpsz = _tcsstr(sLowerThis.m_pchData + pos, sLowerSub.m_pchData)) != NULL )
	{
		pos = (int)(lpsz - sLowerThis.m_pchData);
		Replace( pos+offset, lenSub, lpszReplaceWith );
		offset += lenReplaceWith - lenSub;
		pos += lenSub;
		iReplaced++;
		if( !bGlobal ) break;
	}

	return iReplaced;
}


// ReverseFind	- Searches for the last match of a substring
// Returns	- A zero-based index
// lpszSub	- Substring to search for
// startpos 	- Position to start looking from, in reverse dir
int CStringEx::ReverseFind( LPCTSTR lpszSub, int startpos /*= -1*/ ) const
{
	int lenSub = lstrlen( lpszSub );
	int len = lstrlen( m_pchData );

	if(0 < lenSub && 0 < len)
	{
		if( startpos == -1 || startpos >= len ) startpos = len - 1;
		for ( LPTSTR lpszReverse = m_pchData + startpos ; 
						lpszReverse != m_pchData ; --lpszReverse)
			if (_tcsncmp(lpszSub, lpszReverse, lenSub ) == 0)
				return (lpszReverse - m_pchData);
	}
	return -1;
}


// ReverseFindNoCase	- Searches for the last match of a substring
//			  Search is case insensitive
// Returns		- A zero-based index
// lpszSub		- Character to search for
// startpos 		- Position to start looking from, in reverse dir
int CStringEx::ReverseFindNoCase(TCHAR ch, int startpos /*= -1*/ ) const
{
	TCHAR a[2];
	a[0] = ch;
	a[1] = 0;
	return ReverseFindNoCase( a, startpos );
}


// ReverseFindNoCase	- Searches for the last match of a substring
//			  Search is case insensitive
// Returns		- A zero-based index
// lpszSub		- Substring to search for
// startpos 		- Position to start looking from, in reverse dir
int CStringEx::ReverseFindNoCase( LPCTSTR lpszSub, int startpos /*= -1*/ ) const
{
	//LPTSTR lpszEnd = m_pchData + lstrlen

	int lenSub = lstrlen( lpszSub );
	int len = lstrlen( m_pchData );
	

	if(0 < lenSub && 0 < len)
	{
		if( startpos == -1 || startpos >= len ) startpos = len - 1;
		for ( LPTSTR lpszReverse = m_pchData + startpos ; 
				lpszReverse >= m_pchData ; --lpszReverse)
			if (_tcsnicmp(lpszSub, lpszReverse, lenSub ) == 0)
				return (lpszReverse - m_pchData);
	}
	return -1;
}


// GetField 	- Gets a delimited field
// Returns	- A CStringEx object that contains a copy of 
//		  the specified field
// delim	- The delimiter string
// fieldnum 	- The field index - zero is the first
// NOTE 	- Returns the whole string for field zero
//		  if delim not found
//		  Returns empty string if # of delim found
//		  is less than fieldnum
CStringEx CStringEx::GetField( LPCTSTR delim, int fieldnum)
{
	LPTSTR lpsz, lpszRemainder = m_pchData, lpszret;
	int retlen, lenDelim = lstrlen( delim );

	while( fieldnum-- >= 0 )
	{
		lpszret = lpszRemainder;
		lpsz = _tcsstr(lpszRemainder, delim);
		if( lpsz )
		{
			// We did find the delim
			retlen = lpsz - lpszRemainder;
			lpszRemainder = lpsz + lenDelim;
		}
		else
		{
			retlen = lstrlen( lpszRemainder );
			lpszRemainder += retlen;	// change to empty string
		}
	}
	return Mid( lpszret - m_pchData, retlen );
}

// GetField 	- Gets a delimited field
// Returns	- A CStringEx object that contains a copy of 
//		  the specified field
// delim	- The delimiter char
// fieldnum 	- The field index - zero is the first
// NOTE 	- Returns the whole string for field zero
//		  if delim not found
//		  Returns empty string if # of delim found
//		  is less than fieldnum
CStringEx CStringEx::GetField( TCHAR delim, int fieldnum)
{
	LPTSTR lpsz, lpszRemainder = m_pchData, lpszret;
	int retlen;

	while( fieldnum-- >= 0 )
	{
		lpszret = lpszRemainder;
		lpsz = _tcschr(lpszRemainder, (_TUCHAR)delim);
		if( lpsz )
		{
			// We did find the delim
			retlen = lpsz - lpszRemainder;
			lpszRemainder = lpsz + 1;
		}
		else
		{
			retlen = lstrlen( lpszRemainder );
			lpszRemainder += retlen;	// change to empty string
		}
	}
	return Mid( lpszret - m_pchData, retlen );
}


// GetFieldCount	- Get number of fields in a string
// Returns		- The number of fields
//			  Is always greater than zero
// delim		- The delimiter character
int CStringEx::GetFieldCount( TCHAR delim )
{
	TCHAR a[2];
	a[0] = delim;
	a[1] = 0;
	return GetFieldCount( a );
}


// GetFieldCount	- Get number of fields in a string
// Returns		- The number of fields
//			  Is always greater than zero
// delim		- The delimiter string
int CStringEx::GetFieldCount( LPCTSTR delim )
{
	LPTSTR lpsz, lpszRemainder = m_pchData;
	int lenDelim = lstrlen( delim );

	int iCount = 1;
	while( (lpsz = _tcsstr(lpszRemainder, delim)) != NULL )
	{
		lpszRemainder = lpsz + lenDelim;
		iCount++;
	}

	return iCount;
}


// GetDelimitedField	- Finds a field delimited on both ends
// Returns		- A CStringEx object that contains a copy of 
//			  the specified field
// delimStart		- Delimiter at the start of the field
// delimEnd 		- Delimiter at the end of the field
CStringEx CStringEx::GetDelimitedField( LPCTSTR delimStart, LPCTSTR delimEnd, int fieldnum /*= 0*/)
{
	LPTSTR lpsz, lpszEnd, lpszRet, lpszRemainder = m_pchData ;
	int lenDelimStart = lstrlen( delimStart ), lenDelimEnd = lstrlen( delimEnd );

	while( fieldnum-- >= 0 )
	{
		lpsz = _tcsstr(lpszRemainder, delimStart);
		if( lpsz )
		{
			// We did find the Start delim
			lpszRet = lpszRemainder = lpsz + lenDelimStart;
			lpszEnd = _tcsstr(lpszRemainder, delimEnd);
			if( lpszEnd == NULL ) return"";
			lpszRemainder = lpsz + lenDelimEnd;
		}
		else return "";
	}
	return Mid( lpszRet - m_pchData, lpszEnd - lpszRet );
}



Comments

  • Very Useful but...

    Posted by rajith_cherian on 04/07/2007 03:26am

    If you could do the same features in basic::string then performance will be much..  much better

    Reply
  • REAL fix for VS7/VS8

    Posted by llllskywalker on 01/14/2007 07:26pm

    I don't like the idea of using #define to fool the compiler -- it seems to me like a hack that could easily bite you in the a$$ later on down the road. i'm using VS8. so to fix CStringEx, i replaced all 22 instances of "m_pchData" with (LPTSTR)GetString(). there were three exceptions in the FindReplaceNoCase() function where i needed to replace something like sLowerThis.m_pchData with (LPTSTR)sLowerThis.GetString(). (same for sLowerSub) after i did that, my CStringEx class was happy once again ;-)

    Reply
  • Fix for MFC v7.xx not working for 7.1

    Posted by LightAvatar on 08/03/2005 07:55am

    It's not working for version 7.1.3088. They probably fixed the "fool the compiler" thing. ".. error C2248: cannot access private member declared in class .."

    • correct fix for VS7/VS8

      Posted by llllskywalker on 01/14/2007 07:23pm

      i agree with caswi.
      
      I don't like the idea of using #define to fool the compiler -- it seems to me like a hack that could easily bite you in the a$$ later on down the road. 
      
      i'm using VS8.
      
      so to fix CStringEx, i replaced all 22 instances of "m_pchData" with (LPTSTR)GetString().  there are a couple of exceptions in the FindReplaceNoCase() function where i needed to replace sLowerThis.m_pchData with (LPTSTR)sLowerThis.GetString().  
      
      after i did that, my CStringEx class was happy once again ;-)

      Reply
    • Fix for MFC v7.1

      Posted by caswi on 01/16/2006 04:33am

      try to replace "m_pchData" with "GetString()"

      Reply
    Reply
  • Code for MFC v7.xx

    Posted by timber on 03/28/2005 05:44pm

    Add this code to the beginning of the cpp file. This will let the VS.net work and still work for earlier versions also.
    
    #include "afxver_.h"
    #if _MFC_VER >= 0x0700
    #define private public
    #define protected public
    #define m_pchData m_pszData
    #endif

    Reply
  • Steve Palmer

    Posted by timber on 03/28/2005 11:20am

    Yes they changed the member name and the way CString works. Here's the 'hack' I found on another web page. Put these three lines at the beginning of the cpp file(before stdafx.h). It fools the complier.
    
    #define private public
    #define protected public
    #define m_pchData m_pszData
    
    Good Luck

    Reply
  • VS7 error: "m_pchData undefined"

    Posted by Legacy on 04/26/2003 12:00am

    Originally posted by: Mykel

    Hi,

    the class compiles fine under Visual Studio 6. But now I'm using VS7 and I get the followring error:

    "m_pchData undefined"

    Don't know how to fix this. Does anybody have a clue?
    Did they change the CString class?


    Many thx in advance...
    Mykel

    • Steve Palmer

      Posted by timber on 03/28/2005 11:17am

      Yes they changed the member name and the way CString works. Here's the 'hack' I found on another web page. Put these three lines at the beginning of the cpp file(before stdafx.h). It fools the complier.
      
      #define private public
      #define protected public
      #define m_pchData m_pszData
      
      Good Luck

      Reply
    • Steve Palmer

      Posted by timber on 03/28/2005 11:14am

      Yes they changed the member name and the way CString works. Here's the 'hack' I found on another web page. Put these three lines at the beginning of the cpp file. It fools the complier.
      
      #define private public
      #define protected public
      #define m_pchData m_pszData
      
      Good Luck

      Reply
    • Problem still not solved !!!

      Posted by mYkel on 09/20/2004 08:55am

      Recently I got a few mails from coders that also have the problem with "m_pchData undefined" under VS7. I still don't have a solution or a workaround for the problem. I believe that some parts of the CStringEx class must be recoded cause the MFC base classes changed... but I really have no time at the moment. If anybody finds a solution please post it here!!! :-) Regards, mYkel

      Reply
    Reply
  • Thanks!

    Posted by Legacy on 06/28/2002 12:00am

    Originally posted by: Gayatri

    Thank You a million times...this article is a life saver!:)

    Best Regards
    Gayatri.

    Reply
  • Don't compile with devstudio 7

    Posted by Legacy on 06/11/2002 12:00am

    Originally posted by: Dieter Hammer

    this worked fine for me. Now I want to recompile the application with VC7. But m_pchDATA ist undefined.

    Reply
  • ReverseFind error

    Posted by Legacy on 04/17/2002 12:00am

    Originally posted by: Mouse

    ReverseFind doesnt work if the string your are trying to find is at the front of the string.

    Reply
  • Thanks

    Posted by Legacy on 09/13/2001 12:00am

    Originally posted by: d.e. bugger

    The code plugged right into my large project and seems to work well so far. I'm using it primarily for the reverse find functionality.

    In addition, I'd like to see something that provides "entire word" search (in reverse) for the standard CFindReplace dialog. Good job!

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The hard facts on SaaS adoption in over 80,000 enterprises: Public vs. private companies Mid-market vs. large enterprise GoogleApps, Office365, Salesforce & more Why security is a growing concern Fill out the form to download the full cloud adoption report.

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds