You are on page 1of 42

copy constructor is

• a constructor function with the same name as the class

• used to make deep copy of objects.

there are 3 important places where a copy constructor is called.

when an object is created from another object of the same type

when an object is passed by value as a parameter to a function

when an object is returned from a function

if a copy constructor is not defined in a class, the compiler itself defines one. this will ensure a
shallow copy. if the class does not have pointer variables with dynamically allocated memory,
then one need not worry about defining a copy constructor. it can be left to the compiler's
discretion.

but if the class has pointer variables and has some dynamic memory allocations, then it is a must
to have a copy constructor.

for ex:
class a //without copy constructor
{
private:
int x;
public:
a() {a = 10;}
~a() {}
}

class b //with copy constructor


{
private:
char *name;
public:
b()
{
name = new char[20];
}
~b()
{
delete name[];
}
//copy constructor
b(const b &b)
{
name = new char[20];
strcpy(name, b.name);
}
};

let us imagine if you don't have a copy constructor for the class b. at the first place, if an object is
created from some existing object, we cannot be sure that the memory is allocated. also, if the
memory is deleted in destructor, the delete operator might be called twice for the same memory
location.

this is a major risk. one happy thing is, if the class is not so complex this will come to the fore
during development itself. but if the class is very complicated, then these kind of errors will be
difficult to track.

in windows this will lead to an application popup and unix will issue a core dump. a careful
handling of this will avoid a lot of nuisance.

c++ virtual function is a member function of a class, whose functionality can be over-
ridden in its derived classes. the whole function body can be replaced with a new set of
implementation in the derived class. the concept of c++ virtual functions is different from
c++ function overloading.

c++ virtual function - properties:


c++ virtual function is,

• a member function of a class

• declared with virtual keyword

• usually has a different functionality in the derived class

• a function call is resolved at run-time

the difference between a non-virtual c++ member function and a virtual member function
is, the non-virtual member functions are resolved at compile time. this mechanism is
called static binding. where as the c++ virtual member functions are resolved during run-
time. this mechanism is known as dynamic binding.

c++ virtual function - reasons:


the most prominent reason why a c++ virtual function will be used is to have a different
functionality in the derived class.
for example a create function in a class window may have to create a window with white
background. but a class called commandbutton derived or inherited from window, may
have to use a gray background and write a caption on the center. the create function for
commandbutton now should have a functionality different from the one at the class called
window.
c++ virtual function - example:
this article assumes a base class named window with a virtual member function named
create. the derived class name will be commandbutton, with our over ridden function
create.
class window // base class for c++ virtual function example
{
public:
virtual void create() // virtual function for c++ virtual function example
{
cout <<"base class window"<<endl;
}
};
class commandbutton : public window
{
public:
void create()
{
cout<<"derived class command button - overridden c++ virtual function"<<endl;
}
};

void main()
{
window *x, *y;

x = new window();
x->create();

y = new commandbutton();
y->create();
}

the output of the above program will be,


base class window
derived class command button
if the function had not been declared virtual, then the base class function would have
been called all the times. because, the function address would have been statically bound
during compile time. but now, as the function is declared virtual it is a candidate for run-
time linking and the derived class function is being invoked.

c++ virtual function - call mechanism:


whenever a program has a c++ virtual function declared, a v-table is constructed for the
class. the v-table consists of addresses to the virtual functions for classes and pointers to
the functions from each of the objects of the derived class. whenever there is a function
call made to the c++ virtual function, the v-table is used to resolve to the function
address. this is how the dynamic binding happens during a virtual function call.
we can define a variable in c++ to store a memory address. a pointer in c++ is said to "point to"
the memory address that is stored in it. also, when defining a c++ pointer variable, we must
specify the type of variable to which it is pointing. for example, to define a pointer, which will store
a memory address at which exists an int, we can do the following:

//sample program for c++ pointer


signed main()
{
int* p;
//right now, p contains no particular myval in this c++ code.
}

the asterisk in the above specifies that we have a pointer variable. let's say we want to define an
int variable and then we want to define a pointer variable, which will store the memory address of
this int:

//c++ pointer using an int variable

signed main()
{
int myval(7);
int* p_myval;
//right now, p_myval contains no particular myval.
p_myval = &myval;
//now, p_myval in this c++ program contains the memory address of the variable myval
}

with &myval, & is referred to as "the address-of operator". the expression &myval is of the c++
type int*. we then store this int* myval in our int* variable, which is p_myval. now, we will actually
use this pointer:

//sample program for c++ pointer

signed main()
{
int myval = 7;
int* p_myval = &myval;
*p_myval = 6;
}

with *p_myval = 6, the asterisk is referred to as "the dereference operator". it turns the expression
from an int* into an int. the statement has the effect of setting the myval of myval to 6. so now
what are the uses of pointers in c++? let us see something about how and when they should be
used:

a) when the pointer must be re-seated.

b) when arrays are involved.

consider a string, an array of characters:

signed int main()


{
char my_name[] = "code";
}

here's what the string (char c++ pointer) looks like in memory:

name of variable type of variable address in memory value stored

my_name char 108 'c'

char 109 'o'

char 110 'd'

char 111 'e'

char 112 '\0'

while accessing the characters inside the variable my_name, the position of the first character will
start from 0. so the array of size 4 will be accessed for characters from 0, 1, 2 and 3. we can
define a pointer to point to the second element in the array my_name, as so:

int main()
{
char my_name[] = "code";
char* k( &my_name[1] );
}

now, what k points to looks like so in memory:

name of variable type of variable address in memory value stored

my_name char* 116 109

char 109 'o'

char 110 'd'

char 111 'e'

char 112 '\0'

so that's one usage of c++ pointers there, to point to an individual object in an array. the other
feature of c++ pointers is that they can be "re-seated", which means that you can change their
value, you can change what they're pointing to, as in the following: // c++ pointer program for
modifying values/re-seating.

signed int main()


{
int myval(5);
int myvalue2 = 7;
int* p_primate;
p_primate = &myval;
*p_primate = 9;
p_primate = &myvalue2;
*p_primate = 10;
}
guess what kind of variable we have in the following:

signed main()
{
signed** p_p_cow;
}

an int* c++ pointer points to an int, so an int** points to an int*. in english: the variable p_cow
above stores a memory address. at that memory address exists a variable of type int*. this int*
variable also stores a memory address, at which exists an int. take the following:

//snippet for c++ pointer to pointers

int main()
{
int cow(7);
int* p_cow = &cow;
int** p_p_cow(&p_cow);
int*** p_p_p_cow = &p_p_cow;
}

here's what the above c++ pointers look like in memory:

name of variable type of variable address in memory value stored

cow int 108 7

p_cow int* 110 108

p_p_cow int** 112 110

p_p_p_cow int*** 114 112

with the above code, we can set the value of cow using p_p_p_cow:

//using c++ pointer to pointer

int main()
{
int cow(7);
int* p_cow = &cow;
int** p_p_cow(&p_cow);
int*** p_p_p_cow = &p_p_cow;
***p_p_p_cow = 8;
}

c++ pointers are commonly used when working with strings. let's define a function; this function
will be supplied with a string. we're going to change the 2nd, 5th and 7th characters of the string:

void changestring(char* const p_first_char)


{
p_first_char[1] = 'a';
p_first_char[4] = 'b';
p_first_char[6] = 'c';
}

or we can define a function, which will be supplied with a string. the function will return the first
instance of the character 't' in the string:
char* getfirstt(char* p_first_char)
{

for ( ; *p ; ++p)
{
if ( *p == 't' ) return p;
}
return 0;

signed main()
{

char the_alphabet[] = "abcdefghijklmnopqrstuvwxyz";


char* p_t = getfirstt(the_alphabet);

now i'm going to talk about c++ pointers and constness. if you want a const c++ pointer variable,
a c++ pointer variable whose value you can't change after initialization, then stick the const
directly beside the variable's name:

signed main()
{

int myval = 5;
int myvalue2(8);
int* const p_k = &myval;
p_k = &myvalue2; //compile error
*p_k = 3; //no problem, the variable to which it points is non-const

if you want a non-const c++ pointer variable, but you want the variable to which it points to be
const, then:

signed main()
{

int myval(7);
int myvalue2 = 6;
const int* p_k = &myval;
p_k = &myvalue2; //no problem, the variable is non-const
*p_k = 7; //compile error

if you want a const c++ pointer that points to a const variable then:

signed int main()


{

int myval(17);
int myvalue2 = 4;
const int* const p_k = &myval;
p_k = &myvalue2; //compile error
*p_k = 32; //compile error

dynamic memory allocation in c++

so you have just finished your program and you would like to see it at work. usually you press a
combination of keys to compile and run it. but what actually happens when you press those
keys ? the compilation process translates your code from c++ to machine language, which is the
only language computers "understand". then your application is given a certain amount of
memory to use, which is divided into three segments as follows :

• code segment, in which all the application code is stored

• data segment, that holds the global data

• stack segment, used as a container for local variables and other temporary information.

in addition to these, the operating system also provides an extra amount of memory called heap.

now let us recall what a c++ variable consisted of :

• name, an identifier to recognize the variable

• type, the set of values it may take

• scope, the area of code in which it is visible

• address, the address at which the variable is allocated

• size, the amount of memory used to store its contents.

consider the following sequence of code :

void cookie() {
int integer_x;
...
}

let us try to figure out what are the attributes of the variable declared inside this function.

• name : integer_x

• type : int

• scope : local, only visible within the cookie function

• address : a certain address in the stack segment, where the variable is allocated

• size : 32 bits (depending on the compiler)

what is a pointer then ? we all heard about it, it is the variable declared with a star in front of it...
but let us try and put some order in what we know by enumerating its exact attributes :
• name : an arbitrary identifier

• type : integer address

• scope : local or global (depending on the situation)

• address : the address in the data segment (if it is global) or stack segment (if it is local)

• size : 32 bits (16 bits for segment + 16 bits for offset of the address it points to)

a dynamic variable is a variable that is stored in the heap, outside the data and stack segments,
that may change size during runtime or may even disappear for good. in fact if we do not allocate
a certain amount of memory to store its contents, it will never exist.

for example consider the next declaration :

double *dp;

this tells the compiler we have a pointer dp that may hold the starting address of a dynamic
variable of type double. however, the dynamic variable does not exist yet - to create it we must
use the new operator like this :

dp = new double;

remember one thing - dynamic variables are never initialized by the compiler. therefore it is good
practice to first assign them a value, or your calculations may not come out right. there are two
ways of doing this :
dp = new double;
*dp = a;

or

dp = new double(a);
where a is the initial value. i personally recommend the second version as it is more suggestive
and also more compact.

consider this line of code :


double *dp = new double(3.1415);
the diagram below shows the possible connection between dp and the dynamic variable
associated with it, after the previous line is executed.

now consider you have decided to erase the dynamic variable for good from the heap. use the
delete operator to achieve this :
delete dp;
notice that while we have freed 8 bytes of memory - which is sizeof(double) - the dp pointer
was not erased. it still exists in the data or stack segment of the application and we may use it to
initialize another dynamic variable.

c++ also provides the possibility to allocate an entire array in the heap, keeping a pointer to its
first element. again we use the new operator but mention the size of the array in square brackets :
int *table;
table = new int[100];
this will allocate 100 * sizeof(int) = 100 * 4 = 400 bytes of memory and assign the
starting address of the memory block to the table pointer.

arrays allocated in the heap are similar to those allocated in the data or stack segments, so we
may access an arbitrary element using the indexing operator []. for example the next loop
initializes each element of the array to zero :
for (int i = 0; i < 100; ++i)
table[i] = 0;
bad news is c++ does not provide any method of initializing heap arrays as you would an ordinary
dynamic variable, so we have to do it ourselves using a loop similar to the one above. the
following line will generate errors at compilation :
table = new int[100](0);
to erase a heap-allocated array we will use the delete operator, but this time add a pair of
square brackets so that the compiler can differentiate it from an ordinary dynamic variable.
delete [] table;
all we have learned so far are means of replacing the old malloc() and free() functions in c
with their new and delete c++ analogues. the main reason is that they are better alternatives to
dynamic memory allocation and also have a more compact syntax. however c pointer arithmetics
and other pointer operations are also available in c++.

memory corruption
memory is said to be corrupted if we try one of the following :

• free a dynamic variable that has already been freed


char *str = new char[100];
delete [] str;
delete [] str;
• free a dynamic variable that has not been yet allocated
char *str;
delete str;
• assign a certain value to a dynamic variable that was never allocated
char *str;
strcpy(str, "error");
the previous three examples will most probably cause the application to crash. however the next
two "bugs" are harder to detect :

• assign a certain value to a dynamic variable, after it has been freed (this may also affect
other data stored in the heap)
char *str = new char[100];
delete [] str;
char *test = new char[100];
strcpy(str, "surprise !");
cout << test;
delete [] test;
• access an element with an index out of range
char *str = new char[30];
str[40] = 'c';
last but not least, remember it is good practice to test whether allocation was or was not
successful before proceeding with the code. the reason for this is that the operating system may
run out of heap at some point, or the memory may get too fragmented to allow another allocation.
char *str = new char[512];
if (str == null) {
// unable to allocate block of memory !
}

memory leaks in c++ and how to avoid them

a memory leak is what happens when you forget to free a block of memory allocated with the new
operator or when you make it impossible to do so. as a consequence your application may
eventually run out of memory and may even cause the system to crash. i will now give you a few
tips. remember that code lines shown in red are the best alternative to each situation - they may
be added or may even replace previous code.

• delete it before reallocating it


char *string;
string = new char[20];
delete [] string;
string = new char[30];
delete [] string;
so we have the new, we have the delete... where is the leakage ? obviously we have
two consecutive memory allocations via the string pointer, but something seems to be
missing. we should have a delete [] statement right after the first allocation and then
try to reallocate using a different size parameter. if we choose not to, the second
allocation will assign a new address to the string pointer while the previous one will be
lost. this makes it impossible to free the first dynamic variable further on in the code,
resulting in a memory leakage.

• be sure you have a pointer to each dynamic variable

what do you think will happen at the end of this code fragment ?

char *first_string = new char[20];


char *second_string = new char[20];
strcpy(first_string, "leak");
second_string = first_string;
strcpy(second_string, first_string);
delete [] second_string;
you guessed right, we have a memory leak here. what happened is that we
have lost the address of the dynamic variable associated with second_string
(as a side-effect of the pointer assignment) so we cannot delete it from the
heap anymore. thus the last line of code only frees the dynamic variable
associated with first_string, which is not what we wanted.

the main idea is to try and not lose the addresses of dynamic variables as you may
eventually not be able to free them.

• watch out for local pointers

consider the next function :

void leak() {
int k;
char *cp = new char('e');
delete cp;
}

obviously both the k and cp variables are local so they are allocated on the stack
segment. then when it comes the time to exit the function they will be freed from memory
as the stack is restored.

so what happens if we do not provide a delete cp statement ? will the dynamic


variable associated with the cp pointer also be erased from heap at function exit ? c++
has no mechanism to do this, we have to handle it ourselves by coding an explicit
delete cp line.

• careful with functions returning dynamic variables

let us take a look at the following program.

#include <iostream>

char* tostring(int n) {
char *s = new char[100];
char aux;
int i, j;

for (i = 0; n; n /= 10, ++i)


s[i] = n % 10 + '0';
for (j = 0; j < i / 2; ++j) {
aux = s[j];
s[j] = s[i - j - 1];
s[i - j - 1] = aux;
}
s[i] = '\0';

return s;
}

void main() {
cout << tostring(23) << tostring(146) << endl;
char *temp;
temp = tostring(23); cout << temp; delete [] temp;
temp = tostring(146); cout << temp; delete [] temp;
}

obviously the function char* tostring(int n) converts the integer n to a string, but
that is not of our interest right now. you may have noticed that the string stored in s is
not freed from the heap before exiting the function. we have just been
warned about local pointers though. the reason for this is that the string
should also be available within the calling function main() as we need to print
it out to screen. to solve this "contradiction" we should first assign the return
value to a temporary pointer variable inside main(), print it out and be sure
to delete [] it right away, as shown above.

you may ask yourself why use a supplementary pointer here, why not stick to the
previous variant which is also more compact ? the answer is simple - we may not be able
to delete [] the dynamic variable returned by the tostring() function call as its
address would eventually be lost if we do not store it somewhere. for example the calls
tostring(23), tostring(146) within the cout statement return two dynamic
variables whose adresses are only used at printing, they are then lost. this leads to
memory leakage.

• avoid these combinations : new - delete [] and new [] - delete

as stated above you should use the delete [] operator in order to free a heap-
allocated array and prefer the delete variant to free a single object,
otherwise the application may have an unexpected behaviour. the following
are to be avoided :
int *vector = new int[10];
...
delete vector;
delete [] vector;

and

char *a = new char('a');


...
delete [] a;
delete a;
• avoid these combinations : malloc() - delete and new - free()

char *a = (char*) malloc(10);


...
delete [] a;
free(a);

or

char *b = new char[10];


...
free(b);
delete [] b;

my advice is not to mix c with c++ as they have different approaches to dynamic memory
allocation. make up your mind and choose either of them, or your application may run
erratically. i personally recommend the c++ new and delete operators.

basic graphs terminology


what is a graph ? the name may suggest something that has to do with graphics, which in a way
is true but graphs are generally more than that. their main use is at describing relationships
between various entities in the real world. for example consider four persons - call them mary,
john, april and george. now let us try to examine the relationships between them. for this we will
use arrows with the following convention - we have an arrow going from x to y if x likes y,
otherwise no arrow at all. consider the following - john likes april, april likes john, mary likes
george, george likes april, mary likes john - and draw the diagram below.

with this overall view we may more easily arrive at certain conclusions. it is now obvious that april
and john are the most "liked" persons, while there is nobody who likes mary. also we see that the
relationship between john and april is reciprocal. in the following we will define some basic graph
terminology based on the previous example.

a vertex (or node) is the representation of an entity in the real world as part of a network of such
entities, where each may be connected to another on the basis of a particular relation. this
connection is indicated with an edge (or arc) drawn between the corresponding vertices. in the
above diagram, the vertices are the circles - (john), (mary), (george), (april) - while edges are
denoted by arrows. because replacing names (or any other kind of information describing the
entity) with consecutive numbers does not affect the initial graph and the conclusions we may
get based upon it, we might as well use the following diagram :

now we can give a more rigorous definition of the graph - an ordered pair of sets g = ( x, u )
where

x = vertex set = { 1, 2, ..., n } , n = number of vertices


u = edge set = { ( x, y ) | x, y are from x and there is an edge between x and y }

for example, the previous graph has x = { 1, 2, 3, 4 } and u = { ( 1, 2 ), ( 2, 1 ), ( 3, 1 ), ( 4, 2 ), ( 4,


3 ) } or as in the initial formulation :

x = { april, john, george, mary } and u = { ( april, john ), ( john, april ), ( george, april ),
( mary, john ), ( mary, george ) }
so far the edges we have drawn were arrows with a specific direction (george likes april, but april
does not like george). from now on we will only consider reciprocal relationships as between john
and april. also we will use line segments instead of arrows. these are in fact bidirectional arrows
and whenever we have a line from x to y we should bear in mind that it is actually formed of two
edges ( x, y ) and ( y, x ).

we may now give the definition of an undirected graph - an ordered pair of sets g = ( x, u )
where

x = vertex set = { 1, 2, ..., n }, n = number of vertices


u = edge set = { ( x, y ) | x, y are from x and there is a bidirectional arrow between x and
y}

for example, the undirected graph above has x = { 1, 2, 3, 4, 5 } and u = { ( 1, 2 ), ( 1, 4 ), ( 2, 3 ), (


4, 5 ) }.

now it would be nice if we found a method of storing such data structures in the memory of a
computer. but before we do that let us try and optimize things a little bit - look at the vertex set x,
is it really necessary to keep all elements 1, 2, ..., n ? or is it possible to replace the set x by a
single value n, the number of vertices of the graph ? of course it is possible. this means that an
arbitrary undirected graph is only identified by an ordered pair g = ( n, u ) where u is defined as
above.

the adjacency matrix is a square binary matrix (its elements are either 0 or 1) of order n (this is
the number of graph vertices) with the following property :

a( i, j ) = 1 if the ( i, j ) pair is found in the edge set u, otherwise a( i, j ) = 0, for all values 1
<= i, j <= n

it is easy to see that this matrix is symmetric since whenever we have a ( i, j ) pair we also have
its corresponding ( j, i ) pair - remember the graph is undirected. we have therefore found a
method of storing a graph into a square binary matrix which is easy to store in memory. next we
will implement a c++ class graph that uses stream operators to initialize and display the contents
of a graph stored as an adjacency matrix. also, the isconnected function tests for the connectivity
of two vertices in the graph.
#include <iostream.h>

class graph {

public:
graph(int size = 2);
~graph();
bool isconnected(int, int);

friend istream& operator>>(istream&, graph&);


friend ostream& operator<<(ostream&, graph&);

private :
int n;
int **a;
};

graph::graph(int size) {
int i, j;
if (size < 2) n = 2;
else n = size;
a = new int*[n];
for (i = 0; i < n; ++i)
a[i] = new int[n];
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
a[i][j] = 0;
}

graph::~graph() {
for (int i = 0; i < n; ++i)
delete [] a[i];
delete [] a;
}

bool graph::isconnected(int x, int y) {


return (a[x-1][y-1] == 1);
}

istream& operator >> (istream &in, graph &g) {


int p, x, y;
cout << "input number of edges : ";
in >> p;
cout << endl << "input edges :" << endl;
for (int i = 0; i < p; ++i) {
cout << "edge " << i+1 << endl;
cout << " x = ";
in >> x;
cout << " y = ";
in >> y;
--x; --y;
g.a[x][y] = g.a[y][x] = 1;
}
return in;
}

ostream& operator << (ostream &out, graph &g) {


int i, j;
out << endl;
out << "adjacency matrix : " << endl << endl;
for (i = 0; i < g.n; ++i) {
for (j = 0; j < g.n; ++j)
out << g.a[i][j] << " ";
out << endl;
}
return out;
}
it is time to test our class :
void main() {
int a, x, y;
cout << "input number of vertices : ";
cin >> a;
graph g(a);
cin >> g;

cout << g;

cout << endl << "test for connectivity" << endl << endl;
cout << " input first node : ";
cin >> x;
cout << "input second node : ";
cin >> y;

cout << endl << x << " and " << y;


if (g.isconnected(x, y)) cout << " are connected." << endl;
else cout << " are not connected." << endl;
}
to conclude, we have found a way of storing a graph in memory and test for the connectivity of
two vertices. this may not seem such a great achievement but there are actually lots of
applications that require this simple test. remember that graphs are about relationships and
relationships arise everywhere around us.

stl container class introduction


before we start dig deep into stl, it is mandatory that we learn about container classes.

a container class is defined as a class that gives you the power to store any type of data.
there are two type of container classes available in stl in c++, namely “simple container
classes” and “associative container classes”. an associative container class associates a
key to each object to minimize the average access time.

simple container classes


• vector<>

• lists<>

• stack<>

• queue<>

• deque<>

associative container classes


• map<>

• set<>
• multimap<>

• multiset<>

this article will discuss about the simple container classes in detail. this will show you
how to use the different methods of these classes to achieve your goal. let’s start with
vector<>
vector<> the open array

constructor of vector class


there are 3 overloaded constructors in the vector class excluding the default one. we will
discuss about them one by one. you can create a vector object with predefined size and
values for them. here is how you can do that. suppose you want to create a vector<int>
object whose initial capacity will be 10 and all the values will be set to 2. then you can
create such an object using one of the overloaded versions of the vector constructor
vector<int> nums(10,2);
if you just want to specify the capacity but don’t want to assign values for them then you
can use the other constructor which accepts an integer to set the capacity of the vector
like
vector<int> nums(100);
the last constructor allows you to initialize the vector with data from other vectors or
arrays or other collections. suppose you want to copy one vector to the other then this
constructor proves to be very handy. here is the code snippet.
vector<int> nums; for(int i=1;i<5;i++) nums.push_back(i); vector<int> codes(nums);
here codes is a vector. we have copied the contents of nums in codes using the
constructor.

methods of vector class


• _destroy()

• _eq()

• _lt()

• _ucopy()

• _ufill()

• assign()

• at()
• begin()

• back()

• capacity()

• clear()

• empty()

• end()

• erase()

• front()

• get_allocator()

• max_size()

• insert()

• operator=

• operator[]

• pop_back()

• push_back()

• rbegin()

• rend()

• reserve()

• resize()

• size()

• swap()

• ~vector()

here we will discuss about each method, will show you what it does and then at the end
we will give example where you can use these methods together….
the methods that start with an underscore are protected methods and can’t be accessed
using object of the class. these methods are used by other method internally.
assign() and size()

this method is used to assign an initial size to the vector. this method has 2 overloaded
versions. here we have used the first overloaded method. this method takes an integer
argument to initialize the size of the vector.
#include <iostream>
#include <vector>
#include <conio.h>

using namespace std;

int main()
{
vector<char> codes;
codes.assign(10);//assigning the size to 10.
cout<<codes.size();//prints the size of the vector
getch();
return 0;
}
the next overloaded match takes 2 pointer as arguments. here is a sample code
#include <iostream>
#include <vector>
#include <conio.h>
using namespace std;

int main()
{
char a[3]={'a','b','c'};
vector<char> orders;
orders .assign(a,a+3);
cout<<orders.size();
getch();
return 0;
}
here the pointer used are nothing but the array name. this concept owes to c. basically
array name is a pointer to that array.

at()

no matter how sophisticated tool we get, accessing the old c style array using the index is
really popular. this thing was kept in mind when stl was designed. as vector behaves like
an open array, people expect it to show other behaviors of the array. at() is a method that
takes an integer as argument and return the value at that location. so in a way it simulates
the age-old array indexing. here is a code snippet.
#include <iostream>
#include <vector>
#include <conio.h>

using namespace std;

int main()
{
vector<int> codes;
for(int i=0;i<10;i++)
codes.push_back(i);
cout<<codes.at(9)<<endl;
getch();

return 0;
}
the output of this code will be 9.

begin() , end()

in stl within containers the elements are accessed using iterators. iterators are something
like pointers. begin() and end() returns the iterator to the beginning and to the end of the
container. here one thing should be noted end() points to a location which is one after the
physical end of the container. these two methods are the most used ones in stl, because to
traverse a container, you need to create an iterator. and then to initialize it’s value to the
beginning of the container. here is the code to traverse a vector<>. this code make use of
begin() and end() methods.
#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> numbers;
for(int i=0;i<10;i++)
numbers.push_back(i);
//assiging to the beginning
vector<int>::iterator k = numbers.begin();
//please note that k is kept less than
//numbers.end() because end() points to somewhere which is beyond physical end.
for(;k<numbers.end();k++)
cout<<*k<<endl;
return 0;
}

front(), back()

front() method returns the element at the front of the vector. back() method returns the
element at the back of the vector. here is the code to understand its operations better.
#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> m;
for(int i=0;i<10;i++)
m.push_back(i);
cout<<m.front()<<endl;
cout<<m.back()<<endl;
return 0;
}
the output of this program is
0
9

capacity() , size()

capacity() returns the number of elements the vector can hold initially assigned. for a
vector although it is not very important method. on the other hand the method size()
returns the number of elements currently in the vector. here is the code that will help you
understand the difference between capacity() and size().
#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> m(100);
cout<<m.capacity()<<endl;
for(int i=0;i<10;i++)
m.push_back(i);
cout<<m.size()<<endl;
return 0;
}
the output of the program is
100 110
because initially using the constructor we created a vector whose capacity is 100. then we
are pushing 10 elements to its back. therefore the size increases to 110.
123
next

clear() , erase()

as the name suggests clear() method clears the vector elements.

so the size of the vector becomes zero after the clear(). on the other hand erase helps to
erase a particular element from the vector or a range of vectors. erase() has 2 overloaded
versions for this purposes. one overloaded method takes one iterator the other one takes 2
iterators as starting and finishing deleting locations. here is a code to illustrate
#include <iostream>
#include <vector>
using namespace std;
void display(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
for(int i=0;i<5;i++)
m.push_back(i);
cout<<"the full list is "<<endl;
display(m);
cout<<"after deleting the first element "<<endl;
m.erase(m.begin());//deleting the first element
display(m);
cout<<"after deleting the first 2 elements"<<endl;
//deleting first 2 elements
m.erase(m.begin(),m.begin()+2);
display(m);
return 0;
}
the output of this code is
the full list is
0
1
2
3
4
after deleting the first element
1
2
3
4
after deleting the first 2 elements
3
4

empty()

this method checks whether the vector is empty or not. this returns a bool value if the
vector size is 0, i.e., the vector is empty. here is the code using empty()
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
for(int i=0;i<5;i++)
m.push_back(i);
cout<<"the full list is "<<endl;
if(!m.empty())
display(m);
else
system.out.println(“the vector is empty”);
return 0;
}

swap()

this method swaps two vectors. here is the code


#include <iostream>
#include <vector>

using namespace std;


voiddisplay(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
vector<int> n(5,2);
for(int i=0;i<5;i++)
m.push_back(i);
cout<<"m :"<<endl;
display(m);
cout<<"n :"<<endl;
display(n);
m.swap(n);
cout<<"m :"<<endl;
display(m);
cout<<"n :"<<endl;
display(n);
return 0;
}
here we have two vectors initially. vector m takes the values 0 to 4 while the vector n
takes the values 2. here is the output of the code.
m:
0
1
2
3
4
n:
2
2
2
2
2
m:
2
2
2
2
2
n:
0
1
2
3
4
as you can see values of m and n are swapped.

max_size()

vectors can grow and diminish in runtime, but they also have a maximum size. this
method returns the maximum size possible for a vector. as you may have already
guessed, these values are different from type to type. but this value is same for a
particular type. suppose you want to declare a vector of int and another of int pointers.
both will have the same maximum size.
here is a code that prints some of the max sizes.
#include <iostream>
#include <vector>

using namespace std;


int main()
{
vector<int> iv;
vector<float> fv;
vector<double> dv;
vector<char> cv;
vector<string> sv;
vector<bool> bv;

cout<<"integer vector max size :"<<iv.max_size()<<endl;


cout<<"float vector max size :"<<fv.max_size()<<endl;
cout<<"double vector max size :"<<dv.max_size()<<endl;
cout<<"character vector max size :"<<cv.max_size()<<endl;
cout<<"string vector max size :"<<sv.max_size()<<endl;
cout<<"boolean vector max size :"<<bv.max_size()<<endl;

return 0;
}
here is the output of the code.
integer vector max size :1073741823
float vector max size :1073741823
double vector max size :536870911
character vector max size :4294967295
string vector max size :268435455
boolean vector max size :4294967295

reserve()

this method will reserve the values already there in the vector and then increase the
capacity of the vector. here is the code.
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
vector<int> m(4,3);
cout<<m.capacity()<<endl;
m.reserve(10);
cout<<m.capacity()<<endl;
display(m);
return 0;
}
here previously the capacity of the vector was 4. then the vector size is increased but the
values previously there in the vector will not be erased.

resize()
resize(), as the name suggests the method resizes the vector . if the argument given is
greater than the size, then the capacity will be increased. we can also put a value to fill
the remaining space.
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
vector<int> m(4,3);
cout<<m.capacity()<<endl;
m.resize(8,3);
cout<<m.capacity()<<endl;
display(m);
return 0;
}
here is the output of the program
4
8
3
3
3
3
3
3
3
3
as you can see the capacity of the vector is increased to 8.

rbegin(), rend()

rbegin() returns the iterator to the first element if the vector is reversed. say there is a
vector containing 1,4,5,6 then rbegin () returns an iterator for 6. on the other hand rend()
returns the iterator to a location beyond the physical end when the vector is reversed.
in a nutshell, rbegin() and rend() are nothing but the reverse version of begin() and end().
here is a code to illustrate their usages.
#include <iostream>
#include <vector>

using namespace std;

int main()
{
vector<int> m;
m.push_back(11);
m.push_back(12);
m.push_back(23);
m.push_back(25);
cout<<*m.rbegin()<<endl;
cout<<*(m.rend()-1)<<endl;
return 0;
}
the output of this code is
25
11

insert()

this method allows inserting any value wherever you please.

this has 3 overloaded versions. the first one accepts only 2 arguments. one is the iterator
which indicates where the value is to be inserted and the other argument is the value
itself.
here is a code that uses this version of insert() method.
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> nums;
for(int i=1;i<11;i++)
nums.push_back(i);
cout<<"before inserting the 100 at 2nd place the vector was :"<<endl;
display(nums);
nums.insert(nums.begin()+1,100);
cout<<"after inserting the 100 at 2nd place the vector is :"<<endl;
display(nums);
return 0;
}
here is the output of the above program
before inserting the 100 at 2nd place the vector was :
1
2
3
4
5
6
7
8
9
10
after inserting the 100 at 2nd place the vector was :
1
100
2
3
4
5
6
7
8
9
10
there are two more overloaded versions for this method insert() now we will discuss
about the second one. it takes 3 arguments. this is used for copying part or full of another
collection. (may be a vector, a plain c++ array or some other collections that is accessed
using pointers/iterators) say we have a plain c++ array like this
int array[] = {5,6,3,4};
and we want to insert 5,6,3 in our vector defined in the last program. we want to insert
these values right there where we inserted 100 in the last program. here is the code.
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
int array[]={5,6,3,4};
vector<int> nums;
for(int i=1;i<11;i++)
nums.push_back(i);
cout<<"before inserting the array elements at 2nd place the vector was :"<<endl;
display(nums);
nums.insert(nums.begin()+1,array,array+3);
cout<<"after inserting the array elements at 2nd place the vector is :"<<endl;
display(nums);
return 0;
}
here is the output of the code
before inserting the arry elements at 2nd place the vector was :
1
2
3
4
5
6
7
8
9
10
after inserting the array elements at 2nd place the vector is :
1
5
6
3
2
3
4
5
6
7
8
9
10
as you can see first 3 array elements are inserted in between 1 and 2 of the original
vector.
the last overloaded version of insert allows you to insert one constant value , m number
of times in any place within the original vector. here is the code for inserting three 99 in
between 1 and 2.
#include <iostream>
#include <vector>

using namespace std;

voiddisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}
int main()
{
int array[]={5,6,3,4};
vector<int> nums;
for(int i=1;i<5;i++)
nums.push_back(i);
cout<<"before inserting the constant elements at 2nd place the vector was :"<<endl;
display(nums);
nums.insert(nums.begin()+1,3,99);
cout<<"after inserting the constant elements at 2nd place the vector is :"<<endl;
display(nums);
return 0;
}
here is the output
before inserting the constant elements at 2nd place the vector was :
1
2
3
4
after inserting the constant elements at 2nd place the vector is :
1
99
99
99
2
3
4

applications
suppose we need to put the co-ordinates of a moving point. we can create a structure that
holds two values representing the co-ordinates at any moment and then we can create a
vector of these points.
here is the code.
#include <iostream>
#include <vector>
#include <conio.h>//for getch()

using namespace std;

typedef struct point


{
float x;
float y;
float z;
}point;//this will represent a point at any point of time.

int main()
{
//no need to tell the compiler how many elements
vector<point> movingpoints;
float x,y,z;
point temporarypoint;
for(int i=0;i<4;i++)
{
cout<<"enter the co-ordinates of the points :";
cin>>x>>y>>z;
temporarypoint.x=x;
temporarypoint.y=y;
temporarypoint.z=z;
movingpoints.push_back(temporarypoint);
}
vector<point>::iterator k = movingpoints.begin();
cout<<"the moving point's coordinates were "<<endl;
for(;k<movingpoints.end();k++)
cout<<"("<<k->x<<","<<k->y<<","<<k->z<<")"<<endl;

getch();
return 0;
}
here is the output of the code
enter the co-ordinates of the points :1 2 3
enter the co-ordinates of the points :4 5 6
enter the co-ordinates of the points :6 7 3
enter the co-ordinates of the points :4 6 7
the moving point's coordinates were
(1,2,3)
(4,5,6)
(6,7,3)
(4,6,7)
the computers understand everything by numbers. each character is represented as a number,
which is finally drawn as a character in the screens.it has been a major problem for the legacy
systems to write programs for languages other than english. the primary reason being the non-
availability of enough characters in ascii encoding. so obviously internationalization of
applications becomes a big issue.

ascii and codepage mechanism:


ascii format had one byte or 8 bits for each character. this means that, it can have 2^8 or 256
different characters. so if a program is to be written in a different language, the entire character
set is to be replaced with a different one. windows initially had a scheme called codepage. for
each language, it had a different codepage. if it needs its version of windows in chinese, then it
will use the chinese code page. the problem here is that, at a time it can support only one
language. so if a person in europe connects to us server, he'll see only english characters and
vice-versa.

multi-byte character set or double-byte character set:


one solution proposed for the above problem was to have a multiple byte character set. in this
schema, a character might be represented as a single byte or a double byte. if it is a double byte
schema, the lead byte will have the information about its double byte status. so the applications
have to check the lead byte status always. the vc++ provides an api "isleadbyte(int c)" to check if
a character is a lead byte.

unicode:
finally all big companies have joined together and decided to invent a new strategy for this issue.
a new character encoding scheme was deduced with 16 bits. now this 16 bit character set can
support 2^16 or 65536 characters. this standards of unicode are hosted at unicode. although
original goal of this unicode consortium was to produce a 16 bit encoding standard, it produced 3
different standards.
utf-8: this is a 8 bit encoding standard. the advantage in this schema is that the unicode
characters in/transformed into utf-8 are compatible with the existing softwares.
utf-16: this is the original planned standard using 16-bit characters.
utf-32: this is used where memory is not a constraint.

all 3 forms of data can be transformed into one another without any loss of data. all of them use a
common repertoire of characters.
note:
windows nt/2000/xp use unicode as their character set. so even if a program uses data in ascii, it
internally gets converted to unicode, processed, reconverted to ascii and returned.
most of the times some programs will need conversions from mbcs/dbcs to unicode. if anybody
needs to learn the conversion procedures, please follow the link at microsoft msdn. you can get
all the information you need about this(ofcourse, if the page is not moved to a different location).

infact a common goal expected to be achieved out of the whole effort is to gain
internationalization. but having a common character set solves only a part of the whole issue. the
other issues like date, time, numbers, currencies and conventions also among other things to be
taken care of.

unicode characters are invented to accommodate additional international characters apart from
english. earlier characters were represented in ascii formats with each character occupying 1 byte
of memory. but with unicode, each character is represented with 2 bytes.

there is one more type of character set using 2 bytes i.e.mbcs (multi byte character set) or dbcs
(double byte character set). in fact any article about mfc unicode programming, will have a
reference to the mbcs and dbcs. this character set is used for single locale specific programming
i.e., it can support only one locale set in an application. but using unicode will enable the
programs to use multiple locale character sets simultaneously.

as the benefits of unicode programming looks immense as described above, it is imperative for
any application to give support to unicode. mfc supports unicode in a very flexible way by
providing a single line macro to convert between a unicode and non-unicode application.

mfc unicode macro:

the macro,

#define _unicode

will make the application unicode enabled. but a mere use of the macro will not be enough to
make an application unicode enabled. some of the important things the application should take
care of are listed below.

• the entry point of the application should be set as wwinmaincrtstartup

• strings should be declared and used as tchar type.

• the length of the string should be passed as length * sizeof(tchar)

• use string functions declared in tchar.h viz., _tcscat, __tcscpy, _tcscmp etc.,

using the tchar programming set will enable the compiler to choose between unicode c runtime
library and non-unicode library. if the program is defined to be a unicode program, it will expand
the tchar routines to ascii functions. if the program does not have any unicode macro defined, it
will be built as an ascii application.

mfc unicode - points to be noted:

using unicode makes windows nt/2000 efficient as unicode is the standard character set used
in processing characters. any non-unicode literals will be converted back and forth for
manipulations.

win 98 platforms do not support unicode.

unicode programming is supposed to be easier with windows. but there are certain weird
instances when we need to write some weird code too. this sample presents a code written
in such a situation.
usually if anybody wants to do unicode programming in mfc this is what will have to be
done.
1. make all the declarations using tchar type.
2. use all the functions related to tchar
3. do a #define unicode.

the mfc framework in this case will automatically take care of converting all the strings to
unicode if such steps are followed. if it was not "#define unicode", then all the strings
will be treated as ascii.

there are some operating system level points to be noted too. if the operating system is
below win95, the default strings are ascii only. even if you send unicode strings, it will be
converted to ascii, do the manipulations, reconvert and return the unicode value.
if the os is equal to and above nt/2000, the case is reverse. even if we write ascii code, it
gets converted to unicode strings and after doing all manipulations get reconverted to
ascii.

there might be some circumstances where we'll have necessities to convert a unicode text
file to ascii file. this happens if the application is pre-written without handling unicode
and there is only one place where we need a unicode file to be handled.
this code sample demonstrates conversion of a unicode file to ascii type in such
circumstances.

//check if the file is unicode


int isunicodefile(char* szfilename)
{
file *fpunicode;
char l_szcharbuffer[80];

//open the file


if((fpunicode= fopen(szfilename,"r")) == null)
return 0; //unable to open file

if(!feof(fpunicode))
{
fread(l_szcharbuffer,80,1,fpunicode);
fclose(fpunicode);
if(istextunicode(l_szcharbuffer,80,null))
{
return 2; //text is unicode
}
else
{
return 1; //text is ascii
}
}
return 0; // some error happened
}

the above function opens the file using normal fopen method and checks the first byte if it
is unicode or ascii. it returns 2 if it is unicode. the return value can be modified with any
other values and even using enumerated data. this used istextunicode function to check
for the suitability of the text.
//convert the file to ascii type
cstring convertfile(char *szfilename)
{
cstring strtempfilename ;
cstring strinputfilename;
strinputfilename = szfilename;
char temppathbuffer[255];
gettemppath(255,temppathbuffer);
file *fpascii;
cstdiofileex fpunicode;

strtempfilename = temppathbuffer;
strtempfilename += "tempunicodecheck.txt";

if(isunicodefile(szfilename) == 2) {
//open the unicode file
if(!fpunicode.open(szfilename,cfile::moderead|cfile::typebinary))
{
printf("unable to open the unicode file\n");
return strinputfilename ;
}

//create the temporary file


if((fpascii = fopen(strtempfilename.operator lpctstr(),"w+"))==null)
{
fpunicode.close();
printf("unable to open the output file\n");
return strinputfilename;
}

cstring strdata;
while(fpunicode.readstring(strdata))
{
strdata += "\n";
fwrite(strdata,sizeof(char),strdata.getlength(),fpascii);
}
fflush(fpascii);
fclose(fpascii);
fpunicode.close();
return strtempfilename;
}
else
{
return strinputfilename;
}

this second function convertfile,


1. takes the input of a file name
2. checks if it is a unicode file
3. generates an ascii file at the windows\temp directory.

these functions can be used together in an ascii application. especially if the old code was
written with ascii files and if the input file is suddenly changed to unicode file by an
external application.
cfile is the class used for handling files in mfc. this class can be used for creating, reading, writing
and modifying files. it directly provides unbuffered, binary disk input/output services, and it
indirectly supports text files and memory files through its derived classes.

cfile - creating a file:


there are two ways of creating files. one way is to instantiate the cfile object with the file path. this
creates the file. the second way is to call the open function. this also creates the file.

cfile cfile_object( "c:\\test\\codersource_cfile_example.txt", cfile::modecreate|cfile::


modereadwrite);

cfile cfile_object;
cfile_object.open( "c:\\test\\codersource_cfile_example.txt", cfile::modecreate|cfile::
modereadwrite);

the first parameter to both the functions (cfile() constructor and open()) is the physical path of the
file in the disk. the second parameter is an enumerated constant. this specifies the mode of
opening the file object. the above constants modecreate implies "create a new file" and
modereadwrite means "open the file for both reading and writing".

if the file is opened without specifying the mode constant sharedenynone, this file can be
opened in read mode by other programs. this feature will be necessary for text files, logs created
by programs. for creating text files we use cfile::typetext and for binary files cfile::typebinary.

cfile - writing to a file:


the function write is used to write data to the files. the sample code is as follows.

cfile cfile_object;
cfile_object.open( "c:\\test\\codersource_cfile_example.txt", cfile::modecreate|cfile::modewrite);

char szsampletext[100];
strcpy(szsampletext, "sample text for cfile write function example");
cfile_object.write (szsampletext,100);

if there is any need to write text line by line, it is better to use the class cstdiofile.
cfile - reading from a file:
the function read is used to read data from files. the sample code is,

cfile cfile_object;
cfile_object.open( "c:\\test\\codersource_cfile_example.txt", cfile::modecreate|cfile::modewrite);

char szsampletext[100];
uint lbytesread = cfile_object.read (szsampletext,100);

the function returns the number of bytes read from the file. the maximum number of characters
read will be the second parameter of the read function.

cfile - closing the file:


the close function is used to close the file. but the close function need not be called, as the
destructor will automatically call it if the file is open. so when the object goes out of scope, the
destructor calls close function.

carray is a collection template which can store all data types. unlike a normal c++ array this mfc
collection template can grow and shrink depending on the needs. this allows memory to be
utilized efficiently in mfc programs. this template is declared in afxtempl.h. this class can be used
wherever there is a need to store a list of similar typed objects.

this article describes how to add to, access from and remove objects from the mfc collection
template carray.

carray - adding an element:

the member function add can be used to add objects to the array. before using carray template,
carray has to be declared to accept the corresponding type of object. this code sample declares
and uses cstring.

#include <afxwin.h>
#include <afxtempl.h>

void main()
{

cstring l_strvalue;
carray<cstring,cstring> l_carray;

for(int i=0;i< 20; i++)


{

//use the cstring format function to create different values


l_strvalue.format("value %d",i);
//add the formatted cstring to carray
l_carray.add(l_strvalue);

}
carray - accessing the elements:

the member function getat can be used to access the data stored in carray. this returns the object
on the type stored. modifying the value at a given node is carried out by using setat function.

#include <afxwin.h>
#include <afxtempl.h>

void main()
{

cstring l_strvalue;
carray<cstring,cstring> l_carray;

for(int i=0;i< 20; i++)


{

//use the cstring format function to create different values


l_strvalue.format("value %d",i);
//add the formatted cstring to carray
l_carray.add(l_strvalue);

//this part takes care of accessing and printing the data from carray
cstring l_strgetval;
for(i=0;i<=l_carray.getupperbound();i++)
{
l_strgetval = l_carray.getat(i);
printf("%s\n",l_strgetval);
}

carray - removing the elements:

there are two different functions to remove the data from carray. removeall function can be used
to remove all the elements. the removeat function can be used to remove an element from a
specific location.

l_carray.removeall() //this will remove all the elements from carray

l_carray.removeat(10); //this will remove the 10th element from carray

cstring was used in the above example with carray. but all complex data types can be stored in
this mfc collection template.

cstring is a boon to all c++ converts of mfc. good god! it is so much of a pain to use char* in c++.
a small mistake could lead to big mishaps, to any programmer's nightmare. cstring in mfc gives
lot many features to handle strings, weaning away all such bad memories of char *. this article
deals with the usage of cstring in mfc programs. it also tries to give some small code snippets
wherever necessary.

cstring features:

there are some special features in cstring that makes it really unique and attractive. some of them
are

• cstring has no base classes in mfc. so it is light weight.

• cstring is written based on tchar type. it can automatically support unicode as well as
ascii strings.

• cstring implements a reference counting mechanism for copy of objects. this saves a lot
of memory usage.

how to use cstring:

cstring can be initialized by calling one of its constructors. it can be instantiated with or without
parameters. the constructors can accept a char, char*, tchar, wchar types as a parameter. the
string is constructed according to the parameter passed.

in case it is created without any parameters, the string will be empty. values can be assigned with
the use of '=' operator.

cstring strexample;
strexample = "cstring sample value";

to get the length of a cstring, the function cstring.getlength() can be used. this returns the length
of the string as an integer.

string concatenation with a cstring:

string concatenation is very easy with cstring. cstring has an overloaded operator for + sign,
which makes a programmer's life easier.

cstring strexample1 = "sample value ";


cstring strexample2 = "for concat with cstring";

//concatenation with cstring and cstring


strexample1 = strexample1 + strexample2;

//concatenation with cstring and a const char*


strexample1 = strexample1 + "for concat without cstring";

cstring and bstr:

cstring also provides for conversions to bstr with functions like allocsysstring and setsysstring. it
can allocate memory and copy the data into the allocated space for bstr.

cstring strbstrconv = "sample for cstring to bstr";


bstr bstrvariable = strbstrconv.allocsysstring();

string comparisons with cstring:

cstring contains an operator member function for the '=' operator. this operator performs a case-
sensitive comparison. for case-insensitive comparisons the function comparenocase can be
used.

cstring strcomparestring1 = "string compare sample";


cstring strcomparestring2 = "string compare sample";

//using == operator case sensitive cstring comparison


if(strcomparestring1 == strcomparestring2)
messagebox("both strings are equal - case sensitive");

//using comparenocase for case-insensitive cstring comparison


if(strcomparestring1.comparnocase(strcomparestring2) == 0)
messagebox("both strings are equal - case insensitive");

finding a character inside cstring:

cstring supports find for a single character and also strings. it provides searching a character from
the reverse direction.

cstring strfinddata = "finding a character and a string in forward direction";

//find a char 'c'


int lpos = strfinddata.find('c');

//find "string"
int lpos = strfinddata.find('string');

these find functions return the position of the character or string as an integer.

using format functions in cstring:

the format function in cstring can be used to convert different data types into a string type. this
function is similar to the sprintf function.

cstring strformatdata;
int x = 40;
char strdata[] = "test data ";

strformatdata.format("%s %d",x,strdata);

//the output will be "test data 40"

the above is only a small list of the capabilities of cstring. there are a lot more of features
available with cstring. not all of them can be explained. more of those features will become clear
when one starts using it.

http://www.codersource.net/cpp_stream_operators.html