227 lines
5.9 KiB
Markdown
227 lines
5.9 KiB
Markdown
# CSE332S Object-Oriented Programming in C++ (Lecture 5)
|
|
|
|
## Function and the Call Stack
|
|
|
|
### Function lifecycle
|
|
|
|
Read variable declaration from right to left
|
|
|
|
eg:
|
|
|
|
```c
|
|
int i; // i is a integer variable
|
|
int & r = i; // r is a reference to i
|
|
int *p = &i; // p is a pointer to i
|
|
const int * const q = &i; // q is a constant pointer to a constant integer
|
|
```
|
|
|
|
Read function declaration from inside out
|
|
|
|
eg:
|
|
|
|
```c
|
|
int f(int x); // f is a function that takes an integer argument and returns an integer
|
|
```
|
|
|
|
Cpp use the "**program call stack**" to manage active function invocations
|
|
|
|
When a function is called:
|
|
|
|
1. A stack frame is "**pushed**" onto the call stack
|
|
2. Execution jumps fro the calling functio's code block to the called function's code block
|
|
|
|
Then the function is executed the return value is pushed onto the stack
|
|
|
|
When a function returns:
|
|
|
|
1. The stack frame is "**popped**" off the call stack
|
|
2. Execution jumps back to the calling function's code block
|
|
|
|
The compiler manages the program call stack
|
|
|
|
- Small performance overhead associated with stack frame management
|
|
- Size of a stack frame must be known at compile time - cannot allocate dynamically sized objects on the stack
|
|
|
|
#### Stack frame
|
|
|
|
A stack frame represents the state of an active function call
|
|
|
|
Each frame contains:
|
|
|
|
- **Automatic variables** - variables local to the function. (automatic created and destroyed when the function is called and returns)
|
|
- **Parameters** - values passed to the function
|
|
- **A previous frame pointer** - used to access the caller's frame
|
|
- **Return address** - the address of the instruction to execute after the function returns
|
|
|
|
### Recursion for free
|
|
|
|
An example of call stack:
|
|
|
|
```cpp
|
|
void f(int x) {
|
|
int y = x + 1;
|
|
}
|
|
void main(int argc, char *argv[]) {
|
|
int z = 1;
|
|
f(z);
|
|
}
|
|
```
|
|
|
|
when f is called, a stack frame is pushed onto the call stack:
|
|
|
|
- function `f`
|
|
- parameter `x`
|
|
- return address
|
|
- function `main`
|
|
- parameter `argc`
|
|
- parameter `argv`
|
|
- return address
|
|
|
|
On recursion, the call stack grows for each recursive call, and shrinks when each recursive call returns.
|
|
|
|
```cpp
|
|
void f(int x) {
|
|
if (x > 0) {
|
|
f(x - 1);
|
|
}
|
|
}
|
|
int main(int argc, char *argv[]) {
|
|
f(10);
|
|
}
|
|
```
|
|
|
|
The function stack will look like this:
|
|
|
|
- function `f(0)`
|
|
- parameter `x`
|
|
- return address
|
|
- function `f(1)`
|
|
- parameter `x`
|
|
- return address
|
|
- ...
|
|
- function `f(10)`
|
|
- parameter `x`
|
|
- return address
|
|
- function `main`
|
|
- parameter `argc`
|
|
- parameter `argv`
|
|
- return address
|
|
|
|
### Pass by reference and pass by value
|
|
|
|
However, when we call recursion with pass by reference.
|
|
|
|
```cpp
|
|
void f(int & x) {
|
|
if (x > 0) {
|
|
f(x - 1);
|
|
}
|
|
}
|
|
int main(int argc, char *argv[]) {
|
|
int z = f(10);
|
|
}
|
|
```
|
|
|
|
The function stack will look like this:
|
|
|
|
- function `f(0)`
|
|
- return address
|
|
- function `f(1)`
|
|
- return address
|
|
- ...
|
|
- function `f(10)`
|
|
- return address
|
|
- function `main`
|
|
- parameter `z`
|
|
- parameter `argc`
|
|
- parameter `argv`
|
|
- return address
|
|
|
|
This is because the reference is a pointer to the variable, so the function can modify the variable directly without creating a new variable.
|
|
|
|
### Function overloading and overload resolution
|
|
|
|
Function overloading is a feature that allows a function to have multiple definitions with the same name but **different parameters**.
|
|
|
|
Example:
|
|
|
|
```cpp
|
|
void errMsg(int &x){
|
|
cout << "Error with code: " << x << endl;
|
|
}
|
|
void errMsg(const int &x){
|
|
cout << "Error with code: " << x << endl;
|
|
}
|
|
void errMsg(const string &x){
|
|
cout << "Error with message: " << x << endl;
|
|
}
|
|
void errMsg(const int &x, const string &y){
|
|
cout << "Error with code: " << x << " and message: " << y << endl;
|
|
}
|
|
int main(int argc, char *argv[]){
|
|
int x = 10;
|
|
const int y = 10;
|
|
string z = "File not found";
|
|
errMsg(x); // this is the first function (best match: int to int)
|
|
errMsg(y); // this is the second function (best match: const int to const int)
|
|
errMsg(z); // this is the third function (best match: string to const string)
|
|
errMsg(x, z); // this is the fourth function (best match: int to const int, string to const string)
|
|
}
|
|
```
|
|
|
|
When the function is called, the compiler will automatically determine which function to use based on the arguments passed to the function.
|
|
|
|
BUT, there is still ambiguity when the function is called with the same type of arguments.
|
|
|
|
```cpp
|
|
void errMsg(int &x);
|
|
void errMsg(short &x);
|
|
int main(int argc, char *argv[]){
|
|
char x = 'a';
|
|
errMsg(x); // this is ambiguous, cpp don't know which function to use since char can both be converted to int and short. This will throw an error.
|
|
}
|
|
```
|
|
|
|
#### Default arguments
|
|
|
|
Default arguments are arguments that are provided by the function caller, but if the caller does not provide a value for the argument, the function will use the default value.
|
|
|
|
```cpp
|
|
void errMsg(int x = 0, string y = "Unknown error");
|
|
```
|
|
|
|
If the caller does not provide a value for the argument, the function will use the default value.
|
|
|
|
```cpp
|
|
errMsg(); // this will use the default value for both arguments
|
|
errMsg(10); // this will use the default value for the second argument
|
|
errMsg(10, "File not found"); // this will use the provided value for both arguments
|
|
```
|
|
|
|
Overloading and default arguments
|
|
|
|
```cpp
|
|
void errMsg(int x = 0, string y = "Unknown error");
|
|
void errMsg(int x);
|
|
```
|
|
|
|
This is ambiguous, because the compiler don't know which function to use. This will throw an error.
|
|
|
|
We can only default the rightmost arguments
|
|
|
|
```cpp
|
|
void errMsg(int x = 0, string y = "Unknown error");
|
|
void errMsg(int x, string y = "Unknown error"); // this is valid
|
|
void errMsg(int x = 0, string y); // this is invalid
|
|
```
|
|
|
|
Caller must supply leftmost arguments first, even they are same as default arguments
|
|
|
|
```cpp
|
|
void errMsg(int x = 0, string y = "Unknown error");
|
|
int main(int argc, char *argv[]){
|
|
errMsg("File not found"); // this will throw an error, you need to provide the first argument
|
|
errMsg(10, "File not found"); // this is valid
|
|
}
|
|
```
|