Introduction
There are two main reasons why people use CString
over std:string
. The
first is that a CString
converts easily between a BSTR
and a
char *
. The
way to overcome this is to #include<comdef>
, and cast your
std::string.c_str()
to a _bstr_t
. The second problem requires a little
more explanation...
CString
has a very handy method called Format. This method works the same
way as sprintf
, which is hardly surprising as internally it takes an educated
guess as to the new buffer size and then calls _vstprintf
. The problem
with using sprintf
yourself or relying on functions that do so is
twofold. First of all, you need to make memory allocations beyond what
you need in order to minimise the possibility of a buffer over-run, a
possibility you can do nothing to get rid of entirely. Secondly, because
you need to provide variables, and formatting information separately, there is
no type safety, and a real cause for error.
None of this is completely life threatening, and plenty of commercial
apps I am
sure use sprintf
with no problem. However, it just isn't best
practice. The highly under-rated iostream
library provides a much better
option, in the form of ostringstream
. This class provides a buffered
stream, and returns us a std::string
containing the information we have
streamed to it. Because it is an iostream
, we can pass any type into it
so long as a stream handler exists for it. I'm not going to cover writing
your own in this article, but it is really is very easy to do, and then you can
stream any types you may create.
So the basic idea is that we create an ostringstream
and then pass information
into it, like this:
ostringstream strstrm;
strstrm << "This is how I pass in text, but I can pass in numbers as easily - "
<< 42 << endl;
We can then access the underlying string using the str()
method, which we can
chain to the strings c_str()
method to get a const char * if desired. The
endl
is an example of a stream modifier - it added a newline character and
flushes the stream.
cout << strstrm.str();
The str
method can also be passed a new value for the buffer, so we can clear
it like this:
strstrm.str("");
Now we've got the basics under our belt, lets see what we can do to implement
sprintf
type behaviour. Obviously we can pass in numbers, but what if we
want to format them ? Well, as the following code shows, we can do a lot more
than sprintf
does. We can easily switch between hex, oct and decimal, we
can get bool
s to be printed as words ( a note, BOOL
is NOT a
bool
, it is a
typedef
for an int. iostreams
will always regard BOOL
to be an
int
, this
is your fault for using BOOL
, which Microsoft created for C programming, as C
does not have a bool
type ), we can format floating point numbers as scientific
notation, etc. Note that when we use fixed point we get 6 decimal places,
if we just stream a float, we get six significant figures overall. That
is, if you add another significant figure to the left of the decimal point in
the example, you'll lose a figure to the right.
Here is the code for the example, followed by it's output.
string str;
ostringstream strstrm;
strstrm << boolalpha << true << " is a bool value" << noboolalpha
<< ", so is " << false; cout << strstrm.str() << endl;
strstrm.str("");
strstrm << "100 decimal = " << dec << 100 << ", octal = " << 100
<< ", hex = " << hex << 100 << endl;
cout << strstrm.str();
strstrm.str("");
float f = 199.9273f;
strstrm << "A float: " << f << ", scientific notation " << f
<< " " << scientific << f << " fixed notation " << fixed
<< f << endl;
cout << strstrm.str();
strstrm.str("");
strstrm << "Formatted to two decimal places " << setprecision(2)
<< f << ", formatted to %2.5f " << setprecision(5) <<
leftprec(f, 2) << endl; cout << strstrm.str();
true is a bool value, so is 0
100 decimal = 100, octal = 144, hex = 64
A float: 199.927, scientific notation 199.927 1.999273e+002 fixed notation 199.927307
Formatted to two decimal places 199.93, formatted to %2.5f 99.92731
Now, there is one little trick in here. The modifiers boolalpha
/noboolalpha
turn alpha booleans off and on.
dec
/octal
/hex
changes the number base (setbase(8/10/16) does the same),
scientific/fixed change the way floats are shown (uppercase
/nouppercase
allows
you to set the case of the E in scientific notation, and showpos
/noshowpos
allows you to force a + sign for positive numbers ), and setprecision
allows
you to set the precision to the right of the decimal point. However, no
method is provided to set the precision to the left of the decimal point.
My first thought in providing such a method was to use the % operator, however
it does not work with floats ( why? ). So in order to provide such a
method I had to write the following macro.
#define leftprec(x,y) (x - (((int)floor(x) - (((int)floor(x))% ((int)(pow(10,y)))))))
And people ask me why I think macros are ugly.... Anyhow, you can use
setprecision
to set precision to the right of the decimal, then pass in the
value and the number of significant characters you want to the left of the
decimal. This provides no rounding, it will just strip the extra
values. Note also you'll need to #include<math.h>
I'm afraid I built this project using VS.NET beta 2, so the provided project
will not work with VC 6. However, it is a simple task to create an empty
console app and copy the main() function from the provided cpp file into it, so
I don't feel it's too much of a problem. I hope this article has made you
realise some of the power provided by the iostream
library, as well as
dispelling any reason you may have for feeling that life cannot go on
without CString
. The standard string does everything
CString
does,
it's just that sometimes it's not as clear, because std::string
provides some
of this functionality through iostreams
, and some through the fact it is an STL
container, which means that all the algorithms provided by the STL work with
it. In effect, std::string
is FAR more powerful than CString
, if you know
where to look.