|
|
|||
![]() |
Department of Engineering |
| University of Cambridge > Engineering Department > computing help > Languages > C++ > C++crash course |
Until now we've been avoiding syntactic details, concentrating on concepts. Now we'll try to implement these concepts, revisiting the Standard Library, exceptions and casting before looking at some common errors.
Standardised when ANSI C++ arrived, it includes what was once call "the Standard Template Library" and is useful even for simpler things. It offers new data types (like sets and queues) and routines (like sort and random_shuffle, etc). With it you can avoid writing loops and lots of other code too. However, the notation is a little scary at first. Here for example is the code to create then sort a list of integers
list<int> x; sort(x.begin(),x.end());
Note that it uses templates and objects. If you can cope with this then you can already do quite a lot just by choosing between options
x;
(x.begin(),x.end());
This flexibility isn't at the expense of safety. Behind the scenes, functions still care about whether they're being given the right types of arguments, but the compiler selects the appropriate version for you. Here's another simple example - a complete program this time. It "transforms" a string character by character without explicitly using a loop.
#include <cctype> // for toupper()
#include <iostream>
#include <string>
#include <algorithm> // for transform()
using namespace std;
char capitalize(char c)
{
return toupper(c);
}
int main()
{
string text("Mixed Case String");
cout << "text=" << text << endl;
transform(text.begin(),text.end(),text.begin(),capitalize);
cout << "Now text=" << text << endl;
return 0;
}
The library has support for complex numbers, vectors, lists, etc. vectors are a replacement for arrays. Here's an example showing how you can use them like arrays, and also do a lot more - the vector can expand, you can find out its size, and you can use more standard algorithms like reverse on it
#include <iostream>
#include <vector> // needed for vector
#include <algorithm> // needed for reverse
using namespace std;
int main() {
vector<int> v(3); // Declare a vector of 3 ints
v[0] = 7;
v[1] = v[0] + 3;
v[2] = v[0] + v[1];
v.push_back(99); // make the vector one element bigger.
for (int i=0;i<v.size();i++)
cout << v[i] << endl;
reverse(v.begin(), v.end());
for (int i=0;i<v.size();i++)
cout << v[i] << endl;
}
The following code removes spaces in a string (a task asked about in C++ interviews). Note that the remove command doesn't remove elements, it just shunts them to the end of the string, returning a pointer to the first of the unwanted elements. In this example, that pointer is given to erase, which removes the unwanted elements
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string s="spaces in text";
cout << s << endl;
s.erase(remove(s.begin(), s.end(), ' ' ), s.end() ) ;
cout << s << endl;
}
The library also has a function that conditionally copies items elsewhere. It's called remove_copy_if
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
using namespace std;
int main() {
vector<int> V;
V.push_back(0);
V.push_back(1);
V.push_back(2);
vector<int> V2;
//copy the elements from V that are not <1 into V2
remove_copy_if(V.begin(), V.end(), back_inserter(V2), bind2nd(less<int>(), 1));
}
The following code uses a 'stack' of complex numbers with integer
components.
#include <deque>
#include <stack>
#include <complex>
#include <iostream>
using namespace std;
int main() {
stack< complex<int> > S; // the space between the '>' signs matters
complex<int> a(1,2), b(4,7);
S.push(a);
S.push(b);
S.push(a);
S.pop();
cout << "Top element is " << S.top() <<endl;
S.pop();
S.pop();
cout << "Number of elements is now " << S.size() <<endl;
cout << "Stack empty? (1 means yes) " << S.empty() <<endl;
};
Some of this may look unfriendly at first. The good news is that once you can "transform" a string of characters, the orthogonal design makes it easy for you to transform a vector of doubles, or sort it, search it and permutate it. If you don't want a stack, you can use a queue instead by changing a line or 2. So the time you invest is soon recouped. For examples see
#include <deque> [0]
#include <algorithm> [1]
using namespace std; [2]
int main() [3]
{ [4]
deque<int> Q; [5]
Q.push_back(3); [6]
Q.push_front(1); [7]
Q.insert(Q.begin() + 1, 2); [8]
sort(Q.begin(), Q.end()); [9]
Q[3] = 0; [10]
} [11] |
Here's an example. Note that main doesn't call
function g directly but can still
catch exceptions that g throws.
#include <iostream>
using namespace std;
void g()
{
throw 42;
}
void f() {
g();
}
int main ()
{
try {
f();
}
catch (int codenumber) {
cout << "Caught error " << codenumber << endl;
}
}
There are many types of standard exception - they form a class hierarchy
of their own.
try {
// code
}
// catch a standard exception
catch(std::exception& e){
// order of the catch clauses matters; they're tried
// in the given order
}
// catch all the other types
catch(...) {
// cleanup
throw; // throw the exception up the hierarchy
}
The following shows how to invent your own exceptions.
#include <iostream>
#include <string>
using namespace std;
class DivZero {
public:
DivZero() { message="Division by Zero!";}
void print() const { cout << message << endl;}
private:
string message;
};
int divide (int top, int bottom)
{
if (bottom == 0)
throw DivZero();
return top/bottom;
}
int main(int argc, char* argv[])
{
try
{
cout << divide (3,2) << endl;
cout << divide (3,0) << endl;
}
catch (DivZero error)
{
error.print();
}
}
This code catches divide-by-zero errors. As an exercise you could create
another exception type and add another catch clause to treat
as errors integer divisions that have non-zero remainders.
Some languages let you mix variables of different types or use one variable to store different types of things. For example in some some languages something like the following sequence is possible
x=1; x="one"; x=x+1 x=x*2;
The result might be "one1one1". C++ is much fussier.
Sometimes you need explicitly to convert a variable from one type to
another.
For example,
int seven=7,two=2;
float answer;
answer=seven/two;
gives an answer of 3 rather than the perhaps intended 3.5, because when
2 ints are combined,
the answer is always an int. If one of the ints can - just for the
purposes of the calculation - be converted to a float, the answer will
be 3.5.
C++ is strict about converting from one type to another. When the 2 types are closely related (int and float for example), casting's easy. Parent and child objects are also closely related. Even then you still have to be careful - see the C++ and simple type conversion page for worrying examples. Note that C had a way of forcing conversion, but it's not recommended in C++. Try to use the least dangerous (most specific) casting alternative from those below
int seven=7, two=2; float answer; answer=seven/static_cast<float>(two);All of the casting operators have the same syntax and are used in a manner similar to templates. See Casting in C++ for details.
#include <iostream> [0]
using namespace std; [1]
int main() [2]
{ [3]
const int c = 28; [4]
const int* a; [5]
a = &c; [6]
cout<<"a points to "<<*a<<endl; [7]
int* b = const_cast<int*>(a); [8]
*b = 23; [9]
cout<<"b points to "<<*b<<endl; [10]
cout<<"c="<<c<<endl; [11]
cout<<"a points to "<<*a<<endl; [12]
return 0; [13]
} [14] |
Error 226: "set1.cc", line 16 # No appropriate function found for call of
'operator <<'. Last viable candidate was "ostream &ostream::operator
<<(char)" ["/opt/aCC/include/iostream/iostream.h", line 509]. Argument of
type 'class set<int,less<int>,allocator>' could not be converted to
'char'.
cout << "s1 = " << s1;
^^^^^^^^^^^^^^^^^^^^^
The ^^^^ symbols show which part of the line is at fault. In this
case the compiler can't narrow down the problem, so next read the first
(easiest) part of the error message. There's something wrong with the
use of <<. Remember that in C++ many functions can be invoked by
an operator - it all depends on the number and type of operands. In
this case s1 (a set) isn't something that there's a matching function
for - hence the error message. The compiler, trying to be helpful,
gives the next best option -
a function tied to << that can deal with a char.
Another example: the line double d = sqrt(4); produces the
following error
because the integer given to sqrt has to be promoted to
a real, but the compiler doesn't know what kind of real.
Error 225: "foo.cc", line 6 # Ambiguous overloaded function call; more than
one acceptable function found. Two such functions that matched were "long
double sqrt(long double)" ["/opt/aCC/include/cmath", line 107] and "float
sqrt(float)" ["/opt/aCC/include/cmath", line 57].
<' is used in #include lines,
cout, in template definitions as well as meaning 'less-than'.
This makes deciphering declarations hard. c++decl can
sometimes help. For example, on the Teaching System
c++decl explain "int* &cp"prints "declare cp as reference to pointer to int".
String s;
String s("initial");
String s();
#include <iostream>
using namespace std;
class Test {
int i;
public:
Test() {i=0;};
int add1toi() { i=i+1; return i; }
int add2toi() { i=i+2; return i; }
};
int main() {
Test t1;
cout << t1.add1toi();
cout << t1.add2toi();
cout << endl;
// do the same again, all on the same cout line
Test t2;
cout << t2.add1toi() << t2.add2toi() << endl;
}
On my machine this prints out
13 32Why? With t1, add1toi() is run then the result printed. Then add2toi() is run and the result printed. i is initialized to 0, so unsurprizingly 13 appears. With t2, add2toi() is run first, then add1toi() is run, then the result of add1toi() is printed, then the result of add2toi() is printed. Even though the result of add1toi() has to be printed before result of add2toi(), that doesn't mean that add1toi() has to be run before add2toi(). With t1 the semicolon at the end of the first cout line forced the issue - it's a "sequence point".
#include <iostream>
using namespace std;
int main ()
{
int i = 1;
int& ref1 = i;
const unsigned& ref2 = i;
i = 2;
cout << "i=" << i << ", ref1=" << ref1 << ", and ref2=" << ref2 << endl;
}
Note that it compiles ok, mostly likely without a warning. Before running it, guess what it will print out. ref1 behaves as expected - the types match - but ref2 is more problematic. If ref2 is going to be an alias for i it needs to be the same type. Without the const it wouldn't compile, but here the compiler creates a temporary "unsigned int" object with the current value of i and makes ref2 an alias for it, so subsequent changes to i will only affect ref1, not ref2 (which after all is supposed to be a constant). I find that confusing.
See the debugging handout or the CERT C++ Secure Coding Standard
| | C++ | Languages | computing help | |