HKUST Summer Programming Course 2008

Preview:

DESCRIPTION

HKUST Summer Programming Course 2008. Function ~ Modular Programming. Overview. Introduction to Functions Standard Libraries (cmath, cctype, cstring, ctime, cstdlib) User-defined function Pass-by-value and Pass-by-reference Local and Global scope Identifiers Name Resolution - PowerPoint PPT Presentation

Citation preview

1Department of Computer Science and Engineering, HKUST

HKUST SummerProgramming Course 2008

Function

~ Modular Programming

2

Overview Introduction to Functions Standard Libraries (cmath, cctype, cstring, ctime, cstdlib) User-defined function Pass-by-value and Pass-by-reference Local and Global scope Identifiers Name Resolution Function Definition and Declaration Function Overloading and Default Parameter Separate Compilation (Function Level) Header Files g++ and Makefile Function Execution in Runtime Static Variables in Function Definition Inline Function and its Compilation Model

3Department of Computer Science and Engineering, HKUST

Function

Introduction to Functions

4

Introduction to Functions So far, the code we learnt were completely specified within

main() This was possible because the code is simple and easy.

However, a large software application requires hundreds of thousands, or even millions lines of code. Impractical to write a single piece of code for such a large

application. Therefore, modular design principal is introduced, to put a

small, but meaningful, piece of code together. Other parts of program can use this piece of code once (or several

times code reuse). A principal part of all such schemes is the use of functions.

5Department of Computer Science and Engineering, HKUST

Function

Standard Libraries (cmath, cctype, cstring, ctime, cstdlib)

6

Standard Libraries (cmath, cctype, cstring, ctime, cstdlib)

Before learning how to write a function, let’s see some standard libraries defined by C++ cmath (Math functions) cctype (Character functions) cstring (Character string functions) ctime (time-related functions) cstdlib (commonly-used functions)

To use cmath, you have to include the library by #include <cmath>using namespace std;

You can include other libraries similarly. Good reference: http://www.cplusplus.com/ref/.

7

Standard Libraries (cmath) double ceil(double x)

Smallest integer greater than or equal to x. ceil(3.2) = 4, ceil(3.7) = 4, ceil(-3.2) = -3, ceil(-3.7) = -3

double floor(double x) Largest integer less than or equal to x.

floor(3.2) = 3, floor(3.7) = 3, floor(-3.2) = -4, floor(-3.7) = -4 Other rounding methods

Cast as integer Truncate the floating-point part

(int)(3.2) = 3, (int)(3.7) = 3, (int)(-3.2) = -3, (int)(-3.7) = -3 Round to nearest integer (no default function).

round(3.2) = 3, round(3.7) = 4, round(-3.2) = -3, round(-3.7) = -4

8

Standard Libraries (cmath)

int abs(int x) Absolute value of x (integer version).

double fabs(double x) Absolute value of x (floating-point version).

Common errorsdouble x = -3.2;

double absX = abs(x); C++ will convert x into int and then apply abs.

result = 3, but not 3.2 Use fabs instead.

9

Standard Libraries (cmath)

double exp(double x) ex

double pow(double x, double y) xy

double sqrt(double x) Square root of x

double log(double x) Natural log of x

double log10(double x) Log base 10 of x

10

Standard Libraries (cmath) double sin(double x)

Sine of angle x (in radian). double asin(double x)

Angle (in radian) whose sine is x. double cos(double x)

Cosine of angle x (in radian). double acos(double x)

Angle (in radian) whose cosine is x. double tan(double x)

Tangent of angle x (in radian). double atan(double x)

Angle (in radian) whose tangent is x.

11

Standard Libraries (cctype) int isalpha(char c)

true if c is a letter. Non-zero value means true

int isdigit(char c) true if c is a digit.

int isalnum(char c) true if c is a letter or digit.

int isblank(char c) true if c is a blank or tab.

int isspace(char c) true if c is whitespace character.

space, tab, vertical tab, formfeed, carriage return, or newline.

12

Standard Libraries (cctype)

int islower(char c) true if c is a lowercase letter.

int isupper(char c) true if c is an uppercase letter.

int tolower(char c) returns lowercase version of c if there is one, otherwise it returns the

character unchanged. We can cast the return value back to char.

int toupper(char c) returns uppercase version of c if there is one, otherwise it returns

the character unchanged.

13

Standard Libraries (cstring)

size_t strlen(const char* str) Return str’s length.

typedef unsigned int size_t; char* strcat(char* dest, const char* src)

Appends src string to dest string. char* strcpy(char* dest, const char* src)

Copies src to dest. int strcmp(const char* str1, const char* str2)

Compares str1 to str2 character by character. Returns 0 if str1 is equal to str2. Returns >0 if str1 is greater than str2. Returns <0 if str1 is less than str2.

14

Standard Libraries (cstring)

char* strtok(char* str, const char* delimiters) Sequentially truncate string if delimiter is found. Useful in parsing input, for example:

char input[100];

strcpy( input, "Name=Desmond;Mark1=100;Mark2=99");

char* pch = strtok(input, “=");

int fieldID = 0;

while ( pch != NULL ) {

cout << pch << endl;

if ( fieldID % 2 == 0 )

pch = strtok(NULL, “;"); // continue to use, use NULL

else pch = strtok(NULL, “=");

++fieldID;

}

More on this when we are talking about static variable.

Output:NameDesmondMark1100Mark299

15

Standard Libraries (ctime) time_t time ( time_t * timer )

Get the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC from the system clock. typedef long clock_t; This will overflow in the year 2038, if 32-bit long is used !!

char* ctime( const time_t* timer ) Converts timer to a string containing time and date adjusted to local

time zone in the format Www Mmm dd hh:mm:ss yyyy. clock_t clock ()

Returns the number of clock ticks elapsed. Useful in measuring time for an expensive job.

clock_t start = clock( );// expensive jobclock_t end = clock( );cout << “Elapsed seconds = “ << (end-start)*CLK_TCK;

16

Standard Libraries (cstdlib)

double atof( const char* string ) Convert string to double.

int atoi( const char* string ) Convert string to int.

void qsort( void* base, size_t num, size_t width, int (*fncompare)(const void *, const void *) ) Sort the array with quick-sort algorithm. More about this when we are talking function pointer.

17

Standard Libraries (cstdlib) void exit( int status );

Terminate calling process, return status to OS. char* getenv( const char* varname )

Get system environment variable.char* buffer = getenv ("PATH"); if (buffer != NULL)

cout << "Current path is” << buffer << endl; Linux separates PATH with semicolon, you can use strtok to

separate the path from the long string. int system( const char* command )

Execute command.int status = system(“ls"); if (status == -1)

cout << "Error executing ls\n”;

18

Standard Libraries (cstdlib) int rand()

Generate a pseudo-random number in the range from 0 to RAND_MAX. RAND_MAX is also defined in <cstdlib>

void srand( unsigned int seed ) Initialize the random seed once at the beginning of main( ). Eg.: srand( time(NULL) );

Random generator depends on two parameters. Seed (initialized with srand) Number of rand() called

Detail can be found in lab manual (lab03).

19

Standard Libraries

Guarantee: If it is necessary to use any standard functions in exam, a

brief description of that function will be given. Say, function prototype and function description.

No memorization is needed!!

20Department of Computer Science and Engineering, HKUST

Function

User-defined Function

21

User-defined function

Function Definition Syntax

<return-type> <function name>(<parameter list>){

// function body

}

Function call Syntax

<function name>( <variable list> )

22

Function Name

<function name> can be any valid C++ identifier. It is a good practice to use a meaningful function name.

To describe the tasks that the function performs.

23

Parameter List The parameter list is a comma-separated list of variables (with

their types) that receive values of the arguments when the function is called. If no parameters is needed, the parameter list is empty.

However, even if there are no parameters, parentheses are required. Syntax

<return-type> <function name>(<type1> <name1>, <type2> <name2>, …, <typeN><nameN>){// function body

}int func(int i, int k, int j){ ... } // correctint func(int i, k, int j){ ... } // incorrectint func( ){ ... } // correct

It is a good practice to select meaningful identifiers for the parameters.

24

Parameter List

In old C, it is not allowed to use <return-type> <function name>( )

to define a function. Instead, it will use

<return-type> <function name>( void ), where void is a reserved word that specify there is no argument in this function.

In ANSI C/C++, these two forms are equivalent. C++ is backward compatible with C!!

25

Function Calling - Variable List Syntax

<function name> (<variable list>); <variable list> is a comma-separated list of variables

The type of the variable must be the same as the type specified in <parameter list>. Or auto-convertible to the type specified in <parameter

list>. For example,

int a;double b, c;func(a, b, c); // function call, b is converted to int for the functionint func(int i, int k, double j){ ... }

26

Function header VS function bodyint func(int a, int b, int c){

// function body

} The first line is called function header:

<return-type> <function name>(<parameter list>) The code inside the curly-brace is called function body. The whole piece of code is called function definition.

Terminology

27

Terminology

Formal Parameters VS Actual Parametersint func(int i, int k, int j){ ... }// function definition

func(a, b, c); // function call

The parameters in the function header is called formal parameters, or parameters ONLY. Here, i, k, j are formal parameters.

The variables used in the function call is called actual parameters, or arguments. Here, a, b, c are actual parameters.

28

Return Value <return-type>: specifies the type of data that the function

returns. A function may return any type of data EXCEPT an array.

A function can either return a single result or return nothing. To return nothing, we can put void in the <return-

type>. The return value may either

Signals whether an operation is successful or not. Result of computation. ……

If multiple parameters need to be returned, use passing by reference (will be covered in a minute).

29

Return Value

The function caller may ignore the returned value But this is not a good practice. Some compilers even flag this as a warning.

Non-void return-typed function should have one or more return statements: return <expression>; The value passed back by return should have the same type as

the <return-type> of the function. Or a type that can be converted to <return-type> automatically.

Return statement also indicates the end of the function. Therefore, void function can also have a statement likes:

return;

30

Function Body

function body defines how the function do. Note the function body is enclosed by braces { } It can include any number of statements. Even if there is only one statement, you cannot omit the

braces { } This is different from the if-statement and iterative

statements.

31

Example of user-defined function (1)

int add(int val1, int val2) {return val1 + val2;

}

int main() {cout << "Enter two integers: ";

int v1, v2;

cin >> v1 >> v2;

cout << "The sum is " << add(v1,v2) << endl;

return 0;

}

32

Example of user-defined function (2)

We can implement the tolower (defined in cctype) ourselves.// Convert an upper-case letter to lower case

// Keep it unchanged if it is not an upper-case letter

int tolower(char c) {

if ( c >= ‘A’ && c <= ‘Z’ )

return ( c – ‘A’ + ‘a’ );

else

return c;

} No need to memorize the ASCII table!!

33

Example of user-defined function (3)

Now, try to implement a round function ourselves.

int round( double x ) {

if ( x >= 0 )

return (x + 0.5);

else

return (x - 0.5);

}

C++ will convert the returned value to the desired type. (x+0.5) is a double, but it will cast back to int

automatically.

34

Example of user-defined function (3)

// here is definition of the function: round

int main( ) {double x1 = 3.2, x2 = 3.7, x3 = -3.2, x4 = -3.7;cout << ceil(x1) << “ “ << ceil(x2) << “ “ <<

ceil(x3) << “ “ << ceil(x4) << endl;cout << floor(x1) << “ “ << floor(x2) << “ “ <<

floor(x3) << “ “ << floor(x4) << endl;cout << (int)(x1) << “ “ << (int)(x2) << “ “ <<

(int)(x3) << “ “ << (int)(x4) << endl;cout << round(x1) << “ “ << round(x2) << “ “<<

round(x3) << “ “ << round(x4) << endl;return 0;

}

35

Example of user-defined function (4)

void formattedOutput(char* name, int mark1, int mark2){cout << “Name: “ << name << endl;cout << “Midterm mark: “ << mark1 << endl;cout << “Final Exam mark: “ << mark2 << endl;

}

int main( ) {char* name = “Desmond”;int midtermMark = 100; int finalExamMark = 90;formattedOutput(name, midtermMark, finalExamMark);return 0;

}

36

Example of user-defined function (5)

These two function definitions are equivalentvoid printWelcome(void) {

cout << “Hello World” << endl;

}

void printWelcome() {

cout << “Hello World” << endl;

}

37

Documentation for functions It is a good practice to document the functions (ie. add

sufficient comment to the function). Describe the task that the function performs. Describe the meaning of each parameter. Describe the meaning of the returned value.

For example, /* Convert the character c to lowercase letter if

c is a uppercase letterOtherwise, keep the character c unchanged */int tolower(char c) { … }

/* Round x into nearest integerInput: x, any real numberOutput: nearest integer of x */int round( double x ) { … }

38Department of Computer Science and Engineering, HKUST

Function

Pass-by-value and Pass-by-reference

39

Pass-by-value and Pass-by-reference

In C++, there are two ways that the arguments can be passed to a function: Pass-by-value Pass-by-reference

40

Pass-by-value

Pass-by-value copies the value of an actual parameter into the formal parameters of the function. Changes made to the formal parameter have no effect on the

actual parameter.

41

Examples

int sum(int a, int b) {a = a + b;return a;

}int main() {

int x = 3, y = 5;int z= sum(x,y);return 0;

} What’s the value of x, y, z after the function call sum?

Ans: 3, 5, 8

42

Examples

Reasons: The value of the original variable is copied to the parameter.

changes to the value of the parameter do not affect the original variable.

Even though the value of parameter a is changed, the corresponding value in variable x does not change.

In fact, all information in local variables declared within the function will be lost when the function terminates. The only information saved from a pass by value function is the

return value.

43

Pass-by-reference

Pass-by-reference does not copy the value of the actual parameters. Instead, the formal parameters refer the actual parameters

by some mechanism. More about this later, when we are talking about reference

variable. Any modification of the formal parameters directly changes

the value of the actual parameters. To pass the parameters by reference, we have to add a

symbol & after the type of the formal parameters.

44

Examples

void swap(int& a, int& b) {int temp = a;a = b;b = temp;

}int main() {

int x = 10, y = 20;swap(x, y);cout << “(x, y) = “ << x << ", " << y ;return 0;

} What’s the output?

(x,y) = 20, 10

45

Examples

Explanation: Note that two formal parameters are pass-by-reference.

The formal parameter a refers to the actual parameter x. The formal parameter b refers to the actual parameter y.

The value of a and b are changed inside the function. This is equivalent to modify the actual parameters x and y

directly.

46

Examples

void swap(int a, int b) {int temp = a;a = b;b = temp;

}int main() {

int x = 10, y = 20;swap(x, y);cout << “(x, y) = “ << x << ", " << y ;return 0;

} What’s the output?

(x, y) = 10, 20 (unchanged!!)

47

Final Note on Pass-by-value and Pass-by-reference

A function can contain multiple parameters. Some may be pass-by-value, while other may be pass-by-reference. No restriction on the order of pass-by-value and pass-by-reference.

If a function has multiple return values, we may use pass-by-reference. void largestTwoElements(const int* array, int size,

int& largest, int& secondLargest) Sometimes, when the object is large, we may use pass-by-

reference to avoid copying even if we don’t want to change the actual parameters. Use constant reference (add const before the type). More on this when we are talking about class.

48Department of Computer Science and Engineering, HKUST

Function

Local and Global scope

49

Scope Scope is a region of code.

We can define a scope by enclosing a piece of code within curly-braces.

It is possible to declare the same identifier name in different scopes. Because an identifier is both identified by its name and the scope

where it is defined. Identifiers defined in different scopes are treated as different

identifiers, even if their name are the same. But an identifier should have unique name inside a scope.

The outermost scope is called global scope. All other scopes (may be called local scope) are inner scopes of

global scope. More about this when we are talking about namespace.

50

Global and Local Declaration Declaration inside global scope is called global declaration.

It is valid inside all other scopes (or in the whole program). Declaration inside local scope is called local declaration.

It is valid inside that local scope or other inner scopes . Of course, it is only valid after its declaration inside that

scope.

51

Global Variables Global variables

Declared outside all the function bodies (including main function).

Known throughout the program and may be used by any pieces of code.

They are normally grouped with the other global variables and placed at the beginning of the program file.

Global variable can be accessed in any parts of program Difficult to manage (trace, testing and debug the code). It is a bad practice to use global variables.

Try to avoid them if possible. To communicate between functions, passing local variables by

reference.

52

Local Variables

Local variables Declared inside a scope, except global scope. Known by statements that are inside the block in which the

variables are declared. Not known outside their own scope and it is destroyed

automatically upon exit. Note that formal parameters of a function are also local

variables of that function.

53

54

y is accessible

f is accessible

z is accessible

a is accessible

s, t are accessible

r is accessible

i is accessible

55

Variable Definition in For-loop

for(int i=0; i<10; i++)

cout << “Hello ” << i << endl;

cout << “Printed Hello for “ << i << “times” << endl;

This is compilable in VC6, but not in g++, which follows the standard. According to C++ standard, a variable defined inside the for-init of the for loop should be treated like:{ // a scope is created to enclose the whole for-loop

for (int i=0; i<10; i++){

cout << “Hello” << i << endl;

}

}

// therefore , the variable i cannot be accessed

cout << “Printed Hello for “ << i << “times” << endl;

56Department of Computer Science and Engineering, HKUST

Function

Identifiers Name Resolution

57

Identifiers Name Resolution When a compiler read an identifier, how it relates this identifier

to a declaration of identifier is called Name Resolution. Function name Variable name

When is an identifier encountered in a scope, Compiler first tries to find a declaration in that scope.

If a declaration is found in that scope, finish. Otherwise, search the declaration in the smallest “outer scope”. Continue until the compiler reach the global scope. If a declaration still cannot be found, flag a compile error reports that

the identifier cannot be resolved.

58

Examplesint a = 10, b = 3; // global definition

int func( int b, int c ) {int sum = a; // a: no definition in func scope, use

// global definitionreturn (sum + b + c); // b, c, sum: definition in func scope

// found}

int main( ) {int a = 100, b = 200;while ( a > 0 ) { // a: definition in main( ) scope

int b = 10;a -= b; // a: main() scope; b: while

loop scope}return b; // b: definition in main( ) scope

}

59

Global Scope’s identifier

int a = 10;

int main( ) {

int a = 100;

cout << “a = “ << a << endl;

} It will print the local declaration of a.

How to print the global scope’s a?

60

Global Scope’s identifier

int a = 10;

int main( ) {

int a = 100;

cout << “Local declaration of a = “ << a << endl;

cout << “Global declaration of a = “ << ::a << endl;

}

Use the scope operator ::, to explicitly tell the compiler to use the global declaration of a.

61

Example

int a = 1;int main( ) {

int a = 100;while (1) {

int a = 10;cout << a << endl; // a: while loop scopecout << ::a << endl; // a: global scope

}return 0;

} However, you can’t access the declaration of “a” defined in the

main( ) scope.

62

Example

In practice, rename the identifier to avoid name collision. Longer identifier name is less likely to occur name collision. Easier to trace the program. Try all possible ways to avoid uses of global variable.

63Department of Computer Science and Engineering, HKUST

Function

Function Definition and Declaration

64

Call a function without declaration

In C++, all functions must be declared before use. For example, the following code resulted a compile error

int main() { int i = 1, j = 2; return add(i, j); }

int add(int val1, int val2) { return (val1 + val2); } ERROR: The function add is used in main function without

declaration.

But this will be correct.int add(int val1, int val2) { return (val1 + val2); }

int main() { int i = 1, j = 2; return add(i, j); }

65

Function definition and declaration Note that declaration is not the same as definition in C++.

Declaration means introducing an identifier (with its type). Variable / Function declaration.

Definition means giving the identifier a value or its functionality. Variable / Function definition.

Variable definition/declaration will not be covered in this course because it involves some other concepts. If you are interested, we can discuss after class. We will focus on function declaration/definition here.

A function must be declared before use, but it is not necessary to be defined before use. So that the compiler can determine whether there are any type

mismatch errors in the variables passed in function call.

66

Function definition and declaration So far, all functions are declared and defined at the same time.

int add(int val1, int val2) {return val1 + val2;

} The above three lines together are called function definition. A function definition will also introduce the identifier, and hence it is

also a kind of declaration. We can also declare a function without defining it immediately.

int add(int val1, int val2); // function header only The above line is called function declaration. A semicolon must appear at the end.

67

Function Declaration

To declare a function, it is not necessary to give the identifier name for the formal parameters. The following two are equivalent:

int add(int val1, int val2);

int add(int, int); But it is recommended to give the identifier name, since this helps

others to guess the meaning of the parameters. The type of the formal parameters must be given in function

declaration.

68

Function Declaration

In a program, A function can be declared many times. But a function can only be defined once.

If you define a function several times, the compiler cannot determine which function definitions should be used.

69

Function Declaration

Currently, it seems that it is not necessary to declare a function.

But it will be necessary when we are talking about: Separate compilation. Recursive programming.

You can view function declaration as a guarantee that the function will be defined, but not defined at this moment. Although you can omit the function definition if there is no

one call that function.

70

Common pitfalls of function declaration and function definitionint main() {

int i = 1, j = 2; return add(i, j); // compile error: no declaration before use

}

int add(int val1, int val2) { return (val1 + val2);

}

/////////////////////////////////////////////////////////////

int add(int val1, int val2);int main() {

int i = 1, j = 2; return add(i, j); // linking error: add has not been defined

}

71

Common pitfalls of function declaration and function definition

int add(int val1, int val2);

int main() { int i = 1, j = 2; return add(i, j); // linking error: no

// add(int,int) is defined}

double add(double val1, double val2){

return (val1 + val2); }

72Department of Computer Science and Engineering, HKUST

Function

Function Overloading and Default Parameter

73

Function Overloading

It is sometimes difficult to name several similar (but different) functions with several different names int absInt(int i); double absDouble(double d); etc.

C++ allows using one name for several functions through function overloading.

74

Function Signature We use signature to identify a person. C++ also identify a function with its function signature,

which includes: Function Name. Types of Parameters. Number of Parameter.

No two functions in C++ can have the same signature. But a function with the same name, but with different type of

parameters can be co-exist. This is known as function overloading.

Note that return type and identifier of input parameters are not parts of function signature.

75

Function Overloading

For example, the following functions can be co-exist: int abs(int i); double abs(double d); long abs(long l);

Since all of them takes different types of input. Another example is:

int func(int a, int b); int func(int a, double b); int func(int a, int b, int c);

76

Function Overloading

However, the following functions cannot co-exist with the function int func(int a, int b); int func(int c, int d);

Identifiers of input parameters are not part of signature. double func(int a, int b);

Return type is not part of signature.

77

Function Overloading

// abs is overloaded in three ways

int abs(int i) { return i<0 ? -i : i; }

double abs(double d) { return d<0 ? -d : d; }

long abs(long l) { return l<0 ? -l : l; }

int main() {

cout << abs(-10) << “ “ << abs(-11.0) << “ “ << abs(-9L) << endl;

return 0;

}

C++ will determine which function should be called from the type of the actual parameters. The type of (first) parameter in abs(-11.0) is of double

type, C++ will call the version with single input parameter of double type automatically.

78

Function Resolution

When an actual parameter do not match the formal parameter, C++ will try to convert the actual parameter to the type of formal parameter.

This process is complicated in function overloading, for example, consider the following two overloaded functions:int abs(int i) { return i<0 ? -i : i; }

double abs(double d) { return d<0 ? -d : d; } Which version of function should be matched to the call of

abs(3.5f)? OR should 3.5f (float) be converted to int or double?

79

Function Resolution C++ formally defined a set of rules to handle this difficulties

arose in function overloading. The (long) list of rules are difficult to memorize and not very

useful in practical programming. We will not have too many functions overloaded in practice.

In general, the idea of the rules are: Exact match will be first used. “Non-narrowing” conversion will be secondly used. “Narrowing” conversion will be lastly used.

Therefore, “abs(-3.5f)” will call the double-version, which corresponds to a “non-narrowing” conversion, instead of int-version, which corresponds to a “narrowing” conversion.

80

Function Resolution

Consider the following two overloaded functionsint func(int a, double b);

int func(double a, int b); The function call func(3.2, 4.6) can either

Match to the first version by “narrowing converting” the first parameter to int.

Match to the second version by “narrowing converting” the second parameter to int.

No conversion is at higher priority than the other.

Ambiguity resulted (compile error). Use func( (int)3.2, 4.6 ) to call the first version.

81

Default Parameter

Sometimes, two overloaded functions are very similar, for example:void sort( int* array, int size );

void sort( int* array, int size, bool isAscending ); The first version sorts the array in ascending order, while the

second version sorts the array in an order specified by the flag isAscending.

Notice that the second function is a general case of the first function.

82

Default Parameter

To increase code reusability, C++ allows us to use default parameter

void sort( int* array, int size, bool isAscending = true ) {

if ( isAscending )

; // sort in ascending order

else

; // sort in descending order

}

Defining this function essentially defined two overloaded functions (underlying, not really define two functions). void sort(int*, int, bool); void sort(int*, int);

83

Default Parameter Note that default parameters must be specified at the end of the

parameter list.void sort( int* array, int size, bool isAscending = true ); But not

void sort( bool isAscending = true, int* array, int size ); A function can have several default parameters, for example,

int calcSomeResult(int a, float b, int c = 1, float d = 2); All default parameters should be at the end of the parameter list.

The default value (eg. true, 1, 2) can either be specified in function definition or function declaration, but not both. Usually, we put it in the function declaration. Therefore, the user of your function can know what is the default

value, without reading the implementation.

84

Default Parameter

Since the above function essentially defining two functions: void sort(int*, int, bool); void sort(int*, int);

If you define another function void sort(int*, int), and call the function sort( array, 10 ), this will result in ambiguity in the call of functions.

85

Example – Default Parameter (new slide)void sort( int* array, int size, bool isAscending = true ) {

if ( isAscending )cout << “ascending” << endl;

else cout << “descending” << endl;}

int main(){int array[] = {1,10,9,3,10};sort( array, 5, true );sort( array, 5, false );sort( array, 5 );return 0;

}

Output:

ascendingdescendingascending

86

Other forms of overloading

You can also define a function with any number of parameters (with the help of ellipses list). For example,

void printf( const char*, ...); Here, the three dots ... is a syntax in C++, which is called ellipses

list. It represents any number of parameters.

Therefore, we can call the printf with:printf( “Hello World” );

printf( “Give me %d dollars”, dollar );

printf( “Division tutorial: %f/%f = %f”, x, y, x/y);

87

Other forms of overloading Recall that one of the advantages of C++ is strongly typed.

The compiler will check the datatype for us. This reduces a large number of runtime errors.

Using ellipses list prevents the compiler from checking the type.int x = 10;printf( “ERROR example: %f\n”, x); No compile error but there is runtime error. It is not recommended to define a function with ellipses list.

Therefore, we will not cover how to define functions with ellipses list in this course.

If you really need to define a function with ellipses list, search the word “va_arg” in MSDN.

But you should know how to use it (printf) and aware it is possible to define a function with any number of parameters.

88Department of Computer Science and Engineering, HKUST

Function

Separate Compilation (Function Level)

89

Necessity of Separate Compilation Imagine that we have a program main.cpp that utilizes many

other functions, say sorting, searching. It is useful to keep the implementation of the sorting and

searching in separate files sort.cpp and search.cpp because: We can easily reuse the utilities in another (application) program. Team programming can be achieved easily: one teammate

implements sort.cpp, another implements search.cpp, and the last implements main.cpp. They can edit the code (in three files) concurrently.

When the sorting routines are changed, only sort.cpp needs to be compiled again (instead of recompilation of all three .cpp files), so the compilation is faster. 1 hour may be needed to compile all the code of a large software.

90

Header files

We may not want the user (say, Peter) who writes main.cpp to know the details of the sorting routines (which can be commercial secrets), we may want to give minimal information about the sorting routines to Peter. Just gives the function declaration of sorting routines to

Peter. OR Peter may not want to read the detail implementation of

the sorting routines, which is irrelevant to his job (writing a main.cpp). Peter just want to read the function declaration of sorting

routines, instead of the function definition.

91

Header files

Therefore, we usually separate the function definition and function declaration: Function declarations are written to a header file (.h file).

Sometimes, the header files may have extension .hpp. Function definitions are written to a source file (.cpp file).

Then, we can just give the file sort.h to Peter. This allows Peter know how to call (how many parameters, what’s

the return value, …) the function, say bubbleSort, in the main.cpp.

This also allows the compiler to determine whether there are any compilation error (invalid type of parameters, …).

92

Compilation

After Peter finished the coding of main.cpp, he has to provide both:

main.cpp sort.h

to the compiler. This will produce an immediate object file (main.o).

But without sort.cpp, C++ don’t know the exact implementation of bubbleSort. We must somehow tell C++ how to implement bubbleSort.

93

Linking If Peter can access the source code of sort.cpp (eg.

implemented by his teammate), he can simply gives sort.cpp to the compiler.

Otherwise, the library producer must provide a compiled object code (sort.o) to Peter. Peter can then link the sort.o with main.o (possibly link

with search.o) together. Linker is used to perform this linking.

In summary, the library producer should provides two files to Peter: sort.h (for compilation) sort.o (for linking)

94

(Incomplete) procedure for generating executable

File1.cpp

File2.cpp

FileN.cpp

File1.o

File2.o

FileN.o

a.out

Compilation Linking

…… ……

95Department of Computer Science and Engineering, HKUST

Function

Header Files

96

Header Files

Recall that Function declarations are usually placed in header files (*.h). Function definition are usually placed in source files (*.cpp).

The header file can then be given to other programmers to allow calling this function. We usually add comments for the functions in header files. Other programmers can read the *.h to know what the

function does, instead of reading the *.cpp to know how the function does.

97

Header Files

Header files can be used to store many things: Function Declaration Constant Definition Macro Definition (will be covered later …) Class Definition (will be covered later …) etc.

98

#include Recall that there are two versions to include a header file:

#include <iostream> // standard libraries#include “utility.h” // user-defined functions

To be more precise, The first version will instruct the preprocessor to search the file

iostream from the default include directories. The second version will instruct the preprocessor to search the file

utility.h from the current directory. By default, compiler will define the default include

directories as the paths of the standard libraries. You can also overrides this or add some more additional include

directories in preprocessor setting. You may add additional include directories when you install a third-

party library.

99

Preprocessing

To be more precise, there is a preprocessing step before the compilation. This step will be performed by the preprocessor.

The preprocessor will read all statements started with # in the first column. Never gives extra space before #include "...“.

Usually, compilation is regarded as the name of the procedure of preprocessing and then compilation. You can also view the result of preprocessed file by setting

the compiler options.

100

(Complete) procedure for generating executable

File1.cpp

File2.cpp

FileN.cpp

File1_temp.cpp

File2_temp.cpp

FileN_temp.cpp

File1.o

File2.o

FileN.o

a.out

Preprocessing Compilation Linking

…… …… ……

101

Preprocessing

There are a lot of preprocessor directives in C++. Some compilers will even define more directives themselves. In this course, we will cover only the following preprocessor

directives: #include #ifndef, #define, #endif

102

#include

#include is used to copy the content of the included file to replace the line of #include. However, this may introduce compile error when a header

file includes another header file.

const int WIDTH = 99;

constants.h

#include "constants.h"

void resize(int width = WIDTH*2);

screen.h

#include "screen.h"#include "constants.h"int main( ) { resize(WIDTH); return 0;}

main.cpp

103

#include

The preprocessor will Copy the content of screen.h to replace the line

#include “screen.h” in main.cpp Recursively copy the content of constants.h to replace

the line #include “constants.h” in screen.h Copy the content of constants.h to replace the line

#include “constants.h” in main.cpp

104

#include

After preprocessing, the main_temp.cpp becomes

which will be inputted to the compiler.

const int WIDTH = 99; void resize(int width = WIDTH*2);const int WIDTH = 99;

int main( ) { resize(WIDTH); return 0;}

main_temp.cpp

105

#include

Since a constant can only be defined once. The above preprocessed file will cause compile error

(WIDTH is redefined). To resolve this problem, we can use #ifndef (if not defined),

#define, and #endif #define can be used to define some symbols in

preprocessor. The code between #ifndef and #endif will be discarded by the

preprocessor if the symbol after #ifndef is defined.

106

#ifndef, #define, and #endif

We can use #ifndef, #define, and #endif to enclose the content of header files.

#ifndef CONSTANTS_H#define CONSTANTS_H

const int WIDTH = 99;

#endif // CONSTANTS_H

constants.h#ifndef SCREEN_H#define SCREEN_H

#include “constants.h”

void resize( int width = WIDTH*2);

#endif // SCREEN_H

screen.h#include “screen.h”#include “constants.h”int main( ) { resize(WIDTH); return 0;}

main.cpp

107

#ifndef, #define, and #endif

After including all header files, it becomes:

#ifndef SCREEN_H#define SCREEN_H#ifndef CONSTANTS_H#define CONSTANTS_Hconst int WIDTH = 99;#endif // CONSTANTS_Hvoid resize(int width = WIDTH*2);#endif // SCREEN_H#ifndef CONSTANTS_H#define CONSTANTS_Hconst int WIDTH = 99;#endif // CONSTANTS_H

int main( ) { resize(WIDTH); return 0;}

108

#ifndef, #define, and #endif

If we further process the preprocessor directives #ifndef, #define, and #endif, we have: 2nd Line: define symbol SCREEN_H 4th Line: define symbol CONSTANTS_H 5th Line: define constant integer WIDTH 7th Line: declare the function resize 9th Line: since the symbol CONSTANTS_H is already

defined, the whole block (9th – 12th Lines) are discarded Continue to define the main function.

109

#ifndef, #define, and #endif

The preprocessed file is:

No redefinition of constant WIDTH!!

// SCREEN_H is defined in preprocessor// CONSTANTS_H is defined in preprocessorconst int WIDTH = 99;void resize(int width = WIDTH*2);

int main( ) { resize(WIDTH); return 0;}

110Department of Computer Science and Engineering, HKUST

Function

g++ and Makefile

111

g++

g++ is a tool in UNIX (or Linux) to develop C++ programs. It can compile source code and link object files.

To build an executable with g++, we can:g++ main.cpp By default, this will produce an executable a.out by

compiling (and preprocessing) the single source code file main.cpp We can rename the executable with the -o option:

g++ -o myProgram main.cpp

g++ has many other options. Use man g++ for detail in Linux.

112

g++

When there are multiple source file, we can compile them together by typing, say:

g++ sort.cpp search.cpp main.cpp This will also output a.out by default.

113

g++ To compile (but not link) a .cpp file with g++, we can:

g++ -c main.cpp By default, this will produce an object file main.o by

compiling the source code file main.cpp We can rename the object file with the -o option

g++ -o myObjectFile.o –c main.cpp Similarly, we can:

g++ -c sort.cppg++ -c search.cpp

After producing three object files, we have to link them together:

g++ sort.o search.o main.o This will, by default, output a.out; rename with -o option.

114

g++

Compiling the .cpp into object files separately can speed up the compilation.

A large project may contain 100000 .cpp files and build an executable may take 1 hour. A small change to the code will take another hour to compile

the whole program again. However, if we compile each .cpp into an object file, we only

need to recompile one object file and re-link the object files together, when there is a change in a .cpp file.

This significantly improve the efficiency of compilation.

115

Makefile

Although compiling source files into multiple object files can improve the compilation efficiency, it is tedious to manage the process of compilation and linking of program code. If a single file (say search.cpp) changed, we have to

recompile the search.cpp by the command: g++ -c search.cpp

Then, we have to re-link the three object files (one of them has been updated) together by the command:g++ sort.o search.o main.o

116

Makefile Now, imagine that the header file (sort.h) has been

changed (and we assume that main.cpp) has included the sort.h main.o must be recompiled.

Some functions guaranteed to be defined in sort.h previously may have been removed.

Without recompilation, the main.cpp may call some non-exist function and resulted in linking error (reason is on next slide).

Therefore, we have to reproduce the executable by:g++ -c sort.cppg++ -c main.cppg++ sort.o search.o main.o

To make this process easier, we can make use of Makefile.

117

Calling non-exist function Why is it possible to call a non-exist function? For example:

If the header file declared two overloaded functions:int abs( int val );double abs( double val );

If main.cpp included this header file, the call abs( -3.2 ) will be matched to the double-version of abs (exact match).

Now, if the design is modified so that the double-version of abs has been removed, and further assume the main.cpp has not been recompiled.

The main.o contains the information that it will call the double-version of function call.

In the linking process, the linker cannot find the definition of the double-version of abs linking error resulted.

118

Calling non-exist function

If we recompile main.cpp, the function call will now be matched to the int-version of abs (automatic conversion), no linking error will occur. Although the logic in main.cpp may most-likely need to be

changed (in this example) and resulted in a modification in main.cpp recompilation of main.cpp

Designer of Makefile should make sure the result of separate compilation should be the same as that of compiling the whole program from scratch.

119

Makefile

Makefile is a plain text file that specifies the relationship of the program code and states the commands for updating each file. According to rules defined in the file, pieces of a large

program which need to be recompiled can be determined automatically.

For those don’t need to be re-compiled, they are just by-passed and thus avoiding redundant compilation (faster compilation).

120

Makefile

After writing the Makefile, you can now use the tool make to produce the executable.Simply type “make” By default, make will look for the file makefile. If not found,

it will search the file Makefile, … UNIX Filesystem is case sensitive.

If your Makefile is not named as the default ones, you can use the -f option to specify your Makefile. Eg. make -f MyMakeFile

121

A Simple Example of Makefile

myProgram: main.o search.o sort.og++ -o myProgram main.o search.o sort.o

main.o: main.cpp sort.h search.h

g++ -c main.cpp

sort.o: sort.cpp sort.h

g++ -c sort.cpp

search.o: search.cpp search.h

g++ -c search.cpp

clearAll:

rm -f *.o myProgram

122

Explanation

Lines 1-2 forms a rule A rule has a dependency line follows by some action lines

Line 1: a dependency line that indicates the file myProgram depends on three object files main.o, sort.o and search.o

Line 2: an action line or a command that will be executed if one or more of the .o files specified in the dependency line has been modified. By comparing the modification time of the files myProgram

and *.o

123

Explanation Action lines

There can be zero, one, or more than one action lines following a dependency line.

Each action line must begin with a tab character. It should be careful enough that tab is not equivalent to

several spaces. The general format of a rule is:

<Target>: dependencies separated by space<tab> action1<tab> action2<tab> ...

A Makefile can contain many rules.

124

Explanation make will by default run the first rule of the Makefile.

To run a different rule, for example, make cleanAllto run the rule named cleanAll.

When “make” runs a rule, it will first examine the corresponding dependency line. If that line specify another target (rule in the Makefile), it will

run that rule first. After that, it will determine whether the file has been

modified. If no modification is found, it will stop running that rule.

Otherwise, it will run the action line’s commands.

125

Explanation (Scenario 1) No object file and executable has been generated (at the

beginning). When we type “make”, it will run the first rule (ie. myProgram) by

default. It will scan the dependency line and found the first dependency

main.o Since main.o is another target, run the rule named main.o It will then scan the dependency line and found that the two

dependencies are not another target. It will compare the modification time of main.o and its dependencies. Since main.o do not exist, it will run the action line g++ -c main.cpp

Similarly, it will run the action lines of targets: sort.o and search.o

Then, since myProgram do not exist, it will run the action line:g++ -o myProgram main.o sort.o search.o

126

Explanation (Scenario 2) sort.cpp has been modified after generation of all object

code. “make” will run the first rule (ie. myProgram) by default. It will scan the dependency line and found the first

dependency main.o Since main.o is another target, run the rule named main.o It will then scan the dependency line and found that the two

dependencies are not another target. It will compare the modification of main.o and its dependencies. Since main.o should have a later modification time than main.cpp,

sort.h, search.h, it will not run the action line g++ -c main.cpp to avoid duplicated compilation.

Similarly, no action will be performed for the dependency search.o

127

Explanation (Scenario 2)

It will then encounter the dependency sort.o, it will go to the rule sort.o Since sort.cpp has been modified after sort.o is

generated, the action line g++ -c sort.cpp is performed. After that, it compares the modification time of myProgram

and *.o Since sort.o is just compiled, its modification time is later

than myProgram, hence the action line.g++ -o myProgram main.o search.o sort.o is performed to generate a new myProgram.

128

Explanation (Scenario 3)

sort.h has been modified after generation of all object files. “make” will run the first rule (ie. myProgram) by default. It will scan the dependency line of myProgram.

Since both rules main.o and sort.o depends on the file sort.h, their action lines will be run when make is examining the dependency of the rule myProgram.

Since search.o do not depend on the file sort.h, search.o will not be recompiled.

After that, it generates a new myProgram, since the object files sort.o and main.o have just been modified.

129

Explanation (Scenario 4)

No files have been modified after generation of all object files. “make” will run the first rule (ie. myProgram) by default. It will scan the dependency line and found the first dependency

main.o Since main.o is another target, run the rule named main.o

Since main.o is later than main.cpp, sort.h and search.h, no compilation is carried out to generate main.o

Similarly, no compilation for sort.o and search.o Since main.o, sort.o, and search.o have not been modified,

myProgram will not be re-generated.

130

Explanation (Scenario 5)

sort.o is removed by the command “rm”. sort.o will be generated since sort.o do not exist. Re-linking main.o, sort.o and search.o to generate

myProgram, since sort.o is newly created. Although myProgram should be the same as before (no

source code has been modified).

131

Explanation (Scenario 6)

make cleanAll is typed in the console. “make” will not run the first rule. Instead, “make” will run the rule “cleanAll” directly. Since there is no dependencies in the rule “cleanAll”, the

action line is performed automatically. It will then remove the object files *.o and myProgram.

132

Hints for Writing Makefile By default, make will run the first rule.

Except when we call the rule directly. The first rule is for the final executable.

It depends on all object files (*.o). Each *.o will corresponds to a single rule, which is

generated by compiling a single .cpp file. The dependency list contains the source.cpp Contains all the header files it has included. Recursively find the inclusion of other header files in each

such header file. The name of each target (myProgram or *.o) is the file

name to be generated by that rule.

133Department of Computer Science and Engineering, HKUST

Function

Function Execution in Runtime

134

Program Call Stack When a function (say, funA) calls another function (say,

funB), the program execution is transferred to the funB. After funB is finished, the program execution should be

able to return to the next statement after the function call funB in funA.

This is usually implemented with a stack (called program call stack). A stack is a last-in-first-out data structure Data can be inserted into it (called push) Data can be removed from it (called pop) The last data inserted into it will be popped first (last-in-first-

out)

135

Program Call Stack There is an activation record stored in the program call stack for

each function call in runtime. To store the local variables, … of that function.

For example, when the program executing funA, the topmost record in the stack will be corresponding to funA.

When funA tries to call funB, the activation record of funB will be created and pushed to the program call stack.

After funB returns, the topmost record in the stack will be popped and then the program can continue the remaining task in funA.

The bottommost record in the program call stack may be some OS program startup routines. But we usually only concerns the records corresponds to main and

all the records above only.

136

Program Call Stack

void funA( ) { /* … */ } void funB( ) { funA( ); } void funC( ) { funA( ); funB( ); } int main( ) { funA( ); funC( ); return 0; }

main main

funA

main main

funC

main

funC

funA

main

funC

main

funC

funB

main

funC

funB

funA

main

funC

funB

main

funC

main

137

Activation Record

This is used to store some information about an invocation of a function, including: Local variables. and many other things (COMP251).

Since the set of local variables are stored in program call stack, they will be disappeared when the function returns (record is popped out).

138Department of Computer Science and Engineering, HKUST

Function

Static Variables in Function Definition

139

Static Variables

Local variables are stored in activation record, which is placed in Program Call Stack. Once the function returns, its local variables will be

disappeared. Sometimes, we need to store information across multiple

calls of the function. Static variable is a variable that won’t be deleted after

function returns. It is not stored in activation record.

140

Static Variables - Example

char* strtok(char* str, const char* delimiters){static char* nextStart = NULL;if ( str != NULL )

nextStart = str;// ...

} The line that defines nextStart will only run when the function is

first called. In the second call, the nextStart will not be re-initialized. It will keep

the value at the end of the 1st function call. You may not understand the above code, without understanding

the pointer. Read this after learning pointer.

141Department of Computer Science and Engineering, HKUST

Function

Inline Function and its Compilation Model

142

Function Call is Slow

Calling a function involves: Creation of activation record …

This will be expensive, especially when the function to be called consists of a few lines of code. To improve the runtime efficiency and maintain the code

reusability, C++ introduces inline function. Inline function means the function will not be really called in

runtime. Instead, it seems that the inline function are copied to the

function caller to obtain a more complicated function.

143

Example of Inline Functionsinline int minimum( int x, int y ) {

return (x < y? x : y);}void f( ) {

cout << minimum(5,6) << endl;cout << minimum(2,1) << endl;

} The keyword inline is used to give a hint to the compiler that you

want the function to be inlined. Then, it seems that the function f is rewritten as

void f( ) {cout << (5 < 6? 5 : 6) << endl;cout << (2 < 1? 2 : 1) << endl;

} This will not call the function minimum at runtime, so it is more

efficient.

144

Inline all functions?

Then, why don’t we inline all functions Sometimes, we cannot inline a function

More about this later (when we are talking about recursion) Therefore, it is a hint to the compiler only

Inline will copy the code to the function caller, this will increase the size of the binary (compiled code)

Tradeoff between code size and runtime efficiency

145

Compilation Model for Inline functions

Since the compiler needs to replace the function call with the definition of the function. We have to provide the function definition to the function

caller during compilation. Instead of only providing function declaration.

Therefore, header file should include the definition of the inline functions (this is different from normal functions). For example, in utility.h

inline int minimum( int x, int y ) { return (x < y? x : y);

} In main.cpp, use the line #include “utility.h” to use

the above function.

Recommended