Chapter 3
Chapter 3
- Procedure: A routine that can accept arguments but does not return any values
- Function: A routine that can accept arguments and returns one or more values
- User-defined routine (UDR): A generic term that includes both user-defined
procedures and user-defined functions
If a developer creates the functions and puts all the declarations in a files
Header file (*.h), users need to write just this statement: #include <filename.h>
- Header file (usually has extension of *.h, *.hxx, …, but not necessary)
- Source code file (*.c, *.cpp, *.cxx, …) or destination file (*.obj, *.o, *.lib, *.dll,…)
Passing arguments and returning values are the core basis to create and use functions,
which is crucial to decide the software quality
- Data types of the returning values can be anything, EXCEPT an ARRAY (directly)
- It can be: Values; Pointers; References
- Never return a pointer or a reference of a local variable
- Never return a pointer or a reference of an argument which has been passed with a
value
Return a pointer
#include <iostream>
int* FindMax(int* p, int n) {
int *pMax = p;
int *p2 = p + n;
while (p < p2) {
if (*p > *pMax)
A function to return the pMax = p;
address of the element ++p;
with the largest value in }
an array return pMax;
}
void main() {
int s[5] = { 1, 2, 3, 4, 5};
int *p = FindMax(s,5);
}
Why do we need to return a pointer or a reference?
- Size of the argument’s data type is large; thus, it can avoid copying large data to
stack
- When it is required to access directly and change output values
- How to return a pointer or a reference
Assign it to a global variable
Assign input argument of the function via address or reference
Should be assigned to a memory allocation which still exists after exiting
function
int* f1(int a) { Wrong
... This function takes an integer a by value, meaning a is a local
return &a; copy.
} Returning &a (address of a local variable) leads to undefined
behavior because the memory for a is deallocated when the
function returns.
int& f2(int &a) { Correct
... This function takes an integer by reference, so a refers to a
return a; variable outside the function's scope.
} Returning a by reference is valid since a is not a local variable.
Pointer is a variable
#include <stdio.h>
void callback_func(){
printf(“I am a call back function”);}
void f(void (*ptr)){
ptr(); // a call-back function that p point to
}
int main(){
void (*p)() = callback_func;
f(p);}
This is a simple function called
void callback_func() { callback_func that prints a message to the
printf("I am a callback function"); console.
} It does not take any arguments and does
not return a value (void).
The function f takes a function pointer
void f(void (*ptr)()) { as its parameter: void (*ptr)() means ptr
ptr(); // Call the function that is a pointer to a function that takes no
'ptr' points to arguments and returns void.
}
Inside f, the function pointer ptr is used to
call the function it points to (ptr()).
A function pointer p is declared and
initialized to point to the callback_func
function:
int main() { - void (*p)() means p is a pointer to a
void (*p)() = callback_func; //
function that takes no arguments
Declare and initialize a function
pointer
and returns void.
f(p); // - p = callback_func assigns the
Pass the function pointer to 'f' address of the callback_func
} function to p.
The function f is then called, with p passed
as its argument: f(p) passes the pointer p
to f, and inside f, ptr() calls the function
callback_func via the pointer.
Another example of function pointer with -qsort
// An example for qsort and comparator
#include <stdio.h>
#include <stdlib.h>
int compare (const void * a, const void * b) // this is a call-back function
{return (*(int*)a - *(int*)b);}
int main (){
int arr[] = {10, 5, 15, 12, 90, 80};
int n = sizeof(arr)/sizeof(arr[0]), i;
qsort (arr, n, sizeof(int), compare);
for (i=0; i<n; i++)
printf ("%d ", arr[i]);
return 0;
}
Purpose: This is the comparator function used
by qsort. It defines the sorting logic.
Parameters:
- const void* a and const void* b: These are
generic pointers (void*) used to make the
function compatible with any data type.
- *(int*)a and *(int*)b: The void* pointers are
int compare (const void * a, const void * cast to int* to dereference them and
b){ access the integer values.
return (*(int*)a - *(int*)b); Logic: The return value determines the order of
} the two elements:
- Negative: a should come before b.
- Zero: a and b are equal.
- Positive: a should come after b.
Here, the subtraction *(int*)a - *(int*)b computes
the difference between the two integers, which
implicitly defines their order.
int main() {
int arr[] = {10, 5, 15, 12, 90, 80}; arr is an integer array with 6 elements
int n = sizeof(arr) / sizeof(arr[0]),
i; n is the number of elements in the array,
qsort(arr, n, sizeof(int), compare);
calculated as sizeof(arr) / sizeof(arr[0]).
for (i = 0; i < n; i++)
printf("%d ", arr[i]);
return 0;
}
qsort(arr, n, sizeof(int), compare);
Command-line arguments
- argc is an integer that represents the number of arguments passed to the program
via the command line.
- The first argument (argv[0]) is always the name of the program itself.
char yes_or_no(void){
char answer;
printf (“Please enter 'y' or 'n':”);scanf (“%c”, &answer);
if(answer != 'y' && answer != 'n') answer = yes_or_no();
return answer;
}
When to use recursion?
Recursion summary:
Time/Date
Time zone
#include <stdio.h>
#include <time.h>
#define PST (-8) // Pacific Standard Time is 8 hours behind UTC
#define CET (1) // Central European Time is 1 hour ahead of UTC
int main () {
time_t raw_time; // Variable type time_t to store current time in sec
struct tm *ptr_ts; // pointer to a tm structure, which holds broken-down
time
time ( &raw_time ); // retrieves current time in sec & stores in raw_time
ptr_ts = gmtime ( &raw_time ); //gmtime() converts raw_time (seconds) into
UTC and stores the result as a struct tm in ptr_ts.
printf ("Time Los Angeles: %2d:%02d\n", ptr_ts->tm_hour+PST, ptr_ts-
>tm_min);
printf ("Time Amsterdam: %2d:%02d\n", ptr_ts->tm_hour+CET, ptr_ts->tm_min);
printf ("Time Hanoi: %2d:%02d\n", ptr_ts->tm_hour+ 7, ptr_ts->tm_min);
return 0;}
Random number
- Return a random number within 0 and
int rand(void); RAND_MAX (inclusive)
- RAND_MAX: 32767
- Seed value of an array created, usually start
void srand(unsigned seed); whenever rand is called
time(0):
- Returns the current time in
seconds
- Using time(0) ensures the seed is
different each time the program
runs, leading to different random
sequences
String
- More precise definition: A character array terminates with a null character (the last
element is ‘\0’)
char pet[5];
Equivalent Initialization
pet[0] = 'l'; pet[1] = 'a'; pet[2] =
'm';
#include <stdio.h>
int main(){
char str1[10], str2[] = "Saturday";
int i = 0;
while (str2[i] != '\0') {
str1[i] = str2[i];
i = i + 1;
} // Self-assign strings
str1[i] = '\0';
printf("%s",str1);
}
Open a file
- Open a file: create a link between the OS (filename) and the C program (file
variable)
Function in the library: fopen
Identify parameters “r” to read the file and “w” to write to the file
- File must be opened before being used
- File stdin/stdout (used by scanf/printf) is opened automatically and linked to the
keyboard and screen respectively
Closing file
End of file
- Defined in stdio.h
- #define EOF (a certain negative value)
Usually -1
Input/output functions in the library use EOF to indicate the end of file
Programs can also use EOF
- Attention: EOF is the state, not an input value
putc() and fputc() are the same getc() and fgetc() are the same
Formatting I/O
int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
Binary I/O
- When reading and writing a binary file, the program does work with the object
directly instead of converting this object into a character array
- Binary I/O includes:
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);
- To write structures to a file
struct Astruct mystruct[10];
fwrite(&mystruct, sizeof(Astruct), 10, fp);
Parameters:
- FILE *fp: A pointer to the file stream
- long offset: The number of bytes to move the pointer
- int from: The reference position for the movement, which can
int fseek(FILE *fp, be:
SEEK_SET: From the beginning of the file
long offset, int from);
SEEK_CUR: From the current position of the file pointer
SEEK_END: From the end of the file
Return Value:
- Returns 0 on success
- Returns non-zero on failure
Purpose: Moves the file pointer back to the beginning of the file.
- By using fopen, fclose, fscanf and fprintf, developers can write many applications
related to file
- Many errors and exceptions can occur when working with files
A robust application must handle the errors well
The skills will be acquired gradually
Files in C++
#include <iostream.h>
#include <fstream.h>
ifstream fin; // input
Define a variable: ofstream fout; // output
fstream fio; // input and output
fin.open("file1.txt");
Open/Create a file: fout.open("file2.dat");
fio.open("file3.inf");
ifstream fin("file1.txt"); // input
Declare variables and open/create a ofstream fout("file2.inf");// output
file together fstream fio("file3.dat"); // input and
output
Write data into a file fout << "Caitlyn Kiramman" << endl;
- Similar to cout fout << 23 << endl<< false;
- A file may contain mixed data types,
e.g.:
char name[32];
int age, married;
Read data from a file (similar to cin)
fin.getline(name,32);
fin >> age >> married;
Close a file fin.close();
- Automatically performs when a fout.close();
scope ends fio.close();
- Or call close();
- In C++, it is possible to create multiple functions with the same name, e.g.:
int max(int a, int b);
double max(double a, double b);
double max(double a, double b, double c);
double max(double* seq, int n);
- The purposes of function overloading is
- Simplify function naming (instead of maxInt, maxDouble, maxDouble3,
maxDoubleSequence, …)
- Make it easy for function users who need to remember only one familiar name
- Functions with the same name defined in one file / a library or used in the same
program must be different in:
Number of arguments or type of at least one argument (int ≠ short, const int ≠ int,
int ≠ int&, ...)
They cannot be different only in type of returning value
- Why?
Compiler need to decide which functions to call
Based on call syntax (number and type of actual arguments) compiler will
choose the most suitable functions
Compile can convert type automatically in the most reasonable manner (e.g.:
short=>int, int=> double)
- Problem: a normal function is useful but not very high efficient, especially if the
implementation code is short
The procedure of recording program states, allocating memory, copying
arguments, copying returning values, recovering the program states consume
too much time
If the function code is short, the usefulness cannot compensate for large time
consuming
- Solution in C: Using macro, e.g.
#define max(a,b) a>b?a:b
Problem: macro is executed by preprocessor, type is not checked, program
cannot differentiate used context thus, it results in undesired effects.
E.g.: the statement l=max(k*5-2,l); is replaced by l=k*5-2>l?k*5-2:l; //OOPS
- The method of using bracket even make the code harder to read while cannot
solve the weakness completely
- Put the keyword inline at the beginning of function declaration and definition
inline int max(int a, int b) {
return (a > b) ? a : b;
}
Is replaced by:
int x=k*5-2; // temporary variable
l=(x>1)?x:1; // OK
- Advantages:
Useful as a normal function
As efficient as direct source code, not calling function
More reliable and safer than using Macro
- Disadvantages:
If the function is called too many times in the program, the source code will
grow bigger (implementation code appears multiple times in the program)
Function definition code must be open keep in header file
- Create and use inline function when
Implementation code of the function is short (just a few line, no loop)
Speed is the priority rather than memory capacity