Functions
-
Let’s review functions. Remember that functions are like black boxes that take 0 or more inputs (aka arguments or parameters) and return 0 or 1 output. Functions might also have a side effect (e.g. print something to the console).
Defining Functions
-
To create your own custom functions that don’t come with C++ by default, use the following as a model
int add(int a, int b) { (1) int sum = a + b; return sum; (2) }
1 int add(int a, int b)
is known as the function’s header or signature. It starts with a return type, which isint
in this case. If a function does not return a value, writevoid
. Then comes the name of the function (add
), followed by parentheses inside of which you list the function’s parameters (their data types and names) separated by commas:(int a, int b)
. If a function does not take any input and has zero parameters, write empty parentheses:()
. After the function’s header comes the body of the function defined within curly braces{}
.2 The body of the function includes a return
statement where the function return a value whose type matches the return type specified in the function’s header. -
The above is an example of a function’s definition or implementation.
Declaring Functions
-
The convention is to define functions below
main
. However, if you use (i.e. call) a custom function inmain
(or in any other function defined above the definition of the function you are calling), C++ has to know that that function exist, since the compiler reads code top to bottom, left to right. The solution is to write function declarations (aka prototypes) abovemain
. -
A prototype (declaration) consists of a function’s header followed by a semicolon:
int add(int a, int b);
-
Be sure to write RMEs above any of your own functions' declarations.
RME
-
Additionally, when we define functions (except for
main
) in EECS 183, we create a multi-line RME comment, which includes Requires, Modifies and Effects clauses.- Requires
-
Indicates what the function assumes about its inputs (arguments / parameters). The function’s behavior specified in the Effects clause is guarnteed only if the Requires clause is satisfied.
- Modifies
-
Indicates what the function modifies outside of its own scope. For example,
getline
reads from an input stream and stores the value it read into a string variable that is passed into its second argument. For now, your own functions will should not modify anything, except forcin
andcout
. - Effects
-
Indicates what the function does and specifies what it returns (if it returns a value).
-
Here’s an example of an RME for the
sqrt
function./** * Requires: n >= 0. * Modifies: Nothing. * Effects: Returns square root of n. */ double sqrt(double n);
Examples
-
Here’s a small example of a full program declaring, defining and using a function:
#include <iostream> using namespace std; int add(int a, int b); (1) int main() { int sum = add(1, 2); (2) cout << sum << endl; } /** * Requires: Nothing. * Modifies: Nothing. * Effects: Returns the sum of a and b. */ int add(int a, int b) { (3) int sum = a + b; return sum; }
1 Prototype / declaration. 2 Function call. 3 Implementation / definition. -
Take a look at
functions-0.cpp
and atfunctions-1.cpp
in Week 4 source code for more examples of function declarations, definitions and calls. -
Which is/are valid function prototype(s)?
-
int getInt(void i);
-
printInt(int n);
-
string getString();
-
int square(int n, 2);
-
char capitalize(char c);
Answers: (C) and (E).
-
Remember that we are looking for a valid prototype (declaration).
-
In (A), the type of
i
isvoid
, which is not a valid data type (but it is a valid return type of a function). -
The prototype in (B) lacks a return type before
printInt
. -
(C) is a prototype of a function that doesn’t take any inputs and returns a
string
. -
In (D),
2
should not be inside the parentheses because it is a literal value. Remember that we are looking for a valid function prototype, not a function call. The parameter list in prototypes (declarations) and in implementations (definitions) of functions should have type and names of parameters. -
(E) is a prototype of a function that takes a
char
and returns anotherchar
that is a capitalized version thereof.
-
-
-
Which of the following is a valid function call, given the prototype below?
void foo(int x, double y);
-
foo(int x, double y);
-
cout << foo(1, 42.5);
-
x = foo(1, 42.5);
-
foo(1.0, 42);
Answer: (D).
-
Realize that
foo
is a function that takes two inputs and does not return a value. The question asks for a valid function call (usage offoo
). -
In (A), we specify the types of
x
andy
, which we should not do when calling a function. -
In (B), we print the return value of
foo
. However,foo
does not return a value, so the code would not compile. -
In (C), we assign the return value of
foo
tox
, butfoo
does not return a value, so the code would again not compile. -
In (D), we call
foo
and pass1.0
and42
as arguments. Even though1.0
is adouble
, we can use it as the first argument, just like we can assign the value of1.0
to a variable of typeint
. Similarly, we can use42
as the first argument, just like we can assign the value of42
to a variable of typedouble
. We don’t do anything else wefoo
, sincefoo
does not return a value. (foo
presumably has a side effect.)
-
-
Scope
-
Local variables (defined inside functions) are known within functions wherein they were defined.
If a variable called
numberOfCookies
is defined inmain
, then no other function can access or use it. If another function also defines a variable callednumberOfCookies
, then themain
and that other function will only know about their ownnumberOfCookies
, since they don’t have access to each other’s local variables. -
Local variables are passed by value to other functions.
This means that other functions receive a copy of the value, not the variable itself. This means that the variable in the original function is unchanged unless overwritten.
-
In this example,
doubleScore
has no effect onplayerScore
declared inmain
:int doubleScore(int score); int main() { int playerScore = 100; doubleScore(playerScore); } int doubleScore(int score) { score = score * 2; return score; }
-
And here, the value of
playerScore
is overwritten by the return value ofdoubleScore
:int doubleScore(int score); int main() { int playerScore = 100; playerScore = doubleScore(playerScore); (1) } int doubleScore(int score) { score = score * 2; return score; }
1 Notice how the value of playerScore
is set to the return value ofdoubleScore
. -
Here’s another example. Try to figure out what this program prints:
scope.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28#include <iostream> using namespace std; // prototypes void foo(); void bar(); // global variable int x = 5; int main() { cout << x << endl; int x = 7; cout << x << endl; foo(); cout << x << endl; bar(); } void foo() { x = 10; int x = 42; cout << x << endl; } void bar() { cout << x << endl; }
Swap
-
We often want to swap two variables' values in programs. Suppose we have two integers,
a
, that has a value of2
, andb
, that has a value of5
. After swappinga
andb
, we wanta
to have a value of5
andb
to have a value of2
. -
To illustrate how to swap to variables, recall that we can think of variables as containers that can hold something. Then suppose we have two cups: one has some orange juice and the other one has milk. If we want to swap the contents of the two cups, we must use a third cup (let’s call it
temp
).[1] We can pour orange juice into thetemp
cup, then we can pour milk into the cup that used to hold orange juice, and finally, we’ll pour orange juice from thetemp
cup into the cup that used to hold milk. -
Similarly, to swap two variables' values, we need a third, temporary variable. Here’s an example:
int temp = a; a = b; b = temp;
It’s also possible to swap two variables' values without a temporary variable (in most cases). Here’s how. Suppose
a
has a value of2
andb
has a value of5
.a = a + b; (1) b = a - b; (2) a = a - b; (3)
1 a
is now7
.2 b
is now2
.3 a
is now5
.However, this method does not work for all cases. Can you think of when this method would fail?
This method fails when
a
andb
's values are very large, since addition may cause integer overflow. -
Now, let’s write a program that swaps two variables' values using a function:
noswap.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32#include <iostream> using namespace std; /** * Requires: Nothing. * Modifies: Nothing. * Effects: Fails to swap arguments' values. */ void swap(int a, int b); int main() { int x = 2; int y = 5; cout << "x is " << x << endl; cout << "y is " << y << endl; cout << "Swapping..." << endl; swap(x, y); cout << "Swapped!" << endl; cout << "x is " << x << endl; cout << "y is " << y << endl; return 0; } void swap(int a, int b) { int temp = a; a = b; b = temp; } If you try to compile
noswap.cpp
and run the program. Unfortunately, it will print:x is 2 y is 5 Swapping... Swapped! x is 2 y is 5
As you can see,
x
andy
are not swapped.x
andy
are not swapped because they are passed by value toswap
frommain
. That means thata
andb
inswap
get copies of the values ofx
andy
. That also means that whatever happens toa
andb
inswap
will have no effect onx
andy
inmain
. So even thoughswap
does swap the values ofa
inb
, it fails to swap the values ofx
andy
.
Comparison Operators
-
Comparison operators are binary operators, which means that they are used to compare two values.
-
x < y
checks ifx
is less thany
. -
x <= y
checks ifx
is less than or equal toy
. -
x > y
checks ifx
is greater thany
. -
x >= y
checks ifx
is greater than or equal toy
. -
x == y
checks ifx
is equal toy
. -
x != y
checks ifx
is not equal toy
.
-
-
Notice how two equal signs (
==
) are used for comparison. By contrast, a single equal sign (=
) is used for assignment. -
The result of comparing two values is always a bool, i.e. either
true
orfalse
.
Conditions
-
To execute some code only if some condition is true, write something like this:
if (condition) { // do this }
-
If you want to execute some code if a condition is true, and some other code if that condition is false, write something like this:
if (condition) { // do this } else { // do that }
-
Here’s an example that prints different things depending on whether the value of
course
is183
:if (course == 183) { cout << "EECS 183 is Elementary Programming Concepts" << endl; } else { cout << "I'm not familiar with that class!" << endl; }
-
And here’s another familiar example from Project 1:
string pluralize(string singular, string plural, int number) { if (number == 1) { return singular; } return plural; }
-
And if you want to have three (or more) forks in the road, you can do this:
if (condition) { // do this } else if (condition) { // do that } else { // do this other thing }
-
Let’s expand on the previous example:
if (course == 183) { cout << "EECS 183 is Elementary Programming Concepts" << endl; } else if (course == 203) { cout << "EECS 203 is Discrete Mathematics" << endl; } else { cout << "I'm not familiar with that class!" << endl; }
-
And another example that prints different things depending on how
x
compares toy
:if (x < y) { cout << "x is less than y" << endl; } else if (x > y) { cout << "x is greater than y" << endl; } else { cout << "x is equal to y" << endl; }
-
For each statement below, determine if it is true or false.
-
(T/F)
if
is a function. -
(T/F) Every
if
statement needs anelse
statement. -
(T/F) Every
else
statement must go with anif
statement. -
(T/F) Order of
if
/else if
/else
statements matters.
Answers:
-
False.
-
False.
-
True.
-
True.
-
Boolean Expressions
-
Boolean expressions can be used as conditions in if/else statements to make them more interesting. They always evaluate to
true
orfalse
. -
There are three logical operators that you can use to build boolean expressions. The first two are binary operators (they operate on two operands) and the last one is a unary operator.
-
&&
(and) -
||
(or) -
!
(not)
-
-
To illustrate how these logical operators work, we can use truth tables.
A B A && B true
true
true
true
false
false
false
true
false
false
false
false
A B A || B true
true
true
true
false
true
false
true
true
false
false
false
A !A true
false
false
true
-
We can use these logical operators to build more interesting conditions in if/else statements:
if (condition && condition) { (1) // do this } if (condition || condition) { (2) // do this } if (!condition) { (3) // do this }
1 Both conditions have to be true. 2 Either condition (or both) has to be true. 3 Condition has to be false. -
If you ever find yourself wanting to write this code, where you don’t do anything if some condition is true, but to something if that condition is false, as in
if (condition) { // do nothing } else { // do something }
Know that you can simplify your code and improve its style and readability if you instead write
if (!condition) { // do something }
Practice
-
Write a boolean expression in C++ that means “\(a < b < c\)”. Hint:
a == b
is a boolean expression that checks if \(a\) is equal to \(b\).Answer:
a < b && b < c
Note that the expression
a < b < c
is not equivalent to the one above in C++. -
Write a boolean expression in C++ that means “\(0 ≤ x < 10\) or \(x\) is less than \(y\)”.
Answer:
(0 <= x && x < 10) || x < y
Though not always necessary, putting parenthesis around parts of the expression clarifies precedence to both the programmer and the reader of the code.
-
Write a program that asks the user for an integer between 1 and 9. Then say if that number is small (1, 2, 3), medium (4, 5, 6), large (7 8, 9) or invalid.
The program should behave per sample outputs shown below, wherein red underlined text represents some user’s input:
Give me an integer between 1 and 9: 7 You typed a large number.
Give me an integer between 1 and 9: 183 You typed an invalid number.
Solution:
conditions-2.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18#include <iostream> using namespace std; int main() { cout << "Give me an integer between 1 and 9: "; int n; cin >> n; if (n >= 1 && n < 4) { cout << "You typed a small number." << endl; } else if (n >= 4 && n < 7) { cout << "You typed a medium number." << endl; } else if (n >= 7 && n < 10) { cout << "You typed a large number." << endl; } else { cout << "You typed an invalid number." << endl; } }
Operator Precedence
-
As in math, some operations evaluate before others, and there is a specific, defined order.
-
From greatest to smallest priority, C++ operators are evaluated in the following order. This table does not include all of the operators in C++, but it does cover all of the ones you’ll see in EECS 183.
Level Precedence Group Operators Description Associativity 2
Postfix (unary)
++
--
Postfix increment / decrement
Left-to-right
()
Function call
[]
Subscript
.
Member access
3
Prefix (unary)
++
--
Prefix increment / decrement
Right-to-left
!
Logical NOT
+
-
Unary prefix
&
Reference
(type)
Type-casting
5
Arithmetic: scaling
*
/
%
Multiply, divide, modulo
Left-to-right
6
Arithmetic: addition
+
-
Addition, subtraction
Left-to-right
8
Relational
<
>
<=
>=
Comparison operators
Left-to-right
9
Equality
==
!=
Equality / inequality
Left-to-right
13
Conjunction
&&
Logical AND
Left-to-right
14
Disjunction
||
Logical OR
Left-to-right
15
Assignment-level expressions
=
Assignment
Right-to-left
*=
/=
%=
+=
-=
Compound assignment
16
Sequencing
,
Comma separator
Left-to-right
Operator precedence table is a good thing to put on a notecard for the exam! -
For a complete table of precedence and associativity of all C++ operators, take a look at http://en.cppreference.com/w/cpp/language/operator_precedence.