This document gives a short overview of C++ syntax. One or two isolated examples of each language feature are shown. For more extensive explanation and/or more detailed examples refer to the C++ books listed in the course bibliography.
There is also a void data type that can represent nothing when used as the return type for a Function or as a generic data type when used as the base type for a pointer data type.
int *
, specifies a
data type that is the address of an integer.
The second type in this category is the reference
type specified with &.
This data type is similar to
the REF function parameter in Pascal. It can be considered to create
a new name or alias for a data item. For example, float
&
, would specify an object that references a float item.
This type allows an object to be
accessed using the normal syntax while the compiler handles the
indirection for you.
void
,
void *
,
Here are some examples of variable declarations:
int i; <= integer
char *cPtr; <= pointer to a character
float arrayOfPoints[10]; <= array of 10 floats
unsigned char uc1, uc2, uc3; <= three separate unsigned characters.
void *genericPtr; <= indirect pointer to anything
A variable declaration can be placed anywhere in your program. This allows the programmer to decide if placing the declaration at the point in the program where the variable is used is more appropriate than the common practive of declaring all variables in one location. The variable is known, or scoped, only within the program unit in which it was declared. A variable is scoped from the point where it is declared to the end of the program unit containing the declaration. The lowest level of program unit is the code block delineated by the {} that define a compound statement or function. A variable declared within a compound statement in a function is scoped only within that compound statement. A variable declared outside of all functions in a file is accessible by any code in that file. For large programs that require multiple files of source code variables declared in one file can be made accessed by code in other files by specifying that it is an extern variable. extern declarations are often placed in an include file that is used by any file requiring access to the external variable. If there are multiple variables with the same name whose scopes overlap at one point in a program, the variable with the innermost scope will be used.
One of the concepts in OOP is data hiding. This implies that data contained in an abstract data type should not be directly visible to the outside world. Even before OOP techniques were developed, it was good coding practice to keep the scope of data as narrow as possible. If a variable was only used within a function then declare it within that function so it is only accessible in that program unit. (We will see with the concept of classes how C++ provides for data hiding at the level of data object.) The use of global variables, i.e. those scoped within an entire program, has never been good practice. To maintain compatibility with older C programs, C++ has kept the nasty feature that any variable declared outside of all functions in a file can be accessed by other files in the program simply by declaring the variable as an extern in those other files. If it is necessary to declare variables as global within a file but they are to be hidden from other program files, the compulsive programmer must declare each of these variables as static.
Here is an example of using extern and static variable declarations.
In file a.cc:
int anExternInt;
static int aPrivateInt;
In file b.cc:
extern int anExternInt;
extern int aPrivateInt; <= error was declared as static
The variable anExternInt
is declared in file a.cc and can
be accessed
by the file b.cc by declaring it as an extern. In file b.cc an
references to anExternInt
would use the variable declared in file
a.cc. aPrivateIn
t is not accessible outside of a.cc
because it was
declared static. One point to keep in mind is that a global variable
is declared without the extern in only one file. This should be the
file with which the variable is most closely associated. It is then
referenced with an extern, usually specified in an
include file, in
all other files that will use that variable.
'a'
. A
string constant can be defined by enclosing the string between "",
such as "Hello, world\n"
.
(The '\n' represents a newline character.)
This constant is actually an array of characters that is one larger
than the number of character in the string. This extra byte at the
end of the string stores a '\0' (zero byte) which is considered as the
terminator for the string. Most string functions assume that the zero
byte terminator is present at the end of the string.
There are the standard +, -, *, and / binary arithmetic operators. The value of one of these expressions is the result of the arithmetic operation.
Binary comparisions are performed using the operators == (equal), != (not equal), <=, and >=. The value of these expressions is 1 if the comparison was true and 0 otherwise. C++ does not have a boolean data type.
As is standard in mathematical notation and other programming languages, () can be used to control the order of evaluation of complex expressions. Expressions are evaluated according to the precedence of operators which is specified in a table in any book on C++. (In Stroustrup, it is in section 3.2.)
The post/pre decrement/increment operators are available. The ++
increment operator will add 1 to the value of a variable. For
example, a++
will increment a by 1.
The value of this expression when
used as part of another expression is the initial value of a. The
side effect is the incrementing of a. If, on the other hand,
++a
is
used in an expression, the side effect is still the incrementing of a
but the value for the expression is now the incremented value. This
is pre-increment. It similarly holds for the pre/post decrement
operator, --, which will subtract 1 from a variable.
As noted above, even the assignment operation is considered an expression. The value of this expression is the value assigned to the variable on the left of the assignment. The side effect is storing this value at that location.
The indirection operator, *, is used to dereference a pointer and provide access to the data at that location. The address-of operator, &, can be used to get a pointer to a data item. There is an example of this syntax under Simple Statements below.
One way in which C++ supports polymorphism is by allowing operators to be overloaded. Overloaded operators are described below in the section on classes, which are C++'s primary construct for supporting abstract data types (ADT).
Calls to functions that return values can be included as part of an expression. The function return data type and formal parameter definition must be specified before the function call. This definition is often done in an Simple Statements Statements are terminated in C++ with a ;. Here are examples of some simple statements:
int a,b,c;
int *d;
a = 3 + b;
b = ++c;
d = &a; // example of pointer operations
*d = 4;
b = c + *d;
The syntax for an if statement is:
if expression
if-statement
There is also an if-else statment:
if expression
if-statement1
else
else-statement2
If expression evaluates to a non-zero value then the if-statement is executed. Otherwise the else-statement is executed if it is present.
while expression
while-statment
While expression evaluates to a non-zero value execute the while-statement. The while-statement may not execute if expression is 0 when first executed.
do
do-statement
while expression
Execute the do -statement while expression is non-zero. This loop will always execute at least once.
for (initial-statement;test-expression;end-of-loop-statement)
for-statement
Execute initial-statment, then evaluate test-expression and execute for-statement. Execute end-of-loop-statement and loop to evaluate test-expression. The most common use of the for statement is as a loop counter where the counter variable is initiazed, compared against a termination condition, and adjusted in the three parts of the for statement.
switch expression
case value1:
case-statement1
break;
case value2:
case-statement2
break;
default:
default-statement
break;
Evaluate expression and execute the case-statement with matching value label. If the value does not match any label execute default-statement if the default section is present. Execution falls through from case to case if the break statement is not present.
Here is an example of the definition of a function:
float Quadratic(float x,float a,float b,float c)
{
float result;
result = a * x * x + b * x + c;
return result;
}
A functions can be further defined as an inline function. If the actual operation to be carried out by the function is small (this is small compared to the overhead of calling a function) program execution could be sped up by making the function inline. You still have the advantages of parameter type checking but gain the faster execution as if you had written the code directly inline.
Variables defined within a function are in existence only for the life of the invocation of the function. These are called automatic variables and their values do not persist from one function invocation to the next. If you want to maintain the values across function calls then declare the variable as static. (This is a second use for the keyword static that has no connection with its use for limiting the scope of global variables.)
Here is the above function written as an inline function and streamlined:
inline Quadratic(float x,float a,float b,float c)
{
return a * x * x + b * x + c;
}
Here is a simple class definition for a complex number data type. Features of the definition are described in the sections that follow.
class Complex
{
private:
double re;
double im;
public:
Complex(float r,float i) {re = r; im = i;}
Complex(float r) {re = r;im =0;}
~Complex() {};
double Magnitude(); // calculate magnitude
double Real() {return re;} // return real part
inline double Imag(); // return imaginary part
Complex operator+(Complex b)
{return Complex(re + b.re,im + b.im);}
Complex operator=(Complex b)
{re = b.re;im = b.im; return *this;}
}
inline double Complex::Imag()
{
return im;
}
double Complex::Magnitude()
{
return sqrt(re*re + Imag()*Imag());
}
re
and im
in
Complex
, but also functions
that perform operations on objects of that type. The functions can
have parameters specified, such as with Complex
in
Complex
which is a
special class member called a constructor.
Note here the
C++ support for polymorphism by allowing function names to be
overloaded with different parameters. The compiler will determine
the correct function to use based on the data type of the parameters
in the function call. Complex
has two
Complex
constructors defined with different parameter lists.
Code that is part of a member function of a class, such as the code in
Complex::Magnitude()
, has access to all members of the
class directly. Within Copmlex::Magnitude
references to
re
or im
will access the data members of
that object. Note that defining a class member function is exactly
the same as a regular function except that the function name is
prefaced with class-name::
. A class function
member can also be directly accessed inside of a class function which
was done to get the imaginary part of the complex number in
Complex::Magnitude()
.
Class member functions that are very short can be declared as inline
functions. If code for the member function is defined within the
class definition, such as Complex::Real()
it is assumed
to be an inline function. Otherwise, the function must be explicitely
defined as inline and code provided outside of the class definition, such as
Complex::Imag()
.
Complex
type then access to the magnitude of
this complex number in an expression is gotten using
a.Magnitude()
. If b is declared as a pointer to an
object of Complex
type then access to the magnitude of
this complex number in an expression is gotten using
a->Magnitude()
. If there are public data members in the
class they can be accessed using similar syntax.
Complex
class, to provide for different method to create
new objects in the class. There can only be one destructor specified.
If either constructors or destructors are not defined for the class
then the compiler will substitute a default function.
A call to a class constructor issued by code
generated by the compiler at the beginning of an object's lifetime. A
call to the destructor is performed at the end of the object's
lifetime. For example, consider the simple program below that uses
the complex class:
#include <stdio.h>
#include "Complex.h"
main()
{
Complex a(1.0,1.0);
Complex b(5.0);
printf("a real = %f a imaginary = %f\n",a.Real(),a.Imag());
printf("b real = %f b imaginary = %f\n",b.Real(),b.Imag());
}
In this program the two Complex
variables a and b are
automatically created when main()
starts executing. The
compiler will determine the appropriate constructor to call based on
the parameters specified in the object creation. The data members are
accessed using the structure operator, .. When
main
finishes executing the destructor for all objects
automatically created in main
will be called. In the
case of the Complex
class the destructor is an empty
function. Note the inclusion of the file stdio.h
. This
is necessary for the definition of the printf function.
It is also possible to directly create a new object. This process is
similar to the new operation in Pascal or malloc'ing an object in C.
The C++ operator for creating a new object is new. This will
return a pointer to an object of the requested data type. This is
shown in a rewrite of the proceeding program to explicitely create the
objects:
#include <stdio.h>
#include "Complex.h"
main()
{
Complex *a;
Complex *b = new Complex(5.0);
a = new Complex(1.0,1.0);
printf("a real = %f a imaginary = %f\n",a->Real(),a->Imag());
printf("b real = %f b imaginary = %f\n",b->Real(),b->Imag());
delete a;
delete b;
}
In this program the two complex objects are directly created using the
new operator. Again the compiler will determine the
appropriate constructor to call based on the parameter sequence given.
Access to the class members is now via the structure reference
operator, ->. Since these objects were explicitely created,
good coding practice dictates that the programmer explicitely remove
the objects when through with them. This is done using the
delete operator. Again, if there was a destructor function
defined it would be called at this point.
Complex
class. This
class also overloads the assignment operator so that one complex
number can be assigned to another. These features are used in the
following program:
#include <stdio.h>
#include "Complex.h"
main()
{
Complex a(1.0,1.0);
Complex b(5.0);
Complex c(0,0);
c = a + b;
printf("c real = %f c imaginary = %f\n",c.Real(),c.Imag());
}
The definitions for the overloaded assignment and addition operators
each take only one parameter. C++ in reality treats the
overloaded operator as another function. The left hand side of the
operator is considered to be the current object. In the example
above, this would be the a
. Writting a + b
could have equivalently been written a.operator+(b)
.
The overloaded operators are defined within the class and are
considered class member functions. Note that direct access to the
private data of the Complex object on the right hand side of the
operator is permitted is the operator+ function. Also, not the use of
the keyword this
in the assignment function which
cooresponds to a pointer to the current object. The return statement
dereferences this
to return a value that is equal to what
was stored in the left hand side of the assignment.
#include <stream.h>
#include <stdlib.h>
#include "Complex.h"
This file will include three other files. It is as if the information
contained in those three files was directly typed into the source
file. The C++ compiler provides include files for the standard run
time library functions that are used in C programs. The standard
include files are specified inside <>. This instructs the
compiler to search for the files in the "standard" include file
locations. If your program is using any functions from the run time
library you must include the appropriate files so that the compiler
has a definition (return data type and parameters) for these functions.
Enclosing the include file name inside "" starts
the file search using the current directory and standard file
reference mechanisms. You can specify the fully qualified path name
for the include file in this statement. If the file is not found and
it was not specified as a full path name then
the search is continued in the standard places.
g++ -o executable-file source-files -L/u1/cs173/ca1cs173/libThis will create an executable program in the file you specify as executable-file. If there are several source file that make up your program they can all be listed on the command line separated by spaces. The -L option is needed to specify where to find the g++ library which is not correctly installed on troi. If your program is made up of many files and you have only made changes to a few of them then it would be faster to only compiler the changed files and link everything together separately. To just compile one file use the command for each changed .cc file:
g++ -c source-fileThis generates an object file with a .o extension for each source file you compile. When all the source files have compiled sucessfully then link them together using the command:
g++ -o executable-file object-files -L/u1/cs173/ca1cs173/libThe object-files are the names of all the .o files created when you compiled each of your source .cc files.