1.124J | Fall 2000 | Graduate

Foundations of Software Engineering

Recitations

REC # TOPICS
1 Course Goals & Content, References & Recitations; Compilation; Debugging; Makefiles; Concurrent Versions System (CVS); Introduction to C++; Data Types; Variable Declarations and Definitions; Operators; Expressions and Statements; Input/Output Operators; Preprocessor Directives; Header Files; Control Structures
2 Functions: Declarations, Definitions, and Invocations; Inline Functions; Function Overloading; Recursion; Scope and Extent of Variables; References; Pointers; Function Call by Value, References and Pointers; Pointers to Functions; 1-D Arrays; Strings as Arrays of Char; Arrays of Pointers; 2-D and Higher Dimensions Arrays; Return by Reference Functions; Dynamic Memory Allocation; The Size of Operator; Data Structures; Introduction to Classes and Objects
3 Classes and Objects; Classes: Member Variables & Member Functions; Classes: Constructors & Destructor; Constructor Header Initialization; Copy Constructors; Member Variables & Functions Protection: Private, Protected & Public; Static Class Data and Class Functions; Class Scope; Pointers to Class Members; Operator Overloading; Friend Functions; Type Conversions
4 Inheritance: Public, Protected and Private Derivation; Multiple Inheritance; Inheritance: Constructors and Destructors; Inheritance: Redefining Member Functions; Virtual Functions and Polymorphism; Abstract Classes; File Streams; Namespaces; Assertions; C++ Standard Library String Class; Other Topics
5 Function Templates; Class Templates; Sorting and Searching Algorithms; Insertion Sort; Selection Sort; Shellsort; Quicksort; Linear Search; Binary Search
6 Introduction to Java®; Compiling and Running a Java® Application and a Java® Applet; Data Types; Variables, Declarations, Initializations, Assignments; Operators, Precedence, Associativity, Type Conversions, and Mixed Expressions; Control Structures; Comments; Arrays; Classes and Objects; Constructors; Initializers; Member Data and Functions; Function Overloading
7 Sun Java® Studio Standard 5; Inheritance; Controlling Access to Class Members; Strings; Packages; Interfaces; Nested Classes and Interfaces; Garbage Collection; Applets
8 Exceptions; Threads; I/O; Introduction to Java® GUI and Swing
9 The JComponent Class; Top-Level Containers; Intermediate Swing Containers; Atomic Components

These notes were prepared by Petros Komodromos.

Topics

  1. Course goals & content, references, recitations
  2. Compilation 
  3. Debugging  
  4. Makefiles
  5. Concurrent Versions System (CVS)  
  6. Introduction to C++
  7. Data Types
  8. Variable Declarations and Definitions 
  9. Operators
  10. Expressions and Statements
  11. Input/Output Operators
  12. Preprocessor directives
  13. Header files
  14. Control Structures

1. Course Goals and Content

  •  C++:
     Procedural and Object Oriented Programming
     Data structures
     Software design and implementation
     
  • **Algorithms:
    ** sorting (insertion, selection, mergesort, quicksort, shellsort, hashing)
     searching (linear search, binary search, binary search trees)
     
  • Java®:
     Object Oriented Programming (OOP) using Java®, Java® application and applets,
     Graphical User Interfaces, Graphics using Java®
     
  • **Advanced  Topics:
    ** Geometric algorithms, Java®3D, Database design using  JDBC, etc.
     
  • Term Project

Textbooks - References

  • C++
    • C++ Primer. Lippman and Lajoie. 3rd edition (required).
    • C++ How to program. Deitel & Deitel. 3rd edition.
    • The C++ programming language. Bjarne Stroustrup. 3rd edition.
    • On to C++. H. Winston. 2nd edition.
    • Object Oriented Programming in C++. Johnsonbaugh & Kalin.
    • C++ FAQ. Cline/Lomow.
       
  • Algorithms
    • Algorithms in C++. Sedgewick (required).
    • Algorithms, Data Structures and Problem Solving with C++. Weiss.
    • Introduction to Algorithms. Cormen, Leiserson, and Rivest.
       
  • Java®
    • The Java® Tutorial. Mary Campione and Kathy Walrath (required).
    • Core Java®. Gary Cornell and Cay Horstmann. 2nd edition.
    • The Java® programming language. Ken Arnold and James Gosling. 2nd edition.
    • Java®: How to program. Deitel & Deitel. 2nd edition.

Problem Sets

You need to follow the instructions that are provided with each problem set, concerning what you must submit. In all problem sets you must both electronically turnin the source code files, and submit hardcopies of all completed, or modified, source code files. Sometimes you may also need to provide screen dumps of the window with the output results from the execution of your programs.

The problem statement and the provided source code files can be obtained using CVS, which is a version control system (covered later in this recitation). Whenever source code files are provided, the following naming convention is used:
For each question there is a ps<number_of_problem_set>_<number_of_question>.C which you may need to use, e.g. ps3_2.C for question 2 of problem set 3.
In some cases a makefile (named make<number_of_problem_set>) is provided, which you may use to compile and link your code.

Please comment your code to make it more readable whenever you think it would be helpful for someone else to understand what and how you do it (i.e. for the graders). Comments may be incorporated in your code by either enclosing them between /* and */, or, by putting them on the right side of two division symbols //.
It is also very useful for both yourselves, and the graders, to indent your code in order to emphasize loops and different parts of your code. You should indent your code so that different blocks start in different columns making it less obscure and difficult to understand.
Please, do not make any other changes to the provided code, except those that you are asked to make.

You can print a file in a compact form (saving some paper) using the following command so as to have the name of the file and the time and date printed on the hardcopy.
        athena% enscript   -2Gr    -P<printer name>   <filename>
Whenever necessary, you can dump an X window directly to a printer using the following command and clicking on the window you want to print.
        athena% xdpr  -P<printer name>

Homework that is turned in late will be penalized as follows:
 

  • If turned in one day late i.e. by 2:30 p.m. on the day after the due date, the penalty is 10% of the overall score (i.e. 10 points off). You can turn in the problem set solution only once. The first hardcopy you will turn in will be the one to be graded, i.e. if you plan to submit it late you should not submit a solution on the due date, as well, but only the late one the day after the due date.
  • If turned in more than one day late, the penalty is 100% i.e. no credit will be awarded. No exceptions!

Please, always staple your hardcopies together and write clearly on the first page your name and username. Also, type within a comment at the top of each file you submit the following information:

  •  your first and last name
  •  the problem set number, and
  •  the question number

2. Compilation

After writing the source code of a program, e.g. using an editor such as emacs, you must compile it, which translates the source code into machine instructions. For the C++ programs you need to use the GNU g++ compiler. To be able to use this compiler, you need to add its locker using the following command (on the Athena prompt):
  
% add -f gnu

You can customize your account, using a dotfile, so that it will automatically add the gnu locker at start-up. In the file .environment you need to add the following line:

       add -f gnu

The add command attaches the specified locker to your workstation and adds it to your path. Dotfiles, such as .environment and .cshrc.mine, can be used to set environment variables, shell aliases and attach lockers in order to get the desired working environment. In the .environment dotfile you may also put the following lines to avoid typing them every time you log in:

       add infoagents
       add  1.124
       setenv CVSROOT /afs/athena.mit.edu/course/1/1.124/src

You can check if you properly use the GNU compiler by giving the following commands:

    _% which g++  _                  which should give you something like:
      /mit/gnu/arch/sun4x_55/bin/g++     or     /mit/gnu/arch/sgi_53/bin/g++

Then, you can use the GNU compiler to compile a C++ source code file. For example, to compile and link the source code file ps0_1.C, which is provided in PS0, you can use the following command:

    % g++  ps0_1 .C

Then, you can run the generated executable file, which is by default named a.out, by typing its name at the Athena prompt.
To give a specific name to the generated executable the -o option must be used:

     % g++  ps0_1 .C -o  ps0_1

Then, the generated executable file is named ps0_1

Sometimes, you may need to include an external library, e.g. the math library, using the -l option as follows, and the name of the external library (below the math library is included using m after -l) you want to include (in addition to including its header file in your files):

      %   g++    ps0_1 .C       -o  ps0_1     -lm

In some cases that you only need to compile a file without linking, i.e. to generate only the corresponding object file (machine language versions of the source code) and not the executable, you need to use the -c option. (In the following example a ps0_1.o will be generated)

     % g++  -c   ps0_1 .C

You also need to use the flags -ansi -pedantic to enforce the rules of ANSI Standard C++. In addition, it is useful to use the -Wall option to get all warnings, e.g.:

     % g++  -ansi -pedantic -Wall    ps0_1 .C     -o  ps0_1    -lm

It is more convenient to set an alias (e.g. c++), instead of typing all this every time. In particular you can add in your .cshrc.mine dotfile the following:
alias   “c++”  “g++  -ansi   -pedantic   -Wall   -lm”
and then simply use the following command to compile and link a program:
% c++   ps0_1 .C    -o  ps0_1

In addition, you can use makefiles to help you automate the compilation and linking of your programs. The following command creates the target_filename according to the instructions provided in the makefile make0a.

    % gmake -f makePS0a ps0_1

Eventually you must learn to use makefiles since they are extremely useful for the development of large programs with several different source code files.

Although you may work at any machine and using any compiler you want, you need to make sure that your code compiles properly using the GNU compiler on an Athena workstations. The graders will be using Athena workstations and the GNU compiler to check your solutions.

3. Debugging

Using a debugger can help you find logical and difficult to detect errors in your code much faster and easier. A debugger can be used to step through the program, line by line and examine the program variables while executing it. Therefore, you need to first correct all syntactical errors, using the messages from the compiler and then execute the program using the debugger to detect potential logical errors. Typically, you compile the program, identify errors using a debugger, correct it in emacs, compile and debug again, and repeat as often as necessary.

The debugger allows you to examine in detail what is happening during a program execution, or when it crashes due to a run-time error. In order to be able to use a debugger, such as gdb and ddd, you must first compile and link your code with the flag -g. e.g.:

     % g++   -g    ps0_1 .C    -o  ps0_1
 

  • **gdb debugger:
    **
    The  g++   -g    ps0_1 .C    -o  ps0_1 command generates an executable filename that can be checked with gdb, e.g. using the following command for the program compiled above:

         % gdb   ps0_1

You can find more information about GDB at Debugging with GDB - The GNU Source-Level Debugger.
 

  • **ddd debugger:

    **A user friendlier debugger, available on athena, is the Data Display Debugger (ddd), which uses the gdb for its operations. The ddd program is available in the outland locker. Since we also need the g++ compiler from the gnu locker for the compilation you need to type:

    % add outland
    _% add gnu

    _To invoke the ddd debugger with your executable program, e.g. for ps0_1, please, type:

    % ddd ps0_1 &

You can see that three windows pop up:

  1. The main window, which has three main parts.
    • the top panel with the menu bar and the tool bar
    • the middle panel with your source code, which is a read only panel code here.
    • the bottom panel with the (gdb) prompt, which is the debugging console
       
  2. Debugger command tool window, titled  ddd
  3. A tip window, which often provides useful debugging tips. You can close this window.

In general, the debugging cycle involves stepping through your program carefully once it compiles and seems to run. In particular, step into each function that you wrote; step over, e.g. using the ’Next’ button, system functions. At each step, ’Print’ the variable value(s) computed and  check them for reasonableness. Keep going until you find logical errors. Then, make the proper correction, using the editor, recompile and do this again.

The execution of the program can be controlled from the ddd command tool which has the Run, Interrupt, Step, Next etc. buttons.
 

  • To start debugging:
    • Find the first executable (non-declaration) line in the program
    • Place the cursor on that line (mouse click)
    • Then click on the ’Break’ button in the tool panel at the top of the main window. This will set a breakpoint at that line.  A breakpoint stops program execution. You should see a stop symbol at that   line.
    • Click on ’Run’ button in the ddd window.
    • This starts program execution, which will run until the next breakpoint  or the end. The program will run to the first breakpoint and halt.
       
  •   To step through the program:

You can step through the program using the buttons on the ddd window. A green arrow will indicate the current program statement being executed in the source code panel.

    •  clicking on the ’Next’ button steps over function calls and goes to the next line of the current function
    • clicking on the ’Step’ button steps into any function call on the current line
    • clicking on ‘Finish’ will finish executing the current function. You can use ’Finish’ to come out of any system source code (cout for example) you ’Step’ into
       
  • Looking at variable values:

    • To examine normal variables you can:
    • move the mouse on top of a variable and a pop up box will show the variable’s value
    • click on the variable so that it becomes highlighted, and then click on the ’Print’ button in the toolbar. Then, the variable’s value will appear in the console
       
  • To examine arrays:

    • moving the cursor on top of an array, either the memory address of the first element is shown if values have not been set, or if values have been assigned to the array, the array values are shown
    • highlighting the array name the array name will appear in the text box present in the tool bar. If ’Print’ is clicked now, the memory address is printed in the console.
    • adding a * before the array name and clicking ’Print’ will print the value stored in the first element of the array. If you wish to see all the elements in the array, replace ’*arrayName’ by ’*arrayName@arraySize’ in the text box and then click ’Print’.
       
  • To display a variable:

You can continuously see the values stored in a variable, by displaying it instead of printing it. The displayed variable will be shown in a new panel which will pop up above the source code panel. As you step through the program, any changes to the variable’s value will be shown there. You can display a variable by:

  • highlighting a variable and clicking on the ’Display’ button on the tool bar. Its value is updated every time it changes.

  • To undisplay a variable.

  • Right click on the variable’s box and choose ’undisplay’.

  • To display values of all local variables in the current function:

    • choose “Display local variables” from the ’Data’ tab in the menu bar of the main window.
       
  • To display the arguments passed to the current function:

    • choose “Display function arguments”  from the ’Data’ tab in the menu bar of the main window.
       
  • To input and output:

    • Input and output is done through the debug console.
    • cout line usually found before cin will display a prompt
    • stepping to a cin enter your input in the bottom window in the blank line at the bottom
       
  • Resources:

    You can learn more about ddd from the DataDisplayDebugger web-page.

    You can look at the ddd manual as well.

4. Use of makefiles

In some cases a makefile (named make<number_of_problem>) will be provided, and you may use it to compile and link your code. Makefiles are used to automate the compilation and linking of programs. To compile and link a specific program, assuming that a proper makefile is available, the following command is used:
% gmake -f make_file_name   <program_name>
You do not have to use the provided makefiles, but you can use instead your own makefiles, or any of the makefiles you have seen in the lectures or anywhere else. You need to turnin the makefile that you use to compile your files on athena (either the ones you got using CVS or your own) Learning to use makefiles will help you when you start writing and compiling larger programs with several files, which makes the use of makefiles necessary.

In problem set # 0, a simple makefile is provided for you, called makePS0a, which you may use to compile and link your code. There is also a more advanced makefile named makePS0b which you can use.

e.g.           athena%  gmake   -f makePS0a    ps0_1

Executing the above command creates the target filename ps0_1, according to the instructions provided in the makefile makePS0a.

5 . Concurrent Version Control (CVS)

For the development of large software packages and programs it is useful to use a control system for modifications and revisions. Although it may not seem very useful for the development of small simple programs (like your first homework problems) it would be very useful for your project, and you will benefit from getting used to using it. Therefore, it would be beneficiary for you to get used to using such a revision control system as CVS (Concurrent Versions System). You may obtain more information on CVS from the man command (% man cvs) and from the following URLs:

The provided source code files are in the directory /mit/1.124/Problems/<Problem set number> from where you can copy them using CVS to your directory, and, make the necessary additions and/or modifications. To use CVS to check out the problem sets for the 1.124 you should first set the environment variable CVSROOT as below: (you can also put it in your .environment dotfile)
% setenv CVSROOT /afs/athena.mit.edu/course/1/1.124/src
then you can use the command:
% cvs co Problems/PS<Problem set number>
or, using the alias defined in 1.124/src/CVSROOT/modules
% cvs co OOP_PS<Problem set number>

6. Introduction to C++

C++ is both a procedural oriented programming language and an object oriented programming (OOP) language, since it allows you to organize your program not only around functions (procedures), but also and most commonly used, around data.

A procedural language is based on a list of instructions (statements) organized in procedures (functions) and emphasizing the computations to be performed. Languages such as Fortran and C are procedural languages. C++ which is based on C, has many additional features that enable object oriented programming, where emphasis is given on the data and their behavior. Java® is a pure object oriented language.

The additional features and advantages of C++ are the following:

Classes allow the programmer to create her/his own data types extending the capabilities of the language according to the physical world problems. Classes are similar with the data structures in C, which are also available in C++. However, within a class both data variables and member functions that are used to work with the data variables can be provided.

class Complex
{
public:
double real;
double imaginary;
};

main()
{
Complex x;
x.real =15.5;
x.imaginary = 2.5;
}

There are many additional features, like inheritance and virtual functions, added to C++ which are related with the classes and provide simple ways to handle objects and develop programs in an object oriented programming style.

C++ allows reusability since a class which has been written, debugged and checked can be distributed to other programers and with minimal effort be incorporated in several programming packages.

C++ supports function overloading which allows the use of functions with the same name, as long as they have different signature. The signature of a function is considered its name and the number and type of its arguments. e.g.:
int min(int x, int y) {      }
double min(double x,  double y) {      }

In addition, virtual functions and polymorphism allows the dynamic binding on functions during run-time instead of static binding during compilation.

C++ allows operator overloading, i.e. to use operators with user defined data types according to a specified function associated with the specific operator. For example, we are able to add two complex numbers which are a user defined data type, as long as we provide the necessary functions for operator overloading.
main()
{
Complex x,y,z;
……..
z = x + y ;
}

In C++, there are two ways to comment, // (which comments everything until the end of line), and /*  */ (which comments everything between /* and */.

Two latest features of C++ are the templates and the exception handling. The templates allow us to parameterize the types within a function or a class providing a general definition that can be used for many different purposes. The exception handling mechanism provides a mechanism to respond to and handle run time errors (like division by zero, exhaustion of memory, etc.).

7. Data Types

  • Boolean: bool
  • Character and very small integers: char
  • Integers: **short int, int, long int **    (short, int, long)
  • Floating (single, double and extended precision): float, double, long double

The bool data type can be assigned the values true and false, which correspond to 1 and 0, respectively.
  
A char can be used both as a character and as an integer, depending on how it is used. Therefore, char, short, int, and long are all called integral data types.

There are also unsigned versions of integer types, which allow the increase of the range of the larger number that can be stored with them, by not using any bit for the sign: unsigned char, unsigned short int (unsigned short), unsigned int, unsigned long int (unsigned long).

The reason of using different data types is mainly for memory efficiency, since we can use the data type which correspond to our needs avoiding useless waste of memory. The proper data type must be selected and used based on the expected requirements during the program’s execution.

To refer to particular variables that correspond to a particular chunk of memory in C++ (and in any other programming language) we have to use identifiers (variable names). The variable names in C++ are case sensitive as they are in C, must begin with a letter or an underscore (_), and should not be a reserved keyword. You should use reasonable variable names that provide some meaning to the reader of your code.

Using the above keywords we can declare the data type of a variable giving to the compiler information about the necessary amount of memory required to store (i.e. the memory that is required to be allocated and reserved for) the variable and which is machine dependent, e.g.:

  • int i, j ;
  • double x,y,z;
  • long int k;
  • unsigned short int i;

The const type modifier (or qualifier) defines a variable as a symbolic constant and does not allow any change of its value. Therefore, a const variable must be initialized when defined, since any attempt to change its value results in a compile-time error.

       const int i=10;

Note the different meaning of the following declarations:

const int *p: Pointer to a constant integer
       int *const p: Constant pointer to an integer
       const int *const p: Constant pointer to a constant integer

8. Variable Declarations and Definitions

Before using any variable we have to declare it, i.e. inform the C++ compiler what is the data type of the variable. Every variable has a certain data type that determines the storage requirements and the operations that can be performed on it. In C++, as in C, we can combine several separate variable declarations into one declaration, as long as each variable is of the same data type, e.g.:

<data_type1>  <variable1_name>;
           <data_type2>  <variable2_name> = <initial_value>,  <variable3_name>;< /EM >

int a, b, c;
double x,y ;
float z ;

The above declarations are also definitions. A declaration simply informs the compiler that the variable exists, its data type and that it is defined somewhere else in the program. C++ allows to have many declarations of the same variable as long as they are consistent. The definition of a variable defines the variable’s name, its data type and may also initialize the variable. Defining a variable informs the compiler about the variable’s data type so as to reserve the proper amount of memory to store values for that variable. The difference is that the definition reserves memory for the variable. Therefore, there must be only one definition of a variable in a program. A declaration is also a definition if it also sets aside memory at compile time.

For example, the following statement is a declaration because it informs the compiler that an external global variable will be used, but no memory is allocated for that variable. The memory is allocated at the definition of the variable.
        extern int x_limit ;                         // declaration

We can also initialize a variables in its definition, assigning an initial value and this is called initialization. When a value is assigned to an already defined variable this is called assignment :
      int a, b, c;                          // definitions
      _double x = 3.4, y(4.);< /EM >        // definitions and  initializations
     _ float  z = 9.9;< /EM >                    // definition and initialization
      c = 10; < /EM >                            // assignment

9. Operators

You will often need to use operators which perform a specified action on their operands. Most operators are binary, i.e. they have two operands, e.g. a+b. The operators in C++ are the same as the ones used in C.

These are:

  •  the arithmetic operators of C++ are the +, -, *, / and %
  •  the assignment operator is the =
  •  the shorthand (abbreviated) assignment operators: += ,  -= , *= and /=
  •  the (unary) postfix and prefix increment/decrement operators ++ and –
  •  the relational operators are: > , < , >= , and <=  (used to compare two expressions)
  •  the equality operators are: == and !=   (used to check two expressions for equality)
  •  the logical operators are the: && , || , and !

An assignment expression has the value that is assigned to the variable on the LHS, and, therefore, several variables can be assigned the same value in one statement,

e.g.:    x = y = z = 100;

The RHS of logical operators is executed only if its necessary for the decision that must be taken. e.g. if the LHS of a ‘logical and’ (&&) is false, there is no reason to examine its RHS.

In addition, in C++ it is allowed to create new definitions for operators applied to user defined data types.
main()
{
Point x,y,z;
……..
z = x + y ;
}

10. Expressions and Statements

Each C++ program must contain a function named main(), as in C. The execution of a C++ program begins from the first statement of main and finishes when the last statement of main is executed (e.g. when the last curly brace of main is reached).

An action in C++ is referred as an expression, while an expression terminated by a semicolon is called a statement. More than one statements enclosed in a pair of curly braces is called a compound statement.

Precedence and associativity: The sequence (order) with which individual components of an expression in C++ are executed is based as in C on the order of precedence. When the operators have the same precedence the associativity defines the order of execution. A table with the precedence and associativity of the C++ operators is provided. Similar tables you can find in any C++ textbook.

e.g.         a  =  b   +=    5    +      3      /      2     -     5     +     17    /     2    /     3
(order):     (8)      (7)        (4)           (1)          (5)         (6)          (2)       (3)

The following table provides the precedence and associativity of the C++ operators with the highest precedence being the operator :: having precedence level equal to 1.

PRECEDENCE ASSOCIATIVITY OPERATOR FUNCTION
1 right :: global scope (unary)
1 left :: class scope (binary)
2 left -> , . member selectors
2 left [] array index
2 left () function call
2 left () type construction
3 right sizeof size in bytes
3 right ++ , – increment, decrement
3 right ~

bitwise NOT

3 right ! logical NOT
3 right + , -

uniary minus, plus

3 right * , & dereference, address-of
3 right ()

type conversion (cast)

3 right new , delete free store management
4 left ->* , .*

member pointer selectors

5 left * , / , % multiplicative operators
6 left + , - arithmetic operators
7 left << , » bitwise shift
8 left < , <= , > , >= relational operators
9 left == , != equality, inequality
10 left & bitwise AND
11 left ^ bitwise XOR
12 left | bitwise OR
13 left && logical AND
14 left || logical OR
15 left ?: arithmetic if
16 right = , *= , /= , %= , += , -=
<<= , »= , &= , |= , ^=
assignment operators
17 left , comma operator

Conversions: C++ defines a set of standard conversions, implicit type conversions, that are used in arithmetic conversions, assignments using different data types, and passing arguments to a function of different data types than the function parameters. In particular, when we have such mixed expressions the compiler makes some standard conversions, e.g. in binary operations the lower data type is promoted to the higher one (which dominates), so as to avoid losing information.

The following order is used:

          bool, char, short int < int < long int < float < double < long double
_
bool_, char and short int are always converted to int whenever they appear in any expression, i.e. before performing any operation on them. A bool is promoted to int, getting the value 1 or 0, depending on its value (true or false, respectively). When a number is converted to a type bool all values other than zero are converted to true and a zero value is converted to false. Integer constants (e.g. 17) are considered int, and, floating point constants (e.g. 4.53) are considered double. We can use L after an integer and a floating point constant to define that should be considered as a long int, and, a long double, respectively.

In C++, we can also define rules of conversions to be used with operators applied on user-defined data types.

We can also explicitly define type conversions using casting to force the explicit conversion from one data type to another.

_(dataType) variableOrExpression ; _ and   dataType (variableOrExpression) ;

Another way do an explicit conversion, i.e. to cast a data type constant or variable to another data type, is using the keyword static_cast followed by a data type name surrounded by angle brackets and the certain variable or constant to cast within parentheses, e.g.
_
static_cast <dataType> (variableOrExpression)_
_static_cast <float> (5) / 3        _             //  gives  1.66667

/* Example: Mixed Expressions - Precedence - Associativity - Casting */

#include <iostream.h>
main()
{
int i=4 ;
float f = 2.5 ;
double d = 3;

cout << “\n  i  / 5 * f = "            // Mixed Expressions
<<  i  / 5 * f  << endl ;

cout << “\n ‘a’  = "    <<  ‘a’ << endl ;

cout << “\n ‘a’ - 1 = "                // Mixed Expressions
<<  ‘a’ - 1 << endl ;
cout << “\n ‘f’ - ’d’ = "              // Mixed Expressions
<<  ‘f’ - ’d’ << endl ;

cout << “\n  f + 5 * d = "                 // Precedence
<<  f + 5 * d  << endl ;

cout << “\n  f * 2 * 2.5 = "               // Associativity
<<  f * 2 * 2.5  << endl ;

cout << “\n (float) i  / 5 * f = "         // Casting
<<  (float) i  / 5 * f  << endl ;
cout << “\n float i  / 5 * f = "         // Casting
<<  float (i)  / 5 * f  << endl ;
cout << “\n static_cast <float> (5) / 3 = "     // Casting
<< static_cast <float> (5) / 3 << endl;
}

Results

i  / 5 * f = 0               float 0.0

‘a’  = a                      character

‘a’ - 1 = 96                  int 96

‘f’ - ’d’ = 2                  int 2

f + 5 * d = 17.5        double 17.5

f * 2 * 2.5 = 12.5      double  12.5

(float) i  / 5 * f = 2      float 2
float(i)  / 5 * f = 2      float 2
static_cast <float> (5) / 3 = 1.66667

11. Input/Output Operators

In C++ the predefined objects cin, cout and cerr are available for input and output operations. The predefined object cin refers to the standard input (which is by default the keyboard), cout and cerr refer to the standard output and the standard error, respectively (which are both by default the display). These defaults can be changed using redirection while executing the program.

The output operator (<<) (known as insertion operator) directs (display) output information on your standard output (screen), e.g.
cout << “\n  x = " << x << endl ;
“\n” represents a new line character, while endl inserts a new line and flushes the output buffer. The operating system buffers the output to the display characters and prints them out in a batch to minimize I/O overhead. This may lead to wrong indications of where the error may be if we consider the printed out information without flushing the buffer.
We may have several output operators in the same output statement

Similarly, you can use the input operator (») to obtain (read) input values from the standard input (keyboard). e.g.:
cout << “\n x = "  ;
cin » x ;    cin » y » z;

The standard input-output library (iostream.h) must be included using a preprocessor directive, in order to be able to use the input and output operators, as well as the manipulators without arguments such as endl, flush, hex, oct, etc.

Certain options may be specified when using the output stream operator to select the way that the output should look. The precision can be set using a iostream manipulator, the setprecision(number_of_digits), while the setiosflags(options separated by |) can be used e.g. to specify whether the decimal point or tailing zeros should be shown. The setw() specifies the field width in which the next value should be printed out. It is the only manipulator that does not apply to all subsequent input or output, but becomes zero as soon as something is printed. The setfill(c) makes c the fill character. To use these parameterized stream manipulators (i.e. with arguments) we need to include the iomanip.h header file. e.g.:
cout << setprecision(2) << setiosflags(ios::fixed | ios::showpoint)
<< “\n\n 3. = " << 3. << “\t 0.333333 = " << 0.333333 << endl;
(will give: 3.=3.00    0.333333 = 0.33)

We can also redirect the input from the keyboard to a file and the output to another file:
athena%  executable_file_name  <  input_file_name      >  output_file_name

Finally, there is a standard error stream cerr which is used to display error messages

      cerr << “\n Not proper values were provided!”  ;

The following member functions can be invoked by the input stream, cin: cin.good() returns true if everything is ok;, cin.eof() return true if EOF is reached; cin.fail() returns true if a format error has occurred.

The C input/output functions**scanf()/printf()** can also be used, since C is a subset of C++. In that case the stdio.h header file (which contains their prototypes) must be included. Then, the buffer can explicitly be flushed using “fflush(stdout);”.

However, when both C input/output functions and C++ input and output operators are used, you need to provide the following function call before doing any input or output, to avoid problems:
ios::sync_with_stdio();

12. Preprocessor Directives

The preprocessing takes place prior to the actual compilation. The preprocessor searches all files that are to be compiled and takes action according to the preprocessor directives. The preprocessor directives are the lines which begin with # (usually placed at the top of the source code file).

An include preprocessor directive results in the substitution of it, with the contents of the indicated file. i.e. the following include preprocessor directive:
#include <file.h>

is equivalent to typing the contents of the included file at that point.

There are two variations of the include preprocessor directive:

       _#include <file.h> _     or      #include “file.h”

The difference is that in the first case the preprocessor searches for the included file in the standard include directory, while in the second case it searches in the current directory. The latter case is usually used for the user written functions.

Another preprocessor directive is the #define which it can be used to associate a token string with an identifier, e.g.
#define PI 3.1415926

The preprocessor will replace PI wherever it appears in a file with the provided token. After the preprocessing finishes the compilation starts, in which the token is treated as a floating point constant.

Other preprocessor directives are the following: #ifdef, #ifndef, and, #endif  They can be used for conditional compilation.
e.g. the …… statements will be skipped if  _MY_HEADER_H has already been defined.
#ifndef _MY_HEADER_H
#define _MY_HEADER_H
……..
#endif

Also, while compiling a program we can define a preprocessor constant on the command line using the -D option followed by the name of the preprocessor constant and therefore certain parts of the code can be selectively excluded.

e.g. compiling the file with -DDEBUG_MODE option will consider the cout statement:
#ifdef DEBUG_MODE
cout << “\n testing debug mode \n” << endl;
#endif

The define directive can also be used to specify macro substitutions with variable parameters. e.g. having defined the following macro using:             

#define mult(x,y)  (x*y)

then, the following statement:                   product = 267.2 + mult(25.7, 33.6)
will be replaced during preprocessing by:     product = 267.2 + (25.7 * 33.6)

13. Header Files

To be able to use the input and output operators you must first include the standard input-output library (iostream.h) using a preprocessor directive:
#include <iostream.h>

When the C input/output functions scanf()/printf() are used the stdio.h header file (which contains their prototypes) must be included instead.

Similarly to be able to use any other standard library function you need to include its header file which contains all necessary declarations.
e.g. to be able to use the math function, such as sqrt() you need to include the math.h header file using the following command:
#include <math.h>

When the header file that you include is in the current directory, e.g. a header file that you wrote, then you should use double quotes, instead of
brackets, e.g.:
#include “myheader.h”

According to the new ANSI/ISO standard, which however is not followed by all available compilers yet, the iostream header file can be included using:

  #include <iostream>
using namespace std;
int main()
{
std::cout << “\n testing:
pi = " << 3.1415 << endl;
}

Header files are very useful to provide declarations (e.g. for global variables and functions) in order to avoid incompatible declarations which may happen when multiple declarations are provided in several source-code files. In addition, any changes to a declaration would require only a single local modification instead of having to update all appearances of the declarations. A header file should never contain definitions, (unless its a definition of an inline function).

14. Control Structures

Control statements are used to control the flow of our programs which is normally sequential. i.e. statements are executed one after the other in order. Changing this sequential execution in a controlled way is called transfer of control and is achieved using control structures.

The C++ control structures are identical with those of C. The relational operators ( > , < , >= ,  <= ), equality operators ( ==  ,  != ), and the logical operators (&& , || ,  !) are used in logical tests which produce either true or false (0).

Typically, the following operators are used to form a logical test for the control structures which determines what action should be taken:

  • the relational operators: > , < , >= , and <=  (which are used to compare two expressions)
  • the equality operators: == and !=   (which are used to check two expressions for equality)
  • the logical operators: && , || , and !

The result of the above operators is of type bool, either true (i.e. 1), or false (i.e. 0). You should never compare floating-point values for equality or inequality, since the floating-point numbers can, in general, only be approximated since only a small number of digits are used for computer representation of values.

In all control structures, a simple (i.e. single), or a compound, i.e. a sequence of statements enclosed in curly braces, statement is either conditionally or repeatedly executed, based on a logical test.

The**if** and if-else, as well as the switch control structures are used to make certain selections
The simplest selection control structure is the if, where if the logical test is true (i.e. non zero), then the following statement (or statements in curly braces) is (are) executed. Otherwise the statement (or statements) is (are) skipped and the statement after the if control structure is executed.
if (logical test)
statement ;

if-else if-…-else control structure provides several alternative actions. (It is more efficient to put the most probable selection first to reduce the chances of multiple checks)
if (logical test) (if-else if -else provides alternative actions)
{
statements (executed if (logical test) is true)
}
else if  (another logical test) (checked  if (logical test) is false)
{
statements
}
else (executed if not any (logical test) is true)
{
statements
}

The**switch()** control structure is useful when there are many different selections. It consists of multiple selection cases and an optional default case. Only constant integral expressions can be checked in switch() cases. The controlling expression in the parentheses determines which of the cases should be executed and starts executing statements in that case continuing until the closing brace of the switch control structure, or until a break is reached. The break causes the program to exit the switch structure and execute the next statement after it.
switch (x)
{
case 1:
statements
break;
case 2: case ’b’: case ’B’:
statements
break;
case 3:
case ’c’:
statements
break;
……..
default:
………
}
 

while, do/while, and for are the repetition control structures of C++, i.e. are used when iterations are required.

The statements of the while control structure are executed repeatedly as long as the logical test is true, and, at each iteration when the closing brace is reached, control is passed back at the beginning of the while loop. The while loop is continuously repeated until the logical test becomes false (i.e. equal to zero)
while(logical test)
{
statements    // statements executed repeatedly as
                                       // long as the logical test is true
}

The do/while is similar to while with the only difference that its body is executed at least once since the check is done at the end.
do
{
statements // executed repeatedly as long as the logical test
} while(logical test);             // is true but always executed at least once

The for control structure is used for repetitions, when we have a regular incrementing. First, expr1 is evaluated, which is usually used to initialize the loop variables. Then, the logical test (which is a loop continuation condition) is evaluated, and if it is true (i.e. nonzero), the following statements, within the curly braces, are executed. Finally, expr3 is executed (usually providing an increment or decrement of the control variable), and then the procedure from evaluation of the logical test is repeated, as long as it is true (i.e. non zero.) expr1 and expr2 can be comma separated lists of expressions, which are executed from left to right. All three expressions are optional, although the two semicolon are always required.
for (expr1 ;  logical test ; expr3)
{
statements in body of for loop
}

Finally, the following control structure is the conditional operator which produces a value based on the logical test. Therefore, it can be placed inside another expression. The first expression after the question mark is executed if the logical test is true. Otherwise, the expression after the colon is executed.
( logical test)  ?  when_true_statement : when_false_statement ;

e.g.:   max = (x>y) ? x : y ;
(i%2) ? cout << i << " is an odd integer” : cout << i << " is an even integer” ;

The break statement is typically used to skip the remainder of the switch statement. It is also used to exit repetition control structures (i.e. while, do/while and for). In all these cases execution continues with the first statement after the terminated control structure. The break statement exits the innermost loop, or switch statement.

The continue statement is used to skip the current iteration of a repetition control structure and continue with the next iteration, if there is one, i.e. the current iteration only is terminated and execution continues with the evaluation of the logical test of the next iteration. It goes to the next iteration of the innermost loop.

These notes were prepared by Petros Komodromos.

Contents

  1. Functions: declarations, definitions, and, invocations
  2. Inline Functions 
  3. Function Overloading
  4. Recursion 
  5. Scope and Extent of Variables
  6. References
  7. Pointers 
  8. Function call by value, References and Pointers
  9. Pointers to functions
  10. 1-D Arrays
  11. Strings as arrays of char
  12. Arrays of pointers
  13. 2-D and higher dimensions arrays
  14. Return by reference functions
  15. Dynamic memory allocation
  16. The sizeof operator
  17. Data structures
  18. Introduction to classes and objects

1\. Functions

A function is a block of code which performs specific tasks and can be called from another point of our program. After the execution of all statements of a function, control returns to the point from where the function was initially invoked, and the next executable statement is executed. With functions we can organize components of a program into sub-units using procedural abstraction. This allow us to break a complex problem into several small subproblems that can be handled easier by separate blocks of code.

main() is a special function that is invoked whenever the program is executed. Upon its return the execution of the program is terminated. It typically returns an int, which by convention is equal to zero when there are no problems. In contrast, a nonzero value indicates an error.

The following three steps are required to use a function:

(i) Function declaration or prototype (optional): It informs the compiler that the specified function exists and that its definition appears somewhere else in one of the source code files. In particular, it specifies its name, and parameters. By providing information about certain characteristics of the function, before invocation statements, the compiler is able to make checks for possible inconsistencies in function calls.

The function declaration specifies the function name, which should follow basic naming restrictions, the number and data types of the arguments to be passed to the function and the data type of the returned value. The keyword void is used when the function does not return a value or/and has no arguments. It is also allowable to use empty parameter list, i.e. empty parentheses, but according to the Standard C++, the return type must be explicitly specified.

The name and the data type of the parameters of a function constitute the function signature which uniquely identifies the function.

The declaration is optional if the definition appears before any function call. However, it is a good practice to always provide function declarations. They should preferably be provided in a header file, which can be included whenever it is necessary. A function can be declared multiple times in a program, and, therefore, a header file with declarations can be included several times as well.

            returnType functionName(param1Type param1Name, param2Type,…..);

The return type of a function can be any data type, either built-in or user-defined. It is useful, although optional, to provide the names of the arguments for documentation purposes. A function can return a single value, which can also be a data structure, or an object, when it returns back at the invocation point. If no value is returned then the return type of the function should be defined as void.

                                    e.g:   int fun1(double, int);
void fun2( double x, double y);
float fun3(void);

(ii) Function definition: The function definition consists of the function definition header and the body of the function definition. Although the definition header looks like the declaration, the names of the local parameters are required. Inside the function body the provided statements are executed. More specifically, a function definition consists of 4 parts:

a return type

a function name

an argument list

 the function body

A function declaration is similar to the header of the function definition, providing the return type, name, and parameter list of the function. The function definition must appear once, except in the case of an inline function, providing the body of the function enclosed in curly braces.

When passed-by-value is used, the parameter names are associated with the values passed during function invocation, and they are actually local variables whose memory is released upon exiting the function. When a return statement is encountered control returns to the point from where the function was invoked.

returnType functionName(par1Type par1Name,  par2Type par2Name, …..)
{
function body
}

e.g:     void fun2(float x, double yy)
{
cout << “\n x+y = " << x+yy;
}
 

(iii) Function calling (or, function invocation): A function is invoked by writing its name followed by the appropriate arguments separated by commas and enclosed in parentheses:
   
        function_name(arg1, arg2);

If the data types of the arguments are not the same with the corresponding data types of the parameters of the function then, either an implicit conversion is done, if possible, or a compile-time error occurs. In case of an implicit conversion in which accuracy may be lost, e.g. converting a double to an int, a warning should be issued during compilation.

Functions work with copies of the arguments when they are called-by-value (i.e. without using references). Upon entering the function memory is temporarily allocated in order to store the passed values. After the function has been executed, the control returns to the calling function and the values of the local variables and parameters that are called-by-value are lost, since the corresponding memory is no longer reserved. The only exception is when we deal with a static local variable.

/* Example on functions */
#include <iostream.h>
#include <stdio.h>
double get_max(double x , double y);
void print_max(double x , double y);

main()
{
double x=11, y=22 ;

cout << “\n Max = " << get_max(x,y) << endl;
print_max(x,y);
}

double get_max(double x , double y)
{
if(x>y)
return x;
else
return y;
}

void print_max(double x , double y)
{
if(x>y)
cout << " Max = " << x << endl;
else
cout << " Max = " << y << endl;
}

Output
Max = 22
Max = 22
 
 

Default Arguments of a Function

Some, or all, arguments of a function can be specified and used in case only some, or no, arguments, respectively, have been provided at the function call. Whenever less than the total number of expected arguments are provided, the specified defaults values are used for the corresponding right-most arguments, i.e. the provided values by the function call are used for the left-most parameters and the remaining parameters on the right take their specified default values. Therefore, all parameters without default values must appear first in the parameter list.

/* Exaple on default arguments to a function */
void fun(double a=11.1, int b=22, double c=7.6);

int main(void)
{

fun();
fun(34.9);
fun(5.6, 3);
fun(12.4, 3, 19.5);

return EXIT_SUCCESS;
}

void fun(double a, int b, double c)
{
cout << " a = " << a << "   b = " << b <<”   c = " << c << endl;
}

Output a = 11.1   b = 22   c = 7.6
a = 34.9   b = 22   c = 7.6
a = 5.6    b = 3    c = 7.6
a = 12.4   b = 3    c = 19.5

2. Inline Functions

Using functions saves memory space because all the calls to a particular function execute the same code, which is provided only once. However, there is an overhead due to the extra time required to jump to the point that the instructions of that function are provided and then return back to the invocation point. In some cases, where very small functions are used repeatedly, it may be more efficient to have the code incorporated at the point of the function call instead of actually calling the function, so as to avoid the associated overhead. Inline functions can be used for this purpose. An inline function is a regular function with a suggestion to the compiler to insert the instructions at the points where the function is called.

A definition of a function as inline suggests to the compiler to expand the body of the function at all points where it is invoked. An inline function is expanded by the compiler during the compilation phase and not by the preprocessor (during macro substitutions). This is useful for very short functions which may result in a computational overhead whenever they are called. Inline functions eliminate the overhead of function calls while allowing the usage of procedural abstraction.

A function is defined as inline using the keyword “inline” before its header. The inline directive should be specified in the function definition, rather than in its declaration.

e.g.:
inline max(double x, double y)
{
……..   //  statements
}

The compiler may, or may not, expand the function at its invocation points to avoid this overhead. However, it is necessary that the compiler sees the function definition, and not just the declaration, before it encounters the first function call, so as to be able to expand it there.

A disadvantage of inline functions is that if an inline function is modified, then all the source code files that use that function should be recompiled. This is necessary because, if the compiler follows the inline suggestion, the body of an inline function is expanded at any point where the function is called. In contrast, a non-inline function is invoked during run-time. Also, the inline function must be defined in every file that it is used, with identical definitions. an alternative way to avoid having multiple copies of the same function definition is to have the function definition in one file which can be included in all source code files that make use of the inline function.

3. Function Overloading

C++ allows function overloading, i.e. having several functions with the same name, in the same scope, as long as they have different signatures. The compiler can distinguish which one to actually invoke based on the data types of the parameter list of each one, which should be different, and the provided arguments. The only overhead from using overloaded functions is during compilation, i.e. there is no effect during run time.

The signature of a function is considered to be its name and parameters, specifically their number and data types. The return type of a function is not considered part of a function signature. When an overloaded function is called, the compiler selects the matching function among those with the same name using the data types of the provided arguments. The process in which a function is selected among a set of overloaded functions is called function overload resolution. The latter process follows certain rules based on the arguments provided at a function call. Briefly, the function overload resolution process first identifies all candidate functions, i.e. all visible functions with the same name. Candidate functions are selected based on the argument data types at the function call and considering possible conversions. Finally, the best matching function, if any, is selected among the remaining candidates based on how good the required conversions are.

/* Example on function overloading */
int min(const int x, const int y) ;
double min(const double x, const double y) ;
main()
{
int x,y,m;
x=3;
y=7;
cout << “\n min(x,y) = " << min(x,y) << endl;

double z=4.5, w=2.34 ;
cout << " min(z,w) = " <<  min(z,w) << endl;
}

int min(const int x1, const int x2)
{
if(x1<x2)
return x1;
else
return x2;
}

double min(const double x1, const double x2)
{
if(x1<x2)
return x1;
else
return x2;
}

Output
min(x,y) = 3         // int min(const int x1, const int x2) has been called
min(z,w) = 2.34      // double min(const double x1, const double x2) has been called

4. Recursion

An algorithm can have an iterative formulation, a recursive function, or both formulations combined. A function is said to be recursive if it calls itself. Each time that a function calls itself a new set of local variables is created. This set is independent of any other local variables created in previous calls, assuming that all calls are by value and no static local variables are used.

Typically a recursive function involves a recursive call to itself with a smaller problem to solve. This is the ‘divide-and-conquer’ approach which decomposes a problem into smaller ones with similar characteristics with the original problem. A recursive function should have, at its beginning, a base case that is tested to determine whether the recursive calls should be terminated.

The following two functions can be used to compute the factorial of a number. First, an iterative version is provided, followed by a recursive one.

  _int factorialIterative(int n)           _     // iterative version
{
int result=1;
while(n>1)
result *= n–;
return result;
}
 

int factorialRecursive(int n) // recursive version
{
if(n==0)
return 1;
return  n*factorial_Recursive(n-1);
}

5. Scope and Extent of Variables

The scope of a variable is where in the program the variable is accessible and therefore it can be used, i.e. the scope defines where the variable can be used, or assigned. In general, variables are accessible only in the block in which they are declared. In C++ there are 3 different scopes: the local scope, the namespace scope, and the class scope.

A variable defined inside a function is a local (or automatic) variable. The scope of a variable that is defined in a compound block inside a function is limited inside that block. Local scopes can be nested. However, the parameters of a function have local scope and cannot be redeclared inside the function’s local scope, or inside any other nested local scopes in the function.

An entity that is defined outside any function or class definition has a namespace scope. User-defined namespaces can be defined using namespace definition, as we will see in a later recitation. For now we will consider only the global scope, which is a specific case of the namespace scope. In particular, the global scope is the outermost scope of a program. A global variable (or function) is a variable defined outside of all functions.

The scope of a global variable is universal, since it is accessible by all statements after its declaration or definition. In contrast, the scope of a local variable is local since it is accessible only inside the function in which it has been declared.

A global entity (i.e. variable, object, function) can be declared several times in the source-code files of program, while only one definition can appear. The only exception is inline functions that can have several definitions, identical however, one at each source code file. A global variable can be declared many times using the keyword extern to indicate that somewhere else (in another source code file) that variable is defined. Only one variable definition must be provided and it is only then that memory is allocated for the variable. If a global variable is not initialized at its definition it is automatically initialized to zero.

In addition, in C++, the user defined data types (classes) allow us to specify certain permissions for the access of data members of the objects we define using the classes we develop.

The extent or lifetime of a variable is the duration for which memory is reserved for it. The memory allocated for parameters and local variables is in general reclaimed upon returning from the function, and therefore, they have dynamic extent. In contrast, the global variables have static extent since the memory allocated for them is never reallocated. Local variables can also have static extent if the keyword static is used when they are defined.

There are 4 storage classes:

Automatic: Local variables, or objects, are local variables with dynamic extent, unless they have been defined as static. Memory is allocated for automatic variables and objects from the run-time stack upon entering the function and is automatically deallocated, i.e. released, upon exiting the function. If an automatic variable is not initialized then its value is unspecified since the allocated memory contains random information.
 

External: Global variables which, unless they have been defined as static, are accessible from any part of the code, i.e. in any file as long as either the global variable definition (and this can occur only once), or a declaration using the keyword extern (this can occur several times) is provided.
 

Static: Both local and global variables can be defined static using the static keyword. However, static local and static global variables are different.

Static local variables are initialized once, have extent, i.e. lifetime, throughout the program, but their scope, i.e. visibility, is limited to the function in which they are defined. Defining a local variable as static results in having static extent for that variable, i.e. the allocated memory for that variable is reserved until the termination of the program. Memory for a static local variable is allocated only once at the first time the function is entered and the memory with the currently stored value is retained and can be used during the next entry. An uninitialized static local variable is by default initialized to zero.
_
Static global variables_
, or functions, are global variables, or functions, that are not accessible outside the file they are defined, i.e. their use is restricted only within the file they are defined.
 

Register: This storage class is similar with automatic with the only difference that it is suggested to the compiler to keep these particular variables in some registers in CPU so as to save time when these variables are frequently used, e.g. frequently used variable in a loop. An automatic variable can be declared to have register storage using the register keyword, e.g.: register int i;

Access of variables: A name resolution process determines, during compile time, to which particular entity, i.e. location in memory, a particular name (of a variable, function, object, etc.) refers, considering the provided name and the scope in which it is used.

Global Variables: variableName or  :: variableName
Local Variables:  variableName
Object Member Variables:  object.variableName or point_obj -> variableName
or this -> variableName

If inside a function a local variable has the same name with a global variable, then the local variable hides the global. In C++ we can access the global variable using the scope resolution operator ::var, access the global var variable.

/* Example on scope and extent of variables */
#include <iostream.h>
extern double  y;               //  external variable (defined in another file)
static double x=25.5;      //  static global variable
void fun(double x);

int main()
{
int x=3;                                 // local variable
fun(x);
fun(::x);
fun(x);
}

static void fun(double x)
{
static int s=0;             //  static local variable
int n=0;                    //  automatic (dynamic local) variable

cout << " n = " << n << “\t s =” << s
<<  “\t x = " << x << endl;
}

Output
n = 0   s =0       x = 3
n = 0   s =0       x = 25.5
n = 0   s =0       x = 3

6. References

A reference serves as an alias (i.e. a nickname) for the variable or object with which it has been initialized. A reference is defined using an address-of operator (&) after the data type. References are typically used when a function is called, as an alternative to pointers, in order to be able to work on the actual variables that are used as arguments when calling the function, rather than with their values.

When a parameter of a function is defined to be a reference then that variable is said to be passed-by-reference, rather than by value. Since all operations on a reference are actually applied to the variable that it refers, the only way to assign a variable to a reference is during its definition, i.e. a reference is assigned a variable to which it refers during its definition. It is also possible to have a reference to a pointer as shown in the following section example.

 e.g:             double x=15.75, &rx= x;       //   rx is a reference to a double
                    _rx += 20;       _                           //   x becomes equal to 35.75

7. Pointers

A Pointer variable is used to hold an memory location address. Every pointer has an associated data type that specifies the data type of the variable, or, in general, object, to which the pointer can point. Since pointers are variables that are used to store addresses of other variables, they must be defined before being used. A pointer is declared as a pointer to a particular type using the dereference operator (*) between the data type and the name of the pointer. Using the address stored by the pointer, the variable stored in that address can be indirectly manipulated.

e.g.:     double *px, *py, x, z *pz ;           int  j, *pj, k ;

The memory storage allocated for a pointer is the size necessary to hold a memory address (usually an int). The address of a variable (i.e. the location in memory where it is stored) can be obtained using the address-of operator (&), e.g. &x gives the address (i.e. the memory location) where the variable (here x) is stored. A pointer can be assigned the value 0 or NULL to indicate that it points nowhere. However, it is not allowed to assign to a pointer an address that is used to store a different data type variable.

The address of a variable is stored in a pointer using the address-of operator, by an assignment such as: px =&x;. The value which is stored in an address pointed to by a pointer can be accessed using the dereference operator (*). The value of a pointer is the address that it points to. Dereferencing a pointer gives the value which is stored at the memory location stored as the value of the pointer.

 e.g.    double *px, x;
px = &x ;
*px = 25.5 ; // this is equivalent to assigning x=25.5

Pointers can be used in arithmetic, assignment, and comparison expressions. An integral value can be added to, or subtracted from, a pointer. According to the rules of pointer arithmetic, when adding (or subtracting) a value from a pointer, the new address memory that is assigned to the pointer is equal to the current memory address plus (or minus) the amount of memory required to store that particular data type whose address is stored by the pointer times the value that is added (or subtracted). A pointer can be incremented, decremented, or subtracted from another pointer. However, two pointers cannot be added, and, pointer multiplication and division are not allowed.

A special pointer that can be used to store any type of pointer is called a pointer-to-void, and can be defined using the keyword void as the data type. However, no actual manipulation of the contents of the address pointed to by a pointer to void can be performed directly. Since a pointer-to-void does not provide any data type information concerning the data stored in the memory at which it points, an explicit cast is required to specify the data type of the data stored there.

      double x=10.75, *px=&x;
void *vp = &x;

cout << “\n x = " << x ;
cout << “\n *px = " << *px ;
//  cout << “\n *vp = " << *vp ; //    <——-   Wrong!
cout << “\n *vp = " << *(static_cast <double*>(vp)) ; // ok

The following example demonstrates the use of both references and pointers:

/* Example on references and pointers*/
#include <iostream.h>
#include <stdlib.h>

int main(void)
{
int i=5,  &ri = i ;              //  integer and reference to an integer
double x=24.5, *px=&x, *&rpx=px; // double, pointer and a reference to pointer to a double

ri++;
*px += 100;
*rpx += 1000;

cout << “\n i = " << i << “\t ri = " << ri;
cout << “\n x = " << x << “\t *px = " << *px
<< “\t *rpx = " << *rpx  << endl;

return EXIT_SUCCESS;
}
 

Output
i = 6   ri = 6
x = 1124.5      *px = 1124.5    *rpx = 1124.5

8. Function Call by Value, by Reference and Using Pointers

Pointers and references provide ways to overcome the problems associated with the call-by-value. Often we need to change the values of variables within a function call and this is impossible using directly the call-by-value which passes only copies of the values of the provided parameters. These copies are lost upon exiting the function and only one value can be returned by the function. In addition, large user-defined objects are often passed as arguments to a function. In those cases, using call-by-value requires memory allocation and copying of the passing arguments to the corresponding parameters which can be too costly both in computational time and memory space. Finally, in some cases, we may need to return more than one value from a function.

Therefore, in many cases calling a function by-value does not help much. There are two alternative approaches, either using a call-by-reference, or sending with call-by-value the address of the objects that we want to pass as arguments and operate on them indirectly using pointers.

We can change variables in the function by passing them by reference in which case the local variables are aliases to the actual variable. To pass an argument by reference we need to declare this by using an address-of-operator (&) after the data type of the passing by reference argument. The function is then called by reference. When an argument is sent by reference, it means that an alias to the argument is used in the function to access the actual variable in the calling function. Then, all changes that take place are actually done on the variable that is used as an argument when the function was invoked. A reference serves as an alias for the object with which the function was called and is initialized once upon entering the function, i.e. a parameter that serves as a reference cannot refer to different variables or objects. In cases where we want to pass a large object, that we do not want to change, by-reference, in order to save the overhead from making a local copy to the function parameter, we can define the reference to be a const so as to prevent any accidental modifications of it.

An alternative way is to use**pointers** and pass the address of the variables which allows us to access the variables indirectly and change the values stored at those locations. This is indicated by an * operator after the data type of the argument that is passed as an address, since it is actually a pointer. Using addresses of variables as arguments to functions we can access indirectly and change the values of some variables of the calling function. In addition, memory needs to be allocated only for the pointer and not for the entire object, saving the time and space overhead of copying the arguments to the function parameters. In C++ an array is always passed by a pointer to its first element, i.e. it is never passed by value

The following example demonstrates the use of call by-value, by-reference and using pointers.

/* Example for call-by-value, call-by-reference and using pointers */
#include <iostream.h>
void fun(double x, double &y, double *z);

int main()
{
double x=11.1, y=22.2, z=33.3;
fun(x,y,&z);
cout << “\n x = " << x << “\t y = “
<< y << “\t z = " << z << endl;
}

void fun(double x, double &y, double *z)
{
x *= 2;
y *= 2;        // using call by reference
*z *= 2;       // using pointer to access the actual variable
cout << “\n x = " << x << “\t y = " << y << “\t z = " << *z << endl;
}

Output
x = 22.2        y = 44.4        z = 66.6
x = 11.1        y = 44.4        z = 66.6

9. Pointers to Functions

The name of a function is actually the address of the function in memory. A pointer to a function can be used as an argument to a function to allow us to selectively invoke one out of several different functions, depending on the name of the function we use as argument. To use a pointer to a function we need to declare it in the declaration and definition of the function, i.e. specify that the function accepts as an argument a pointer to another function. A pointer to function is defined using the function’s type, which consists of its return type and parameter list. For example, the following declaration declares that the function fun() has 3 arguments: an int, a pointer to a function that itself returns a double and has a float and an int as arguments, and a float.

double fun(int i, double (*f) (double, int), float);

A function name, in general, gives a pointer to that function, although an address-of-operator can also be used to get (explicitly) the same. A pointer to a function can also be initialized, or assigned a value. When calling the function to which the pointer points to, the pointer’s name, either by itself or dereferenced, can be used.

An example of a pointer to a function is presented below. The function compute() has 3 arguments: a pointer to a function, and 2 integers. The name of any function that returns a double and has two doubles as arguments can be provided in the function call of compute(). The provided function is then used inside the compute() function whenever f() is used.

/* Example of Pointers to functions */
#include <iostream.h>   // pointers to a functions
double adding(double x, double y);
double subtracting(double x, double y);
double compute(double (*f)(double,double), int i, int j);

int main()
{
int x=7, y=3;
cout << “\n compute(adding,x,y) = " << compute(adding,x,y) << endl;
cout << " compute(subtracting,x,y) = “
<<  compute(subtracting,x,y) << endl;
}

double  compute(double (*f)(double,double), int i, int j)
{
return f(0.5*i,j);            // The following is equally valid:  return (*f)(0.5*i,j);
}

double adding(double x, double y)
{
return x+y;
}

double subtracting(double x, double y)
{
return x-y;
}

Output
compute(adding,x,y) = 6.5
compute(subtracting,x,y) = 0.5

10. 1-D Arrays

An array is used to store a set of values, or objects, of the same (either built-in or user- defined) data type, in one entity. An individual element of the array, i.e. a member of this set, can be accessed using the array’s name and an index which should be a value, or an expression, of integral type. The individual objects of an array are accessed by their position in the array using indexing with the index beginning from 0. Therefore, the last element of an n-size array has index equal to n-1. An array is defined using a pair of square brackets as shown below:

<data_type> <array_name> [size];

The dimension of the array, at the array definition, must be a constant expression, i.e. to be known during compilation, except in the case in which all elements of the array are explicitly initialized at the definition. If less elements of an array are initialized, according to the provided size during definition, the remaining elements are initialized to zero, e.g.:

double x[5];                 // an array of 5 doubles is defined
int y[]={ 3 , 56,  4, 6 };        // an array of 4 int
float z[6] = { 0. };                // all 6 float members are set to 0.
int h[7]={ 13 , 26,  42 }; // an array of 4 int

When an array is defined, the appropriate amount of consecutive memory is allocated. Memory is also allocated for a constant pointer that is associated with the name of the array and which stores the beginning address of the memory allocated for the array. Therefore, the array name is itself a constant pointer that stores the address of the first element of the array, i.e. the address of memory where the first element is stored, e.g. x is equal to &x[0]. The name of an array is a constant pointer because it cannot be assigned a different address. Since the name of an array is a pointer, an individual element of an array can be alternatively accessed using pointer arithmetic, instead of using the index notation. In essence, the index notation mat[i] is equivalent to *(mat+i).

You should be particularly careful not to exceed the range of an array since the compiler does not make such checks. However, in most cases memory is wasted when using arrays since we often allocate much more memory that we will probably ever need. Dynamic memory allocation can be used to avoid this waste of memory by allocating dynamically during execution the exact required memory, e.g.:

  double x[5];
x[0]= 5;
x[3] = x[0]+23;

/* Example on references, pointers and arrays */

#include <iostream.h>
#include <stdio.h>

main()
{
double x=11.1 , *px;
double &rx = x;

cout << “\n x = " << x << endl;
cout << " rx = " << rx << endl;

rx = 33.3;
cout << “\n x = " << x << endl;
cout << " rx = " << rx << endl;

px = &x;

ios::sync_with_stdio();
printf( “\n px = %p \n” , px );
ios::sync_with_stdio();
cout << " *px = " << *px << endl;

*px = 44.4;
cout << “\n x = " << x << endl;
cout << " *px = " << *px << endl;

double mat[] = { 10 , 20 , 30};
px = mat ;
cout << “\n mat[0] = " << mat[0] << endl;
cout << “\n px = " << *px++ << endl;
cout << " px = " << *px << endl;
cout << “\n px[1] = *(px+1) = " << *(px+1) << endl;
cout << " mat[2] = *(mat+2) = " << *(mat+2) << endl;
}

Output
x = 11.1
rx = 11.1

x = 33.3
rx = 33.3

px = 7fffae20
*px = 33.3

x = 44.4
*px = 44.4

mat[0] = 10
px = 10
px = 20

px[1] = *(px+1) = 30
mat[2] = *(mat+2) = 30

11. Strings as Arrays of char

In C++ there are two string representations: the traditional way of an array of characters, and, the newer standard C++ string class. For now, we consider the former string representation.

The string is stored as an array of char with a special character at the end ’\0’, which is called the terminator character, since it is used to indicate the end of the string. Therefore, memory space for an extra character must be provided to be able to store the terminator character.

Several library functions that can be used to manipulate strings are provided by the Standard C-library. To use them, the cstring header file, which contains their declarations, must first be included. The most commonly used are the following:

strcpy(char str1[] , char str2[]): strcpy copies the contents of str2 (including the terminator character) into str1

 strcmp(char str1[] , char str2[]): strcmp compares the two strings alphabetically, returning zero if they are exactly the same, otherwise a nonzero value

 strlen(char str1[]): strlen counts the number of characters in the string (not including the terminator character)

Although an array of strings can be initialized using the string notation (i.e. a literal enclosed in double quotes), it is not possible to assign a string to an array of char after its definition. A C-standard library need to be used to make this copying, e.g.:

char s1[] = “MIT” , char s2[4] ;
strcpy (s2,“MIT”);
char str3[] = { ‘M‘ , ‘I‘ , ‘T‘ , ‘\0‘ };

The following example demonstrates how a string can be defined, initialized or assigned a literal string, how can be modified, etc.

/* Example for strings as arrays of char */
#include <iostream.h>
#include <cstring>

int main(void)
{
char str1[] = “test” ;
const char *str2 = “Test” ;
char str3[50] ;
 

cout << “str1 and str2 are " ;
strcmp(str1,str2) ? cout << “different” << endl : cout << “the same” << endl;

cout << “\n str1 = " << str1 << endl ;
cout << " str2 = " << str2 << endl ;

strcpy(str3,str1);
strcat(str3,str2);

cout << “\n str3 = " << str3 << endl ;
str3[0] = ‘T’;
cout << " str3 = " << str3 << “\t length = “
<< strlen(str3) << endl ;
return 1;
}
 

Output

str1 and str2 are different

str1 = test
str2 = Test

str3 = testTest
str3 = TestTest         length = 8

12. Arrays of Pointers

Since pointers are variables we can have arrays of pointers. Such arrays are often used to store the location in memory of a collection of data with the same type. Each element of an array of pointers is a pointer which can be used to point to a memory location.

   e.g.:     _double *pd[100]; _     //   pd is an array of 100 pointers to doubles
              _char *pc[20]; _           //   pc is an array of 100 pointers to char

13. 2-D and Higher Dimensions Arrays

Multidimensional arrays (of any dimension) can be defined using additional brackets, one for each dimension. A multidimensional array can be initialized similarly to a 1-D array, with the option to use nested curly braces to group the data along the different dimensions (e.g. rows).

double mat2[6][3];
double mat3[5][3][2];
double m[][] = { {3 , 6.2 , 0.5} , { 23.7 , 0.75 , 4.8 } };
double m[3][10] = { {4.5} , {13.7} };

Although it is natural to think a 2-D array having a rectangular 2-D form, the elements of arrays (of any dimension) in C++ are actually stored in a contiguous memory location. The following graph shows how a 2-D array is stored. The top graph shows the virtual representation of a 2-D array, while the bottom one shows how actually it is stored in memory:

Therefore, the following expressions are exactly equivalent to m[i][j]:

*(m[i]+j)
(*(m+i))[j]
*((*(m+i))+j)
*(&m[0][0]+WIDTH_SIZE*i+j)

/* Example for 2-D arrays */
#include <iostream.h>
#include <stdlib.h>
#include <iomanip.h>
#define ROW_SIZE 4
#define COL_SIZE 7

int main()
{
double m[ROW_SIZE][COL_SIZE] = { { 4.5 , 0.45 } ,
{ 13.7 , 67.3 , 17.7 } , { 2.6 } };
int i,j;

for(i=0;i<ROW_SIZE;i++)
{
cout << endl;
for(j=0;j<COL_SIZE;j++)
cout << "  " << setw(5) << m[i][j];
}
return EXIT_SUCCESS;
}
 
 
 

**Output
**
4.5     0.45   0      0      0      0      0
13.7   67.3   17.7  0      0      0      0
2.6      0      0      0      0      0      0
0      0      0      0      0      0      0

14. Return by Reference Functions

A function can either return nothing, in which case it is declared as void and a return statement is optional, or return a value. In the latter case the default is to return a value by-value, i.e. a copy of the value that is returned is passed to the function from where the terminating function was called. However, there are some cases that it is preferable to return a value by-reference, or using pointers. For example, it may be useful to return a reference to an object or variable so as to be able to manipulate it, or it may be more efficient to pass by reference, or using a pointer, a large user-defined object to avoid the overhead due to copying it.

When a function returns by-reference, i.e. returns a reference to a variable or object, the function call can be placed in the LHS of an assignment statement. However, a variable, or object, with local scope cannot be returned by-reference, since the memory allocated for it is released upon exiting the function.

The following example shows one such a case, in which a reference to a specific element of an array is returned.

/* Example on return by reference */
double & fun(int i, double *x);
void main(void);

void main(void)
{
double x[10]={0};
fun(3,x) = 57.6;
cout << “\n x[5] = " << x[5] << endl;
cout << “x[6] = " << x[6] << endl;
}

double & fun(int i, double *x)
{
return x[2*i];
}

**Output
**
x[5] = 0
x[6] = 57.6

15. Dynamic Memory Allocation

Memory can be obtained dynamically from the system, in particular from a pool of free memory named the free store (or heap), after the program has been compiled, i.e. during execution, using the new operator. This operator can be used to allocate sufficient memory for one or more variables of any data type (standard or user-defined), i.e. for a single variable, or object, or an array of variables, or objects.

Memory is allocated dynamically using the operator new followed by a type specifier and in the case of an array followed by the array size inside brackets. In the case of a single variable an initial value can also be provided within parentheses. The new expression returns the address of the beginning of the allocated memory and can be stored in a pointer in order to access that memory indirectly. If the dynamic memory allocation is not successful a NULL (i.e. a 0 value) is returned by the operator new. The following statements allocate memory for one float and an int, and the address of that memory location is returned and then assigned to the pointer pf and pi, respectively. Dynamically allocated memory, if not explicitly initialized, is uninitialized with random contents.

float *pf = new float; // allocate memory for a float
int *pi = new int(37); // allocate memory for an int and assign an initial value to it

Similarly, the following statement allocates contiguous memory for a size number of doubles and then the address of the beginning of that memory is returned and assigned to the pointer pd. The size does not have to be a constant, i.e. known at compilation, but can be specified during execution of the program according to the program demands. However, there is no way to initialize the members of a dynamically allocated array.

double *pd = new double[size];

In contrast, to allocate memory for an array of doubles the size must be known prior to compilation, i.e. the size should be a constant. In the following definition of an array the name of the array is a constant pointer, since it cannot point anywhere else, while in the previous example pd can be used to store any memory address where a double is stored.

double mat[50];

Multidimensional arrays can also be dynamically allocated using a new expression. However, only the left-most dimension can be specified at run-time. The other dimensions need to be defined at compilation time, i.e. to have a constant size, e.g.:

    _double (*pmat)[100] = new double [size][100];    _ // size does not need to be a constant

The allocation and release of memory for variables for which memory is statically allocated is done automatically by the compiler. Memory of local variables is automatically released upon exiting the function, unless they are defined as static, and that memory can be used for other purposes. However, dynamically allocated memory is not released upon exiting a function and care must be taken to avoid losing the address of that memory, resulting in a memory leak. Dynamic allocation and deallocation of memory is a programer’s responsibility. When memory that is allocated dynamically is not needed any more, it should be released using the delete operator as shown in the following example which is based on the previous one:

delete ps;
delete [] pd;

The brackets are required when the pointer points to consecutive memory of a dynamically allocated array, in order to release all memory that has been allocated earlier. Only memory that has been dynamically allocated (i.e. using the new operator) can be released using the delete operator.

Its a good practice to set the value of a dangling pointer, a pointer that refers to invalid memory, such as a pointer that was used to point to a released memory, to NULL (or 0). Then, we avoid the error of trying to read or write to an already released memory location. However, it is not wrong to apply the delete expression to a pointer that is set to 0, because a test is performed before actually applying the delete operator on the pointer. Therefore, there is no reason to check whether the pointer is set to 0. The delete operator should not be applied twice to the same memory location, e.g. by mistake when having two pointers store the same memory location, because it may lead to corruption of data that have already been stored after the first release of the memory.

If the available memory from the program’s free store is exhausted, than an exception is thrown, and as we’ll see later there are ways to rationally handle such exceptions.

16. The sizeof Operator

The sizeof operator gives the size in bytes of a variable, a built-in data type, or a user-defined data structure, or a class, it can be used to determine the number of bytes that are required to store a certain object.

  e.g:
int i, mat[10];   double d;      //  On an athena SGI or SUN workstation:
  sizeof(char);                         //   returns 1 (byte)
         _sizeof(int);  sizeof i;   _              //   returns 4 (bytes)
         _sizeof d ;  _                               //   returns 4
sizeof mat;                        //   returns 40

17. Data Structures

A data structure is very similar to a class and it is not often used in C++, since more features are provided by a class. A structure can be used to store as a single entity several different variables not necessarily of the same data type. Structures in C++ have some extra features from the structures in C, such as access restriction capabilities, member functions and operator overloading.

A data structure is defined using the keyword struct, followed by the name of the structure and then its body where it is defined. Then, to define an instance of the data structure we can use its name directly (the struct keyword is not required as in C). To access a member of a data structure the dot or the arrow operator are used depending on whether we have the actual data structure instance or a pointer to it.

Structures can be passed to a function as any other variable, i.e. by value, by reference, or, using pointers. Because data structures are large in size, pass by value is typically not preferred, in order to avoid the copy overhead.

/* Example on data structures */
struct point
{
double x;
double y;
};
typedef struct point Point;  // the typedef is not necessary in C++

int main()
{
Point p;          //  p is a data structure point
point *pp;               // pp is pointer to a  point data structure

p.x = 3.2;                // using the dot operator
pp = &p;
pp -> y = 7.5;            // using the arrow operator

cout << “\n x = " << pp->x << “\t y = " << (&p) -> y << endl;
struct point p2 = {4.7 , 9.2};    //   A data structure instance can be intialized using
pp = &p2;                             // comma separated values enclosed in curly braces
cout << " x = " << pp->x <<  " y = " << p2. y << endl;
}

**Output
**
x = 3.2         y = 7.5
x = 4.7         y = 9.2

Note: The typedef allow us to assign a name for a specific data type, built-in or user defined, and then, use it as a type specifier. In the above example, struct point p and Point p are exactly equivalent, since the following typedef has been defined: typedef struct point Point; The typedef keyword is followed by a data type and an identifier that we want to specify as alias to that data type.

18. Introduction to Classes and Objects

A class is a user defined specification that encapsulates in a single entity both data and functions that can operate on them. An object is an instance of a class and the class/object relation is similar to the built-in data type/variable relation.

A class is defined using the keyword class followed by the name of the class. A class typically has**data members, which contain the data that is stored using the class; member functions, which are functions that operate on these data; constructors, that are member functions with a name the same as the class name and are executed upon a creation of an instance of the class in order to make the proper initialization; destructors, that are used when an instance of a class goes out of scope; and many other features such as operatoroverloading**, declarations of friend functions, etc.

The following simple example demonstrates the use of a Point class with some of the most basic features of a class.

/* Example on  classes and objects */
class Point
{
private:
double x,y;

public:
Point();
Point(double x, double y);
void print();
};

Point::Point()
{
cout << " In   Point() default constructor " << endl ;
x = 0.0 ;
y = 0.0 ;
}

Point::Point(double xx, double yy)
{
cout << " In   Point(double,double)  constructor " << endl ;
x = xx ;
y = yy ;
}

void Point::print()
{
cout << " (x,y) = (” << x << “,” << y << “)”  ;
}
 

int main ( )
{

Point p1;
Point p2(17,45.75);

cout << “\n Point P1: " ;
p1.print();

cout << “\n Point P2: " ;
p2.print();

return EXIT_SUCCESS ;
}

**Output
**
In   Point() default constructor
In   Point(double,double)  constructor

Point P1:  (x,y) = (0,0)
Point P2:  (x,y) = (17,45.75)

These notes were prepared by Petros Komodromos.

Topics

  1. Classes and Objects
  2. Classes: member variables & member functions 
  3. Classes: constructors & destructor
  4. Constructor header initialization
  5. Copy constructors
  6. Member variables & functions protection: private, protected & public 
  7. Static class data and class functions
  8. Class scope
  9. Pointers to class members
  10. Operator overloading
  11. Friend functions
  12. Type conversions

1 . Classes and Objects

A class is a user-defined data type with which we can define not only data members (or data members), but also member functions to manipulate these data. It is essentially an aggregate of data elements and a set of operations to manipulate them.

The definition of a class consists of the class head (the keyword class and the class tag name, i.e. the class name) and the class body enclosed by braces { }; and terminated by a semicolon. The class body contains the member variables, and the definitions or/and declarations of the member functions. The access levels of the class members can be  specified in the class body by placing declarations in certain parts of the class body.

The definition of a class should be provided in every source code files that uses the class. A class definition is allowable to appear many times in a program as long as it is identical in each case. Since the definition should be exactly the same, its preferable to have a class definition in a header files that is included whenever necessary, in order to avoid inconsistencies due to different definitions of the same class.

A class declaration is the class header followed by a semicolon. It can be  used to inform the compiler that a certain class is defined somewhere in the program.

_class MyComplex;           _ //                 Class declaration

_class MyComplex   _ // class head            Class definition
_{                       _ // class body
public:
double real; //  member variables
double imaginary;

MyComplex()   // default constructor definition
{
real = 0.0 ;
imaginary = 0.0;
}

MyComplex(double r, double i)   // inline, since its definition is provided
{
real=r;
imaginary=i;
}

~MyComplex() //  Destructor definition
{
…….
}

double get_real(void); //  Member function prototype
void print(void);   //  Member function prototype
void set_real(double) //  Member function definition
{
……………
}
};

_double MyComplex:: get_real(void)   _ // Externally defined member function
{
……….
}

void MyComplex::print(void)
{
……….
}

Definition of an Object

An object is defined using the class’ name, in the same way a built-in data type is used to define a variable of that data type. The keyword class can optionally be used before the name of the class. Memory, sufficient to store the data members of an object, is allocated as soon as an object is defined.

 e.g.:

MyComplex x;
double d;
MyComplex y(3,2.5);
class MyComplex t,r;

Access of an Object

A publicly declared data member, or a member function, of an object can be accessed using the dot operator (.)

   x.real = 12;
y.imaginary = 2.5;

To access a member data or function of an object using a pointer to that object, the arrow operator (->) can be used instead. Alternatively, the pointer can be dereferenced and then the dot operator can be applied on the dereferenced pointer.

MyComplex *px ;
px = &x ;
px ->real =24.5;
(*px).real =24.5;

2. Classes: Data Members and Member Functions

The class body typically contains the class data members and the member functions. It may also contain constructors, a destructor, friend function declarations, operator overloadings, etc. Data members are the variables in which the state of each instance (i.e. object) of a class is stored, while member functions are used to specify the behavior of any instance of the class.

Data members (or member variables) of a class are usually defined in the private part of the class definition in order to restrict access to them. Data members can be of any build-in, or user-defined, data type. A class cannot have data members of its own type, although it may have pointers or references to such data type objects.

A member function is a class specific function which is declared, or defined, in the body of the class definition, and it is always associated with a certain object, i.e. a specific instance of the class, the one that has been used to call the function. A member function can be invoked using one of the class-member selector, the dot operator (.) for objects (i.e. instances of that class), or the arrow operator (->) for pointers to objects.

classObject.memberFunctionName(arguments) ;
pointerToClassObject  ->   memberFunctionName(arguments) ;

e.g., from the previous example:

MyComplex x, *px ;
double y ;
y = x.get_real() ;
px = &x;
px -> get_real() ;
(*px).get_imaginary();

Whenever a member function is called, a special parameter, named this, is implicitly used. Actually, whenever a (non-static) member function is invoked a pointer to that class type that points to the object that is used to invoke the function, is implicitly passed as an argument (behind the scenes) to the member function. The member function has an additional parameter, a pointer to that class data type, named this. The parameter “this” is a pointer to the object which was used to invoke the function. Therefore, this special parameter can be used as any other pointer to access explicitly a member variable, or function, of the object, e.g.:

_this -> member_variable    _       or      this -> member_function(….)

Thus, the actual object with which the member function is invoked can be obtained by dereferencing this special pointer, i.e. by using *this. This special pointer can be used to resolve name conflicts, e.g. when an argument has the same name as the data members of the class. Pointer this can also be used to return the object with which the member function has been invoked. Finally, this can be used to check whether the object that was used to invoke a member function is the same with an argument passed to the member function by comparing this with the address of the object passed as parameter, e.g. when copying one object to another.

Data members and member functions of a class can be accessed from inside a member function that has been invoked with a certain object of that class without the need to use the dot or the arrow operator. The object pointed by this is the one with which the member function is invoked.

A member function can be overloaded, as any other regular function. The compiler uses the signature of the alternative member functions and the data type of the passed arguments to select the proper function to invoke. The constructors of a class are typically overloaded considering all possible arguments that can be used during the definition of an object of the class.

An externally defined member functions can be defined outside the class, as long as its declaration has been provided in the class body. The function definition consists of the header and the body of the definition. The header is similar to the function declaration (prototype) with the only difference the specification that defines to which class the member function belongs. This is indicated by providing the class name followed by the class scope resolution operator after the return data type, i.e. before the name, of the member function.

returnDataType    className :: memberFunctionName(arguments)
{
………
}

A member function that is defined within the body of the class definition (i.e. not externally), is automatically considered to be an inline function. Functions defined outside the class body, that are small and frequently called, can be declared as inline to save the overhead due to the function call. The declaration of a function to be inline can be done either in the function declaration inside the class body, in the header of the class definition, or both. However, the definitions of externally defined functions declared as inline should be placed into a header file that can be included in every source code file that invokes that function.

Typically the member data of a class are defined in its private part which provides restrictions on the accessibility of them, by allowing only to the members of this class to access it, supporting information hiding. Most member functions are defined in the public part of the class, providing a public interface to the private part.

A class may also have another class as one of its member, and the latter class is called a nested class. In general, a nested class and its enclosing class follow the usual access privileges, i.e. they do not have access to the private members of each other.

Finally, a class can be defined within a function body, i.e. having a local scope. However, the members of a local class cannot be defined outside the class definition, and static members are not allowable since they require a definition outside the body of the class definition which is impossible.

3. Classes: Constructors & Destructor

Constructors and destructors are special member functions that are automatically, i.e. implicitly, invoked when an object is created, i.e. when defined, or destroyed, i.e. going out of scope. It is allowable to define within curly braces values to which the member data of an object should be initialized as long as the members are public, e.g.: Point p = { 3 ,1.5}. However, with the exception of specific applications that need to initialize huge number of data members with constant values, it is preferable to use constructors to explicitly define the desired initializations while retaining the data hiding and encapsulation of C++.

A constructor is automatically called whenever a new class object is created allowing the explicit initialization of the member data upon the creation of the object. The name of a constructor is the same as the name of the class. A constructor should not have a return type specified, not even void, although it does not return anything. A constructor is used for initialization, assignment of certain values, that may be implicitly passed to it as arguments, type conversion, and dynamic memory management. You may have many constructors as long as they have different signatures, i.e. with different arguments so that the compiler can distinguish among them which one to call. It is preferable to provide a default constructor, rather than let the compiler to implicitly define and use one. This is necessary when having pointers as data members, since the constructor that is implicitly employed by the compiler probably cannot do the correct dynamic memory allocation and copying.

The default constructor is a constructor that does not necessarily requires arguments when it is invoked. It is called automatically whenever a new object is created without providing arguments at the definition. You can also use a constructor with parameters as the default constructor by assigning default values to its parameters, which allows its invocation without using any arguments. However, if constructors, but not a default one, are defined, it is not allowable to define an object without specifying the required arguments so as to invoke the one of the existing (and non-default) constructors. Therefore, it is preferable to always define a default constructor if any other constructor is defined.

/* Example on constructors */

class MyComplex
{
public:
double real;
double imaginary;

MyComplex()    // default constructor
{
real = 0.0 ;
imaginary = 0.0;
}

MyComplex(double real, double imaginary)
{
this->real = real;
MyComplex::imaginary = imaginary;
}

MyComplex(const MyComplex &c) // copy constructor
{
real = c.real ;
imaginary = c.imaginary ;
}
};

int main()
{
MyComplex x;
cout << “\n x = " << x.real << " + “
<< x.imaginary << " i " << endl ;

x.real = 15.5;
x.imaginary = 2.5;
cout << " x = " << x.real << " + “
<< x.imaginary << " i " << endl ;

double r=3.3;
double i=7.5;
MyComplex y(r,i);
cout << " y = " << y.real << " + “
<< y.imaginary << " i " << endl ;

MyComplex z(x);
cout << " z = " << z.real << " + “
<< z.imaginary << " i " << endl ;
}

_Output
_ x = 0 + 0 i
x = 15.5 + 2.5 i
y = 3.3 + 7.5 i
z = 15.5 + 2.5 i
 

A constructor can be invoked using any of the following two forms to define an object:

MyComplex c(7.3, 0.65);
MyComplex c = MyComplex(7.3, 0.65);

Any constructor, as well as the destructor, can also be defined as inline to avoid the overhead of the function call, when it is defined outside the class. A constructor cannot be defined using the const keyword to consider the object pointed by the pointer this as constant, because the const property of an object is set after the constructor returns and the object is completely initialized.

Typically, the constructors are defined in the public section of a class definition. However, in some cases a constructor may be declared as a private member to prevent the definition of an object of that class using specific data type parameters as arguments (or no arguments for the default), or to forbid, in general, the use of objects of that class.

A constructor with a single parameter can serve as a conversion function, and the compiler can implicitly invoke such a constructor to convert a data type variable to the constructor’s class. To avoid the implicit use of a constructor as conversion function, we can declare an explicit conversion rule.

An array of objects can be defined and initialized using the following form. With this statement an array of 3 objects is defined. Each of them is initialized using the provided values and the corresponding constructor, i.e. the MyComplex(double real, double imaginary) constructor.

MyComplex mat[]= { MyComplex(3,5), MyComplex(7,1), MyComplex(2,4) };

Another special member function in C++ is the destructor, which has the name of the class preceded by a tilde (~). The destructor is used for cleanup that may be required whenever an object goes out of scope and before, the memory allocated for it, is released, or, when the delete operator is used to free memory dynamically allocated for an object. The destructor is used to free resources allocated by the constructor, such as release dynamically allocated memory, close files, etc. However, many classes do not need a destructor, because no resources need to be deallocated, and no special actions need to be performed when an object is “destroyed”.

The destructor is automatically called whenever an object is destroyed, either because of going out of scope, or because its dynamically allocated memory is released using the delete operator. Memory dynamically allocated for an object of a class can be reallocated (i.e. release) using the delete operator which invokes the corresponding destructor. Release of dynamically allocated memory, typically allocated earlier by a constructor, is done in the destructor using the operator delete (or delete[]) in order to avoid memory leaks. To release memory dynamically allocated for an array of objects (built-in or user defined) the brackets are required to ensure that the entire memory is released and all necessary calls to the class destructor have been made. The destructor of an object can be explicitly invoked without necessarily releasing dynamically allocated memory by calling the destructor using the pointer to the object, the arrow operator and the destructor name, i.e. the class name following a tilde.

It is illegal to specify a return type, including void, for the destructor of a class, as well as to specify any parameters. Therefore, there can be only one destructor per class.

When a function is called passing an object by value, a temporary object with a copy of the object is created using the copy constructor and allocating temporarily memory to store the object parameter. Similarly, when an object of a class is returned by value from a function, the copy constructor is implicitly invoked to allocate the necessary memory and initialize the object’s data, member according to the object returned by the function.

4. Constructor Header Initialization

An alternative way to initialize the data members of an object is using header initialization, which is a comma separated list of data members with the desired initial values in the constructor’s definition. It is also known as member initialization list. The header initialization is achieved by using a colon after the header of a constructor followed by a comma separated list of the data members to be initialized and the value to which each of them is to be initialized inside parentheses. Typically, the parameters that are passed as arguments to the constructor are used to provide values for the data members. Using a member initialization list is considered to be an initialization, while initializing the members inside the constructor’s body is considered to be an assignment.

The header initialization is preferred, in cases of user defined data members considering performance, relative to assignment, since the latter involves extra calls to constructors. The use of header initialization is necessary when a constant member data must be initialized, since a const data type is not allowable to appear on the LHS of an assignment, i.e. it is illegal to initialize a const in a constructor’s body. In addition, a reference data member can also be initialized only using a member initialization list, since it cannot appear of the LHS of an assignment.

The following example demonstrates the use of a constructor header initialization.

class MyComplex
{
private:
double real;
double imaginary;

public:
MyComplex(double re=0, double im=0) : real(re), imaginary(im) {  }
void print(void);
};
void MyComplex::print(void)
{
cout << real << " + " << imaginary << " i " ;
}

int main()
{
MyComplex x, y(7, 2.1);
cout << “\n x = " ;     x.print() ;
cout << “\n y = " ;     y.print() ;
}

_**Output
**
_ x = 0 + 0 i
y = 7 + 2.1 i

5. Copy Constructors

A copy constructor is a constructor with one parameter, an object of the same class of which the constructor belongs passed by reference. It is used whenever an object is explicitly initialized with another object of the same class as argument. It is also used whenever an object is passed as an argument to a function, or, when an object of the class is returned from a function by value. Finally, the copy constructor can also be used when an object is assigned using the assignment operator another object, when the assignment operator is not explicitly overloaded, or when the assignment operator is used to initialize an object at its definition.

If a copy constructor is not provided a default member wise initialization takes place, which in some cases may not be the proper action, e.g. when having pointers as data members, to take.

_MyComplex(const MyComplex &c)              _ // copy constructor
{
real = c.real ;
imaginary = c.imaginary ;
}

6. Member Variables and Functions Protection: Private, Protected, and Public

You can specify different access privileges to specific member data and functions by selectively defining them in the private, protected, or public parts of the class definition. These sections, i.e.the private, protected, and public parts, are specified using the corresponding **access specifiers ** keywords private, protected and public.

The member variables and functions declared, or defined, in the public part of a class are accessible by everywhere within the program without any limitation. Usually the member data of a class are defined in the private (or protected) part and member functions to access them are defined in the public part of the class.

The member variables and functions declared, or defined, in theprivate part of the class definition can be accessed only by member functions defined in the same class and by friend functions of the class. Member functions declared, or defined, in the private part of the class definition, i.e. private member functions, can be invoked only by member functions of the class, or by friend functions to the class, similarly as the data members.

Finally, the member variables and functions in the protected part are accessible only by member functions defined in the member class, or in subclasses of that class, and any friend functions of the class.

Member functions of a class have access to variables and functions defined in any part, private, protected or public, of the class. Typically all data of a class, i.e. its member variables, are defined in its private or protected parts to restrict access to them which provides information hiding. The member functions, which represent the behavior of the class that should be accessible to the user of the class, are typically defined in its public section providing a public interface of the class.

There can be any number of labeled with the access specifiers, i.e. public, protected and private, sections. The access level that is specified remains the same until a new access specifier is encountered. The default access level is private, in case no access specifier is specified.

An object that is passed by reference to a member function of a class, using another object to invoke the function, can be protected against modification by declaring the corresponding parameter as const, e.g.:

                    MyComplex(const MyComplex &c);

The object that is used to invoke the member function, i.e. the object pointed by this, can be protected by declaring is as const. This is specified after the parameter list and before the body of the member function in the definition of a function, e.g.:
                   double get_real(void) const { …………  }
If the function is externally defined, it must also be specified as const after the parameter list and before the semicolon in the function declaration, e.g.:
                   double get_real(void) const;
An object declared as const is considered constant after its initialization, i.e. by a constructor, is finished and ends up when its deletion, i.e. using a destructor, starts. Therefore, constructors and destructors, which are never defined as const member functions, can be invoked by a constant object. In contrast, a non-const member function cannot be invoked by a const object.

Modifying the last example by putting the member variables in the private part, we no longer have access to them from outside of the class. Therefore, we must provide functions that can read their values and functions and modify their values. With these member functions which are defined in the public part of the class definition we have indirect access to the private member data.

/* Example on member variables & functions protection  */
class MyComplex
{
private:                                                  // private part
double real;
double imaginary;

public:                                                    // public part
MyComplex()        // default constructor
{
real = 0.0 ;
imaginary = 0.0;
}

MyComplex(double r, double i) : real(r), imaginary(i)       // header intialization
{
}

MyComplex(const MyComplex &c)            // copy constructor
{
real = c.real ;
imaginary = c.imaginary ;
}

~MyComplex()
{
//    cout <<  “\nAn object has been detroyed” << endl;
}

double get_real(void) const ;
double get_imaginary(void) const;
void **set_real(**double);
void set_imaginary(double);
};
 

// Member functions defined outside the body of the class definition
double MyComplex::get_real(void) const
{
return real;
}

double MyComplex::get_imaginary(void) const
{
return imaginary;
}

void MyComplex::set_real(double real)
{
this -> real = real ;
}

void MyComplex::set_imaginary(double im)
{
imaginary = im;
}

int main()
{
MyComplex x;
cout << “\n x = " << x.get_real()
<< " + " << x.get_imaginary()
<< " i " << endl ;

double r=3.3;
double i=7.5;

MyComplex y(r,i);
cout << " x = " << y.get_real()
<< " + " << y.get_imaginary()
<< " i " << endl ;
return EXIT_SUCCESS;
}

_**Output
**
_ x = 0 + 0 i
 x = 3.3 + 7.5 i

7. Static Class Data and Class Functions

If a data member of a class is defined using the keyword static before its data type, then memory is allocated for only one such element for the entire class, irrespectively of the number of instances (i.e. objects) of that class. The lifetime (i.e. the extent) of this static data is the entire program and there is only one such a variable shared by all objects of the class. A static class data is typically used to store information common to all objects of a class and to avoid unnecessary duplication of information.

Memory space is allocated for each static class variable only once even if there are no objects of that class. Not only member data i.e. variables, but also member functions can be defined as static. The latter are used to manipulate the former. A function is declared as static in the class body, i.e. at its declaration, and not at its definition.

A static class member, data or function, can be accessed using an object and the dot, operator, or a pointer to an object and the arrow operator. In addition, it can be accessed using the class name followed by the class scope resolution operator (::).

Because the pointer this is not associated with function calls to a static member function, it is a compile time error to attempt to access directly non-static members of the class from a static function.

The access levels and constraints of a static class member, data or function, are the same as those of non-static members. The only exception is when a static variable is initialized. Then, the access level is relaxed to allow the initialization, as shown in the following example.

A static class member is defined and initialized outside the class definition, as any other non-member global variable, i.e. outside of any function. The definition of a static member should appear only once in a program and, therefore, it should not be placed in a header file.

Someone could alternatively use a regular global variable to store information that refers to the entire class and not to individual objects. However, the use of static class members should be preferred since it provides all advantages of object-oriented programming, namely information hiding, data encapsulation, physical and direct correspondence and association of the specific information with the class, etc.

Because there is only one instance (one copy) of a static member data of a class, a static member data can be of the same type as the class itself.

The following example shows how a static class variable and function are defined and used.

_/*  Example on static class data and functions */
_class Employee
{
private:
char *first_name ;
char *last_name ;
double salary ;
int social_security ;
static int employeesNumber; // static class data declaration

public:
Employee(char *first=“None”,char *last=“None”, double sal=0.0, int soc=0)
{
first_name = new char[strlen(first)+1] ;
last_name = new char[strlen(last)+1] ;
strcpy(first_name,first) ;
strcpy(last_name,last) ;
salary = sal ;
social_security = soc ;
employeesNumber++;
}
……
static void printEmployeesNumber(void);
// static class function declaration
}

**void Employee::printEmployeesNumber(void)   ** // static class function definition
{
cout << “\n Number of employees: " <<  employeesNumber;
}
 

int  Employee::employeesNumber=0;
// static class data definition and intialization

int main ( )
{
Employee::printEmployeesNumber();

Employee a;
a.printEmployeesNumber();

char first_name[20]=“Bugs”;
char last_name[30]=“Bunny”;
double salary=100000 ;
int social_security=103038 ;
Employee b(first_name,last_name,salary,social_security);
b.****printEmployeesNumber();
}

8. Class Scope

The member data and functions of a class are considered to belong in the corresponding class scope. Inside the class scope, in general, there is no need to specify the class that a member belongs so as to access it. The body of a class definition, the code that follows the name of an externally defined member function up to the end of the body of its definition, and the code following the name of a static member at its definition up to the semicolon are all considered to be in class scope. However, outside class scope the access operators, i.e. the dot and arrow operators, and the class scope resolution operator must be used to specify the class scope in which the member belongs.

When an identifier, i.e. a variable or function name, is used in a class definition, first the declarations of the already declared members are considered, and if no member matches the name the declarations in the namespace scope (e.g. the global scope) located before the class definition are considered.  When an identifier is used in a member function of a class the resolution of the name starts with the local scope declarations, e.g. local variables and function parameters, then if nothing is found, it continues with declarations of all members of the class. Finally, if the name is still not resolved the declarations that appear in the namespace scope are also considered.

_/* Example on class scope */
_class MyClass
{
public:
int number;
};

int number = 33;

void main()
{
MyClass n;
n.number=22;
int number = 11;

cout << “\n number = " << number ;
cout << “\n n.number = " << n.number ;
cout << “\n ::number = " << ::number<< endl;
}

_**Output
**
_ number = 11
n.number = 22
::number = 33

9. Pointers to Class Members

Member data can also be accessed using pointers to specific member data. To define a pointer to a member data of a class the name of the class followed by the class scope resolution operator must be used between the data type of the variable to which the pointers may point and the dereference operator (*). Then, the pointer can be assigned the address of a specific member data of the class using the address-of operator (&) followed by the class name, the class scope resolution operator and the specific member data name. Having defined a pointer to a specific data member of a class, the pointer can be dereferenced and used with any instance ,i.e. object, of the class, as shown in the following example. Therefore, a specific object should be used when using a pointer to a data member.

Similarly a pointer to a member function can be defined. Again it is necessary to provide the class type whose member is the function, in addition to the return type and the number and type of the parameters of the function.

Note that pointers to static member data and functions should be defined as regular pointers to variables and functions, i.e. without specifying the class. No association with a specific object when accessing a member data needs to be resolved, and no this pointer is associated with static member functions calls.

_/* Example on the use of pointers to class objects */
_class MyComplex {
public:
double real, imaginary;
void print()  {  cout << real << " + " << imaginary << “i “;   }
};

void main()
{
MyComplex x, y, *py=&y ;
**double MyComplex:: *pd; ** // pointer to a double data member
void (MyComplex::*pf)()=0; // pointer to a member function

pd = &MyComplex::real;
x.*pd = 1.1;
y.*pd = -22.4;
pd = &MyComplex::imaginary;
x.*pd = 0.3;
y.*pd = 44.5;
cout << “\n x = " << x.real << " + " << x.*pd << " i " ;
cout << “\n y = " << y.real << " + " << y.*pd << " i " << endl;

pf = &MyComplex::print;
cout << “\n\n x = " ;
(x.*pf)();
cout << “\n y = " ;
((*py).*pf)();
cout << “\n y = " ;
(py->*pf)()****;
}

_Output:
_ x = 1.1 + 0.3 i
y = -22.4 + 44.5 i

x = 1.1 + 0.3i
y = -22.4 + 44.5i
y = -22.4 + 44.5i

10. Operator Overloading

C++ allows us to define new definitions for operators to be used with user-defined data types, i.e. objects. This is feature is called operator overloading and allows us to give to normal operators additional meaning when they are applied to user defined data types.

All operators can be overloaded except the following ones in double quotes: “.”, “.*”, “::”, “?:”, and “sizeof”. The subscript [ ], function call ( ) and arrow access ->, operators can be overloaded only as member functions. An operator overloading function needs to be either a member function of a class, or have a class object as parameter, except when the overloaded operator is new, delete, or delete[].

To overload an operator we need to define a member function with the keyword operator followed by the operator that is overloaded, instead of a name for the member function. This declaration syntax informs the compiler that this member function should be called whenever the particular operator is encountered next to an object of this class as operand. A member function that is overloading an operator can also be overloaded as a function, having the same operator overloading function in several forms, as long as each of them has a unique signature, i.e. differs in its parameters from all others. The compiler distinguishes among overloaded operators by looking at the operator and the data types of its operands. The precedence and associativity of operators is retained when overloaded. It is not possible to define additional operators for the built-in data types. Also it is not possible to change the arity of an operator, e.g. use a unary operator as a binary and vice versa, unless its one of the four operators that have both a unary and a binary form, (+), (-), (*), and (&).

Member functions that overload operators require one less argument than the number of the operands used on the operator, since the one operand is the object whose member function is invoked, i.e. *this.

The following example demonstrates the definition and use of an overloaded operator. A unary operator (++) and a binary operator (+) are defined and whenever either of these is encountered together with an object in an expression, the corresponding member function is invoked.

/* Example on operator overloading */

class MyComplex
{
…….
void operator ++(void);                           // member function declarations
Complex operator +(const MyComplex &c);
};

void MyComplex::operator ++(void)
{
++real;
}
// member function definitions

Complex MyComplex::operator +(const MyComplex & c)
{
MyComplex sum;
sum.real = real + c.real;
sum.imaginary = imaginary + c.imaginary;
return sum;
}

main()
{
MyComplex x,y(5,2.4);
MyComplex z = **++**x + y;
}

Although the input and output operators are usually overloaded as friend functions, an alternative way is to define it as a non-friend operator overloading function and provide proper get and set member functions that can be called from inside the overloaded operator functions.

ostream& operator << (ostream &o, const MyComplex &c)
{
o << c.get_real() << " + " << c.get_imaginary() << " i " ;
return o;
}

int main()
{
MyComplex x;
….
cout << " x = " << x <<  endl;
}

An alternative way to access an operator overloading member function is to use its actual name which consists of the keyword operator followed by the specific operator that is overloaded. For example the member function that overloaded the operators ++ and +, in the previous example can also be accessed as follows.

int main()
{
MyComplex x,y(5 , 2.4);
MyComplex z ;
x.operator++( );
z = x.operator+(y);
cout << “\n x = " << x <<  " y = " << y <<  " z = " << z;
}

If no assignment operator is overloaded, a one-by-one member copy is performed by default using a compiler-provided assignment operator that is implicitly invoked. However, there are some cases in which such a “shallow” copy is not our intention, e.g. when there are pointer data members pointing to dynamically allocated memory. In those cases, an assignment operator can be used to make a “deep” copy, i.e. instead of copying pointer values, resulting in pointer data members of two objects to point to the same memory location, memory is dynamically allocated and the contents in the memory pointed by the source-object pointer is copied at the memory location pointed by the corresponding pointer of the other object (the target one).

When an initialization of an object is done at its definition using an object of the same class, even if there is an assignment operator overloading available, the copy constructor is used, instead, to initialize the object.

Postfix and prefix versions of increment (++) and decrement (–) operators can be overloaded. The methods that overload the postfix operators have an additional integer parameter that is used to distinguish them from the prefix versions, i.e. the postfix form is defined as binary operator with an auxiliary extra operand of type int. The prefix version can be invoked using ++x, or x.operator++(), in the operator or method form respectively. The postfix version, operator++(int), can be invoked using x++, or x.operator++(0), (any number can be used as parameter).

The memory management operators new, new[], delete, ordelete[], can also be overloaded to achieve specific memory management requirements. The overloaded new operator should return a type pointer to void and have its first parameter of type size_t, (size_t is a typedef defined in the header file cstddef).

e.g:. void * operator new (size_t s) { ……… }

The delete operator should return void and have a parameter of type void* which points to the memory that is to be released.

e.g.: void operator delete (void * p) { ……… }

In both cases other parameters of any type are optional. If the new and delete operators are overloaded, they are automatically invoked every time the operators are used, instead of the provided standard ones. The global new and delete operators can still be selectively called by using the global scope resolution operator,

e.g MyComplex *pc = ::new MyComplex and ::delete pc.

Similarly the array versions new[] and delete [] can be overloaded and used.

11. Friend Functions

A friend function is not a member function of a class, but a function that is granted special access privileges to all member data and functions of a class. This is achieved by declaring the function in the class body using the keyword friend which gives unlimited access to that function, even to the private part of the class. A friend declaration may appear in any section of the class definition without any effect in which, private, protected, or public, part it appears.

A friend function can be a member function of another class, or even all the member functions of another class. One case where friend functions are useful is when a function needs to have access to two or more unrelated classes. In addition, friend functions allow more flexible operator overloadings, since the object of the class is passed as an argument and the function its not an object’s member function.

For example if we overload the + operator as a member function of MyComplex class then we can add two objects of this class c1+c2, and an object of this class and a number, e.g. c1+4.5, assuming for the latter case that a convert constructor is available to be used to convert the number to a MyComplex object. An overloaded operator of a class is considered and may be invoked only if an instance of the class (i.e. an object) appears to the left of the operator. However, the addition 4.5+c1 is not valid because operator+ is a member function of the class of c1. Using a friend function to overload the operator+ both c1+4.5 and 4.5+c1 are valid because in both cases the convertion constructor of MyComplex is invoked to convert it, if necessary, to a MyComplex object. However, in the latter case we need to provide a way to make the conversion from a double to an object of our class.

Input and output overloaded operators are typically defined to be friend functions in order to have access to the data members of the class.

The following example shows a use of two friend functions, of which the one is overloading the input operator:

_/* Example on friend functions */
_class MyComplex
{
private:
double real, imaginary;
……..
friend void printMyComplex(const MyComplex &c);
friend istream& operator » (istream &i, MyComplex &c);
};

void printMyComplex(const MyComplex &c)
// a friend function has unlimited access
{
cout << c.real << " + " << c.imaginary << " i " ;
}

istream& operator » (istream &i, MyComplex &c)
{
cout << “\n Please give the real part: " ;
i » c.real ;                                                // access to private members
cout << “\n   and the imaginary part: " ;
i » c.imaginary ;                                               // access to private members
return i;
}

int main()
{
MyComplex x;
cin »x;
printMyComplex(x) ;
}

A function may be declared as friend for more than one classes. Also a member function of a class may be declared as friend for another class. In addition, a whole class, i.e. all its member functions, may be declared as friend for another class, which grants access to all member functions of the friend class to all member data and functions, even those defined in the private part, of the other class.

class Point
{
……..
friend Design::draw(); // the member function draw() of
                                           // the  Design class is declared friend
friend Spline;    // the Spline class, i.e. all its member
};                                 //  functions,  is declared friend

There are cases in which overloading an operator needs to be done using a friend rather than a member function. For example, if the multiplication operator (*) is overloaded using a member function, in particular using a set of overloaded member functions with the same name to allow the multiplication with any possible data type, i.e. an int, double, a MyComplex, etc. Then, although the multiplication of a MyComplex number with a different data type value is allowable when the latter is on the right of the operator, the case of having the Mycomplex on the right is not allowable. For that case a friend function can be used as shown in the next example.

class MyComplex
{
………
friend MyComplex operator+(double d, const MyComplex &c);
};

MyComplex operator+(double d, const MyComplex &c)
{
MyComplex sum;
sum.real = d + c.real;
sum.imaginary = c.imaginary;
return sum;
}

int main()
{
MyComplex x(3,1.5), y;
y = 17.5 + x;
}

12. Type Conversions

Implicit type conversions are performed when different built-in data types occurred in mixed expressions. The rules that govern these conversions are specified by the language as we have seen in an earlier recitation. C++ allows the definition of conversion rules for user-defined data types that can be used when conversions from one data type to another are required.

Member functions can be defined and used to achieve certain conversions when objects are used as operands to operators (either built-in or overloaded), or as arguments to functions. These functions are implicitly invoked by the compiler whenever necessary to handle conversions.

Even when no explicit conversions are provided the compiler tries to use constructors that are related with the conversion that has to be performed, e.g. by assigning to a user-defined data type object of the constructor’s class a different data type. By default a constructor with one parameter may be used by the compiler for a type conversion, as a conversion function. The following example shows how a constructor is employed to make a type conversion from a built-in data type to a user defined, i.e. a class type.

_/* Example on the use of a convert constructor */
_class LengthFT
{
public:
int feet;
double inches;

LengthFT(double d)                 // convert constructor
{
cout << “\n Using the convert constructor” ;
feet = (d*100/2.54)/12;
inches =  d*100/2.54 - 12*feet ;
}
};

int main()
{
LengthFT x;
double distance = 0.65;

x = (LengthFT)1.45; //  Type casting (conversion) using the convert constructor
cout << “\n x = " << x.feet << " - " << setprecision(3) << x.inches << “’” << endl;

x = distance; //  Implicit type conversion using the convert constructor
cout << “\n Distance (m) = " << distance << endl ;
cout << " x = " << x.feet << " - " << x.inches << “’\n” << flush;

return EXIT_SUCCESS;
}

_**Output
**
_ Using the convert constructor
x = 4 - 9.09”

Using the convert constructor
Distance (m) = 0.65
x = 2 - 1.59”

In addition, operator overloading functions may also be used to handle different data types. An explicit conversion rule can be defined using a conversion function. A conversion function can be used to define how a conversion between a user-defined data type and another data type, user-defined or built-in, should be performed. A type conversion function is defined using the keyword operator followed by the data type name. Although a (converted) value is returned the function declaration and definition should not specify a return data type. Also a parameter list should not be defined. A conversion function can be invoked by an explicit cast, or when a mixed expression is encounter and conversions are necessary to be performed.

The following example shows a very simple case where a conversion function is defined in the class LengthFT to convert a LengthFT object to a double (in this case considering only its real part. The class LengthFT is used to represent a length in feet-inches form and whenever appears in a mixed expression we want to convert it to a double and express the length in meters.

_/* Example on conversion functions using explicit conversion*/
_class LengthFT
{
public:
int feet;
double inches;
LengthFT(int f=0, double i=0)
{
feet = f;
inches = i;
}
operator double();
};

LengthFT::operator double()
{
return 0.0254*(feet*12+inches);
}

int main()
{
LengthFT x(6,3);
double distance=4.2;

cout << “\n x = " << x.feet << " - " << x.inches << “’” << endl;
distance += x; // the member function LengthFT::operator double() is called
cout << " Distance [m] = " << distance << endl ;
cout << " x [m] = " << x << endl ;
**     //  LengthFT::operator double() is called**

return EXIT_SUCCESS;
}

_**Output
**
_ x = 6 - 3”
Distance [m] = 6.105
x [m] = 1.905

These notes were prepared by Petros Komodromos.

Topics

  1. Inheritance: public, protected and private derivation
  2. Multiple inheritance
  3. Inheritance: constructors and destructors 
  4. Inheritance: redefining member functions 
  5. Virtual functions and polymorphism
  6. Abstract classes
  7. File streams

Appendix: Extra Material

  1. Namespaces 
  2. Assertions 
  3. C++ standard library string class
  4. Other topics

1. Inheritance: Public, Protected and Private Derivation

Inheritance is the ability to create a new class, called derived class, from an existing one, called base class. The derived class is also called subclass of the base class, which in turn is called superclass of the derived class. A derived class inherits all the data members and member functions of its superclasses, and, in general, it implements an is-a relationship. In contrast, a has-a relationship is implemented using a class object as a member of another class. New members can be defined in the derived class refining the definitions of its superclasses. Inheritance can be used to extend the capabilities of a base class. Classes can be derived by classes that are themselves derived and this process can be extended to an arbitrary number of levels of inheritance.

An object inherits data members and member functions from the superclass  of its class, and from all superclasses of that class. Member functions  of a derived class can only access members of its base class that are declared  in the public or protected part, i.e. private members cannot be accessed. The derived class methods invoked by a derived class object can access only the protected members that have been inherited from the base class of the particular object that invoked the member function (i.e. *this), or any other object of the derived class. The derived class member functions have also access to all private and protected, as well as public of course, members of its own class.

The derived member functions and data of a derived class object, can be accessed directly as if they were members of the derived class and not of the base class, as long as access is permitted. However, if the same name is used for both a member of the base class and a member of the derived class, the member of the derived class hides the corresponding member of the base class. By using the class scope resolution operator, the base class member, instead of the derived one, can be explicitly accessed. If the base class has a static member data then there will be only one instance of that regardless of how many subclasses have been derived from the base class and how many objects have been created.

To specify the base class of a derived class a colon is used after the name of the class, at its definition, followed by a specifier that defines the type of inheritance and the name of the superclass. The expression after the colon, which is used to specify the inheritance, is called class derivation list. To define a subclass using public derivation the keyword public should be used. In addition to public derivations, we can use protected and private class derivations. The following example shows a public derivation of a class.

class derivedClassName :  public  baseClassName { _    ……….._ };

The colon (:) specifies that the derivedClassName class is derived from the baseClassName class. The derived class inherits the member variables and functions of the class from which it is derived, as well as the member variables and functions of all superclasses of that class. 

The access to specific member variables and functions of the superclasses of a class are specified according to the location where they have been defined (i.e. in which section of the class definition) in those superclasses, and the way that the subclasses are derived, i.e. which of the keywords public, protected, and private has been used during the derivations. The access to members of a superclass and the “cost” of that access do not depend on the depth of the inheritance tree.

With a public derivation of a subclass all member variables and functions of the superclass retain their status in the derived subclass, i.e. a public member remains public allowing (unlimited) access to everyone.

Using a private derivation, all public and protected members of the superclass become private in the derived class, i.e. a public member of the superclass within the derived class can only be accessed by the members of the derived class.

Finally, with protected derivation all the public parts of the superclass become protected in the derived class. A protected member data, or function, can be accessed only by member functions of the class and certain subclasses of the class, or by friend functions.

Therefore, when deriving a subclass we can reduce the access privileges using private or protected derivation. The access specifier (public, protected, and private) defines the type of access of the members inherited by the derived class from its base class. The access is specified based on which part (public, protected, and private) the declaration of a member data and function is declared. The protected part is used to allow access to the members declared in there only to members of that class and any subclasses.

The derived class has no access to the private members of the base class unless it is declared to be a friend of the base class. Note that friendship is not inherited, i.e. a subclass of a derived class that has been declared a friend of its own base class, is not considered a friend of its base class superclass. Friendship must be explicitly granted from each class to all classes that should have access privileges to that class.

The access level to a member that is inherited from a superclass can be adjusted to the access it has in the superclass, instead of following the rules based on the keyword used in the subclass derivation, by declaring the member in the public, or protected, access section accordingly using the name of the superclass followed by the class scope resolution operator and the name of the member. However, any attempt to change the access status of a base class member is invalid.

/* Example on public, protected and private derivation */

class Point { private: _  double x;_ protected: _  double y;_ public: _  double z;_ _  Point(double,double,double);_ _ };_

Point::Point(double xx=0, double yy=0, double zz=0) { _   x = xx ;    y = yy ;    z=zz;_ }

// Alternative derivations _  class Voxel : public Point                         _   // all members retain their status                                                                    // in the derived class _//class Voxel : protected Point                _ // public members become protected                                                               // in the derived class //class Voxel : private Point        _ // all members become private in the derived class { private: _  int color;

public: _  Point::z; _                    // e.g. declared in the public section to                                    // adjust the access of z to public _  Voxel(double x, double y, double z, int col);_ _  void print();_ };

Voxel::Voxel() { _   color=0;_ }

Voxel::Voxel(double x=0, double y=0, double z=0, int col) : Point(x,y,z) { _   color = col;_ }

void Voxel::print() _{                                 _ //  x is always not accessible since x is private _   cout << y ;            _ // Accessible but if the derivation is private it                                    // becomes private in the derived class _   cout <<  z ;               _ //  Accessible but it becomes whatever is                                      // the derivation in the derived class }

void main() { _                Voxel v(4,7,2,101110101);_ _                v.print();_ }

2. Multiple Inheritance

A class may inherit the member variables and functions from more than one superclasses, i.e. a derived class may have multiple base classes. Then, all superclasses are defined after the colon separated by commas and with the indication of the type of derivation the should be used, i.e. one of the access specifiers: public, protected, or private. There is no limit on the number of the base classes that a derived class inherits from. The base class constructors are invoked in the order that appear in the class derivation list, while the destructors are invoked in the reverse order.

/* Example: on multiple inheritance */

class People { _ public:_ _   char first_name[20];_ _   char last_name[20];_ _   int age;_ };

class Student : public People { _  public:_ _   int student_id;_ };

class Staff { _  private:_ _   int social_sec_num;_ };

class Faculty : public People, protected Staff { _ private:_ _   int num_papers;_ ….. };

If  a member function is hidden by another member function we  can explicitly specify which one we want to be used by putting the name  of the class and two colons in front of the name of the member function.

className::functionName(…)

3. Inheritance: Constructors and Destructors

Although a derived class inherits the member data and functions of its base class, the constructors of the base class are not inherited. Therefore, the derived class needs to provide its own constructors which are called after the base class and member object constructors are called. When a derived class object is created, first a base class constructor is automatically invoked, typically to initialize the member data that correspond to the base class, and then the derived class constructor is called to initialize the additional data members of the derived class. The constructors of the superclasses/subclasses chain are executed in a top-down order, i.e. a base class constructor, if executed, is executed prior to the derived class construction.

A new set of constructors is typically provided for a derived class to make the proper initialization and, if necessary, call the proper superclass constructor with certain arguments. The derived class constructor is used to initialize the members that have been added by the derived class, while the superclass constructor(s) should take care of the corresponding classes data members.

A specific constructor of a superclass can be invoked by providing after the header of the constructor definition a colon followed by the name of the superclass (i.e. the constructor name) and the desired arguments with which it is to be called. This is called**member initialization list** and it can be used to pass arguments to the constructor of the base class. The order of the comma separated member initialization list does not affect the order of construction invocation. The order in which the constructors are invoked is: first, the base class constructor is called and in case of multiple inheritance the order is according to the order that has been used in the class derivation list. Next the constructors of member objects are called in the order in which the members have been defined in the derived class definition. Finally, the derived class constructor is called. A derived class constructor can invoke a constructor only of its direct superclass.

If the derived class has constructors but the base class has not, then the proper derived class constructor is called every time a derived class object is defined.

In the opposite case, i.e. the derived class having no constructors while the base class has, the base class must have a default constructor which is automatically invoked whenever a derived class object is defined.

A derived class constructor needs to explicitly invoke one of the base class constructors, if the base class has constructors but not a default constructor, i.e. a derived class constructor must explicitly invoke one of the base class’ constructors in its header. Alternatively, a default constructor can be provided for the base class. Then, if no base class constructor is explicitly invoked, the default constructor is automatically invoked whenever an object of the derived class is defined.

If a class is used as a base class only to be able to define the subclasses and there is no intention to have objects of that class, the constructors of the base class can be defined as protected, which restricts their access to the derived class (constructors). A class that has no actual objects, instances of itself, is called an abstract base class.

The following example demonstrates how the constructors of the class and its superclass are invoked and how a specific constructor of a superclass can be explicitly called with certain arguments.

/* Example on constructors calling other constructors */

class Point { private: _    double x, y;_ public: _  Point();_ _  Point(double,double);_ _ };_

Point::Point() { _   x = 0 ;   y = 0 ;_ }

Point::Point(double xx, double yy) { _   x = xx ;   y = yy ;_ }

class Pixel : public Point { private: _  int color;_ public: _  Pixel();_ _  Pixel(double x, double y, int col);_ };

Pixel::Pixel() { _  color=0;_ }

Pixel::Pixel(double x, double y, int col) : Point(x,y) { _  color = col;_ }

void main ( ) { _  Point p(1.7,7.2);             _ //  The Point::Point(double xx=0, double yy=0)                                             // is called with (1.7,7.2)

Pixel px1; //  The default constructors, Point::Point()                                          // and then  Pixel::Pixel(), are called

Pixel px2(2.75, 8.23, 111000101);                        // The Point::Point(double xx, double yy) constructor and then                        // the Pixel::Pixel(double x, double y, int col) are called }

In the above example the expression: Point(x,y) after the parameters in the header of the Pixel constructor causes the invocation of a specific constructor of the class Point.

Similarly, the derived class, member object class and base class destructors are invoked as soon as the lifetime of an object (of the derived class) reaches its end. In contrast to the constructors that are invoked in a top-down order starting from the base class first, the destructors are called in the reverse order, invoking first the derived class destructor, and then, its member objects constructors, and, finally, the superclass constructor, etc. In addition, although constructors may not be virtual, destructors may be virtual allowing the invocation of the destructor of the class derived for the object pointed to by a pointer (of base class data type). The reverse order is used so as to ensure that the most recently allocated memory is the first to be released.

4. Inheritance: Redefining Member Functions

A member function of a superclass can be redefined (with the same name) in a subclass and, depending on which object invokes it, the corresponding one is invoked. When an object of the derived class is used to invoke a function, the search to find the member function starts from the derived class definition, which results in invocation of the derived class member function if it is available, unless a class scope resolution operator is used to explicitly specify whose class the member function should be called. A specific member function of a superclass can be called by using the name of the superclass followed by the class scope resolution operator before the name of the class to be invoked, since the member of the derived class hides the inherited member. The invocation, i.e. the explicit call of a member function of a superclass, can also be achieved from inside the body of the member function of the subclass. i.e. if the member function of a superclass is hidden by a member function of the subclass, the class scope resolution operator can be used to explicitly specify whose class the member function should be invoked.

The member functions of any superclass and subclass can be overloaded as any other set of functions in a certain scope. Note that the member functions of a base class and the member functions of its derived class do not all together make up a set of overloaded member functions, because the former are considered to be in the base class scope, while the latter in the derived class scope.

The previous example has been extended, as shown below, to show how to explicitly call a superclass member function.

/* Example on redefining and invoking member functions */

class Point { private: _    double x, y;_ public: _  Point();_ _  Point(double,double);_ _  void print();_ };

Point::Point() { _  x = 0 ;   y = 0 ;_ }

Point::Point(double xx, double yy) { _  x = xx ;   y = yy ;_ }

void Point::print() { _  cout << " (x,y) = (" << x << “,” << y << “)  "  ;_ }

class Pixel : public Point { private: _  int color;_ public: _  Pixel();_ _  Pixel(double x, double y, int col);_ _  void print();_ };

Pixel::Pixel() { _  color=0;_ }

Pixel::Pixel(double x, double y, int col) : Point(x,y) { _  color = col;_ }

void Pixel::print() { _  this -> Point::print();         _           // the member function print()                                                       // of class Point is called _  cout << " color = " << color;_ }

int main ( ) { _  Pixel px1;_ _  cout << “\n Pixel px1:” ;_ _  px1**.print();  **                                _   // the member function print()                                                        // of class Pixel is called _  cout << endl;_

Pixel px2(2.75, 8.23, 111000101); _  cout << " Pixel px2:” ;_ _  px2.**Point::print();    **                   _     // the member function print()                                                         // of class Point is called

cout << endl; _  return EXIT_SUCCESS;_ }

Output

Pixel px1: (x,y) = (0,0)   color = 0 _ Pixel px2: (x,y) = (2.75,8.23)_

5. Virtual Functions and Polymorphism

Virtual functions allow dynamic (or late) binding, which means that the selection of which function to call is done during execution, rather than during compilation. This provides the flexibility to perform the same kind of action on different types of objects as long as they are all instances of classes of or derived from, a superclass whoce function  is defined as virtual. The selection of which of the virtual functions to invoke is done at run-time. In contrast the resolution of a non-virtual function is done by the compiler during compilation and the process is called static binding. A non-virtual member function is invoked using implicitly the pointer this which is of a certain data type. If a pointer to a base class object is used to invoke the function, even if the pointer stores the address of a derived object, the base class member function will be called.

Polymorphism is the ability of having a member function, or an operator overloading function, that behave differently on different types of data. It is the ability of dynamic (i.e. run-time) binding of a pointer of a base class to a method, based on what is stored in the memory pointed to by the pointer and not the data type of the pointer. This is possible by the ability of a pointer to a base class to point not only to base class objects, but also to any object of any of its subclasses. In contrast, a pointer to a derived class object cannot point to a base class object unless explicit casting is used. The decision of which function is invoked is delayed until the run-time, instead of being made during compilation as in non-virtual functions. However, polymorphism which is a major characteristic of object-oriented programming can be used only when pointers (or references) are used and not actual objects.

A function is defined as virtual by preceding the return data type at the member function declaration of the base class with the keyword virtual, e.g. virtual void print(void); Declaring a member function of a class as virtual, the corresponding member functions in all that class’s subclasses are automatically considered to be virtual. However, the keyword virtual, although optional, is typically used also in the derived classes at the corresponding virtual function declarations to clarify the nature of the function. The keyword virtual should be used only in the function declarations and not at external definitions of the defined functions. The keyword virtual indicates that the selection of which function to invoke should be delayed until run time and be based on the data type of the object that is pointed to by the pointer that invoked the member function.

A derived class does not need to redefine a member function that has been indicated as virtual in its base class. In that case it inherits the member function from the base class. A virtual function that is redefined in a derived class must have the same signature as the base class function. Otherwise it will simply hide the base class function and compile-, rather than run-, time binding will be used. It is wrong to provide in a derived class a member function with the same signature as the virtual function declared in the base class but with different return data type. The only exception is to have as return data type the address or reference of a derived class object instead of a base class object.

/* Simple example on virtual functions */

#include <iostream.h> #include <stdlib.h>

class MyBase { public:

void print()   {     cout << “\n Printing through the base class: MyBase” << endl;   }

virtual void print(int i)   {     cout << “\n Printing through the base class: MyBase:”   << " i = " << i << endl;   } };

class MyDerived : public MyBase { public:

void print()   {     cout << “\n Printing through the derived class: MyDerived” << endl;   }

virtual void print(int i)   {     cout << “\n Printing through the derived class: MyDerived:”   << " i = " << i << endl;   } };

int main(void) {   MyBase b;   MyDerived d;

b.print();   d.print();

MyBase *pb=&d;   MyDerived *pd=&d;

pb -> print();   pd -> print();

pb -> print(1);   pd -> print(2);

return EXIT_SUCCESS; }

Output

Printing through the base class: MyBase  Printing through the derived class: MyDerived

Printing through the base class: MyBase  Printing through the derived class: MyDerived

Printing through the derived class: MyDerived: i = 1  Printing through the derived class: MyDerived: i = 2

A pointer to an object of a certain class can point not only to any object of that class but also to any object of that class’ subclasses. Therefore, we may have an array of pointers to a base class which are used to point to objects of any of the base class’ subclasses. Having defined a virtual function, a pointer to the base class can be used to point to an object of the base or any of its subclasses object, and the decision which member function to invoke depends on the current contents of the pointer, i.e. to which class object it points to, rather than its (the pointer’s) data type. In contrast a pointer to a derived class cannot point to an object of the base class unless it is an explicit cast is used. If a pointer to a base class stores the address of a derived class object and both base and derived class have a non-virtual function, or data, with the same name, the base class member function, or data, is selected during static binding.

If the virtual member function is never expected to be used with an object of the base class, it can be specified as pure virtual function by providing instead of the body of the function an assignment to 0, e.g. virtual void print(void) = 0; Then, if the function is called run-time error will occur, since it is not intended to be invoked, but it is only provided to allow derived functions to define the actual functions, which will be called based on the contents of the memory pointed by the pointer that invokes the virtual function.

The class scope resolution operator may be used to disable the virtual mechanism and explicitly invoke the member function of a certain class. Such explicit invocation is resolved at compile, rather than at run, time. Declaring a pure virtual function results in no consideration of it during the virtual mechanism resolution, i.e. it cannot be invoked through the virtual mechanism, and specifies the class to be an abstract base class. However, a definition for a pure virtual function may be provided (i.e. the pure virtual function may be defined) and the function may be statically invoked (i.e during compile time).

If a pointer to a class is used to point to objects of subclasses of that class, the destructor of the class must be declared as virtual, to ensure that proper deallocations of memory occur when an object is deleted.

Although a constructor may not be declared as virtual, a destructor can be a virtual function. The reason for having a destructor declared as virtual is that if the dynamically allocated memory for a derived class is assigned to a pointer to a base class object, then the base class destructor will be called instead of the derived class destructor resulting in a memory leak. Therefore, it is good to declare as virtual the destructor of the base class if any virtual functions are used and especially when dynamic memory allocation is used. When the destructor is virtual then the order of destructor invocations starts with the derived class and continues with the destructors of its superclasses. Also a virtual function may not be static, since a virtual member function needs to be associated with a particular object of a class rather than the class as a whole.

Another use of the keyword virtual is to declare a base class as virtual. This is useful when a derived class inherits from multiple (direct) superclasses that happen to have already inherited from a common superclass higher in the class hierarchy. Then, the derived class inherits multiple times from the same (the common class to its superclasses) base class. To avoid this we can use the keyword virtual at the derivation of its superclasses, as shown below:

class MyBase { …….}; class MySuper1: public virtual MyBase { …….}; class MySuper2: public virtual MyBase { …….}; class MyDerived: public MySuper1, public MySuper2 { …….};

The use of virtual base class (as above), which is called virtual inheritance, allows the inheritance and sharing of a single base class sub-object instead of having unecessary multible copies of the base class whenever the base class occurs in the derivation hierarchy. Virtual inheritance avoids duplications of the base class sub-objects and ambiguities that rise with such duplicates. However, there is a performance and complexity impact when using virtual inheritance.

6. Abstract Classes

A class that is used as a general base class to derive other classes, without any instances of that class ever being created, is called an abstract class. A class can be made abstract by declaring one or more of its member functions of the class as a pure virtual function(s). This is achieved by setting to zero the declaration of the function. then, the member function will not be considered when a function of the same signature is called, rather one of the derived class functions will be called.

e.g.:  virtual void print(void) = 0;

An abstract class needs to have a derived class, i.e. it is invalid to define an object of an abstract class. A set of functions are typically defined as**pure virtual functions** in the base class to provide a common public interface for any current or future derived classes. A member function of an abstract base class is not ever intended to be called, as no instance of the abstract base class is ever anticipated. An attempt to define an object of an abstract base class results in a compile-time error.  

7. File Streams

Although the easiest way to read from a file or to write to a file is using redirection, the direct way to open a file and read from or write to it is using the file-handling library of C++. The declarations of the library are in the header file fstream.h, which must be included in a program so as to be able to use input and output streams to a file.

To create an input stream, i.e. open an input file for reading, the following definition should be used, which instantiates an ifstream object:

ifstream inputStreamName (“fileName”);

Then, the inputStreamName can be used instead of the input operator cin to read from a file named fileName, instead from the standard input.

Similarly to write to a file, i.e open an output file for writing to it, the following definition should be used, which instantiates an ofstream object:

ofstream outputStreamName (“fileName”);

Then, the outputStreamName can be used instead of the output operator cout to write to a file named fileName instead to the standard output.

After using the ifstream, or ofstream, object to read from, or write to, a file, the file should be closed when access to it is no longer needed. A file can be closed using the member function close(), i.e.     inputStreamName.close(); or  outputStreamName.close();

EOF (end-of-file), which is a constant defined in the iostream.h header file can be used to read data until the end of file (EOF) is reached, by checking whether what was read is equal to EOF (machine dependent). EOF is entered in Unix workstations using <Control-d>.

You may optionally take a look to the following topics, which were not covered in the course.

_A1. Namespaces_

When several different libraries are used in a program there may be conflicts among identical global names of variables and functions. Namespace definitions can be used to reduce this problem by enclosing the source code (declarations and definitions) in certain namespaces. Each namespace has an associated namespace scope and contains the namespace members, which can be variables, class definitions, functions, etc., i.e. anything that could have been declared in the global scope. A namespace is defined using the keyword namespace followed by the name of the namespace and the declarations and definitions enclosed in curly braces. The definition of a namespace does not need to be contiguous, but it can be provided in several different points, even in different files.

To refer to a namespace member the qualified name notation indicating the namespace is required. The name of the namespace should be provided followed by the name of the member (variable or function) that is to be accessed. In addition, to be able to refer to a member of a namespace it is required to have earlier declared the namespace. Typically, the namespace declaration is provided in a header file which is included everywhere the namespace needs to be used. Since a member variable should be defined only once, the keyword extern should be used in the declaration of the member variables of a namespace. A member of the global namespace can be referred to by using the scope resolution operator (::) without a namespace preceding it. Therefore, it can be used to access global member that are hidden by local ones. Nested namespaces, i.e. defining a namespace within another namespace, are allowed. In that case more than one namespace names and scope resolution operators need to be used to specify the namespace scope.

Namespaces is a recent feature of C++ and not all compilers conform to the corresponding to namespaces C++ standard.

To avoid the need for typing of long names to specify the namespace scope there are two mechanisms that can be used the namespace aliases and the**using directives**. However, not all compilers support these mechanisms according to the C++ standard.

Using a namespace alias we can associate a simpler name to an existing (and often long) namespace that we need to use. In particular, we can declare an alias (e.g. MYLIB) for a namespace with a long name (e.g. GraphicsDrawingFunctions) using the following declaration:

namespace MYLIB = GraphicsDrawingFunctions;

Then, to invoke the member function draw of the namespace we can use MYLIB::draw(), instead of GraphicsDrawingFunctions::draw().

The using directive can be used to access members of a namespace without the need to explicitly refer to the specific namespace, i.e. it provides unqualified access to the namespace. A namespace can become visible with a declaration in which the keyword using is used followed by the name of the namespace and the name of a member of the namespace we want to access. If no specific member is given then all members of the namespace become visible, i.e. are considered in the scope in the scope in which the using declaration is used. The next example demonstrates the use of a namespace named NameSpaceTest2 and its declaration (in the file test2.h) and definition (in the file test2.C).

test2.h

namespace NameSpaceTest2 { _  extern int x;_ _  extern void print(double d);_ }

test2.C

#include <iostream.h>

namespace NameSpaceTest2 { _  int x = 22;_ }

namespace NameSpaceTest2 { _  void print(double d)_ _    {_ _      cout <<"\n printing through NStest2::print(double d) “_ _    << " d = " << d << endl;_ _    }_ }  

test1.C

#include <iostream.h> #include <stdlib.h> #include “test2.h”

int x = 11;

void print(int i) { _  cout <<”\n printing through ::print(int i):   i = " << i << endl;_ }

using NameSpaceTest2::print;

main() { _  int x = 77;_

cout << “\n x = " << x << endl; _  cout << " ::x = " << ::x << endl;_ _  cout << " NameSpaceTest2::x = " << **NameSpaceTest2::**x << endl;_

print(3); _  **NameSpaceTest2::**print(4);_

print(4.11); _  **NameSpaceTest2::**print(4.22);_

return EXIT_SUCCESS; }  

Output

x = 77 _ ::x = 11_ _ NameSpaceTest2::x = 11_

printing through ::print(int i):   i = 3 _ printing through NStest2::print(double d)  d = 4_

printing through NStest2::print(double d)  d = 4.11 _ printing through NStest2::print(double d)  d = 4.22_

A namespace without defining its name, called unnamed namespace, can be used to define members (functions, classes, and variables) only in a portion of a program without access from other files. An unnamed namespace is defined using the keyword namespace followed by curly braces where all definitions are located. Its members are visible only in that file (scope limited in that file) but have extent until the termination of the program. An unnamed namespace is equivalent to a static global member that is defined and used in one file but cannot be accessed from any other file although its extent lasts until the end of the program.

A special namespace named std has been used to declare and define all components of the C++ standard library. However, many compilers do not support this feature. All members of this namespace can become visible with the following statement:    using namespace std;

_A.2. Assertion_

Assertions are used in a program as conditions that must be true in order to ensure correctness of the program. They can be used as preconditions, postconditions, and invariants, to verify that a condition is true, e.g. in the entrance, exit, or anywhere within a function.

To use assertions the header file assert.h must be included and the preprocessor macro assert() can be used to check whether a condition is true. If the assertion fails the program terminates providing information about the error that occurred.

The following example demonstrates how assertions can be used to check whether a file has properly opened for reading. In this case, it was attempted to open a nonexistent file and the assert which checks whether the pointer to a file is not equal to null fails resulting in a program termination.

/* Example on the use of assertions */

#include <iostream.h> #include <fstream.h> #include <stdlib.h> #include <assert.h>

int main() {   char str1[]=“existing”;   system(“ls>existing”);   // Using system() an OS command can be executed   ifstream ifp1 (str1);   assert(ifp1);           // line 14   cout << “\n File " << str1 << " has been opened properly” << endl;

char str2[]=“nonexisting”;   ifstream ifp2 (str2);   assert(ifp2);           // line 19   cout << “\n File " << str2 << " has been opened properly” << endl;

return EXIT_SUCCESS; }

Output

assertions.C:19: failed assertion ‘ifp2’ Abort

A.3. C++ Standard Library String Class

A string class, that has several convenient and object-oriented capabilities, is provided by the C++ Standard library. In order to use it, the string header file needs to be included, the following example shows how an object of this class can be defined and used, and how it can be combined with the more traditional C Standard library string which is represented as an array of characters.

/* Example on the C++ standard library string class */

#include <iostream.h> #include <iomanip.h> #include <cstring> #include <string>

int main(void) {   string str1(“Testing”), str2;   string str3(str1);   char str4[] = “MIT”;   const char *str5 = “”;

cout << “\n str1: " << setw(20) << str1 << “\t size = " << str1.size();   str1.empty() ?  cout << “\t (empty)” << endl : cout << endl ;

cout << " str2: " << setw(30) << str2 << “\t size = " << str2.size();   str2.empty() ?  cout << “\t (empty)” << endl : cout << endl ;

cout << " str3: " << setw(20) << str3 << “\t size = " << str3.size();   str3.empty() ?  cout << “\t (empty)” << endl : cout << endl ;

cout << " str4: " << setw(20) << str4 << “\t size = " << strlen(str4);   strlen(str4) ?  cout << endl : cout << “\t (empty)” << endl ;

cout << " str5: " << setw(20) << str5 << “\t size = " << strlen(str5);   strlen(str5) ?  cout << endl : cout << “\t (empty)” << endl ;

if(str1==str3)     str2 = str1 + str4 ;   str2 += str3 ;   str2[10] = ’t’ ;

cout << " str2: " << setw(15) << str2 << “\t size = " << str2.size();   str2.empty() ?  cout << “\t (empty)” << endl : cout << endl ;

return 1; }

Output

str1: Testing                   size = 7  str2:                           size = 0        (empty)  str3: Testing                   size = 7  str4:                  MIT      size = 3  str5:                           size = 0        (empty)  str2: TestingMITtesting         size = 17< /EM >

A.4. Linkage Specifications: extern “C”

An existing compiled C function may be incorporated in a C++ program and used if a declaration of the function with extern “C” preceding its return data type is provided. e.g.: extern “C” double fun(int, double); 

Command line arguments

To be able to use command-line arguments, i.e. provide arguments while executing a program main must be declared as: main(int argc, char *argv[ ]){……..}, where argc is the number of command-line arguments and argv is an array of strings each of them corresponding to a command-line argument.

These notes were prepared by Petros Komodromos.

Topics

  1. Function templates 
  2. Class templates 
  3. Sorting and searching algorithms 
  4. Insertion sort
  5. Selection sort 
  6. Shellsort 
  7. Quicksort
  8. Linear search 
  9. Binary search 

1. Function Templates

The template mechanism of C++ allows the development of general functions and classes without the need to know the data type of the variables used during implementation. Templates allow the development of type-independent source code. Suppose that we want to find the maximum of a set of numbers that may be of int, float, or double data type, the algorithm is the same regardless of the particular data type. Using templates a single function, a function template, can be written that can selectively be instantiated and work considering a specific data type. Similarly, class templates can provide generic descriptions of classes without the need to specify the data types used.

A template line, called template parameter list, precedes a template function declaration or definition specifying the parameters that are to be used as data types in the function. One or more data types are parameterized, allowing the instantiation of the function with varying data types specified to the corresponding parameters. The template parameter list begins with the keyword template followed by comma separated parameters enclosed in <> brackets. Two types of parameters can be specified as template parameters: template type parameters, that consist of the keyword class, or typename, and an identifier; and template nontype parameters which are essentially ordinary parameter declarations that are used to represent a constant in the template definition.

The template parameter list is followed by a function template declaration or definition. The only difference of the function definition of a template function from an ordinary function is the presence and use of the template type parameters as data types. The template type parameters can be used in the same way as any other built-in or user-defined data type, in the template declaration or definition that follows the template line. Similarly, the template nontype parameters can be used as constant values. If there is a name conflict in the function template declaration or definition with a name used in the global scope, the latter is hidden. Although the name of a template parameter can be used in several function template declarations and definitions, it is not allowable to use the name of a template parameter more than once in the same template parameter list. The template parameter names used in template declarations and the actual definition of a function template may be different. To specify a function template as inline or extern, the corresponding keyword must be used after the template parameter list, i.e before the return type of the function.

A template function is a specification for an actual function that is created when the template is instantiated with a certain data type. During template instantiation, the parameters of a template are replaced by the actual data types, and the actual code for an individual function (with the data types defined) is created by the compiler. Any data type dependent errors are detected only during instantiation and not at the template definition. Prior to instantiation a function is not defined, since the function template is simply a specification on how function should be created during instantiation. A function template is instantiated either when it is  invoked, or when its address is taken to be assigned to a pointer to a function. Then, according to the arguments that are used for the function call, the data types that correspond to the template type parameters, and the values corresponding to the template nontype parameters are determined. This process is called template argument deduction and it is based on the examination of the function arguments. Note, that the return data type of the function template is not considered in the template argument deduction. In addition, the template arguments can be explicitly specified, instead of relying on the template argument deduction mechanism. Template arguments can be explicitly specified by a comma separated list of template arguments in a <> brackets between the name of the function template and its, enclosed in parentheses, argument list.

The following simple example shows how a template function can be used to determine the maximum element of a vector of numbers that can be of int or double data type.

/*  Simple example of using function templates */

template<typename MyType>
MyType findMax(MyType vect[], int n)
{
MyType maximum = vect[0];

for(int i=1; i<n ;i++)
if(vect[i]>maximum)
maximum = vect[i];

return maximum;
}

_template<class MyType, int SIZE>       _ // keywords class and typename are equivalent
inline MyType findMin(MyType (&vect)[SIZE])
{
MyType minimum = vect[0];

for(int i=1; i<SIZE ;i++)
if(vect[i]<minimum)
minimum = vect[i];

return minimum;
}

int main(void)
{
int nx = 10, x[] = {3, -78, 12, 52, 17, -53, 2, 49, -9, 43}, ny = 7;
double y[] = {39.2, -72.8, 5.2, 14.7, -15.3, 41.9, -92.3};

cout << “\n Maximum element of x = " << findMax(x,nx) << endl;
cout << " Maximum element of y = " << findMax(y,ny) << endl;

cout << “\n Minimum element of x = " << findMin(x) << endl;
cout << " Minimum element of y = " << findMin(y) << endl;

return EXIT_SUCCESS;
}

_Output
_

Maximum element of x = 52
Maximum element of y = 41.9

Minimum element of x = -78
Minimum element of y = -92.3

The definition must be visible at the point of instantiation, e.g. when a function template is called. In the above simple example the definition of the function template and the code in which it was instantiated appeared in the same file.

For larger programs the function template definitions are typically provided in a header file that is included in every file in which the function template is used, similarly as when using inline functions.

2. Class Templates

Class templates can be used to develop a generic class prototype (specification) that can be instantiated with different data types. This is very useful when the same kind of class is used with different data types for individual members of the class. Parameterized types are used as data types, as in the function templates, and then a class can be instantiated, i.e. constructed and used, by providing arguments for the parameters of the class template. A class template is a specification of how a class should be built (i.e. instantiated) given the data type or values of its parameters.

A class template is defined by a line which defines the parameters, using the keyword template followed by the parameters (template type and nontype parameters) enclosed in <> brackets known as template parameter list. Each template type parameter is preceded by the keyword class or typename, and it is replaced by the corresponding argument when the class is instantiated. The name of a template type parameter represents a built-in or user-defined data type that would be defined during the class instantiation. A template nontype parameter is like a an ordinary (function-parameter) declaration, and represents a constant in the class template definition, i.e. it should be possible to be determined at compilation time. A parameter can be specified only once in the template parameter list and should not have the same name with a member of the class. The name of a template parameter can be reused in other template declarations or definitions, and also can be different in declarations or definitions of the same class template. If the name of a template parameter is the same as a global variable then the latter is hidden by the parameter.

In addition, the class template parameters (both type and nontype) can have default arguments that are used, during class template instantiation, if arguments are not provided. Because the provided arguments are used starting from the far left parameter, default arguments should be provided for the rightmost parameters.

Then, the declaration or definition of the class follows, using the defined type parameters as data types. The definition of a class template as well as the definitions of externally defined member functions are similar to ordinary class and member function definitions with the only difference being, besides the template parameter list at their beginning, the use of the template parameters. To define externally defined member functions of a class template, the template parameter list must precede the definition to make the parameters available to the function. In addition, the template parameters should also be used in <> brackets list before the scope resolution operator to indicate the actual name of the specific class which is a certain instantiation of the class template. A member function of a class template is itself a function template which is instantiated only whenever the function is invoked or its address is taken (and not when the class template is instantiated), using the corresponding data types used for the associated class object.

To create a particular class and define an object of that class the name of the template class is used followed by a comma-separated list of either data types that are used as arguments to the type parameters, i.e. specifying the data types to be used for the class creation, or arguments that are passed as values to the non-type parameters of the class template. This is process is called template substitution. Each different instantiation of a class template is considered a different class which is identified by the name of the class template followed a comma separated list of parameters enclosed in <> brackets that are used for the instantiation. In contrast to function templates, where some non-type parameters may be deduced from the way they are used, e.g. the size of an array, the template parameters must be either provided as arguments or have default values that can be used.

Sometimes ambiguity may arise during instantiation of a template class, e.g. due to already existing member functions using certain data types which come in conflict with generated member functions using the specified data type which may happen to be the same.

The following simple example demonstrates the definition and use of a class template.

myTemplate.h

?

template <class myTypeX, typename myTypeY>
class Point; template <typename myTypeX, class myTypeY>
class Point
{
private:
myTypeX x;
myTypeY y;
Point();

public:

Point(myTypeX x, myTypeY y)
{
this->x = x;
Point::y = y;
}

void print(void);
};

template <class myTypeX, class myTypeY>
void Point<myTypeX,myTypeY>::print(void)
{
cout << " (x,y) = (” << x << “,” << y << “)  "  ;
}

 

myTemplate.C

#include  <iostream.h>
#include  <stdlib.h>
#include  “myTemplate.h”

int main ( )
{
Point<int,double> p1(3,9.25);
cout << “\n p1 = “;
p1.print();

Point<double,int> p2(3.74,9);
cout << “\n p2 = “;
p2.print();

cout << endl;
return EXIT_SUCCESS;
}

3. Sorting and Searching Algorithms

Sorting and searching are fundamental operations in computation and information technology.

Because searching a sorted array is much more efficient than searching an unsorted one, sorting is used to facilitate searching. In many programs the running time is determined by the time required for sorting. Therefore, it is important to implement a fast sorting algorithm.

For all algorithms, presented below, assume that the N data (elements) are stored in an array named A.

4. Insertion Sort

This is an elementary algorithm with nested loops which cause an O(N2) time.

Each element, starting from the element A[0], is considered one at a time and it is positioned in the proper ordered among those who have already been considered.

Example:
 

7 4 8 2 1 3
7          
4 7        
4 7 8      
2 4 7 8    
1 2 4 7 8  
1 2 3 4 7 8

5 . Selection Sort

This is another elementary sorting algorithm with an O(N2) time.

The element with the smallest value in the array is identified and placed in the first position. Then, the element with the smallest value among the remaining N-1 elements is selected and placed in the first position of the N-1 subarray. Continuing this procedure the array is sorted as shown by the following example.

Example:
 

7 4 8 2 1 3
1          
1 2        
1 2 3      
1 2 3 4    
1 2 3 4 7  
1 2 3 4 7 8

 

6. Shellsort

Shellsort is an extension of insertion sort which can increase its efficiency. It allows exchanges of non adjacent elements. Although in some rare cases an O(N^2) time is required, the required time is usually O(N3/2).

First, a gap size is selected by dividing the number of the elements by 2. Then, the corresponding every “gap-size” elements are sorted. Next, the “gap-size” is divided by 2 and repeat the sorting of the elements at every “gap-size”. Finally, the “gap-size” becomes equal to 1 and the entire array is sorted.

Example:
 

8 7 6 5 4 3 2 1
4 7 6 5 8 3 2 1
4 3 6 5 8 7 2 1
4 3 2 5 8 7 6 1
4 3 2 1 8 7 6 5
2 3 4 1 8 7 6 5
2 1 4 3 8 7 6 5
2 1 4 3 6 7 8 5
2 1 4 3 6 5 8 7
1 2 4 3 6 5 8 7
1 2 3 4 6 5 8 7
1 2 3 4 5 6 8 7
1 2 3 4 5 6 7 8

 

7. Quicksort

Quicksort is a very fast sorting algorithm which has O(N.lgN) average times. Its worst case performance is O(N2), but can be avoided with certain techniques. It is a “divide and conquer” algorithm for sorting. It partitions the data into two parts and then sort them independently.
It uses in place sorting and a simple recursive structure.

An element of the array, called pivot, is picked.

Then, one index start from each side of the array moving towards each other. The higher index is decreased until an element with a value smaller than the value of the pivot is found. Similarly the lower index is increased until an element with a value higher than the value of the pivot is found. If the two indices are different, the two corresponding elements are out of order and need to be exchanged with each other.

The above step is repeated from the point where the process was interrupted.
If the two indices are the same, then if the value of the selected element is less than that of the pivot the selected element and the pivot are exchanged. Otherwise, no exchange should occur.

At this point the algorithm has grouped the elements of the array into two subarrays. One has all elements smaller than or equal to the pivot and the other all the elements larger or equal to the pivot.

Then, the algorithm is applied recursively to each subarray until the number of the elements of a subarray is equal to 0 or 1.
 

Example:
 
 
 

4 5 2 8 3 6 1 7
4 5 2 8 3 6 1 7
4 5 2 8 3 6 1 7
4 1 2 8 3 6 5 7
4 1 2 8 3 6 5 7
4 1 2 3 8 6 5 7
4 1 2 3 8 6 5 7
3 1 2 4 8 6 5 7

 
 

3 1 2   4    8  5  7
 3 1 2    4    8 6  7
 3 1 2    4    8 7
 2 1 3   4   7  6

 

2 1   3   4   7 6 5   8
2 1   3   4   7 6 5   8
1 2   3   4   7 6 5   8
1 2   3   4   5 6 7   8

 

2 1   3   4   5 6   7   8
2 1   3   4   5 6   7   8
1 2   3   4   5 6   7   8

 
 

1 2 3 4 5 6 7 8

Note

pivot   -   left index  -  right index -both indices

 Black cells are used to separate the elements of the array, i.e. they are not elements of the array.
The above array is of size N=8.

Linear search is the simplest searching algorithm and can be applied to unsorted data. The search proceeds in sequence searching for a certain element. In the worst case, which is the case of an unsuccessful search, it takes N iterations. On average it takes N/2 iterations.

Example:

Search for element with key value equal to 33
 

2 5 8 14 27 33 49 51 67 95
2                  
  5                
    8              
      14            
        27          
          33        

 

Binary search can be used on sorted data. It splits the data in half, determines in which half the desired element must be located (if it exists in the data set). Then, recursively repeats the cutting in half of the elements and keeps selecting the part in which he desired data element may be.
This algorithm requires O(lgN) computational time.

Example:

Search for element with key value equal to 33
 

2 5 8 14 27 33 49 51 67 95
          33 49 51 67 95
          33 49      
          33        

These notes were prepared by Petros Komodromos.

Topics

  1. Introduction to Java®
  2. Compiling and running a Java® application and a Java® applet
  3. Data types 
  4. Variables, declarations, initializations, assignments
  5. Operators, precedence, associativity, type conversions, and mixed expressions
  6. Control structures
  7. Comments 
  8. Arrays 
  9. Classes and Objects
  10. Constructors 
  11. Initializers 
  12. Member data and functions 
  13. Function overloading

1. Introduction to Java®

Java® is an Object-Oriented Programming (OOP) language, which is similar to C++ but with certain characteristics that allow the simple development of portable programs with graphics and graphical user interfaces. The provided classes allow very simple and efficient development of complicated programs that can be executed in any machine irrespectively of the operating system, as long as it supports Java®. You can read more about “what is Java®” in the relevant paragraph of the Java® Tutorial, provided by Sun.

The portability of Java® programs is based on the Java® Virtual Machine (Java® VM) and the intermediate compilation into bytecode. The bytecode can, then, be interpreted by the Java® VM, which translates the bytecode instructions into machine instructions that your computer can understand and execute.

The Java® platform consists essentially by the Java® VM, which takes care of the compilation and interpretation issues (e.g. portability), and by the Java® API, which provides a large collection of software components that can be directly used by a Java® programmer. You can read more about it in the on-line paper “The Java® Platform”, by Douglas Kramer.

The Application Programming Interface (API)provides several classes that can be used to efficiently write programs with graphics content and graphical user interfaces. The latter can be achieved with C++ only by combining it with graphic libraries such as Open Inventor or OpenGL, and with toolkit libraries such as TCL and TK.

In addition, Java® facilitates the development of programs that deal with networking, security issues, databases, 3D graphics, and many other issues that a typical high level language, such as C++,  does not provide.

The following are good references to learn Java®:

  • The Java® Tutorial. Mary Campione and Kathy Walrath. 2nd edition.
  • Core Java®. Gary Cornell and Cay Horstmann. 2nd edition.
  • The Java® programming language. Ken Arnold and James Gosling. 2nd edition.
  • Java®: How to program. Deitel & Deitel. 2nd edition.
  • Java_®_ 2 Platform API, v 1.3

If you are interested to read more about Java® you can find more information in the following on-line paper by James Gosling and Henry McGilton:

You can find more information about Java® in the Sun’s Java® page.

2. Compiling/Running a Java® Application/Applet

Java® is a pure Object Oriented Programming (OOP) language. Any Java® program is built from classes. C++ can be used as an OOP language but not necessarily since someone can use it to develop non-object oriented programs.

The simplest, probably, Java® program is a Java® application which prints a message. A Java® application is a Java® program that can be executed independently without the need of any browser.

The following java program is written in a file named welcome.java":

welcome.java

class Welcome
{
public static void main(String args[])
{
System.out.println(“Welcome to 1.124”);
}
}

Because global functions are not allowable in Java® we need to provide the main() function in a class. In addition, we need to make it public so as to be accessible, and static so as to be a class function rather than being a function associated with a certain instance of the class. The main function must have a single parameter of type String[] and must return nothing (i.e. being void). Any class can have its own main function.

To compile a java program the java compiler javac is used as follows:

javac welcome.java

This command generates the bytecode for the classes that are defined in the Java® program. In this case, it generates the file Welcome.class which contains the bytecode for the class Welcome. The name of the file with the bytecode is constructed from the name of the class plus the extension class. The bytecode is instructions for the _Java_® Virtual Machine. These instructions are the same for any type of machine or operating system. To run the program, the Java® interpreter needs to be used to interpret the Java® bytecode into instructions of the specific machine on which the program is running.

The command to run a Java® program is as follows, using the java interpreter:

java Welcome

Then, the class Welcome is loaded and interpreted printing out the following:

Welcome to 1.124

Some programming languages, such as Basic, also use an interpreter, which makes the development and the debugging of the programs faster and more efficient. However, most high level languages use a compiler and not an interpreter, while Java® uses both. The bytecode files can, in general, run in  any machine with any operating system, as long as the proper interpreter is available. However, the execution of such interpreted programs is relatively slow.

Many other programming languages, such as C/C++, are using a compiler, which translates the source code files into machine instructions. Although the execution of compiled programs is much faster, the executable cannot run on a machine with a different architecture, since it recognizes a different set of instructions.

Java® combines both a compiler and an interpreter. The compiler (javac) compiles the Java® source code files into bytecode, and the interpreter (java) is used every time the program is executed to translate the bytecode (i.e. the Virtual Machine instructions) to the specific machine instructions and execute them. This way Java® programs can run on any type of computer and under any operating system assuming that the Java® interpreter is available and can be used on that machine. However, Java® programs are, in general, slower than compiled programs (e.g. C++ executable programs) since interpretation takes place before execution.

Java® Development Kit (JDK)also provides an appletviewer to check and run applets, a debugger named jdb to debug Java® programs, and several other tools that help in the development and documentation of Java® programs.

_Java_® applications are stand alone Java® programs that can be executed without the need of a browser, while the _Java_® applets run within a Java® compatible browser. The execution of any Java® application begins with the main method of the corresponding class, i.e.. the class with which the Java® interpreter was invoked. The above example is a Java® application, while the following is a simple Java® applet.

A _Java_® applet is based on a set of conventions and functionalities that are inherited that allows it to be executed in an appletviewer or any Java® enabled browser. The source code for the applet is provided below, followed by the html file that needs to be used so as to load the class from a Java® enabled browser, or using the appletviewer provided with the Java® Development Kit (JDK).

An applet inherits (extending) the Applet class provided by the java.applet package of the Java® Core API. Here, the AWT Applet is used, mostly for historical reasons. Today, the Swing JApplet is preffered in most cases. In the following example the function paint(), which is inherited, is overridden by the new definition. This function is used to draw the applet in the browser, or the appletviewer.

myApplet.java:

import java.applet.Applet;
import java.awt.Graphics;

public class myApplet  extends Applet
{
public void paint(Graphics g)
{
g.drawString(“Welcome to 1.124”, 50, 35);
}
}
 

The above program is compiled using the javac compiler, i.e. executing the command:

        javac myapplet.java

The resulting file with the bytecode is the myApplet.class which takes its name from the name of the class. This file can be loaded and interpreted in any Java® enabled browser, or the appletviewer, using an html file.

The html code is used to specify at least the location and the dimensions of the applet to be loaded.

myApplet.html:

<HTML>

 <HEAD>
 <TITLE> A simple program to run a Java Applet</TITLE>
 </HEAD>

 <BODY>
    Here is the class myApplet is loaded:
   <APPLET CODE=“myApplet.class” WIDTH=150 HEIGHT=100 align=center>
   </APPLET>
 </BODY>

</HTML>

It is possible to write a Java® program that can work both as an applet and as an application.

You can, also, find detailed instructions on how to write your first Java® program at the Lesson: “Your First Cup of Java®” of the on line Java® Tutorial, which is provided by SUN.

3. Data Types

Java® has two kinds of data types, primitive and reference data types. Primitive data type variables contain a corresponding of the data type value, while reference data type variables, such as arrays and classes, contain a reference to the actual set of values.

The following are the primitive (or built-in) data types:

  • boolean  (boolean value, true or false)
  • char  (2-byte, character - Unicode)
  • byte (1-byte, signed integer)
  • short (2-byte, signed short integer)
  • int (4-byte, signed integer)
  • long (8-byte, signed long integer)
  • float (4-byte, floating point)
  • double (8-byte, double precision floating point)

It is allowable to assign the value of a primitive data type variable from one type to another without an explicit cast if the variable that the value is assigned is on the right of the following order list.

                                 byte < short < int < long < float < double

A char can be promoted to an int, long, float or double. However_,_ a boolean cannot be converted to any other primitive data type, since boolean values are not considered to be numbers. The following table presents all the allowable promotions:
 

DATA TYPE ALLOWABLE PROMOTIONS
double none
float double
long float, double
int long, float, double
char int, long, float, double
short int, long, float, double
byte short, int, long, float, double

An assignment from a “higher” order to a “lower” is allowed only when an explicit casting is used, because information may be lost from the conversion, e.g.: int x = (int) 4.75;

Each of  the primitive data types has a corresponding class, called wrapper class, defined in the java.lang package. e.g. a double primitive data type has the corresponding class Double.

4. Variables, Declarations, Definitions, Initializations, and Assignments

The data type of every variable has to be specified in a definition, by preceding the name of the variable that is defined with a data type. A data type can be one of the built-in (primitive) data types, one of the data types defined in the provided Java® packages, or the user defined data type. The name of a variable must be a legal identifier and it should not be the same with any other variable that is defined in the same scope.

The scope of a variable is where the variable is accessible. It is specified by the location where the variable is defined. There are 4 different scope categories:

  1. local variables: variables defined anywhere in a function
  2. member variables: data members of a class (static or non static)
  3. function parameters: parameters of functions in which values are passed when invoking the function
  4. exception-handler parameters: parameters of exception-handlers in which values are passed when the exception handler is called.

Local variables are undefined prior to initialization. Therefore, a local variable must be either initialized or assigned a value before being used. The scope of a local variable is from the point where it has been defined up to the end of the code block in which it has been defined. The memory allocated for a local variable is automatically be reclaimed when control goes out of its scope, upon exiting the function in which it is defined.

The scope of a function or an exception-handler parameter is the entire corresponding function.

A named constant can be defined using the keywords static and final. Static indicates that it is a class variable, while final indicates that its value cannot be changed after it has been initialized.

                e.g.: static final double PI = 3.1414926

5. Operators, Precedence, Associativity, Type Conversions, and Mixed Expressions

Java® has the following categories of operators. Some of them can be used as either unary or binary. Also in Java® the corresponding from the C++ conditional operator is a tertiary operator, i.e. having 3 operands.

  • arithmetic: + , - , *, / , %
  • shorthand arithmetic: ++ , –
  • relational:  > , < , >= , <= , == , !=, instanceof
  • conditional: && , || , ! ,  &, |
  • assignment:  =
  • shorthand assignment: += , -= , *=  , /= , %=, etc.
  • bitwise and logical operators: » ,<< , etc.
  • conditional operator: (logical Test) ? trueStatement : falseStatement

The order in which the operations in expressions are performed is decided according to the precedence and associativity rules, which are the same as in C++. According to any precedence table, the operators of higher precedence are evaluated first, before operators with lower precedence.

The following precedence table (copied from the Java® Tutorial) lists the operators according to their precedence order. Higher precedence operators are evaluated before lower precedence operators.

postfix operators [] . (params) expr++ expr–
unary operators ++expr –expr +expr -expr ~ !
creation or cast new (type)expr
multiplicative * / %
additive + -
shift << » »>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
conditional ? :
assignment = += -= *= /= %= &= ^= |= <<= »= »>=

For operators on the same line, that have equal precedence, associativity decides which operator to be executed first. In Java® all operators, except the assignment operators, have left associativity.

6. Control Structures

Control structures, similar to those of C++, are used to specify the flow of control in Java® programs.

A block of statements, i.e. statements within curly braces, may appear instead of a single statement.

The following are the control structures of Java®:

  • Selection control structures

            if(logical test)
              statement;
 

            if(logical test)
                statement;
           else if(logical test)
                statement;
           else
               statement;
 

            switch(variable)
                {
                    case value1:
                         statements
                         break;
                    case value3:
                         statements
                         break;
                    case value4: case value5:
                         statements
                         break;
                    default:
                         statements
                 }

  • Repetition control structures (looping)

          for(intialization ; logical test; modification)
                  statement;
 

          while(logical test)
               statement;
 

         do
             {
               statements;
              } while(logical test);

Java® provides the break and continue as branching statements. The former cause the exit from the block of statements in which it resides, while the latter causes the flow of control to be transfer to the next iteration. There are also labeled versions of break and continue in which labels are used  where the control is transferred to the block with the specified label. The labeled break and the labeled continue are useful in nested loops. A return statement also is used to return form a function, passing control to the invoking function.

7. Comments

Java® supports 3 kinds of comments. The familiar C++ kinds of comments, the pair /* */ which encloses a comment and the // which indicates that the remaining of the line is a comment are supported.

In addition, Java® supports the documentation comment which is enclosed between /** and */. Comments of this kind are used for automatically generated documentation using the javadoc tool of the Java® Development Kid (JDK).

Having wrote a java file, such as the file Welcome1.java below, using javadoc someone can automatically create an html file corresponding to that java source code.

Welcome1.java:

/**
* This class can take a variable number of parameters on the command
* line. Program execution begins with the main() method.
*/
public class Welcome1
{
/**
* The main entry point for the application.
*
* @param args Array of parameters passed to the
* application via the command line.
*/
public static void main (String[] args)
{
System.out.println(“Welcome to 1.124!!!!”);
}
}

Then, the javadoc can be used to create the corresponding html file:

>javadoc Welcome1.ja
Loading source file Welcome1.java…
Constructing Javadoc information…
Building tree for all the packages and classes…
Building index for all the packages and classes…
Generating overview-tree.html…
Generating index-all.html…
Generating deprecated-list.html…
Building index for all classes…
Generating allclasses-frame.html…
Generating index.html…
Generating packages.html…
Generating Welcome1.html…
Generating serialized-form.html…
Generating package-list…
Generating help-doc.html…
Generating stylesheet.css…

8. Arrays

An array is a set of values of the same data type stored together as an entity, in a contiguous part of memory, and can be accessed using an integer index.

The declaration of an array in Java does not make any memory allocation, but simply defines a reference to an array. A new statement is required to make the proper allocation of memory. Then, an element of the array is accessed using an index within square brackets. An array in Java® has a length field which stores the number of its elements.

The class System has a function called arraycopy() that can be used to copy part or the whole array to another array.

A function in which an array is passed as an argument can change it, since the reference to that array is what is passed by value. An array can be returned from a function, i.e. the return data type of a function can be an array.

The length of an array is fixed upon its definition and cannot be modified. A class named Vector can be used to represent an array whose size can be modified.

An array can contain references to other arrays or objects, in which case memory for the individual members of the array must also be explicitly allocated using a new statement.

A multidimensional array can be defined using an array whose elements are arrays. If the array has the same number of columns then such an array is defined using a statement similar to the following: double x[][] =  new double [n][m]. Otherwise for each row a new expression is used to dynamically allocate memory for it.

Example of Arrays

class introArrays
{
public static final int SIZE=5;

public static void main(String args[])
{
double d[] = new double[SIZE];
int [] i ;
i = new int[SIZE];

for(int j=0 ; j<d.length ; j++)
d[j] = j/2;
for(int j=0 ; j<i.length ; j++)
i[j] = j*j;

for(int j=0 ; j<SIZE ; j++)
System.out.println( "  d[" + j + “] = " + d[j]);
for(int j=0 ; j<i.length ; j++)
System.out.println( "  i[” + j + “] = " + i[j]);

int m=5, n=3;
int im, in;
double x[][] = new double[m][n];

for(im=0;im<m;im++)
{
System.out.println();
for(in=0;in<n;in++)
{
System.out.print(” " + x[im][in] + " “);
}
}

}
}

Output:

  d[0] = 0.0
d[1] = 0.0
d[2] = 1.0
d[3] = 1.0
d[4] = 2.0
i[0] = 0
i[1] = 1
i[2] = 4
i[3] = 9
i[4] = 16

0.0  0.0  0.0
0.0  0.0  0.0
0.0  0.0  0.0
0.0  0.0  0.0
0.0  0.0  0.0
 

In Java® you can easily have a ragged array, such as a 2-D array with rows of different lengths. When an array is created only the length of the primary array must be specified. The length of the sub-arrays can be left unspecified until they are created, as it is shown in the following example.

Example of a Ragged Array

class TriangularArray
{
public static void main(String args[])
{
int i, j;

double [][]x;

x = new double[5][];

for(i=0;i<5;i++)
{
x[i] = new double[i+1];

System.out.println();

for(j=0;j<i+1;j++)
{
x[i][j] = (i+1)*10+j+1;
System.out.print(” " + x[i][j] + " “);
}
}
}

Output:

11.0
21.0  22.0
31.0  32.0  33.0
41.0  42.0  43.0  44.0
51.0  52.0  53.0  54.0  55.0

9. Classes and Objects

In Java® every member function and data belongs to a class and must be defined into a class declaration, i.e. it is not allowable to have global functions and variables. A class may contain data (fields) and functions (methods), which can be class or non-class members of the class.

An object is an instance of a class. It is created using a new operator which instantiates the class, allocating memory for a new object, and initializes the objects data members, usually through constructors rather than directly. The new operator returns a reference to the new instance of the class that has been created, i.e. to the new object. The new object is referenced typically by the variable at the left side of the new statement. The declaration of an object, e.g. Point p, does not allocate any memory for an instance of the class Point. It simply declares that p can be used as a reference to an instance of the class Point. In Java® memory for objects is allocated from the heap using the keyword new. If there is not enough memory to be allocated the garbage collector may run to reclaim some memory and if there is still not enough memory an OutOfMemoryError exception is thrown. The variable that is associated with an object, in contrast to a variable of primitive data type, is actually a reference to that object.

A _class member data, or stat_ic field, is a field that is shared among all objects of that class, as in C++. Similarly, static (or class) functions are methods, that can operate on class member data (static fields), or perform operations on the entire class and not on a certain instance of the class (i.e. object). A static function can access only static members, variables or functions, of the class, since it is not invoked on a specific object. Static fields and methods are declared using the keyword static at its declaration. Class variables and methods are accessible from the class itself. There is no need to create an object, i.e. to instantiate the class in order to access its class (i.e. static) members. The static variables of a class are initialized before any use of any static variable and before the use of any of the member functions of the class.

When a class is defined it is required to use the keyword class followed by the name of the class in the class declaration. The class body, in curly braces, follows the class declaration. Other possible components of a class declaration are the public, abstract, final, extends (superclass), and implements (one or more interfaces). If any, or all, of these optional components are not provided the Java® compiler considers the defaults, which are nonpublic, non-abstract, non-final subclass of the class Object that does not implement any interface.

A public class is publicly accessible, i.e. it can be used by classes in any package, not necessarily classes in its package.

An abstract class is a class that cannot be instantiated, i.e. no objects of that class can be defined. An abstract class must necessarily be subclassed to be used, since it may contain methods with no implementation, i.e. abstract methods. an abstract class may provide definitions of all or some of the methods that the subclasses may inherit. Although an abstract class cannot be instantiated, a reference to an abstract class can be defined and used to achieve polymorphism. Typically, some of the functions are left to be implemented by the subclasses. If a class contains an abstract function then the class is abstract and cannot be instantiated. In that case the class should be explicitly specified as abstract.

A final class is a class that cannot be subclassed. Specifying a class as final automatically implies that all its methods are considered to be final. Specifying a class, or a function, as final may sometimes be useful, considering security and optimization issues.

The extends <superclass> specifies that the class that is declared is a subclass of the provided <superclass>.
Finally, the implements<interface1>, <interface2>,…., specifies that the class implements one or more interfaces, whose names are provided after the keyword implements in a comma separated list.

Note that assigning a reference data type variable, i.e. a variable that refers to an instance of a class, to another reference variable then both variables refer to the same object. The function clone() may be used to actually make an actual complete copy of one object to another, i.e. copy the object state into a new identical object but in a different memory location.

The following is an example of a simple class. The class has two member data, x and y, and a class (or static) data and has no functions.

Example of a Simple Class

class Point
{
public double x, y=100;                        // data member (or field)
public static int num = 0;                       // class or static data member
}

class introClasses
{
public static void main(String args[])
{
Point p1;                                            // declaration of a Point object
Point p2 = new Point();
Point.num++;

p1 = new Point();
p1.y = 1.1;
p1.x = 2.2;
p2.num ++;
System.out.println(“Number = " + Point.num);
System.out.print(”\n p1:    x = " + p1.x + "      y = " + p1.y );
System.out.println(”\n p2:    x = " + p2.x + "      y = " + p2.y );
}
}

Output:

Number = 2
p1:    x = 2.2      y = 1.1
p2:    x = 0.0      y = 100.0

10. Constructors

A class can provide one or more constructors to make the proper initializations of newly created objects. As in C++, a constructor has the same name with the class and has no return type. Java® supports constructor overloading. Constructors are differentiated from each other from the number and type of their parameters. The compiler, upon an object creation, invokes the constructor that matches with the provided arguments. A constructor with no argument is known as the default constructor (or no-argument constructor). If no constructors are provided, Java® provides by default the no-argument (default) constructor which does nothing.  Upon the creation of an object its data fields are set to the default value of zero (for numeric types), ‘\u0000’ (for char), false (for boolean), or null (for reference) depending on the variable’s data type. Then, the initializers and initialization blocks are called to initialize the fields. Finally, the constructor is called, which first, invokes, explicitly or implicitly, its superclass constructor, and then the statements in the body of the constructor(s) are executed.

Another constructor of a class can be invoked from inside a constructor of that class using this which is a reference to the current object. This is called explicit constructor invocation. A constructor of a superclass can be invoked from inside a constructor using the super keyword followed by parentheses in which arguments may be provided. The invocation of a superclass constructor must be the first statement in the subclass constructor so as to perform first the super class initialization. Otherwise, the superclass default constructor will be invoked implicitly.

A constructor of a class can be specified as private, protected, package, and public which specifies which classes are eligible to create instances of that class. When a constructor is private no other class can instantiate the class and only if the class provides public classes to instantiate the class, an object can be created. When a constructor is specified as protected, only subclasses can use it to create objects of the class. If a constructor is specified as public any class can create an object of the class using the constructor. Finally, a package constructor can be used only by classes within the same package to create objects of the class.

Example on Constructors

class Point
{
private double x, y;

Point()                           // default constructor
{
x = y = 0.0;
}
Point(double x, double y)         // constructor overloading
{
this.x = x;
this.y = y;
}

public String toString()            // toString method
{
return (“x = " + x + "    y = " + y);
}
}

class constructorsClasses
{
public static void main(String args[])
{
Point p1= new Point(), p2;
p2 = new Point(2.22,4.8);

System.out.println(”\n p1:    "  +  p1);
System.out.println("\n p2:    "  +  p2);
System.out.println();
}
}

Output:

p1:    x = 0.0    y = 0.0
p2:    x = 2.22    y = 4.8

11. Initializers

In Java® someone can use static initializers and instance initializers to provide initial values for static (i.e. class) and instance (i.e. object) data members. Static initializers cannot call functions that are declared to throw checked exceptions.

If no values are provided to the variables of a class using either initializers or constructors a zero, ‘\u0000’, false, or null value is assigned depending on the variable’s data type.

The following example demonstrates the use of static and instance initializers.

Initializers Example

class Initialization1
{
public static void main(String args[])
{
Point p = new Point();
System.out.println(“Number of points = " + Point.pointsNumber);
System.out.println("(x,y) = (” + p.x + “,”  + p.y + “)”) ;
}
}

class Point
{
double x=200, y=100;                     // instance initializer
static int pointsNumber=1;               // static initializer
}

Output:

Number of points = 1
(x,y) = (200.0,100.0)

However, these initializations can be done only when using assignment statements, i.e. without the ability to call any other method, or throw an exception. For such cases a static initialization block can be used to initialize static members, while a constructor is, in general, used for the initialization of instance members. Constructors are called after the initializers have assigned the default values to the member data.

A static initialization block is a block enclosed with curly braces with the keyword static before it. The static initialization blocks and the static initializers are called in order from left to right and from top to bottom.

Static Initialization Block Example

class Point
{
double x=200, y=100;           // instance initializer
static int pointsNumber;

static                                     // static initialization block
{
pointsNumber = 1;
System.out.println(“Inside static initialization block”);
}
}
 

class Initialization2
{

public static void main(String args[])
{
System.out.println(“Inside main”);
Point p = new Point();
System.out.println(“Number of points = " + Point.pointsNumber);
System.out.println("(x,y) = " + p.x + “,”  + p.y + “)”) ;
}
}
 

Output:

Inside main
Inside static initialization block
Number of points = 1
(x,y) = 200.0,100.0)

Java® supports also instance initialization blocks (or non-static initialization blocks) which are sometimes useful, such as in anonymous classes where having constructors is not possible. An instance initialization block is placed at the location where a new object of the class is created. The non-static initializers are executed upon the creation of an object before the invocation of the corresponding constructor. The order of execution of instance initializers and initialization blocks is again  from left to right and from top to bottom.

An anonymous class is a class without a name that is defined within another class. At the same time that it is defined an instance of it is created using the new keyword. Since an anonymous class has no name it cannot have any constructors.

12. Member Data and Functions

Both member data and member functions are accessed and invoked, respectively, using an object reference followed by a dot and at the right of the dot the name of the data or function of the class. The object reference can be any expression that returns a reference to an object of the class, e.g. a new operator. In the invocation of an object’s function, parentheses, which may contain provided arguments, follow the name of the function. Invoking a function of an object is known as “sending a message” to that object.

Member data (or member variables) are declared using the data type followed by the name of the variable. The data type can be any primitive or reference data type, while the name should be any legal Java® identifier. In addition, the following attributes can also be specified:

  • access level: specifies the access level for this variable, which can be public, package, protected, and private.
  • static: specifies that the variable is static (i.e. class) variable
  • final: specifies that the value of the variable after it is assigned cannot be modified
  • transient: indicates that the variable is transient, which is not yet fully specified
  • volatile: indicates that the Java compiler should not perform certain optimizations on the variable

Member functions are typically provided, as in C++, to operate on member data allowing data encapsulation, hiding data behind functions (methods). However, in Java® global functions are not allowable. Every function must be provided within a class definition. Also externally defined member functions are not possible in Java®, since everything must be defined within a class. Java® supports recursion, allowing a function to call itself either directly (direct recursion), or indirectly (indirect recursion) through another function.

A function has two parts, the function declaration and the function body. The function declaration must provide the return data type and the name of the function followed by parentheses that enclose the parameters of the function.

A function may return a value or no value in which case it is declared as void. A function that returns an object of a class can return an object of any subclass of that class as well. In addition, a function may have an interface as a return type, in which case an object of any class that implements that interface may be returned.

The function name should be any legal Java® identifier. The name of a function can be the same as the name of a data member of the class. Java® supports, as C++, function overloading allowing functions to have the same name as long as the individual functions with the same name differs in the number or/and type of the parameters. The signature of a function is its name together with the number and type of its parameters. Functions with different signatures, although with the same name, are allowable.

A function may have no or any number of arguments. A function with no arguments is defined using empty parentheses. An argument with the same name as a member variable of the class hides the member variable. In that case the reference this can be used. The latter, i.e. this, is a reference that refers to the object with which the member function has been invoked. The reference this may be used to pass a reference to the object that has invoked the member function, as an argument to the member functions. Similarly the reference super refers to the superclass of a class and can be used when a member variable or function of a superclass is hidden. In Java, it is not possible to pass a function (or a pointer to a function) as an argument to a function.

All arguments to functions in Java® are passed by value, which means that primitive data type arguments and the actual references cannot be modified. The values of the parameters are copies of the values of the arguments passed to the function. Declaring a function parameter using the final modifier prohibits the modification of that parameter within the function.

A function declaration may also provide more information about it using any of the following attributes:

  • access level: specifies the access level for this variable, which can be public, package, protected, and private.
  • static: specifies that the function is static (i.e. class) function, i.e. that it is not associated with a certain object of the class
  • abstract: indicates that the method is not implemented. Therefore, the class is abstract and cannot be instantiated.
  • final: specifies that the function cannot be overridden by a subclass
  • native: indicates that the function is implemented in another language (e.g. C++)
  • synchronized: indicates that certain precautions should be taken to ensure that functions that operate on same data, do it in a threat-safe way.
  • _throw_s <exceptions>: specifies the checked exceptions that the function may throw

An implicit reference to the object with which a function is invoked, called this, is available in every non-class (i.e. non-static) function. It is used to explicitly refer to members of the object that have invoked the function, or when an object reference is required.

A function of a class with the name toString() that takes no arguments and returns a String is a special function. It allows the object to be used in a string concatenation using the + operator, e.g. in a println statement. Note that all the primitive data type variables are implicitly converted to String objects whenever they are used in String expressions.

In Java® there is no need to worry about explicitly destroying objects that are not needed anymore. Java® provides a garbage collector that periodically frees memory of objects that are no more being referenced. When a variable that is used to reference to an object goes out of scope or it is set to null, that object, if not referenced by any other variable becomes eligible for garbage collection.
 

Example on Member Data and Methods

class Point
{
private static int num = 0;        // static field
private double x, y;                      // non-static data members (fields)

/* set methods */
public void setX(double x)
{
this.x = x;
}
public void setY(double yy)
{
y = yy;
}
public static void incrNum()    // static method
{
num++;
}

/* get methods */
public double getX()
{
return x;
}
public double getY()
{
return y;
}
public static int getNum()     // static function
{
return num;
}

public String toString()            // toString method
{
return (“x = " + x + "    y = " + y);
}
}
 

class methodsClasses
{
public static void main(String args[])
{
Point p1, p2 = new Point();
Point.incrNum();

p1 = new Point();
p1.incrNum();

p1.setX(1.1);
p2.setX(2.2);

System.out.print(”\nNumber = " + Point.getNum());

p1.setY(0.11111);

System.out.print(”\n p1:   x = " + p1.getX() + "     y = " + p1.getY() );
System.out.print("\n p2:   " + p2 );
System.out.println();
}
}

Output:

Number = 2
p1:   x = 1.1     y = 0.11111
p2:   x = 2.2     y = 0.0

13. Function Overloading

Java® allows function overloading with which the selection of the function is based on its signature. The signature of a function is its name and the number and type of its parameters, i.e. the return type of a function is not part of its signature.

Example on Function Overloading

class methodsOverloading
{
  public static void main(String args[])
  {
    myPrint();
    myPrint(3);
    myPrint(1.7);
  }

public static void myPrint()
  {
    System.out.println(" Inside myPrint()");
  }
  public static void myPrint(int i)
  {
    System.out.println(" Inside myPrint():    i =" + i);
  }
  public static void myPrint(double x)
  {
    System.out.println(" Inside myPrint():   x = " + x );
  }
}

Output:

 Inside myPrint()
 Inside myPrint():    i =3
 Inside myPrint():   x = 1.7

These notes were prepared by Petros Komodromos.

Topics

  1. Sun Java® Studio Standard 5 
  2. Inheritance 
  3. Controlling access to class members
  4. Strings
  5. Packages 
  6. Interfaces 
  7. Nested classes and interfaces 
  8. Garbage Collection 
  9. Applets 

1. Sun Java Studio Standard 5

Sun Java® Studio Standard 5 is an integrated Java® development environment (IDE) that provides visual design, editing, compilation, debugging, and deployment of Java® software. It is itself written entirely in Java® and is from the Sun’s Java® web site: Sun Java® Studio Standard 5 update 1. If you prefer to use a free IDE and only require J2SE and web application development capabilities then use the open source IDE NetBeans.

2. Inheritance

A class (called subclass) can inherit the behavior of another class (called superclass). This is called inheritance and it is essential for Object Oriented Programming (OOP), allowing the extension of existing classes. A class is extended using the keyword extends following the name of the new class (i.e. the subclass) and before the name of the superclass.

          e.g.: class Pixel extends Point

A subclass object can be used in any function that an object of its superclass is expected. Note, that the converse i.e. using a superclass where a subclass is expected is, in general, false. This feature enables polymorphism, another important characteristic of OOP, which allows a different function to be called depending on the actual object that is associated with the function call. Polymorphism is a Greek word which means that it allows many (poly) forms or shapes (morphes). An object in Java® can be polymorphic, i.e. have various different forms and this is possible due to the fact that a subclass can be used wherever a superclass is legal and the late binding feature of Java®. With late binding the compiler does not associate the function call with a certain function during compilation, but delays that decision until execution (i.e. running)  time. In contrast, with static binding the decision of which function to be invoked is decided at compile time.

Although when calling a function the decision of which function of a class to call depends on the actual class of the object, when accessing a data field of an object the decision is according to the type of the reference to the object.

Although a subclass object can be assigned to a superclass object, i.e. to a variable that may refer to a superclass, the converse is not allowable unless an explicit casting is used. The syntax for casting between two objects is the similar to the casting from one primitive data type to another. In order to avoid illegal castings the instanceof operator may be used to verify the data type of the object prior to any casting. Casting of objects is allowable only between objects within an inheritance hierarchy. If an object has been assigned to a reference to its superclass, then to be able to use the objects (i.e. the subclass’ functions) it is required to cast it back to the subclass.

An implicit reference, named super, is available and references to members of the superclass of the class of the object that invoked a function. It is similar to the this reference.

Subclasses can add other member and class data, or member or class functions. Data fields of a subclass can hide data fields of a superclass if they are given the same name. Then, although the data fields of the superclass exist they can be accessed only indirectly. Similarly, a subclass can inherit and use, or override, the methods provided in its superclasses. Member functions of the superclass can be overridden by new implementations provided in the subclass. However, a subclass cannot override superclass functions that have been declared as final or static (i.e. class functions). It can only override accessible non-static functions. A subclass must override functions that are declared as abstract in the superclass or the subclass must be abstract itself.

A subclass function with a name the same as a superclass function may have different parameters, according to function overloading. However, a subclass function with the same signature with a superclass function must also have the same return type, in order to hide the superclass’ function. The overriding function must have the same signature (name and parameters) and the same return type with the function it overrides. Which function is called depends on the actual object and its class that is referenced by the object reference and not by the class of the reference to the object itself.

The overriding function can have a different access specifier but only if it allows more access than the one of the superclass function that overrides. It can have also different throws specified but not a throw clause that has not been declared by the superclass function or an exception type that is not a subtype of the exceptions specified in the superclass. The hidden variables and overridden functions of a superclass can be accessed using the super keyword.

Inheritance Example

class Point
{
private double x, y;

Point()
{
System.out.println(" In default constructor of Point class");
x = y = 100.0;
}
Point(double x, double y)
{
System.out.println(" In constructor Point(double x, double y)");
this.x = x;
this.y = y;
}

public void set(double x, double y)
{
System.out.println(" In set method of Point");
this.x = x;
this.y = y;
}

public String toString()
{
System.out.print(" In toString method of Point class");
return (“x = " + x + "    y = " + y);
}
}
 

class Pixel extends Point
{
private int color;

Pixel()
{
System.out.println(” In default constructor of Pixel class");
color = 111111111;
}
Pixel(double x, double y, int color)
{
super(x,y);
System.out.println(" In  Pixel(double x, double y, int color) “);
this.color = color;
}

public void set(double x, double y, int color)
{
System.out.println(” In set of Pixel class calling Point’s set");
super.set(x,y);
this.color = color;
}

public String toString()
{
System.out.print(" In toString method of Pixel class");
return (“color = " + color );
}
}

class introInheritance
{
public static void main(String args[])
{
Pixel p1;
p1 = new Pixel();
p1.set(1.11, 0.22, 100111010);
System.out.println();

Point p2 = new Point(200,25.7);
Pixel p3 = new Pixel(3.33,9.6,111000111);
Point p4 = new Pixel();
System.out.println();

System.out.println(”\n p1: " + p1);
System.out.println("\n p2: " + p2);
System.out.println("\n p3: " + p3);
System.out.println("\n p4: " + p4);
System.out.println();
}
}

Output:

In default constructor of Point class
In default constructor of Pixel class
In set of Pixel class calling Point’s set
In set method of Point

In constructor Point(double x, double y)
In constructor Point(double x, double y)
In  Pixel(double x, double y, int color)
In default constructor of Point class
In default constructor of Pixel class

In toString method of Pixel class
p1: color = 100111010
In toString method of Point class
p2: x = 200.0    y = 25.7
In toString method of Pixel class
p3: color = 111000111
In toString method of Pixel class
p4: color = 111111111
 

Java® does not allow multiple inheritance. All classes in Java® are subclasses of the class Object, which is defined in the package java.lang. Even if no inheritance is used, i.e. no extension, the class is considered to be a subclass of the Object class. Variables of  class Object can be used to refer to any reference data type, object or array. The functions toString(), equals(), clone(), and finalize() are functions of the class Object that are often overridden by the subclasses. The toString() method returns a string representation  of the object. The clone() method is used to make a field-by-field copy of one object to a new one which is returned by the method. The equals() method is used to compare two objects for equality according to a selected equality test. Finally, the finalize() method can be used to clean up resources that may have been allocated from the object before it is garbage collected. Another useful function is the getClass(), which, however, cannot be overridden since it is a final function. This function returns the runtime class of the object, an instance of Class, which can be then query for various information. The class Class provides various useful functions.

Object Wrappers

Java® provides object wrappers which are subclasses of the Object class that are counterparts of the primitive data types and allow the conversion of a primitive data type variable to an object.

The wrapper classes which are the Boolean, Character, Byte, Double, Float, Integer, Long, Short and Void, are final classes that are useful when doing generic programming. The wrapper classes provide several useful functions.

3. Controlling Access to Class Members

Java® provides, as C++ does, an access control mechanism which restricts the access to member data and functions of a class. In Java® the access level is specified for each individual member of the class. There are 3 different access specifiers which may be used to specify one of the 4 access levels. The access specifiers (or access control modifiers) are the private, protected, public and package which are specified as follows:

ACCESS SPECIFIER ACCESS LEVEL ACCESSIBLE IN CLASS ACCESSIBLE IN SUBCLASS ACCESSIBLE IN PACKAGE ACCESSIBLE IN WORLD
private private yes no no no
protected protected yes yes yes no
public public yes yes yes yes
none package yes no yes no

A private member of a class is accessible only within the class itself. Instances of the same class have access to each other’s private members. The keyword private is used to specify a private member.

A protected member of a class allows access for and can be inherited by only the class itself, its subclasses, and classes defined in the same package. A protected member (data or function) can be accessed using a reference variable that can be either a reference to the class itself or to one of its subclasses. Protected static data and functions of a class can be accessed in any of its subclasses as well as by any class in the same package. The keyword protected is used to specify a protected member.

A package member is accessible from and is inheritable by all classes in the same package as the class. This is the default access level used when no access specifier is used.

Finally, a public member is accessible from all classes and is inherited by any subclass. It is specified using the keyword public.

Therefore, a subclass inherits the members of its superclass that are protected and public, as well as the members without access specifier, i.e. the package members, as long as  the subclass is in the same package with the superclass. However, subclass member variables hide the superclass members variables with the same name, and subclass member functions override the corresponding ones in the superclass. The keyword super can be used to refer to hidden variables and overridden functions of the superclass.

4. Strings

The String class can be used to deal with strings, i.e. a sequence of characters. This class provides several functionalities that can be used on strings. It provides a concatenation operator “+”, and a shorthand operator “+=”. In addition, it provides a length() method to obtain the number of the characters in the string. A method, named equals() is also available to compare two strings whether they have the same contents.

When concatenating any primitive data type value with a string it is always converted to a string. For objects a special function named toString() which is inherited from the Object class can be overridden to provide this facility.

String objects are only readable. Their contents cannot be modified, i.e. they are immutable. When a concatenation, or assignment is done, what actually happened is that a new String object is created and referenced. Java® provides another class, named StringBuffer which can be used to store and modify a set of characters.

Example with Strings

class introStrings
{
public static void main(String args[])
{
String course = new String();
double number = 1.124;
String title[] = new String[3];

title[0] = “Computer” + “-”;
title[1] = “Aided”;
title[2] = " Engineering";

course = number + “: “;
for(int i=0 ; i<title.length ; i++)
course += title[i];
System.out.println(course);
}
}
 

Output:

1.124: Computer-Aided Engineering

5. Packages

A package essentially defines a certain namespace, allowing functions in different packages to use same names. A package is used to group together a collection of related classes and interfaces.

Every class in Java® belongs to a package, either the one specified at the top in a package statement or the default package. Everything in the java.lang package is by default imported in any Java® program, and therefore, it does not need to be imported.

A package is created by using a package statement in the java® source code file. The statement is the keyword package followed by the name of the package. Then, everything defined after that point is considered to belong to that package. When a Java® source code file is compiled the resulting class file(s) is (are) created in the directory specified by the package statement. An extra option to the javac compiler (javac -d <directory> <myProgram.java>) can be used to specify where to create all subdirectories according to the package statement. To enable the use of any new packages it may be necessary to properly setup the CLASSPATH environment variable. The latter variable indicates where to search for user-developed packages.

The classes defined in a package can be used in a program using an import statement. This statement consists of the keyword import followed by the name of the package to be inserted. The import statement specifies to the Java® compiler the location of the classes enabling the use of shorter names for each imported class. No import statements are required if the class files that are used in a Java® program are in the same directory that the class that uses them is located.

A class for which a package name has not been specified belongs to the default package. The default package is without a name and it is always imported by default.

The Core Java® API provides a collection of packages that can be used.

6 . Interfaces

Interfaces are similar to classes, but they contain only declarations of methods. An interface declares sets of constants and functions, without providing implementations for any of them. They are used to declare methods that should be supported by a class without, however, to actually implement these functions. In particular, interfaces may contain public abstract methods and public final static data.

Any class that implements an interface must provide the actual implementation for any functions that have been declared in the interface. Such a class is said to implement the interface. If even one function is left unimplemented the class is considered to be abstract.

A class is specified to implement an interface using the keyword implements. A class can implement more than one interfaces, although it cannot extent more than one class. However, a class that implements an interface must provide implementations for all of the functions of the interface.

In addition interfaces can be extended using the keyword extends.

 Example with Interfaces

interface Geometry
{
public void print();
}

class Point implements Geometry
{
private double x, y;

Point()
{
x = y = 100.0;
}
Point(double x, double y)
{
this.x = x;
this.y = y;
}

public void print()
{
System.out.print(” x = " + x + "   y = " + y );
}
}
 

class introInterfaces
{
public static void main(String args[])
{
Point p1;
p1 = new Point();
Point p2 = new Point(2.2, 0.44);

System.out.print(”\n p1: “);
p1.print();
System.out.print(”\n p2: “);
p2.print();
System.out.println();
}
}

Output:

p1:  x = 100.0   y = 100.0
p2:  x = 2.2   y = 0.44

7. Nested Classes and Nested Interfaces

A nested class is a class defined within another class. Similarly Java® allows nested interfaces, i.e. interfaces which are members of another interface.

The simplest nested class is a static nested class which is a class with a name provided inside another class with the specifier static used at its declaration. Since it is a member of the class the access level can be set using the access specifiers. Outside the class someone refers to the nested class by the name of the class in which it is nesting followed by a dot and the name of the nested class.

A non-static nested class is called an inner class and it is associated with a particular instance of the class rather than the entire class. When an object of an inner class is created an enclosing object, of the “outer” class is, in general, associated with that object. Inner classes cannot have static members. A local inner class is an inner class that is not a member of a class but it is defined in a function of a class and, therefore, it is local to the function.

A nested class, non-static nested or inner class, can use any member of its enclosing class without the need for qualification and with access to all members, including the private ones. However, a static nested class can access only the static members of the enclosing class, since it is not associated with any particular object.

In some cases, e.g. handling AWT events, there is no reason for naming an inner class. In those cases, an anonymous class is preferable to be used if the class is only for a single use. An anonymous class is an inner class without a name that is defined at the point where it is needed rather somewhere within the class with a name.

Nested interfaces can only be static since an interface cannot have an implementation.

8. Garbage Collection

Java® provides a garbage collector which takes care of dynamic memory deallocation. As soon as Java® objects become unreferrenced the corresponding memory may automatically reclaim it. The garbage collector periodically destroys any unused object in dynamic memory, typically only when running out of memory. In Java® there is no need of explicit dynamic memory release and, therefore, the danger of memory leak is reduced.

A mark-sweep algorithm is used by the garbage collector. First, the dynamic memory is scanned for referenced objects and then all remaining objects are treated as garbage. Any object that is not referenced in any way is eligible for garbage collection. An object that is only referenced by objects that are also unreferrenced is considered unreferrenced and is eligible for garbage collection. An object is considered unreferrenced when any references that used to refer to it have changed, referring to another object or to null, or if there has been a return from a function in which the local references used to refer to the object. However, a memory leak is still possible when references are kept to objects that are no longer needed.

Prior to the actual memory deallocation of memory for an object, the object’s finalizer, i.e. a function named finalize, is invoked. This process know as finalization, allows the object to perform a cleanup of any associated system resources. It is typically used to reclaim external resources that have been allocated, e.g. to make sure that files that have been opened are properly closed. The finalize() function is inherited from the Object class and can be overridden. It is allowable to use try-catch in a finalize function to handle exceptions that may result from function calls within its body. In general, the last thing that a finalize() method should do is to call super.finalize() to give the superclass the chance to finalize itself, since the superclass finalize is overridden.

The garbage collection may be performed at any time and in any order according to efficiency considerations of the garbage collector. However, it is possible to explicitly request finalization and garbage collection using the System.runFinalization() and System.gc() commands, respectively.

9. Applets

An applet inherits functionalities that allow it to run in a Java®-enabled browser. Although applets do not need to implement a main method, every applet has to implement at least one of the init, start, or paint methods that inherits from its superclass. For AWT, the class Applet that is provided in the package java.applet of the Application Programming Interface (API) is inherited by any applet. For Swing applets the class JApplet is inherited by any applet. Class JApplet extends the AWT Applet class and implements the Accessible and RootPaneContainer interfaces.

Java® applet is based on a set of conventions and functionalities that are inherited allowing it to be executed in an appletviewer or any Java® enabled browser. An html file needs to be used so as to load the class from a Java® enabled browser, or using the appletviewer provided with the Java® Development Kit (JDK). The class file of an applet can be loaded and interpreted in any Java® enabled browser, or the appletviewer, using an html file. The html code is used to specify at least the location and the dimensions of the applet to be loaded. When a Java®-enabled browser, or the appletviewer, encounters an <APPLET> tag, it reserves a display area according to the specified width and height for the applet, loads the bytecodes for the specified subclass of Applet, then, creates an instance of that subclass. Finally, it calls the applets init() and start() methods. The execution of the applet can be customized using the options that the <APPLET> tag provides. When the applet needs to use a class, the browser, first,  tries to find the class on the host that’s running the browser, and if it cannot find, it searches for it in the same place from where the Applet subclass bytecode came from in order to create that applet’s instance.

An AWT applet inherits, since it extends it, the Applet class provided by the java.applet package of the Java® Core API. The Applet class extends the AWT (Abstarct Window Toolkit) Panel class, which itself extends the Container class (which provides the ability to include other components and using a layout manager to control the size and position of those components). The latter extends the Component class (which provides the drawing and handling events capabilities). Swing applets extend the JApplet class, which is a subclass of the AWT Applet class and implements the Accessible and RootPaneContainer interfaces.

As soon as an applet (i.e. an instance of an Applet subclass) is loaded, that instance of the Applet subclass is created, the applet initializes itself (i.e. method init() is called), and starts running (i.e. method start() is invoked). If the user leaves from the page with an applet, or icognify the window that contains an applet, the applet has the chance to stop itself. In case of returning to that page, or opening the icognified window, the applet can start itself again. Upon quitting a browser that shows an applet, or in general unloading an applet, the applet has the chance to stop itself (i.e. call the method stop()) and do final cleanup (i.e. call the method destroy()).

It is preferable to put the code for initialization of an object of an Applet subclass in the init() method instead of using a constructor, which, in general, should be avoided. The code to be executed by an applet after its initialization should be put in the start() method. The code to stop that execution should be provided in the stop() method.

The main display method of an applet is the_paint()_ method which is inherited by the Panel class and can be overridden. This method is invoked when the applet needs to draw itself to the screen. Method update() can also be overridden to improve the quality and performance of the drawing.

Any Applet subclass must provide at least one of the init()start(), and paint() methods.

In general, due to security considerations, an applet cannot read and write files, or start any program on the host that’s executing it. It, also, cannot make network connections except to the host from where it was loaded and it cannot read certain system properties. On the other hand, applets have some additional capabilities that are not available to applications. The Applet API enables an applet to load data files specified relative to the URL of the applet or the page in which the applet is running, force the browser to display a document, find and communicate with other applets running in the same page, play sounds, etc.

Since the Applet class is a subclass of the Panel class, which provides drawing and event-handling capabilities, it is easy to create a GUI (graphical interface), or, in general, to use graphical compontents. An applet does not have to create a window to display itself in, since it can display itself within the browser window.

An applet can have its own threads allowing it to create and use its own threads to perform time-consuming tasks. In most cases, a browser, in which an applet is loaded, allocates a thread, or a thread group, for the applet. The drawing methods of an applet (i.e. paint() and update()) are called from the AWT drawing thread.

When implementing Swing applets, the JApplet class should be used instead of the Applet class in order to avoid problems due to mixing of AWT and Swing components. Similarly, when implementing Swing applications the JFrame should be used instead of the Frame class.

These notes were prepared by Petros Komodromos.

Topics

  1. Exceptions
  2. Threads
  3. I/O
  4. Introduction to Java® GUI and Swing

1. Exceptions

Java® provides ways to manage exceptions, i.e. errors that may occur during the execution of a program that disrupt the normal flow of instructions. When an unexpected error condition happens during runtime a Java program “throws” an exception. This can avoid the premature termination of the program, which in some cases is not necessary. A function that detects an error throws an exception, which can be caught by an exception handler. The exception object contains information about the exception, e.g. its type and the state of the program when the error had occurred.

The runtime system tries to find some code to handle the error, i.e. an exception handler. The exception causes a search through the call stack to find an appropriate handler to handle the exception. The Java® runtime system searches backwards through the call stack to find any methods that are interested in handling a particular exception. The corresponding, or the default, exception handler catches the exception and may throw a new exception or attempt to handle the exception. If the runtime system, after an exhaustive search of all the methods on the call stack, cannot find an encompassing exception handler the exception is caught by a default exception handler. The latter, usually, prints information about the thrown exception, e.g. where it was thrown, and, then, the runtime system, and, consequently, the Java® program, terminate. An exception handler can catch an exception based on its group or general type by specifying any of the exception’s superclasses in the catch statement.

An exception in Java® is an object which is a subclass of the class Throwable, and, in most cases, it is derived by the class Exception, which is a subclass of Throwable. The Throwable  class is the superclass of all errors and exceptions in the Java® language. Only objects that are instances of this class, or of one of its subclasses, can be thrown by the Java® VM, or by a Java® throw statement.

In Java®, exceptions are managed by first trying something in a try{….} compound block. The code that might throw an exception is placed in a try block. If an error occurs in that block an exception is thrown. Then, a catch{…..} block is used to catch any exception that is thrown. Zero or more catch blocks may follow the try block. The code that may provide the action that must be taken in the case of a certain exception occurs is provided in a catch block that follows the try block. Each catch block specifies the type of exception it can catch. Only the Throwable class or one of its subclasses can be the argument type in a catch clause. An optional finally {….} block, which follows the last catch block or the try block when there are no catch blocks, provides code that is executed in any case, i.e. regardless whether an exception occurs or not.

Example

class Exceptions1 
{ 
public static void main(String args[]) 
{ 
double d[] = new double[4]; 
for(int j=0 ; j<d.length ; j++) 
d[j] = j*7.15 + 2.19 ;

for(int j=0 ; j<=d.length ; j++) 
{ 
try{ 
System.out.print( "  d[" + j + “] / " + j + " = “); 
if(j==0) 
& #160;  throw new DivideByZeroException(); 
System.out.println(d[j]/j); 
} 
catch(ArrayIndexOutOfBoundsException e) 
{ 
System.out.println(“Exception: " + e.getMessage()); 
System.out.print(“e.printStackTrace(): “); 
e.printStackTrace(); 
} 
catch(DivideByZeroException e) 
{ 
System.out.println(“Exception: " + e.getMessage()); 
} 
finally 
{ 
System.out.println(“Inside finally()\n”); 
} 
}

System.out.println( “\n  Program exiting \n”); 
} 
}

class DivideByZeroException extends ArithmeticException 
{ 
DivideByZeroException() 
{ 
super(“Trying to divide by zero”); 
} 
} 
 

Output:

d[0] / 0 = Exception: Trying to divide by zero 
Inside finally()

d[1] / 1 = 9.34 
Inside finally()

d[2] / 2 = 8.245000000000001 
Inside finally()

d[3] / 3 = 7.880000000000002 
Inside finally()

d[4] / 4 = Exception: null 
e.printStackTrace(): java.lang.ArrayIndexOutOfBoundsException 
at Exceptions1.main(Compiled Code) 
Inside finally()

Program exiting 
 

A checked exception requires the specification of the way that the exception should be handled. If a function may result in a checked exception, this must be defined using the keyword throws at the definition of the class, after its name. A method can only throw checked exceptions that have been specified in its declaration. Java® requires that a method should either catch, or specify all checked exceptions that can be thrown within the scope of that method.

Unchecked exceptions are of type RuntimeExceptionError, or subclasses of them, and can be thrown anywhere without the need to specify the possibility of such exceptions to be thrown. Classes Exception and Error are subclasses of the class Throwable_,_ as shown in the following figure, which was adapted from Sun’s Java® Tutorial. Class RuntimeException is a subclass of the class Exception.

<section data-uuid=“f494f101-342f-990d-5567-57018b331777”></section>

Any method (i.e. function) that may produce a non-RuntimeException should declare the type of exception that it can produce using the throws keyword, or that exception should be caught in a try/catch block in the method. The basic error type is class Exception, although there are more specific types of exceptions. When a checked exception occurs, the throw keyword may used to actually create the Exception object and either an exception handler should catch and handle the exception, or the function exits. Java® uses the “termination model of exception”, since control cannot return to the throw point, when an exception is thrown.

Code that may produce an exception is placed within the try block of a try-catch statement. If the code within the try block fails, the code within a corresponding catch block, if exists, is executed. When an exception is thrown the control goes out of the try block and searches the catch blocks to find an appropriate handler. If the code succeeds, then control passes to the next statement following the try-catch statement or to the finally block. In the presence of a finally block, the latter is also executed irrespectively of whether an exception has occurred. If no exception handler is found for an exception, the search continues in the enclosing try block and a Java® application terminates if no exception handler is eventually found.

Class Error  is, also, provided in Java® to be used for serious problems that should not be caught. The Error class is a subclass of the Throwable class. RuntimeExceptions should be dealt directly rather than throw an exception.

The main advantages of using exception handling are that regular code is separated from error handling code making the code much more readable. Also, error types can be grouped together, and the errors can propagate up the call stack to find a proper exception handler.

However, use of exception handling for purposes other than error handling should be avoided since it can cause a performance overhead. Exception handling should be used only when a method is unable to detect and process an error and, thus, it throws an exception under an exceptional situation, instead of processing it locally. You should avoid using exception-handling for purposes other than error handling. The latter reduces clarity but also results in an execution time overhead imposed by the exception handling code. However, when an exception does not occur there is almost no execution overhead.

An exception handler can do several things such as try to recover and resume execution, rethrow an exception, throw a different type of exception. As soon as the exception handler begins execution the try block has expired, and control is in a different scope. If an exception occurs in a handler, it may be processed only by code outside the try block in which the original exception had been thrown. Similarly, a rethrown exception can be caught only by an exception handler after the next enclosing try block.

A subclass class overridden method can have in its throws list only the same with, or a subset of, its superclass’s method throws list.

2. Threads

A threat is a single sequence of steps executed one at a time, i.e. a sequential flow of control, running within a program and taking advantage of the resources allocated for that program and its environment.

Multithreading allows to a program to perform several tasks, i.e. to use more than one flow of control, concurrently. All threads of a multithreading Java® program share the same data and system resources, running at the same time and performing different tasks. Since most computers have only one CPU, threads share the CPU with other threads.

Threats can be used by one of the following ways:

By providing a subclass of the Thread class

By providing a class that implements the Runnable interface.

 1. Providing a subclass of the Thread class:

We need to provide a subclass of the Thread class. This subclass should override the run() method of class Thread. The latter must contain all code that is to be executed within the thread. An instance of the subclass can then be allocated and started.

class MyThreadClass extends Thread 
{

public void run() 
{ 
// Code to be executed within the thread 
}

} 
 

Then, we can create a new thread by instantiating a subclass of the Thread class. Then, we can configure the thread, e.g. by setting its initial priority, name, etc. Finally, we can run it, by calling the start() method that is inherited from the class Thread, which invokes the new thread’s run() method.

MyThreadClass x = new MyThreadClass(); 
x.start();

However, using this approach is not possible when we need to extend some other class, since Java® does not allow multiple inheritance.

Example

class MyThreadClass extends Thread 
{ 
String name;

MyThreadClass(String str) 
{ 
name = str; 
}

public void run() 
{ 
for(int i=0; i<5;i++) 
{ 
System.out.println(“Thread: " + name); 
try 
{ 
Thread.sleep((int) (Math.random()*1000)); 
} 
catch(InterruptedException e) 
{ 
System.out.println(“Catch: " + getName()); 
} 
} 
}

}

class ThreadExample1 
{ 
public static void main(String args[]) 
{ 
MyThreadClass  th1 = new MyThreadClass(“Thread-1”); 
MyThreadClass  th2 = new MyThreadClass(“Thread-2”); 
MyThreadClass  th3 = new MyThreadClass(“Thread-3”); 
 

th3.start(); 
th1.start(); 
th2.start();

} 
}

*Output: *   > java ThreadExample1

Thread: Thread-3 
Thread: Thread-1 
Thread: Thread-2 
Thread: Thread-1 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-1 
Thread: Thread-2 
Thread: Thread-3 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-2 
Thread: Thread-3 
Thread: Thread-2 
Thread: Thread-2

2. Implementing the Runnable Interface

The other way to create a thread is to declare a class that implements the Runnable interface. The Runnable interface requires the implementation of the run() method, in which all code that is to be executed within the thread should be placed. An instance of the class can then be allocated. Finally, a Thread object can be created passing as an argument the object of the class that implements the Runnable interface, and, then start the thread.

class MyRunnableClass implements Runnable 
{

public void run() 
{ 
//  Code to be executed within the thread 
}

}

A new thread can by created by creating a Thread object using an object of type MyRunnableClass, and, then, by calling the start() method, of the Threat class, which runs the thread.

MyRunnableClass x = new MyRunnableClass(); 
Thread t = new Thread(x); 
t.start();

Example

class MyRunnableClass implements Runnable 
{ 
String name;

MyRunnableClass(String str) 
{ 
name = str; 
}

public void run() 
{ 
for(int i=0; i<5;i++) 
{ 
System.out.println(“Thread: " + name); 
try 
{ 
Thread.sleep((int) (Math.random()*1000)); 
} 
catch(InterruptedException e) 
{ 
System.out.println(“InterruptedException in " + name + " thread.”); 
} 
} 
}

}

class ThreadExample2 
{ 
public static void main(String args[]) 
{ 
MyRunnableClass  r1 = new MyRunnableClass(“Thread-1”); 
MyRunnableClass  r2 = new MyRunnableClass(“Thread-2”); 
MyRunnableClass  r3 = new MyRunnableClass(“Thread-3”);

Thread th1 = new Thread(r1); 
Thread th2 = new Thread(r2); 
Thread th3 = new Thread(r3);

th3.start(); 
th1.start(); 
th2.start();

} 
} 
 

Output:      > java ThreadExample2

Thread: Thread-3 
Thread: Thread-1 
Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-2 
Thread: Thread-3 
Thread: Thread-2 
Thread: Thread-1 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-3 
Thread: Thread-1

States of a thread during its lifetime:

New: A new thread is one that has been created, i.e. using the new operator, but has not yet been started.

Runnable (ready state): A thread becomes runnable once its start() method has been invoked, which means that the code in the run() method can execute whenever the thread receives CPU time from the operating system. A threat that had been set to sleep and the time interval has expired, a thread which was waiting and has been notified, a thread that had been suspended and it has been resumed, and a thread that had been blocked by an I/O request and the I/O has been completed are also in a runnable state.

Not Runnable (blocked state): A thread can become not runnable if: the thread’s sleep() method is invoked (in which case, the thread remains blocked for a specified number of milliseconds, giving a chance to lower-priority threads to run); the thread’s suspend() method is invoked (in which case, the thread remains blocked until its resume() method is invoked); the thread calls the wait() method of an object (in which case, the thread remains blocked until either the object’s notify() method or its notifyAll() method is called from another thread); the thread has blocked on an I/O operation (in which case the thread remains blocked until the I/O operation has been completed)

Dead: A thread can die either because the run() method has finished executing, i.e. has terminated, or because the thread’s stop() method has been called. The latter should be avoided because it throws a ThreadDeath which is a subclass of Error.

The following diagram shows the possible life cycle of a thread:

Note that the methods stop(), suspend(), and resume() have been deprecated, and, therefore, should be avoided to manage threads. Instead of using the stop() method, someone can modify some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method as soon as this variable indicates that it is to stop running. A variable indicating the desired state of the thread can be used to specify whether it should be active or suspended, in order to avoid the suspend(), and resume() methods. When the desired state is suspended, the thread can wait using the wait() method. When the thread is resumed, the target thread is notified using the notify() method.

The wait() method makes a thread wait until some condition is satisfied. Actually wait() not only pauses the corresponding tread but also releases the lock on the object, which allows other threads to invoke synchronized code on that object. The notification methods notify() and notifyAll() can be used to inform waiting threads that there was some change that might have caused the satisfaction of that condition. Almost always the notifyAll() method is used to wake up all waiting threads, while notify() picks one of the waiting threads and wakes it up. Notifications affect only threads that have been waiting ahead of the notification, i.e. the wait() has been executed before the occurrence of the notification. The wait and notify methods can be invoked only from synchronized code, either directly or indirectly (through another method called from the synchronized code).

Typically wait() is used as follows. Note that the condition test should always be in a loop.

synchronized  returnType functionName() 
{ 
while(!condition) 
wait(); 
statements to be executed when the condition is true 
notifyAll(); 
}

The normal and cleanest way to end a thread’s life is having the run() method return.

Method interrupt() can be used to interrupt a thread execution.

Every thread has a priority, which is a number between Thread.MIN_Priority and Thread.MAX_Priority. Threads with higher priority are executed in preference to threads with lower priority. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread.

When multiple threads are runnable (i.e. ready to be executed), the runtime system chooses the runnable thread with the highest priority for execution. Only when that thread stops, yields, or becomes not runnable for some reason a lower priority thread will start executing.

Java® platforms that support time-slicing allow each thread of equal priority to run by providing to all threads a limited amount of the processor’s time. On Java® platforms that do not support time-slicing a thread of a given priority runs to completion or until a higher (but not equal) priority thread becomes runnable. Method yield(), which is useful only on non-time-slicing systems, allows threads of the same priority to run.

Execution of multiple threads in some order is called scheduling. This is supported by  a very simple, deterministic scheduling algorithm (fixed priority scheduling) of the Java® runtime. This algorithm schedules threads based on their priority relative to other runnable threads. The Java® scheduler keeps the highest priority thread running at all times, and when time-slicing is supported allows equal high-priority threads to execute by giving slices of the processor time to them in sequence. In general, it is good practice to have the continuously running part of a program, or  threads that do frequent and continual updates set to MIN_PRIORITY to avoid blocking other threads.

Threads on multiprocessor machines utilize the multiple processors. A number of the highest-priority threads equal to the number of the available processors execute.

Someone can set and get the name of a threat using the functions setName() and getName(), respectively, of the Threat class. The priority can be set and got using the methods set and get methods of the Thread class.

The thread object of the currently running thread can be obtained using the currentThread() method of the Thread class.

Example

class MyRunnableClass implements Runnable 
{ 
String name;

MyRunnableClass(String str) 
{ 
name = str; 
}

public void run() 
{ 
for(int i=0; i<5;i++) 
{ 
System.out.println(“Thread: " + name); 
try 
{ 
Thread.sleep((int) (Math.random()*3)); 
} 
catch(InterruptedException e) 
{ 
System.out.println(“InterruptedException in " + name + " thread.”); 
} 
} 
}

}

class ThreadExample3 
{ 
public static void main(String args[]) 
{ 
MyRunnableClass  r1 = new MyRunnableClass(“Thread-1”); 
MyRunnableClass  r2 = new MyRunnableClass(“Thread-2”); 
MyRunnableClass  r3 = new MyRunnableClass(“Thread-3”);

Thread th1 = new Thread(r1); 
Thread th2 = new Thread(r2); 
Thread th3 = new Thread(r3);

th2.setPriority(10); 
th3.setPriority(1);

System.out.println(” th1 priority: " + th1.getPriority()); 
System.out.println(” th2 priority: " + th2.getPriority()); 
System.out.println(” th3 priority: " + th3.getPriority() + “\n”);

th3.start(); 
th1.start(); 
th2.start();

} 
}

Output:     > java ThreadExample3

th1 priority: 5 
th2 priority: 10 
th3 priority: 1

Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-2 
Thread: Thread-1 
Thread: Thread-1 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-1 
Thread: Thread-1 
Thread: Thread-3 
Thread: Thread-3 
Thread: Thread-3 
Thread: Thread-3

Java®’s garbage collector runs as a low-priority thread when processor time is available and when there are no higher-priority runnable threads.

Each thread may or may not be marked as a daemon. A daemon thread is a thread that runs in the background (when the processor is available) for the benefit of other threads. An example of a daemon thread is the garbage collector. When code running in some thread creates a new Thread object, the new thread is a daemon thread if and only if the creating thread is a daemon. Daemon threads do not prevent a program from terminating, since the program exits when only daemon threads remain in it. The Java® VM exits when the only threads running are all daemon threads. The function setDaemon(boolean on) can be used, before the thread is started, to mark a thread as a daemon thread or a user thread. If the argument to the setDaemon(boolean on) is true, the thread is marked as a daemon thread.

A s_elfish thread_ is a thread that never voluntarily gives up the control of the CPU, continuing running until either the thread’s run() method terminates, or until the thread is preempted by a higher priority thread. Time-slicing systems do not allow to a selfish thread to keep the control when there are other multiple runnable threads of equal priority, allowing the other threads to run.

In particular, someone should never rely on time-sharing and should write well behaved threads that periodically give up, voluntarily, the control of the CPU, giving to other threads an opportunity to run.

Race hazard, or race condition, is the situation in which two or more threads can modify the same data in a way that the data can be corrupted by inconsistent changes.

Starvation is an indefinite postponement of the execution of lower-priority threads due to higher-priority threads which do not give them the chance to run. Starvation occurs when one (or more threads) in a program cannot progress because it is blocked from gaining access to a certain resource.

Deadlock is the worst case of starvation, which occurs when two or more threads are waiting on a condition that cannot be satisfied, e.g. when two (or more) threads are each waiting for the action of the other(s). Race conditions can arise from multiple, asynchronously executing threads that are trying to access a single object (data) at the same time which may result on a wrong result and inconsistencies. Synchronization can be used to avoid such race problems.

When several concurrent threads are competing for resources, starvation and deadlock should be prevented. Each thread should get enough access to limited resource in order to reasonably progress without causing problems to the other threads or corrupting common data.

Synchronization

In some cases separate, concurrently running threads share data in a way that they must take into account the state of other threads. An example of such a case is the so-called producer/consumer scenario in which the “producer” thread generates data that are consumed by a “consumer” thread.

In such cases, problems can be avoided using synchronization, which allows a thread to lock an object and in case of another thread trying to invoke a synchronized method on the same object, the second thread will be blocked until the object is unlocked from the first thread. A lock is associated with every object that has synchronized code.

Critical code sections are code segments (e.g. a method) within a program that access the same data (e.g. object) from separate but concurrent threads. In the Java® language, the synchronized keyword is used to identify a critical section. Whenever a synchronized method of a class is invoked, the associated object is locked by that method. A synchronized method cannot be called on the same object until the object is unlocked, i.e. another thread cannot invoke a synchronized method on that object until the lock is released. When a synchronized method is invoked on an object it obtains its lock, not allowing any other thread to invoke any synchronized method until it releases the lock. A thread can voluntarily call wait(), which releases the lock and the processor, and wait in a queue while other threads are able to obtain the lock and invoke synchronized code. The methods notify() and notifyAll() can be used to signal to waiting threads to become ready again and attempt to obtain the lock. If waiting threads are not notified the threads will wait forever causing a deadlock.

Java® locks are reentrant, i.e. it is allowed to a thread to re-acquire a lock that it already holds. This avoids problems in which arise when a thread calls from a synchronized method of an object another synchronized method of the same object, which could lead into deadlock.

Static methods can also be synchronized. In that case a “class lock” for the corresponding class is used, which does not allow two threads to execute synchronized static methods on the same class at the same time.

A synchronized statement is an alternative, in some cases, way to execute synchronized code that locks the object so that no synchronized method can be invoked on that object, since it is locked. 
 

synchronized(objectTobeLocked) 
{ 
statements 
}

Thread Group

In Java® every Thread object is a member of a thread group, which allows to handle multiple threads, which belong in the same thread group, as a group. The ThreadGroup class is used to implement thread groups and allow to deal with the threads in a thread group as a group.

The Java® runtime system creates a ThreadGroup by default, named main, as soon as a Java® application starts. A newly created thread is placed in the same group with the thread group in which the thread that created it belongs. Therefore, unless specified otherwise, all new threads that are created become members of the main thread group by default. In an applet a newly created thread may belong to some other than main thread group depending on the browser. A thread cannot change thread group after its creation.

ThreadGroup  can contain any number of threads, which usually, however, are related in some way, and even other ThreadGroup objects. The top-most ThreadGroup in a Java® application is the ThreadGroup named main, which is the default thread group.

Method activeCount() gives the number of active threads in a thread group plus those in all its child thread groups. The ThreadGroup class provides a variety of methods to manage, and operate ThreadGroup objects and the Thread objects that are members of those thread groups. 
 

3. I/O

The System class, which is a final class and has private constructors (i.e. not allowing its extension), provides several useful class variables and methods. For example System.out is a PrintStream object that implements the standard output stream, and System.getProperty(String key) is a static method that returns the system property indicated by the specified key.

The System class provides:

  • in: the standard input stream to read input data (static InputStream object)
  • out: the standard output stream to output results (static PrintStream object)
  • err: the standard error stream to display error messages (static PrintStream object)

Both standard output and error, which are derived from the PrintStream class, can invoke one of the PrintStream’s methods print(), println(), and write() to print text to the stream. The latter method, which is not that common is used to write non-ASCII data, i.e. to write bytes to the stream.

The java.io package provides a collection of stream classes that support reading from and writing to an external destination. The provided classes operate on either characters or byte data types.

The abstract superclasses for character streams in java.io are the Reader (abstract class for reading character streams) and the Writer (abstract class for writing to character streams) classes, which partially provide the functionality for the characters reader and writer stream classes, respectively. [The following inserted figures have been adapted from the on-line Java® Tutorial of SUN]

In general, readers and writers should be preferred to read and write information since they can handle any character in the Unicode character set because they use 16-bits per character.

Similarly, the abstract superclasses for byte streams in java.io are the InputStream (abstract class for reading byte streams) and the OutputStream (abstract class for writing to byte streams) classes.

Reader and InputStream provide methods for reading characters and bytes, respectively. They also provide methods for marking a location in the stream, skipping input, and resetting the current position. Similarly, Writer and OutputStream provide methods for writing characters and bytes, respectively.

All readers, input streams, writers, and output streams are automatically opened upon the creation of the corresponding objects. Although they are eligible to be closed by the garbage collector, as soon as they are not referenced in any way, they can be explicitly closed by calling their close() method.

The above stream classes can be categorized into two groups: one that deals with reading and writing data, and another that performs some kind of operation on the data while reading and writing. The former are the ones shown with gray color in the above figures, while the latter are the ones shown in white.

A useful class provided by the java.io package is the StreamTokenizer class. A StreamTokenizer object can be created using as argument to its constructor call an InputStreamReader object. Then, the StreamTokenizer object may be used on an input stream to parses it into tokens, which it reads one at a time.

4. Introduction to Java® GUI and Swing

Java® Foundation Classes(JFC) provide features to facilitate the development of Graphical User Interfaces (GUI_s). Among other, JFC provides Swing*_,* which includes several components that can be used for the development of GUIs, support for a choice on the look and feel that a program uses, and provides Java®2D for high quality 2D graphics. Finally, swing allows the specification which look and feel a program should use. This is done with the setLookAndFeel() method of the UIManager class. Swing is built on top of the AWT (Abstract Window Toolkit) using the AWT infrastructure. However, Swing provides its own graphical user interface (GUI) components, many of which have a close relation or correspondence to the AWT components. Essentially, Swing is an extension and improvement to the AWT.

JFC 1.1 (with Swing API 1.1) is built into JDK 1.2_._ The latest JFC development is the JFC/Swing portion of JDK 1.3, which is code-named Kestrel and it was released this year.

The Swing API is provided in the following Swing packages:

  •  javax.swing   (the main swing package)
  •  javax.swing.border
  • javax.swing.colorchooser
  • javax.swing.event
  • javax.swing.filechooser
  • javax.swing.plaf
  • javax.swing.plaf.basic
  • javax.swing.plaf.metal
  • javax.swing.plaf.multi
  • javax.swing.table
  • javax.swing.text
  • javax.swing.text.html
  • javax.swing.text.html.parser
  • javax.swing.text.rtf
  • javax.swing.tree
  • javax.swing.undo

The AWT (Abstract Window Toolkit) was first used to develop GUI’s in Java®, and provided the foundation on which the JFC and swing were built. The main difference between AWT and Swing is that the components of the latter are implemented without any native code. Because of this, swing components are called lightweight, while AWT components, which use native code, are called heavyweight components. Lightweight components are drawn entirely using Java®, while heavyweight components use native peers. Actually, AWT 1.1 also introduced some lightweight components, while earlier versions were based solely on heavyweight components. Swing components, in general, have much more capabilities than the AWT corresponding ones. For instance some Swing components (such as labels and buttons) can display icons, while the corresponding AWT components cannot. Swing provides a number of additional components that were not provided by AWT.

Swing provides several standard components (e.g. buttons, checkbuttons, radiobuttons, menus, lists, labels, and text areas) to create a program’s GUI using one or more containers (e.g. frames, dialogs, windows and tool bars). Most Swing components that begin with J, except the top-level containers, are subclasses of the JComponent class. The letter J is used to differentiate the actual extra user interface classes provided by Swing from the support classes that it provides. Swing components inherit many features from the JComponent class, such as a configurable look and feel, borders, and tool tips, as well as many methods. In addition, some Swing components can display images on them. The JComponent class extends the Container class (provides support for adding and laying out components), which itself extends the Component class (provides support for  painting, events, layout etc.). JComponent is the base class for almost all lightweight J components. Therefore, all swing lightweight components (derived from the JComponent class) are subclasses of the Container class of AWT.

The following article, by Bill Harlan, provides some useful information about Improving Swing Performance.

Containers

Every program with a Swing GUI contains at least one top-level Swing container, i.e., in general, an instance of a JAppletJFrame, or JDialog, which enables the painting and event handling of the Swing components. The JAppletJFrame, or JDialog, are considered top-level containers because they are used to provide an area in which the other containers and components can appear. Other containers, such as the JPanel, are used to facilitate the positioning and sizing of other containers and components. Between a top-level container and an intermediate container, a content pane (JRootPane), which is also an intermediate container, is indirectly provided. The content pane contains all of visible components of its GUI, except a menu bar. One of the add() methods of a container can be used to add a component to it. The component to be added is used as argument to the add method. In some cases another argument, which has to do with the layout, is used with the add() method.

Besides the top-level containers (JAppletJFrameJDialog, and JWindow) there are special-purpose containers (such as the JInternalFrameJLayeredPane, and JRootPane) that are used for special purposes, and general-purpose containers (such as JPanelJScroll PaneJSplitPaneJTabbedPane, and JToolBar) that are used for other any general purpose.

The JApplet and JFrame classes should be used to implement Swing applets or applications, respectively. Both JApplet and JFrame classes are containers that contain an instance of the JRootPane class. The latter contains the content pane, which is a container, that contains all the components contained in the applet or application. Therefore, components should be added and layout managers should be set to the content pane.

Atomic Components

There are many atomic components (i.e. components that exist as self-sufficient entities and not to hold other Swing components). Atomic components can be grouped into 3 categories:

  • Basic Controls, which are components that can primarily be used to get input from the user (such as JSlider_, _ JTextField_, _ JButton_, _ JComboBox_, _ JList_,_ and JMenu)
  • Uneditable Information Displays, which are used to display information to the user (such as JProgressBarJLabel_,and _JToolTip)
  • Editable Displays of Formatted Information, which are components that can be used to display formatted information that can be editable by the user (such as JColorChooser_, _ JTextComponent_, _ JFileChooser_, _ JTable, and JTree)

Applets and Applications

Swing applets use the JApplet class which is a subclass of the AWT Applet class and implements the Accessible and RootPaneContainer interfaces. The following simple example shows an applet with a single component a JLabel_._ The BorderLayout manager is used by the JApplet for its content pane (which is used in both Swing applets and applications), in contrast to the FlowLayout manager used by the Applet class. 
 

SwingApplet1.java

import java.awt.*; 
import javax.swing.*;

public class SwingApplet1 extends JApplet 
{ 
public void init() 
{ 
Icon icon = new ImageIcon(“1.124.gif”, “1.124 GIF”); 
JLabel label = new JLabel(” Test”, icon, SwingConstants.CENTER); 
getContentPane().add(label); 
} 
}

The above applet can be executed using an html file like the following:

SwingApplet1.html

<html> 
<head> 
<title>JApplet Example # 1</title> 
</head> 
<body>

<center> 
<h2> 
<u>JApplet Example # 1</u></h2> 
<hr><applet code=“SwingApplet1.class” WIDTH=“486” HEIGHT=“94”></applet> 
<hr></center>

<p><br> 
</body> 
</html>

Then, appletviewer (using the command: appletviewer SwingApplet1.html) displays the following window:

Similarly, the JFrame class can be used to create a Swing application similar to the above applet. The JFrame class is a subclass of the AWT   Frame class and implements the Accessible_,_ WindowConstants_,_ and RootPaneContainer interfaces. It uses the BorderLayout manager for its content pane. The source code is presented below:

SwingApplication1.java

import java.awt.*; 
import javax.swing.*; 
import java.awt.event.*;

public class SwingApplication1 extends JFrame 
{

public SwingApplication1() 
{ 
super(“Swing Application”); 
Icon icon = new ImageIcon(“1.124.gif”, “1.124 GIF”); 
JLabel label = new JLabel(” Test”, icon, SwingConstants.CENTER); 
getContentPane().add(label); 
} 
 

public static void main(String args[]) 
{ 
final JFrame jfr = new SwingApplication1();

jfr.setBounds(100,50,500,150); 
jfr.setVisible(true);

jfr.setDefaultCloseOperation(DISPOSE_ON_CLOSE);

jfr.addWindowListener(new WindowAdapter() 
& #160;       { 
& #160;              public void windowClosed(WindowEvent e) 
& #160;                   { 
& #160;                     ; System.exit(0); 
& #160;                   } 
& #160;            }              ); 
}

}

As any Java® application, a main() method must be provided. In main()JFrame is instantiated, sized, and made visible. The default close operation is set to DISPOSE_ON_CLOSE (to dispose any native resources when the window is closed), and a window listener is added in order to exit the application as soon as the frame is closed.

Running the Java® interpreter (using java SwingApplication1) will execute the program and give the following window:

Often, the source code file can be written in a way that it can be used both as an applet and an application. The above example is rewritten in way to facilitate such a use. The source code is provided below:

AppletApplication1.java

import java.awt.*; 
import javax.swing.*; 
import java.awt.event.*;

public class AppletApplication1 extends JApplet 
{

public void init() 
{ 
Icon icon = new ImageIcon(“1.124.gif”, “1.124 GIF”); 
JLabel label = new JLabel(” Test”, icon, SwingConstants.CENTER); 
getContentPane().add(label); 
} 
 

public static void main(String args[]) 
{ 
final JFrame myFrame = new SwingApplication1(); 
JApplet myApplet = new AppletApplication1(); 
myApplet.init();

myFrame.setContentPane(myApplet.getContentPane()); 
myFrame.setBounds(100,50,500,150); 
myFrame.setVisible(true);

myFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

myFrame.addWindowListener(new WindowAdapter() 
& #160;          { 
& #160;             public void windowClosed(WindowEvent e) 
& #160;                  { 
& #160;                     ; System.exit(0); 
& #160;                  } 
& #160;          }                    ); 
}

}

Mixing AWT and Swing

Attention should be given when mixing AWT and Swing components, since when lightweight components overlap with heavyweight components, the heavyweight components are always painted on top. In general, it should mixing AWT and Swing components should not be mixed whenever possible. The “depth” at which components are displayed in a container is represented by the Z-order. The latter is determined by the order with which each component is added to the container, i.e. the first component to be added to a container has the highest Z-order which means that it is displayed in front of all other components added to that container. When lightweight and heavyweight components are mixed, the lightweight components, which need to reside in a heavyweight container, have the same Z-order of their container, and within it the order of each of the lightweight components is determined by the order in which they are added to the container. Note, that whenever a container is extended and the paint() method is overridden, the superclass paint() method should be explicitly invoked using super.paint() to force drawing of the lightweight components.

When Swing popup menus, which are lightweight, are mixed with a heavyweight component, the latter overlaps the former. This can be avoided by forcing the popup menus to be heavyweight using the method setLightWeightPopupEnabled() of the JPopupMenu class. A similar problem happens with a JScrollPane instance when any heavyweight components to it. Because there is no option of setting the JScrollPane as a heavyweight, the AWT ScrollPane can be used instead, which works fine with both lightweight and heavyweight components. Finally, heavyweight components should be avoided (i.e. not be added) to Swing internal frames, i.e. to a JInternalFrame instance.

Layout Managers

Layout managers (such as the BorderLayout_, _ BoxLayout_,_ GridLayout_, _ FlowLayout_, _ CardLayout_, _ GridBagLayout) are used to determine the size and position of the components that are contained in a container. Each container is provided a default layout manager, which, however, can be changed using the setLayout() method and as an argument a newly created instance of the preferred layout manager. The minimum, preferred, and maximum sizes of a component can also be specified to the layout manager, as well as the preferred alignment.

Painting System

The AWT painting system controls the painting of a Swing GUI, starting with the highest component that needs to be repainted and going down the hierarchy of containers, using the event-dispatching thread. The painting of swing components occurs whenever necessary. Swing uses double buffering to improve the efficiency and quality of the provided GUI. During painting, each component paints itself before any of the components that it contains. A Swing top-level container is painted on-screen using one of the methods: setVisible(true), show(), or pack(). Painting starts with the highest component that needs to be repainted and moving down the containment hierarchy, i.e. each component paints itself before any of the components it contains.

Swing performs, by default, double-buffered to ensure smoothness and avoid flushing. Double buffering is essentially implemented by an offscreen buffer in which all painting takes place, and then it is flushed to the screen.

Event Handling

The event handling features of the Swing (and AWT) components allow to a Java® program to respond to external events, such as the interaction of the user with the GUI. Events are handled by event handlers that are registered with event sources. Any Swing component can be notified for the occurrence of any event simply by implement the corresponding interface and registering it as an event listener on a relevant event source. The latter is usually component, e.g. a button. for each event there is a corresponding object which provides all the relevant information. An event handler is implemented by:

  • either
    • implementing the corresponding listener interface, or
    • extending a class that implements that interface
  • implement the method in the listener (interface or subclass)
  • adding that kind of listener to a component

Sometimes, anonymous inner classes are used to keep the code clearer and the event handler closer to the point where it is registered. The event-handling code executes in the event-dispatching thread (single thread) ensuring that each event handler will execute in sequence allowing a previous event handler finish executing before the next one starts execution. Therefore, the code in the event handlers should be either very short and quick to execute, or another thread should be initiated to execute the code, in order to not harm the performance of the program. Otherwise, painting, which also executes on the event-dispatching thread, will not occur while an event is being handled.

Threads

Swing, in general, is not thread safe, because a Swing component can be accessed by only one thread at a time, which, in general, is the event-dispatching thread. Because Swing is not thread safe, Swing components, in general, should be accessed only from the event-dispatching thread. The event-dispatching thread is the thread that invokes callback methods (e.g. paint() and update() functions) and event handler methods. Swing has been designed to be not thread safe in order to avoid the overhead of multithreading (e.g. obtaining and releasing locks) and to simplify the subclassing of its component. It is not allowed to access Swing components from any thread other than the event-dispatching thread.

However, some Swing components support multithread access. Actually after the realization of a Swing component code that might affect or depend on the state of that component should be executed in the event-dispatching thread. Realization of a component means after the component is available for painting on screen, after it is painted or become ready to be painted using one of the methods: setVisible(true), show(), or pack().

The access only through the event-dispatching thread is not required in the following cases:

  • when dealing with thread safe methods (as specified in the Swing API documentation) to construct and show a GUI in the main thread of  an application, as long as  no components have been realized in the current runtime environment
  • constructing and manipulating the GUI in an applet’s init() method, as long as the components have not been made visible, i.e. the method show() or setVisible(true) has never been called on the actual applet object
  • methods repaint() and revalidate() are safe to call from any thread

In general, Swing it is not safe to access Swing components from any thread other than the event-dispatching thread. However, there are times that it is preferable to update Swing components from another thread, or perform time-consuming operations on a separate thread, and not use the event-dispatching thread. In those cases, Swing provides the methods invokeLater() and invokeAndWait() in the SwingUtilities class, which can be used to queue a runnable object on the event dispatch thread. They essentially allow a block of code from another thread to be invoked and executed by the event-dispatching thread. Both methods can be used to access Swing components from a thread other than the event-dispatching thread. Method invokeLater() queues the runnable object and returns immediately, while invok eAndWait() waits until the runnable object’s run() method has started before returning. Only the invokeLater() (and not invokeAndWait()) method can be called from the event dispatching thread.

These notes were prepared by Petros Komodromos.

Topics

  1. The JComponent class
  2. Top-Level Containers
  3. Intermediate Swing Containers
  4. Atomic Components

In this last recitation more information is provided for Swing components.

1. The JComponent Class

Most Swing components that begin with J, except the top-level containers, are subclasses of the JComponent class. The letter J is used to differentiate the actual extra user interface classes provided by Swing from the support classes that it provides. Swing components inherit many features from the JComponent class, such as a configurable look and feel, borders, and tool tips, as well as many methods. In addition, some Swing components can display images on them. The JComponent class is the base class for almost all lightweight Jcomponents. The JComponent class extends the Container class (provides support for adding and laying out components), which in turn extends the Component class (provides support for painting, events, layout etc.). Therefore, all Swing J-components are AWT containers and inherit all methods from the Container and Component classes. Any instance of a JComponent subclass can contain both AWT and Swing components, since JComponent extends the java.awt.Container class.

The on-line JFC/Swing tutorial provides a summary of the following methods of the JComponent class:

The JComponent class provides to its subclasses the following functionalities:

  • Borders: Any instance of a JComponent subclass can be fitted in one of the several different border styles, such as custom, compound, etched, etc.
  • Double buffering: Flickering is avoided by using an offscreen buffer (which is maintained by the RepaintManager) to update components before displaying them on screen.
  • Graphics debugging support: Method setDebugGraphicsOptions() of the JComponent class facilitates debugging of graphics by slow motion painting with flushing before each graphics call and with optional diagnostic information.
  • Autoscrolling: All JComponent subclasses can autoscroll, i.e. scroll their contents when the mouse is dragged outside their bounds. Autoscrolling is activated, or deactivated, using the method setAutoscrolls() of the JComponent class.
  • Tooltips: Strings describing the functionality of a JComponent subclass object can be displayed above it, providing help to the user of the component, whenever the cursor rests over it based on timing characteristics specified by the ToolTipManager class. The method setToolTipText(String text) of the JComponent class is used to associate a tooltip with a Swing component.
  • Keystroke handling support: Keystrokes can be specified for JComponent subclass objects, using the method registerKeyboardAction_(ActionListener anAction, String aCommand, KeyStroke aKeyStroke, int aCondition)._ Then, when the specified keystroke is typed, the specified ActionListener’s ActionPerformed() method is invoked if the conditions specified by the last parameter are satisfied.
  • Focus management: Pressing the Tab-key in a Swing container, by default, moves focus to the next focusable component. Modifying the JComponent focus properties or replacing the default focus manager allows the modification of the default focus behavior.
  • Improved layout support: the JComponent class add setter (and getter) methods to set (and get) some size requests to (from) the layout manager, such as the setPreferredSize(), setMinimumSize(), setMaximumSize(), setAlignmentX(), and setAlignmentY() methods.
  • Pluggable look and feel: A corresponding, to each JComponent object, ComponentUI object performs all the drawing and event handling using the current look and feel, which can be set using the UIManager.setLookAndFeel() method.
  • Accessibility support: Assistive technology is built into Swing components, enabling software accessibility to everyone, e.g. using magnifying fonts, audio look and feel alternative.

All lightweight Swing components extends the JComponent class, which keeps a reference to a corresponding component UI, or UI delegate. The name of a class delegate is derived from the name of the component by removing the J from the front and adding UI at the end. The UI delegate is responsible for the look and feel and the events handling of a Swing component, e.g. the delegate of the class JLabel is the LabelUI.

For the rendering of lightweight Swing components, the paint() method of the JComponent class is used. In particular, a Graphics object is passed to the paint() method in which it draws the component, the component’s border, and the component’s children in order. When the component has a UI delegate, the delegate’s paint() method is invoked, which clears the background in case of an opaque component, and, then, paints the component. For double buffered components it paints the component into an offscreen buffer and then copies it into the component’s onscreen area. Since double buffered is provided by Swing there is no need to override the paint() method, as it is for AWT to achieve avoid flickering. To redefine the way a component is painted the paintComponent() method of the JComponent class should be overwritten, which usually needs to invoke the super.paintComponent() method.

In contrast to AWT components, it is not necessary to override the update() method, which erases the background of a component and then invokes paint(), to avoid flickering. The JComponent class overrides the update() method invoking directly the paint() method, while the UI delegate is responsible for erasing the background. The flickering is avoided since double buffering is used by the Swing components, i.e. the components are repainted first in an offscreen buffer and, then, copied to the screen. Only JRootPane and Jpanel of the Swing lightweight components are by default double buffered. Components that reside in a double buffered container are automatically double buffered. The method setDoubleBuffered() of the JComponent class can be used to set whether the receiving component should use a buffer to paint. The offscreen buffer used for the double buffering can be obtained using the method RepaintManager.getOffscreenBuffer().

The validate() method of the Container class positions and sizes the components of a container. The revalidate() method of the JComponent class should be invoked by any Swing component that is modified in order to invalidate the component and then invoke the validate() method using the event dispatch thread, which result in the component to be positioned and sized properly. Sometimes, repaint() should also be called after invoking revalidate().

A Swing lightweight component can be opaque, i.e. its background is filled with the component’s background color, or partially transparent, i.e. has transparent background. Swing components are by default opaque, which can be changed using the setOpaque(false) method.

The Graphics subclasss DebugGraphics supports graphics debugging by slowing the rate of graphical operations and flushing prior to each operation. To use graphics debugging the setDebugGraphicsOptions() of the JComponent class should be invoked with one of the debugging options:

  • DebugGraphics.LOG_OPTION: causes a text message to be printed.
  • DebugGraphics.FLASH_OPTION: causes the drawing to flash several times.
  • DebugGraphics.BUFFERED_OPTION: creates an ExternalWindow that displays the operations performed on the View’s offscreen buffer.
  • DebugGraphics.NONE_OPTION: the graphics debugging feature is not used

2. Top-Level Containers

A GUI component must be part of a hierarchy with a top-level container as its root to be displayed onscreen. The top-level container classes provided by Swing are the following:

  • JFrame: A top-level window with a title and a border.
  • JDialog: The main class for creating a dialog window.
  • JApplet: Enables applets to use Swing components.
  • JWindow: A not, in general, useful window with no controls or title.

A standalone application with Swing components uses at least one containment hierarchy with a JFrame object as its root, while a Swing-based applet uses a containment hierarchy with a JApplet object at its root. Menu bars usually are added, using the setJMenuBar() method, only in frames (JFrame_)_ and in applets (JApplet_)_.

A top-level container has a content pane, which contains the visible components, and, optionally, a menu bar that is positioned within the top-level container, but outside the content pane. To add a component to a container the method getContentPane() can be used to obtain the content pane to which the component can be added using the add() method. The default content pane is a simple intermediate container that extends JComponent, and uses a BorderLayout as its layout manager. Adding/removing components, setting layout managers, adding borders, etc. are invoked on the content pane, which is a field of the JRootpane class. The getContentPane() method returns a Container object, not a JComponent object, which means that to be able to use the content pane’s JComponent features, either the return value must be typecast, or a (opaque) component to be the content pane must be created and used (using the setContentPane method()).
 

Frames: JFrame

A JFrame object is essentially a window with some additional decorations and functionalities, such as a title, a border, and closing and iconifying buttons.

The following program creates a JFrame object that contains a JLabel object.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class FrameApplication1
{
public static void main(String args[])
{
JFrame jfr = new JFrame(“My JFrame”);

jfr.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);
}
} );

jfr.setBounds(100,100,800,200);

JLabel myLabel = new JLabel(" 1.124J: Foundations of Software Engineering “);
myLabel.setPreferredSize(new Dimension(300,80));
jfr.getContentPane().add(myLabel, BorderLayout.CENTER);

jfr.pack();
jfr.setVisible(true);
}

}

Executing the above Java® application (java FrameApplication1) opens the following window with its left top corner at the location (100,100):

Applets: JApplet

Any applet that contains Swing components must be implemented using the JApplet or any subclass of the JApplet class. Applets are typically loaded by web browsers based on references inserted in html files. Upon the creation of an applet, its init() method is called. An applet does not have a close button or a title bar. The browser calls the start() method after calling the init() method, but also every time the page that contains the applet is revisited. The size of an applet is determined by the browser (or the appletviewer) using the size request provided in the corresponding html file.

Each Swing applet has a root pane (JRootPane) which provides additional functionalities, such as support for a menu bar. Components should be added and layout managers should be set to the root pane and not the applet itself. The default layout manager for the content pane of a Swing applet is the BorderLayout, and not the FlowLayout, which is the default layout manager for AWT Applet.

The following Java® source code demonstrates the use of JApplet to create the simplest possible applet. It is followed by the html file, which can be used to load the corresponding class file (TestingJApplet.class). Finally, a snapshot of the resulting window that appears, when the appletviewer is used to load the html file, is presented.

TestingJApplet.java

import java.awt.*;
import javax.swing.*;

public class TestingJApplet extends JApplet
{
public void init()
{
Icon icon = new ImageIcon(“1.124.gif”, “1.124 GIF”);
JLabel label = new JLabel(” Testing JApplet",
icon, SwingConstants.CENTER);
getContentPane().add(label);
}

}

TestingJApplet.html

<html>
<head>
<title>JApplet Testing # 1</title>
</head>
<body>

<center>
<h2>
<u>JApplet Example # 1</u></h2>
<hr><Applet code=“TestingJApplet.class” WIDTH=“500” HEIGHT=“100”></Applet>
<hr></center>

<p><br>
</body>
</html>

> appletviewer TestingJApplet.html

Dialogs: JDialog

Class JDialog extends the AWT Dialog class and implements the WindowConstants, Accessible, and RootPaneContainer interfaces. The JDialog class can be used to create a window that dependents on another window, e.g. to disappear/appear whenever the other window is iconified/deicognified, and has a border and a title bar. The JDialog class, which extends the Dialog class by adding a root pane and support for a default close operation, can be used to create a custom dialog. It is a heavyweight Swing container that contains a JRootPane instance to which components are added and layout managers are set.

To create a dialog using directly the JDialog class requires the creation and laying out of the components as well as creating of the buttons and listeners necessary to dismiss the dialog. The JDialog class has a JOptionPane child, which provides many static methods that can be used to create a variety of standard dialogs. A dialog provided by the JOptionPane is modal, i.e. when it is visible it blocks user input to all other windows in the program. The JDialog class must be used to create a non-modal dialog.

The following example is a Java® application which provides a button in a frame. Whenever the button is pushed a non-modal dialog is created as shown in the snapshot of the program execution, which follows the source code:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class DialogApplication1
{
public static void main(String args[])
{
MyJFrame jfr = new MyJFrame(“My JFrame”);

jfr.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.out.println( “Exiting the program”);
System.exit(0);
}
} );

jfr.setBounds(100,100,800,200);
jfr.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

JButton myButton = new JButton(“Show JDialog instance”);
myButton.setPreferredSize(new Dimension(300,80));
myButton.addActionListener(jfr);
jfr.getContentPane().add(myButton, BorderLayout.CENTER);

jfr.pack();
jfr.setVisible(true);
}

}
 

class MyJFrame extends JFrame implements ActionListener
{
public MyJFrame(String title)
{
super(title);
}

public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals(“Show JDialog instance”))
{
System.out.println(“Button has been pushed causing " +
“the appearance of a JDialog”);
JDialog dialog = new JDialog(this, “A Non-Modal Dialog”);
JLabel myLabel = new JLabel(” 1.124J: Foundations of Software Engineering “);
myLabel.setPreferredSize(new Dimension(300,80));
dialog.getContentPane().add(myLabel, BorderLayout.CENTER);

dialog.pack();
dialog.setVisible(true);
}
}
}

Option panes are components that can be placed in dialox boxes. The JOptionPane class allows the creation and customization of several different kinds of dialogs, such as support for laying out standard dialogs, specifying the dialog’s title and text, providing icons, customizing the button text and the components the dialog displays, and specifying where the dialog should appear onscreen. Any one of four standard JOptionPane icons (which indicate question, information, warning, and error) can be displayed by the dialog.

The JOptionPane class allows the easy pop up of a standard dialog box, providing the following methods:

  • showOptionDialog(): displays a customized dialog with a variety of buttons, or a collection of components, allowing a selection from a set of options
  • showMessageDialog(): provides a simple message with a button to dismiss it,
  • showConfirmDialog(): asks for the user’s confirmation providing a number of choices.
  • showInputDialog: Shows a question-message dialog requesting input from the user.

The Java® application with the following source code uses the showMessageDialog() method of the JOptionPane class to create a message dialog as it is shown in the snapshot following the source code:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class DialogApplication2
{
public static void main(String args[])
{
MyJFrame jfr = new MyJFrame(“My JFrame”);

jfr.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.out.println( “Exiting the program”);
System.exit(0);
}
} );

jfr.setBounds(100,100,800,200);
jfr.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

JButton myButton = new JButton(“Show JDialog instance”);
myButton.setPreferredSize(new Dimension(300,80));
myButton.addActionListener(jfr);
jfr.getContentPane().add(myButton, BorderLayout.CENTER);

jfr.pack();
jfr.setVisible(true);
}

}
 

class MyJFrame extends JFrame implements ActionListener
{
public MyJFrame(String title)
{
super(title);
}

public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals(“Show JDialog instance”))
{
System.out.println(“Button has been pushed causing the invocation” +
“of the JOptionPane.showMessageDialog()method”);
JOptionPane.showMessageDialog(this, “Testing the showMessageDialog() method”);
}
}
}

The JFC/Swing Tutorial provided by Sun has a series of tables with details on the:

Windows: JWindow

JWindow, which extends AWT Window class, provides a window with no resizing controls, border, menu bar, or title that is always on top of every other window. A JWindow instance can be used to display something in a rectangular region (window) without any title or menus that appears on top of other components.

Although an instance of JWindow is a heavyweight component, i.e. it does not inherit capabilities of the JComponent class, it contains an instance of JRootPane, which can be manipulated as any other lightweight component. Actually the main difference of JWindow, from the AWT Window is that the former contains a root pane to which the components should be added and layout managers should be set.

The following program creates a JWindow instance with a JButton instance with an icon on it. When the mouse clicks on the button the actionPerformed() method is called and the program exits after a short message is printed.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class WindowApplication1
{
public static void main(String args[])
{
MyWindow window = new MyWindow();

Icon icon = new ImageIcon(“1.124.gif”, “1.124 GIF”);
JButton myButton = new JButton(“Exit”, icon);
myButton.setPreferredSize(new Dimension(300,80));
myButton.addActionListener(window);
window.getContentPane().add(myButton);

window.setBounds(100,50,500,150);
window.setVisible(true);
}

}

class MyWindow extends JWindow implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals(“Exit”))
{
System.out.println( “Exiting the program”);
System.exit(0);
}
}
}

The execution of the above application gives a window that looks like the following:

3 . Intermediate Swing Containers

Intermediate Swing components are non-top-level containers that are used to contain other components.

Panels: JPanel

A panel, which is implemented using the JPanel class, is the most frequently used intermediate container. The JPanel class extends the JComponent class, which provides among others double buffering support, and implements the Accessible interface. A JLabel object can display text, an image, or both, but it does not react to input events. Being able to be used as a general-purpose container for lightweight components, but also as a component (e.g. as a canvas) to which text and graphics can be rendered, it is, essentially, the successor of the AWT’s Panel and Canvas classes. A component can be made non-opaque (i.e. transparent) by invoking setOpaque(false) method. The default layout manager of a JPanel object is an instance of the FlowLayout class, which places the panel’s contents in a row. To add components to a panel, the add() method is used.

Example of JPanel

import javax.swing.*;
import java.awt.event.*;

public class MyJPanel extends JPanel
{
MyJPanel()
{
setBackground(Color.cyan);
setPreferredSize(new Dimension(200,200));
}
 

public void paintComponent(Graphics g)
{
super.paintComponent(g);

g.setColor(Color.red);
g.drawLine(100,100, 350,200);
g.setColor(Color.blue);
g.drawRect(200,50, 50, 20);
g.setColor(Color.black);
g.fillRect(20,150, 90, 150);
g.drawString(“Testing JPanel and Graphics”, 120, 250);
}

public static void main(String args[])
{
final JFrame jfr = new JFrame(“JPanel Example”);

JPanel panel = new MyJPanel();

jfr.getContentPane().add(panel, BorderLayout.CENTER);

jfr.setBounds(200,100,600,400);

jfr.setVisible(true);

jfr.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

jfr.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);
}
} );
}

}

The above example, when executed, gives the following window:

Internal Frames: JInternalFrame

Internal frames are lightweight components to which heavyweight components should not be added. An internal frame is an instance of the JInternalFrame class that provides many of the features of a native frame, such as closing, iconification, title displaying, dragging, resizing, and menu-bar support. Typically, internal frames are contained in JDesktopPane instances.

Components are added and layout managers are set on the content pane of the JInternalFrame. An internal frame needs to be added to a container, since it is not a top-level container. As with a regular frame, the setVisible(true), or the show() method should be invoked on an internal frame to display it.

The size of an internal frame must be set, e.g. using any of the setSize(), pack(), or setBounds() methods, since by default has zero size being invisible. The location of an internal frame may need to be set, since the default is at (0,0), i.e. at the upper left of its container. JInternalFrame, or JOptionPane, should be used to implement Dialogs.

Internal frames can be used in combination with the JDesktopPane to implement Multiple Document Interface (MDI) applications, in which a window acts as a desktop for documents created in the application. Class JDesktopPane extends the JLayeredPane class and implements the Accessible interface. Desktop panes are intended to contain internal frames allowing the development of MDI applications. Desktop managers, which are instances of the DesktopManager, implement the look-and-feel-specific behavior of desktop panes.
 

Root Panes: JRootPane

A JRootPane is contained in all Swing top-level containers. It is a fundamental component in the container hierarchy, providing to the top-level (heavyweight) containers (JFrame, JDialog, JWindow, and JApplet) the JComponent’s capabilities. Not only these heavyweight, but also the lightweight container JInternalFrame, delegate their operations to a JRootPane instance, which is automatically created as soon as any of these containers is instantiated. Therefore, almost all Swing components reside in a JRootPane instance. The interface RootPaneContainer is implemented by components that have a single JRootPane child: JFrame, JDialog, JWindow, JApplet and JInternalFrame.

A root pane consists of the following components, as shown in the following figure (adapted from the Sun’s Java® Tutorial):

  • Glass pane: The glass pane is the topmost component in a root pane. It is hidden, by default, unless it is made visible. It’s also completely transparent unless its paint() method is overwritten. The glass pane, when it is visible, can trap mouse events. If it expresses interest in handling mouse events, by adding a mouse listener or enabling mouse events, it blocks all input events from reaching the components in the content pane, since it is the topmost lightweight component in its container. The glass pane can also be used to paint over an area that already contains one or more components.
  • Layered pane : The layered pane, which is underneath the glass pane, can be used to place components on separate layers. The layered pane serves to position its contents, and, optionally, in a specified z-order. A JLayeredPane allows to the components that it contains to be placed in specific layers with which the depth at which the components are displayed is controlled. Each layer is assigned a specific numerical value, which can be explicitly set using the setLayer() method of the JLayeredPane class. Also, the relative depth of a component with respect to other components on the same layer can be specified using the setPosition() method. Positions are specified using an integer between -1 and the number of components at that depth minus one. Overlapping components can appear one on top of the other, displaying the layers with higher values in front of those with lower values. JLayeredPane class specifies certain values to some specific layers, as described in the corresponding API documentation. It does not help to add a heavyweight component in a JLayeredPane, since a heavyweight component is always displayed above a lightweight component. A JLayeredPane instance contains, in turn, a content pane and an optional menu bar.
  • Content pane: It is the container of the root pane’s visible components, it contains applet/application’s components.
  • Optional menu bar: It is used for the root pane’s container’s menus.

The z-order of components is determined by the order with which they are added to their container. The first component added to a container is displayed on top of all other components.

The RootPaneContainer interface provides assessor methods for the JRootPane and its contents (glass, layered, and content pane and optional menu bar).
 

Scroll Panes: JScrollPane and JViewport

For scrolling Swing has two lightweight containers, the JScrollPane and the JViewport, one interface, named Scrollable, and one component named JScrollBar.

The JScrollPane is a container that can be used (instead of the AWT ScrollPane ) to display a component that is larger than the available display space. It extends the JComponent class and implements the ScrollPaneConstants, and Accessible interfaces. It contains a JViewport instance. It controls the latter as well as the optional vertical and horizontal scrollbars, and optional row and column header viewports. Using the scrollbars the viewport’s view and the row and column headers can be scrolled. There are, also, corner components, which are by default instances of JPanel with background color that of the scrollpane background.

The following figure (taken from the Java® 2 API) shows the JScrollPane components.

The JViewport is rarely instantiated and used directly. It is used, as shown above, by JScrollPane instances, through which a particular region of a view is displayed. The position of the view displayed by a viewport can be modified in order to allow to different regions of the view to be selectively displayed.

The JScrollBar class can be used in some special cases where it is necessary to implement manual scrolling because the provided scrolling model may not be satisfactory.
 

Split Panes: JSplitPane

A JSplitPane contains two components separated by a divider, which can be dragged and its position be adjusted. These two components, contained in the JSplitPane can be oriented either vertically (one on top of the other) or horizontally (side by side).
 

Tabbed Panes: JTabbedPane

A JTabbedPane allows the selection of a component, from the several (usually panels) it may contain, to be displayed. It enables the user to switch between a group of components by clicking on a tab with a given title and/or icon. Which component to be viewed is specified by selecting the tab corresponding to the desired component.
 

Tool Bars: JToolBar

The Toolbar class, which represents Swing toolbars, is a container that groups several components (of any kind) vertically or horizontally into a column or row, respectively. The user can drag the tool bar to a different edge of its container or out into its own window (unless the floatable property is set to false, making the tool bar immovable).

4. Atomic Components

Atomic components, are components that do not exist to contain any other components. They are subclasses of the JComponent class.

Buttons: JButton

Class JButton extends the AbstractButton class and implements the Accessible interface, providing a button. A Swing button can display not only text but also an image. In addition, a mnemonic, i.e. an underlined letter (from the button’s text) which can be used as a keyboard alternative, can be set and used.

The abstract class AbstractButton is the superclasses not only of the JButton but also of the following: JCheckBox, JRadioButton, JMenuItem, JCheckBoxMenuItem, JCheckBoxMenuItem, and JToggleButton. Class AbstractButton provides most of the functionality of all these classes. It extends the JComponent class and implements the ItemSelectable and SwingConstants interfaces.

An action listener, can be registered to a button enabling event handling, which is notified every time the user clicks the button by invoking the actionPerformed() method.

Example

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class AppletApplicationBut extends JApplet
implements ActionListener
{
protected JButton button1;
protected Icon icon;

public void init()
{
Icon icon = new ImageIcon(“1.124.gif”);

button1 = new JButton(“Welcome!”, icon);

button1.setVerticalTextPosition(AbstractButton.BOTTOM);
button1.setHorizontalTextPosition(AbstractButton.CENTER);
button1.setActionCommand(“welcome message”);
button1.setMnemonic(KeyEvent.VK_M); // Alt-M
button1.addActionListener(this);

getContentPane().add(button1, BorderLayout.CENTER);
}

public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals(“welcome message”))
{
System.out.println(“Welcome to 1.124!”);
}
}

public static void main(String args[])
{
JFrame myFrame = new JFrame(“Button Example”);
JApplet myApplet = new AppletApplicationBut();
myApplet.init();

myFrame.setContentPane(myApplet.getContentPane());
myFrame.setBounds(100,150,500,300);

myFrame.pack();
myFrame.setVisible(true);

myFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

myFrame.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);
}
} );
}

}

Execution of the program:

Toggle Buttons: JToggleButton

The JToggleButton class can be used to create toggle buttons, i.e. two-state buttons that can be selected and deselected. The JToggleButton class also serves as the base class for the JRadioButton and JCheckBox classes.
 

Check Boxes: JCheckBox and JCheckBoxMenuItem

The JCheckBox class can be used to create check box buttons, typically, to represent choices that are not mutually exclusive. Any number of check boxes in a group of check boxes, or even none, can be selected. Class JCheckBox extends JToggleButton class and implements the Accessible interface.

The JCheckBoxMenuItem class, which is a JMenuItem subclass, can be used to put check boxes in a menu.
 

Radio Buttons: JRadioButton and JRadioButtonMenuItem

Radio buttons are implemented in Swing using the JRadioButton class, typically, to represent choices that are mutually exclusive. Only one radio button, at a time, can be selected from a group of radio buttons.

The class JRadioButtonMenuItem, which is a JMenuItem subclass, can be used to put a radio button in a menu.
 

Menus: JMenu, JMenuItem, JCheckBoxMenuItem, and JRadioButtonMenuItem

Swing provides components for menus (e.g. menus within menu bars and pop-up menus), and for menu items. Class JMenuItem extends the AbstractButton class and implements the Accessible and MenuElement interfaces.

A menu item, which is an instance of a JMenuItem or one of its subclasses, is essentially a button, as well as a menu is. The latter, i.e. the menu, is an instance of the JMenu class or one of its subclasses. Menu items, since they are buttons, send action events when activated. In addition, they can send MenuDragMouseEvents when a mouse is dragged over them, and MenuKeyEvents when a key is pressed, released, or pressed.

An accelerator, which offers keyboard shortcuts to bypass navigating the menu hierarchy, can be specified for menu items. A mnemonic, which offers a way to use the keyboard to navigate the menu hierarchy, is a key which is pressed in conjunction with a Meta key.

The following image (adapted from the Sun’s Swing/JFC Tutorial) presents the menu-related inheritance hierarchy:

Since a menu is a lightweight component, a component of any type can be added to it. A menu that resides in a menu bar is considered as a top-level menu, while a menu contained in a menu is considered a cascading menu.

Menus are buttons with a pop-up menu associated with them, which is displayed beneath the menu whenever the menu is activated. Items can be added, inserted, or removed from menus using the JMenu methods. The JMenu class extends the JMenuItem class and implements the Accessible and MenuElement interfaces. Components and separators can also be added to JMenu instances. Menu listeners and be added and removed from JMenu instances.

The JCheckBoxMenuItem and the JRadioButtonMenuItem classes are extensions of the JMenuItem subclass.

A menu bar, implemented by the JMenuBar class, contain a row of menus and may be used both in applications and applets. Class JMenuBar extends the JComponent class and implements the Accessible and MenuElement interfaces.

Pop-up menus are implemented in Swing using the JPopupMenu class, which is a subclass of the JComponent class and implements the Accessible and MenuElement interfaces. Pop-up menus are usually shown in response to a pop-up trigger, i.e. a sequence of events, which depends on the window system.
 

Labels: JLabel

Instances of the JLabel class can be used to display images and unselectable text. The JLabel class extends JMenuItem and implements the Accessible interface.

Although the JLabel and JButton classes are not related by inheritance, they have many similar methods and identical properties. A menu usually appears either in a menu bar, contains one or more menus, or as a pop-up menu, which becomes visible only when the user makes a platform-specific mouse action.
 

Separators: JSeparator

Separators are used to separate components or sets of components. They are implemented in Swing using the class JSeparator which extends the JComponent class and implements the Accessible and SwingConstants interfaces.
 

Monitor Progress: JProgressBar

Swing provides the JProgressBar class to graphically indicate to the user about how much of a certain task has been performed and how much it might take to finish. JProgressBar extends the JComponent class and implements the Accessible and SwingConstants interfaces.

However, JProgressBar and ProgressMonitorInputStream utilities can also be used to provide the percentage of a time-consuming task.
 

Sliders: JSlider

A slider displays a value, which is between a minimum and a maximum value, that can be changed by dragging the slider’s knob or clicking on the slider. A slider can be implemented in Swing using the JSlider class, which is a subclass of the JComponent class and implements the Accessible and SwingConstants interfaces.

Methods of the JSlider class can be used to set (major and minor) tick marks, which indicate specific values associated with the slider, and labels, which are displayed on major tick mark locations, on a slider.
 

File Choosers: JFileChooser

File choosers are lightweight components, instances of the class JFileChooser, that are placed in a dialog or a container providing a GUI for selecting a file or navigating the filesystem. File choosers support files only, directories only, or, files and directories display modes. Class JFileChooserextends the JComponent class and implements the Accessible interface. File choosers also support single and multiple selection.

Although a file chooser displays, by default, all files and directories, file filtering can be applied to a file chooser in order to selectively show only some certain files, e.g. based on the file type. Method setFileHidingEnabled(false) can be use to show hidden files, which by default are not shown.

File choosers can be customized in many different ways using the methods of the JFileChooserclass. They can also accommodate accessory components to show the contents of selected files, or just to make themselves fancier.
 

Color Choosers: JColorChooser

Color choosers, represented by the JColorChooser class, can be used to provide to users with a palette of colors and allow them to manipulate and choose a color. Class JColorChooser extends the JComponent class and implements the Accessible interface. A color chooser contains a set of color chooser panes in a tabbed pane and a preview panels that displays the selected color.
 

Combo Boxes: JComboBox

Combo boxes, which are implemented using the JComboBox class, contain an editable area and a drop-down list of selectable items. Class JComboBox extends the JComponent class and implements the Accessible,ItemSelectable, ListDataListener and ActionListener interfaces.

Both JComboBox and JList classes can be used to display a list of items. However, combo boxes can have an editor, while list cells are not editable. In addition, combo boxes support keys selections, while lists do not.
 

Lists: JList

Swing Lists, implemented using the JList class, cab be used to display a list of selectable objects, from which the user can choose. Class JList extends the JComponent class and implements the Accessible, and Scrollable interfaces. Although the JList class does not provide scrolling support, instances of JList are almost always placed in scrollpanes. An instance of the ListSelectionModel class, which is used by a list to manage its selection, allows any combination of items to be selected at a time.
 

Tables: JTable

Tables, implemented by the JTable class, can be used to display tables, i.e. rows and columns, of data. The user is, optionally, allowed to edit the data. A number of selection modes, e.g. column, cell and cell selection, are supported by Swing tables. Tables consists of a table header, column headers, and columns with cell values.

A package, the javax.swing.table, that contains all interfaces and classes related to tables is provided in Swing. The main Swing class associated with tables is the class JTable, which extends the JComponent class and implements the Accessible, TableModelListener, Scrollable, TableColumnModelListener, ListSelectionListener, CellEditorListener interfaces.

Almost always, a tables is contained in a scroll pane, which automatically gets the table’s header (with the column names) and puts it on top of the table. The column names remain visible even when the user scrolls down.
 

Trees: JTree

Trees, which are implemented by the JTree class, can be used to display hierarchical data using folders and leaf items. Class JTree extends the JComponent class and implements the Accessible, and Scrollable interfaces.

Every tree has a root node from which all nodes, which may have children themselves, descend. The user can expand and collapse branch nodes, nodes that may have children, by double clicking on them, or by clicking on the folder’s handle.

Tool Tips: JToolTip

Tooltips, which are implemented by the class JToolTip, are used to display a tip for a component. Class JToolTip the JComponent class and implements the Accessible interface. The method setToolTipText(String text) of the JComponent class is used to associate a tooltip with a Swing component.

With tool tips, strings describing the functionality of a JComponent subclass object can be displayed above it, providing help to the user of the component, whenever the cursor rests over it based on timing characteristics specified by the ToolTipManager class.

Text Components: JTextComponent, JTextField, JTextArea, JEditorPane, JTextPane, and JPasswordField

Text components display text, which can, optionally, be editable by the user. Swing provides 5 classes that are sublclasses of the JTextComponent class. JTextComponent extends the JComponent class and implements the Accessible, and Scrollable interfaces.

The following figure, (adapted from the Sun’s Java® Tutorial) shows the hierarchy of the JTextComponent, which is the base class for swing text components.

  • JTextField allows the editing of a single line of text.
  • JTextArea is a multi-line area that displays plain text.
  • JEditorPane enables the editing of various kinds of content.
  • JTextPane is a lightweight text component that can be marked up with attributes that are represented graphically.
  • JPasswordField allows the editing of a single line of text while not showing the original characters.

Course Info

Learning Resource Types
Exams with Solutions
Presentation Assignments
Programming Assignments with Examples
Written Assignments