2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 1/11
ICT Home Outline Timeline Notes IPC Notes MySeneca Workshops Assignments Instructor
Software
Development
OOP244
Part B – Foundations
Types, Overloading and References
Review types, declarations, definitions and scoping
Introduce overloading and function signatures
Introduce pass by reference and compare it to pass by address
“Correctness, simplicity, and clarity comes first” (Sutter, Alexandrescu, 2005)
Types | Declarations | Scope | Overloading | References | Array of Pointers | Keywords | Summary | Exercises
Object-oriented languages inherit from their non-object-oriented predecessors the concepts of variable declarations, data types,
data structures, logic constructs, and modular programming. The C++ language inherits these features from the C language (see
IPC Notes for a detailed exposition).
This chapter elaborates on these inherited concepts and introduces the new concepts of references and overloading, which the C++
language adds to its C core. This chapter concludes with a section on arrays of pointers, which is important later in the design of
polymorphic objects.
TYPES
The built-in types of the C++ language are called its fundamental types. The C++ language, like C, admits struct types
constructed from these fundamental types and possibly other struct types. The C++ language standard refers to struct types as
compound types. (The C language refers to struct types as derived types.)
Fundamental Types
The fundamental types of C++ include:
Integral Types (store data exactly in equivalent binary form and can be signed or unsigned)
bool – not available in C
char
int – short, long, long long
Floating Point Types (store data to a specified precision – can store very small and very large values)
float
double – long double
bool
The bool type stores a logical value: true or false.
The ! operator reverses that value: !true is false and !false is true.
! is self-inverting on bool types, but not self-inverting on other types.
bool to int
Conversions from bool type to any integral type and vice versa require care. true promotes to an int of value 1, while
false promotes to an int of value 0. Applying the ! operator to an int value other than 0 produces a value of 0, while
applying the ! operator to an int value of 0 produces a value of 1. Note that the following code snippet displays 1 (not 4)
int x = 4;
cout << !!x; 1
Welcome
Notes
Welcome to OO
Object Terminology
Modular Programming
Types Overloading
Dynamic Memory
Member Functions
Construction
Current Object
Member Operators
Class + Resources
Helper Functions
Input Output
Derived Classes
Derived Functions
Virtual Functions
Abstract Classes
Templates
Polymorphism
I/O Refinements
D C + Resources
Standards
Bibliography
Library Functions
ASCII Sequence
Operator Precedence
C++ and C
Workshops
Assignments
Handouts
Practice
Resources
https://ict.senecac.on.ca/
https://ict.senecacollege.ca/~oop244/index.html
http://www.senecacollege.ca/ssos/findwithoutsemester/oop244/sict
https://ict.senecacollege.ca/~oop244/pages/timeline.html
https://ict.senecacollege.ca/~oop244/pages/content/index.html
https://scs.senecac.on.ca/~ipc144/pages/content/index.html
https://my.senecacollege.ca/webapps/portal/frameset.jsp
https://ict.senecacollege.ca/~oop244/dynamic/workshops/index.html
https://ict.senecacollege.ca/~oop244/pages/assignments/index.html
https://ict.senecacollege.ca/~oop244/pages/instructors/index.html
https://ict.senecacollege.ca/~oop244/pages/content/rudim_p.html
https://scs.senecac.on.ca/~ipc144/pages/content/index.html
https://ict.senecacollege.ca/~oop244/pages/welco.html
https://ict.senecacollege.ca/~oop244/pages/content/index.html
https://ict.senecacollege.ca/~oop244/pages/content/langu.html
https://ict.senecacollege.ca/~oop244/pages/content/objec.html
https://ict.senecacollege.ca/~oop244/pages/content/compi.html
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html
https://ict.senecacollege.ca/~oop244/pages/content/dynam.html
https://ict.senecacollege.ca/~oop244/pages/content/cppst.html
https://ict.senecacollege.ca/~oop244/pages/content/ctors.html
https://ict.senecacollege.ca/~oop244/pages/content/membe.html
https://ict.senecacollege.ca/~oop244/pages/content/overl.html
https://ict.senecacollege.ca/~oop244/pages/content/deepc.html
https://ict.senecacollege.ca/~oop244/pages/content/nonme.html
https://ict.senecacollege.ca/~oop244/pages/content/custo.html
https://ict.senecacollege.ca/~oop244/pages/content/inher.html
https://ict.senecacollege.ca/~oop244/pages/content/dfunc.html
https://ict.senecacollege.ca/~oop244/pages/content/inclu.html
https://ict.senecacollege.ca/~oop244/pages/content/abstr.html
https://ict.senecacollege.ca/~oop244/pages/content/param.html
https://ict.senecacollege.ca/~oop244/pages/content/adhoc.html
https://ict.senecacollege.ca/~oop244/pages/content/basic.html
https://ict.senecacollege.ca/~oop244/pages/content/dclas.html
https://ict.senecacollege.ca/~oop244/pages/content/ansis.html
https://ict.senecacollege.ca/~oop244/pages/content/bibli.html
https://ict.senecacollege.ca/~oop244/pages/content/libraries.html
https://ict.senecacollege.ca/~oop244/pages/content/ascii.html
https://ict.senecacollege.ca/~oop244/pages/content/prece.html
https://ict.senecacollege.ca/~oop244/pages/content/c_cpp.html
https://ict.senecacollege.ca/~oop244/dynamic/workshops/index.html
https://ict.senecacollege.ca/~oop244/pages/assignments/index.html
https://ict.senecacollege.ca/~oop244/pages/handouts/index.html
https://ict.senecacollege.ca/~oop244/pages/practice/index.html
https://ict.senecacollege.ca/~oop244/pages/resources/index.html
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 2/11
Both C and C++ treat the integer value 0 as false and any other value as true.
Compound Types
A compound type is a type composed of other types. A struct is a compound type. An object-oriented class is also a compound
type. To identify a compound type we use the keywords struct or class. We cover the syntax for classes in the following
chapter.
For example,
// Modular Example
// Transaction.h
struct Transaction {
int acct; // account number
char type; // credit 'c' debit 'd'
double amount; // transaction amount
};
The C++ language requires the keyword identifying a compound type only in the declaration of that type. The language does not
require the keyword struct or class in a function prototype or an object definition. Note the code snippets listed on the left.
Recall that the C language requires the keyword struct throughout the code as listed on the right.
// Modular Example - C++
// Transaction.h
struct Transaction {
int acct;
char type;
double amount;
};
void enter(Transaction*);
void display(const Transaction*);
// ...
int main() {
Transaction tr;
// ...
}
// Modular Example - C
// Transaction.h
struct Transaction {
int acct;
char type;
double amount;
};
void enter(struct Transaction*);
void display(const struct Transaction*);
// ...
int main() {
struct Transaction tr;
// ...
}
auto Keyword
The auto keyword was introduced in the C++11 standard. This keyword deduces the object's type directly from its initializer's
type. We must provide the initializer in any auto declaration.
For example,
auto x = 4; // x is an int that is initialized to 4
auto y = 3.5; // y is a double that is initialized to 3.5
auto is quite useful: it simplifies our coding by using information that the compiler already has.
DECLARATIONS AND DEFINITIONS
Modular programming can result in multiple definitions. To avoid conflicts or duplication, we need to design our header and
implementation files accordingly. The C++ language distinguishes between declarations and definitions and stipulates the one-
definition rule.
Declarations
A declaration associates an entity with a type, telling the compiler how to interpret the entity's identifier. The entity may be a
variable, an object or a function.
For example, the prototype
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 3/11
int add(int, int);
declares add() to be a function that receives two ints and returns an int. This declaration does not specify what the function
does; it does not specify the function's meaning.
For example, the forward declaration
struct Transaction;
declares Transaction to be of structure type. A forward declaration is like a function prototype: it tells the compiler how to
interpret the entity's identifier. It tells the compiler that the entity is a valid type, but does not specify the entity's meaning.
Although a declaration does not necessarily specify meaning, it may specify it. Specifying a meaning is an optional part of any
declaration.
Definitions
A definition is a declaration that associates a meaning with an identifier.
For example, the following definitions attach meanings to Transaction and to display():
struct Transaction {
int acct; // account number
char type; // credit 'c' debit 'd'
double amount; // transaction amount
};
void display(const Transaction* tr) { // definition of display
cout << "Account " << tr->acct << endl;
cout << (tr->type == ‘d’ ? ” Debit $” : ” Credit $”) << endl;
cout << tr->amount << endl;
}
In C++, each definition is an executable statement. We may embed it amongst other executable statements.
For example, we may place a definition within an initializer:
for (int i = 0; i < n; i++)
//...
One Definition Rule
In the C++ language, a definition may only appear once within its scope. This is called the one-definition rule.
For example, we cannot define Transaction or display() more than once within the same code block or translation unit.
Declarations are not necessarily Definitions
Forward declarations and function prototypes are declarations that are not definitions. They associate an identifier with a type, but
do not attach any meaning to that identifier. We may repeat such declarations several times within the same code block or
translation unit.
Header files consist of declarations. When we include several header files in a single implementation file, multiple declarations
may occur. If some of the declarations are also definitions, this may result in multiple definitions within the same translation unit.
Any translation unit must not break the one-definition rule. We need to design our header files to respect this rule.
Designing Away Multiple Definitions
A definition that appears more than once within the same translation unit generates a compiler error. Two solutions are shown
below.
The program listed below consists of three modules: main, Transaction and iostream.
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 4/11
In the main module's implementation file we have introduced a new function called add(), which receives the address of a
double and the address of a Transaction object. This function update the value stored in the first address:
// One Definition Rule
// one_defintion_rule.cpp
#include
#include “main.h” // prototype for add()
#include “Transaction.h” // prototypes for enter() and display()
using namespace std;
int main() {
int i;
double balance = 0.0;
Transaction tr;
for (i = 0; i < NO_TRANSACTIONS; i++) {
enter(&tr);
display(&tr);
add(&balance, &tr);
}
cout << "Balance " << balance << endl;
}
void add(double* bal, const Transaction* tr) {
*bal += (tr->type == ‘d’ ? -tr->amount : tr->amount);
}
The Transaction module’s header file defines the Transaction type:
// Modular Example
// Transaction.h
struct Transaction {
int acct; // account number
char type; // credit ‘c’ debit ‘d’
double amount; // transaction amount
};
void enter(Transaction* tr);
void display(const Transaction* tr);
Design Question
Into which header file should we insert the prototype for this add() function?
If we insert the prototype into the main module’s header file, main.cpp will not compile:
// main.h
#define NO_TRANSACTIONS 3
void add(double*, const Transaction*);
The compiler will report Transaction* as undeclared. Note that the compiler analyzes code sequentially and does not yet
know what Transaction is when it encounters the prototype for add().
If we insert Transaction.h into this header file (main.h), we resolve this issue but break the one-definition rule in
main.cpp:
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 5/11
// main.h
#define NO_TRANSACTIONS 3
#include “Transaction.h” // BREAKS THE ONE-DEFINITION RULE!
void add(double*, const Transaction*);
The main.cpp translation unit would contain TWO definitions of Transaction.
Possible designs are possible include:
Forward Declaration Solution – insert the prototype into main.h
Compact Solution – insert the prototype into Transaction.h
Forward Declaration Solution
Inserting the prototype into main.h along with a forward declaration of Transaction informs the compiler that this identifier
in the prototype is a valid type.
// main.h
#define NO_TRANSACTIONS 3
struct Transaction; // forward declaration
void add(double*, const Transaction*);
This design provides the compiler with just enough information to accept the identifer, without exposing the type details.
Compact Solution
Inserting the prototype into the Transaction.h header file is a more compact solution:
// Modular Example
// Transaction.h
struct Transaction {
int acct; // account number
char type; // credit ‘c’ debit ‘d’
double amount; // transaction amount
};
void enter(Transaction* tr);
void display(const Transaction* tr);
void add(double*, const Transaction*);
This design localizes all declarations related to the Transaction type within the same header file. We call functions that
support a compound type the helper functions for that type.
Proper Header File Inclusion
To avoid contaminating system header files, we include header files in the following order:
#include < ... > – system header files
#include ” … ” – other system header files
#include ” … ” – your own header files
We insert namespace declarations and directives after all header file inclusions.
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 6/11
SCOPE
The scope of a declaration is the portion of a program over which that declaration is visible. Scopes include
global scope – visible to the entire program
file scope – visible to the source code within the file
function scope – visible to the source code within the function
class scope – visible to the member functions of the class
block scope – visible to the code block
The scope of a non-global declaration begins at the declaration and ends at the closing brace for that declaration. A non-global
declaration is called a local declaration. We say that an identifier that has been locally declared is a local variable or object.
Going Out of Scope
Once a declaration is out of its scope, the program has lost access to the declared variable or object. Identifying the precise point
at which a variable’s or object’s declaration goes out of scope is important in memory management.
Iterations
In the following code snippet, the counter i, declared within the for statement, goes out of scope immediately after the closing
brace:
for (int i = 0; i < 4; i++) {
cout << "The value of i is " << i << endl;
} // i goes out of scope here
We cannot refer to i after the closing brace.
A variable or object declared within a block goes out of scope immediately before the block's closing brace.
for (int i = 0; i < 3; i++) {
int j = 2 * i;
cout << "The value of j is " << j << endl;
} // j goes out of scope here
The scope of j extends from its definition to just before the end of the current iteration. j goes out of scope with each iteration.
The scope of i extends across the complete set of iterations.
Shadowing
An identifier declared with an inner scope can shadow an identifier declared with a broader scope, making the latter temporarily
inaccessible. For example, in the following program the second declaration shadows the first declaration of i:
// scope.cpp
#include
using namespace std;
int main() {
int i = 6;
cout << i << endl;
for (int j = 0; j < 3; j++) {
int i = j * j;
cout << i << endl;
}
cout << i << endl;
}
6
0
1
4
6
FUNCTION OVERLOADING
In object-oriented languages functions may have multiple meanings. Functions with multiple meanings are called overloaded
functions. C++ refers to functions first and foremost by their identifier and distinguishes different meanings by differing
parameter lists. For each identifier and parameter list combination, we implement a separate function definition. C++ compilers
determine the definition to select by matching the argument types in the function call to the parameters types in the definition.
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 7/11
Function Signature
A function's signature identifies an overloaded function uniquely. Its signature consists of
the function identifier
the parameter types (ignoring const qualifiers or address of operators as described in references below)
the order of the parameter types
type identifier ( type identifier [, ... , type identifier] )
The square brackets enclose optional information. The return type and the parameter identifiers are not part of a function's
signature.
C++ compilers preserve identifier uniqueness by renaming each overloaded function using a combination of its identifier, its
parameter types and the order of its parameter types. We refer to this renaming as name mangling.
Example
Consider the following example of an overloaded function. To display data on the standard output device, we can define a
display() function with different meanings:
// Overloaded Functions
// overload.cpp
#include
using namespace std;
// prototypes
void display(int x);
void display(const int* x, int n);
int main() {
auto x = 20;
int a[] = {10, 20, 30, 40};
display(x);
display(a, 4);
}
// function definitions
//
void display(int x) {
cout << x << endl;
}
void display(const int* x, int n) {
for (int i = 0; i < n; i++)
cout << x[i] << ' ';
cout << endl;
}
20
10 20 30 40
C++ compilers generate two one definition of display() for each set of parameters. The linker binds each function call to the
appropriate definition based on the argument types in the function call.
Prototypes
A function prototype completes the function's signature by specifying the return type. However, the parameter identifiers are also
optional in the prototype. The prototype provides sufficient information to validate a function call.
A prototype without parameter types identifies an empty parameter list. The keyword void, which the C language uses to
identify no parameters is redundant in C++. We omit this keyword in C++.
Prototypes Required
A programming language may require a function declaration before any function call for type safety. The declaration may be
either a prototype or the function definition itself. The compiler uses the declaration to check the argument types in the call
against the parameter types in the prototype or definition. The type safety features of C++ require a preceding declaration.
For example, the following program will generate a compiler error (note that the absence of any printf declaration):
int main() {
printf("Hello C++\n");
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 8/11
}
To meet type safety requirements, we include the prototype:
#include
using namespace std;
int main() {
printf(“Hello C++\n”);
}
Default Parameter Values
We may include default values for some or all of a function’s parameters in the first declaration of that function. The parameters
with default values must be the rightmost parameters in the function signature.
Declarations with default parameter values take the following form:
type identifier(type[, …], type = value);
The assignment operator followed by a value identifies the default value for each parameter.
Specifying default values for function parameters reduces the need for multiple function definitions if the function logic is
identical in every respect except for the values received by the parameters.
Example
For example,
// Default Parameter Values
// default.cpp
#include
using namespace std;
void display(int, int = 5, int = 0);
int main() {
display(6, 7, 8);
display(6);
display(3, 4);
}
void display(int a, int b, int c) {
cout << a << ", " << b << ", " << c << endl;
}
6, 7, 8
6, 5, 0
3, 4, 0
Each call to display() must include enough arguments to initialize the parameters that don't have default values. In this
example, each call must include at least one argument. An argument passed to a parameter that has a default value overrides the
default value.
REFERENCES
A reference is an alias for a variable or object. Object-oriented languages rely on referencing. A reference in a function call
passes the variable or object rather than a copy. In other words, a reference is an alternative to the pass by address mechanism
available in the C language. Pass-by-reference code is notably more readable than pass-by-address code. To enable referencing,
the C++ rules on function declarations are stricter than those of the C language.
The declaration of a function parameter that is received as a reference to the corresponding argument in the function call takes the
form
type identifier(type& identifier, ... )
The & identifies the parameter as an alias for, rather than a copy of, the corresponding argument. The identifier is the alias for the
argument within the function definition. Any change to the value of a parameter received by reference changes the value of the
corresponding argument in the function call.
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 9/11
Comparison Examples
Consider a function that swaps the values stored in two different memory locations. The programs listed below compare pass-by-
address and pass-by-reference solutions. The program on the left passes by address using pointers. The program on the right
passes by reference:
// Swapping values by address
// swap1.cpp
#include
using namespace std;
void swap ( char *a, char *b );
int main ( ) {
char left;
char right;
cout << "left is ";
cin >> left;
cout << "right is ";
cin >> right;
swap(&left, &right);
cout << "After swap:"
"\nleft is " <<
left <<
"\nright is " <<
right <<
endl;
}
void swap ( char *a, char *b ) {
char c;
c = *a;
*a = *b;
*b = c;
}
// Swapping values by reference
// swap2.cpp
#include
using namespace std;
void swap ( char &a, char &b );
int main ( ) {
char left;
char right;
cout << "left is ";
cin >> left;
cout << "right is ";
cin >> right;
swap(left, right);
cout << "After swap:"
"\nleft is " <<
left <<
"\nright is " <<
right <<
endl;
}
void swap ( char &a, char &b ) {
char c;
c = a;
a = b;
b = c;
}
Clearly, reference syntax is simpler. To pass an object by reference, we attach the address of operator to the parameter type. This
operator instructs the compiler to pass by reference. The corresponding arguments in the function call and the object names within
the function definition are not prefixed by the dereferencing operator required in passing by address.
Technically, the compiler converts each reference to a pointer with an unmodifiable address.
ARRAY OF POINTERS
Arrays of pointers are data structures like arrays of values. Arrays of pointers contain addresses rather than values. We refer to
the object stored at a particular address by dereferencing that address. Arrays of pointers play an important role in implementing
polymorphism in the C++ language.
An array of pointers provides an efficient mechanism for processing the set. With the objects' addresses collected in a contiguous
array, we can refer to each object indirectly through the pointers in the array and process the data by iterating on its elements.
In preparation for a detailed study of polymorphic objects later in this course, consider the following preliminary example:
// Array of Pointers
// array_pointers.cpp
#include
using namespace std;
const int N_CHARS = 31;
struct Student {
int no;
double grade;
char name[N_CHARS];
2021/8/8 Foundations | Types, Overloading and References
https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 10/11
};
int main() {
const int NO_STUDENTS = 3;
Student john = {1234, 67.8, “john”};
Student jane = {1235, 89.5, “jane”};
Student dave = {1236, 78.4, “dave”};
Student* pStudent[NO_STUDENTS]; // array of pointers
pStudent[0] = &john;
pStudent[1] = &jane;
pStudent[2] = &dave;
for (int i = 0; i < NO_STUDENTS; i++) { cout << pStudent[i]->no << endl; cout << pStudent[i]->grade << endl; cout << pStudent[i]->name << endl; cout << endl; } } 1234 67.8 john 1235 89.5 jane 1236 78.4 dave Here, while the objects are of the same type, the processing of their data is done indirectly through an array of pointers to that data. KEYWORDS The 84 keywords of the C++11 standard are listed below. We cannot use any of these keywords as identifiers. Those shaded in grey are also C keywords. The italicized keywords are alternative tokens for operators. alignas alignof and and_eq asm auto bitand bitor bool break case catch char char16_t char32_t class compl const constexpr const_cast continue decltype default delete do double dynamic_cast else enum explicit export extern false float for friend goto if inline int long mutable namespace new not not_eq noexcept nullptr operator or or_eq private protected public register reinterpret_cast return short signed sizeof static static_assert static_cast struct switch template this thread_local throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while xor xor_eq C++ compilers will successfully compile any C program that does not use any of these keywords as identifiers provided that that program satisfies C++'s type safety requirements. We call such a C program a clean C program. SUMMARY a bool type can only hold a true value or a false value C++ requires the struct or class keyword only in the definition of the class itself a declaration associates an identifier with a type a definition attaches meaning to an identifier and is an executable statement a definition is a declaration, but a declaration is not necessarily a definition the scope of a declaration is that part of the program throughout which the declaration is visible we overload a function by changing its signature a function's signature consists of its identifier, its parameter types, and the order of its parameter types a C++ function prototype must include all of the parameter types and the return type the & operator on a parameter type instructs the compiler to pass by reference pass by reference syntax simplifies the pass by address syntax in most cases an array of pointers is a data structure that provides an efficient way for iterating through a set of objects based on their current type EXERCISES Complete the Handout on Basic Concepts https://ict.senecacollege.ca/~oop244/pages/handouts/h1.html 2021/8/8 Foundations | Types, Overloading and References https://ict.senecacollege.ca/~oop244/pages/content/rudim.html 11/11 print this page Top Previous: Modular Programming Next: Dynamic Memory ICT Home Outline Timeline Notes IPC Notes MySeneca Workshops Assignments Instructor Designed by Chris Szalwinski Copying From This Site Last Modified: 01/24/2018 12:15 https://ict.senecacollege.ca/~oop244/pages/content/rudim_p.html https://ict.senecacollege.ca/~oop244/pages/content/compi.html https://ict.senecacollege.ca/~oop244/pages/content/dynam.html https://ict.senecac.on.ca/ https://ict.senecacollege.ca/~oop244/index.html http://www.senecacollege.ca/ssos/findwithoutsemester/oop244/sict https://ict.senecacollege.ca/~oop244/pages/timeline.html https://ict.senecacollege.ca/~oop244/pages/content/index.html https://scs.senecac.on.ca/~ipc144/pages/content/index.html https://my.senecacollege.ca/webapps/portal/frameset.jsp https://ict.senecacollege.ca/~oop244/dynamic/workshops/index.html https://ict.senecacollege.ca/~oop244/pages/assignments/index.html https://ict.senecacollege.ca/~oop244/pages/instructors/index.html https://ict.senecacollege.ca/~oop244/pages/copyright.html http://creativecommons.org/licenses/by/2.5/ca/