Historically classes are what distinguishes c++ from c; c++ began as an extension of c which includes classes, now the difference is much greater and c++ is generally a more advanced language than its parent but it is hard these days to imagine programming large programmes in a language without classes. At the simplest classes play a similar role to functions in spliting the programming tasks up and in helping to keep the programme neat and easy to modify.
Classes are a user defined datatype. Think back to vectors, once you define a particular vector, what is called an instance of a vector, v say, then there are vector methods available like size so that v.size() returns the size of the vector, or at which returns the i element. With a class you define the methods that an instance of the class will have.
Here is a class called Bop stored in a file called Bop.cpp.
Bop.cpp
7.1
1 #include<string> 2 #include<iostream> 3 4 using namespace std; 5 6 class Bop 7 { 8 public: 9 Bop(); 10 Bop(string word); 11 void speak(); 12 int howMany(){return counter;} 13 void setHowMany(int counter){this->counter=counter;} 14 private: 15 string word; 16 int counter; 17 }; 18 19 Bop::Bop() 20 { 21 word="bop"; 22 counter=0; 23 } 24 25 Bop::Bop(string word) 26 { 27 this->word=word; 28 counter=0; 29 } 30 31 void Bop::speak() 32 { 33 cout<<word<<" "; 34 counter++; 35 }
This programme has no main statement and can't be run on its own, next we are going to write a programme which will include this one in much the same way as the standard packages are included. It is possible to compile the programme by writing g++ -c Bop.cpp, but compiling it won't produce a binary which can run: the -c tells the compiler not to expect a main statement and therefore not to try to produce a executable, in fact it produces something called an object or .o file, but for now the only point in compiling this file separately is to check that it is syntactically correct.
Before discussing how the class works, lets use it in a programme.
DoBop.cpp
7.2
1 #include<iostream> 2 #include<string> 3 #include<cstdlib> 4 5 #include"Bop.cpp" 6 7 using namespace std; 8 9 int main() 10 { 11 12 int loopN=100; 13 14 Bop bop1; 15 Bop bop2("bip"); 16 17 for(unsigned int i=0;i<loopN ;++i) 18 if(rand()%2==0) 19 bop1.speak(); 20 else 21 bop2.speak(); 22 23 cout<<endl; 24 25 cout<<bop1.howMany()<<" Bops and "<<bop2.howMany()<<" Bips"<<endl; 26 27 }
First, notice that the file containing the class definition has been included at line 7.2.5; this is just like the earlier preprocessor commands including the packages iostream and so on, but here there is double quotes instead of angle brackets, basically this tells the preprocessor to look for the Bop.cpp programme in the current directory, rather than where ever it stores the packages. You might worry about the includes at the start of Bop.cpp, it looks like iostream and string are going to be included twice, in fact, the preprocessor is clever enough to deal with this and it is useful to have all the includes Bop.cpp needs in Bop.cpp so that it can be compiled separately.
Anyway the key lines are lines 7.2.14/15 where two instances of Bops are made, much the same way as we would make two ints or two vectors. The first isn't initialized, there is no bracket after bop1, the second is initialized with the word Bip. Back in the class definition, we have the class statement at the top in line 7.1.6, this says that a class called Bop is going to be defined. Inside the curly brackets are a list of all the methods and variables the class will have, some are private and can't be accessed from outside the class, some are public and can. The syntax is easy to get wrong, the class definition block starts with a curly bracket at line 7.1.7 and ends with a curly bracket followed by a semicolon at line 7.1.17, missing the semicolon can lead to very confusing errors. The words public and private are followed by colons.
In the Bop class there are two constructors, these give the code that is run when an a Bop is declared. As with other methods the constructors are prototyped in the class definition block but the actual code is given outside the block, so the prototype for the default constructor, the one with no argument, is at line 7.1.9 whereas the actual code is at lines 7.1.19-23. Notice that there is a Bop:: to indicate that this definition is for something in Bop. The other construct has an argument, a string: it is prototyped inside the class definition and, again, the actual code is given outside the definition with a Bop:: to indicate what is being defined.
In the actual programme, when bop1 is declared without being initialized at line 7.2.14 the default constructor is called and so the word inside bop1 is set to bop and counter to zero. The only subtly inside the constructor is the use of this->; basically there are two word, the one in the argument and the one declared at line 7.1.15 in the class definition. The one in the argument is the more local one, so if you write word it will refer to the word from the argument, this->word is the one from class definition. The only way to call the default constructor is to declare without initializing, surprisingly Bop bop1(); gives an error. At line 7.2.15 bop2 is declared and initialized using the other constructor, the one that takes a string as an argument, hence bop2 has its word set to bip, its counter is also set to zero.
The speak method is prototyped at line 7.1.11 and the actual definition is given at lines 7.1.31-35; it prints out word and advances counter by one. Apart from the constructors, all the methods have the same syntax as functions, they have a return type, a name and an argument list, the difference is that it is prototyped in the class definition and is defined with the Bop:: in front of the name. The difference between a function and a method is that a method belongs to the instance of the class, to the object, so line 7.2.19 is bop1.speak(), it calls bop1's speak method, so it will print out bop1's word and advances bop1's counter.
There is also a method for returning the value of the counter: howMany at line 7.1.12: it is defined in the class definition rather than being prototyped there and being defined later, this is because it only has a one line definition, it is possible to give all the definitions in the class definition, but this would make it harder to read what the class does, the usual thing is to have the definitions separate if they are more than one line long. There is also the method setHowMany defined for setting the counter, this isn't ever used in the programme, but reflects the way programmes with classes are often written, often we write the important classes first and give them the obvious methods: once the classes are written writing the programme itself is easy, but it may not actually use all the methods. Often, the same class with be reused in some later project.
Here is another example: it is a polynomial class which stores polynomials as a set of coefficients in the private vector called coefficients. One awkward thing about polynomials is that all polynomials have at least one coefficient since the zero polynomial is regarded as having constant coefficient zero. In the class, there are three constructors, but they are all designed to make sure coefficients is not empty; the default constructor at line 7.3.13, for example, gives a coefficients vector holding just the value zero and corresponds to the zero polynomial. Polynomials are slightly awkward to print out as well and a print method has been defined to handle this, even printing out x is awkward since you don't write the one in x^1; to make this a bit clearer printing out powers of x has be separated out in to a separate method xToI. However, there is no reason xToI would be needed from outside the class, so it is listed as private; this means that it can be called inside the class, for example, at lines 7.3.77/84 in the print method but f.XToI(i) called from the main programme will give an error.
Polynomial.cpp
7.3
1 #include<iostream> 2 #include<fstream> 3 #include<cmath> 4 #include<vector> 5 #include<string> 6 #include<cstdlib> 7 8 using namespace std; 9 10 class Polynomial 11 { 12 public: 13 Polynomial(){coefficients.push_back(0);} 14 Polynomial(vector<double> coefficients){this->coefficients=coefficients; if(coefficients.size()==0){coefficients.push_back(0);}} 15 Polynomial(string fileName); 16 double operator()(double x); 17 Polynomial differentiate(); 18 int degree(){return coefficients.size()-1;} 19 void save(string fileName); 20 void print(); 21 private: 22 vector<double> coefficients; 23 void xToI(int i); 24 }; 25 26 Polynomial::Polynomial(string fileName) 27 { 28 ifstream coeffs; 29 coeffs.open(fileName.c_str()); 30 31 while(!coeffs.eof()) 32 { 33 double entry; 34 coeffs>>entry; 35 coefficients.push_back(entry); 36 } 37 38 coeffs.close(); 39 40 } 41 42 double Polynomial::operator()(double x) 43 { 44 45 double fOfX=coefficients[0]; 46 47 for(unsigned int i=1;i<coefficients.size();i++) 48 fOfX+=coefficients[i]*pow(x,i); 49 50 return fOfX; 51 } 52 53 Polynomial Polynomial::differentiate() 54 { 55 if(coefficients.size()==1) 56 { 57 Polynomial zero; 58 return zero; 59 } 60 61 vector<double> newCoeffs; 62 63 for(unsigned int i=1;i<coefficients.size() ;++i) 64 newCoeffs.push_back(i*coefficients[i]); 65 66 Polynomial newPoly(newCoeffs); 67 68 return newPoly; 69 } 70 71 void Polynomial::print() 72 { 73 74 cout<<coefficients[coefficients.size()-1]; 75 76 if(coefficients.size()!=1) 77 xToI(coefficients.size()-1); 78 79 80 for(unsigned int i=coefficients.size()-2;i>0;--i) 81 if(coefficients[i]!=0) 82 { 83 cout<<"+"<<coefficients[i]; 84 xToI(i); 85 } 86 87 if(coefficients[0]!=0) 88 cout<<"+"<<coefficients[0]; 89 90 cout<<endl; 91 92 } 93 94 95 void Polynomial::save(string fileName) 96 { 97 ofstream file; 98 file.open(fileName.c_str()); 99 100 for(vector<double>::iterator it=coefficients.begin();it!=coefficients.end() ;++it) 101 file<<*it<<endl; 102 103 file.close(); 104 } 105 106 void Polynomial::xToI(int i) 107 { 108 if(i>1) 109 cout<<"x^"<<i; 110 else 111 cout<<"x"; 112 }
One of the methods, differentiation at line 7.3.17 and lines 7.3.53-69, returns another Polynomial; this can be assigned to an instance of Polynomial in the main programme, ignoring for now how f is constructed, look at g below at line 7.4.20 in the DoPolynomial.cpp programme; a Polynomial is declared and assigned the value f.differentiate(). It should be noted that if the class contains pointers, copying it can cause problems and sometimes the assignment operator = needs to be overloaded, we have no pointers here so there is no problem.
One nice thing is that the class has a method overloading the () operator, this is done at line 7.3.16 and at line 7.3.42-51; you name the method operator() and then have an argument list as normal: the programme will then expect these arguments inside the bracket. What this means is that f(2), for example, is the polynomial evaluated at x=2.
DoPolynomial.cpp
7.4
1 #include<iostream> 2 #include<string> 3 4 #include "Polynomial.cpp" 5 6 using namespace std; 7 8 int main() 9 { 10 11 Polynomial f("fCoeffs"); 12 13 cout<<"Degree of f="<<f.degree()<<endl; 14 15 cout<<"f(x)="; 16 f.print(); 17 18 cout<<"f(2)="<<f(2)<<endl;; 19 20 Polynomial g=f.differentiate(); 21 22 cout<<"g(x)="; 23 g.print(); 24 25 }
The DoPolynomial.cpp programme uses a constructor that reads the coefficients in from a file. This constructor is prototyped at line 7.3.15 where we see it has one argument, a string, and it is defined at lines 7.3.26-40; using files also requires the fstream, for file stream, package is included, this happens at line 7.3.2. The syntax for dealing with files is very natural: at line 7.3.28 an ifstream, standing for input file stream, object called coeffs is declared. ifstream has a method called open which opens a file; the file is passed as a c-style char array, which is annoying, but the string class has a method which converts strings into these char arrays, this is what happens with fileName.c_str(): fileName is converted and passed to open which then opens the file with the name stored in fileName. This process can be made safe, but as written here the programme fails very badly if there is no file with that name: it hangs and seems to use lots of memory.
Inside the constructor there is a while loop at lines 7.3.31-36; this reads entries from the file one-by-one using a stream operator, much as for cin; coeffs>>entry; takes the next item in the file opened by the ifstream object coeffs and assigns it to entry. It does this until it reaches the end of the file; in coeffs.eof() the eof stands for end of file and this method returns true when the file opened by coeffs is finished: !coeff.eof() is true until the end of the file is reached. In the DoPolynomial.cpp example the constructor is called with the string fCoeffs so there should be a file in the same directory as the programme with this name. The stream operator is clever about taking the next non-white string, so it doesn't matter if the coefficients in the file are written in a column or a row; it isn't clever about spotting the end of the file so it is important that the file has no blank lines after the last data entry.
The input file could look like
fCoeffs
7.5
1 3 4 5 0 2
or
7.6
1 3 2 4 3 5 4 0 5 2
Outputting and inputting is quite complicated; the example described above and the one for outputting given at lines 7.3.95-104 only serve to illustrate a kind of simplest case scenario for doing file input and output; nonetheless copying these example will be enough for lots of applications.
The c++ language has only been introduced here; there is a lot more to do, classes can be built on classes for example and even compiling can be done in a more sophisticated way by splitting off the linking stage and using makefiles. However, it is hoped that the basics given here will be enough to make it easy enough to learn more by looking things up. Most importantly, however, the way to get good at programming is to programme.
Exercises 7