Functions
COP-3223H
Table of Contents
What is a function in C?
- Named computation
There are languages with anonymous functions as well.
Abstraction
Replace a chunk of code with a name.
Define it in terms of its input and output.
Allows us to think and reason about the whole computation a function does as one, instead of all of its individual computational steps.
Benefits
- Reuse
- Organization
Naming a chunk of code
We can name code, i.e., define a function by wrapping it in curly braces:
myfunction() {
printf("hello, world!\n");
printf("1 + 1 = %d\n", 1 + 1);
}
We can use, i.e., call a function that code by writing the name:
int main(int argc, char **argv) {
myfunction(); // call the function
}
In C, parentheses are used for both the function definition and call.
They can optionally take arguments for the inputs (and outputs in C) of the code snippet.
Notice that main itself is also a function. It is called by the operating system when your program is run, which is why we put the code we'd like to run first there.
Reuse
Functions allow reuse without having to copy code
int main(int argc, char **argv) {
myfunction();
myfunction();
myfunction();
}
Reuse with specialization
What if a snippet of code uses a variable?
myfactorial() {
int product = 1;
// n is defined somewhere else
for (int i = 1; i <= n; i++) {
product = product * i;
}
printf("product = %d\n", product);
}
We still want to be able to define a function without having to define all the variables is uses. This allows for the function to generalize beyond a single computation.
Functions can take inputs
We can declare special variables for the inputs
myfactorial(int n) {
int product = 1;
for (int i = 1; i <= n; i++) {
product = product * i;
}
printf("product = %d\n", product);
}
Declare the special input variables in between the parentheses.
Give functions inputs when calling them
Giving the input is called passing arguments:
int main(int argc, char **argv) {
myfactorial(3);
myfactorial(5);
myfactorial(2);
}
Functions can produce an output
// prefix name with the type of the return
int myfactorial(int n) {
int product = 1;
for (int i = 1; i <= n; i++) {
product = product * i;
}
// use the return keyword to specify the output
return product;
}
In C, the arguments can also be used to specify outputs by passing arguments by reference. This can be seen later when introducing pointers.
Using function output
The function can be used in expressions
int main(int argc, char **argv) {
int result = myfactorial(5) + 10;
printf("%d\n", result);
}
Think of the call like a variable, where it is replaced with it's value before the expression is computed.
Another example: exponent function
int exponent(int x, int e);
Multiple parameters delimited by comma
Using the abstraction
We can write code that uses a function before its implemented
int exponent(int x, int e);
int main(int argc, char **argv) {
int a = 3;
int b = 4;
int csquared;
csquared = exponent(a, 2) + exponent(b, 2);
printf("csquared: %d\n", csquared);
}
The code won't run, but this shows the benefits of the function abstraction. We can ignore the low-level details of how exponent works, and focus instead on the code for the Pythagorean theorem.
Implementing exponent
int exponent(int x, int e) {
int result = 1;
for (int i = 0; i < e; i++) {
result = result * x;
}
return result;
}
int main(int argc, char **argv) {
int a = 3;
int b = 4;
int csquared;
csquared = exponent(a, 2) + exponent(b, 2);
printf("csquared: %d\n", csquared);
}
Delimit multiple parameters with a comma.
void functions
void is used when functions take either no input or output (or both)
void print_int(int x) {
printf("%d\n", x);
}
int read_int(void) {
int x;
scanf("%d", &x);
return x;
}
int is the default output type of functions
Combining abstractions: struct and functions
- struct creates new data types
- functions declare input and output types
struct point {
int x;
int y;
};
struct point shift_up(struct point p) {
p.x = p.x + 1;
return p;
}
chase with sprites
// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example
#include <stdio.h>
#include <stdbool.h>
#include "term.c"
struct character {
int state;
int x;
int y;
char description[128];
char sprite[3];
};
void draw_character(struct character c) {
printf("\033[%d;%dH", c.y, c.x);
for (int i = 0; i < 3; i++) {
putchar(c.sprite[i]);
}
}
int main(int argc, char **argv) {
set_input_mode ();
printf("\033[?25l"); // hide cursor
int width = 40;
int height = 20;
struct character player;
struct character enemy;
enum { vulnerable, invincible, dead };
player.state = vulnerable;
player.x = width / 2;
player.y = height / 2;
player.sprite[0] = ':';
player.sprite[1] = '-';
player.sprite[2] = ')';
enum { chasing, running, defeated };
enemy.state = chasing;
enemy.x = 1;
enemy.y = 1;
enemy.sprite[0] = '>';
enemy.sprite[1] = ':';
enemy.sprite[2] = '(';
struct {
struct character character;
int counter;
int random;
int str[10];
} potion;
enum { available, using, done };
potion.character.state = available;
potion.character.x = 29;
potion.character.y = 11;
potion.character.sprite[0] = '*';
potion.character.sprite[1] = '-';
potion.character.sprite[2] = '-';
potion.counter = 0;
potion.random = 1;
while (1) {
printf("\x1b[2J");
printf("\x1b[H");
draw_character(player);
draw_character(enemy);
switch (potion.character.state) {
case available:
draw_character(potion.character);
break;
}
// debugging output
printf("\033[%d;%dH", height + 1, 0);
printf("player.y: %d\n", player.y);
printf("player.x: %d\n", player.x);
printf("enemy.y: %d\n", enemy.y);
printf("enemy.x: %d\n", enemy.x);
printf("potion.character.y: %d\n", potion.character.y);
printf("potion.character.x: %d\n", potion.character.x);
// read input
int direction_x = 0;
int direction_y = 0;
bool valid = true;
do {
char c = '\0';
scanf("%c", &c);
switch (c) {
case 'i':
direction_y = -2;
break;
case 'k':
direction_y = 2;
break;
case 'j':
direction_x = -2;
break;
case 'l':
direction_x = 2;
break;
default:
valid = false;
break;
}
} while (! valid);
switch(player.state) {
case vulnerable: // case 0:
player.x += direction_x;
player.y += direction_y;
break;
case invincible: // case 1:
player.x += direction_x;
player.y += direction_y;
if (enemy.x - 1 <= player.x && player.x <= enemy.x + 1 &&
enemy.y - 1 <= player.y && player.y <= enemy.y + 1) {
player.state = vulnerable;
enemy.state = defeated;
potion.character.state = done;
}
break;
}
switch (enemy.state) {
case chasing: // chasing
if (player.x > enemy.x) {
enemy.x += 1;
}
if (player.x < enemy.x) {
enemy.x -= 1;
}
if (player.y > enemy.y) {
enemy.y += 1;
}
if (player.y < enemy.y) {
enemy.y -= 1;
}
if ((player.x == enemy.x || player.x == enemy.x + 1) &&
(player.y == enemy.y || player.y == enemy.y + 1)) {
player.state = dead;
}
break;
case running:
if (player.x > enemy.x) {
enemy.x -= 1;
}
if (player.x < enemy.x) {
enemy.x += 1;
}
if (player.y > enemy.y) {
enemy.y -= 1;
}
if (player.y < enemy.y) {
enemy.y += 1;
}
if (enemy.x < 0) {
enemy.x = 0;
}
if (enemy.x > width) {
enemy.x = width;
}
if (enemy.y < 0) {
enemy.y = 0;
}
if (enemy.y > height) {
enemy.y = height;
}
break;
}
switch (potion.character.state) {
case available:
if ((player.x == potion.character.x || player.x == potion.character.x + 1) &&
(player.y == potion.character.y || player.y == potion.character.y + 1)) {
player.state = invincible;
enemy.state = running;
potion.character.state = using;
potion.counter = 10;
}
break;
case using:
potion.counter -= 1;
if (potion.counter == 1) {
player.state = vulnerable;
enemy.state = chasing;
potion.character.state = available;
potion.random = (potion.random * 97) % 65533;
potion.character.x = potion.random % width;
potion.random = (potion.random * 97) % 65533;
potion.character.y = potion.random % height;
}
break;
}
}
}