C++ Workshop II
The next chapter of the C++ book gives a whirlwind tour of the basics
of the C++ syntax that are necessary for object oriented programming.
This section is almost like jumping into the pool and hope you get
acclimated to the water. C++ is not a good language for a beginner,
and this section makes it clear why. To really get a handle on this
section without prior programing experience is a very tall order.
In parsing this chapter, keep a few things in mind. Much of the code
snippets given as an example in this section would be written outside of
your main program, as library files and header files. While we discussed
the preprocessor directive previously in both the C and C++ workshops,
we hadn't looked at them in detail. For those not familiar with these
construction, which are core to C programing, a few words are in order
about them.
There are two different external file types that your compiler uses to
build your program, aside from the file that your main program code is
stored in. They are called header files or libraries. Most normally,
there is a header file for each library. They also share part of their
name according to a naming convention. Standardized libraries, such
as iostream have header files. The libraries can be static or shared.
Either type is unlinked C bitcode, or objects files in C language
parlance. A group of object files archived together, usually with the
'ar' program, form these libraries. Thee files normally exist in
/usr/lib or /lib and might have a suffix of '.so'. It is sometimes
necessary to compile a program with the -I option to tell the compiler
to include a library, even though the preprocessor include line is
designating the header file. Usually if the header file is math.h then
the library is libmath.
Declaration of variable types, function names, and preprocessor constants
which apply to the library files are header files. These are what
is referred to within your program, such as <iostream> or <cassert>.
In C these are .h files such as /usr/include/strings.h. In C++ they
might not, as in /usr/include/g++/iostream. Take a peek and look at
some of your header files. Becoming familiar with their syntax will
greatly improve your hacking skills.
The reason to discuss this, and we should really talk even more about
this, is that the examples in the C++ text implicitly and explicitly
imply that the code being written is for library objects, and written in
external files. When we discuss making classes, the most logical place to
make a new class is in an external file, compiled to an C object for use
later by someone else's program. Calling class objects is not a problem.
A simple explanation of the “new” keyword and type defining the class
structures puts you on your way. But once we discuss Constructors,
Destructors, data hiding, public interface design, private data and
implementation design, operator overloading, and function overloading
and polymorphism, then we are talking writing modules for others to use,
or even for us to use concurrently to the main code we are writing.
Section 2.1 is a big section which walks through much of the C++ basic
design, and I will go over it piece by piece over the next couple of days.
In organizing the concepts of C++ which are being introduced we can view each of them separately, but studying them it is essential to keep in mind that they are designed to function together in the construction of programs which are easier to debug, which allow for cleaner overall syntax, and which encourage creation of reusable code.
We shall explore:
Data Types (build in and user created)
Pointers and References (and their subtle deference)
Manual Memory Allocation and the 'new' and delete keywords
Class Declaration
Class Definition
Private data
Public data
Object instantiation and access, Class typedef
Class Constructors
Class Destructor
Function or Method Overloading
Operator Overloading
External extension of definitions
Copy Constructors
at the end we'll try to actually make the example class, which is an extended array.
1) Data Types: DATA DATA DATA, everything is DATA
As we know, C and C++ have built in data type which each variable and object the language needs to be defined as. Both C and C++ are typed language.
We have char, int, double, float, pointer, and so on, a complete list of which is available around the net. A pointer stores an address of another C++ object. Different hardware and software platforms also have different sizes that it allocates for these data types, creating an inconsistency which, as an aside, the Free Software community has tried to address with the creation of the glib library, part of the GTK project.
What is less apparent is that with regard to a computer, everything is data. We have the data that we create and manipulated with instructions, but the instructions themselves are a form data which we can package up and save for later use, and feed to the CPU at will. When the CPU runs out instructions the resulting actions are further instructions that can be packaged and saved as data. In a word, everything is data, and how we package that data is what separates one programming language from the next.
This concept is covered extensively in NYLXS “Introduction Programming with Perl” class and since this is an advanced topic, we won't get much further into this. But we will review the basics of data types and then look at the new feature that C++ gives which C didn't have in such a generous way, the ability to easily create new data types easily and in a reusable fashion.
An integer in the C family on the 32 bit Intel clone architecture is defined as a marked space in memory of 32 bits in size to represent both positive and negitive numbers. On the new 64 bit architecture that many of you might have, I don't know if this still holds true since the word size of a those machines is 64 bits or 8 bytes.
when you use the declaration
int a = 3456;
The computer your program sets aside 32 bits, 4 bytes, of space in ram and puts the binary representation of that value in that space. The leftmost bit is usually the signed bit determining whether the number represented within is either positive or negative. A signed integer can therefor have a maximum value of 2,147,483,647 positive or negative. beyond that you must use a long int, which on 32 bit architecture actually won't help you, or to use external libraries with other data types defined.
By default, C allows certain syntax with a data type. It will automatically translate it, for example, into a char that will print its representation for functions such as printf or in C++ the cout object:
printf (“%d\n”, myint);
cout << myint;
It can be combined with operators
it can be used with the assignment operator to fill or initialize its space with data.
int myans,myx = 6,myy =12;
It can be combined with arithmetic operators and have results assigned accordingly.
myans = myy + myx;
Two of them can be compared.
while (myy < myx){
...
...
}
They can be auto incremented
myy++;
++myy;
and so on....
One data type is actually a serial arraignment of data, that is an array.
int myarray[100];
This defines an array of 100 elements indexed from zero to one hundred.
myy = myarray[4];
assigns the fifth element of our array to our integer variable myy. One thing you can not do with an array data type is use an assignment operator on the entire array object.
myarray[] = myarray2[]; //THIS IS AN ERROR
C++ allows up to define our own data types that have all the properties of the built in ones. It uses the class mechanize, operator overloading, and the “new” keyword to accomplish this.
2) Pointers and References – Where did I PUT THAT!
When we create data for our program, we ask the program to insert memory
into RAM and to retrieve or assign the data from that memory location
for use. Internally the program keeps track of the symbols and the
memory locations. In fact is you run the program “nm” on a C or C++
binary it will tell you all the symbols that it has in that binary.
ruben@www2:~/cplus> nm file3|less
0804a210 A __bss_start
08048a84 t call_gmon_start
0804a29c b completed.1
0804a0bc d __CTOR_END__
0804a0b4 d __CTOR_LIST__
U __cxa_atexit@@GLIBC_2.1.3
0804a204 D __data_start
0804a204 W data_start
08048ed0 t __do_global_ctors_aux
08048ab0 t __do_global_dtors_aux
0804a208 D __dso_handle
......
But we can also create memory locations that are assignable, and store
a representation of that memory location directly into a variable that
only stores the memory location as data, not the data itself. In c
and C++ this is called pointers and we can use the following syntax to
create them.
int *pt = &myint;
This declares the pointer to an int variable called pt which stores the
address for myint. The syntax int * in a declaration (and ONLY in a
declaration) says make a pointer to an int. The & syntax in front of a
variable myint says don't return the value of the variable, but return
the address of the data stored in the variable itself.
There are functions that return only pointer data. Those functions make
it possible to access memory without the declaration of variables at all.
There are also declarations that can be made in C and C++ which can
create variables without variable names either.
int (*pt)[10];
This declares a pointer (pt) to an array of 10 integers.
char (*p)[10][100]
This is a pointer which addresses an array of 10 pointers (implied)
to arrays of 100 chars each. Commonly this is know as a point to an
array of 10 strings. The symbolic variable name for an array often
gets automatically cast as a point type.
Most string functions in C return a char pointer for example
char * strtok(char *s1, const char *s2);
This would return an address of a char, which in theory would represent
an array of chars. In use it would look like this
char * spt;
char wd1[100] = 'hello world', wd2[100] = ' ';
spt = strtok(wd1,wd2);
printf(“%s\n”, spt);
3) Manual Memory Allocation and the 'new' and delete keywords
C++ makes it very convent to create dynamically allocated memory which
is accessed by pointers. We might call these anonymous pointers because
they do not point to any variables, just defined memory. We do this
with the key word “new”.
int *pt = new int(124);
This creates a new int pointer called pt and assigns to the memory
pointed to by pt with the integer value 124.
delete pt;
deletes the anonymous pointer pt.
int *pt = new int[100];
This declaration creates a new int pointer to an array of 100 integers,
with no data assigned yet to that block of memory.
delete [] pt;
deletes the entire array pointed to by pt and then undefines pt.
4) Class Declaration – Type Type Type
When using C++ one of the key reasons for choosing this language involves
our ability to roll our own data types. While with C programming we
can use typedef with unions and structures, the class system with C++ is
much ore extensive and flexible, giving genuine data type ability to the
programmer, a flexibility previously reserved for the elite programmers
who could hack the core C programming interface, probably involving a
bit of assembly to boot.
The first thing you need to do when making a class is to declare it,
similarly to how one needs to declare a function. Declaring the class
alerts the compiler of the new symbols that will be used in further code.
The declaration does not include the definition. that can be done later.
All of this most commonly takes place in an external file so that it
can be reused, or even a .h header file.
class IntArray;
once we make this declaration, we can declare objects of this class as
we could with any other data type
IntArray array_object;
or even declare pointers to it such as:
IntArray *pt_array_obj = new IntArray;
After declaring the class itself, we need to now declare the internals
of our class, or its body.
class IntArray{
public:
bool operator==(const IntArray&) const; //This is a declaration
bool operator=!(const IntArray&) const; //This is a declaration
bool operator=(const IntArray&); //This is a declaration
int size() const; //This is a declaration
void sort(); //This is a declaration
int min() const; //these are all declarations
int max() const;
int find() const;
private:
//nothing yet
}
All this is so that later we can use the IntArray as a data type
IntArray myarray;
and we can then use the IntArray objects as any other variable
myarray[] = a[];
Then we can use the
Class Definition dot syntax to gain access to the public interface
myint = myarray.size();
or if we use a pointer as follows
IntArray * parray = new IntArray;
myint = parray->size();
5) Class Definition:
Once we declare the class type by also declaring the internals and externals of a class, and by using the keywords 'class', 'public:' and 'private:' we need to define the class its qelf, or essentially code the class. All the methods that we declared now need to be coded, either either as 'inline' methods or normal methods. For the purposes of object oriented languages like C++, methods are a subclass of functions, like functions in every way accept scoped to class objects. Inline functions are quick jobs that can be simply defined and which the compiler can replace at the call point, reducing the number of times the code has to be loaded, interpreted and run in our program, all of which is overhead of normal function or method calls. Functions that are used to access basic data which is otherwise private in a class are ideally suited for inline functions. Methods defined in classes are automatically attempted by the compiler to be compiled inline.
Lets look at the example that the text is using to give us a tour of C++. The text is trying to create an object which wraps the common C type integer array in order to give it more flexibility and features. A common beginners error in C programming is the attempt to assign one array to another array with code simlar to this
a[] = b[]; //ERROR
This is particulary common when working with strings, which are char arrays
char quote[255] = “A society which can not archive, can not share digital materials can not remain politically Free in the digital era”; //legal as an assignment;
char quote2[255];
quote2 = quote; //ERROR
If we wrap arrays in a new class, creating a new data type, we might first declare it as above, preferably in a header file:
class IntArray;
Now we declare the classes members, defining the internals of the class. Unlike functions (but like C structs) we need to end the body of a class definition with a semi-colon.
We define both public and private components of our class. the public section is the API that we are building. The private area is the internal machination of the class. The inner machinery that the programmer using our class doesn't need to become confused with.
#ifndef TOP
#include <iostream>
#endif
#ifndef INTARRAY_H
#define INTARRAY_H
#endif
class IntArray{
public:
static
//constructors
explicit IntArray(int size = DefaultArraySize);
IntArray( int *array, int array_size);
IntArray(const IntArray &rhs);
//destructor
~IntArray(){ delete [] ia };
//operators
bool operator==(cont IntArray&);
bool operator!=(const IntArray&);
//assignment operator
IntArray operator=(const Intarray&);
int size() const;
void sort();
int min() const;
int max() const;
int find( int value) const;
private:
static const int DefaultArraySize = 255;
int _size;
int *ia;
}; //remember the colon
This defined Class has several declared methods, which can later be defined. The
purposes of these methods can be classified and we will be looking more closely at them in the coming sections.
6) Private Data
Much of the internal work your class does, and the data it keeps track of is the domain of the class objects, and which have what is commonly know as no consumer usable parts within. These internal segments of your object code are best hidden from the users space, and is safer for the user not to monkey with. This data is designated by the private tag, and this technique is called information hiding. In our example we have only two data items which are no accessible to the user directly, int _size and int * ia .
private:
int _size;
int *ia;
Our job is to do the accounting of out objects size and the int array which we are wrapping in our objects. So direct access doesn't make much sense. We access these data's in a variety of ways which we will build examples of, mostly through a well documented public interface.
7) Public data
The public interface is, at least in our example, more complicated. We will need accessory methods, methods to gain access to otherwise private data, and we will need constructors, destructors, operator overload methods, and copy method. We've declared a few of these already in our class definition, but we haven yet defined the methods themselves. Our public interface is the application interface we are building for our new data type. Special attention needs to be given to the planning of this interface if you expect wide adoption of the class.
At this point, a good example program is needed. While I'm not that masterful with language, and the texts are as clear as mud on many issues, the following has been put together in an effort to create a working sample C++ program for us to work with.
Our C++ program is in three files which are located on the NYLXS site and posted here for convince.
http://www.nylxs.com/docs/workshops/intarray.h.html
1 #ifndef INTARRAY_H 2 #define INTARRAY_H 3 #endif 4 5 using namespace std; 6 7 class IntArray; 8 9 10 class IntArray{ 11 12 public: 13 //constructors 14 //explicit IntArray(int size = DefaultArraySize); 15 IntArray(); 16 explicit IntArray(int); 17 IntArray( int *array, int array_size); 18 IntArray(const IntArray &rhs); 19 20 //destructor 21 22 ~IntArray(){ delete [] ia; }; 23 24 //operators 25 bool operator==(const IntArray &); 26 bool operator!=(const IntArray &); 27 //assignment operator 28 IntArray& operator=(const IntArray &); 29 //index operator 30 int& operator[](int); 31 32 int size() const; 33 void sort(); 34 35 int min() const; 36 int max() const; 37 38 int find( int value) const; 39 40 41 42 private: 43 static const int DefaultArraySize = 255; 44 int _size; 45 int *ia; 46 }; //remember the colon 47 48
This is the example header file
http://www.nylxs.com/docs/workshops/file4.C.html
1 #ifndef TOP_H 2 #define TOP_H 3 #include <iostream> 4 #include <stdlib.h> 5 #endif 6 #ifndef INTARRAY_H 7 #include "intarray.h" 8 #define INTARRAY_H 9 #endif 10 using namespace std; 11 IntArray:: 12 IntArray(){ 13 _size = DefaultArraySize; 14 int * tmp; 15 ia = new int[_size]; 16 for (tmp = ia; tmp < (ia + _size); tmp++) 17 *tmp = 0; 18 } 19 20 IntArray:: 21 IntArray( int sz ){ 22 int * tmp; 23 if(sz < 0){ 24 cerr << "Size must be greater than 0 and an integer\n"; 25 exit (-1); 26 } 27 28 _size = sz; 29 30 ia = new int[sz]; 31 for(tmp = ia; tmp < (ia + sz); tmp++) 32 *tmp = 0; 33 } 34 35 IntArray:: 36 IntArray( int *tmp, int sz){ 37 38 if(sz < 0){ 39 cerr << "Size must be greater than 0 and an integer\n"; 40 exit(-1); 41 } 42 _size = sz; 43 int * tmp2; 44 ia = new int[sz]; 45 for(tmp2 = ia; tmp2 < (ia + sz); tmp2++,tmp++){ 46 *tmp2 = *tmp;//copy tmp to ia 47 } 48 } 49 50 IntArray:: 51 IntArray(const IntArray &rhs){ 52 _size = rhs.size(); 53 ia = new int[_size]; 54 for(int i = 0; i < _size; i++){ 55 ia[i] = rhs.ia[i]; 56 } 57 } 58 59 60 //operators 61 bool 62 IntArray:: 63 operator==(const IntArray &rh){ 64 bool test; 65 int sz = size(), i; 66 if (sz == rh.size()) 67 test = true; 68 else 69 return false; 70 71 for(i = 0; i < 0; i++){ 72 if (ia[i] == rh.ia[i]) 73 test = true; 74 else 75 return false; 76 } 77 return test; 78 } 79 80 81 int& 82 IntArray:: 83 operator[](int index){ 84 if (index < 0 || index > _size) 85 exit (-1); 86 return ia[index]; 87 } 88 bool 89 IntArray:: 90 operator!=(const IntArray &rh){ 91 bool test; 92 int sz = size(), i; 93 if (sz != rh.size()) 94 return test = true; 95 96 for(i = 0; i < 0; i++){ 97 if (ia[i] != rh.ia[i]) 98 return true; 99 else 100 test = false; 101 } 102 return test; 103 } 104 105 //assignment operator 106 IntArray& 107 IntArray:: 108 operator=(const IntArray& rh){ 109 int sz = rh.size(), *tmp, *tmp2; 110 delete [] ia; 111 int * ia = new int[sz]; 112 tmp = ia; 113 for (tmp2 = rh.ia; tmp < (tmp + sz); tmp++){ 114 tmp2++; 115 *tmp = *tmp2; 116 } 117 return *this; 118 } 119 120 inline int 121 IntArray:: 122 size() const{ 123 return _size; 124 } 125 126 void 127 IntArray:: 128 sort(){ 129 //good ole bubble sort 130 int *sorted, last, *index; 131 132 for(index = ia; index < (ia + _size); index++){ 133 for(sorted = index; sorted < (ia + _size); sorted++){ 134 last = *(sorted + 1); 135 if (*sorted > last){ 136 //switch - pushing higher to back 137 last = *sorted; 138 *sorted = *(sorted + 1); 139 *(sorted + 1) = last; 140 } 141 } 142 } 143 } 144 145 int 146 IntArray:: 147 min() const{ 148 int low, *tmp = ia; 149 for(low = *tmp;tmp < (ia + _size); tmp++){ 150 if (*tmp < low) 151 low = *tmp; 152 } 153 154 return low; 155 } 156 157 int 158 IntArray:: 159 max() const{ 160 int high, *tmp = ia; 161 for(high = *tmp;tmp < (ia + _size); tmp++){ 162 if (*tmp > high) 163 high = *tmp; 164 } 165 166 return high; 167 } 168 169 int 170 IntArray:: 171 find( int value) const{ 172 int * tmp = ia; 173 for(;tmp < (ia + _size); tmp++){ 174 if(value == *tmp) 175 return value; 176 } 177 return (-1); 178 } 179 180 181 182 183 184
This is the example library file and this is the test program that uses our classes
http://www.nylxs.com/docs/workshops/file4_main.C.html
1 #ifndef TOP_H 2 #include <iostream> 3 #define TOP_H 4 #endif 5 6 #ifndef INTARRAY_H 7 #include "intarray.h" 8 #define INTARRAY_H 9 #endif 10 11 int main(){ 12 IntArray myarray1; 13 myarray1[2] = 400; 14 cout << myarray1[2] << _end; 15 return 1; 16 } 17 18