Escolar Documentos
Profissional Documentos
Cultura Documentos
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() {}
}
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.
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.
void main()
{
window *x, *y;
x = new window();
x->create();
y = new commandbutton();
y->create();
}
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:
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:
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:
here's what the string (char c++ pointer) looks like in memory:
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] );
}
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 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:
int main()
{
int cow(7);
int* p_cow = &cow;
int** p_p_cow(&p_cow);
int*** p_p_p_cow = &p_p_cow;
}
with the above code, we can set the value of cow using p_p_p_cow:
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:
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()
{
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:
int myval(17);
int myvalue2 = 4;
const int* const p_k = &myval;
p_k = &myvalue2; //compile error
*p_k = 32; //compile error
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 :
• 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.
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
• address : a certain address in the stack segment, where the variable is allocated
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
• 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.
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.
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 :
• 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 !
}
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.
what do you think will happen at the end of this code fragment ?
the main idea is to try and not lose the addresses of dynamic variables as you may
eventually not be able to free them.
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.
#include <iostream>
char* tostring(int n) {
char *s = new char[100];
char aux;
int i, j;
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.
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
or
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.
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 = { 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
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);
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;
}
cout << g;
cout << endl << "test for connectivity" << endl << endl;
cout << " input first node : ";
cin >> x;
cout << "input second node : ";
cin >> y;
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.
• lists<>
• stack<>
• queue<>
• deque<>
• 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
• _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>
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>
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()
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>
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()
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>
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>
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>
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>
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 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>
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>
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>
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()
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.
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.
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.
• 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.
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.
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.
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 ;
}
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;
}
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 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 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.
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.
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;
}
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;
//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);
}
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.
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 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.
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 is very easy with cstring. cstring has an overloaded operator for + sign,
which makes a programmer's life easier.
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 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 supports find for a single character and also strings. it provides searching a character from
the reverse direction.
//find "string"
int lpos = strfinddata.find('string');
these find functions return the position of the character or string as an integer.
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 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