CSE 374, Lecture 21: Intro to C++

C++ is an enormous language:

We will focus on a couple themes rather than just a "big bag of new features to memorize"

Object-oriented programming in a C-like language may actually help you understand C and Java better.

IDE

I used an IDE (integrated development environment) today rather than emacs on klaatu to demonstrate a more realistic way to develop code. The IDE that I used is called Atom and you can download it for free if you like. However, before turning in homeworks, always make sure that they run as you expect on klaatu or the CSE VM!

Hello world!

Let's take a look at our Hello World example in C++. It should look familiar to you after your work in C:

    // In file hello.cpp
    #include <cstdlib>
    #include <iostream>

    int main() {
      std::cout << "hello world!" << std::endl;
      return EXIT_SUCCESS;
    }

We can then compile and run this program (compilation is slightly different - a different compiler):

    $ g++ -Wall -std=c++11 -o hello hello.cpp
    $ ./hello
    hello world!

Let's take the differences with a C program bit by bit.

File names

Rather than files that end in ".c", C++ files end with ".cpp", although there are other conventions like ".cc". You can use either .cpp or .cc - the compiler recognizes both and it doesn't matter. We still have ".h" files for header files.

Includes

The C preprocessor is the same for C++, so we have #include and #define. However, the standard library includes are a bit different: instead of we have , and instead of we have . The actual content of those files is basically the same, however.

Namespaces

When we are printing hello world, we see that we are referring to "std::cout" and "std::endl" - what is that part before the double colon "::"? In C++ we have the idea of NAMESPACES, which are another way to group pieces of code into logically related things. We can create our own namespaces containing functions as follows:

    namespace foo {
    int doSomething(int x);
    }

    namespace bar {
    int doSomething(int x);
    }

    int main() {
      foo::doSomething(3);
      bar::doSomething(3);
    }

Note that we have two functions with the same name defined in two different namespaces (foo and bar) - this is totally ok! In C, the compiler does not allow two functions with the same name, but in C++ it's ok as long as the functions are in different namespaces. We can then disambiguate between the functions by prefixing with the namespace and the double colon ("::").

We can also nest namespaces:

    namespace bar {
    namespace baz {
    void nested(string s);
    }
    }

    int main() {
      bar::baz::nested("foo");
    }

We see that quickly our function name lengths may get out of hand with nested namespaces - they could be very long! Fortunately C++ gives us a way around that: we can use the "using" keyword to tell the compiler to treat all functions within a specified namespace as if they were in our namespace. For instance, in our hello world example:

    using namespace std;

    int main() {
      // Now we can use cout instead of std::cout
      cout << "hello world!" << endl;
      return EXIT_SUCCESS;
    }

Other notes about namespaces:

I/O

In C to do input/output we used printf and scanf/gets. C++ has a much simplified way of doing this, which looks very similar to bash I/O redirection. The standard library has an object called "cout" which represents the stdout stream, and we can use the "<<" operator to redirect a string to stdout. Much simpler than printf! We also use "endl", which represents the end-of-line character (ie "\n", but portable across different operating systems).

Likewise, C++ has an object called "cin" which represents the stdin stream, and we can use ">>" to read from it into variables of any type, just like we would do with scanf:

    cout << "What is your name? ";
    string name;
    cin >> name;

    cout << "What year were you born? ";
    int year;
    cin >> year;

(Aside: in terms of implementation, ">>" and "<<" are operators kind of like "+" is - they work on a left and a right operand and return a stream as well, which is how we can chain the "<<" operators together one after the other.)

Pass-by-reference

Let's say we want to write a function that takes a string and modifies it so that it stores the pig-latin form of the word. In C++, we can use the "string" object to store a string (more on objects later):

    void pig(string s) {
      char first = s[0];
      s = s.substr(1);
      s += first;
      s += "ay";
    }
    string name = "melissa";
    pig(name);

However this function doesn't work! That's because we are making a COPY of the string in the parameter - it isn't a pointer! We might fix this as follows:

    void pig(string* s) {
      char first = (*s)[0];
      *s = s->substr(1);
      *s += first;
      *s += "ay";
    }
    string name = "melissa";
    pig(&melissa);

This works, but it's a lot of *'s everywhere. C++ has a solution for us though! It adds another type of variable that is called a REFERENCE, and it is designated by the ampersand symbol.

    void pig(string& s) {
      char first = s[0];
      s = s.substr(1);
      s += first;
      s += "ay";
    }
    string name = "melissa";
    pig(melissa);

References are great because other than the "&" when giving the type of the argument, you can treat the variable as if it were normal (and not a pointer), but under the covers, the compiler will treat it just like a pointer and do the dereferences for you.

const

In C++ we also have the new "const" keyword, which says "this thing must not change". We can use this to declare global constants:

    const int CURRENT_YEAR = 2018;

Global constants look a lot like global variables, but they are OK stylistically whereas regular global variables are not because the "const" keyword says that this value CANNOT CHANGE.

    // This won't compile.
    CURRENT_YEAR = 2038;

new/delete

In C, we created arrays on the heap with malloc and then when we were done, we freed them with free:

    int* arr = (int*) malloc(sizeof(int) * 100);
    free(arr);

In C++, we have a nicer syntax for this that does the same thing:

    int* arr = new int[100];
    delete [] arr;

We can also do this for non-array types:

    int* x = new int(4);  // x stores the value 4.
    delete x;