C++ Language Basics Part I

(I/O, functions, keywords, structures, memory alloc, commenting)


The C++ programming language can be thought of as a ``superset'' of the C language; that is, just about any legal Ansi C program is a legal C++ program, and means the same thing. The reverse is not true of course: there are many C++ constructs that are not found in the C programming language. This handout describes some of those constructs that we will be using in this class.


Input and Output

In C, input and output is traditionally done using the functions printf and scanf (along with getchar()). When reading from files, the routines fprintf and fscanf are used. Unfortunately, it's pretty easy to crash a program using these functions. For example,


int    d = 3;
char   *str = "hi there!";

printf("d is %d, and str is %s\n", str, d);

will probably cause your program to crash, because the programmer mixed up the arguments to printf. The routine scanf is even worse: for example,


double d;
float  f;
int    i;

scanf("%d", i);     /* wrong, should be &i */
scanf("%f", &d);    /* wrong, needed to specify %lf */
scanf("%f", &f);    /* this one is OK */

The major problem is that you can usually only find these bugs at run-time. However, it would be much better for the compiler to be able to look at the statement scanf("%d", i) and determine that a pointer to an integer, not simply an integer, was required here. While you can't do that with C style operators (since the correct arguments to printf and scanf vary, depending on the formatting string), in C++ there are routines that will eliminate most of these bugs.

Let's look at an example:


#include  <iostream.h>

main() {
  int  a, b;
  double  d;
  char  str[20];

  cin >> a;  // equivalent to scanf("%d", &a);
  cin >> b;  // equivalent to scanf("%d", &b);
  cin >> d;  // equivalent to scanf("%lf", &d);
  cin >> str;  // equivalent to scanf("%s", str);

  cout << "The sum of a and b is";
  cout << a + b;
  cout << "\n";
}

First, the file iostream.h is a standard C++ header file that defines cin, cout, and the operators << and >>. The expression cin >> a causes the program to read an integer into the variable a, from the standard input. Similarly, cin >> b reads an integer into b, but cin >> d interprets its input as a real number, and stores it in d. The next statement, cin >> str reads characters until a whitespace is encountered, and stores a null-terminated string in str (excluding the whitespace). Note that if you typed a very long string (i.e. more than 20 characters), your program could crash.

Output is much the same: with the expression cout << thing, the contents of thing is printed to the standard output. Note a couple of subtleties:


int  ival = 'g';  // The ASCII code for g is 103, so ival = 'g'
char  cval = 'g';  // is equivalent to ival = 103.

cout << ival;    // treats ival as a number, and prints out 103
cout << cval;    // prints out simply the letter g

A few other things: You can combine any number of output or input operations in a row e.g.


cin >> a >> b >> d >> str;
is fine, and is equivalent to the four seperate lines above. Similarly, statements like

cout << "The sum of a and b is " << a+b << "\n";
are permissible as well. (Note the space after the word ``is.'' Without extra space, i.e.,

int x = 23;
cout << "The value of x is" << x << "\n";

you get


The value of x is23

You can test when you've run out of input:


int  x;

while(cin >> x) {
  ...
}

will loop until there is no more input, or until an invalid string is entered (e.g. ``hi,'' which is not a valid integer).

It is also possible to make the << and >> output operators know how to output user-defined types: for example, if you create new datatypes, you can teach the << and >> operators how to input and output directly from those datatypes. (See the C++ book, or ask the teaching staff how this works, if you're interested).

If you want to do I/O to a file (i.e. the equivalent of fprintf), you can get declare variables like cout that are used just as cout is, but that cause the output to go to a particular file. You can do input from a file similarly. There is an example of this in Assignment 1. If you want to know more details, consult the C++ book, or the teaching staff.

Finally, in addition to sending output to cout, you can also send output to the ``standard error'' by using cerr e.g.


if(i < 0) {
  cerr << "Fatal error: i is less than zero!\n";
  exit(1);
}

This is analogous to using fprintf to stderr.


Function Declarations

In C++ it is illegal to use a function without first declaring its type. In C, if you don't declare a function's type, certain defaults apply. This is not the case with C++. You must define a function before you can use it.


Additional Keywords

Every keyword, or reserved-word in C is also a keyword in C++. There are extra keywords in C++ that have no meaning in C. Since C++ is becoming prevalent, even when programming in C you should avoid the use of all of the following C++ keywords as function or variable names. In C++ you cannot use any of the following words as function or variable names:


asm        delete   if           return      try
auto       do       inline       short       typedef
break      double   int          signed      union
case       else     long         sizeof      unsigned
catch      enum     new          static      virtual
char       extern   operator     struct      void
class      float    private      switch      volatile
const      for      protected    template    while
continue   friend   public       this
default    goto     register     throw


Defining Structures

In C++, when you define a structure, the name of the structure is henceforth treated as a new datatype. For example:


struct complex {
    double x, y;
};

complex a, b, c;

Also, note the semicolon at the end of the definition of the complex structure: this semicolon is required. After defining the structure, you can use complex as a datatype. Its still legal to say


struct complex a, b, c;
if you want to. The above discussion applies to unions as well.


Memory Allocation

In C, we allocate space using malloc. For example,


double *dspace = (double *)malloc(sizeof(double) * 10);
dynamically allocates space for 10 doubles. In C++, we use the construct new:

double *dspace = new double[10];
To allocate space for the complex structure defined above, the syntax is simply

complex *cptr = new complex[37];

To return memory in C, we use free:


free(dspace);
In C++, memory is returned by

delete dspace;    /* or,  delete []dspace;  */
or

delete cptr;      /* or,  delete []cptr;    */

The format with square brackets is needed when you have an array of items each having its own destructor (we'll talk about destructors later on in the course). As in C, though, if you try to write past the end of the memory allocated by new, your program will probably not work correctly. Incidentally, the delete operator is guaranteed to have no effect when applied to a pointer whose value is zero (NULL). Handing delete a pointer that was not returned by new is a bad idea. To more easily catch cases when you've deleted memory but later tried to use it, the code


delete dspace;
dspace = 0;

is recommended, since the use of dspace after the deletion will be easier to detect.


Overloading of Functions

In C, a name can only be used once to define a function. For example, in C, the following is illegal:


double minimum(double x, double y)
{
    if(x < y)  return x;
    else return y;
}

int minimum(int x, int y)
{
    if(x < y) return x;
    else return y;
}

This is perfectly legal C++ code, however, and is known as an overloaded function. If you call minimum in your code, the compiler will decide which version of minimum to invoke based on the types of the arguments you supply to the function. Appropriate uses for overloading are reusing mathematical function names for operations on different data; for example, two versions of sqrt: one for real numbers, and one for complex numbers. Another typical use might be a function named node_count which returns the number of nodes in differing data structures such as linked lists, trees, or graphs. You will probably have little use for operator overloading in this class.

The reason we bring up the subject at all, is to make you aware of the following pitfall. Suppose that you declare in a header file defs.h a function foo as follows:


/* file defs.h */

int foo(double, int, char);

Now suppose that you wish to use the function foo in a file a.c:


/* file a.c */
#include   "defs.h"

void do_something()
{
         int result;
         result = foo(3.4, 7, 'c');
}

Ok, no problems so far: the file a.c correctly includes defs.h so it can access the function foo. Suppose, though, you made a mistake in the file b.c, where foo is actually defined:


/* file b.c */
#include   "defs.h"

/* Here's my great implementation of foo: by juxtaposing the
   seventh and ninth bit from the third argument, I cleverly
   manage to ...
*/

int foo(double a, int b, int c)
{
        ...
}

The problem is that the C++ compiler thinks you're defining a function foo which takes a double and two integers. This is perfectly legal. The problem comes when you actually compile and try to run everything. At that point, the function named foo that takes a double, an integer, and a character, will be undefined, because in b.c you defined a different function foo.

Moral of the story: if you get undefined function errors from the compiler, make certain that you defined your function with the types you really meant to.

Commenting

C++ allows two types of comments. In addition to the standard ``/*'' and ``*/'' comments of C, there is a special comment which is good only to the end of a line. In C++, characters occuring after the sequence ``//'' and on the same line are ignored, as long as the ``//'' does not occur in a string. For example:


/*
   If low is greater than high, interchange
   the values and set 'swapped' to 1.
*/

if(low > high)
{
     tmp = low;           // save low
     low = high;
     high = tmp;          // now swap
     swapped = 1;         // indicate we swapped the data
}

One thing to be VERY careful of is not to use C++ style comments in #define statements, because most compiler systems don't mix the two together correctly. That is, for now (and probably for at least another 10 years!) don't write


#define MAX_LEN    128    // maximum length string

but use


#define MAX_LEN    128    /* maximum length string */

instead.