Programming Language Concepts Using C and C++/Introduction to Programming in C++

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Legacy C Programs[edit]

Plain C Program[edit]

Thanks to its unique relationship with C++, most C programs can be compiled as C++ programs with no or few modifications. This (that is, passing a C program as a C++ program) is not a recommended practice though; it’s advisable only when you have to use some preexisting C code in a C++ program.[1]

C_Commands.cxx
1 #include <stdio.h>
2 
3 int main(void) {
4 printf(An example to the usage of C commands in a C++ program\n);
5 
6 return 0;

In addition to the C-style comments C++ offers an alternative single-line comment that is delimited from the rest of the line with double slashes.[2] Everything on the program line to the right of the delimiter is treated as a comment and ignored by the compiler.

7 } // end of int main(void)

C Commands in Namespace[edit]

C_Commands_In_NS.cxx

A C header file can be included from a C++ program in two different ways: using either its C or C++ name. Next line is an example to the latter. The C++ name of the C header file is prefixed with the letter c and the .h file suffix is dropped.

Since C++ library names are generally[3] declared in a namespace, names included from header files are not visible unless we explicitly make them visible with a using directive. In the case of C library header files this is the std namespace. Failing to bring in the names found in this namespace means we must use all these names together with their namespaces. That is; omitting line 2 requires us to change line 4 to the following:

std::printf("An example to ...specification\n");

This may at times prove to be a useful tool. Consider calling a function4 that happens to have a name and signature clashing with one of those in the current namespace. Depending on whether you have imported this function via some using directive or not, simply issuing the name will lead to invocation of the function in the current namespace or a compile-time error. Fixing this error is possible by means of the scope operator and explicitly stating the namespace [and possibly class name] before the function name.

Example: Use of the scope operator.

// No using directives!!! // Next line will create a Stack object, which happens to live in the nested namespace of CSE224::DS. CSE224::DS::Stack<int> int_stack; ... ... int_stack.push(5); ... // Let's say Stack class has a static function named st_f. With no using directives in place, this function can be called as given below. ... CSE224::DS::Stack::st_f(...) ...; ...

Explicit referrals to names not living in a particular namespace—that is, names living in the global namespace—can be made through prefixing the scope operator to the function and/or class name. According to this, in case you may want to use the C-style-included printf from within a C++ program, you must write the following:

::printf("printf included a la C lives in the global namespace\n");

As a final note keep in mind that C++-style inclusion of C facilities does not enable object-oriented style programming. You still have to call the functions like in C; there is no way of sending messages to objects.

1 #include <cstdio>
2 using namespace std;
3 
4 int main(void) {
5   printf("An example to the usage of C commands in a C++ program with a namespace specification\n");
6 
7   return 0;
8 } // end of int main(void)

C++ Program[edit]

C++_Commands.cxx

Yet better is using C++ like genuine C++! Functionality similar to that found in C header file stdio.h is found in C++ header file iostream. Like other names declared in standard library header files, names in iostream are declared in the std namespace.

1 #include <iostream>
2 using namespace std;
3 
4 int main(void) {

The C++ compiler internally transforms the following line to

operator<<(operator<<(cout, A program with C++ commands), endl);

where cout is an ostream object representing standard output (like stdout in C) and endl is an ostream manipulator, which inserts a newline to the output stream and then flushes the ostream buffer (like '\n' in C). << is an operator that is used to write output to some ostream object. As can be seen from the transformed line, it associates left to right. Thus the next line writes a message to the standard output and then appends a newline character to the end.

Note that the << operator is called twice in a row: once to output "A program with C++ commands" and once to append an end-of-line character. That is, the following line is equivalent to

cout << "A program with C++ commands"; cout << endl;

which is transformed to

operator<<(cout, "A program with C++ command"); operator<<(cout, endl);

This also shows the reason why the << operator must return an ostream object.

5   cout << A program with C++ commands << endl;
6 
7   return 0;
8 } // end of int main(void)

Linking C and C++ Programs[edit]

Next program is a C program and it was meant to be so; it is not a C program that is compiled as a C++ program.

C_File.c
1 #include <stdio.h>

Although next two functions do basically the same thing, we can neither express them in one single function nor give both of them the same name. This is because C does not support templates or overloading.

3 void fint(int ipar) {
4   printf("In C function fint...Value of ipar: %d\n", ipar);
5 } /* end of void fint(int) */
6 
7 void fdouble(double dpar) {
8   printf("In C function fdouble...Value of dpar: %f\n", dpar);
9 } /* end of void fdouble(double) */
C++_Linking_With_C.cxx
1 #include <iostream>
2 #include <string>
3 using namespace std;

The following extern specification declares fint and fdouble to be functions that will be linked using C linkage. That is, no name mangling, except probably prepending an underscore, will take place. Alternatively, we could have written it as follows:

extern "C" void fint(int); extern "C" void fdouble(double);

5 extern "C" {
6   void fint(int);
7   void fdouble(double);
8 }

There are two definitions for the function named f_overloaded: one that takes an int argument and another that takes a double argument. Remember that this is a C++ program and C++ (thanks to a process called name mangling) supports function name overloading. This becomes possible by encoding the argument types in the function name and passing this transformed name to the linker. This makes type-safe linkage a non-issue. For instance, name of the first function below would be transformed [by the compiler] to f_overloaded__Fi while the second one to f_overloaded__Fd.[4] That is, the linker sees two different function names.

Although [unlike Java] the return type is taken into consideration in differentiating between overloaded functions, one should be very careful in using this property. This is because value returned from a C++ function can be ignored; a function may have been called for its side-effect. The following code fragment should make this clear.

int f(int i) { ... } ... void f(int j) { ... } ... // OK. The compiler can easily figure out the programmer’s intention from the context. int res = f(3); ... // Ambiguous!!! It might well be the case that the user intended to call the second function or the first one for its side-effect. f(5);

10 void f_overloaded(int ipar) {
11   cout << "In C++ function f_overloaded..."
12        << "The value of ipar: " << ipar << endl;
13 } // end of void f_overloaded(int)
14 
15 void f_overloaded(double dpar) {
16   cout << "In C++ function f_overloaded..." 
17        << "The value of dpar: " << dpar << endl;
18 } // end of void f_overloaded(double)

C++ enables the parameterization of types by means of templates.[5] The programmer parameterizes all or a subset of the types in the interface (the parameter and return types) of a function whose body otherwise remains invariant.

Unlike overloading, template mechanism does not require multiple definitions of a function by the programmer; instances of a particular function are constructed by the compiler. This process is referred to as template instantiation. It occurs implicitly as a side effect of either invoking a function template or taking the address of a function template.

20 template <class Type>
21 void f_template(Type par) {
22   cout << "In C++ function f_template..."
23        << "The value of par: " << par << endl;
24 } // end of void f_template(<class>)
25 
26 int main(void) {

Unlike C, C++ lets you mix declarative statements with executable ones. This means you can now declare identifiers just before their first points of reference; you don't have to declare them upon entry to the related block.[6]

27   int i = 10;
28   fint(i);
29 
30   double d = 123.456;
31   fdouble(d);
32   string s = "A random string";

Depending on the argument type the function to be called is resolved at the compile time. Do not forget: what we do here is to call one of the preexisting functions, which happen to have the same name.

34   f_overloaded(i);
35   f_overloaded(d);

Each of the following invocations causes a different instance to be constructed by the compiler; with no invocations, the compiler would have made no instantiations.

37   f_template(i);
38   f_template(d);
39   f_template(s);
40 
41   return 0;
42 } // end of int main(void)
Running the Program[edit]

gcc –c C_file.c↵ # Using DJGPP-gcc gxx -o C++_Linking_With_C C++_Linking_With_C.cxx C_File.o↵

In other environments where there is a port of the GNU Compiler Collection, such as Linux and Cygwin, you may see a message telling the command is not recognized. In such a case, try something like g++ or gpp.

C++_Linking_With_C↵ In C function fint...Value of ipar: 10 In C function fdouble...Value of dpar: 123.456000 In C++ function f_overloaded... The value of ipar: 10 In C++ function f_overloaded... The value of dpar: 123.456 In C++ function f_template... The value of par: 10 In C++ function f_template... The value of par: 123.456 In C++ function f_template... The value of par: A random string

Default Arguments[edit]

Default_Arg.cxx
1 #include <iostream>
2 using namespace std;

Arguments passed to some functions may for most cases have certain expected values, whereas in peculiar cases they may assume different values. For such situations C++ offers the use of default arguments as an option. For instance, consider a function for printing an integer. Giving the user an option of what base to print it in seems reasonable, but in most programs integers will be printed as decimal integer values. A function to print an integer using this facility would have the following prototype:

void print_int(int num, unsigned int base = 10);

It is possible to provide default arguments for trailing arguments only. retType f(argType1 arg1, ..., argTypen argn = def_value); // OK retType f(argType1 arg1, ..., argTypem argm = def_value, ... , argTypen argn); // Erroneous!!!

The effect of a default argument can alternatively be achieved by overloading. The print_int function above can be expressed with the following functions:

void print_int(int num, unsigned int base); void print_int(int num);

 4 int greater_than_n(int *ia, int size, int n = 0) {
 5   int i, count = 0;
 6 
 7   for (i = 0; i < size; i++)
 8     if (ia[i] > n) count++;
 9 
10   return count;
11 } // end of int greater_than_n(int[], int. int)
12 
13 int main(void) {
14   int inta[] = { 1, 2, -3, 6, -10, 0, 7, -2};
15 
16   cout << "The count of numbers greater than 5: "
17        << greater_than_n(inta, 8, 5) << endl;
18   cout << "The count of positive numbers in the sequence: "
19        << greater_than_n(inta, 8) << endl;
20 
21   return 0;
22 } // end of int main(void)

C++ References[edit]

Reference.cxx
1 #include <iostream>

The following using directive is different than those we have already seen. It is not used for bringing all the names found in the entire namespace but a particular class found in the namespace; it will bring in the iostream class found in the std namespace while all other classes/functions in the same namespace will be invisible and therefore can only be used with the help of the scope operator.

2 using std::iostream;

The following line is an example to the usage of references in C++. References are used to provide the call-by-reference semantics.[7] They help programmers write code that is cleaner and much easier to understand than the code that would have been written using pointers. Call-by-reference semantics simulated by means of pointers and all its chores are done by the compiler [like var parameters in Pascal or ref parameters in C#].

Pragmatically, a reference may be seen as an alias for another variable. While passing an argument, formal parameter becomes an alias for the corresponding actual parameter. Technically, it is a constant pointer to another variable. For this reason a reference must be initialized at the point of its definition.[8] That is,

int ivar = 100; ... // Next line will be transformed by the compiler to // int *const iref = &ivar; int &iref = ivar; // OK. int &iref2; // error

Once defined to be so, a reference cannot be made to refer to another variable. That is, given the above definitions,

int ivar2 = 200; ... iref = var2; // will be transformed to *iref = var2;

will not cause iref to become an alias for ivar2. It will rather set iref and, through iref, ivar to 200.

Accordingly, transformed code corresponding to the following function will be as given below:

void swap(int *const x, int *const y) { int temp = *x; *x =*y; *y = temp; } // end of void swap(int *const, int *const)

4 void swap(int& x, int& y) {
5   int temp = x;
6   x = y;
7   y = temp;
8 } // end of void swap(int&, int&)

Its undeniable flexibility aside, special relationship of arrays and pointers in C/C++ may at times lead to difficult to find run-time errors. Take the following equivalent declarations, for instance.

long sum(int arr[5]); long sum(int arr[]); long sum(int *arr);

Compiler will eventually convert the first two declarations to the third one, which means we can pass an array of any length. This is in stark contrast with the intentions of the first declaration. A consequence of this is the programmer, together with the user, should try harder to avoid any possible run-time errors such as using out-of-bounds index values.

Next line is a type definition that we will be utilizing in stricter type checking of array arguments. It defines a reference to an array of five ints. Any array identifier claiming to be of this type will not only be checked for component type but also for its length. For instance, any attempts to pass an array of size other than five to sum will be caught as a compile-time error.

It should be emphasized that this is valid for arrays whose sizes can be determined at compile-time. C++ compiler does not incorporate any run-time checks, which would slow down the program and therefore would not go well with the design philosophy of C/C++, into the resulting object code. For this reason, the following fragment will not even compile.

long sum(int size) { // Value of size depends on the argument passed. Therefore, length of la will be determined at run-time. int la[size]; array_of_five_ints a = la; ... } // end of long sum(int)

10 #define NO_OF_ELEMENTS 5
11 typedef int (&array_of_five_ints)[NO_OF_ELEMENTS];
12 
13 long sum(array_of_five_ints arr) {
14   long res = 0;
15   for (int i = 0; i < NO_OF_ELEMENTS; i++) res += arr[i];
16 
17   return res;
18 } // end of long sum(array_of_five_ints)

Now that a reference is a constant pointer, the following can be seen as follows:

typedef int (*const rf)(int);

That is, the following typedef defines a synonym, rf, for function reference type that takes an int and returns an int. In other words, any function that takes an int and returns an int, such as multiply_with_3 or raise_to_the_3rd_power, may be considered to be an instance of rf.

20 typedef int (&rf)(int);
21 
22 int multiply_with_3(int i) {
23   cout << "Tripling " << i << ": ";
24 	
25   return 3 * i;
26 } // end of int multiply_with_3(int)
27 
28 int raise_to_the_3rd_power(int i) {
29   cout << "Raising " << i << " to the third power: ";
30 	
31   return i * i * i;
32 } // end of int raise_to_the_3rd_power(int)

Next function shows an alternative way of implementing the callback mechanism in C++. The side-effect produced by f_caller depends on the function passed to it as its argument.

34 void f_caller(rf f) {
35   cout << "In the f_caller..." << f(5) << endl;
36 } // end of void f_caller(rf)
37 
38 int main(void) {
39   int a = 5, b = 3;
40 
41   cout << "TESTING CALL-BY-REFERENCE" << endl;
42   cout << "a: " << a << "\tb: " << b << endl;

No address-of operators! Everything is taken care of by the compiler.[9] All user has to know is that the side effects that take place in the function are permanent.

43   swap(a, b);
44   cout << "a: " << a << "\tb: " << b << endl;
45 
46   cout << "TESTING ARRAYS WITH SIZE INFORMATION" << endl;
47   int ia[] = {1, 3, 5, 7, 9};
48   cout << "Sum of array elements: " << sum(ia);
49 
50   cout << "TESTING CALLBACK" << endl;
51   f_caller(multiply_with_3);
52   f_caller(raise_to_the_3rd_power);
53 
54   return 0;
55 } // end of int main(void)

Stream Manipulators[edit]

All stream objects in C++, such as cout and cin, maintain a state information that can be used to control the details of input/output operations. This includes attributes like precision of a floating-point number, width of tabulated data, and so on. What follows is a simple program to demonstrate some of the manipulators found in C++.

Manipulators.cxx
 1 #include <fstream>
 2 #include <iomanip>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int main(void) {
 7   int i;
 8   ofstream outf("Output.dat");
 9 
10   cout << "Enter an int value: "; cin >> i;
11   outf << "Number entered: " << i << endl;

A manipulator modifies the internal state of a stream object and causes subsequent input/output to be performed differently; it does not write to or read from the underlying stream. setw in the following statement, for instance, reserves as many characters of space as the value passed in the argument for output of the next argument; left writes all subsequent output—until it's changed with another manipulator such as right—in a left-justified manner

12   outf << setw(12) << left << "Hex" 
13        << setw(12) << " Octal"
14        << setw(12) << " Dec" << endl;

In case output produced does not fill up all space reserved for it we choose to fill the empty spaces left with the underline character; whatever integral value is written to the window of twelve characters will be written in a right-justified manner using hexadecimal notation and this will be relayed to the user, thanks to the showbase manipulator, by prefixing the output.

  outf.fill('_');
  outf << right << setw(12) << hex << showbase << i;
  outf << " " << setw(12) << oct << i;
  outf << " " << setw(12) << setbase(10) << /* noshowbase << */ i << endl;

  bool bool_value = true;
  outf << endl << "bool_value: " << boolalpha << bool_value 
       << '\t' << noboolalpha << bool_value << endl;

Next line initializes the local variable precision with the default precision value. This happens to be 6, which means six digits are written after the decimal point. If you crave for higher precision you can pass it as an argument to the same function or use setprecision in a similar fashion.

24   int precision = outf.precision();
25   double d, divisor;
26   do {
27     cout << "Enter a double value: "; cin >> divisor;
28     if (divisor == 0) break;
29     outf << endl << "Double value: " << divisor << endl;
30     d = 1 / divisor;
31     while (divisor != 0) {
32       outf << "Precision: " << precision << "... d: " << fixed << d;
33       outf << " Using sci. notn.: " << scientific << uppercase << d << endl;
34       cout << "New precision: "; cin >> precision;
35       if (precision != 0) {
36         outf << "New precision: " << precision;
37         outf << setprecision(precision);
38       } else break;
39     } // end of while(divisor != 0)
40     precision = outf.precision();
41   } while (divisor != 0);
42 
43   return 0;
44 } // end of int main(void)

Running the Program[edit]

gxx -o Test_Manipulator.exe Manipulators.cxx↵ # Using DJGPP-gcc Test_Manipulator > Output.dat↵ Enter an int value: 12345↵ Enter a double value: 5.6↵ New precision: 15↵ New precision: 16↵ New precision: 17↵ New precision: 18↵ New precision: 19↵ New precision: 0↵ Enter a double value: 4.56↵ New precision: 18↵ New precision: 17↵ New precision: 16↵ New precision: 15↵ New precision: 0↵ Enter a double value: 0↵

Output.dat

Number entered: 12345 Hex Octal Dec ______0x3039 ______030071 _______12345 bool_value: true 1 Double value: 5.6 Precision: 6... d: 0.178571 Using sci. notn.: 1.785714E-01 New precision: 15 Precision: 15... d: 0.178571428571429 Using sci. notn.: 1.785714285714286E-01 New precision: 16 Precision: 16... d: 0.1785714285714286 Using sci. notn.: 1.7857142857142858E-01 New precision: 17 Precision: 17... d: 0.17857142857142858 Using sci. notn.: 1.78571428571428575E-01 New precision: 18 Precision: 18... d: 0.178571428571428575 Using sci. notn.: 1.785714285714285754E-01 New precision: 19 Precision: 19... d: 0.178571428571428575 Using sci. notn.: 1.785714285714285754E-01 Double value: 4.559999999999999609E+00 Precision: 19... d: 0.219298245614035103 Using sci. notn.: 2.192982456140351033E-01 New precision: 18 Precision: 18... d: 0.219298245614035103 Using sci. notn.: 2.192982456140351033E-01 New precision: 17 Precision: 17... d: 0.21929824561403510 Using sci. notn.: 2.19298245614035103E-01 New precision: 16 Precision: 16... d: 0.2192982456140351 Using sci. notn.: 2.1929824561403510E-01 New precision: 15 Precision: 15... d: 0.219298245614035 Using sci. notn.: 2.192982456140351E-01

Notes[edit]

  1. Even in such situations there may still be a better solution as will be presented later in Linking C and C++ Programs.
  2. Widely supported in many compilers, this is now a standard feature of the C programming language.
  3. That is, you can still write C++ programs without placing the programming entities [e.g.; classes, functions, and etc.] in a particular namespace. In such a case these entities are said to be placed in the global namespace. Such a style, however, may give rise to name clashing problems, which is due to the fact that entities in the same namespace cannot have the same name. If you have access to the source code, this problem can be solved by changing the name of the relevant entities and making them unique. But this won’t work when you don’t have the source code. Answer in such a case is using namespaces.
  4. Note that there is no standard way of mangling a function name; the compiler writer is free to choose a scheme of her own.
  5. In addition to functions one can parameterize classes, too. For more on this, see the Parameterized Types chapter.
  6. If you think this doesn't sound so right, it is very likely that you have been using a C compiler that supports language extensions, which means porting your code will probably prove to be a difficult task. If you are not convinced try adding -pedantic (in gcc) or /Tc (in MS Visual C/C++) to your command line and see what happens!
  7. There are other occasions where use of references makes life easier. For more, see the Object-Based Programming chapter. It should also be noted- as will be shown in the Inheritance handout- dynamic dispatch is possible through references and pointers.
  8. Remember the difference between initialization and assignment? A constant must be given a value at its point of creation; it cannot be created without an initial value. Nor can it be assigned a new value, which explains why a reference—a constant pointer managed by the compiler—cannot be modified to refer to another variable after its initialization.
  9. That is, the compiler will silently transform this line to swap(&a, &b);