0% found this document useful (0 votes)
3 views24 pages

C Essentials – Part 1 Module 5

This document covers essential concepts in C programming, including array indexing, pointers, memory allocation, and function declarations. It emphasizes the importance of understanding pointer arithmetic and the potential pitfalls of using uninitialized pointers and exceeding array bounds. Additionally, it discusses the use of the void type and dynamic memory management through malloc() and free() functions.

Uploaded by

loganlozano7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views24 pages

C Essentials – Part 1 Module 5

This document covers essential concepts in C programming, including array indexing, pointers, memory allocation, and function declarations. It emphasizes the importance of understanding pointer arithmetic and the potential pitfalls of using uninitialized pointers and exceeding array bounds. Additionally, it discusses the use of the void type and dynamic memory management through malloc() and free() functions.

Uploaded by

loganlozano7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 24

C Essentials – Part 1 Module 5

 array indexing,

 using pointers: perils and disadvantages,

 the void type,

 arrays of arrays and multidimensional arrays,

 memory allocation and deallocation: the malloc() and free() functions,

 arrays of pointers vs. multidimensional arrays,

 functions,

 how to declare, define and invoke functions,

 the scope of variables, local variables and function parameters.

indexing vs. pointers

let’s summarize what we have learned so far. consider the following declaration: //char word[10]
= “dump”;

we’ve created a 10 element array of char and put a string [dump] there. uses 5 chars of array.

word[1] = ‘a’;

puts(word);

Note that we’ve sued apostrophes, not quotes.

Don’t forget:

 aposrophes: [char]
 quotes: [char *]

what’ll happen if we do something like this;

word[-1] = 'x';

word[1000000] = 'y';

we can tell you what won’t happen for sure:

 the compiler won’t signal either an error or a warning


 the string contained in the array will not be changed

we can’t predict with certainty what will happen when the two above statements execute

the burden of responsibility for indexing accuracy falls solely on the programmer.

Let's consider the following expression:

t[i] ≡ *(t + i)

the “C” language standard says: if any pointer is followed by an indexing operator, like this: t[i]
it’s always taken as: *(t + i) In particular, this means that the following expression
*(word + 1) is treated by the compiler just like this one: word[1]

STEP 2: the pointer is increased by one (word + 1). this means the pointer is “shifted toward the
right by one element of the array. the increased pointer is an argument for the dereference
operator, which means that it’s of type char, at least form a syntactic and semantic point
of view. it’s also an I-value. this means that this assignment is fully permissile:
*(word + 1) = ‘a’;

STEP 3: take a look at the expression: *(word + 1). can you expleain why we used the
parentheses? are they necessary? Yes, they are, due to the very high priority of the
dereference operator (*).

we’ve removes the parentheses: *word + 1; what’ll happen now? Firstly, the value (a
character) pointed to by [word] is dereferenced. secondly, a value of [1] is added to the
dereference character (not to the pointer itself). that’s not what we wanted.
let’s consider t[i] = i[t]

 if [t] is a pointer and [i] is an expression of type int, t[i] is equivalent to *(t + i)
 the addition is commutative, so we can write the prvious epression in the
following way: *(i + t)
 this alo means that we’re allowed to write the same indexing operation as i[t]

this means the [] operator is commutative. the compiler doesn’t care whether you write:

word[1] = ‘a’;

or

1[word] = ‘a’;

the first version is used most commonly.

We advise caution when constructing expressions using the * operator, since any lack
of parentheses will not be detected by the compiler as syntax flaws.

Imagine a set of declarations:

char string[] = "ABC";

char *p;

char c;

Now we set p to point to the second element of the array string. The recommended
form of this assignment is as follows:

p = string + 1;

Acceptable, though less elegant (however, some would argue, clearer), is the following
form:

p = &string[1];

The p pointer will point to the second element of the array string – look at the figure:
Can you answer the question of what distinguishes the following two instructions:

c = *p++;

and

c = (*p)++;

We can explain: the first assignment is as if the following two disjoint instructions have
been performed:

c = *p;

p++;

In other words, the character pointed to by p is copied to the c variable; then, p is


increased and points to the next element of the array.

The second assignment is performed as follows:

c = *p;

string[1]++;

The p pointer is not changed and still points to the second element of the array, and
only this element is increased by 1 .

this is L-value not I-value “l-value” refers to memory location which identifies an
object.

Imagine the following assignment:

p = string + 2;

p points to the third element of the string array. What happens now?

p[-1] = 'e';
It looks suspicious, because we’ve used the [] operator for a pointer that isn’t the name
of an array. Is this legal? Can we do it?

Yes, it is, and we can. The compiler treats this as normal and thinks that we’re trying
to do something like this:

*(p - 1) = 'e';

We want to change the element located before the one pointed to by the p pointer (in
effect, it leads us to the second element of the array). The entire expression is
treated as an l-value and is assigned the character 'e'.

1 [] , ++ , -- postfix
2 ! , ~ (type), ++ , -- , + , - , * , & , sizeof prefix
3 *, /, % binary
4 +, -
5 << , >>
6 < , <= , > , >=
7 == , !=
8 &
9 |
10 &&
11 ||

Pointers can be dangerous

Mistake No.1: use of an uninitialized pointer. the compiler is unable to detect the
error, because its nature is revealed at run time only. consider the example
provided in the editor.

#include <stdio.h>
#include <string.h>
int main(void) {
char *ptr; //pointer declared but not initialized.
strcpy(ptr, "you may get into trouble soon");
puts(ptr);
return 0;
}

strcpy will use the current value of the ptr pointer to determine the location where
the string specified in the second parameter should be copied. However,
the ptr variable hasn’t been assigned. strcpy brings with it trouble.

on the flip side when trying to dereference an uninitialized pointer, it won’t


end well either.

MISTAKE NO.2: exceeding the size of the array. your program may finish its work
with a message about a memory violation error, although if you’re unlucky, the
program will go further, but the results may have little in common with your
intentions.

MISTAKE NO.3: non-terminated strings. example, when we haven’t put a null


character inside the array and likely has no end.

Not only vectors

let’s consider the case where the array’s elements are just arrays, example a
chessboard.

MATRIX!!! example chessboard so the chessboard variable is a two-dimensional


array. it’s also called, by analogy to algebraic terms, a matrix. declared as so
int chessboard [8][[8]; . (row by column)

the appearance of two pairs of brackets tells the compiler that the declared array is not
a vector – it’s an array whose elements are vectors.

now let’s go deeper into the multi-dimensional nature of arrays. to find any element of a
a two-dimensional array, we have to use two coordinates: (wording weird) a
vertical (row number) one and a horizontal (column number) one.

The “C” language doesn’t limit the size of the array's dimensions. Here we show
an example of a three-dimensional array.
Now imagine a hotel. It's a huge hotel consisting of three buildings, 15 floors each.
There are 20 rooms on each floor. We need an array that can collect and process
information on the number of guests registered in each room.
Step one – the type of the array's element. We think an int would fit, although it can be
unassigned as there’s no such thing as a negative number of guests.
Step two – calm analysis of the situation. Summarize the available information: 3
towers, 15 floors, 20 rooms.
Now we can write the declaration:

int guests[3][15][20];
The first index (0 through 2) selects one of the buildings; the second (0 through 14)
selects the floor, the third (0 through 19) selects the room number.

Now we can book a room for two newlyweds: in the second building, on the tenth floor,
room fourteen:

guests[1][9][13] = 2;

and release the second room on the fifth floor located in the first building:

guests[0][4][1] = 0;

Before we say goodbye and finish this part of our course, let's check if there are any
vacancies on the fifteenth floor of the third building:

int room;
int vacancy = 0;
for (room = 0; room <20; room++)
if (guests[2][14][room] == 0)
vacancy++;

The vacancy variable contains 0 if all the rooms are occupied; otherwise it displays the
number of available rooms.

void – the very exceptional type

we used it to indicate that a function doesn’t return a result, or doesn’t expect any parameters.
According to the following function prototype: void nothingatall (void);

the function should be invoked without parameters and return no result. this is how we should
invoke it: nothingatall();

despite the fact that the [void] type doesn’t represent any useful value, you can still declare
pointers to this type, as in the following example: void *ptr;

a pointer that points to nothing is a kind of pointer of the type void [*] and is called an
amorphous pointer to emphasize that fact that it can point to any value of any type. this
means that a pointer of type void [*] cannot be subject to the dereference operator, so
you must not write anything like this *ptr = 1; it can be justified by the argument that if
[ptr] was of type void *, [*ptr] would be of type [void] and the assignment of a value of
type int is prohibited by the compiler.
pointers of type [void *] are useful when you need to have a pointer, but don’t know what you’re
going to use it for in the future.

Memory on Demand

normally compiler taking care of how memory is allocated but sometimes the developer needs
full control. to manage allocating and freeing of memory the “C” language proivdeds a
set of specialized functions. will be shown two of those functions which require the
inclusion of the header file [stdlib.h]. the first function is used to request access to the
memory block of the specified size. we are asking so request for memory can be accessed
or denied. when allocated memory is no longer need we free\return the memory with the
second function.

the function of the first task has the following prototype:

void *malloc(int size);

 malloc short for Memory ALLOCation


 its only parameter provides information about the size of the requested memory and is
expressed in bytes
 the function returns a pointer of type [void *] which points to the newly allocated
memory block, or is equal to NULL to indicate that the allocation requested could not be
granted
 the function doesn’t know what the block of memory will be used for and result is of type
[void *] we’ll have to convert it to another usable pointer type.
 the allocated memory area is not filled (initiated) in any way, so you should expect it to
contain garbage

the function invoked when the memory is no longer necessary has he following prototype:

void free(void *pointer)

 the function doesn’t require any comments;


 the function doesn not return any results so its type is defined as void;
 the function expects one parameter – the pointer to the memory block that is to be
released; usually it’s a pointer previously received from the [malloc] or its kindred; using
another pointer value may cause some kind of disaster;
 the function doesn’t need to know the size of the freed block; you can only release the
entire allocated block, not a part of it;
 after performing the [free] function, all the pointers that point to the data inside the freed
area become illegal; attempting to use them may result in abnormal program termination.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
int *ptr;
ptr = (int *) malloc(sizeof(int));
if(ptr != NULL) {
*ptr = 1000;
printf("ptr points to value of %d", *ptr);
free(ptr);
} else
printf("allocation failed");
return 0;
}

 We declare a variable called ptr which will point to the data of type int (the
pointer's type is int * ); initially, we assign no value to this variable;
 We invoke the malloc function, requiring the allocation of a memory block
sufficient to store one value of type int ( sizeof (int) ) – this enables our
program to correctly execute, regardless of how many bytes are utilized by
the int type in the specific implementation of the language and/or hardware
platform;
 The malloc 's return value is assigned to the ptr variable using type casting,
converting the pointer of the ( void * ) type into an ( int * ) type;
 We need to check the resulting pointer value – if it’s not NULL, it can be safely
used; our program assigns a value of 200 to the allocated memory, prints the
value using the printf function and frees the memory;
 If the memory allocation goes wrong, malloc returns NULL and we do nothing but
print an error message.

int *tabptr, i, sum = 0;

tabptr = (int *) malloc(5 * sizeof(int));


for(i = 0; i < 5; i++)
tabptr[i] = i;
sum = 0;
for(i = 0; i < 5; i++)
sum += tabptr[i];
free(tabptr);

The option of allocating the amount of memory that we really need lets us write
programs that can adapt themselves to the size of the data currently being processed.

Let's go back to the bubble sort algorithm that we looked at some time ago. That
program assumed that there were exactly five numbers to sort. This is obviously a
serious inconvenience. It may happen one day that we want to sort 10,000 numbers or
maybe even hundreds of thousands of numbers.

What then? You can, of course, declare an array of the maximum predictable size, but it
would still be inconvenient. A much better way is to ask the user how many numbers
will be sorted and then allocate an array of the appropriate size.
Let's try to start with a simpler example. In the following program, we allocate an array
containing five elements of type int , set their values, sum them up and, finally, release
the previously allocated memory. As you see, we’ve done so quite haphazardly and
haven’t checked if the memory allocation has succeeded. It can be omitted in a
program like this, but when we perform more complex tasks, we need to verify it.

We want you to pay attention to the fact that the pointer returned by malloc is treated
as if it’s an array. Surprising?

The handling of dynamic arrays (created during the run of the program) is no different
than using regular arrays declared in the usual way.

We owe it all to the [] operator. Regardless of the nature of the array, we can access its
elements in the same way.

elements of arrays can be pointers. what if a 2D array needed to be a dynamic array? we would
NOT attempt to allocate the array like this:

int *ptrtab = (int *) malloc(rows * cols * sizeof(int));

this would make the array rely on us to do the work to calculate the pointer to the element like
this: ptrtab + (cols * r) + c

this is due to the fact that the “C” language arranges two-dimensional arrays row by row. this is
not a very satisfactory solution. we would prefer a clearer notation like this:

ptrtab[r][c] // is this possible?

yes, it is.

this is how we do it:

 we’ll store the pointer to the beginning of every row so we can each row without
t any acrobatics. how do we store theses pointers? in the array, of course. we’ll
call it the array of rows. every row will have as many elements as columns of the
desired array
 every element in the array of rows will be a pointer to a separate row.
 we need one more pointer to point to the array of rows – we call it [ptrtab]
what is the type of the variable [ptrtab]?

the type of [ptrtab] is a pointer to a pointer to [int], which is denoted as [int **]. we can write a
complete declaration now – you can see it in the editor window.

int **ptrtab;

once we’ve declared the pointer, we can allocate the array of rows.

ptrtab = (int **) malloc (rows * sizeof (int*));

Firstly, the pointer returned by [malloc] surrender is converted to type [int **] and assigned to
[ptrtab].

Secondly, the elements of the array of rows are pointers to the rows, to their type is [int *] and
hence, the size of the array is expressed as sizeof(int *) multiplied by the number of
rows.

Finally, we need to allocate memory for every row and store the resulting pointer inside the right
element of the array of rows. easiest way is using a loop

for (r = 0; r <rows; r++)


ptrtab[r] = (int *) malloc (cols * sizeof (int));

it’s surprisingly simple to use this kind of array.

for example, if we want to assign 0 to the element lying in row [r], column [c], we’ll do it this
way: ptrtab[r][c] = 0;

how does it work?

 the [ptrtab[r]] expression is interpreted as *(ptrtab + r), which means the


dereferencing of the element pointing to the selected row.
 the pointer is dereferenced once more so the entire indexing expression looks as
follows: *(*(ptrtab + r) + c)

and this is simply the desired value of the type int

process of dereferencing this element: ptrtab[2][1] is shown in the figure:

the advantage of such arrays is that, unlike ordinary arrays, every row may be of a different
length.

this is useful for the algorithms that don’t need the entire array to run but only a slic of it. it refers
specifically to triangular metrices. such an array can be allocated in this way:

This is how it’s built:

int rows = 5, r;
int **ptrab;
ptrtab = (int **) malloc (rows * sizeof (int *));
for (r = 0; r <rows; r++)
ptrtab[r] = (int *) malloc (sizeof (int) * (r + 1));//r being the
//row of the actual array

Pay attention to how the triangularity is obtained (the size of the allocated memory
block depends on the row number).

Some declarative traps

take a look at the following declaration:

int *array[10];
in this way, we’ve declared a variable [array] which is a 10-element array of pointers to the data
of type [int].

and now let’s look at a seeminly very similar, but completely different, declaraion:

int (*array)[10];

it declares [array] as a pointer to a 10-element array of type [int]

look how the praentheses have changed the meaning of the declaration.

And now something really difficult:

int *(*array)[10];

the statement creates a vraible [array], which is a pointer to a 10-element array whose
elements are pointers to [ints].

Functions – rationale

Going to learn how to declare and write your own funcitons, and how to use them. Also, how to
use functions written by someone else; even when we don’t have their source code.

Why would we want to write functions? Reason #1

it often happens that a particular piece of code is repeated many times in your program.

we can define the first condition which an help you decide when to start writing your own
function: if a particular fragment of the coe begins to appear in more than one place,
consider the possiblilibty of isolating it in the form of a function invoked from the points
where the original code was placed before

Reason #2

it may happen that the alogorithm you’re going to implement is so complex that the [main]
funciton begins to grow in an uncontrolled manner, and suddenly you notice that you’re
having problems simply navigating through it.

decomposition – a good, attentive developer divides the code (or more accurately: the problem)
into well-isolated pices and encodes each of them in a the form of a function. this
considerably simplifies the work on thte program ecause each piece of code could be
encoded separately and tested separately.

we can now state the second condition: if a piece of code becomes so large that reading and
understanding it may cause a problem, consider dividing it into separate, smaller
problems and implement each of them in the form of a separate function. this
decomposition contues until you get a set of short funcions, easy to understand and test.

Reason #3
it often happens that the problem is so large and complex that it cannot be assigned ot a single
developer, and a team of developers have to work on it.

the third condition: if you’re going to divide the work among muliple programmers, decompose
the problem to allow the product to be implemented as a set of separatel written
functions.

What does the compiler need?

it’ll try to make sure that:

 the function you want to call is available


 the parameters you’ve specified (or haven’t specified at all) are consistent with
what is expected for the function
 the return type of the function is compatible with the type of targeting l-value(L-
value)

in other words, the compiler must have the following information for each function you’re going
to use: What is the name of the funcion? How many parameers does the funcion expect
and of which types? what is the function’s return type?

if you don’t provide this information, the compiler will try to deduce it from the first invocation
that appears in our code – this is called an implicit delcaration of the function, and is
both convenient and dangerous at the same time. Imagine what havoc can be wreaked if
the first funcion invocation contains an error.

the compiler can derive infroamtion avout the fucntions from two sources:

 the declaration of the function


 the definition of the function

Declaration vs. definition

the declaration of a funciton is the part of the code containging all three key pieces of
information (name, parameters, type), but doesn’t contain the body of the funcition. it
say how to invoke the function but nothing about what the function does.

the declaration of the function is often called a function prototype

A definition of a function is a part of the code containing its full implementation (including the
body)
int CountSheep(void); /* declaration */

int CountSheep(void) { /* definition */


return ++SheepCounter;
}

Our first function


Let’s start with a simple function, its sole purpose to signal that it has been invoked.
The function won’t have any parameters (we don't need any), won’t return any result
(we don't want it too) and will be named hello .

Here’s its definition:

void hello(void) {
printf ("You've invoked me – what fun!\n");
return;
}

What does the definition contain?

 the first void means that the function does not return any useful value; a type
name may appear at this point, announcing that the function calculates the
result of a given type and returns it after completion;
 an opening parenthesis – in fact, the presence of the parenthesis assures the
compiler that it’s dealing with the definition (or declaration) of the function;
 a parameter list (we’ll soon say more about this) or the word void if the function
doesn’t expect any parameters;
 a closing parenthesis;
 a complete block enclosed in curly brackets and containing a set of “C” language
instructions and declarations.

The declaration of this function would be as follows:

void hello(void);

Note – there’s a semicolon in place of the body.

How do we invoke our function?


We can invoke our new function in the following way:

hello();

How do we not invoke our function?


We mustn't invoke our function in the following way:

We try to treat it as if it evaluates and returns the value of type int which is
at odds with the declaration. The compiler will emit an error message.

Invoking it like this is prohibited, too:

hello(2);
We're trying to pass an argument whose type is int through the declaration's void
parameter.

Don’t forget – whenever the compiler knows that the entity has been declared, but knows
nothing about the entity’s type, it thinks it’s an int type.

Note the place in the code where the invocation of the hello function has appeared.
The compiler is ready to invoke the function there – it knows the function's name,
it knows that no parameter is expected and it knows that no return value has
been provided.
#include <stdio.h>

void hello(void) {
printf ("You've invoked me – what fun!\n");
return;
}

int main(void) {
printf("We are about to invoke hello()!\n");
hello();
printf("We returned from hello()!\n");
return 0;
}

Imagine that, due to some important reason, we had to change our code – it now looks
like this:

#include

int main(void) {
printf("We are about to invoke hello()!\n");
hello();
printf("We returned from hello()!\n");
return 0;
}
void hello(void) {
printf ("You've invoked me – what fun!\n");
return;
}

A layout of our code like this will puzzle the compiler, because it’s forced to guess all
the traits of the hello function before the compiler even reads its declaration or
definition. You should expect the compiler to generate a warning message and the
implicit declaration will perform its deduction.
The deduction is very simple – it assumes that all entities of unknown types
are ints. This means that the compiler is convinced that the actual hello declaration
looks as follows:

int hello(void);

The mismatch between the implicit and explicit declarations will cause the compiler to
signal a warning or an error.

Don't forget – whenever the compiler knows that the entity has been declared, but
knows nothing about the entity's type, it thinks it’s an int type.

what should we do to convince the compiler that the implicit delaration isn’t a good idea? is
there even anything we can do?

Yes, there is. we should warn the compiler that the funciton will be used and provide complete
information about it. in other words, we ought to provide the declaration before the first
invocation occurs.

look at the editor. ther’s the correct, unambiguous code.

#include <stdio.h>

void hello(void);

int main(void) {
printf("We are about to invoke hello()!\n");
hello();
printf("We returned from hello()!\n");
return 0;
}

void hello(void) {
printf ("You've invoked me – what fun!\n");
return;
}

The return statement

the [return] statement executed inside any function causes immediate function termination and
a return to the invoker. the form of the [return] statement, and whether there is a need to
sue it, depends on the following circumstances: if the funciton is defined as void, then the
acceptable [return] statement looks like this: return;

if the body of the function doesn’t contain a [return] statement, it will be implicity added after
the last instruction of the funcion’s block.
this means that you can wirte the hello fucntion in the following way too:

void hello(void){

printf(“You’ve invoked me – what fun!\n”);

Note that more than one return statement mayexist in the funtion body.

if the function type isn’t specified as void, the only acceptable form of terun statement is as
follows: return expression;

where the expression must provide the value of the type matching the type of the function; in
this case using the [return] statement is mandatory and you cannot omit it in the funciton
body.

if the body doesn’t contain a [return] statement, it will be implicitly added after the last
instruction of the fnction’s block

Functions and their local variables

Fucntion blocks and blocks in general containg variable delcarations.

The blocks are opaque to the declarations contatined therein.

This means that if we delcare a variable inside a block (e.g. a function’s block) the variable will
be known and recognized only inside that block and, consequently, will not be known in
any other part of the program.

this also means that the name will not interfere with other variables with identical names defined
inside other blocks.

Let’s have a look at an example in the editor, which illustrates this rule.

#include <stdio.h>

void hello(void) {
int i;

for(i = 0; i < 2; i++)


printf ("You've invoked me – what fun!\n");
return;
}
int main(void) {
int i;
printf("We are about to invoke hello()!\n");
for(i = 0; i < 3; i++)
hello();
printf("We returned from hello()!\n");
return 0;
}

We want the hello function to be more exciting and print its happy message twice.
We’ll use the for loop for this task. We’ll also need a control variable, so we add
the int i ; declaration at the beginning of the function block.

The i variable is recognized only inside the hello function's block and nowhere else.

If you need to have another variable of the same name, but inside the main function,
you can declare it there without any problems.

Each of the functions have their own i variable. The variables exist independently of
each other and have nothing in common.

Global variables
If the variable is declared outside of all the blocks, it becomes a global variable.

A global variable is accessible to all functions in a source file. In contrast, variables


declared inside a function are called local variables.

Now look carefully at the example in the editor.

It should help you understand the operation of both kinds of variables.

This is how it works.

#include <stdio.h>

int global;

void fun(void) {
int local;

local = 2;
global++;
printf("fun: local=%d global=%d\n", local, global);
global++;
}

int main(void) {
int local;

local = 1;
global = 1;
printf("main: local=%d global=%d\n", local, global);
fun();
printf("main: local=%d global=%d\n", local, global);
return 0;
}
 the program begins with the declaration of a global variable – it’s truly
global because it’s outside of any function; this implies that the variable is
accessible to all the functions declared in the source file;
 the local variable, which is declared within the fun function, is known only in
this function and has nothing to do with the variable of the same name declared
inside the main function;

Even though we’re using the global variable global , which is outside of all blocks, if you
code like below you may experience some problems, because it may be used in
the fun function before it’s declared (if we declare it too late). Remember that a
function or variable must be declared before it’s used.

We can expect the following text to be sent to the screen:

main: local=1 global=1


fun: local=2 global=2
main: local=1 global=3

Function Parameters

the function parameter is a special kind of local variable. it behaves like a local variabe but
differs from a local variable in two important features:

 first, the paremeter is not declared within the function (i.e. it’s not a part of the
function defintion), but must be declared inside a pair of parentheses after the function
name (which means that the parameter declaration is a part of the function declaration)
for example, //void hello2(int times);

the [time] variable may e used inside the function in exactly the same way as if it were a local
variabl; this is called a formal parameter

 second, a prototype of the function containign formal parameters forces us o


invoke that function with a list of expressions, and the number of expressions
must be equal to the number of formal perameters in the prototypej
 the types of these expression must be compatible with the types of the
corresponding formal perameters; each of these expression is called actual
parameters
 at the beginning of the invocation every formal parameter is assigned the
value of the corresponding actual parameter

let’s look again at the [hello2] definition.

int notmany = 5;
hello2(100); /* the actual parameter is a literal */
hello2(notmany); /* the actual parameter is a variable */
hello2(2 * notmany); /* the actual parameter is an expression */
it clearly shows that these three invocations are valid (they all deliver a value of type [int] to the
formal parameter)

we cannot call the function [hello2] in any of the following ways:


hello2(); /* too few actual parameters */
hello2(1,2); /* too many actual parameters */
hello2("Hey"); /* incompatible actual parameter */

the values of actual parameters are assigned ot formal parameters at the beginning of function
execution.

let’s assume that the [hello3] function has the following declaration:

void hello3(int i, float f);

and has been invoked as follows:

hello3(100, 3.14);

the following assignments will be performed implicitly and beyond our control: [f = 3.14].

the second formal parameter is assigned with the current value of the second actual parameter.

the parameterized function may modify its own behavior according to the parameter’s value.

look at the updated [hello2] function provided in the editor.

void hello2(int times) {


int i;
for(i = 0; i < times; i++)
printf(“You’ve invoked me – what fun!\n”);
return;
}

if you invoke this function as follows: [hello2(100);] the following assignment will take place
automatically: [times = 100];

this time causes the function to manifest its joy a hundred times.

Function Results

If the function has been declared with a type before its name, it must perform the return
statement equipped with an expression.
We’ll write a simple program helping us tot solve a trigonometry problem for right-angled
triangles. the program will calculate the length of the hypotenuse using the lengths of the
legs

this is what we need to do:

 ask the user for the length of the first leg and square it
 ask the user for the length of the second leg an square it
 square root the sum of both values using the sqrt() function (pay attention: the
[math.h] header file needs to be included at the top of our source)

you can see the first version of the solution in the editor

#include <math.h>
#include <stdio.h>

int main(void) {
float a, b, a_sqr, b_sqr, c;
printf(“A?\n”);
scanf(“%f”, &a);
a_sqr = a * a;
printf(“B?\n”);
scanf(“ %f”, &b);
b_sqr = b * b;
c = sqrt(a_sqr + b_sqr);
printf(“The length of the hypotenuse is: %f\n”, c);
return 0;
}

introducing a function

in the previous code, there’s a repeated clause used to square a leg. It’s the perfect opportunity to
introduce a function into our program.

the function won’t be particularly advanced – we expect it to:

 accept one parameter of type [float];


 square the value of he parameter and return it as a the result
 the result type is [float] (can you explain why?)
 we’ll name our function [square] – it’s good practice to name function using verbs

here is the first version of the function:


float square(float param) {
float x_sqr;

x_sqr = param * param;


return x_sqr;
}

Of course, the function could be slightly simplified – here it is;

float square(float param) {


return param * param;
}

let’s use the function is our program. here it is:

/******************************
#include <math.h>
#inlcue <stdio.h>

float square(float param){


return param * param;
}

int main(void){
float a, b, a_sqr, b_sqr, c;

printf(“A\n”);
scanf(“%f”, &a);
a_sqr = square(a);
printf(“B?\n”);
scanf(“%f”, &b);
b_sqr = square(b);
c = sqrt(a_sqr + b_sqr);
printf(“The length of the hypotenuse is: %f\n”, c);
return 0;
}
*****************************/

we can make one simple modification – note [a_sqr] and [b_sqr] are used as temporary
containers only. the [c] variable is utilized in the same way. we can remove them – take
a look at the code in the editor.

/*******************************
#include <math.h>
#inlcude <stdio.h>

float square(float param) {


return param * param;
}

int main(void) {
float a, b;

printf(“A?\n”);
scanf(“%f”, &a);
printf(“B?\n”);
scanf(“%f”, &b);
printf(“The length of the hypotenuse is: %f\n”, sqrt(square(a) + square(b)));

return 0;
}
**********************************/

You might also like