Overview of Small Enhancements
- New keywords and reserved words
- Line-style comments
- Stronger typing
- struct/union/enum tags become true type names
- Declaration placement
- Scope operator
- const specifier (stricter than ANSI C)
- Anonymous union
- Explicit (function-like) type conversion
- Function prototypes (stricter than ANSI C)
- Overloading of function names
- Default values for function parameters
- Functions with unspecified number of parameters (stricter than ANSI C)
- Reference parameters in functions
- inline specifier
- new and delete operators
- Pointers to void and functions returning void (stricter than ANSI C)
New Keywords and Reserved Words
C keywords
auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
C++ keywords added:
and and_eq asm bitand bitor bool catch class compl const_cast delete dynamic_cast explicit export false friend if inline mutable namespace new not not_eq operator or or_eq private protected public reinterpret_cast static_cast template this throw true try typeid typename using virtual w_char_t xor xor_eq
- bool, true, false are used for the C++ Boolean type
- catch, throw, try are used for C++ exception handling
- w_char_t in C is a typedef; in C++ it becomes a built-in type
- and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, and xor_eq are alternatives to the standard operator symbols.
Line-Style Comments
/*
* The C++ version of a famous program
*/
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
// Output the famous message
return 0;
}- /* and */ are the standard C comment delimiters
- // starts a line comment; end of line ends the line comment
Some Differences Due to Stronger Typing
In C++, literal character constants (‘a’) are of type char; in C, they are of type int:
- In C, sizeof(‘a’) equals sizeof(int)
- In C++, sizeof(‘a’) equals sizeof(char)
In C++, the use of an undeclared function is an error; in C, an undeclared function is assumed to be of type int foo() (K&R-style):
int main()
{
printf("Hello world\n"); // Error in C++; printf not declared
return 0;
}
In C++, assigning a void * pointer to any other type of pointer requires an explicit cast:
int main()
{
void *vptr;
float *fptr;
/* ... */
fptr = vptr; // Error in C++!
fptr = (float *)vptr; // OK
return 0;
}
In C++, the following:
int mumble()
declares mumble to be a function that has zero arguments. In C, it indicates that a function has an unspecified number of arguments of unspecified type.
- In C++, the following initialization is illegal:
char ch[2] = "Hi";
because it does not provide space for the terminating NUL character.
The following is the recommended method:
char ch[] = "Hi";
There is a bool type in C++:
bool done = false; // bool to bool
int yesno = true; // bool to integer
bool isalive = -100; // integer to bool
done = 1; // integer to bool
isalive = ptr; // pointer to bool
struct/union/enum Tags Become True Type Names
#include <iostream>
enum account_type {SAVINGS,CHECKING};
struct bank_account
{
int id;
double balance;
account_type type;
};
int main()
{
bank_account checking
= {1234, 20000.00, CHECKING};
std::cout << "Balance for account # "
<< checking.id << " type "
<< checking.type << " is "
<< checking.balance << std::endl;
return 0;
}- You don’t have to precede the tag with a struct, etc.
- cout and the << operator can be ‘chained together’ to replace the printf functionality.
Enumerations are Treated More Strictly
enum Color {Red, Green, Blue};
enum Fish {Herring, Mackerel, Salmon};
int main()
{
Color hue;
Fish swimmer;
int c;
hue = Red; // OK
swimmer = Mackerel; // OK
hue = Salmon; // Error!
swimmer = Blue; // Error!
c = Green; // OK!
hue = 1; // Error!
hue = (Color) 1; // OK
return 0;
}- In C, enums are all of type int, and any enum is assignment compatible with an int.
- In C++, each enum is a distinct int type; an enum type can be assigned to an int type, but not the other way around.
- An enum is a subtype of an int — an enum is a kind of int, but not vice versa.
Declaration Placement
#include <iostream>
int main()
{
for (int i = 0; i < 10; i++)
std::cout << i << '.';
// ...
char *ptr = 0;
// ...
return 0;
}- Allows the declaration of a variable to be placed near its first use — can make the program more readable
Scope Operator
#include <iostream>
int value = 27;
int main()
{
double value = 16.298;
std::cout << "local value = " << value
<< '\n'
<< "global value = " << ::value
<< std::endl;
return 0;
}- The scope operator, (::), overrides the default scope (in this case, local scope) to file scope
- The scope cannot be overridden in the opposite direction
- The scope operator becomes indispensable with classes.
const Specifiers
const means that a variable can’t be changed directly:
const int stable = 1000;
stable = 2000; // Error!
In C:
const int arraysize = 100;
int array[arraysize];
is illegal.
In C++, it is legal — in C++, a const value may be used in place of any literal constant of its type.
In C, the default scope of a const is extern;
In C++, the default scope of a const is static; much better for including in header files!
Can localize the scope of a const, whereas a #define is effective from the point of its definition through the rest of the file(s) in the compilation.
Often used in specifying function parameter types:
int blather(const char *readonly,
char *readwrite );
A const definition must be initialized:
const char *fixed_ptr = somewhere;
Anonymous unions
struct token
{
int id;
union
{
int integer;
double real;
} value;
// ...
} current_token;
// ...
f = current_token.value.real;- In C, a union often introduces an annoying extra level of names that must be referenced:
struct token
{
int id;
union
{
int integer;
double real;
}; // no name!
// ...
} current_token;
// ...
f = current_token.real;- In C++, an anonymous union avoids unnecessary names:
Explicit Type Conversion
C++ supports two kinds of syntax for a cast:
- The traditional C cast:
int i = 5;
double x = (double)i;
- The new-style ‘function-like’ cast:
int i = 5;
double x = double(i);
The new form tends to be more natural when used with classes
Function Prototypes
Both C++ and ANSI C have function prototypes:
extern FILE *fopen(const char *filename,
const char *mode);
...
FILE *fp;
...
fp = fopen("myfile.dat", "r");
In ANSI C, they are optional; the old K&R style is still legal.
In C++, function prototypes are required.
In C++:
int foo();
is equivalent to:
int foo(void)
not the ANSI C (K&R) interpretation !
In C++:
int doit(...);
means that the function accepts any number of arguments of undefined type.
- ANSI C requires at least one argument before the ellipsis, …
- ANSI C requires a comma between the last required argument and the ellipsis.
Overloading of Function Names
C (and most other languages) requires that every function have a unique name.
For example, if you wish to have a function that returns the absolute value of an int, long, float and double, you must give each function a different name, even though its purpose is exactly the same:
int iabs(int i);
long labs(long l);
float fabs(float f);
double dabs(double d);
which tends to cause ‘name pollution’.
#include <iostream.h>
long Abs(long i)
{
cout << "Abs of long\n";
return (i > 0) ? i : -i;
}
double Abs(double d)
{
cout << "Abs of double\n";
return (d > 0.0) ? d : -d;
}
int main()
{
long lv = 5;
double dv = -27.3;
cout << Abs(lv) << '\n';
cout << Abs(dv) << '\n';
return 0;
}- C++ allows many functions to have the same name, which allows the same operation to be applied to different types.
- This is used heavily with classes.
Function Signature
Overloaded functions are distinguished from each other by their signature
A function’s signature consists of:
- its name
- the number of its parameters
- the type(s) of its parameters, in order
- but not its return type!
a function’s return value can be discarded in C/C++, so the compiler cannot distinguish between functions which are called with the same arguments but without the call making use of any return value.
The C++ compiler attempts to match a function call to its corresponding function, based on the function name and the number and type(s) of its actual arguments:
- If there is an exact match, then that is the function chosen
- If there is no exact match, then implicit conversions can be used, which can result in:
- no match
- more than one match (ambiguity)
- a single unique match
- Other than a single match produces a compilation error
The exact rules for matching a call to its overloaded function, when combined with implicit type conversions, are quite complex.
Templates (covered much later in the course) complicate things still further.
Default Values in Function Prototypes
#include <iostream.h>
void create_window(int height = 24,
int width = 80)
{
// ...
cout << "Creating Window "
<< height << " by "
<< width << '\n';
// ...
}
int main()
{
create_window(10, 20);
create_window(12);
create_window();
return 0;
}- There are many situations where a function is called with an argument usually having the same value. It would be convenient to simply omit the parameter and have it default to a specified value. C++ supports this.
- Default values are only allowed on trailing arguments.
Unspecified Function Parameters
The checking for number and types of function parameters can be suppressed in C++:
int noway(...);
which is equivalent to the K&R-style C function declaration:
int noway();
But this is strongly discouraged!
It should only be used when there is no alternative!
(And there almost always is a better alternative!)
Type-Safe Linkage
/* fother.c */
double fother_the_grommet(int x, double d)
{
/* ... */
}
/* main.c */
int main()
{
int i;
i = fother_the_grommet(); /* Bad call! */
return 0;
}- A major weakness of C compilers is that there is no cross-module type checking.
- The compiler and linker cannot detect the mismatch; you only find the problem at run/debug time, when all manner of strange things are likely to happen.
- Type-Safe Linkage is achieved, in most C++ compilers, by name mangling of external names, so that they convey more information, such as parameter number and types. Unfortunately, often, different compilers implement this incompatibly.
Calling non-C++ Functions
It is a goal for C++ to be able to call non-C++ routines (such as functions written in C). We therefore need an escape mechanism from the normal C++ calling conventions:
extern "C" {
double sin(double x);
double cos(double x);
double tan(double x);
}
extern "C" {
#include "myfile.h"
}
Note that function prototypes are still required:
extern "C" {
int foo(); /* Means int foo(void); !*/
}
Reference Parameters
void swapint(int *first, int *second)
{
int temp = *first; // Must dereference
*first = *second; // Must dereference
*second = temp; // Must dereference
}
int main()
{
int a = 1, b = 2;
swapint(&a, &b); // Must take address
return 0;
}- Passing parameters in C can sometimes be clumsy, particularly when the parameters are out or in/out.
- Both the function definitions, and the calls to them become complex.
- Most programming languages take care of this for you!
void swapint(int &first, int &second)
{
int temp = first;
first = second;
second = temp;
}
int main()
{
int a = 1, b = 2;
swapint(a, b);
return 0;
}- C++ has added the reference.
- The C++ compiler takes care of doing the right thing to satisfy the parameter passing mechanism
References
int main()
{
int i, val = 1;
int *iptr;
int &refval = val; // must be initialized!
float data[] = {2.3, 5.2, 17.9};
float &refdata = data[2];
refval += 2; // adds 2 to val
i = refval; // assigns val to i
iptr = &refval; // points iptr to val
return 0;
}- Reference parameters are simply a special case of a reference variable:
- A reference is an alias for another variable.
- A reference is very like a pointer, except:
- a reference must be initialized, and:
- once initialized, a reference cannot be made to alias another object.
- A reference is not an object
References to Pointers
void swap(void **first, void **second)
{
void *temp = *first;
*first = *second; // swap pointers
*second = temp;
}
int main()
{
int a = 1, b = 2;
int *aptr = &a, *bptr = &b;
float f = 1.0, g = 2.0;
float *fptr = &f, *gptr = &g;
swap(&aptr, &bptr);
swap(&fptr, &gptr);
return 0;
}- In C, we can often generalize functions by using arguments of type pointer to pointer to void:
void swap(void *&first, void *&second)
{
void *temp = first;
first = second; // swap pointers
second = temp;
}
int main()
{
int a = 1, b = 2;
void *aptr = &a, *bptr = &b;
float f = 1.0, g = 2.0;
void *fptr = &f, *gptr = &g;
swap(aptr, bptr);
swap(fptr, gptr);
return 0;
}- C++ also supports references to pointers, which provides a better solution.
void swap(int &first, int &second)
{
int temp = first;
first = second;
second = temp;
}
int main()
{
int a = 10;
unsigned int b = 20;
swap(a, b);
return 0;
}- Note that you have to be very careful mixing types with references; if the type does not match exactly:
- Here, the compiler does the equivalent of:
int main()
{
int a = 10;
unsigned int b = 20;
int T = int(b);
swap(a, T);
return 0;
}Note the careful datatype matching in the previous example!
Functions Returning References
struct Location
{
int x, y;
};
Location &Position(int newx, int newy)
{
static Location myloc;
myloc.x = newx;
myloc.y = newy;
return myloc;
}
int main()
{
Location here = {0, 0};
here = Position(10, 20);
return 0;
}- Functions may also be declared as returning references:
- Note that the static storage specifier is extremely important here! Why?
Advantages of References
Advantages of references are:
- They clean up syntax significantly.
- They place the responsibility for proper argument passing on the compiler, and on the supplier of a function, rather than the client.
- They typically are more efficient for function argument passing, especially for large objects.
The inline Specifier
We encourage procedural abstraction, but this can cause programs to run more slowly if you use lots of functions.
C’s solution to this was to define a macro ‘function’:
#define min(x, y) ((x) < (y) ? (x) : (y))
But this is considered undesireable because:
- Lack of Type Checking: Macros do not undergo type-checking during compilation. …
- Difficulty in Debugging: Macros can make code harder to debug due to their inline nature. …
- Potential for Code Bloat: Macros can result in code duplication when used extensively throughout the program.
C++ helps solve this problem by supporting inline functions:
inline int min(int i, int j)
{
return (i < j) ? i : j;
}
- inline is only a hint to the compiler
- inline causes the function to have default internal linkage
- inline becomes very important within classes
The new and delete Operators
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *iptr;
iptr = malloc(sizeof(int)); // check return value!
*iptr = 5;
printf("%d\n", *iptr);
free(iptr);
return 0;
}- C does not have support in the language for dynamically allocated objects; you have to call the malloc() (or calloc(), etc.) function to allocate space, make sure the space is initialized, and eventually deallocate the space using the free() function:
- Modern languages like Ada support this in the language:
type INT_PTR is access INT;
MY_INT : INT_PTR;
...
MY_INT := new INT_PTR;
#include <iostream>
int main()
{
int *iptr = new int;
char *string = new char[40];
// Check for success
// (Really need to catch exception)
if ((iptr == NULL) || (string == NULL))
{
std::cerr << "No memory"
<< std::endl;
return 0;
}
*iptr = 5;
std::cout << *iptr;
delete iptr; // use this on scalars
delete [] string; // use this on arrays
return 0;
}- C++ provides similar operators, new and delete:
new and delete are C++ operators, and are typed. The typing is particularly valuable.
For a scalar, use the simple forms of new and delete:
int *iptr = new int; /* ... */ delete iptr;
For an array, specify size to the new operator:
int *iarrayptr = new int[5];
For an array, use the delete[] form of the delete operator, and do not specify the size of the array:
delete[] iarrayptr;
Space is allocated from the free store(heap)
- If the space requested is not available, new returns a NULL pointer, just like malloc, except that:
- The C++ ANSI/ISO committee changed the behavior of new to raise an exception if it fails to allocate space.
- Note that some compilers (including Microsoft Visual C++) have maintained the older behavior, for backwards compatibility. You can explicitly choose the new behavior with the use of a compiler switch.
new and delete become particularly important with classes.
#include <new.h>
#include <iostream.h>
void heap_problem()
{
cerr << "operator new failed\n";
/* ... */
}
int main()
{
void (*old_new_handler)();
/* ... */
old_new_handler =
set_new_handler(heap_problem);
/* ... */
set_new_handler(old_new_handler);
return 0;
}- Unlike malloc(), new can handle failures to allocate space:
void* and Functions Returning void
ANSI C includes two concepts which came from C++:
- Pointers to void (meaning unknown type):
void *generic_ptr;
- Functions returning void (meaning no value)
void my_procedure(int errogate,
double cross);
{
/* ... */
}
ANSI C allows the assignment of a pointer of type void* to a pointer of any other type.
C++ is stricter; it requires an explicit cast:
int main()
{
int i;
void *vptr = &i;
char *cptr = vptr; // allowed in ANSI C
char *cptr = (char*) vptr; // required by C++
/* ... */
return 0;
}