1 of 41

Functions

03603111 Programming Fundamentals I

Department of Computer Engineering, Faculty of Engineering at Sriracha

2 of 41

Course outline and schedule

  • L01 Introduction
  • L02 Data and I/O
  • L03 Conditionals
  • L04 Loops
  • L05 Arrays, strings, and pointers (today!)
  • L06 Functions
  • Lab exam #1
  • L07 Nested loops and file I/O
  • L08 Safe string and buffer operations
  • L09 Sorting
  • L10 Searching
  • L11 Structures
  • L12 Linked data structures
  • Lab exam #2

2

3 of 41

Overview

  • Functions
  • Parameter passing
  • Scope of variables
  • Recursive functions

3

4 of 41

Previous lesson recaps

4

5 of 41

Convenient counter-controlled style for loop

  • This while loop pattern is used often

initialize;

while (condition) {

statement(s);

step;

}

  • But it’s more concise to use for loop for this pattern

for (initialize; condition; step)

statement

5

1

2

4

3

1

2

4

3

True

False

6 of 41

When to use which?

  • Prefer while for conditional loops in general

  • Prefer for when you want a counter-controlled loop or when you’re working with a list of items

  • Use do … while when either of the followings holds:
    • You need to always have at least one iteration regardless of the condition
    • The value you’re checking requires the loop body to run to get it
    • Input validation is the most common use case for do … while

  • These are general guidelines, not rules – use whatever that works best for your specific situations!

6

7 of 41

Arrays

  • Array is a fixed-size collection of elements of the same type
  • Each element is accessible via an index
  • Declaration and initialization:

// 10-element int array (40 bytes allocated), uninitialized

int xs[10];

// 3-element float array (24 bytes allocated), with initialization

double ys[] = { 1.0, 2.5, 4.5 };

// 5-element unsigned int array (20 bytes), initialized to 1, 2, 3, 0, 0

unsigned zs[5] = { 1, 2, 3 };

7

Value

5

3

1

2

4

10

7

4

6

11

Index

0

1

2

3

4

5

6

7

8

9

8 of 41

Operations on arrays

  • Array indexes starts from 0 to N-1
  • Use an integer expression as a subscript (inside square brackets) to refer to an element

int arr[3];

arr[0] = 1;

arr[1] = arr[0] + 2;

arr[arr[0] + 1] = arr[0] + arr[1];

8

Value

1

3

4

Index

0

1

2

9 of 41

Variables, memory, and addresses

9

Address

Memory

Variable

0xBC04

A0

exp

0xBC05

86

0xBC06

01

0xBC07

00

0xBC08

42

name

0xBC09

65

0xBC0A

63

0xBC0B

6B

0xBC0C

00

0xBC0D

00

0xBC0E

00

0xBC0F

00

0xBC10

04

ptr

0xBC11

BC

  • Variables are stored in memory
  • Memory is a collection of byte-addressable cells

int exp = 100000; // 0x186A0

char name[8] = "Beck"; // 0x42 0x65 0x63 0x6B 0x00

int *ptr = &exp; // 0xBC04

  • ͏Pointers are variables that store addresses – they point to data stored in memory
  • Note that actual in-memory ordering of variables is not guaranteed to be the same as the declaration order

10 of 41

C null-terminated strings

char word[10] = "Hello!";

  • This example is a string pre-allocated to 10 bytes, using 7 bytes to store a string of length 6
  • Use strlen(word) to find the length of the string
  • Strings can be read using scanf() with %s, which will read a “word” (not the line)
    • Note that & is not needed for the variable to be read
  • To prevent buffer overflow, width specifier can be used

scanf("%9s", word);

10

Content

'H'

'e'

'l'

'l'

'o'

'!'

'\0'

Memory

48

65

6C

6C

6F

21

00

00

00

00

Index

0

1

2

3

4

5

6

7

8

9

Address

0xA10B

0xA10C

0xA10D

0xA10E

0xA10F

0xA110

0xA111

0xA112

0xA113

0xA114

0xA115

0xA116

11 of 41

Let’s continue with the lesson

12 of 41

Can we reduce this code duplication?

#include <stdio.h>

int main()

{

int a, b, c;

do {

printf("Enter value of dice A (1-6): ");

scanf("%d", &a);

} while (a < 1 || a > 6);

do {

printf("Enter value of dice B (1-6): ");

scanf("%d", &b);

} while (b < 1 || b > 6);

do {

printf("Enter value of dice C (1-6): ");

scanf("%d", &c);

} while (c < 1 || c > 6);

printf("Total value = %d\n", a + b + c);

return 0;

}

Enter value of dice A (1-6): 0

Enter value of dice A (1-6): 1

Enter value of dice B (1-6): 7

Enter value of dice B (1-6): 2

Enter value of dice C (1-6): 6

Total value = 9

Result

13 of 41

What if we have a command to “read_dice_value”?

int main()

{

int a, b, c;

a = read_dice_value("A");

b = read_dice_value("B");

c = read_dice_value("C");

printf("Total value = %d\n", a + b + c);

return 0;

}

13

Note how the code is much more readable compared to earlier

14 of 41

We can actually create such a command

int read_dice_value(char dice_name[])

{

int value;

do {

printf("Enter value of dice %s (1-6): ", dice_name);

scanf("%d", &value);

} while (value < 1 || value > 6);

return value;

}

14

What we’ve created is called a function

Return

type

Function name

Parameter

Return statement – type must match the return type

15 of 41

Function in action

#include <stdio.h>

int read_dice_value(char dice_name[])

{

int value;

do {

printf("Enter … %s (1-6): ",

dice_name);

scanf("%d", &value);

} while (value < 1 || value > 6);

return value;

}

int main()

{

int a, b, c;

a = read_dice_value("A");

b = read_dice_value("B");

c = read_dice_value("C");

printf("Total value = %d\n",

a + b + c);

return 0;

}

15

Call the function

Pass an argument

Assign return value to

16 of 41

Terminology

  • Parameters (also called formal parameters)
    • Part of the function’s signature (declared in the function declaration)
    • Define the type and name of inputs to the function

  • Arguments (also called actual parameters)
    • Actual values that are passed to the function when it is called
    • They are assigned (copied) to the corresponding function parameters

  • Return value
    • Value returned as the result of a function call
    • Can be assigned to a variable, used inside an expression, or ignored and thrown away

16

17 of 41

Function is an abstraction

  • The key is abstraction, which is a way to simplify things by hiding the details under a newly defined concept

  • In the previous example, we abstract the process that:
    • Asks the user to enter the dice value
    • Keeps checking and re-asking until the user enter a valid value
  • Into what we call: read the dice value

  • By using functions, we create a new vocabulary, that allows us to think at a higher level

17

18 of 41

By using functions, we can…

  • Reduce code duplication
  • Produce code that is easier to understand and maintain
  • Think and solve problems at a higher level

  • Syntax:

return-type function-name(parameter-list)

{

function-body

}

  • Parameter list is a list of declarations inside parentheses, like this:

int max(int a, int b, int c)

18

19 of 41

Swapping two variables

  • Given two variables, a and b, swap the values between each variable
  • We’ve done this before

  • This doesn’t work

a = b;

b = a;

  • We need a temporary variable

int temp = a;

a = b;

b = temp;

19

20 of 41

Let’s put it in a function

#include <stdio.h>

void swap(int a, int b)

{

int temp = a;

a = b;

b = temp;

}

int main()

{

int a = 5, b = 10;

swap(a, b);

printf("a = %d, b = %d\n", a, b);

return 0;

}

20

void means the function returns nothing

  • Try running this code
  • The result will be wrong!
  • Why?

21 of 41

Parameters: value and reference semantics

  • Value (copy) semantics
    • Parameter passing (assignment) in C has value semantics
    • Values of the arguments are copied into parameters – after that they are independent of each other

  • Reference semantics
    • Assignments using reference semantics do not copy values, but the addresses (also called references) of the arguments
    • The parameters implicitly reference (or point to) the copied arguments
    • C does NOT have native reference semantics (C++ does)
    • ͏But we can work around this using pointers!

21

22 of 41

Pointer review

  • A pointer is used to reference to another variable via its memory address
  • Pointer variable declaration: type *name;

int *ptr;

  • Address of” operator (&) is used to obtain the address of a variable

ptr = &exp;

  • Array and string variable names already represent their memory addresses by themselves – we can get their addresses directly without using & operator just by referring their names

  • Pointer dereference” operator (*) is used to get the value stored at the memory address pointed to by the pointer

*ptr = *ptr + 1000;

  • It is also used for assigning a value to that memory address

22

23 of 41

Correct version of swap()

#include <stdio.h>

void swap(int *a, int *b)

{

int temp = *a;

*a = *b;

*b = temp;

}

int main()

{

int a = 5, b = 10;

swap(&a, &b);

printf("a = %d, b = %d\n", a, b);

return 0;

}

23

You’ve seen this kind of parameter passing in scanf()

  • Now, check the result again!
  • Why does it work now?

24 of 41

Functions that returns nothing

  • ͏swap() has a void (“nothing”) return type

  • Some functions “perform actions” rather than “compute results”
    • Functions with side effects, e.g., printing on screen, changing some global variables, or alter the argument variables
  • These functions should have a void return type

  • We can also use an “empty” return statement in these functions to return from them without a return value

24

25 of 41

Array and string parameters

  • Recall that array and string variables are “addresses” or “pointers” by themselves
  • Therefore, arrays or strings can be passed as arguments and modified without explicit & operators
  • Think of scanf() – we need to use & for most arguments, except for strings, because scanf() needs to modify its arguments

  • We know the length of string arguments by their null character ('\0') ending, so passing strings is straightforward
  • For arrays, we have no way to know their size by themselves!
  • We need to always pass the size as another argument

25

26 of 41

Example: Counting string length

#include <stdio.h>

int my_strlen(char str[])

{

int count = 0;

while (str[count] != '\0')

count++;

return count;

}

int main()

{

char str[20];

printf("Enter a word: ");

scanf("%19s", str);

printf("Length of \"%s\" = %d\n", str, my_strlen(str));

return 0;

}

26

Enter a word: Hello!

Length of "Hello!" = 6

Result

27 of 41

Example: Adding to an array

#include <stdio.h>

void add_arr(int arr[], int size, int value) {

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

arr[i] += value;

}

}

int main() {

int arr[5] = {1, 2, 3, 4, 5};

add_arr(arr, 5, 3);

printf("Updated array: ");

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

printf("%d ", arr[i]);

}

printf("\n");

}

27

Updated array: 4 5 6 7 8

Result

28 of 41

Problem: Reversing a string

  • Write a function that takes a string as its only parameter and reverse the passed string in-place

  • Let’s U-SWACT it together!
    • UNDERSTAND: Take a minute to understand the problem first
    • SAMPLE INPUT: Let’s create some sample inputs
      • How about ABC (odd length), ABCD (even length), and A (one character)?
    • WORK OUT:
      • The result should be CBA, DCBA, and A
      • But we’ll need to work with it in the context of strings and functions
      • Draw slots that represent a string and try moving things around – do it!

28

UNDERSTAND

SAMPLE INPUTS

WORK OUT

ALGORITHM

CODE

TEST

29 of 41

Blank sheet – work out your solutions here

29

30 of 41

Scope of variables

31 of 41

Local and global variables

#include <stdio.h>

int count = 0;

int inc1()

{

return ++count;

}

int inc2()

{

int count = 0;

return ++count;

}

int inc3()

{

static int count = 0;

return ++count;

}

int main()

{

printf("%d, %d, %d\n",

inc1(), inc2(), inc3());

printf("%d, %d, %d\n",

inc1(), inc2(), inc3());

printf("%d, %d, %d\n",

inc1(), inc2(), inc3());

return 0;

}

31

  • What are the results?
  • Try running the code to verify

32 of 41

Local and global variables

  • We’ve seen 3 locality types of variables in the previous example
    • Global variables
    • Local variables
    • Static local variables

  • Global variables are declared outside of any functions

  • Variables declared inside a function or block are called local variables
  • Function parameters are also considered local variables
  • Local variables are not initialized by default, and their values are not retained across different function calls

  • Static local variables (declared static) are local variables that retain their values across function calls

32

33 of 41

Scope of variables

  • Every variable lives inside a scope
  • Scope is a region of the program where a declared variable is accessible (or visible)

  • Variables declared inside a function can only be accessed from inside that function (they have a local scope inside the function)
  • Variables declared inside a block can only be accessed inside that block (they also have a local scope, but inside the block)
  • Global variables are accessible anywhere in the file

  • Inner (more local) variables can hide outer (global or less local) variables of the same name – this is called shadowing

33

34 of 41

Can we put main() first?

int main()

{

double quintuple = common(2.0, 2.0) + triple(2.0);

}

double triple(double a)

{

return common(a, common(a, a));

}

double common(double a, double b)

{

return a + b;

}

34

  • This code will not compile!
  • Not only main() comes first here, but triple() comes before common() but calls it as well

35 of 41

Functions must be declared before use

// We can either rearrange the functions such that common() comes first

// Or we can use "forward declaration" like below:

double common(double a, double b); // forward declaration has no body

double triple(double a); // forward declaration has no body

int main()

{

double quintuple = common(2.0, 2.0) + triple(2.0);

}

double triple(double a)

{

return common(a, common(a, a));

}

double common(double a, double b)

{

return a + b;

}

35

With forward declaration,

this code now compiles

36 of 41

Recursive functions

37 of 41

Computing factorial

  •  

37

38 of 41

Translating mathematical definitions to code

  •  
  •  

38

Let’s work out fact_*(5) together!

39 of 41

Recursion

  • Function that implements the second definition of factorial is a recursive function

  • Recursive function is a function that calls itself

  • It should have these two parts:
    • Base case – terminating scenario, i.e., the result when the arguments are at their smallest values (base values)
    • Recursive step – steps that reduce all successive cases toward the base case by calling itself with smaller arguments

  • Recursion is a natural solution to some classes of problems

int fact_r(int n)

{

if (n == 0)

return 1;

else

return n * fact_r(n - 1);

}

39

Base case

Recursive step

40 of 41

Pre-order and post-order operations

Pre-order operation

void count(int n) {

if (n <= 0) {

return;

} else {

printf("%d ", n);

count(n - 1);

}

}

int main() {

count(5);

printf("\n");

}

Post-order operation

void count(int n) {

if (n <= 0) {

return;

} else {

count(n - 1);

printf("%d ", n);

}

}

int main() {

count(5);

printf("\n");

}

40

Printing is performed before calling itself

Printing is performed after calling itself

Try running and compare both versions!

41 of 41

That’s all for today!

41