C++: Non-OO Features
C++ added some language features to C that are not directly related to Object Oriented ideas. The main ones are discussed below.
Non-OO Improvements in Function Calling
True Call-by-Reference: Using the &
on a
parameter type declaration (e.g., int&
) declares the
parameter as a reference to a variable. You no longer
need to use cumbersome pointer syntax to do call-by-reference.
Note in the function below, the parameters can be refered to
as simple integer variables, not as pointers.
// call-by-reference swap function
void swap(int& a, int& b)
{
int tmp;
tmp = b;
b = a;
a = tmp;
return;
}
C++ reference variables can be used elsewhere, too.
Default Arguments: starting at last parameter, default values can be specified in function prototype, and then if no arguments are given for those parameters in any call of the function, default values will be supplied. The function below could have calls with 1, 2, or 3 arguments given.
int add(int x, int y=12, int z=0);
int add(int x, int y, int z)
{
return x+y+z;
}
Function Overloading: You can create multiple different functions of the same name, but only if they have different parameters. The compiler will figure out which one to call each time you write a function call in your program using that function name. E.g., you could create both of the functions that have the prototypes below:
int add(int x, int y);
double add(double x, double y);
Improvements in Memory Allocation and Deallocation
In plain C, memory allocation and deallocation is done
using the library functions malloc()
and free()
In C++, memory allocation and deallocation is done using
the built-in operators new
and delete
.
C++ memory allocation is type-safe – that is, C++ knows what type you are creating and you can’t accidentally create the wrong-sized space for the type that you want.
Simple single-allocation form: p = new T;
where
p
is a pointer of type T
and T
is
either a built-in type or a user-defined type.
Simple single-deallocation form: delete p;
where p
is a pointer that points to a single allocated instance of its
type T.
Array allocation form: p = new T[size];
where size
is an integer expression (constant, variable, or more complex
expression). This allocates size
elements of type T
.
Array deallocation form: delete[] p;
where p is
a pointer that points to an array of its type T
.
Warning: delete
will sometimes seem to correctly
delete an array of elements, but you should not use it
to do this. Always use delete[]
if you have an array
of elements to delete.
Warning: Do not mix C and C++-style allocation. Most especially
do not use free()
to deallocate memory that was
allocated with new
, and do not use delete
to
deallocated memory allocated by malloc()
.
Safe Programming: Using const
in Parameter and Variable Declarations
The new keyword const
allows you to tell the compiler
that you are not allowed to modify a particular parameter or
variable. (Note: newer C standards have also introduced the
const
keyword.)
Why? To protect you against yourself (or your teammates). Using const promotes safer interactions between functions and modules.
How to read declarations: As we have been doing with all type declarations, read the declaration backwards, starting with the variable name.
Forms:
const T v
or T const v
– this declares
a call-by-value parameter v that is constant (i.e. you
are not allowed to change it). This form is rarely used, since
call-by-value parameters can be changed without affecting the
rest of the program anyways.
T const *p
– p is a pointer to a constant variable
of type T. As above, this form can be reversed:
const T *p == T const *p
. The pointer itself
can change, but you cannot modify the values that it points to.
This is the most common form of constant parameter declaration.
If you want to pass an array, or linked structure, or object,
and not allow the function to modify the thing, use this form.
T * const p;
– p is a constant pointer to T. This
means that you cannot change the pointer itself, but you are
allowed to modify what it points to. This is useful if you don’t
want the function to make the pointer point to anything it wants,
but you still want the function to be able to modify the value it
points to.
T const * const p;
– p is a constant pointer to a
constant T. Both the pointer itself and the data it points to
are not allowed to be changed.
Const can be used in variable declarations too. For example, you cannot do this:
void func(const char *s)
{
char *tmp;
tmp = s;
...
because s
points to constant data, and you will lose
the “const-ness” of it if you assign s
to tmp
.
So you need to do:
void func(const char *s)
{
const char *tmp;
tmp = s;
...
so that the compiler can continue checking that you will not modify the data.
Also, you can use const
to create real constants.
The old C and new C++ methods for declaring constants are shown
below:
// old C way
#define MAX_SIZE 48
// new C++ way
const int MAX_SIZE=48;
The new C++ form allows the compiler to do more type checking,
and it is more integrated into the programming language, rather
than relying on the C preprocessor (which is what the macro
definition #define
does).
Const methods: save this information for later; it will be understandable when we get to OO (object-oriented) features.
- Placing
const
after a method head and before the body means that it does not modify the object that it is called on. - Placing
const
in front of the return type means that the value returned cannot be modified.
C++ Input and Output
Rather than printf/scanf
style functions, C++ has
a new style of input and output, called iostreams.
To use iostreams, you have to do
#include <iostream>
Iostreams give you three standard channels, the
standard input, called std::cin
, the standard
output, called std::cout
, and the standard error
output, called std::cerr
.
Many people will do using namespace std;
after the include line above, and
then directly use the symbols cin
, cout
, and cerr
without the std::
qualification. This is bad practice, because the namespace std
has
many many symbols that get imported in the using
statement, and some
may conflict with your own names. I strongly recommend just using
std::cout
, etc., rather than importing. If you really want to use the
short versions, then do using namespace std::cout;
and only import that
single symbol (and then do the same for cin
and cerr
if you need to.
Output is done using the <<
operator, with
std::cout
or std::cerr
(or other file stream handle)
on the left, and then multiple right-hand arguments, each
separated by <<
.
The constant named std::endl
represents a newline
character. You can use it to end the current output line and start
a new line, but newer practice actually prefers just using
a \n
character in a string.
The two lines below do the same thing:
std::cout << "my int var is " << i << " and my double var is " << d << std::endl;
std::cout << "my int var is " << i << " and my double var is " << d << "\n";
Input is done using the >>
operator with
std::cin
on the left (or other input file stream handle),
and multiple variables on the right. For example,
std::cin >> i >> d;
will input an integer value and then a double value, and assign them to the variables i and d, respectively.
Although it looks wierd, it is actually safer than the printf/scanf form, because the compiler can check and verify that the values being assigned to the variables are of the same type. A plain C compiler cannot do the same types of checks with printf/scanf.