C++ started as a C preprocessor (the cfront compile), but by now has become an independent language. And although it is still possible to use plain C code and C's standard libraries, C++ now offers its own libraries for the most important extensions such as input/output, strings, and collections (see Section 13.2.5>). We therefore start all over again with some simple examples.
#include <iostream> using namespace std; int main() { cout << "Hello World" << endl; return 0; }
The code still resembles the original in C, but also shows many differences. First, the standard C++ header files don't have a suffix (since the standard commitee could not agree on one). Second, the namespace directive tells the compiler that we want to use the standard library without explicit qualifiers. C++ solves (as a late addition) C's problem of name clashes between different libraries by introducing namespaces. Without the using directive, we would need to put a std:: qualifier in front of all symbols defined in the std namespace, in our case the symbols cout and endl from the input/output library.
#include <iostream> int main() { std::cout << "Hello World" << std::endl; return 0; }
As another observation, C++ seems to be stricter about the return type of the main function. We have to properly define main as an integer function and must not forget the return statement. However, the most striking line is the print statement which does not even remotely resemble the print statements we have seen so far.
cout << "Hello World" << endl;It reminds more of a UNIX shell command, although the arrows point in the other direction. The analogy is not so far fetched, since the shift operator << indicates that objects are pushed into the standard output stream cout. The last object endl is the newline. C++'s relies heavily on operator overloading to implement type-safe and efficient input and output streams. In its standard incarnation, streams are even template types depending on the underlying character implementation and its stream related properties (or traits). In other words, to understand the simple print statement to its full extend, we have to understand most of the complex C++ features in the first place.
for (int i=0; i<n; i++) { ... }
int main() { string name = "Homer"; cout << "Hello, " << name << endl; cout << "How are you, " + name << endl; string ho = name.substr(0, 2).append(", "); cout << ho << ho << ho << endl; return 0; } Hello, Homer How are you, Homer Ho, Ho, Ho,
string ho = name.substr(0, 2).append(", ");
static void count(int* counter) { *counter += 1; } void main() { int counter = 0; count(&counter); }
static void count(int& counter) { counter += 1; } int main() { int counter = 0; count(counter); }
static void show(const string& s) { cout << s; }
class Person { string _name; int _age; public: Person(const string& name, int age); const string& name() const; void name(const string& name); void printOn(ostream& out) const; };
After this lengthy explanation let's look at the implementation of the class. C++, each method is implemented individually by taking the method signature, qualifying method name with the class name, and following it with the method body. [1]
Person::Person(const string& name, int age) : _name(name), _age(age) {} const string& Person::name() const { return _name; } void Person::name(const string& name) { _name = name; } void Person::printOn(ostream& out) const { out << "name=" << _name << ", age=" << _age; }
The only striking definition is the constructor which uses initializers for the two attributes. These initializers are put between the signature and the constructor body. When a person object is constructed, the attributes are directly initialized with the associated constructor calls. The alternative assignment in the constructor body
Person::Person(const string& name, int age) { _name = name; _age = age; }
first initialized the name string with the default constructor and then assigns the actual name. Besides being more efficient, initializers may be the only option in scenarios where the attribute's class does not support default constructor or assignment operator.
It looks like we are eventually ready to use the new class.
int main() { Person person("Homer", 55); person.printOn(cout); return 0; } name=Homer, age=55
C++ leaves the developer many choices. One of these choices is the memory management. Like C's structures, instances of C++ classes can be allocated on the stack or the head. We have already used stack based strings in the examples above. Here, we create a Person object on the stack by passing the constructor arguments to the person variable as if it were a function. Behind the scenes, C++ reserves a block of memory for the object on the stack and calls the constructor whose signature matches the passed arguments. The string initialization
string s = "blah";
is actually equivalent to
string s("blah");
and therefore calls the string constructor which takes a plain C-string (const char*).
The heap-based version of the problem looks more familiar in front of the background of the previous chapters.
int main() { Person* person = new Person("Homer", 55); person->printOn(cout); delete person; return 0; }
The allocation is indicated by the new operator and the initialization by the constructor call. This is complemented by the delete operator which first calls the destructor (which we have not covered yet) before returning the allocated memory to the operating system. We will not get into the details here, but the new and delete operators can be changed to implement different allocation policies for particular classes or in general.
#include <iostream> #include <vector> #include <iterator> using namespace std; int main() { vector<int> v; for (int i=0; i<5; i++) v.push_back(i); for (vector<int>::iterator i=v.begin(); i!=v.end(); ++i) { cout << *i << ' '; } }
int main() { const int n = 5; int v[n]; for (int i=0; i<n; i++) v[i] = i; int* begin = &v[0]; int* end = begin + n; for (int* i=begin; i!=end; ++i) { cout << *i << ' '; } }
class IntArray { int _n; int* _v; public: IntArray(int n) : _n(n), _v(new int[n]) {} ~IntArray() { delete[] _v; } typedef int* iterator; iterator begin() { return _v; } iterator end() { return _v + _n; } int& operator[](int i) { return _v[i]; } };
The standard STL loop looks exactly the same as for the built-in vector collection.
IntArray v(5); for (int i=0; i<5; ++i) v[i] = i; for (IntArray::iterator i=v.begin(); i!=v.end(); ++i) { cout << *i << ' '; }
[1] | As you can see, this individual implementation of each method requires more typing and reduces the readability when compared to treating the implementation as a block (like Objective C's @implementation/@end). |