Programming in C Volume 2
Programming in C Volume 2
Programming in C
Also of interest
Programming in C, vol. : Basic Data Structures and Program
Statements
Xingni Zhou, Qiguang Miao, Lei Feng,
ISBN ----, e-ISBN (PDF) ----,
e-ISBN (EPUB) ----
C++ Programming
Li Zheng, Yuan Dong, Fang Yang,
ISBN ----, e-ISBN (PDF) ----,
e-ISBN (EPUB) ----
MATLAB® Programming
Dingyü Xue,
ISBN ----, e-ISBN (PDF) ----,
e-ISBN (EPUB) ----
Programming in C++
Laxmisha Rai,
ISBN ----, e-ISBN (PDF) ----,
e-ISBN (EPUB) ----
Xingni Zhou, Qiguang Miao and Lei Feng
Programming in C
Lei Feng
School of Telecommunication Engineering
Xidian University
Xi’an, Shaanxi Province
People’s Republic of China
fenglei@mail.xidian.edu.cn
ISBN 978-3-11-069229-7
e-ISBN (PDF) 978-3-11-069230-3
e-ISBN (EPUB) 978-3-11-069250-1
www.degruyter.com
Contents
1 Arrays 1
1.1 Concept of arrays 1
1.1.1 Processing data of the same type 1
1.1.2 Representation of data of the same type 5
1.2 Storage of arrays 6
1.2.1 Definition of arrays 6
1.2.1.1 Definition of arrays 6
1.2.1.2 Reference of array elements 7
1.2.1.3 Storage characteristics of arrays 8
1.2.1.4 Comparison of variables of the same type with plain
variables 9
1.2.2 Initialization of arrays 10
1.2.2.1 Initialize all elements 10
1.2.2.2 Initialize some elements 11
1.2.2.3 Array size determined by number of initial values 11
1.2.3 Memory layout of arrays 11
1.2.3.1 Memory layout of one-dimensional arrays 11
1.2.3.2 Memory layout of two-dimensional arrays 11
1.2.4 Memory inspection of arrays 12
1.3 Operations on one-dimensional arrays 16
1.4 Operations on two-dimensional arrays 26
1.5 Operations on character arrays 37
1.6 Summary 45
1.7 Exercises 48
1.7.1 Multiple-choice questions 48
1.7.2 Fill in the tables 50
1.7.3 Programming exercises 51
2 Pointers 53
2.1 Concept of pointers 53
2.1.1 Reference by name and reference by address 53
2.1.2 Management of storage space 55
2.1.2.1 Management of computer memory space 57
2.1.2.2 Storage rules of data in memory 58
2.1.2.3 Address management in memory 58
2.1.3 Definition of pointers 60
2.1.3.1 Comparison of pointer variables and plain variables 60
2.1.3.2 Syntax of pointer definitions 61
2.2 Pointer operations 61
2.2.1 Pointer operators 62
VI Contents
3 Composite data 93
3.1 Concept of structures 93
3.1.1 Introduction 93
3.1.2 Storage solution of mixed data table 94
3.1.2.1 Discussion of possible storage solution of mixed data table 94
3.1.2.2 Issues of constructing “combinatorial data” 95
3.1.2.3 Key elements of constructional data 95
3.2 Storage of structures 96
3.2.1 Type definitions of structures 96
3.2.2 Definition of structure variables 98
3.2.3 Structure initialization 99
3.2.4 Memory allocation of structure variables 100
3.2.4.1 Definitions related to structure 100
3.2.4.2 Memory layout of structure variables 100
3.2.4.3 Inspection of memory layout of structure variables 100
3.2.4.4 Data alignment of structures 102
3.2.5 Referencing structure members 105
3.3 Applications of structures 106
3.4 Union 117
3.4.1 Introduction 117
3.4.2 Memory layout of unions 118
3.4.2.1 Union-type definition 118
Contents VII
4 Functions 139
4.1 Concept of functions 139
4.1.1 Introduction 139
4.1.1.1 Modularization and module reuse in practice 139
4.1.1.2 Abstraction of practical problems: independent code
modules 140
4.1.2 Concept of modules 141
4.1.2.1 Coordination problems in teamwork 141
4.1.2.2 Coordination problems in modularization of programs 141
4.1.2.3 Concept of modules 142
4.2 Function form design 143
4.2.1 Methods of communication between modules 143
4.2.2 Function form design 144
4.2.2.1 Analysis of outsourcing structure 144
4.2.2.2 Abstraction of outsourcing structure 145
4.2.2.3 Function form design 145
4.2.2.4 Information transmission mechanism design 146
4.2.2.5 Three syntaxes related to functions 147
VIII Contents
Index 287
1 Arrays
Main contents
– Concept, usage, and available methods of arrays
– Introduction of representation and nature of arrays through comparison between array/
array elements and plain variables
– Storage characteristics and debugging techniques of arrays
– Programming techniques of multidimensional arrays
– Top-down algorithm design practices
Learning objectives
– Know how to define and initialize arrays as well as how to access array elements
– Be able to define and use multidimensional arrays
– Know how to deal with character arrays
Program statements and data construct programs. They are sequences of instruc-
tions created through algorithm design that conform to program control structures.
However, are we able to solve all problems after learning statements, basic data
types, program control structures, and algorithm implementation methods of C?
Let us look at a few problems in practice.
https://doi.org/10.1515/9783110692303-001
2 1 Arrays
Case study 1
Encryption and decryption of Caesar code
Mr. Brown stared at the ciphertext and thought that it would not be hard to design
an algorithm to solve the problem. He could simply shift each character in the ciphertext
by one position in the alphabet and repeat this process 26 times to list all possible plain-
texts, in which the one that is not nonsense would be the real plaintext. A universal al-
gorithm could be designed using this technique to crack ciphertexts of arbitrary length.
– If the length of the ciphertext is 2, we shift letters by one position in the alpha-
bet each time and list all 26 possible plaintexts.
– If the length of the ciphertext is 10, we shift letters by one position in the alpha-
bet each time and list all 26 possible plaintexts.
– If the length of the ciphertext is 100, we shift letters by one position in the al-
phabet each time and list all 26 possible plaintexts.
Discussion: Solving a problem with computers involves two major steps: first, we should use
reasonable data structures to describe the problem to store data into computers; second, we
create algorithms to solve it. To answer the earlier questions, we need to find a mechanism that
describes variables of the same type and handles them consistently.
Code implementation of the algorithm that solves Caesar codes is rather complicated,
so we shall introduce it later. Before that, let us consider a reversed order problem
that is more trivial.
Case study 2
Reversing 100 numbers
We need a way to
represent variables of
X1 X2 X3 ......... Xi .......... X99 X100 the same type so that
they can be processed
consistently
The flow of outputting 100 numbers backward is given in Figure 1.3. The pro-
gram reads the numbers in a loop starting from X1, and outputs them using a loop
starting from X100.
Start
End
01 int main(void )
02 {
03 int i;
04 int x[100];
05 for ( i=1;i<=100; i++) scanf ("%d", &x[i] );
06 for ( i=100; i>=1; i-- ) printf ("%d", x[i] );
07 return 0;
08 }
On line 4, the statement defines 100 variables with subscripts of type int. It is more
convenient to “batch” define variables of the same type.
It is worth noting that the starting subscripts on line 5 and line 6 do not follow
the convention of using C arrays exactly.
4 1 Arrays
Subscripts of arrays start from 0 in C. Here, we are trying to make the flow more
intuitive by not following this rule.
Case study 3
Simple table processing
i 0 1 2 3 4 5
grade[i] 80 82 91 68 77 78
Pseudo code
Store grades in int grade[6]
Total score total = 0; Counter i = 0;
while i < 6
total= total+grade[i];
i++;
Average= total / 6
We use a while loop to add each grade[i] to total grade total. The value of i increases
in each iteration so that all variables are handled.
It is clear that the algorithm is trivial as long as we find a way to store and rep-
resent data of the same type. This also shows that the way data are organized and
represented is a crucial issue when solving problems with computers.
Case study 4
Complex table processing
We can use a for loop to process grades for a single student and use another
one to calculate average grades for all of them. The algorithm and code implemen-
tation will be given in the section of two-dimensional arrays.
The discussion earlier showed that a new mechanism is necessary to handle data of
the same type. With respect to data representation and processing, arrays are a
data structure that regularly expresses data so that they are processed regularly.
Since arrays are collections of variables whose names have a pattern, they are sup-
posed to have features of variables. Figure 1.6 compares arrays with plain variables.
During the definition of a plain variable, the system allocates memory according to
its type specified by programmers. The definition of an array consists of type, name
and, in particular, the number of variables in the array.
There are multiple variable values in an array, so they should be stored in mul-
tiple storage units, whose sizes depend on types of the variables. The size of a stor-
age unit is measured in bytes and can be computed using the sizeof operator.
Besides, a referencing method of the address of a storage unit is necessary so
that programmers can inspect the unit.
We can infer from the examples earlier that the referencing method of variable
values in an array is to use the array name with an index.
Moreover, we should be able to initialize an array since we can do the same
with plain variables. Hence, a corresponding syntax is necessary.
There are four issues related to array storage, namely definition, initialization, mem-
ory allocation, and memory inspection.
Arrays
E.g.
Number of Number of Memory size
Definition Type Name
dimensions variables
int x[ 100 ] int x 1 100 100* sizeof(int)
char c[2][3] char c 2 2*3 2*3* sizeof(char)
In the figure above, the first row defines a one-dimensional integer array x with 100
variables. To compute the size of its memory space, we can obtain the size of its
type using the sizeof operator and multiply it with the number of variables. The second
row defines a two-dimensional character array with two rows and three columns. In
other words, it has six variables in total. The array name is c.
Think and discuss Do contents inside square brackets in an array definition and an element ref-
erence refer to the same thing?
Discussion: The index of an array element is a numerical expression, which indicates the posi-
tion of the element in an array; the object inside square brackets in an array definition has to
be a constant, which indicates the number of elements in the corresponding dimension. It is
worth noting that the number of elements must not be a variable. Like plain variables, arrays
obtain memory space from the system during array definition. The size of the allocated space
does not change during execution once the array is defined. Such a way of memory utilization
and management is called static memory allocation. On the other hand, C also provides “dy-
namic memory allocation,” which will be introduced in examples in chapter “Functions”.
Indices of array elements in C must start from 0. Accessing an array out of bound leads to a
logic error, but it is not a syntax error.
For example, the one-dimensional array x defined in Figure 1.8 has 100 elements with an
index range 0 to 99. If we try to access an element outside this range, we are accessing the
array out of bound. Grammatically, it is equivalent to using undefined variables.
Array elements
E.g.
Definition Index range Correct usage Out of bound examples
int x[ 100 ] [0]~[99] x[0], x[6], x[99] x[-1], x[100]
The reason that out-of-bound errors are not syntax errors is that the compiler will not check whether
the index is valid. As a result, programmers should take care of indices when using arrays.
8 1 Arrays
Having learned how to define arrays and how to reference array elements, we can
complete the program for number reversing problem.
01 int main(void )
02 {
03 int i;
04 int x[100]; // Array definition
//x[i] references array elements, the index is an expression
05 for ( i=0;i<100; i++) scanf ("%d", &x[i] );
06 for ( i=99; i>=0; i-- ) printf ("%d", x[i] );
07 return 0;
08 }
Line 4 contains definition of an array. Note how we reference array elements on line
5 and line 6.
Indices in square brackets on line 5 and 6 are variables, which are special forms
of expression. They start from 0 and end at 99.
Grammatically, the index of an array element should be a numerical expression
and the index of the first element of an array must be 0.
We can modify the keyboard input part in the code implementation of the number
reversing problem so that the array is initialized with values. The revised program
is as follows:
01 int main(void )
02 {
03 int i; //Defines an array and initializes array elements
04 int x[10]={1,2,3,4,5,6,7,8,9,10};
05 //for ( i=0;i<10; i++) scanf ("%d", &x[i] );
06 for ( i=9; i>=0; i-- ) printf ("%d", x[i] );
07 return 0;
08 }
Statement on line 4 defines the array and initializes array elements, so the keyboard
input assignment can be skipped.
What is the advantage of initializing an array? If we have to debug the program
multiple times, it is more efficient to initialize the array than typing in numbers
repeatedly.
Array initialization defines an array and initializes its elements at the same time.
There are three ways to initialize an array in C, as shown in Figure 1.11.
Array initialization
An array initialization defines an array and initializes its elements at the same time
int n[ ] = {1,3,5,7,9} 5
Array size determined by
3 String termination mark ‘\0’
number of initial values char c[ ] =“abcde”; 6
is also an element
three columns, so it consists of six elements. Note that how curly brackets are used
when assigning all six values.
the beginning position of the 0th row and a[1] denotes the beginning position of the
first row.
C defines that the one-dimensional form of a two-dimensional array which de-
notes “row address”.
With the help of IDE, we can inspect how arrays are stored in the memory. We shall
start from cases where arrays are initialized. The program is as follows:
On line 6, we define an integer array m of size 5 and initialize it. If we type in the
array name m in the Watch window, we can see the beginning address of the array
and values of each element, as shown in Figure 1.14.
m n c
x a
String termination
mark \‘0’ is
automatically
inserted by the
system
On line 7, we define an integer array n without specifying the size and initialize it
with four initial values. We can see that 4 memory units are allocated to it.
On line 8, we define an integer array x of size 8 and partially initialize it. It is
clear that the uninitialized elements are set to 0 by the system.
On line 9, we define a character array c without specifying the size and initialize
it with a string of five characters. The system allocates six storage units, where the
last one has value 0. This is the string termination mark inserted by the system
automatically. It also takes up one storage unit.
On line 10, we define a 2 by 3 two-dimensional array a and initialize it. Each row
of the array has a beginning address, where the address of the first row is also the
beginning address of the entire array.
14 1 Arrays
0 1 2 Row i 0 1
a[0] → 0 1 3 5 Column j 0 1 2 0 1 2
a[1] → 1 2 4 6 a[i][j] 1 3 5 2 4 6
Similarly, int a[2][3] takes up a continuous block of memory with size 6*4 bytes, as
shown in Figure 1.17.
Note that the array name refers to the address of the entire array, which is also the
beginning address of the array.
With rules of storage and elements referencing, we may now process data in
arrays.
16 1 Arrays
2. Algorithm description
We have seen this problem in section “representation of algorithms”, where the scores were
read from keyboard input. Now we can store scores given by referees in an array score[10]. The
algorithm can then be updated accordingly, as shown in Figure 1.18.
In the second refinement, a counter i is used to record the number of comparisons. Variable
Largest is initialized with score[0]; then, Largest is compared with score[i] repeatedly and up-
dated with the larger value in the loop body. Once the loop is done, Largest is printed.
3. Code implementation
01 //Finding the maximum number in an array
02 #include <stdio.h>
03 #define SIZE 10
04
05 int main(void)
06 {
07 int score[SIZE]
08 = {89,92,97,95,90,96,94,92,90,98};
09 int i; //Counter
10 int Largest =score[0]; //Initialize Largest with score [0] as a comparison basis
11 for ( i = 0; i < SIZE; i++ )
12 {
13 if (Largest < score[i])
14 Largest=score[i]; //Find the maximum
15 }
16 printf( "The highest score is %d\n", Largest );
17 return 0;
18 }
1.3 Operations on one-dimensional arrays 17
Program result:
The highest score is 98
Note: the score array is initialized on line 8 so that testing becomes easier.
On lines 11–15, the for loop finds the largest value and stores it in variable Largest.
Based on this program, it is trivial to write a program that finds the minimum number. Now we
can discard both the highest score and the lowest score by replacing them with 0.
4. Debugging
One should carefully design test cases for inspection or verification. Critical points in the de-
bugging of the earlier program are shown in Figure 1.19.
Debugging
plan 11 for ( i= 0; i< SIZE; i++ )
12 {
– Inspect memory layout of 1-d array
13 if (Largest < score[i])
– Reference of array elements
14 Largest=score[i];
– Use breakpoints to find required values quickly
// Find the maximum
15 }
Figure 1.20 shows the score array in the Watch window. There are 10 elements, each of which
are initialized with an initial value. The maximum value Largest is initialized with the value of
score[0], which is 89.
In Figure 1.21, the condition of if statement in the for loop evaluates to false when i = 0, so
Largest keeps unchanged.
18 1 Arrays
In Figure 1.24, we insert a breakpoint in the line pointed by the yellow arrow to inspect pro-
gram execution conveniently. Using the Go command, we can interrupt the program at this
statement whenever the condition of if statement evaluates to true. Here i = 2 and score[2] has
value 97, which is larger than the value of Largest, 92.
In Figure 1.25, we execute the Go command and the program pauses again. Now, i = 9 and
score[9] has value 98, which is larger than the value of Largest, 97.
20 1 Arrays
In Figure 1.26, the loop terminates and the final value of Largest is 98.
[Analysis]
1. Algorithm design
The algorithm is shown in Figure 1.27. Code implementation can be easily adapted from the pseudo
code in the second refinement.
1.3 Operations on one-dimensional arrays 21
After eliminating the highest score and the lowest score, we can compute the total score that
complies with the scoring rule.
2. Code implementation
01 //Compute sum of array elements
02 #include <stdio.h>
03 #define SIZE 10
04
05 int main(void)
06 {
07 int score[ SIZE ] = {98,92,89,95,90,96,94,92,90,97};
08 int i; //counter
09 int total = 0; //sum
10
11 for ( i = 0; i < SIZE; i++ )
12 {
13 total +=score[ i ]; //Compute sum of array elements
14 }
15 printf( "The total score is %d\n", total );
16 return 0;
17 }
Program result:
The total score is 933
[Analysis]
1. Algorithm analysis
Let low denote the position of the minimum value in the searching range, and high denote the
position of the maximum value in the searching range. The comparison position in binary search
is then mid = (low + high)/2. Comparing key value with the element at position mid yields one of
the following results:
– Equal: the element at position mid is what we are looking for.
– Greater: we will look for the element in the lower range by setting low = mid + 1.
– Less: we will look for the element in the higher range by setting high = mid – 1.
22 1 Arrays
Figures 1.28 and 1.29 illustrate processes of finding values 19 and 66.
Search for
19
R[] 5 10 19 21 31 37 42 48 50 55
Position 0 1 2 3 4 5 6 7 8 9
R[] 5 10 19 21 31 37 42 48 50 55
Position 0 1 2 3 4 5 6 7 8 9
Search for
66
R[] 5 10 19 21 31 37 42 48 50 55
Position 0 1 2 3 4 5 6 7 8 9
R[] 5 10 19 21 31 37 42 48 50 55
Position 0 1 2 3 4 5 6 7 8 9
R[] 5 10 19 21 31 37 42 48 50 55
Position 0 1 2 3 4 5 6 7 8 9
low mid
Now mid=9, R[mid].key=55<k, we proceed in range
R[10…9].Because low>high, the search failed.
2. Code implementation
#include <stdio.h>
#define N 10
int main(void)
{
int a[N]={5,10,19,21,31,37,42,48,50,55};
int low=0, high=N-1,mid;
int key;
int flag=0; //Search flag, 0=fail, 1=success
printf("Please enter number to search:");
scanf("%d",&key);
while (low<=high) //Search range is not empty
{
mid = (low+high+1)/2;
if (a[mid]== key) //Match
{
flag=1;
break;
}
else
{
if (a[mid]> key) high = mid-1; //Continue searching in lower range
else low = mid+1; // Continue searching in higher range
}
}
if (flag==1)
printf("Search succeeded, index of %d is %d\n",key,mid);
else
printf("Search failed\n");
return 0;
}
[Analysis]
1. Data structure design
Since indices and values in the Fibonacci sequence are 1-to-1 corresponded, we can store values
into a one-dimensional integer array, which will be represented by int f[20] in this example.
2. Algorithm design
We shall construct the first 20 entries based on the recurrence equation of the Fibonacci se-
quence, store them into the array and eventually output them.
24 1 Arrays
3. Code implementation
1 //Find first 20 entries in Fibonacci sequence
2 #include <stdio.h>
3 int main(void)
4 {
5 int i;
6 int f[20]={0, 1}; //Array initialization
7
8 for (i=2; i<20; i++) //Generate the sequence
9 {
10 f[i]=f[i-1]+f[i-2]; //Recurrence equation of Fibonacci equation
11 }
12 for (i=0; i<20; i++) //Output array elements
13 {
14 if (i%5==0) printf("\n"); //Print 5 entries on each line
15 printf("%8d", f[i]);
16 }
17 return 0;
18 }
Program result:
0 1 1 2 3
5 8 13 21 34
55 89 144 233 377
610 987 1597 2584 4181
4. Program analysis
We shall analyze characteristics of iterated data processing by reading the program.
Lines 8–11 insert values into the Fibonacci array. Let the index be i, which corresponds to
array element f[i]. We can construct a table for them and fill in it with their values, as shown in
Figure 1.31. In addition to dynamic tracing and debugging, a static approach like this can also
help us analyze patterns in program execution. Note that indices start from 0, so the index of
the last element should be one less than the array size.
1.3 Operations on one-dimensional arrays 25
Index i 0 1 2 3 4 5 … 18 19 20
f[i] 0 1 2 3 5 8 … … … …
5. Discussion
(1) What if we do not initialize array f?
Discussion: If so, values of f[0] and f[1] will be arbitrary values, so further computation
will be wrong.
(2) How can we construct the Fibonacci sequence of arbitrary size?
Discussion: We can make the array size a symbol constant, so the program can be easily
adapted.
(3) What if we change the execution condition of the first for loop (line 8) to i ≤ 20?
Discussion: An out-of-bound error will happen because we are going to write to f[20],
which is not in the range of the array. This is a logic error in the program.
1. Algorithm description
Let the ratings array be rating[ ]. It records number of occurrences of each score. The index i can
be computed by subtracting 6 from score x (6 ≤ x ≤ 10), that is, i = x–6, so we can use values
score–6 as indices of the ratings array. Whenever we find a new occurrence of a certain score,
we add one to the corresponding array element.
2. Code implementation
1 #include<stdio.h>
2 #define RESPONSE_NUM 50 //Size of review array
3 #define RATING_SIZE 5 //Size of ratings array
4
5 int main(void)
6 {
7 int answer; //Counter
8 int counter;
9
10 int rating[RATING_SIZE]={0}; //Rating array
11 int responses[RESPONSE_NUM] //Review array that stores students’reviews
12 ={ 6,8,9,10,6,9,8,7,7,10,6,9,7,7,7,6,8,10,7,
13 10,8,7,7,6,7,8,9,7,8,7,10,6,7,6,7,7,10,8,
14 6,7,7,8,6,6,7,8,9,7,7,10
15 };
16
26 1 Arrays
Program result:
Rating Number of occurrences
6 10
7 19
8 9
9 5
10 7
[Analysis]
1. Data description
As shown in Figure 1.32, we can store the scores in a two-dimensional array.
Essentially, this problem is equivalent to finding the maximum value in a two-dimensional
array with N rows and M columns and its row and column indices. To do this, we can simply
repeat the process of finding the maximum value in a one-dimensional array N times.
Figure 1.33 shows how row and column indices change when traversing the array in a row-
first manner. We first traverse row 0, with column index changing from 0 to M–1. Then we tra-
verse row 1, with column index changing from 0 to M–1 as well. We repeat this process until we
reach row N–1.
1.4 Operations on two-dimensional arrays 27
Group Grade
1 80 77 75 68 82 78
2 78 83 82 72 80 66
3 73 50 62 60 91 72
Column
0 1 2 3 4 5
Row
0 80 77 75 68 82 78
1 78 83 82 72 80 66
2 73 50 62 60 91 72
2. Algorithm description
Figures 1.34 and 1.35 show the pseudo code of the algorithm.
3. Code implementation
We can write the code based on the second refinement, in which we use for statements to
implement while loops. The complete code is as follows:
01 #include <stdio.h>
02 #define N 3
03 #define M 6
04
05 int main(void)
06 {
07 int i,j,max,line,col;
08 int a[N][M]= { {80,77,75,68,82,78},
09 {78,83,82,72,80,66},
10 {73,50,62,60,91,72}
11 };
12 max=a[0][0];
13 line=col=0;
14 for (i=0; i<N; i++)
15 {
16 for ( j=0; j<M; j++)
17 {
18 if (max<a[i][j])
19 {
20 max=a[i][j];
21 line=i;
22 col=j;
23 }
24 }
25 }
26 printf("max=%d\t line=%d\t col=%d\n",max,line,col);
1.4 Operations on two-dimensional arrays 29
27 return 0;
28 }
Program result:
max=91 line=2 col=4。
4. Debugging
Based on the characteristics of two-dimensional arrays and key points of this problem, we
designed a few test cases for debugging, as shown in Figure 1.36.
18 if(max<a[i][j])
Debugging 19 {
plan 20 max=a[i][j];
• Inspect memory layout of 2-d array 21 line=i;
• Pattern of row and column indices 22 col=j;
• Use breakpoints to find required values quickly 23 }
Figure 1.36: Key points of debugging the program that finds maximum value in a two-dimensional
array.
One may notice that the row addresses of a two-dimensional array are represented in the form of
a one-dimensional array in the IDE debugger, as shown in Figure 1.37. To traverse the entire array,
we traverse every column for each row. Note that a two-dimensional array is stored row by row in
memory (each row as a one-dimensional array).
Address Row 0 1 2 3 4 5
a[0] 0x18feec 0 80 77 75 68 82 78
a[1] 0x18ff04 1 78 83 82 72 80 66
a[2] 0x18ff1c 2 73 50 62 60 91 72
As shown in Figure 1.38, we insert one breakpoint to the line where the current maximum
value is updated and to the line where the result gets printed. When the program enters the
first loop, as shown in Figure 1.39, the 0th element of the array is selected as the comparison
basis, whose value is a[0][0] = 80. In Figure 1.40, the program pauses after we execute the Go
command. The value of the element with index i = 0 and j = 4 is 82, which is larger than max.
Figure 1.38: Debugging the program that finds maximum in two-dimensional array 1.
Figure 1.39: Debugging the program that finds maximum in two-dimensional array 2.
1.4 Operations on two-dimensional arrays 31
Figure 1.40: Debugging the program that finds maximum in two-dimensional array 3.
In Figure 1.41, the program pauses after we execute the Go command. The value of the element
with index i = 1 and j = 1 is 83, which is larger than max.
Figure 1.41: Debugging the program that finds maximum in two-dimensional array 4.
In Figure 1.42, the program pauses after we execute the Go command. The value of the element
with index i = 2 and j = 4 is 91, which is larger than max.
Figure 1.42: Debugging the program that finds maximum in two-dimensional array 5.
In Figure 1.43, the program completed scanning the array, and the loop is terminated. Now, i = 3,
j = 6, and the maximum value of the array is max = 91.
32 1 Arrays
Figure 1.43: Debugging the program that finds maximum in two-dimensional array 6.
Outer loop F
condition
T
1 for(i=0;i< N;i++)
2 { Inner loop F
3 for( j=0;j< M;j++) condition
4 { T
5 if (max<a[i][j])
Outer Inner loop body
6 { max=a[i][j]; Inner loop
7 line=i; loop
Inner loop
8 col=j;}
increment
9 }
10 } Outer loop
increment
1. Algorithm description
The program uses random functions srand and rand to generate positions at which moles ap-
pear. The following Whac-A-Mole program has a 3 by 3 “ground” and treats user input coordi-
nates as positions the mallet hits. Although it is a console program and has a simple user
interface, the way it works is the same as a Whac-A-Mole game with beautiful graphics.
2. Code implementation
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//To simplify the code, we omitted curly brackets for some of the if-else statements
int main(void)
{
int times = 0; //Number of chances
int mousey = 0; //Row index of the mole
int mousex = 0; //Column index of the mole
int posy = 0; //Row index of the mallet
int posx = 0; //Column index of the mallet
int hits = 0; //Number of hits
int missed = 0; //Number of misses
int num = 0, row = 0, col = 0;
srand(time(0));
//Obtain game chances
printf("How many times do you want to play?:");
scanf("%d", ×);
//Print the map
printf("***\n***\n***\n");
printf("Mallet position input should be row index followed by column index, separated by
space\n");
//Actual game process
for (num = 1;num <= times;num++)
{
//Obtain position of mole and mallet
mousey = rand() % 3 + 1;
mousex = rand() % 3 + 1;
do
{
printf("Enter mallet position:");
scanf("%d %d", &posy, &posx);
} while (posy < 1 || posy > 3 || posx < 1 || posx > 3);
//Update number of hits and misses
if (mousey == posy && mousex == posx) hits++;
else missed++;
//Print the map
for (row = 1;row <= 3;row++)
{
for (col = 1;col <= 3;col++)
{
34 1 Arrays
[Analysis]
1. Data analysis
We shall first use the given information to eliminate the wrong answers.
Based on conditions 1, 2, and 3, we can conclude that A is not American, E is not Russian,
and C is not German. Based on occupation limits (A and the German have different jobs, so do E
and the American, E and the German, C and the American, and C and the Russian), it is clear
that A is neither Russian nor German, E is neither American nor German, and C is neither
American nor Russian.
It can be inferred from conditions 4 and 5 that neither B nor F is German, A is not French, and
C is not Italian.
Given condition 6, we know B is neither American nor French (because B and the French are
going to different cities next week), and C is not French.
To sum up:
A: A is not American, Russian, German, or French.
B: B is not German, American, or French.
C: C is not German, American, Russian, Italian, or French.
D: no information.
E: E is not American or German.
F: F is not German.
1.4 Operations on two-dimensional arrays 35
We can store the earlier information into matrix a, and country names into another one-
dimensional array countries, as shown in Figure 1.45.
Rows of matrix a represent these guests, while columns represent their home countries. The
0th row is a special row for progress flags, which is either 1 for not processed or 0 for proc-
essed. The values of other elements indicate nationalities. For example, 4 represents Germany
in the countries array. If a value is 0, the person represented by the row does not come from the
country represented by the column.
2. Algorithm design
Following steps 2 and 3 in Figure 1.45, we can find the solution by repeatedly zeroing out rows.
0 1 2 3 4 5 6
Am Br Fr Ge It Ru
1
Column data process flag
0 1 1 1 1 1 1
1: not processed, 0:processed
1 A 0 2 0 0 5 0
2 B 0 2 0 0 5 6
3 C 0 2 0 0 0 0 3
5 E 0 2 3 0 5 6
6 F 1 2 3 0 5 6
2
Find a column y with only one non-zero element
Counter num=1, row index x=4, column index y=4
0 1 2 3 4 5 6
American British French German Italian Russian
3. Code implementation
#include<stdio.h>
char *countries[7]={" ","American","British","French","German","Italian","Russian"};
//The asterisk before countries indicates that the array stores addresses,
//which are beginning addresses of strings
int main(void)
{
int a[7][7],i,j,k,num,x,y;
for(i=0;i<7;i++) //Initialize the matrix
for(j=0;j<7;j++) a[i][j]=j; //Row for person, column for country,
//and value for nationality
for(i=1;i<7;i++) a[0][i]=1; //0-th element in each column is the progress mark,
//1 means not processed
//Enter know information, 0 means the person is not from a country
a[1][1] = a[1][3] = a[1][4] = a[1][6] = 0; // A is not American, Russian, German or French
a[2][1]= a[2][3]= a[2][4] =0; // B is not German, American or French
a[3][1] = a[3][3] = a[3][4]= a[3][5] =a[3][6] = 0;
// C is not German, American, Russian, Italian or French
36 1 Arrays
Program result:
A is Italian
B is Russian
1.5 Operations on character arrays 37
C is British
D is German
E is French
F is American
Index 0 1 2 3 4 5 6 7 8 9 ... 18 19
Registered
'a' 'b' 'c' '2' '4' '6' '8' '0'
password
[Analysis]
1. Storage structure of data
If we use character arrays to store passwords, there are two possible ways to assign initial val-
ues: the first is to assign characters one by one, while the other is to assign a string. Characters
stored in these two approaches are the same, but termination mark ‘\0’ will be automatically
inserted to the end of the string by the system, as shown in Figure 1.47.
Storage
solution 1 char password1[20]={'a','b','c','2','4','6','8','0'};
What is the
Storage difference between
solution 2 char password2[20]="abc 24680";
these two solutions?
Solution 1
Index 0 1 2 3 4 5 6 7 8 9 ... 18 19
Registered
password 'a' 'b' 'c' '2' '4' '6' '8' '0'
Solution 2
The string has
Index 0 1 2 3 4 5 6 7 8 9 ... 18 19
termination mark
Registered
password 'a' 'b' 'c' '2' '4' '6' '8' '0' \0
Note: one can store strings of any length in C. When storing strings in character arrays, pro-
grammers need to make sure that the array size is large enough so that the longest string can
fit in; if the string is longer than the array, characters beyond the array bound will override data
after the array in memory.
2. Algorithm description
Figure 1.48 shows the stepwise refined algorithm.
38 1 Arrays
Second refinement
char password[20]; int i=0;
ch=getchar();
while (ch!='\n')
if (ch != password[i])
printf(“Password is wrong");
Jump out of loop
ch=getchar();
i++;
if ( i==strlen(password) )
printf(“Password is correct");
In the second refinement, ch! = ‘\n’ checks whether there are more inputs. The loop control
variable i acts as a counter as well. strlen is a library function that computes string length (not
counting termination mark ‘\0’). To determine whether the entire string has been checked, we
compare i with the string length.
3. Code implementation
01 #include <stdio.h>
02 #include <string.h>
03 int main(void )
04 {
05 int i=0;
06 char ch;
07 char password[20]="abc24680";
08 ch=getchar();
09 while (ch!='\n')
10 {
11 if (ch != password[i]) break;
12 ch=getchar();
13 i++;
14 }
15 if (i==strlen(password)) printf("Password is correct\n");
16 else printf("Password is wrong\n");
17 return 0;
18 }
Note that the header file for library function strlen on line 19 is included on line 2.
1.5 Operations on character arrays 39
Ciphertext Plaintext
Decryption
[Analysis]
1. Data processing
Without loss of generality, we shall use right shift (the alphabet is shifted by one character to its
right each time) in the following discussion. To crack the ciphertext, we can list all 26 possible
results and look for a meaningful string. Figure 1.50 shows the case of shifting by one character.
2. Algorithm description
Figure 1.51 shows the pseudo code of the algorithm.
In the second refinement, ‘\0’ is used to determine whether the entire string has been proc-
essed. Space is represented by a space wrapped with single quotation marks. The shifted ci-
phertext is computed using the formula we derived earlier. When printing strings, a number
indicating the number of characters shifted is added to the beginning. Finally, we need to find a
meaningful string in printed contents manually.
3. Code implementation
01 #include "stdio.h"
02 #define SIZE 80
03 int main(void)
04 {
05 char ciphertext[SIZE]="lettc fmvxlhec hehhc pszi csy";
06 int i=0,j=0;
07 printf( "%s\n",ciphertext);
08 while (j<26)
09 {
Second refinement
while(j<26)
while(ciphertext[i]!='\0') //while not reaching string end
if (ciphertext[i] !=‘ ’) //skipspace
ciphertext[i]=(ciphertext[i]+1-‘a’)%26+‘a’ //right shift by 1
i++ Finally, we need
printf( "%d:%s\n", j, ciphertext ) to manually find
meaningful string
i=0
j++
10 while (ciphertext[i]!='\0')
11 {
12 if (ciphertext[i]!=' ')
13 {
14 ciphertext[i]=(ciphertext[i]+1-'a')%26+'a';
15 }
16 i++;
17 }
18 printf("%d:%s\n",j,ciphertext);
19 i=0;
20 j++;
21 }
22 return 0;
23 }
Program result:
lettc fmvxlhec hehhc pszi csy
0:mfuud gnwymifd ifiid qtaj dtz
1:ngvve hoxznjge jgjje rubk eua
2:ohwwf ipyaokhf khkkf svcl fvb
3:pixxg jqzbplig lillg twdm gwc
4:qjyyh kracqmjh mjmmh uxen hxd
5:rkzzi lsbdrnki nknni vyfo iye
6:slaaj mtcesolj olooj wzgp jzf
7:tmbbk nudftpmk pmppk xahq kag
8:unccl oveguqnl qnqql ybir lbh
9:voddm pwfhvrom rorrm zcjs mci
10:wpeen qxgiwspn spssn adkt ndj
11:xqffo ryhjxtqo tqtto belu oek
12:yrggp szikyurp uruup cfmv pfl
13:zshhq tajlzvsq vsvvq dgnw qgm
14:atiir ubkmawtr wtwwr ehox rhn
15:bujjs vclnbxus xuxxs fipy sio
16:cvkkt wdmocyvt yvyyt gjqz tjp
17:dwllu xenpdzwu zwzzu hkra ukq
18:exmmv yfoqeaxv axaav ilsb vlr
19:fynnw zgprfbyw bybbw jmtc wms
20:gzoox ahqsgczx czccx knud xnt
21:happy birthday daddy love you
22:ibqqz cjsuiebz ebeez mpwf zpv
23:jcrra dktvjfca fcffa nqxg aqw
24:kdssb eluwkgdb gdggb oryh brx
25:lettc fmvxlhec hehhc pszi csy
It is clear that the twenty-first string is what we want: “happy birthday daddy love you”. Mr.
Brown was impressed by what he saw: Daniel had not learned to program, but he was able to
manually compute the ciphertext without mistakes.
42 1 Arrays
[Analysis]
1. Data storage
Each family name is a string, so multiple family names are multiple strings, which can be stored
into a two-dimensional character array, as shown in Figure 1.52. Since we have five family names,
the number of rows in the array should be 5. The longest name has five characters, so the number
of columns should be 6 to store the name and a termination mark. The one-dimensional form of
the array can represent the beginning address of a row in a two-dimensional array.
C[0] Z h a o \0 \0
The beginning address of
C[1] Z h o u \0 \0
a row in a 2-dimensional
array can be represented C[2] Z h a n g \0
by the 1-dimensional C[3] Z h a n \0 \0
form of the array. C[4] Z h e n g \0
2. Algorithm description
The pseudo code is shown in Figure 1.53.
C provides many library functions to process strings. As shown in Figure 1.54, this algorithm
requires strcpy for string copying and strcmp for string comparison.
On line 10, we use the string copy function to copy c[0] into array str.
On line 13, we compare the contents of str and c[i].
On line 15, we copy the larger string into str.
4. Debugging
Based on characteristics of the earlier program, we can conclude the key steps in debugging as
follows:
Inspecting a two-dimensional character array: string initialization and termination mark.
Inspecting row addresses of a two-dimensional array: the beginning address of a row in a
two-dimensional array is represented by the array name suffixed with one-dimensional index;
Inspecting how strcpy and strcmp functions work.
Figure 1.55 shows the two-dimensional character array c after initialization. Each row has
length 6 and stores a string. If a string has less than six characters, the system pads it with 0.
The address of a row is the beginning address of the string in that row, represented by c[i]. i is
an integer in the range 0–5.
When the one-dimensional array character str was not initialized, its elements were decimal num-
ber –52, which corresponded to random Chinese characters. The reason “Zhou” is also displayed
is that the system stops upon reaching the termination mark ‘\0’ when displaying strings.
We use strcpy function to copy c[0] into str. In Figure 1.56, this change is shown in the Watch
window.
In Figure 1.57, i has value 1, and c[i] has value “Zhou” in the first iteration.
In Figure 1.58, the strcpy function in the if statement is executed and str now stores the string
“Zhou”. This indicates that the result of the strcmp function is less than 0.
1.6 Summary
Arrays are one of the most commonly used data structures in programming. An
array can be one-dimensional, two-dimensional, or multidimensional.
An array declaration consists of a type identifier, an array name and an array
length. An array element is also called an indexed variable.
Assigning values to an array can be done through initialization, input functions,
or assignment statements. Figure 1.59 shows the use cases of these approaches.
The main contents and relations between them are shown in Figure 1.60.
A variable is a single datum,
Whereas an array stores a group of data together,
46 1 Arrays
The three key elements of variables are name, value, and address,
Arrays are just about the same.
Memory is allocated to arrays during definition, and it does not change during
execution,
An array name can also be used as the beginning address of the array,
Array elements are of the same type, but their values can be different.
An array element is similar to a variable,
The index indicates its position in the array,
We should remember that indices start at 0,
And that out-of-bound accesses lead to errors.
The system uses ‘\0’ to mark the end of a character array.
48 1 Arrays
1.7 Exercises
1. [Array definition]
Which of the following statements define an array correctly? ( )
A) int num[0. . .2008]
B) int num[]
C) int N=2008; int num[N]
D) #define N 2008 int num[N];
2. [Character array]
Which of the following statements is wrong about character arrays in C? ( )
A) A character array can be used to store a string.
B) A string stored in a character array can be input/output together.
C) We can assign values to a character array using assignment operator "=" in
an assignment statement.
D) We cannot use relational operators to compare strings stored in character
arrays.
5. [String input]
Suppose we have char s[30]={0} and we type “This is a string. <Enter>” during
program execution.
Which of the following statements cannot read the entire string “This is a string”.
into character array s correctly? ( )
A) i=0;while ((c=getchar())!='\n') s[i++]=c
B) gets(s)
C) for (i=0; (c=getchar()) !='\n'; i++) s[i]=c
D) scanf("%s", s)
1.7 Exercises 49
6. [Array access]
What is the output of the following program? ( )
int y=18,i=0,j,a[8];
do
{
a[i]=y%2;
i++;
y=y/2;
} while(y>=1);
for(j=i-1;j>=0;j--) printf("%d",a[j]);
7. [Two-dimensional array]
int i, t[][3]={9,8,7,6,5,4,3,2,1};
for(i=0;i<3;i++) printf("%d ",t[2-i][i]);
8. [Two-dimensional array]
A) peachflower is pink
B) pink is flower Peach
C) peachflowerispink
D) pink is flower peach
i 0 1 2 3
a[i][i] 1 None
s End of loop
Output
i 5 4 3 2 1 0
c[i]=c[i-1]
c[i] Unknown
Before
assignment c[i-1] ‘\0’
Output
0 1 2 3 4 5 6 7 8 9
p[ ]
p[ ] '
Output
int main(void)
{
int a[3][3]={1,2,3,4,5,6,7,8,9}, i, s=0;
for(i=0;i<=2;i++)
s=s+a[i][i];
printf("s=%d\n",s);
return 0;
}
int main(void)
{ int i=5;
char c[6]="abcd";
do
{
c[i]=c[i-1];
} while(--i>0);
puts(c);
return 0;
}
#include <string.h>
int main(void)
{
char p[20]={'a', 'b', 'c', 'd'}; //————①
char q[]="xyz", r[]="mnopq";
strcat(p, r); //————②
strcpy(p+strlen(q), q); //————③
printf("%d\n", strlen(p)); //————④
return 0;
}
1. Thirteen people stand in a circle and are numbered off using only numbers 1, 2,
and 3. That is, they shout out numbers 1, 2, 3, 1, 2, 3, 1, 2, 3. . .. If someone
shouts out number 3, that person should leave the circle. Write a program to
find out which person is the last one remaining in the circle.
52 1 Arrays
2. Write a program that reads a string from keyboard input, sorts it in ascending
order based on ASCII values of characters, and prints the sorted string.
3. Please write a program, in which you define a one-dimensional character array
str[50], read a sequence of characters from keyboard input and store it into str,
read an integer M (M < 50), and finally copy characters after position M in array
str into a new character array ch[50].
4. Write a program that finds the element with value x in a one-dimensional inte-
ger array with 10 elements. If such an element exists, the program should print
its index; otherwise, the program should print "Search failed".
5. In an image encoding algorithm, we need to do a Zigzag scan on a given square
matrix. The Zigzag scan processes of 4 × 4 and 5 × 5 matrices are illustrated
in Figure 1.64. Write a program to simulate this process.
Main contents
– Meaning, usage, and examples of pointers
– Representation and nature of pointers, shown through comparing pointers with plain
variables
– Differences and similarities between pointers and plain variables
– Relations between pointers and arrays
– Nature of pointer offsets
– Program reading practices
– Top-down algorithm design practices
– Debugging techniques of pointers
Learning objectives
– Understand the concept of pointers
– Understand relations between pointers, arrays, and strings
– Know how to use pointers to reference variables and arrays
– Can use string arrays through pointers
– Can design algorithms using the top-down stepwise refinement approach
https://doi.org/10.1515/9783110692303-002
54 2 Pointers
Case study 1
we can reference
Location an object that has
a location attribute
either by name or
by address
Reference by Reference by
name address
seat is a “reference by address.” When assigning homework, a teacher may use the
following statements: “questions 6 and 8 of chapter 3” or “question 6 and 8 on page
126.” The chapter here is a “reference by name,” while the page number is a “reference
by address,” as shown in Figure 2.2.
Case study 2
Reference by Reference by
name address
Student Seat
name location
Chapter Page
We just saw in these examples that we could access a real-life object with location
attributes through its name or its address.
Data in programs are also objects with location attributes, so we can use the
same methods to access them.
2.1 Concept of pointers 55
Case Study 3
Data reference in programs
Data in programs
are also objects with
Reference by name Reference by address location attribute
Variable Variable
name address
x &x
With the concept of variable addresses in mind, we can study how computers man-
age their memory space.
1 5 9 13
2 6 10 14
Stickers:
“Reference
by name”
3 7 11 15
4 8 12 16
1 5 9 13
2 6 10 14
Numbers:
“Reference
by address”
3 7 11 15
4 8 12 16
a customer presses the “Store” button, the system looks for an empty compartment
and opens one following specific rule; if no compartment is currently available, “No
available compartment” will be displayed.
In this process, the number of the compartment is necessary. If we consider the
number of a compartment as its address, then locating compartments through num-
bers is also “reference by address.”
Memory
...
Address ...
In memory, every byte unit 127
(8 bits) has a number, which
is its address. ...
To simplify management, we attach a number to each unit. These numbers are called
addresses. A computer can execute memory read and write operations quickly using
addresses. The length of a memory unit is 1 byte. Addresses of variables are numbers
of memory units allocated by the system.
It is also trivial to convert between hexadecimal and binary representations. C uses prefix 0x to
represent hexadecimal numbers, while assembly languages and some other high-level languages
use suffix H (Hexadecimal).
Memory
Suppose size
....
of int is 2 bytes
Address of an variable is
the number of memory
unit allocated to it by the 2000 1 byte
system during compilation int x
or function call 2001 1 byte
In this case, we need the “address allocation rule.” The system defines the address
of a variable as the unit with the smallest number.
Suppose type int has length of 2 bytes. Professor defined three integer variables
i, j, and k. Then he looked for empty units in the memory. In Figure 2.8, empty units
are colored in gray. Hence, he could allocate unit 2000 to i, unit 2002 to j, and unit
2004 to k.
Similar to managing a locker, the system needs to record which units are already
allocated. This is done using unit addresses as well. As a result, a block of memory
is needed to store these addresses. In addition, we should be able to reference it by
unit addresses.
How should addresses be recorded?
Professor defined a special variable ptr to store the address of variable k. We
often use an arrow to indicate the relation between pointer ptr and variable k. The
value of ptr can be changed, so ptr can be used to store other variable addresses as
well. In C language, we call variable ptr a pointer pointing to variable k. In short,
ptr points to k.
With the help of pointers, system administrators can manage addresses in
memory.
Mr. Brown then stopped being an imaginary system administrator, returned to
reality by slightly shaking his head, and asked himself another question from the per-
spective of a computer user: “as programmers, what is the advantage of using ad-
dress variables?”
It is undoubtedly more intuitive and convenient for programmers to use variables
named by identifiers. However, the system has to find the memory unit that corre-
sponds to a variable name when executing the program. This process slows down
computation, so the system enables programmers to operate memory units through
pointers to enhance execution efficiency, as shown in Figure 2.9. Furthermore, using
60 2 Pointers
1 5
3 7
4 8
pointers help solve problems like batch transfer of data or user space requesting. We
will cover related topics in the chapter “Functions.”
Having discussed the topics earlier, we shall now proceed to introduce pointers
formally.
Rules
Pointers are special variables. Unlike plain variables, they
– are used to store addresses;
– use types of data stored in the memory units they point to as their own type
Figure 2.10: Similarities and differences between pointer variables and plain variables.
C considers the type of a pointer to be the type of data stored in the memory unit it
points to, so it is not necessarily an integer. For example, an integer pointer iPtr
points to an integer memory unit; a float pointer fPtr points to a floating-point num-
ber memory unit; a character pointer works the same way.
Because using pointers requires special rules, it is recommended to name them
using “ptr,” the abbreviation of “pointer,” as a kind reminder.
Having learned how to access a pointer, we can now handle pointer data.
62 2 Pointers
As shown in Figure 2.12, there are two operators related to pointers: the address-of
operator “&” and the dereference operator “*”. We can access contents stored in
memory units by referencing their addresses with the help of these operators.
Index 0 1 2 3 4
x[ ] 2 4 6 8 0
aPtr bPtr
1. Code implementation
Figure 2.15 shows the code implementation, in which we first define the integer array x and ini-
tialize it.
On line 2, we define two integer pointers aPtr and bPtr.
On line 3, we make aPtr point to the beginning address of array x, namely the 0-th unit, with
statement aPtr = x. By definition, the array name x represents the beginning address, which is
the address of element with index 0.
On line 4, we make bPtr point to element with index 3. The & sign is used to obtain the ad-
dress of x[3].
The first 4 lines complete the first task in the problem description. Now we are going to com-
plete the second.
On line 5, we use *bPtr to fetch value stored in the memory unit pointed to by bPtr. Similarly,
the value stored in the memory unit pointed to by aPtr can be acquired by *aPtr. After the as-
signment, this value becomes 8.
Index 0 1 2 3 4
C defines that an
x[ ] 8 4 6 8 0 array name refers
to the array’s
beginning address
aPtr bPtr
2. Debugging
Using data in the Watch window and the Memory window, we obtain the graph shown in
Figure 2.16, which reveals relations between addresses and data.
Note that bytes
What is value with lower
of *aPtr in the addresses are
Watch window? on the left in the
Memory window
Index 0 1 2 3 4
Element
2 4 6 8 0
value
Element
0x18ff34 0x18ff38 0x18ff3c 0x18ff40 0x18ff44
address
We shall inspect array x first. In the Watch window, it is clear that the beginning address of x is
0x18ff34. The Memory window shows the addresses and values of elements in x starting from
address 0x18ff34. Note that bytes in the Memory window are displayed in the order of their ad-
dresses, where bytes in lower addresses are on the left. We can list these addresses and values
in a table for further analysis.
It is known that aPtr points to x[0] and bPtr points to x[3]. This can be verified by comparing
the value of pointer aPtr and the address of x[0] in the Watch window. We can see that the value
of pointer bPtr and the address of x[3] are also identical. Values of aPtr and bPtr are shown in
the square above them in the figure. They are both addresses of other variables.
What are the addresses of memory units in which these pointers are stored then?
In the Watch window, &aPtr indicates the address of the memory unit in which aPtr is stored,
which is 0x18ff30. Similarly, &bPtr shows the address of bPtr. As of now, we have seen all three
key elements of pointer variables, namely variable names, variable addresses, and variable
values.
There remains one last question: what is *aPtr then?
We can derive the answer from definitions and verify them in the debugger. In fact, the an-
swer is already given in the next line of aPtr in the Watch window.
3. Exceptions of pointers
If a postman is going to deliver to a new location and heads to the default address without set-
ting a destination in the navigation system, we can well imagine that he will not succeed.
Similarly, beginners may make the same mistake when using pointers.
In Figure 2.17, what will happen if we make aPtr point to nowhere by removing the third line
in the program?
A warning pops up
when executing
Element *aPtr=*bPtr
2 4 6 8 0
value
Element
0x18ff34 0x18ff38 0x18ff3c 0x18ff40 0x18ff44
address
After debugging, we can see that everything in the Watch window and the Memory window re-
mains the same, except the value of aPtr being 0xcccccccc. This is because we did not initialize
aPtr with the beginning address of array x. The value is determined by the compiler instead of
by programmers, so it is unpredictable. Such pointers are often called “wild pointers.”
During the execution of line 5, namely *aPtr = *bPtr, a protection mechanism interferes: a
warning window pops up, and the program is terminated. This protection mechanism prevents
users from writing data to unknown units, so data w be modified unknowingly.
2.2 Pointer operations 67
Pointer variables are special variables that need special care. The most common mis-
takes one may make when using pointers is accessing them without assigning initial val-
ues. Figure 2.18 elaborates on this mistake and introduces principles we should follow.
Programming mistake
These principles are also critical issues in using pointer. Assigning to a pointer that
does not point to a certain location has two cons:
(1) It may lead to severe runtime errors, namely logic errors, which affects program
execution and may crash the system in the worst case.
(2) Even if the program runs without crashing, the assignment illegally modifies
data stored in some memory unit that we do not know. Such errors are ex-
tremely hard to find during debugging because we do not know when the modi-
fied data are going to be used. If we cannot reproduce such errors, they will
become one of the most challenging errors to debug.
2.2.4.1 Introduction
Suppose we have an integer array a, and a pointer aPtr pointing to a[0]. We are
asked to output the value of the memory unit aPtr points to, and value of the next
unit using aPtr as well.
The previous example showed how to point a pointer to an array element and
obtain its value, as shown in Figure 2.19.
To move pointer aPtr forward, we can certainly use reference by address, or to
be more specific, aPtr = &a[1]. &a[1] returns the location of the element with index 1
in array a. This is an example of reference by array names. When executing this
statement, the system needs to convert &a[1] into memory address corresponding to
the element, which may become inconvenient and inefficient when moving the
pointer multiple times. Are there alternative methods of moving pointers?
Can we use reference by address by adding an offset to the pointer? If so, it
would be easier to move a pointer forward multiple times, as shown in Figure 2.20.
68 2 Pointers
Index 0 1 2 3 4
a[ ] 2 4 6 8 0
aPtr
Approach 1: point
1 the pointer to the
aPtr=&a[1] next element
Index 0 1 2 3 4
a[ ] 2 4 6 8 0
Approach 2:
aPtr aPtr+offset 2 move the pointer
using an offset
Figure 2.22 illustrates the memory layout under the assumption that aPtr points to
unit 2001 after adding 1 to it. We will verify this assumption below.
First of all, the assumption is not consistent with the definition of “pointer
types.” Type of a pointer is the type of the memory unit it points to. If aPtr + 1 points
to unit 2001, which belongs to the array element a[0], what is the size of the object
it points to then? Second, the value stored in unit 2001 contains half of the informa-
tion of element a[0].
Hence, it is not reasonable to make aPtr + 1 point to unit 2001. It is natural to
infer that we should move the pointer by the length of “pointer type” when adding
1 to it. In this case, it only makes sense if aPtr + 1 points to unit 2002.
Figure 2.23 presents the rule of pointer offsets in C language. Adding an integer
to or subtracting an integer from a pointer moves a pointer in memory space.
Pointer types determine the distance of such moves.
01 #include <stdio.h>
02 int main(void)
03 {
04 int a[5]= {2,4,6,8}; //Define and initialize an integer array
05 int *aPtr; //Define an integer pointer
06 aPtr =a; //aPtr points to the beginning address of a
07 printf("%d",*aPtr); //Output value of the memory unit a points to
08 aPtr++; //Make aPtr point to the next unit
09 return 0;
10 }
On line 6, aPtr points to a[0]. On line 8, we add 1 to aPtr and make it point to a[1].
Figure 2.24 shows the debugging information of this program.
Before adding 1 to aPtr, aPtr points to a[0] and aPtr + 1 points to a[1].
After adding 1 to aPtr, aPtr points to a[1] and aPtr + 1 points to a[2].
Conclusion
The distance a pointer moves past in one shift is the size of its type.
E.g.
When adding 6 to a float pointer,actual offset is
6*sizeof(float)=24bytes;
When subtracting 7 from a char pointer, actual offset is
7*sizeof(char)=7 bytes;
a[0]
a[2]
Pointers are used to reference array elements by address. We shall introduce how to
use them in one-dimensional and two-dimensional arrays.
Analysis
We can use the algorithm of the “computing sum” problem in section “one-dimensional arrays op-
erations”, but we need to reference array elements by address in this problem instead of by name.
Index 0 1 2 3 4 5
score[ ] 80 82 91 68 77 78
ptr
2. Algorithm description
Figure 2.26 shows the pseudo code. The top-level pseudo code and the first refinement remain
the same as before because data reference details are not involved at these two levels. In
the second refinement, we start with the initialization of variables and making ptr point to the
data array. Then we construct the loop of repeated addition by determining loop control vari-
able, loop execution condition, and offset of ptr.
01 Compute sum of array elements 01 //Compute sum of array elements int *ptr=score;
02 #include <stdio.h> 02 #include <stdio.h> is equivalent to
03 #define SIZE 10 03 #define SIZE 10 int *ptr;
04 04
ptr=score;
05 main(void) 05 int main(void)
06 { 06 {
07 score[ SIZE ] = {98,92,89,95,90,96,94,92,90,97}; 07 int score[ SIZE ] = {98,92,89,95,90,96,94,92,90,97};
08 i; // Counter 08 int i, *ptr=score; //ptr points to the array
09 total = 0; // Sum 09 int total = 0; // Sum
10 10
11 ( i = 0; i < SIZE; i++ ) 11 for ( i = 0; i < SIZE; i++ , ptr++)
12 { 12 {
13 total +=score[ i ]; // Compute sum of score elements 13 total +=*ptr; //Compute sum of score elements
14 } 14 }
15 printf( “The total score is %d\n", total ); 15 printf( "The total score is %d\n", total );
16 0; 16 return 0;
17 } Reference by name 17 } Reference by address
3. Code implementation
It is trivial to obtain actual code starting from pseudo code in the second refinement. Figure 2.27
lists programs of reference by name and by address together for readers to compare.
On line 8, pointer ptr is defined. Note that the statement int *ptr = score defines the pointer and
assign a value to it.
On line 11, ptr should be increased as well.
On line 13, the element pointed to by ptr is added to the sum.
Analysis
The test program is as follows:
1 int main(void)
2 {
3 char a[]="dinar##";
4 char *b="dollar##";
5
6 a[6]=':';
7 b[5]=':';
8 return 0;
9 }
If we run the program, an “Access Violation” warning will pop up. Debugging shows that the
error occurs when executing line 7. That is, we cannot write to the string pointed to by pointer
b. This is because a constant string is stored in the constant segment in memory, which cannot
be altered during execution; on the other hand, assigning a constant string to an array essen-
tially puts the string into the variable segment, which can be modified. Readers can refer to
chapter “Functions” for more details on memory layout.
11 }
12 aPtr=&a[1]; //Step 1
13 bPtr =&b[1]; //Step 2
14 for (i=0; i<5; i++)
15 {
16 *aPtr +=i; //Step 3
17 *bPtr *=i; //Step 4
18 printf("%d\t%d\n", *aPtr++,* bPtr ++);
19 } //*aPtr++ fetches value first, and then adds 1 to aPtr
20 return 0;
21 }
Analysis
Figure 2.28 shows values of arrays a and b after the for loop on line 6 terminates.
a 0 1 2 3 4 5
b 0 2 4 6 8 10
i 0 1 2 3 4
*aPtr in step ① 1 2 3 4 5
*aPtr in step ③ 1 3 5 7 9
*bPtr in step ② 2 4 6 8 10
*bPtr in step ④ 0 4 12 24 40
Analysis
Figure 2.30 illustrates the case where pointers pPtr and sPtr point to array a.
pPtr sPtr
Values in a 2-d array are
a b c \0 \0 d e f g \0 stored consecutively
a[0] a[1]
pPtr sPtr
a b c \0 \0 d e f g \0
a[0] a[1]
(2) sPtr points to “d” at first. In the loop “while (*sPtr) *pPtr++ = *sPtr++” on line 6, we repeatedly
assign value pointed to by sPtr to the unit pointed to by pPtr. In the first iteration, the value is
updated to “d,” then both pointers move to the next element, as shown in Figure 2.32.
pPtr sPtr
a b c \0 \0 d e f g \0
d
a[0] a[1]
(3) Now sPtr points to “e.” After another iteration, the value of the unit pointed by pPtr is up-
dated to “e,” as shown in Figure 2.33.
pPtr sPtr
a b c \0 \0 d e f g \0
d e
a[0] a[1]
(4) Now sPtr points to “f.” After another iteration, the value of the unit pointed by pPtr is up-
dated to “f,” as shown in Figure 2.34.
pPtr sPtr
a b c \0 \0 d e f g \0
d e f
a[0] a[1]
(5) Now sPtr points to “g.” After another iteration, the value of the unit pointed by pPtr is up-
dated to “g,” as shown in Figure 2.35.
pPtr sPtr
a b c \0 \0 d e f g \0
d e f g
a[0] a[1]
(6) Now sPtr points to “\0.” Because the loop condition is not met, the loop terminates, as
shown in Figure 2.36.
pPtr sPtr
a b c \0 \0 d e f g \0
d e f g
a[0] a[1]
(7) On line 7, %s format specifier prints character starting from the given address and stops
upon reaching “\0.” Hence, the string starting at address a[0] is abcdefgfg and the string
starting from address a[1] is fgfg. As a result, the final output is abcdefgfgfgfg.
Analysis
We can simply apply the same algorithm four times to complete the task.
1. Data analysis
Let us analyze the data to be processed first. Fetching address for each element in a row can be
done in the same way as in one-dimensional arrays. We will use a pointer ptr and update it to
the beginning address of the next row after processing one row.
To obtain the address of the next row, we can certainly reference the one-dimensional row by
score[1]. However, this is a reference by name instead of by address. Can we reference the row
by address then? In other words, can we use another pointer sPtr as a row pointer for the two-
dimensional array, as shown in Figure 2.38?
The answer is affirmative because we can treat score[0] to score[3] as array elements as well.
The next question is what should be the offset of sPtr?
According to the definition of pointer offset, it should be the number of elements in a row
multiplied by the size of elements type.
There is a special term in C for pointers pointing to row addresses of a two-dimensional array. As
shown in Figure 2.39, these pointers are called “pointer to arrays.” This is a confusing term, so
parentheses are added to pointer names to distinguish them from plain pointers and arrays.
2.3 Pointers and arrays 79
A pointer used
Pointer to arrays for 2-d arrays
It is worth noting that this is still a pointer, even though there is a constant wrapped by square
brackets after the pointer name. In fact, the constant indicates the number of elements the
pointer moves past in one shift.
For this problem, we can define a pointer to array as shown in Figure 2.40.
E.g.
int (*sPtr)[6];
Now we can obtain information of the two-dimensional array through reference by address, and
find out relation between ptr and sPtr, as shown in Figure 2.41.
Relation
between ptr ptr and score sPtr and score
and sPtr ptr=score[ ] sPtr=&score[ ]
*ptr=score[ ][ ] *sPtr=score[ ]
ptr = * sPtr
Obtain address of the first element in a row
The object in the unit pointed to by ptr is an element of array score. The object in the unit
pointed to by sPtr is address of a one-dimensional row of array score. Hence, we can write state-
ment ptr = *sPtr, where *sPtr represents the beginning address of a row.
2. Code implementation
01 #include <stdio.h>
02 #define N 4 //Number of rows
03 #define M 6 //Number of columns
04 int main(void) {
05
06 int score[N][M]=
07 {
08 {80,77,75,68,82,78},
09 {78,83,82,72,80,66},
10 {73,50,62,60,91,72},
11 {82,87,89,79,81,92}
12 };
13 int i,j;
14 int total; //Total grade
15 int *ptr; //Row pointer
16 int (*sPtr)[M]; //A pointer to array, offset is M int
17 sPtr=&score[0]; //Make sPtr point to the first row
18
19 for (i=0; i<N; i++, sPtr++)
20 {
21 total = 0;
22 ptr=*sPtr; //Make ptr point to the beginning address of the row
23 for ( j= 0; j< M; j++, ptr++)
24 {
25 total +=*ptr; //*ptr=score[ ][ ]
26 }
27 printf( "Total grade of student %d is %d\n", i+1,total );
28 }
29 return 0;
30 }
Program result:
Total grade of student 1 is 460
Total grade of student 2 is 461
Total grade of student 3 is 408
Total grade of student 4 is 510
Note: On line 16, we define a pointer to array sPtr with offset being M int, where M is 6.
On line 17, we make sPtr point to the first row of the array.
On line 22, ptr is set to point to the first element in a row.
In the for loop between line 23 and 26, we use pointer ptr to retrieve elements in array score
and add them to the sum.
2.3 Pointers and arrays 81
In each iteration, ptr is increased by 1 in the loop increment part. That is, it moves to the next
element of the row.
In loop increment part of the for loop on line 19, sPtr is increased by 1. That is, it moves to
the next row.
3. Debugging
We can inspect the memory layout in the Watch window and the Memory window. Figure 2.42
shows the beginning addresses of each row of array score.
score[0]
score[1]
score[2]
score[0] is 0x18fee8, which corresponds to the first gray block in the Memory window.
score[1] is 0x18ff00, which corresponds to the first white block.
Similarly, we have score[2] being 0x18ff18 and score[3] being 0x18ff30.
It is clear from the figure how ptr moves along each row. The value of ptr in the Watch win-
dow is the address of the 0th element in the score[0] block in the Memory window, which is
0x18fee8. The value of ptr + 1 is the address of the 1st element in score[0] block, which is
0x18feec.
sPtr moves along the first column of score. We have sPtr = score[0] and sPtr + 1 = score[1] at
first.
Values are displayed as hexadecimal numbers in the Memory window. The value of score[0][2]
is 4B, which corresponds to its decimal form 75 in the Watch window.
It can be derived from the Memory window that int type takes up 4 bytes in the system in
which this program is executed. The address of the last element in the 0th row is 0x18fefc. After
shifted by 4 bytes, it becomes 0x18ff00, which is precisely the address of the first element in
the first row. This shows that the addresses of these two elements are consecutive. Similarly,
one can inspect the addresses of the first and last elements of other rows and conclude that
rows of a two-dimensional array are stored consecutively.
82 2 Pointers
Once again, we notice that elements of multidimensional arrays are stored consecutively,
which is a general rule of array storage.
Analysis
c[0] "Zhao"
c[1] "Zhou"
Beginning addresses
of rows of a 2-d array, c[2] "Zhang"
referenced using the
c[3] "Zhan"
1-d form of the array
c[4] "Zheng"
Because there are five names, we need to define a character array c with five rows. The longest
name has five characters, so we need six columns to store it and the termination mark.
The address of each row of a two-dimensional array can be referenced in a one-dimensional
format. c[0] to c[4] can be treated as elements of a special array whose elements are pointers.
Based on discussion earlier, we may define a pointer array cPtr[]. It is a one-dimensional
array of pointers with five elements, each of which is the address of a string.
2. Algorithm description
The pseudo code is shown in Figure 2.44.
2.4 Pointers and multiple strings 83
3. Code implementation
01 #include <stdio.h>
02 #include <string.h>
03 #define M 5 //Number of strings
04 #define N 5 //Longest string length + 1
05
06 int main(void)
07 {
08 char *cPtr[M]= {"Zhao","Zhou","Zhang","Zhan","Zheng"};
09 char str[N];
10 int i;
11 //Use strcpy to copy cPtr[0] into str, beware of out-of-bound error
12 strcpy(str, cPtr[0]);
13 for (i=1; i<M; i++)
14 {
15 if (strcmp(str, cPtr[i])< 0) //If str is less than cPtr[i]
16 {
17 strcpy(str, cPtr[i]); //Then copy cPtr[i] into str
18 }
19 }
20 printf("The largest string is: %s\n", str);
21 return 0;
22 }
Program result:
The largest string is: Zhou
Note: On line 8, pointer array cPtr is defined and initialized with 5 strings.
On line 9, a one-dimensional character array is defined. Note that its length is the number of
characters in the longest string plus one.
The watch window shows the elements of the pointer array, as shown in Figure 2.45. Values
of these elements are the beginning addresses of strings.
84 2 Pointers
On line 12, we use strcpy function to copy cPtr[0] into array str.
On line 15, we compare strings stored in str and cPtr[i].
On line 17, we copy the larger one into str.
In the earlier example, if we wish to use a pointer to point to elements in array cPtr,
what does this pointer that points to pointer array look like?
Let the pointer pointing to pointer array cPtr be cPtrPtr, then its relation with
elements in array cPtr should be as shown in Figure 2.46. In this case, we need a
new kind of pointers.
To inspect a double pointer in debugger, we can use the three statements shown
in Figure 2.48.
As shown in the Watch window, elements of cPtr are beginning addresses of strings.
Addresses of these elements are presented in the Memory window. For example, ele-
ment cPtr[0] has value 0x420f94, which is the beginning address of the string “Zhao.”
The address of cPtr[0] is 0x18ff34, as shown in the Memory window. The double
cPtrPtr points to elements of cPtr. By adding 1 to it, we move it to the next element.
Readers can reimplement the largest string algorithm using double pointers.
86
2.5 Summary
The main contents of this chapter and the relations between them are shown
in Figure 2.49.
Objects with location attribute can be referenced either by name or by address,
Which are both key elements of variables,
Variable names are usually meaningful, so it is more intuitive and convenient
to use them,
Reference by address operates on memory directly, so it is more efficient.
2.6 Exercises
1. [Null pointer]
Which of the following is the output of this program?()
# include <stdio.h>
int main(void)
{
printf("%d\n",NULL);
return 0;
}
A) We do not know B) 0 C) –1 D) 1
2. [Concept of pointers]
Which of the following statements is correct about addresses and pointers?()
A) We can assign a pointer of one type to a pointer of another type through
forced-type conversion.
B) We can compute the address of a constant and assign it to a pointer of the
same type.
88 2 Pointers
3. [Pointer assignment]
Suppose x is an integer variable and pb is an integer pointer. Which of the fol-
lowing statements is correct?()
A) pb = &x B) pb = x C) *pb = &x D) *pb = *x
4. [Pointer exception]
Suppose we have the following definitions: int x=2, *p=&x; float y=3.0; char
z='c';.
Which of the following operations is unsafe?()
A) p++ B) x++ C) y++ D) z++
6. [Pointer offset]
What is value of y after executing the following program?()
int a[]={2,4,6,8,10};
int y=1,x,*p;
p=&a[1];
for(x=0;x<3;x++) y + = * (p + x);
printf("%d\n",y);
A) 17 B) 18 C) 19 D) 20
7. [Operations on string]
Which of the following statements is a correct string assignment statement?()
A) char s[5] = {"ABCDE"} B) char s[5] = {'A', 'B', 'C', 'D', 'E'}
C) char *s; s = "ABCDEF" D) char *s; scanf("%s",s)
int main(void)
{
int a, b;
int *p1, *p2;
p1 = &a;
p2 = &b;
a = 50;
b = 20;
a = *p1 - *p2;
return 0;
}
2. [Pointer operations]
Fill in the table in Figure 2.51 based on the following program:
Expression Result
p1==&m
*p1
*p2
(*p1)/(*p2)
a
b
int main(void)
{
int i, s=0, t[]={1,2,3,4,5,6,7,8,9};
int *p=t;
for(i=0;i<9;i+=2)
{
s+=*(p+i);
}
printf("%d\n",s);
return 0;
}
i 0 2 4 6 8 10
p &t[0] End of loop
s 1
int main(void)
{
char str[81],*sptr;
gets(str);
sptr=str;
while(*sptr)
{
2.6 Exercises 91
putchar(*sptr+1);
sptr++;
}
return 0;
}
*sptr 'a'
putchar(*sptr+1) 'b'
Functionality:
1. Suppose we have an integer array with 10 elements. Show the output of its ele-
ments using the following methods: through array index, through array name,
and through a pointer.
2. Write a program that reads n number from keyboard input and outputs them in
the reversed order of input. Your implementation should use pointers.
3. Please write a program, in which you define a one-dimensional integer array
num[20], read an integer n (n ≤ 20), and an integer sequence (of n numbers)
from keyboard input, find the maximum and the minimum in the sequence and
swap them.
4. Write a function that stores input characters backwards. Input of characters and
output of the reversed characters should be done in the main function.
5. Write a program that connects two strings without using strcat function.
6. Please write a program that handles character input in the following way: if the
input is a lowercase letter, the program should output its uppercase counter-
part; if the input is uppercase, the program should output its lowercase counter-
part; other characters should be output as such.
7. A palindromic number is a nonnegative integer that remains the same when its
digits are reversed, for example, 12321. Please write a program that determines
whether the input integer is palindromic. If so, the program should output the
sum of its digits; otherwise the program should output "no."
8. Please write a program to encrypt string "China" using Caesar code with a right
shift of 4. For example, the fourth letter after "A" is "E," so ciphertext of "A" is
"E." As a result, ciphertext of "China" should be "Glmre."
3 Composite data
Main contents
– Introduction of construction of structures through comparison of structures and arrays
– Analysis of the nature of structure types through comparison of structure types and basic
types
– Summarization of usage of structures through comparison of structure members and plain
variables
– Program reading practices
– Practice of top-down stepwise refinement algorithm design
– Storage characteristics and debugging techniques of structures
Learning objectives
– Understand the significance of custom data types
– Know steps and methods of type definition, variable definition, initialization, and reference
of structures
– Know the concept of unions and how to use them
– Know the concept of enumerations and how to use them
3.1.1 Introduction
There were four students in a study group instructed by Mr. Brown. Their informa-
tion was recorded in a student management table, as shown in Figure 3.1. One day,
Mr. Brown asked his students, “We have learned how to compute total grade in a
two-dimensional table, can you write a program that computes total grades and
prints the entire management table?”
How to
implement
in code?
https://doi.org/10.1515/9783110692303-003
94 3 Composite data
Compared with a two-dimensional array, data in this table are not of the same type.
To process this table, we must first figure out how to access the data on a computer
before designing an algorithm. To be more specific, we need a method to store the
table in the computer and to retrieve grades from the table. This is also the general
approach to solve problems from a computer’s perspective.
What are
possible storage
solutions?
There are multiple types of data in the table, so the size of the table type should
be the sum of the sizes of each entry.
Because such a combinatorial type is data-dependent, its size varies for different
tables. As a result, it is not possible to use a single type for all of them. Otherwise, the
system cannot allocate a suitable amount of memory for each table. Hence, it is the
programmers’ task to define types. It is thus necessary to design syntax for type defi-
nitions. In C language, such definitions are done in the format “keyword + identifier,”
in which programmers name the identifier.
Figure 3.4 shows the definition of a structure (struct) and its data entries. Structures
are one of the aggregate data types in C.
Structure
Aggregate
A structure (struct) is a collection of multiple data entries. data type
Each entry in a structure is called a structure member.
Members can have different types.
them. A structure must be defined before being used. Members of a structure can be
of any valid types in C.
Structure name
Think and discuss Will the system allocate memory for structure members after defining the
structure?
Discussion: Note that a structure is a user-defined data type. In C, types describe the size of
memory allocated, but a type definition will not trigger memory allocation. Memory will not be
allocated until a variable of this type is defined.
Analysis
Figure 3.7 presents two solutions to structure definition for the table.
Solution 1: the structure name is student, which becomes the type name together with keyword
struct. The members are defined one by one, each with an appropriate type.
Solution 2: we can combine data of the same type into an array to make the definition simpler.
98 3 Composite data
Solution 1
struct student
{
int id; Solution 2
char name[10]; struct student
char gender; {
int time; int id;
int score_1; char name[10];
int score_2; char gender;
int score_3; int time;
int score_4; int score[4]; //Combine 4 grades in an array
int total; int total;
}; }
Figure 3.7: Solutions of structure definition for the student management table.
With the definition of a structure type, we can define structure variables. As shown
in Figure 3.8, the definition is similar to plain variable definitions, except that the
type is a structure type.
structureType variableName;
Analysis
Figure 3.9 shows required definitions, where struct student is the structure type, x is the name
of the structure variable, com[30] is the structure array, and sPtr is the pointer pointing to a
structure.
3.2 Storage of structures 99
Description Form
Structure type struct student
Structure variable definition struct student x;
Structure array definition struct student com[30];
Structure pointer definition struct student *sPtr;
Similar to arrays, a structure variable can also be initialized, as shown in Figure 3.10.
Uninitialized
//Structure array initialization
struct student com [30] elements are set
= { { 1001, "ZhaoYi", 'M', 2009, 90, 83, 72, 82 }, to 0 by the
{ 1002, "QianEr", 'M', 2009, 78, 92, 88, 78 }, system
{ 1003, "SunSan", 'F', 2009, 89, 72, 98, 66 },
{ 1004, "LiSi", 'F', 2009, 78, 95, 87, 90 }
};
Admission
x ID Name Gender Grade 1 Grade 2 Grade 3 Grade 4 Total
year
sPtr=com;
sPtr Admission
ID Name Gender Grade 1 Grade 2 Grade 3 Grade 4 Total
Year
com[0]
com[1]
… … ...
com[9]
sPtr+9
com[0]
//Structure pointer
points to structure array
sPtr=com;
Figure 3.13: Inspection of the memory layout of variables related to the student management table.
expansion that each element consists of data entries defined in the structure-type
definition.
The Watch window for variable x shows that assigning value to a structure vari-
able copies all data entries.
In the window for sPtr, we can inspect contents pointed to by it. sPtr points to
the beginning address of com, and we can verify that id is indeed 1001. sPtr + 1 has
id 1002, so it points to com[1]. This is consistent with the definition of pointer offset.
102 3 Composite data
Suppose:
sizeof(short)=2 sizeof(long)=4
Analysis
Figure 3.15 shows the addresses of A’s structure members. They all have length of 2 bytes and
are stored consecutively.
Figure 3.16 shows the addresses of B’s structure members. B.a1 is stored at 0x19ff20 and has
4 bytes. B.a2 is store right after it at address 0x19ff24. sizeof(B) yields 8, so 4 bytes are allocated
to B.a2. However, short type only takes up 2 bytes. Thus, the remaining 2 bytes are not in use.
3.2 Storage of structures 103
Figure 3.17 shows the addresses of C’s structure members. C.a1 is stored at 0x19ff18. It is of
type short, so its length is supposed to be 2 bytes. Nonetheless, C.a2, a long variable, is stored
at 0x19ff1c, which is 4 bytes after C.a1. sizeof(C) yields 8 and we know the length of a long vari-
able is 4 bytes, so 4 bytes are allocated to C.a1. Once again, 2 bytes are not in use.
Think and discuss Why are there “holes” in memory that are not used?
Discussion: We can infer from the memory layout shown earlier that these memory units are allo-
cated in such a way that the addresses of structure members are aligned. Data alignment allows
the CPU to access memory more efficiently. It is an optimization done by compilers during mem-
ory allocation of variables. The optimization (alignment) rule for basic types is as follows:
Variable address %N = 0 (Alignment parameter N = sizeof(variable type))
Note: this rule may vary in different compilers.
Memory is allocated in multiples of M bytes: if a member is longer than M bytes, then M more bytes
are allocated; if a member is shorter than M bytes, then the next member is padded following the
same set of rules (which also apply to nested structures).
struct stu
{ int StudentId;
char StudentName[10];
char StudentGender[7];
int TimeOfEnter;
int Score[4];
}
Beginning
Member 4 bytes
address
int StudentId 19FF04 01 00 00 00
char StudentName[10] 19FF08 5A 68 61 6F
59 69 00 00
char StudentGender[7] 19FF12 00 00 4D 61
Memory “hole”
6C 65 00 00
3 bytes
00 CC CC CC
int TimeOfEnter 19FF1C 03 00 00 00
int Score [4] 19FF20 04 00 00 00
05 00 00 00
06 00 00 00
07 00 00 00
We can conclude that a good structure member design makes the structure simpler and saves
memory space. Carefully designed structures can make our programs more efficient.
We obtain memory for structures by defining structure variables and assigning val-
ues to them through initialization. These are all write operations of data. Because
we need to read them as well, a referencing method is necessary for members of a
structure variable.
There are three ways to reference a structure member in C, as shown in Figure 3.21.
The first one references a member by its name. Its syntax is structure variable name
and member name connected by a dot. The rest references a member by its address.
They require a pointer pointing to the structure. This pointer is then used together
with member names to complete the task. Essentially, these two methods work in the
same way.
106 3 Composite data
Method 1 Reference
structureVariableName.memberName by name
Method 2
structurePointerName->memberName Reference
by address
Reference
Object Value to be referenced Statement prefix
struct student Total grade x.total
Structure
{ x
variable x th
The 0 grade x.score[0]
int id;
char name[10]; Total grade of the 1st student com[1].total
Structure
char gender; array com[i]
int time; com[30]
th
The 0 grade of the 2 nd
student com[2].score[0]
int score[4];
int total; Total grade sPtr->total
} sPtr->
Structure The 3rd grade sPtr->score[3]
struct student
pointer
x, com[30],*sPtr; Total grade (*sPtr).total
sPtr
(*sPtr)
The 2nd grade (*sPtr).score[0]
To reference a member of the structure variable x, we use the statement “x.member name.”
To reference a member of structure array com, we use the statement “com[index].member name.”
To reference a member of the structure pointer sPtr, we use either “sPtr-> member name” or
“(*sPtr).member name.”
Seat No. 1 2 3 4 5 6
Grade 90 80 65 95 75 97
Analysis
1. Data structure design
We may use one of the following three solutions:
(1) Using a one-dimensional array
Score array: int score [6] = {90,80,65,95,75,97};
Seat number array: int set[6] = {1,2,3,4,5,6};
(2) Using a two-dimensional array
Combination of score and seat number: int score[2][6] = {{90,80,65,95,75,97},{1,2,3,4,5,6}};
We have learnt how to store data with arrays: data of the same type are stored in order;
each element is accessed using array name and index.
(3) Using a structure
Solution 1:
struct node {
int score[6];
int seat[6];}
struct node x={{90,80,65,95,75,97},{1,2,3,4,5,6}}
Solution 2:
struct node {
int score;
int seat;}
struct node y[6]={{90,1},{80,2},{65,3},{95,4},{75,5},{97,6}};
Structures “pack” correlated data together. Type of a structure is defined by users. Memory is
allocated during definition of variables of the structure type.
After storing data into memory using structures, we need to reference them for further com-
putation. Figure 3.24 shows how data are stored and accessed using a one-dimensional array,
two-dimensional array, and structure.
Figure 3.25 shows how to reference members of structure variable x and structure array y
and their corresponding values.
Address Type Variable Storage order Characteristics
108
reference
Array name score[index]
int Elements are stored consecutively;
1-d score It is convenient to
two arrays are not necessarily stored
array Array name handle large amount
int seat[index ] consecutively
seat of data of the same
2-d type using arrays
Array name score[index][ Elements are stored consecutively in
array int
score index] a row-first manner
Members of the structure are stored
Structures combine
3 Composite data
2. Algorithm design
Figure 3.26 shows pseudo code of the algorithm.
Pseudo code
Find the current maximum in score, record the
corresponding seat number as num
Swap max with the 0th element of score
Swap num with the 0th element of seat
Output contents of array score and array seat
Figure 3.26: Algorithm of finding the highest score and swapping it with the first column.
3. Code implementation 1
Program result:
No. 1: seat no. 6, 97 pts
4. Code implementation 2
Program result:
No. 1: seat no. 6, 97 pts
5. Code implementation 3
32 x.seat[num-1]= temp2;
33
34 //Output
35 printf("No. 1: seat no. %d, %d pts \n", x.seat[0],x.score[0]);
36 return 0;
37 }
Program result:
No. 1: seat no. 6, 97 pts
6. Code implementation 4
Program result:
No. 1: seat no. 6, 97 pts
Analysis
1. Data structure design
We have studied how to store the table in Section 3.2. To be more specific, we shall use the
following structure definition for the student management table:
struct student
{
int id;
char name[10];
char gender;
int time;
int score[4];
int total;
};
To compute the sum, we need to retrieve the score data by referencing structure members.
Row i Column j
com[i].score[j]
Use address of a
grade for
referencing score ptr ptr=sPtr->score
2. Code implementation
Figure 3.29 shows the reference by name program.
01 #include <stdio.h>
02 #define N 4//Number of students
03 #define M 4//Number of courses
04 struct student
05 {
06 int id;
07 char name[10]; Structure type definition
08 char gender;
09 int time;
10 int score[M];
11 int total;
12 };
13 int main(void)
Structure array definition
14 {
15 struct student com [N] and initialization
16 = {{ 1001, "ZhaoYi", 'M', 2009, 90, 83, 72, 82 },
17 { 1002, "QianEr", 'M', 2009, 78, 92, 88, 78 },
18 { 1003, "SunSan", 'F', 2009, 89, 72, 98, 66 },
19 { 1004, "LiSi", 'F', 2009, 78, 95, 87, 90 }
20 }; //Structure array initialization
21
22 int i, j;
23 printf( “ID Name Gender Admission Year CompArch C Compil OS Total\n"); //Table header
24 for(i=0; i<N; i++)
25 {
26 com[i].total = 0; Compute total grade of a row
27 for ( j= 0; j< M; j++)
28 {
29 com[i].total +=com[i].score[j];
30 }
31 printf( "%d %s %s %d",com[i].id,com[i].name,&com[i].gender,com[i].time);
32 printf( " %d %d",com[i].score[0],com[i].score[1]);
33 printf( " %d %d %d\n",com[i].score[2],com[i].score[3],com[i].total);
34 }
35 return 0;
36 }
Note:
Lines 4–12 define the structure type.
Lines 15–20 define and initialize the structure array.
Line 23 prints the header of the table.
Lines 27–30 compute the total score for one student.
The for loop on line 24 repeats the sum computation N times.
Lines 31–33 print other data in the row.
Program result:
ID Name Gender AdmissionYear CompArch C Compil OS Total
1001 ZhaoYi M 2009 90 83 72 82 327
1002 QianEr M 2009 78 92 88 78 336
1003 SunSan F 2009 89 72 98 66 325
1004 LiSi F 2009 78 95 87 90 350
The implementation shown in Figure 3.30 uses the same algorithm but references grades by
address. Please refer to the program earlier for the first 20 lines.
21
22 int i, j;
23 printf( “ ID Name Gender AdmissionYear CompArch C Compil OS Total\n ”); //Table header
24 for (i=0; i<N; i++)
25 {
26 com[i].total = 0;
27 for ( j= 0; j< M; j++)
28 { Compute total grade of a row
29 com[i].total +=com[i].score[j];
30 }
31 printf( "%d %s %s %d",com[i].id,com[i].name,&com[i].gender,com[i].time);
32 printf( " %d %d",com[i].score[0],com[i].score[1]);
33 printf( " %d %d %d\n",com[i].score[2],com[i].score[3],com[i].total);
34 }
35 return 0;
36 }
Analysis
1. Data structure design
There are two types of data in vote statistics, so it is better to use a structure to store them. The
structure should have two members: candidate name and number of votes. There are three can-
didates, so we can use a structure array to store their information.
(1) Structure design
Information of each candidate can be stored in the following structure:
struct person
{ char name[16]; //Candidate name
int sum; //Number of votes
}
Pseudo code
while counter<number of total votes N
Input candidate name in_name
Output result
{
scanf("%s",in_name); //Input candidate name
for(j=0;j<3;j++) //Add one to corresponding sum
if (strcmp(in_name, vote[j].name)==0)
{
vote[j].sum++;
}
}
for (i=0;i<3;i++) //Output result
{
printf("%s,%d\n",vote[i].name,vote[i].sum);
}
return 0;
}
3.4 Union
3.4.1 Introduction
There is a lab in the university Mr. Brown works for. The lab is available to members
of all related research groups. One should book the lab before using it. However,
118 3 Composite data
researchers from different groups cannot use the lab together. We can list research
groups and researchers that are entitled to use the lab as follows:
Public lab
{
Research group 1: Person 1;
Research group 2: Person 2;
...
Research group n: Person n;
}
To save memory space, we also use such a memory sharing strategy in computers.
We can store variables that cannot be accessed simultaneously into one memory
unit. Such a data structure is called a “union.” When we have multiple variables
and use exactly one of them each time, we can use a union to store them into the
same memory unit.
Similar to structures, type definition, variable definition, and member access are
also key issues for unions.
Reference
Method 1 by name
unionVariableName.memberName
We need a
Reference pointer pointing
by address to the union first
Method 2
unionPointerName->memberName
int x
Memory space
shared by multiple char ch
union members float y
Union Structure
Memory space is shared by all
Memory size Memory space size is sum of
members, its size is determined by
sizes of members
the member with the largest size
Member All members are stored
Only one member is valid at a given
relation consecutively in the order in
time, which is the last stored member
which they are defined
Relation Union types can appear in structure type definitions
1. Test program
The following test program defines a union and assigns values to its members in the order in
which they are defined. After running the program, we can inspect the memory layout of the
union using a debugger:
#include <stdio.h>
int main(void)
{
union number //Define a union type
{
int x;
char ch;
float y;
};
union number unit; //Define a union variable
unit.x=1; //Reference union members
unit.ch='a';
unit.y=2;
return 0;
}
2. Debugging
Figure 3.38 shows that the three members are all stored at 0x12ff7c. In particular, it shows
memory layout after value 1 is assigned to x, whose value is shown in the Memory window as
well.
3.4 Union 121
Figure 3.39 shows the memory layout after the character ‘a’ is assigned to ch. ch’s value in the
Memory window is 0x61, which is exactly the ASCII value of ‘a’. This indicates that the valid
value of the union has changed to 0x61.
In Figure 3.40, we have assigned real number 2 to y. However, the value displayed in the
Memory window is 0x40000000. Why is this the case?
122 3 Composite data
2 Qian P 3
3 Sun P 5
4 Li L English
5 Zhou P 4
1 //Operations on union
2 #include <stdio.h>
3 #define N 5 //Number of teachers
4
5 union work
6 { char course[10]; //Course name
7 int num; //
8 };
9
10 struct teachers
11 { int number; //ID
12 char name[8]; //Name
13 char position; //Title
14 union work x; //Number of courses or papers
15 } teach[N];
16
17 int main(void)
18 {
19 struct teachers teach[N]
20 ={ {1, "Zhao",'L',"program"},
21 {2, "Qian",'P',3},
22 {3, "Sun",'P',5},
23 {4, "Li",'L',"English"},
24 {5, "Zhou",'P',4},
25 };
26 int sum=0;
27
28 for(int i=0; i<N; i++)
29 {
30 printf ( " %3d %5s %c ",teach[i].number,
31 teach[i].name, teach[i].position);
32 if (teach[i].position =='L')
33 {
34 printf ("%s\n", teach[i].x.course);
35 }
36 else if ( teach[i].position =='P' )
37 {
38 printf ("%d\n", teach[i].x.num);
39 sum=sum+teach[i].x.num;
40 }
41 }
42 printf ("paper total is %d\n", sum);
43 return 0;
44 }
Program result:
1 Zhao L program
2 Qian P 3
124 3 Composite data
3 Sun P 5
4 Li L English
5 Zhou P 4
paper total is 12
3.5 Enumeration
3.5.1 Introduction
When Daniel started to learn watercolor painting, he was shocked by how different
colors could be mixed into a new color. He kept asking Mr. Brown questions like
“What is the result of mixing red and blue?” or “What if I mix yellow and red?”
which made Mr. Brown exhausted. As a result, Mr. Brown decided to write a pro-
gram that could answer these questions, for given input from his son.
Figure 3.43 shows results of mixing two of three primary colors.
number 0 1 2 3 4 5
color Red Yellow Blue Orange Purple Green
string red yellow blue orange purple green
Before he could write the code, Mr. Brown needed to design a data structure. He
used a pointer array to store color names in Figure 3.43: char *ColorName[] =
{“red”,“yellow”,“blue”,“orange”,“purple”,“green”};
The two-dimensional array in Figure 3.43 should be initialized with indices of
colors in array ColorName, instead of actual name strings, because indices require
less memory space and are easier to process.
int ColorTab[3][3]={{0,3,4},{3,1,5},{4,5,2}};
During the initialization process, Mr. Brown found that it was difficult to remember
the number corresponding to a color. If there were more colors, it would be even
harder to remember them and initialize the array correctly. The reason is that
names of colors are more intuitive compared with abstract numbers. To solve this
3.5 Enumeration 125
issue, Mr. Brown tried to define macros for each color so that he could directly use
these intuitive names in the program.
01 #include "string.h"
02 #include "stdio.h"
03 #define red 0
04 #define yellow 1
05 #define blue 2
06 #define orange 3
07 #define purple 4
08 #define green 5
09
10 //Define the color mixer
11 int ColorTab[3][3]={{red,orange,purple},{orange,yellow,green},{purple, green,
blue}};
12
13 int main(void)
14{
15 char color1[8]; //Read input 1
16 char color2[8]; //Read input 2
17 char *ColorName[]= {"red","yellow","blue","orange","purple","green"};
18 int i=0,j=0;
19
20 printf("Please enter any two colors of red, yellow and blue:\n");
21 gets(color1);
22 gets(color2);
23 while (0!=strcmp(color1,ColorName[i])) i++;
//Find index i of the first input
24 while (0!=strcmp(color2,ColorName[j])) j++;
//Find index j of the second input
25 //Find mixing result using i and j in the color mixer
26 printf("%s+%s=%s\n",ColorName[i],ColorName[j],ColorName[ColorTab[i][j]]);
27
28 return 0;
29 }
However, Mr. Brown needed to define 6 macros for mixing results of three primary
colors. If there were more base colors, it would be tedious to define a macro for
each possible outcome.
We often use numbers to represent states in programs, but numbers are less
intuitive and readable than state names, as we have just seen in the color example.
126 3 Composite data
In fact, C and some other languages do provide a method of using words in natural
languages to represent possible values of a variable. This method is enumeration
(enum).
In C, an enumeration is a collection of integer constants represented by identi-
fiers. The value of an enumeration variable must be a member of this collection. It
is worth noting that the system will not throw an error if an enumeration variable is
assigned a value that is out of the enumeration range.
The syntax of enumerations is similar to that of structures and unions, as
shown in Figure 3.44. We can define an enumeration for days in a week as follows:
enum name
{
identifier 1[=integer constant], Contents in
identifier 2[=integer constant], square brackets
… are optional
identifier n[=integer constant]
};
Define type
Syntax of enumeration variable definition before defining
variables
enumType variableList;
Notes:
(1) Identifiers in an enumeration-type definition are constants.
(2) One needs to list all members when defining an enumeration.
(3) Contents in square brackets are optional. If we omit them, numbers 0, 1, 2, . . .
will be assigned to the identifiers. However, if one of the members is explicitly
assigned a value, members after it will automatically obtain a value, in which
each member is one larger than the previous.
We can explicitly assign values to all enumeration members. Note that the values
must be integers. For example: enum WeeksType {Mon = 1, Tues = 2, Wed = 3,
Thurs = 4, Fri = 5, Sat = 6, Sun = 7};
Besides, we can also explicitly assign values to a few members: enum WeeksType
{Mon = 1, Tues, Wed = 1, Thurs, Fri, Sat, Sun}; In this definition, Mon and Wed are de-
fined to be 1. Based on the note earlier, values of Tues, Thurs, Fri, Sat and Sun are 2, 2,
3, 4 and 5, respectively.
(4) Value of an enumeration variable must be one of the enumeration members.
For example, it is valid to write statement Day = Wed.
Analysis
We can list all periods in an enumeration:
enum enumType{Time1, Time2, Time3} rebateTime ;
There are many restrictions on enumerations for their uniqueness. We shall use the
following enumeration in the discussion:
Weekday = Sat;
Weekday++; //Invalid
Note: this is because increment may break the first rule. In this example, Weekday
is assigned the last value of enumeration members, so increment will make this
value invalid.
3.6.1 Introduction
In this case, if we rename 32-bit and 16-bit int types as UIN32 and UIN16, we can
define the WAV file header as the following structure. When porting the code, it suffi-
ces to replace UIN32 and UIN16 with the corresponding types of the target platform:
struct tagWaveFormat
{
char cRiffFlag[4];
UIN32 nFileLen;
char cWaveFlag[4];
char cFmtFlag[4];
char cTransition[4];
UIN16 nFormatTag ;
UIN16 nChannels;
UIN16 nSamplesPerSec;
UIN32 nAvgBytesperSec;
UIN16 nBlockAlign;
UIN16 nBitNumPerSample;
char cDataFlag[4];
UIN16 nAudioLength;
};
Syntactically, line 2 is similar to a variable definition. One may imagine that this
issue can be solved if PTR is a data type equivalent to int*. As a result, we need a
way to define aliases for data types in C.
The purpose of using typedef is to fix issues made by macros and to make code
more readable. In practice, typedef is often found in network code and drivers
where type sizes are critical. To conform to different compilers, we better define
and use our own types. Thus, it suffices to update a few header files when porting
our code to new platforms. Typedef can hide complicated structures or platform-
dependent data types so that programs are easier to port and maintain.
The following section will present the syntax and applications of typedef.
Figure 3.46 shows how to define a new type using typedef. In essence, typedef cre-
ates aliases for existing data types.
In addition to creating aliases that are intuitive and easy to remember, another use
case of typedef is to simplify complex type declarations. Figure 3.47 shows two ex-
amples of typedef.
Example 1 Example 2
Declare a new type typedef int integer; typedef struct student Stu;
Statement integer x,y; p=( struct student *)malloc(sizeof(struct student));
Equivalent statement int x,y; p=(Stu *)malloc(sizeof(Stu));
In example 1, we create an alias integer for int; integer and int are equivalent types.
In example 2, we have a structure type struct student; create an alias Stu for it,
so we can replace all occurrences of struct student with Stu, thus making the code
easier to read.
The difference between #define and typedef is as follows: #define is a simple
text replacement that happened in the preprocessing phase, while typedef enables
flexible type replacement during compilation.
3.7 Summary
This chapter discusses how to describe, store, and reference a group of data that
are logically correlated. Figure 3.48 shows concepts related to structures, while
those of unions and enumerations are shown in Figures 3.49 and 3.50, respectively.
Use the same memory units for logically correlated data that
Concept cannot be used simultaneously
Keyword: union
Reference
Reference a single data entry: member reference
methods
Variables of different types that share the same memory space construct a union,
Whose size is determined by members programmers put in it,
A variable must be a member of the union to use the shared space,
The actual space is allocated upon definition of a union variable,
134 3 Composite data
3.8 Exercises
struct student
{ char name[20];
char sex;
int age;
} stu[3]={“Li Lin”, ‘ M’, 18, “Zhang Fun”, ‘ M’, 19, “Wang Min”, ‘F’, 20};
struct student *p;
p=stu;
p+=2;
printf(“%s, %c, %d\n”, p->name, p->sex, p->age);
2. [Chain structure]
struct sT
{ int x; struct sT *y; } *p;
struct sT a[4]={20,a+1,15,a+2,30,a+3,17,a };
int main(void)
3.8 Exercises 135
{ int i;
p=a;
for(i=1; i<=2; i++) { printf("%d,", p->x ); p=p->y; }
return 0;
}
struct abc
{ int a, b, c; };
struct abc sum[2]={{1,2,3},{4,5,6}};
int t;
t=sum[0].a + sum[1].b;
printf("%d \n", t);
A) 5 B) 6 C) 7 D) 8
typedef struct
{ char name[10];
int age;
} ST;
ST stud[10]={ "Adum", 15, "Muty", 16, "Paul", 17, "Johu", 14, };
5. [typedef]
Which of the following statements is wrong?()
A) We can use typedef to create new types.
B) We can use typedef to create a new name for an existing type.
C) After defining a new type name with typedef, the original type name is still
valid.
D) We can use typedef to define aliases for existing types, but we cannot de-
fine aliases for variables.
136 3 Composite data
6. [Unions]
Character “0” has decimal ASCII value 48. Suppose the 0th element of an array
is stored at lower bytes and sizeof(int) is 4 bytes. What is the output of the fol-
lowing program? ()
union
{ int i[2];
long k;
char c[4];
} var, *s=&var;
s->i[0]=0x39;
s->i[1]=0x38;
printf(“%c\n”, s->c[0]);
A) 39 B) 9 C) 38 D) 8
Suppose we have the following structure definition. Figure out values of structure
members shown in Figure 3.51 after executing the following program:
#include <stdio.h>
#define N 5
#define M 4
struct person
{
int Id;
char Name[10];
int Score[M];//Grade
int total;//Total grade
};
int main(void)
{
struct person allone[N]
={{ 1,"mark", { 9,6,8,7 },0 },
{ 2,"bob", { 8,6,8,5 },0 },
{ 3,"alice", { 5,9,7,8 },0 },
{ 4,"william",{ 8,9,9,9 },0 },
{ 5,"eric", { 8,9,6,9 },0 } };
struct person temp;
int i, j;
for ( i = 0; i < N; i++) //————①
{
3.8 Exercises 137
allone[i].total = 0;
for ( j = 0; j < M; j++)
{
allone[i].total += allone[i].Score[j];
}
}
for (i =1; i < N; i++) //————②
{
for (j = 0; j < N-i; j++)
{
if (allone[j].total < allone[j+1].total)
{
temp = allone[j];
allone[j] = allone[j+1];
allone[j + 1] = temp;
}
}
}
return 0;
}
i 0 1 2 3 4
struct person
{
char lastName[15];
char firstName[15];
char age[4];
}
Please write code that reads 10 person objects (lastName, firstName, and age) from
keyboard input.
138 3 Composite data
Main contents
– Analyze why functions are necessary
– Explore relations between multiple functions
– Declaration, definition, and call of functions
– Purpose and rules of function parameters
– Key elements in function design and examples
– Program reading practices
– Top-down stepwise refinement algorithm design practices
– Debugging techniques of information transfer between functions
Learning objectives
– Understand the concept of modularization in large-scale programs
– Understand information transferring mechanism of functions
– Understand information masking mechanism of functions
– Know how to design new functions
– Understand the concept of recursion
4.1.1 Introduction
Case study 1
Combination and permutation We can reuse
the factorial
n n! n n! program
Pm = Cm =
(n−m)! (n−m)! m!
https://doi.org/10.1515/9783110692303-004
140 4 Functions
Case study2
Scholarship application process
6. If an applicant is caught cheating in an exam, fill in the vacancy based on grade ranking
7. Class advisor signs the form and submits to the department of student affairs
Figure 4.4: Work that requires cooperation in the scholarship application process.
The workload is heavy for one person but small for a team. Multiple skills are neces-
sary for a single individual to complete all the work, while one skill may suffice for
a person in a team to complete the task assigned to him/her. There is no need to
communicate if a single person does the work, but communication is of great signif-
icance in teamwork because the output of one step is often the input of the next.
Discussion: There is no difference in workload or complexity of these two ways. However, using
several child programs require information transfer, which is precisely the key we are looking for.
Hence, programming languages must provide such mechanisms.
Modules
A module is a collection of statements The internal
that has its own name and can complete implementation of a
specific tasks independently module is hidden from
the outside; a module
Interface information communicates with
outside world through its
information interface
Functionality
Interface It indicates how this module should be used by other modules or programs. It
information includes information like input/output.
Module reuse We can extract functions that can be repeatedly called into modules.
Multimodule
We divide a program into modules, each of which completes a different task.
structure
There are other concepts related to modules, such as module reusing and multi-
module structures.
The word “module” has many aliases, such as function or child program. C uses
“function” to describe modules, as shown in Figure 4.7. We use the word “module”
in structured analysis and design; and it becomes “class” in object-oriented analysis
and design; the term used in component-based development is “component.”
4.2 Function form design 143
Child
Module Function
program
Case study 1
Department of
Place tables and chairs
general services
Both methods are feasible in programming design patterns. If modules are executed in order,
then the program is procedure oriented; if a module would not be executed until certain events
happen, then the program is object oriented. Readers can refer to Appendix B for more details
on this topic. Functions in C use the first method mentioned earlier, where “the outsourcer coor-
dinates between service providers.”
The outsourcer
coordinates between Make and install the Compute the sum Service providers
service providers inkjet background of a data table communicate
with each other
Place tables and Sort and classify
chairs a data table
Install and test Search, delete and
audio devices insert in a data table
Advertising Definition of
company production
Description of production process
Image layout, materials, specifications,
rendering preview, service price
Background
production
Definition of
Manufacturer manufacture
Definition of manufacture
(input, output, processing)
Manufacture
User Drive
manufacture
Function
Function call
(actual data)
Drive the
Caller implementation
which includes input, output, and processing. Users of functions are referred to as
“callers” in programming languages. Callers provide functions with actual data so
that they can complete specific tasks.
Manufacturer
Outsourcer
Manufacturer
Outsourcer
Design
/*
Functionality: describes that the function does Where are the
Parameters: explain meaning of input data “result submission
Return value: describe processing result and its type method” and the
Result type */ “result receiving
4 Functions
Information
submission interface
Figure 4.16: Analysis of data in critical steps of the scholarship application process.
150 4 Functions
Think and discuss In the process of transmitting data to function through its interface, what are
characteristics of the data and how are they transmitted?
Discussion: As shown in Figure 4.17, issues related to data transmission are data type and data
size. Since the nature of types is the size of the memory space used, type issues are necessarily
size issues. We shall discuss the later below.
Data
transmitted Passed
Small
directly
Data size
Passed
Large
indirectly
In real life, we can send items to others directly or indirectly. For example, a mail carrier can
send parcels to recipients directly or put them in a self-service parcel pick-up machine.
We can use these methods in information transmission between functions as well. In pro-
grams, a small amount of data are often passed to functions directly. In contrast, a large amount
of data are often passed to functions indirectly by providing the beginning address of the data so
that functions can fetch them on their own.
Information transmission
Argument Parameter
The memory space allocated to actual values that a function caller needs to pro-
cess is called “argument space.” The system copies the actual data and sends it to
the function called, as shown in Figure 4.19. In other words, the function receives a
copy of actual values. We can imagine the process as sending copies of assets to
advertisement companies for printing. The memory space allocated to these copies
is called “parameter space.”
If the size of the data is large, the cost of passing copies is also high, which
affects communication efficiency. In this case, the function caller can pass the be-
ginning address of the data to the “manufacturer,” because a large amount of data
are usually stored continuously in memory. The “manufacturer” then fetches data
from the specified address, as shown in Figure 4.20.
Pass by value
One-directional
information
transmission
Fetch
Copy of information from
Address of location specified
address of
actual data by users
actual data
Note that the information passed is a copy of the “address of actual data.” It is simi-
lar to data maintenance of library servers: service providers can operate servers re-
motely as long as they know IP addresses and passwords.
Service providers can also carry out maintenance on-site. Similarly, the “manu-
facturer” can also head to the address of data and process them directly, as shown
in Figure 4.21. This is “pass by reference” in C. Function callers can pass the begin-
ning address of data to the “manufacturer” so that it can process data at the address
directly.
Pass by reference
Bidirectional
information
transmission Use the
original
data space
Argument
Function receives
Data to be processed
and processes
Parameter
Function
Function call
definition
(outsourcer)
(manufacturer)
Note that argument space and parameter space is the same in “pass by reference.”
We have just seen that function callers can pass data to functions by value or
by reference. In fact, how function results are received are related to how data are
submitted.
Argument Parameter
Pass by reference
Bidirectional
information Use the
transmission original
data space
Argument
Function receives
Data to be processed
and processes
Parameter
A function name briefly describes the processing, while the function body im-
plements the processing.
Data receiving and result submission of a function are done through informa-
tion interfaces. The input information interface is implemented as the parameter
list. The information passed can be either values or addresses. Results can be out-
put in two ways: using a return statement or putting them at a specified address for
callers to access. Again, results can be either values or addresses.
4.4.1.2 Relations between function syntax and key elements of function design
As shown in Figure 4.25, input information determines the parameter list, while
output information determines the function type.
A function in C consists of a function header and a function body. The function
header describes the structure of a function, while the function body implements
its functionality. As such, input, output, and processing of a function determines its
structure.
a function call via pass by value is called a “call by value,” while one via pass by refer-
ence is called a “call by reference.”
Outsourcer
Start
Complete
main function
main terminates
A completed the task quickly and reported the result to him. Then Mr. Brown
asked, “If we simulate this process with a program and you are asked to implement
the child functions, how are you going to do that?”
“That’s simple,” answered A, “I’ll write two functions. The max function com-
putes the highest score, and the min function computes the lowest score. The main
function can obtain the highest and the lowest scores by calling them, and then
compute the difference.”
Mr. Brown smiled and asked, “Is that a complete simulation?” A thought for a
while and responded, “No, I should have written another function for difference
computation.” “How does this function work then?” Mr. Brown followed up.
A said, “Let the function be dif, then the execution order of these functions is
as shown in Figure 4.30. We have learned nested if and nest loops, can we call this
‘nested function call’?” “Of course,” Mr. Brown commended, “We do use this term
in C.”
4.4 Overall function design 159
In C programs, a function can call another function, and the function being
called can further call other functions, resulting in a nested function call. We may
have arbitrary layers of nested calls and complete sophisticated tasks with them.
All C programs are constructed by functions, each of which is independently
defined. That is, one cannot define another function in the definition of a function.
When using functions, one should always use the correct syntax. One of the com-
mon mistakes beginners make is using the wrong arguments. Programs with such mis-
takes cannot be compiled. Furthermore, it is often hard for them to realize the mistake.
(1) Parameter list: In a function definition, the definitions of parameters are listed
in the parameter list.
(2) Argument list: In a function call, references of arguments are listed in the argu-
ment list; in the case of arrays, we simply use array names in the argument list.
variable=functionName(arguments); functionName(arguments);
function is three integers, which determines the parameter list. The output of the function is the
computed maximum, which is an integer, so the function should be int as well. Figure 4.33 sum-
marizes key elements of the function.
01 #include <stdio.h>
02 int max(int a, int b, int c); //Declare function max
03 /*---------------------------------------- -----------
04 Find the maximum among numbers a, b and c
05 ------------------------------------ ----------------- */
06 int max(int a, int b, int c ) //Define function max
07 {
08 int m;
09 m=a>b ? a:b;
10 m=m>c ? m:c;
11 return (m);
12 }
13 int main(void)
14 {
15 int a, b, c, x;
16 scanf("%d,%d,%d", &a, &b, &c);
17 x=max(a,b,c); //Call function max
18 printf("max=%d", x);
19 return 0;
20 }
4. Debugging
We can study how child functions are called using a debugger.
Before debugging, we need to determine what we would like to inspect and act accordingly.
Issues we are going to investigate related to call by value are shown in Figure 4.36.
Debugging
plan
– Are addresses of arguments and parameters the same?
– How are parameters and arguments passed?
– Is it easy to debug if parameters and arguments have the same names?
The input parameters of the main function and max function are a, b, and c. We can make a
table to record their values during debugging and then analyze these values. Figure 4.37 shows
the completed table with “Address” values obtained from the debugger. The debugging process
is shown in Figure 4.38, in which the image on the left shows the Watch window before max is
called, and the image on the right shows the Watch window after entering the max function.
Command of stepwise tracing has been introduced in chapter “Execution of Programs.” With
values and addresses of a, b, and c displayed in the Watch window, we can complete the table
in Figure 4.37. Note that the variable addresses may vary after each linking and compilation.
Figure 4.38: Debugging process of maximum finding program using the same name for
arguments and parameters.
We shall rename the arguments as d, e, and f, and repeat the debugging process.
If we step forward in the main function, variables in the main function will obtain memory space
and addresses as shown in Figure 4.40. At this moment, their values are still random numbers.
Values of variables in child function max are “not found” at this moment. This is due to the
masking mechanism of modules, which prevents a function from accessing data inside other
functions.
Figure 4.41 shows the state after executing scanf and before calling max. Now values of d, e,
and f are 2, 3, and 6, respectively. The value of x is still a random number.
By pressing F11, we step into child function max. As shown in Figure 4.42, variables in the main
function are now invisible, while variables in max become visible. We can see that parameters a,
b, and c have obtained values of arguments d, e, and f, but their addresses are different from
those of d, e, and f. The value of m is a random number at this moment.
After max function terminates, result 6 is stored into m, as shown in Figure 4.43.
The program then steps out of max and returns to main, as shown in Figure 4.44. We can see
that the value of x has become 6.
4.5 Examples of function design 165
2. Code implementation
#include <stdio.h>
struct student
{ int num;
float grade;
};
struct student func1(struct student stu) //Structure variable as parameter
{
stu.num=101;
stu.grade=86;
return (stu); //Return a structure variable
}
int main(void)
{
struct student x={0, 0};
struct student y;
y = func1(x); //Structure variable as argument
return 0;
}
4.5 Examples of function design 167
3. Debugging
In Figure 4.45, note that address of structure argument x is 0x12ff78.
In Figure 4.46, note that the address of parameter stu is 0x12ff14, which is different for the
address of x. In conclusion, the value of the argument is copied into the parameter.
In Figure 4.47, members of structure stu are modified in child function func1.
In Figure 4.48, structure y in the main function is used to store the value of the structure vari-
able returned by func1.
Note: x, y, and stu have different addresses. The value of x is not modified.
In essence, call by value copies values of arguments into parameters. Thus, updates
of parameters in child functions would not affect the variables used in the function
call. Hence, call by value protects our data by preventing the function being called
from modifying variables in the caller.
Analysis
In this problem, parameters can be passed in multiple ways. We shall implement the program
using three ways of parameter passing.
m n
Index 0 1 2 3 4 5 6 7 8 9
score[ ] 1 2 3 4 5 6 7 8 9 0
Solution 1
1. Function structure design
Figure 4.50 analyzes the number of inputs and outputs of the child function.
The input needs to contain all information of array score and values of indices m and n. There are
multiple elements in score. m and n are both single variables. Hence, we shall pass the array
score by address and pass m and n by value.
The output is the partial sum of elements between indices m and n. Because it is a single
value, we can return it using a return statement. The return type is int.
Function
Function Function type Parameter list
name
header
int func (int *sPtr, int m, int n)
{ int i, sum=0;
sPtr = &sPtr[m];
for ( i= m; i<=n; i++, sPtr++)
Function
body sum = sum + *sPtr;
//Compute sum of elements between index m and index n
return sum;
}
3. Code implementation
The code implementation is given in Figure 4.52.
The child function is between line 5 and line 15, while the remaining part is the main function.
170 4 Functions
01 #include "stdio.h"
02 #define SIZE 10
03 int func( int score[ ], int m, int n);
04
05 //Compute the sum of elements of array score between index m and index n
06 int func(int score[ ],int m,int n)
07 {
08 int i,sum=0;
09
10 for (i= m; i<=n; i++)
11 {
12 sum=sum+score[i];
13 }
14 return sum;
15 }
16 int main(void)
17 {
18 int x;
19 int a[SIZE]= {1,2,3,4,5,6,7,8,9,0};
20 int p=3 , q=7; //Specify range of sum
21 Display
22 printf( “Elements of array a between index %d and index %d are:",p,q); specified
23 for ( int i= p; i<=q; i++) elements
24 {
If an argument is
25 printf( "%d",a[i]); an array, we only
26 } need to write the
27 printf( "\n"); array name
28 x=func(a,p,q);
29 printf( “Sum of elements of array a between index %d and index %d are: %d\n",p,q,x);
30 return 0;
31 }
Program result:
Elements of array a between index 3 and index 7 are: 4 5 6 7 8
Sum of elements of array a between index 3 and index 7 are: 30
Lines 22–27 print values of elements in the specified range so that it is easier to debug later.
On line 28, function func is called with argument array a and indices p and q. Pay attention
to how we use the array name in the argument list.
4. Debugging
Before debugging, we should list issues we would like to investigate, which include questions
related to address passing and variables we want to inspect. Figure 4.53 shows the values of
these variables in the Watch and Memory windows of the debugger. During debugging, we can
use a table to record the values of variables for further analysis. With all the information we
have, we can conclude that call by reference uses the same memory space for parameters and
arguments, while call by value uses separate memory spaces for them.
4.5 Examples of function design 171
Debugging
plan
– Are parameters and arguments Call by reference
stored in the same memory units? Parameters and arguments
– How are parameters and share memory units
arguments passed?
Call by value
Parameters and arguments use
different memory units
Solution 2
1. Function structure design
The computation result of the child function can also be accessed using a shared address, as
shown in Figure 4.54. In this case, the function type is void, and the result is stored at a speci-
fied position of array score. An integer variable size represents the position.
Function
Function Function type Parameter list
name
header
void func (int score[], int m, int n, int size)
{ int i, sum=0;
for ( i=m; i<=n; i++)
Function sum=sum+score[i];
body
score[size]=sum; //The sum is stored in specified position of score
}
3. Code implementation
Figure 4.56 shows the code implementation of solution 2.
01 #include "stdio.h"
02 #define SIZE 10
03
04 void func( int score[ ],int m,int n,int size);
05
06 //Compute the sum of elements of array score between index m and index n,
//store result in position with index size
07 void func( int score[ ],int m,int n,int size)
08 {
09 int i,sum=0;
10
11 for ( i= m; i<=n; i++)
12 {
13 sum=sum+score[i];
14 }
15 score[size]=sum; //The sum is stored in specified position of score
16 }
17 int main(void)
18 {
19 int a[SIZE]= {1,2,3,4,5,6,7,8,9,0};
20 int p=3, q=7; //Specify range of sum
21
22 printf("Elements of array a between index %d and index %d are:",p,q);
23 for (int i= p; i<=q; i++)
24 {
25 printf("%d ",a[i]); Nonvalue-returning
26 } functions
27 printf("\n");
28 func(a,p,q,SIZE-1);
29 printf("Sum of elements of array a between index %d and index %d are:
30 %d\n",p,q,a[SIZE-1]);
30 return 0;
31 }
The child function is declared and defined between line 4 and line 16. Note that the type of func-
tion is void, as shown in line 28, so it is a nonvalue-returning function call.
The main function is the same as in solution 1, except that the function call is slightly different.
4. Debugging
As usual, we need to list issues for investigation before debugging. We shall focus on issues
related to passing by reference in this solution, as shown in Figure 4.57. The value of the last
element of array a in the main function should be 0 before calling child function func and 30
after the call. Figure 4.58 shows debugging information of the program.
174 4 Functions
Debugging
plan
– Inspect how the result is stored into score in child function
– Check whether a[SIZE-1] is changed after function call
Using pass by reference,
child functions can
m n
modify data at specified
addresses so that the
Index 0 1 2 3 4 5 6 7 8 9 caller can obtain
updated values
a[] before the call 1 2 3 4 5 6 7 8 9 0
a[] after the call 1 2 3 4 5 6 7 8 9 30
Solution 3
1. Function structure design
Figure 4.59 shows the third solution. Compared with solution 1, the only difference is that the
beginning address of the score is passed to the child function using a pointer. This is an alter-
native form of “call by reference:” using a pointer as an argument.
to compute the sum. Note that we refer elements of score using pointer sPtr. Finally, the sum is
returned using the return statement.
An alternative way of
pass by reference: using
a pointer as parameter
Parameter Parameter passing
Content Quantity
passing method implementation
Pass by
Information of array score Multiple int *sPtr
reference
Input Parameter
Values of m and n Single Pass by value int m,int n
Sum of array elements
Output between index m and Single Pass by value Return int type
index n
Function Function
Function Parameter list
type name
header
int func (int *sPtr, int m, int n)
{ int i, sum=0;
sPtr = &sPtr[m]; //sPtr points to address of the
element to be accessed
Function for ( i= m; i<=n; i++, sPtr++)
body sum = sum + *sPtr;
//Compute sum of elements of score between index m and index n
return sum;
}
3. Code implementation
01 #include "stdio.h"
02 #define SIZE 10
03 int func( int *sPtr,int m,int n);
04
05 int func( int *sPtr, int m, int n)
06 {
07 int i,sum =0;
08
09 sPtr = &sPtr[m]; //Point sPtr to address of element to be accessed
10 for ( i= m; i<=n; i++, sPtr++)
11 {
12 sum = sum + *sPtr;
13 }
176 4 Functions
14 return sum;
15 }
16 int main(void)
17 {
18 int x;
19 int a[SIZE] = {1,2,3,4,5,6,7,8,9,0};
20 int *aPtr = a;
21 int p=3, q=7; //Specify the range
22
23 x=func(aPtr,p,q);
24 printf( "%d\n",x);
25 return 0;
26 }
4. Debugging
As usual, we list issues related to pass by reference and variables we want to inspect, as shown
in Figure 4.61. Argument aPtr and parameter sPtr should have the same value, which ought to
be the address of array a.
Debugging
plan
Are memory units of parameters and arguments the same Simulated call by reference
when using a pointer as parameter? Parameters and arguments
use different memory units
Arguments in main function Parameters in child function Call by value
Variable Address Value Variable Address Value Parameters and arguments
use different memory
Address of
a array a units
Address Address
aPtr sPtr of array a
of array a
p 3 m 3
q 7 n 7
x=func(aPtr,p,q); int func(int*sPtr, int m, int n)
In the child function func, the address of sPtr is 0x18feb8 and the value of sPtr is the same
as array a.
With the information displayed in the Watch window, we can complete the table shown in
Figure 4.63. In conclusion, parameter addresses and argument addresses are different when
passing a pointer to the child function. This is similar to call by value. We call such a function
call a simulated call by reference.
We continue the debugging process until sPtr is going to point to the element with index 3, as
shown in Figure 4.64. Note that the value of sPtr is 0x18ff1c at this moment. Then we enter the
for loop with i = 3. The value of sPtr is updated to 0x18ff28. Using the asterisk operator, we can
obtain the value of the unit it points to, which is 4. We can also inspect the memory layout at
this address in the Memory window.
178 4 Functions
In the next iteration, as shown in Figure 4.65, we have i = 4 and sPtr pointing to 0x18ff2c after
increasing by 1. The value stored at this address is 5. In the next iteration, i becomes 5 and sPtr
points to address 0x18ff30, in which value 6 is stored.
When the value of i becomes 8, the loop terminates, as shown in Figure 4.66. Note that sPtr
now points to address 0x18ff3c. The function func then terminates, and the program returns to
the main function. The computation result 30 is now stored into x. It is worth noting that the
address and value of aPtr did not change as sPtr changed.
4.5 Examples of function design 179
Conclusion
Memory units of Information
Type Call type
parameters and transmission
arguments direction
Value Different Single
Call by value
Parameter Pointer Different Double
Array name Shared Double Call by reference
Analysis
As shown in Figure 4.68, the formula requires the computation of multiple factorials. Consequently,
we can write a function that computes factorial and reuse it.
180 4 Functions
C
n k =
n!
k !× (n − k)!
(n > k)
Reuse
factorial
function
We can write out the function header based on these key elements, and the function body
based on its functionality. An exception handling routine is necessary so that the function re-
turns –1 when x<0. The cumulative product computed in the for loop is stored in variable f.
Although there are two return statements, the function has to terminate through one and ex-
actly one exit, as shown in the flowchart (Figure 4.70).
{
f=1
int i;
x<0 Y Return"-1"
int f=1; //Cumulative product
Function if ( x<0 ) return (-1);
body f = x!
for ( i=1; i<=x; i++) f=f*i;
return( f ); Return value of f
2. Code implementation
As shown in Figure 4.71, the main function calls factorial multiple times in one expression.
4.5 Examples of function design 181
#include <stdio.h>
int factorial (int x); Function declaration
int main(void )
{
int c;
int m,n;
printf("input m,n:");
scanf(" %d%d",&m, &n);
c=factorial (m)/(factorial (n)*factorial (m-n)); Function call
printf("The result is %8.1f", c);
return 0;
}
1. Algorithm design
Regardless of finding the keyword or not using a binary search function, the mid value will be the
index of the position at which the new number will be inserted. Having found this index, we can
use the move function to move array elements backwards and insert the number at position mid.
2. Code implementation
/*===============================================
Functionality: binary search
Input: address of sorted array, array length, value of keyword to be found
Output: position of the last search
===============================================*/
int BinarySearch (int a[], int n, int key)
{
int low=0, high=n-1;
int mid;
while (low<=high)
{
mid = (low+high+1)/2;
if (a[mid]== key) break; //Search succeeded
else
182 4 Functions
{
if (a[mid]> key) high = mid-1; //Search in the low range
else low = mid+1; //Search in the high range
}
}
return mid;
}
/*===============================================
Functionality: move array elements
Input: address of array, array length, position from which the move starts
Output: None
===============================================*/
void move(int a[],int n,int subscript)
{
int i;
for(i=n;i>subscript;i--)
{
a[i]=a[i-1];
}
}
#include <stdio.h>
#define N 11
int main(void)
{
int array[N]={5,10,19,21,31,37,42,48,50,55};
int number; //Number to be inserted
int insert_sub; //Insert position
printf("The original array:\n");
for(i=0;i<N;i++) printf("%d ",array[i]);
printf("\n");
printf("Please insert a new number:");
scanf("%d",&number);
insert_sub=BinarySearch (array,N-1,number); //Compute insert position
move(array,N-1,insert_sub+1); //Move elements after insert position afterwards
array[insert_sub+1]=number; //Insert the number
printf("The array after insertion:\n");
for(i=0;i<N;i++) printf("%d ",array[i]);
printf("\n");
return 0;
}
4.5 Examples of function design 183
Analysis
Although the problem is trivial, we shall use three functions to solve it in order to demonstrate
nested function calls. The code implementation is as follows:
int dif(int x,int y,int z); //Compute the difference of the maximum and
//the minimum of x, y and z
int max(int x,int y,int z); //Compute the maximum of x, y and z
int min(int x,int y,int z); //Compute the minimum of x, y and z
int main(void)
{
int a,b,c,d;
scanf("%d%d%d",&a,&b,&c);
d=dif(a,b,c);
printf("Max-Min=%d\n",d);
return 0;
}
int dif(int x,int y,int z) //Compute the difference of the maximum and
//the minimum of x, y and z
{
return (max(x,y,z) - min(x,y,z));
}
int max(int x,int y,int z) //Compute the maximum of x, y and z
{
int r;
r= x>y ? x:y;
return(r>z?r:z);
}
int min(int x,int y,int z) //Compute the minimum of x, y and z
{
int r;
r = x<y ? x:y;
return(r<z ? r:z);
}
184 4 Functions
Analysis
1. Data structure design
We shall use a two-dimensional array studentGrades[number of students][number of courses]
to store all the grades.
2. Function design
Based on the problem description, we can summarize the key elements of the function, as
shown in Figure 4.72.
3. Code implementation
//Process 2-dimensional array in child function
#include <stdio.h>
#define STUDENTS 3
#define EXAMS 4
//Function declaration, see section 4.6.6 for introduction of const
int maximum( const int grades[ ][EXAMS], int pupils, int tests );
//When using 2-d array as parameter, the row size can be omitted in
//definition and declaration, but the column size cannot
int main(void)
{
//Initialize students’ grades
int studentGrades[STUDENTS][EXAMS]
= { { 77, 68, 86, 73 },
{ 96, 87, 89, 78 },
{ 70, 90, 86, 81 }
};
printf( "Highest grade: %d\n",maximum(studentGrades,STUDENTS,EXAMS));
return 0;
}
int maximum(const int grades[ ][EXAMS], int pupils, int tests )
{
int i; //Counter of student
int j; //Counter of courses
int highGrade = 0; //Initialize with lowest possible grade
4.5 Examples of function design 185
Analysis
1. Algorithm design
The student records are stored in a structure array student stu[], which is passed to child func-
tion output() by the main function through passing its address.
2. Code implementation
#include <stdio.h>
#define N 5
struct student
{
int num;
char name[8];
int score[4];
};
void output(struct student stu[])
{
int i,j;
printf("\nNo. Name Sco1 Sco2 Sco3\n"); //Print table header
for (i=0; i<N; i++)
{
printf("%-6d%-6s",stu[i].num,stu[i].name); //Print ID and name
for (j=0; j<3; j++) printf("%-6d",stu[i].score[j]); //Print grades
printf("\n");
}
}
186 4 Functions
int main(void)
{
struct student stu[N]=
{
{1001,"zhao",98,78,86,76},
{1002,"qian",92,68,76,67},
{1003,"sun",78,65,81,72},
{1004,"li",91,73,85,74},
{1005,"zhou",90,73,85,71},
};
output(stu);
return 0;
}
Analysis
1. Code implementation
#include <stdio.h>
struct student
{ int num;
float grade;
};
struct student* func2(struct student stu)
{
struct student *str=&stu;
str->num=101;
str->grade=86;
return (str); //Return structure pointer
}
int main( )
{
struct student x={0, 0};
struct student *stuPtr;
stuPtr = func2(x);
return 0;
}
2. Debugging
The process of debugging this program is shown further. In Figure 4.73, the address of argu-
ment x is 0x12ff78.
4.5 Examples of function design 187
In Figure 4.75, the values of members in the structure stu are updated.
In Figure 4.76, stuPtr in the main function is used to store the value of local variable str. A local
variable is a variable defined inside a function.
Note: It is not recommended to return the addresses of local variables. Because the system re-
claims the memory space of local variables after the function returns, information related to
local variables is no longer guaranteed to be correct.
Analysis
1. Background knowledge
The array definition method introduced in chapter “Array” allocates memory statically. In other
words, the size of the array and the address it is stored at are unchanged during program exe-
cution. Suppose that we want to insert new data into the array during program execution, but
the allocated array space is already fully used. Is there a way to expand the array space? There
is a memory allocation method called “dynamic memory allocation” in C: if a program needs
extra storage space during execution, it can “request” memory space of a certain size. When
the program no longer needs the space, the space can be returned to the system. Related li-
brary functions include malloc(), calloc(), free(), and realloc(). One must include the header file
stdlib.h or malloc.h to use these functions.
1) Memory allocation function malloc()
Prototype: void *malloc(unsigned size);
Functionality: allocates a block of memory of size bytes.
Parameters: size is an unsigned integer, which represents the size of the requested memory
space.
Return value: the address of the newly allocated memory is returned. If there is no memory
available, NULL shall be returned.
Note:
(1) NULL is returned when size is 0.
(2) void* is a typeless pointer that can point to memory units of any type. A typeless
pointer can be assigned to pointers of other types after forced type conversion.
2. Code implementation
1 #include <stdio.h>
2 #include <malloc.h>
3
4 int *DefineArray(int n); /*Define a dynamic array of size n*/
5 void FreeArray(int *p); /*Release memory pointed to by p*/
6
7 int main()
8 {
4.5 Examples of function design 189
9 int *p, i;
10 int nCount; /*Number of students*/
11 float fSum=0; /*Total grade*/
12
13 /*Input number of students*/
14 printf("\nPlease input the count of students: ");
15 scanf("%d",& nCount);
16
17 /*Define a dynamic array p*/
18 p= DefineArray(nCount);
19 if (p==NULL) return 1; /*Exception routine*/
20
21 /*Input grades of each student*/
22 printf("Please input the scores of students: ");
23 for( i=0; i< nCount; i++ )
24 {
25 scanf("%d", &p[i]);
26 }
27
28 /*Compute total grade*/
29 for(i=0;i< nCount;i++)
30 {
31 fSum+=p[i];
32 }
33
34 /*Print average grade*/
35 printf("\nAverage score of the students: %3.1f", fSum/nCount);
36
37 /*Free dynamic array p*/
38 FreeArray(p);
39 return 0;
40 }
41
42 /*Dynamically request memory space of size n*sizeof(int), which is used
for an int array with n elements*/
43 int *DefineArray(int n)
44 {
45 return (int *) malloc( n*sizeof(int) );
46 }
47
190 4 Functions
Having learned rules of information transmission between functions, readers can try to analyze
this program on their own. Figure 4.77 lists intermediate results of each step. If readers find it
hard to analyze the program by merely reading it, it is also possible to use a debugger to in-
spect variable values.
Index 0 1 2 3 4 5 6 7
(s[]) in[] 1 2 3 4 \0
(g[]) out[] 1
in[] in step 3 2
4.5.4.1 Introduction
We mentioned before that Mr. Brown wrote an arithmetic questions program for his
son Daniel. The program could generate random arithmetic problems and determine
whether Daniel’s answer was correct. Daniel spent much time on it and enjoyed it, so
Mr. Brown wanted to recommend the program to his nephew Annie, who was living
192 4 Functions
in another city. Mr. Brown knew that Annie’s parents were not familiar with installing
programs, so he only sent a .exe file through email. The executable file required no C
compiling environment so that Annie could run the program from file explorer di-
rectly. When he tested the executable file, however, he found that the console win-
dow popped up after the program is started and then disappeared quickly before he
could even see the result.
“What should I do?” Mr. Brown thought to himself. He then recalled a method
used before graphical user interfaces were invented. Back in those days, DOS was
the dominating operating system. In DOS, all commands were sent to computers
from keyboard input, including the execution of applications. The interface used
for command input was all black. It is precisely the popped-up window which dis-
plays result after we execute programs in VC6.0 IDE, namely the console. The con-
sole is also called a command line interface, in which users type in commands for
applications in the command line environment. Program results are also displayed
in the command line interface.
However, the graphical user interface has been the de-facto standard nowa-
days. Is there a way to fall back to “console?” The Windows system does preserve
this function. We can enter the command line interface by typing cmd in the Run
window of Windows, as shown in Figure 4.78. Even if a computer is not equipped
with a compiling environment, we can still run console applications in cmd com-
mand line interface. After a console application terminates, it returns to cmd so that
we can inspect its result.
The return type of the main function is int, which is consistent with the return state-
ment at the end of the program. 0 is the return value of the main function. Where is
it returned to then? After the main function terminates, the return value is sent
back to the operating system, indicating that the program terminates normally.
We can use parameters in plain functions, but can we do the same with the
main function? If a function has parameters, then we have to pass arguments when
calling it. However, no function can pass arguments to the main function because it
cannot be called by any function. As a result, the argument must be provided exter-
nally. How can we do this?
A C program turns into an executable file with extension .exe after being com-
piled and linked. An executable file can be executed directly in the operating sys-
tem. In other words, it is the system that runs the file. Since other functions cannot
call or pass arguments to the main function, it has to be done by the system. In C
programs, we can pass arguments to main functions by typing them in the com-
mand line interface.
Let us take a look at the syntax of the main function with parameters:
Command line arguments are also called positional arguments. They can be passed
to programs. Value of argc (argument count) is equal to the total number of posi-
tional arguments (including the program name). argv (argument value) is a pointer
array, in which program name is stored in argv[0] and the ith positional argument
is stored in argv[i], up until argv[argc-1]. In this way, we can pass command line
arguments into C programs without using input statements.
194 4 Functions
Analysis
The code implementation is shown in Figure 4.79.
On line 13, the sscanf function is also an input function, which is similar to scanf. We have
learned that scanf uses keyboard input (stdin). sscanf, on the other hand, uses fixed strings as
inputs. Readers can refer to Appendix C of Volume 1 for more on input functions. sscanf reads
data in a specified format from a string. Here it reads data from argv[1] and puts it into variable w.
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main( int argc, char*argv[] )
//arg c is the number of parameters; arg v[0] is the program name, other parameters are stored after it
05 {
06 float w,h; // Width and height of rectangle Exception routine
07 if(argc< 3) // Parameters less than 3
08 {
09 printf(“input:File_Name width height\n");
10 printf(“E.g.: %s 3.2 4.5\n",argv[0]);
11 exit(0); //Exit the program
12 } There are two
13 sscanf(argv[1],"%f",&w); //Width parameters besides
program name
14 sscanf(argv[2],"%f",&h); //Height
15 printf("area = %f\n",w*h);
16 return 0;
17 }
On line 11, exit() is a library function whose header file is either stdlib.h or windows.h. It closes
all files and terminates the current process. In the statement exit(x), the value of x represents
exit status and is returned to the operating system. If x is 0, the program exited normally; other-
wise, it exited with exceptions.
Figure 4.80 shows the command line interface opened by cmd command. We first enter the
directory of the executable file with command cd, which stands for “change directory.” In this
example, the directory is “D:\MyWin32App\Win32App\Debug” and the executable file is “demo.
exe.” Then we type in command line arguments of the main function in the interface. After typ-
ing in the program name, width, and height (the program specifies the order in which argu-
ments are input), the program outputs area of the corresponding rectangle. We test the
program with three groups of inputs, two of which are valid. In the case of invalid input, the
program outputs the format of valid input and an example in the exception routine.
4.5 Examples of function design 195
Enter directory of
the executable file
Correct example 1
Correct example 2
Wrong example
int indicates the return type of main( ). Information passed to functions is normally written in-
side parentheses after function names. void means that no arguments should be passed to
main( ). However, we often find the following forms of main( ) in legacy C code:
(1) main( )
This is allowed in C90 standard, but not in C99. Hence, do not write this even if it is valid in
your compiler.
4.6 Scope
When solving practical problems with programs, the scale of programs becomes
larger as problems become more complicated. This leads to many issues in program-
ming. In response to these issues, we introduced the idea of modularization. To be
more specific, we introduced functions into the C language. Figure 4.81 shows some
issues related to functions, which we have seen in previous sections.
4.6.1 Introduction
said, “I prefer using i, j, and k for loop control variables. Can I still use them if Prof.
Brown uses them as well? Should we discuss with each other before defining our
variables?”
A teacher thought for a while and responded, “From the perspective of the over-
all workflow, we should create program files on our own, but a program cannot run
if it has no main function. From the perspective of program execution, however, we
are writing one large program, which should contain only one main function. How
should we do this?”
Mr. Brown summarized everyone’s questions, as shown in Figure 4.82. Then he
said, “To answer these questions, we need to introduce new rules in the program-
ming language. Can you imagine what the best mechanism of working in team is? I
think we should work in different files. Variables in different files can have the
same name. One program should have only one main function.” Everyone nodded.
He asked further, “In this case, what mechanisms of program execution are neces-
sary to make what I just said possible?” “I think we can attach scope and lifespan
to variables so that they are isolated in a file or a function. This can stop them from
messing around,” another teacher answered. Everyone agreed with him.
As they have imagined, the rule in C is: a C program can consist of multiple
files, each of which can have multiple functions, as shown in Figure 4.83. Variables
in different files can have the same name. A C program must have one and only one
main function.
Independent files
Duplicated names
Single main function
A convenient
C program
and reasonable
mechanism
Recall the idea of modularization: we aim to hide internal implementation and data
of modules from outside and to ensure that modules communicate with other mod-
ules through information interfaces. To design such a mechanism, where should we
start? Based on the discussion in the introduction part, it is clear that we should
start from the isolation of internal data and the masking mechanism of functions.
4.6 Scope 199
Interface information
– Allocation of variable spaces
Variables in
Data processing a function – Life span of variables
– Scope of variables
C program
We use signs
File A File B ... File N “External”and“Internal”
to represent availability
of functions in a file
E Function A1 I Function B1 I Function N1
......
I Function A2 I Function B2 E Function N2
...... ...... ...... I Internal
E External
namely local variables. The static segment stores data that are shared among func-
tions, that is, global variables.
Memory
…
Code segment User
Constant segment workspace
Data space
Static segment
Dynamic segment
…
Storage
segment Life span Storage class Notes
Type
register Register Variables stored in registers
Same as
Dynamic function Auto variables are local variables that are valid only once
auto Auto
in the function in which they are declared
Auto variables are local variables that are valid multiple
Same as static Static
Static times in the function in which they are declared
program
extern External Global variables declared outside any function
Registers are fast storage locations inside CPUs. They provide the fastest way to ac-
cess data, even surpassing RAM. However, the size of the registers is limited.
Programmers nowadays seldom use register class themselves, because compilers
will handle it automatically. Register class is often used for variables that are ac-
cessed frequently, such as loop variables.
4.6 Scope 201
Variables defined in functions are in the auto class by default unless otherwise speci-
fied. The value of an auto variable disappears when the function terminates. In its essence,
this happens because the system needs to reclaim storage units of the auto variable.
The value of a static variable is preserved after the function terminates. In other
words, the system will not reclaim its memory unit. Consequently, this value is still
available when the function is called again.
Scope
A scope is the visibility of an object (such as a variable) in the code.
Rule of scope
Each function in a C program is an independent code block.
Code that constructs a function body is hidden from other parts of the program. It can’t be
accessed by statements (except the statement that calls the function) in other functions.
Data type Data type indicates the size of a variable in the memory
Variable
attribute Storage class indicates the life span of a variable in the
Storage class
memory and its scope
01 #include"stdio.h"
02 void varfunc()
Local variable: value is not accessible
03 { after the function terminates
04 int var=0; //Local variable
05 static int static_var=0; //Local static variable
06 printf("var=%d ",var);
07 printf("static_var= %d\n",static_var); Static variable:value is preserved
08 var++; after the function terminates
09 static_var++;
10 }
11 int main(void)
12 {
13 int i; Program result:
14 for(i=0; i<3; i++) Iteration 0 var=0 static_var= 0
15 { Iteration 1 var=0 static_var= 1
16 printf(“Iteration %d\n",i); Iteration 2 var=0 static_var= 2
17 varfunc();
18 }
19 return 0;
20 }
Local variable
A local variable is defined inside a function. It is available only in this function.
We can omit the storage class auto for local variables defined in a function.
Local variables are
locally available, global
Global variable variables are globally
available
A global variable is defined outside any function. It can be accessed by all
functions in the program.
We can omit the storage class extern for global variables defined in the
program. However, we must use extern when accessing global variables
defined in other files.
Keyword extern can be added in front of variables and functions to indicate that
their definitions are located in other files. Compilers will look for definitions in
other modules upon seeing extern.
Analysis
1. Data structure design
Let scores be stored in array data[N]. As we need to access it in all processing steps, we can
make it a global variable. To make testing easier, we can initialize it with initial values in our
program, as shown in Figure 4.92.
2. Algorithm design
Based on the problem description, we can use the algorithm shown in Figure 4.93 to solve the
problem.
4. Code implementation
Figure 4.95 presents the implementations of each child function. Storage classes of local varia-
bles defined in these functions are omitted, so they are auto by default. To discard the minimum
score in data, we initialize Least with the first element of data and compare every other element
with Least until we find the minimum. After finding the minimum score, we update it to be 0. We
can discard the maximum score using the same way. Finally, we add all values in data together
and divide the sum by the number of judges minus 2 to obtain the mean.
//Discard the minimum value Least //Discard the maximum value Largest //Compute the average of data
void Del_Least() void Del_Largest() float average()
{ { {
int Least,tag=0; int Largest,tag=0; float sum=0;
Least=data[0]; Largest=data[0]; for(int i=0; i<N; i++)
for(int i=0; i<N; i++) for(int i=0; i<N; i++) {
{ { sum+=data[i];
if (Least>data[i]) if(Largest<data[i]) }
{ { return (sum/(N-2));
Least=data [i]; Largest=data[i]; }
tag=i; tag=i;
} }
} }
printf("Least=%d \n",Least); printf("Largest=%d \n",Largest);
data[tag]=0; data[tag]=0;
} }
Figure 4.96 is a screenshot of part of the program, which includes function declarations, global
variables declarations, and the main function.
Note that on line 7, array data is declared outside the main function. Because it is declared
in the same file, extern can be omitted.
The three function calls are between line 63 and line 65. The first two functions are nonvalue-
returning functions, while the third is a value-returning function.
int a,b,c;
Definition of function 1
Definition of function 2
Scope of global
variables a, b and c int m,n;
Scopes of a, b and c are from function 1 to function 4, while m and n are only visible to the last
two functions. In other words, function 3 and function 4 can use all these variables; function 1
and function 2 can only use a, b, and c.
We can conclude that scope of global variables depends on its location in the program.
Analysis
1. Program design
Let a and b be two local variables defined in main function and in child function sub. We assign
values to them in both functions. The following code shows their values before and after calling
sub:
1 #include <stdio.h>
2 void sub();
3
4 int main(void)
5 {
6 int a,b; //They are local variables in main function
7
8 a=3; b=4;
9 printf("main:a=%d,b=%d\n",a,b); //Print their values in main function
10 sub(); //Call sub, which assign new values to a and b
11 printf("main:a=%d,b=%d\n",a,b); // Print their values in main function
12 return 0;
13 }
14
15 void sub()
206 4 Functions
16 {
17 int a,b; //They are local variables in sub
18
19 a=6; b=7;
20 printf("sub: a=%d,b=%d\n",a,b); //Print their values in sub
21 }
Program result:
main:a=3,b=4
sub: a=6,b=7
main:a=3,b=4
Note: Before calling sub, values of a and b are local variable values in main. When calling sub,
their values are local variable values in sub. Values in main function are masked. After returning
to main, values of a and b are once again local values in main.
2. Debugging
In Figure 4.98, we can see that addresses of a and b in main are 0x12ff7c and 0x12ff78
respectively.
In Figure 4.99, the program has just entered sub function. Addresses of a and b have become
CXX0069 error: variable needs stack frame. This error occurs because we have not allocated
memory to the variables we wish to inspect. Are a and b in the window local variables in main
function or in sub? Because the “execution arrow” points to the beginning of sub function, we
can infer that local variables a and b in sub have not been declared. Thus, this is an error re-
lated to variables in sub function.
In Figure 4.100, it is clear that addresses of a and b are different from what we have seen be-
fore. Although these variables have the same name in main and sub, they are actually stored in
different memory units
Example 4.16 Local variables and global variables with duplicated names
Examine scopes of a global variable and a local variable with the same name.
Analysis
Let a and b be two global variables. We also define local variables in child function max and in
main with the same names. Both functions contain operations on variables a and b. The pro-
gram is as follows.
1. Code implementation
1 #include <stdio.h>
2 int max(int a, int b);
3
4 int a=3,b=5; //Define a and b as global variables
5
6 int max(int a, int b) //a and b here are local variables
7 {
8 return (a>b ? a:b);
9 }
10
11 int main(void)
208 4 Functions
12 {
13 int a=8; //Define local variable a
14
15 printf( "max=%d\n", max(a,b)); //Use local variable a and global
16 //variable b as arguments
17 return 0;
18 }
Program result:
max=8
2. Debugging
In Figure 4.102, we have just entered main function. The Watch window displays both address
and value of global variable b. CXX0069 error occurs for variable a because there is a global
variable and a local variable with the same name.
In Figure 4.103, the address of local variable a is 0x12ff7c. No value has been assigned to it
at this time.
Figure 4.102: Local variables and global variables with duplicated names debugging step 1.
Figure 4.103: Local variables and global variables with duplicated names debugging step 2.
4.6 Scope 209
In Figure 4.104, we have assigned a value to a. In Figure 4.105, the address of local variable in
child function is 0x12ff28, instead of 0x12ff7c of the local variable a in the main function. Global
variable b is invisible in function max. Address of local variable b is 0x12ff2c.
Figure 4.104: Local variables and global variables with duplicated names debugging step 3.
Figure 4.105: Local variables and global variables with duplicated names debugging step 4.
Figure 4.106 shows addresses of a and b after returning to main. In Figure 4.107, the local vari-
able a in the main function is changed to c. In this case, both global variables a and b are visible
in the main function.
210 4 Functions
Figure 4.106: Local variables and global variables with duplicated names debugging step 5.
Figure 4.107: Local variables and global variables with duplicated names debugging step 6.
Conclusion
It is recommended to use different names for local variables and global variables. Duplicated
names will mask global variables, resulting in confusion.
Rules
If a local variable
– Local variables are locally visible; defined in a function
has the same name as
– Global variables are globally visible;
a global variable, the
– If a local variable has the same name as a global global variable is masked
variable, the local variable has the higher priority . in this function.
In the “shared resources” example, we mentioned that functions also have the
“availability” attribute. If a function defined in a source file can only be called by
functions in the same file, it is called an internal function; if functions in other files
can call it as well, it is called an external function.
To identify internal functions and external functions, we use two keywords of
storage classes: static and extern. When used for this purpose, they are merely iden-
tifiers and are no longer indicators of storage classes.
Figure 4.109 shows how to define an internal function and how to declare and
define an external function. The scope of an internal function is restricted to its
source file, while the scope of an external function is the entire program. If we omit
Internal function
An internal function is a function that is only available in the file Also called
in which it is defined “static function”
Syntax of definition: static type name(parameters) {body}
External function
An external function can be called by files other than the one in We need to declare
which it is defined. an external function
before calling it
Syntax of definition: [extern] type name (parameters) {body}
We have seen in previous examples that data are often shared among functions.
Functions can access the same data objects at different stages in different ways.
Sometimes, an unintentional operation may change the data, which is not what we
expect to see.
Our goal is to make data accessible by multiple functions and to ensure they
cannot be arbitrarily modified. To do this, we can use const keyword to define
data in parameters as constant. const is a keyword of C which prevents a variable
from being modified. Using const can partially enhance security and robustness
of programs.
Test file1.cpp:
01 #include <stdio.h> These are function declarations.
02 extern int reset(void); //Declare reset as an external function Their definitions are not in this file
03 extern int next(void); // Declare next as an external function
04 extern int last(void); // Declare last as an external function
05 extern int news(int); i // Declare news as an external function Whether a variable is global depends
06 the location at which it is defined
07 int i=1; //Define global variable i (outside all functions)
08 int main()
09 {
Global variable i and local variable i
10 int i, j; //Define local variables I and j
are not store in the same memory unit
11 i=reset();
12 for (j=1; j<4; j++)
13 {
14 printf("%d %d ",i, j );
15 printf("%d ",next()); Call an external function
16 printf("%d ",last());
17 printf("%d\n",news(i+j));
18 } Program result
19 return 0; 1 1 2 3 7
20 } 1 2 4 5 10
1 3 6 7 14
Test file2.cpp
01 extern inti; //Declare global variable i These are function declarations. Their
02 definitions are not in this file
03 int next(void)
04 { Definition of external function next.
05 return ( i+=1); Because we omit the extern keyword, it
is by default external. Global variable i
06 }
is visible in this function.
07
08 int last(void)
09 { Definition of external function last.
Global variable i is visible in this function.
10 return ( i+=1);
11 }
12
13 int news( int i) //Define parameter i, which is a local variable
14 {
Definition of external function news.
15 static int j=5; //Define static variable j
Local variable i is visible in this function.
16 return( j+= i);
17 }
Test file3.cpp
02 int reset(void)
05 }
1 //Example of const
2 #include <stdio.h>
3 #define SIZE 3
4 void modify( const int a[] ); //Function prototype
5 int b[SIZE]; //Globa variable used to store updated array
6
7 //Program starts from main
8 int main(void)
9 {
10 int a[SIZE] = { 3, 2, 1 }; //Initialization
11 int i; //Counter
4.7 Recursion 215
12
13 modify(a); //Function
14 printf( "\n Array a after calling modify:" );
15 for( i = 0 ; i < SIZE ; i++ )
16 {
17 printf( "%3d", a[i] ); //Print the array after function call
18 }
19
20 printf( "\n Array b after calling modify:" );
21 for( i = 0 ; i < SIZE ; i++ ) //Print updated array
22 {
23 printf( "%3d", b[i] );
24 }
25 return 0;
26 }
27
28 //Fetch values from array a, process them and store results in array b
29 void modify(const int a[])
30 {
31 int i; //Counter
32 for(i=0;i<SIZE;i++)
33 {
34 //a[i]=a[i]*2; Compilation error if we attempt to modify a
35 b[i]=a[i]*2;
36 }
37 }
Program result:
Array a after calling modify: 3 2 1
Array b after calling modify: 6 4 2
4.7 Recursion
Last weekend, Mr. Brown took Daniel to a family reunion. Since Daniel had never
attended a reunion, Mr. Brown introduced him to the four other kids in the room.
216 4 Functions
Five kids then sat together. When Mr. Brown asked about their age, the first kid A
said, “I am 2 years older than B on my left.” B decided to do the same and said, “I
am 2 years older than C on my left as well.” C imitated, “I am 2 years older than D
on my left.” D said, “I am 2 years older than Daniel.” When it came to Daniel, he
answered honestly that he was 10.
Mr. Brown burst into laughter and asked, “This is fun. How will you solve this
problem?”
“I am 10, so D is 10+2 = 12, C is 12+2 = 14, B is 14+2 = 16, and A is 16+2 = 18.” Daniel
answered quickly. “Well done!” said Mr. Brown, “Can any of you generalize a for-
mula?” B reckoned that this was a recursive relation. A, who had been learning to pro-
gram, said that this could be easily implemented by a loop, as shown in Figure 4.113.
Start
n=1, age=10
age=age+2
n++
Y
n<6
End
“‘n decreases by 1 proactively’ means that we scale down the problem. Here we
are decreasing the number of people n. What to decrease and how to decrease
should be determined by us. ‘n increases by 1 passively’ refers to the upscaling
when we come back from the base case. In this case, it is the increment of n. How
larger the scale of a previous problem is than the current one is determined during
the downscaling. Here we are increasing by 1” answered A.
Then Mr. Brown asked if anyone could draw the execution flow of this solu-
tion. Receiving no response after a while, he drew the graph himself, as shown
in Figure 4.115.
Y
n=1 Output age[n]
N
n=n+1
How is pausing Pause computation
implemented age[n] Resume to
in programs? age[n]+2
n=n-1 N
n=N
Y
End
“But how do we pause in a program? You can say that in words, but statements
in programs are executed one after another,” asked A.
“Calling another function,” Mr. Brown responded, “pauses the caller and exe-
cutes the callee. Since we have a ‘pause’ in the second method, it is only possible
through calling another function.”
“It is hard to see the correspondence between the change of n and age[n],
though,” argued A.
“In this case, let the child function be int age(int n), then the calling relation
between the main function and age can be represented by the chart in Figure 4.116.
Please note that there are multiple ‘pausing points.’ The age()’s in the gray area are
all pausing points.”
According to this figure, we can write the code shown in Figure 4.117.
01 #include <stdio.h>
02 int age(int n)
03 {
04 if (n==1) return(10); // Base case
05 else return (age(n-1)+2); // Move to the base case and derive result from it
06 }
07 age calls itself
08 int main(void)
09 {
10 printf("%d",age(5));
11 return 0;
12 }
On line 5, we can see that the function age() is calling itself but with a smaller
argument.
“It is like looking into the mirror.” A said, “Standing between two mirrors, you
can see many images of yourself, each smaller than another.” See Figure 4.118 for
an illustration of this metaphor.
4.7 Recursion 219
When a computation process calls itself directly (or indirectly), we call it a re-
cursive process. If the description of an object contains itself, or it defines itself,
then we call such an object a recursive object.
A recursive process is a round-trip process. As the scale of a problem becomes
smaller and smaller, there is an endpoint at which the scale can no longer be de-
creased. Then we start from the endpoint and return to where we started along the
original path.
We have roughly talked about recursion in the age example. Now we are going to
define recursion formally.
obtain results. Otherwise, it will call itself infinitely. As a result, a recursive process must con-
tain two key elements:
– Base case: the most straightforward instance of the problem, which can be solved without
recursion.
– Recursive case: an instance of the problem that can be solved through solving more
straightforward instances.
func(…)
{
……
func(…);
……
} Figure 4.119: Direct recursive call.
funcA(…) funcB(…)
{ {
…… ……
funcB(…); funcA(…);
…… ……
} }
Compared with nested function calls, recursive calls are special cases of nested
calls: the functions being called are exactly the caller. Both direct and indirect re-
cursive calls lead to a loop of function calls. If there is no base case, the program
will end in a situation that is similar to infinite loops.
loops: iteration uses loops explicitly, while recursion uses loops through repeated
function calls. Both recursion and iteration require a termination condition: itera-
tion terminates when the loop condition evaluates to false, while recursion stops
upon the base case.
(
1, n=o
n! =
n · ðn − 1Þ!, n>0
Analysis
1. Algorithm description
The process of computing factorial is as follows:
– n! can be computed by n * (n–1)!, so it suffices to compute (n–1)!;
– Similarly, it suffices to compute (n–2)! to obtain (n–1)!;
– In this way, n gets smaller and smaller. When n = 1, 1! is something we already know;
– then we trace back to compute 2!;
– and 3!;
– finally, we trace back to n!;
2. Code implementation
#include "stdio.h"
float fac(int n);
//fac computes n!
float fac(int n)
{
float f;
if (n<0) printf("Error!\n"); //Input is invalid when n<0
if (n==0||n==1) return 1; //Base case
return n*fac(n-1); //n! = n * (n-1)!
}
int main(void)
{
printf("%f",fac(4));
return 0 ;
}
3. Execution process of recursion
The calling process of the recursion is shown in Figure 1.121. Unlike nested calls of multiple
functions, all child functions are the same in recursion.
222 4 Functions
Analysis
We can derive the key elements of recursion based on the recursion relation of the Fibonacci
sequence.
– Base cases: fab(1) = 1, fab(2) = 1.
– Recursive cases: fab(n) = fab(n–2) + fab(n–1).
Analysis
– Base case: f(1) = 1
– Recursive cases: f(n) = n + f(n–1)。
The code implementation is as follows:
int fn(int n)
{
4.7 Recursion 223
Program reading exercise Finding the maximum element in an array with recursion
Analysis
Let the array be arr[] with length len.
– Base case: when len = 1, the maximum is the first element arr[0].
– Recursive cases: the maximum is the larger of arr[0] and the maximum of the array starting
from the second element.
The critical step in the algorithm: the maximum of the array starting from the second element is
max(arr + 1, len-1).
The code implementation is as follows:
#include <stdio.h>
int max(int arr[], int len)
{
if(1 == len) //Only one element
{
return arr[0];
}
int a = arr[0]; //The first element
int b = max(arr + 1, len - 1); //Maximum of the array starting from
the second element
return a > b? a : b;
}
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,10};
printf("Maximum: %d\n", max(a, sizeof(a) / sizeof(a[0])));
return 0;
}
224 4 Functions
4.8 Summary
Figure 4.122 shows the main contents of this chapter and their relations.
Syntax: typename(parameters)
Keyelements:input,output,functionality
Function
Information receivinginterface:parameters
definition
Result submission method:return statement,parameter addresses
Content:functionality codesegment that complete a certain task
Syntax:name(arguments)
Information submission interface: arguments
Function Syntax Function call
Purpose: execute the functionality code segment
Calling method: value-returning call, nonvalue-returning call
Function Syntax:typename(parameters)
declaration Purpose:a brief introduction of function
Information Statement
return statement: can only return one result
transmission interface
4.9 Exercises
1. [Concept of functions]
Which of the following statements is correct? ( )
A) A function in a C program can call or be called by any other functions in the
same program.
B) The location of the main function in a C program is fixed.
C) We cannot define another function in a function.
D) Every C program file has to have a main function.
2. [Pass by value]
3. [Pass by reference]
#define N 4
void fun(int a[][N], int b[],int n)
{
int i;
for(i=0;i<n;i++) b[i] = a[i][i];
}
4.9 Exercises 227
int main(void)
{
int x[][N]={{1,2,3},{4}, {5,6,7,8},{9,10}}, y[N], i;
fun(x, y, N);
for (i=0;i<N; i++) printf("%d,", y[i]);
printf("\n");
return 0;
}
5. [Recursion]
#include <stdio.h>
void my_put()
{
char ch;
ch = getchar();
if (ch != 'C') my_put();
putchar(ch);
}
228 4 Functions
int main(void )
{
my_put();
return 0;
}
Suppose input is: ABC <Return>, what is the output of the program above? ( )
A) ABC B) CBA C) AB D) ABCC
6. [Global variables]
Which of the following statements is wrong about global variables? ( )
A) The scope of a global variable starts from its definition and ends at the end
of the source file.
B) A global variable is one that can be defined at any position outside functions.
C) We can restrict the scope of a global variable using the extern keyword.
D) The lifespan of a global variable is the entire execution process of the
program.
7. [Scope]
Which two storage classes include variables that only take up memory units
when being used? ( )
A) auto and static
B) extern and register
C) auto and register
D) static and register
1. [Pass by value]
Functionality of fun
#define N 5
void sub( int n,int uu[])
{
int t;
t=uu[n-1]+uu[n];
uu[n]=t;
}
int main(void)
{
int i, aa[N]={1,2,3,4,5};
for(i=1; i<N; i++) sub(i,aa);
for(i=0; i<N; i++) printf("%d_",aa[i]);
230 4 Functions
printf("\n");
return 0;
}
n 1 2 3 4
(1) aa[n] 1,2,3,4,5,6
t 1+2
Program output:
#define N 3
#define M 3
select(int a[N][M], int *n)
{
int i,j,row=1,colum=1;
for(i=0;i<N;i++)
for(j=0;j<M;j++)
if(a[i][j]>a[row][colum]){ row=i; colum=j; }
*n= row;
return ( a[row][colum]);
}
int main(void)
{
int a[N][M]={9,11,23,6,1,15,9,17,20},max,n;
max=select(a,&n);
printf("max=%d,line= %d\n",max,n);
return 0;
}
4.9 Exercises 231
0 1 2
0 9 11 23
a[N][M] 1
2
i 0 1 2
j 0 1 2 0 1 2 0 1 2
a[row][colum]
9
row
0
colum
0
int ff(int n)
{
static int f=1;//————②
f=f*n;
return f;
}
int main(void)
{
int i;
for(i=1;i<=5;i++) //————①
printf("ff=%d\n",ff(i));//————③
return 0;
}
①i 1 2 3 4 5
②f 5
③ff(i)
#include<string.h>
#define N 3
struct stu
{
int ID;
char name[10];
int age;
};
int fun(struct stu *p)
{
p->ID+=201700; //————①
return (strlen(p->name)); //————②
}
int main(void)
{
struct stu students[N]=
{{1, "Zhang",20},
{2, "Wang", 19},
{3, "Zhao", 18}};
int len;
for(int i=0;i<N; i++ )
{
len=fun(students+i);
printf("Name %d has length %d\n",students[i].ID,len);
}
return 0;
}
i 0 1 2
p &students[0]
p->ID
p->name
1. Please write a program that reads two integers from keyboard input and out-
puts the one’s digit of the larger and the smaller to the second power.
2. Sequence A is defined as follows:
A(1)=1,
A(2)=1/(1+A(1)),
A(3)=1/(1+A(2)),
. . .. . .
A(n)=1/(1+A(n–1))。
Please write a function that computes the nth item of the sequence.
3. Please write a function that reads a string from the main function, computes,
and outputs its length.
4. Suppose users type in multiple words in the console. Words are separated by
spaces. The '#' sign is used to indicate the end of input. Please write a function
that converts the first letter of each word into uppercase. The input is handled
in the main function.
5. Please write a program that: reads a nonzero integer n from keyboard input,
computes the sum of each digit of n and outputs the sum if the sum is a one-
digit number. If the sum has multiple digits, the program should repeat the
above process until the sum has single digit.
For instance, the conversion process of n = 456 is as follows:
4 + 5 + 6 = 15
1+5=6
The output is 6
6. Please write a program that reverses n input numbers using pointers.
Requirements:
(1) The program should have only one main function.
(2) Write a child function for reversing numbers. Input/output of data should
be done in the main function.
5 Files: operations on external data
Main contents
– The concept of files
– Basic procedures of operating files using programs
– Categorization and functionality of file operation library functions
– Examples of file operation library functions
Learning objectives
– Understand the purpose of file storage
– Can create, read from, write to, and update a file
– Know sequential access of files
– Know random access of files
5.1 Introduction
A had been learning the C language and thought it was interesting. He was eager to
solve some practical problems with what he had learned.
One day, the class president asked him to compute the ranking of average grades
of his classmates in the midterm exam. He then used his programming knowledge
and completed the task quickly. His program asked users to input grade information
of every student in the class and then printed the sorted average grades onto the
screen.
However, the class president complained to A after trying the program, “I have
given you access to the electronic version of grades, but you didn’t use it. Instead,
your program asked me to input every grade on the keyboard. That was too tedious.
Also, your program merely printed the result on the screen. I had nothing left after I
closed it. Our school requires us to submit an electronic record. Your program is not
user-friendly enough, and I can’t use it.”
The class president’s complaint made A speechless. “How can a program read
electronic records of grades? We have only learned to read keyboard input. Besides,
how do I save the result to a data file?” he thought to himself.
To sum up, A’s question is: how should we fetch data from and save the result
to persistent storage automatically with programs?
As we all know, the purpose of programming is to process data as needed to com-
plete specific tasks. The data processing flow consists of data input, data processing,
and result output. Execution and testing of programs also involve data input and
https://doi.org/10.1515/9783110692303-005
236 5 Files: operations on external data
It was these two features of normal data input/output that made A’s program not user
friendly. In practice, we often have the following needs regarding data processing:
(1) Input: the amount of input data is large; the input data are always the same.
(2) Output: we need to inspect the results frequently; the output is too much to be
displayed on a screen without scrolling.
In these cases, we can save these data for easier inspection or repeated use. To save
data permanently in computers, we store them into external memory. Operating sys-
tems manage data in the external memory in the form of files. As a result, it is neces-
sary to learn file operations to complete programming tasks quickly and flexibly.
A file is an ordered set of correlated data. The name of a file is called filename. We
have encountered files in previous chapters many times. For example, we have
mentioned source files, object files, executable files, and library files (header files).
Files are a persistent form of data, and they make data sharing possible.
Depending on how data are stored, files in C can be divided into binary files
and text files.
Binary files, as the name indicates, store data in binary codes. For example, integer
5678 is stored as 00010110 00101110, which takes up 2 bytes in memory (the hexa-
decimal form of 5678 is 0x162E).
Although we can view binary files on screen, their contents are often garbled
characters because they are mostly nontext characters.
5.2 Concept of files 237
Text files are also called as ASCII code files. Each character in such a file is stored
as a 1-byte ASCII code on disks. For example, Figure 5.1 shows the storage format of
number 5678.
ASCII code files can be displayed as characters on screen. For example, source files
are also ASCII code files. We can read them because they are displayed as characters.
The difference between text files and binary files is that: text files are constructed
by characters, while binary files are constructed by bits. Note that both of them are
handled as “stream files” in the C language.
Term explanation
Stream files: C treats files as “data streams,” which are sequences of consecutive bytes with no
breaks. Such a structure is called a “stream file structure,” in which each byte is accessible. A
termination mark exists at the end of a file, which is similar to the string termination mark.
We do not bother figuring out data characteristics, types, and storage formats when process-
ing stream files. We merely access data in bytes. The analysis and processing of data are left to
be done by other programs. As a result, this file structure is more flexible and can better utilize
storage space.
(1) End-of-file (EOF) is the file termination mark. EOF is an integer symbolic con-
stant defined in header file as <stdio.h>, whose value is usually –1. It is worth
noting that EOF is only used for text files because –1 is also a valid character in
binary files.
(2) feof function is a function in the standard library, which is used to determine
whether we have reached the end of a file. It works for both binary files and
text files.
Files are regarded as data streams in C. There is also a file termination mark and a
function to determine whether we have reached the end. In practice, the internal
pointer of a file points the beginning of the stream by default when users open it
with programs. As users execute operations, the pointer can move to other positions
in the stream. Finally, we check whether the pointer points to the end of the file to
make sure we have read the entire file.
By now, we should have had a basic idea of files. How do we operate files in prac-
tice? Files are usually stored in an external medium (like disks) and brought to in-
ternal memory when needed. We call the process of data moving to memory from
disks “read” and the process of data moving to disks from memory “write.”
In operating systems, each file is identified by a unique filename. Computers
use filenames to read and write a file.
When we look for data in a file on the disk by ourselves, we must find the file
using its name, read data from it, and close it. We use the same steps to operate files
with programs. The three fundamental steps of accessing files with programs are:
(1) Opening the file
(2) Processing the file
(3) Closing the file
through the keyboard is then inputting data from the standard input file. Functions
like scanf and getchar are examples of such input.
ANSI defines standard input/output functions and uses them to read and write
files. Readers can refer to Appendix C of Volume 1 for details of these functions. We
shall analyze some of the most common library functions in subsequent sections.
As shown in the file operation flow introduced in Section 5.3, the internal memory
and the external memory must communicate with each other to implement read
and write operations of files. Ideally, we want such communication to be done si-
multaneously. However, the reality is cruel. Different components of computer
work at different speeds, so tasks are often completed at different times. To solve
this problem, we introduce the buffer system into the communication between the
internal and the external memory.
Memory
A buffer is a block of storage space in the internal memory, allocated and managed by the sys-
tem upon opening a file. The size of a buffer depends on the version of C. It usually is multiples
of 512 bytes.
When writing data to files in the external memory, we do not directly write to the external
memory. Instead, we write to the buffer. When the buffer is full or the file is closed, data in it
are automatically written to the external memory. It is the same for reading from a file. At first,
only one block of data is read into the buffer. When we read the data, we first look for them in
the buffer. If they exist, we simply fetch them from the buffer. Otherwise, we search for them in
the external memory. After finding the data we want, we read the data block in which they are
located in the buffer. Buffers can effectively reduce external memory accesses.
Reading and writing using buffers can better utilize disks. Standard C also uses a buffer
system.
240 5 Files: operations on external data
When using a buffer system, the system creates a buffer for each file opened.
Operations on files then become operations on buffers.
For programmers’ convenience, ANSI C defines a structure for information re-
lated to file buffers (such as filename corresponding to the buffer, operations al-
lowed on the file, size of the buffer, and location of the data being accessed in the
buffer). We can obtain information about file buffers by accessing this structure var-
iable. The type of this structure is FILE, which is defined in stdio.h (note: based on
what we have learned about header files, we must include this header file when
using FILE to operate files).
The information contained in FILE type is as follows:
Whenever a file is opened successfully, the operating system creates a FILE variable
for the file, allocates memory, and returns a pointer to it. The system stores infor-
mation about the file and the buffer into this FILE variable. Our program can use
the pointer to obtain file information and access the file, as shown in Figure 5.4.
Filename
FILE pointer
When a C program opens
When the file is a file,the operating
closed, the FILE system creates a FILE
FILE
structure variable structure variable for it
structure
is released and returns a pointer
pointing to it
As long as we have this file pointer, we can use file operation functions provided
by the system to operate the file without knowing details of the buffer. File operating
code is thus easier to write. Now we are going to study how to operate files.
We have introduced in Section 5.3 that programs operate file following three steps:
opening files, reading files, and closing files. There are corresponding library functions
for all these steps in ANSI C. We shall analyze these functions in the following sections.
The library function for opening file is fopen, whose detailed information is as follows:
– Prototype: FILE fopen (char *filename, char *mode)
– Functionality: allocate a file buffer for a file in the memory.
– Parameters:
filename: a string that contains the path and the name of the file to be opened
mode: a string indicating the mode of file opening.
– Return value: file pointer (NULL indicates that the file was not opened because
an exception happened)
Note: Beginners often ignore exceptions when programming. They often think that
the file is opened after calling fopen function and uses the returned file pointer di-
rectly. However, this is problematic. fopen do not always open files successfully.
Invalid filenames or not enough access privileges can lead to an exception in fopen.
It is recommended to check if the file is successfully opened after calling fopen. To
be more specific, we should check whether the returned file pointer is NULL before
actually accessing the file.
The mode parameter of fopen determines the mode in which the file is opened.
There are multiple modes, whose values and meaning are shown in Figure 5.5.
Note:
(1) The opened file can be either text file or binary file.
(2) A text file is represented by “t” (optional), while a binary file is represented by “b.”
Programming error
As shown in Figure 5.5, mode “w” always checks whether the file exists first, regardless of the
file being a text file or a binary file. If the file exists, the function deletes the existing file and
create a new one. As a result, we should be careful when using it. If we want to preserve the
original contents in the file, we should not use mode “w” because it will delete the contents
without any warnings.
File opening mode Meaning
242
"r" Open a text file in read-only mode, fail if the file doesn’t exist
Read-only
"rb" Open a binary file in read-only mode, fail if the file doesn’t exist
Open a text file in write-only mode, create a new file if the file doesn’t exist, delete and
"w"
create a new one if it exists
Write-only
Open a binary file in write-only mode, create a new file if the file doesn’t exist, delete
"wb"
and create a new one if it exists
"r+" Open a text file in read/write mode, fail if the file doesn’t exist
"rb+" Open a binary file in read/write mode, fail if the file doesn’t exist
Open a text file in read/write mode, create a new file if the file doesn’t exist, delete and
"w+"
create a new one if it exists
Read/write Open a binary file in read/write mode, create a new file if the file doesn’t exist, delete
"wb+"
and create a new one if it exists
5 Files: operations on external data
Open a text file in read/write mode, create a new file if the file doesn’t exist, append to
"a+"
the file if it exists
Open a binary file in read/write mode, create a new file if the file doesn’t exist, append
"ab+"
to the file if it exists
Append data to the end of a text file, create a new file if the file doesn’t exist, append if
"a"
it exists
Append
Append data to the end of a binary file, create a new file if the file doesn’t exist,
"ab"
append if it exists
Although it is not grammatically wrong, using the wrong file opening mode can lead to logic
execution errors. For example, when we use write mode “w” to open a file instead of using up-
date mode “r+,” the file contents will be deleted. In conclusion, we must determine the correct
file opening mode before accessing files.
Drive letter:\Path\Filename.Extension
Ex. 1: We are looking for the file c:\windows\system\config. If we are currently in the directory
c:\windows\, then the relative path is system\config, and the absolute path is c:\windows\sys-
tem\config.
Ex. 2:
fp=fopen("a1.txt","r");
This is a relative path with no path information. In this case, file a1.txt is in the current directory
(note: the current directory refers to the directory of the project which contains this program).
fp=fopen("d:\\qyc\\a1.txt","r");
This is an absolute path and file a1.txt is located in the directory qyc in D drive.
Note: we use “\\” instead of “\” because “\” should be escaped in strings.
Unlike opening files, there are many cases of reading and writing, so people created
a series of library functions for them, as shown in Figures 5.6 and 5.7.
Note: We read from and write to the current position of a file. The current position is
the position currently pointed to by the data read/write pointer. When a file is opened,
the pointer points to the beginning of the file; after we read or write a byte success-
fully, the pointer moves forward automatically (moves to the next byte).
Functionality Function Parameters
244
Read/write char * fgets (char *str, int num,FILE * fp) num: the number of characters being read
strings int fputs (char *str, FILE * fp) str: the address of the char array
int fread (void *buf,int size,int count,FILE * fp) count: the number of data entries
Read/write size: the length of a data entry
data blocks buf: the address of the buffer
int fwrite (void *buf,int size,int count,FILE * fp)
Analysis
1 //Read characters one by one from file
2 #include <stdio.h>
3 #include <stdlib.h>
4
5 int main(void)
6 {
7 char ch;
8 FILE *fp; //Define a FILE pointer fp
9 fp=fopen("file.txt","r"); //Open text file file.txt in read-only mode
10 if (fp==NULL) //Failed to open the file
11 {
12 printf("cannot open this file\n");
13 exit(0); //Call library function exit to terminate the program
14 }
15 ch=fgetc(fp); //Read a character and assign it to ch
16 while(ch!=EOF) //Check whether we have reached the end, equivalent to
(!feof(fp)) in this case
17 {
18 putchar(ch); //Output the character
19 ch=fgetc(fp); //Read a character and assign it to ch
20 }
21 fclose(fp); //Close the file
22 return 0;
23 }
Note: We use while(ch!=EOF) to determine whether we have reached the end of the file on line
16. This statement only works for files opened as text file. If we open a file in binary modes,
then we should use !feof(fp). Otherwise, we may wrongly consider a file to be completely read
when seeing value “–1.”
Term explanation
exit function: exit is declared in <stdlib.h>. It is used to terminate a program forcibly. When
there are input errors or the program cannot open a file, we can use this function to end the
program. The parameter of exit is passed to some operating systems so that other programs
can use it.
exit(0) means the program exits normally. In contrast, exit(1) indicates an exception (there
must be an exception as long as the argument is not 0, but we recommend using the macro
EXIT_FAILURE defined in stdlib.h to indicate the reason of exception. The macro is defined as 1
in the header file).
246 5 Files: operations on external data
Knowledge ABC What are the differences between exit() and return in C?
Exit function is used to exit the program and return to the operating system, while a return state-
ment merely returns to the caller from the function currently being executed. If we use return in
the main function, then the program terminates after return is executed and returns to the operat-
ing system. In this case, the return statement is equivalent to exit. However, one merit of exit is
that we can call it in other functions and use a search program to look for these calls.
Analysis
1 //Write the specified string into a file
2 #include <stdio.h>
3 char *s="I am a student"; //Specify the string to be written
4 int main(void)
5 {
6 char a[100];
7 FILE *fp; //Define file pointer fp
8 int n=strlen(s); //Compute length of s
9
10 //Open text file f1.txt in write mode
11 if ((fp=fopen("f1.txt","w"))!=NULL)
12 {
13 fputs(s,fp); //Write string pointed by s into file pointed by fp
14 }
15 fclose(fp); //Close the file pointed to by fp
16
17 //Open text file f1.txt in read-only mode
18 fp = fopen("f1.txt","r");
19 fgets(a, n+1, fp); //Read contents in file pointed to by fp into array a
20 printf("%s\n",a); //Print a
21 fclose(fp); //Close the file pointed to by fp
22 return 0;
23 }
Note: fgets(a, n+1, fp) on line 19 reads a string and stores it in array a. a is a character array de-
fined earlier. n+1 instructs the program to read n characters from the file pointed to by fp and
store them into a. These n characters are precisely string s. Because a string must be terminated
with “\0”, we use n+1 instead of n.
Think and discuss Is it necessary to check the result of file opening function?
Discussion: good programmers try their best to consider all possible error cases when program-
ming. In this example, we use a short string s for the convenience of demonstration, so it is fine
to write line 19. However, string s may be an extremely long string in practice. In this case, we
have to consider whether a is large enough to store the string in order to avoid out-of-bound
errors. Besides, we did not check the result of fopen when opening the file in read-only mode,
which is a risk in the program.
5.5 Operations on files using programs 247
Analysis
1 //Write data block into a file
2 #include "stdio.h"
3 #include "stdlib.h"
4
5 struct student //Define the structure
6 {
7 char name[15];
8 char num[6];
9 float score[2];
10 } stu;
11 int main(void)
12 {
13 FILE *fp1;
14 int i;
15
16 fp1=fopen("test.txt","wb");
17 if( fp1 == NULL) //Open file in binary write-only mode
18 {
19 printf("cannot open file");
20 exit(0);
21 }
22 printf("input data:\n");
23 for( i=0;i<2;i++)
24 {
25 //Input a row of record
26 scanf("%s%s%f%f",
27 stu.name,stu.num,&stu.score[0],&stu.score[1]);
28 //Write data block into the file, one row at a time
29 fwrite(&stu,sizeof(stu),1,fp1);
30 }
31 fclose(fp1);
32
33 //Open the file again in binary read-only mode
34 if((fp1=fopen("test.txt","rb"))==NULL)
35 {
36 printf("cannot open file");
37 exit(0);
38 }
39 printf("output from file:\n");
40 for (i=0;i<2;i++)
41 {
42 fread(&stu,sizeof(stu),1,fp1); //Read block from the file
248 5 Files: operations on external data
Program result:
input data:
xiaowang j001 87.5 98.4
xiaoli j002 99.5 89.6
output from file:
xiaowang j001 87.50 98.40
xiaoli j002 99.50 89.60
Programming error
After writing content into a file, we may need to read the file later. Sometimes, we may see gar-
bled characters in the file. This is due to the inconsistency between the format we used when
writing to the file and the format of the file operating function. In the example above, if we
change line 29 into fprintf, there will be an output error.
There is an old saying which goes “Timely return of a loan makes it easier to borrow
a second time.” We should return things we borrow from others quickly. If our credit
is good, then people are likely to help us when we need to borrow the second time.
In programs, dynamically allocated resources should follow this rule as well.
Otherwise, a memory leak may happen. In the worst cases, it will lead to results be-
yond our expectations. The FILE pointer in file operations is also a resource. We ob-
tain it by successfully calling fopen function. As a result, we have to return this
resource after using the file. The return here refers to closing the file. Readers may
have noticed that we always call fclose function after we are done with the file in
previous examples. fclose is the function we use to close files. It is defined as follows:
– Prototype: int fclose(FILE *fp)
– Functionality: it closes the file pointed to by the file pointer, handles the data
in the buffer, and releases the buffer eventually.
– Output: if an exception happens, the function returns a nonzero value; other-
wise, it returns 0.
Note: we should close a file in time after we use it. Otherwise, data may get lost.
Data are not written into the file until the buffer is full. If we terminate the program
when the buffer is not yet full, data in the buffer will be discarded.
5.5 Operations on files using programs 249
Analysis
1 //Write 10 record into data.txt
2 #include <stdio.h>
3 int main(void)
4 {
5 FILE *fp; //FILE is the file type
6 int i;
7 int x;
8
9 fp=fopen("data.txt","w"); //Open data.txt in text write mode ‘w’
10
11 for(i=1;i<=10; i++)
12 {
13 scanf("%d",&x);
14 fprintf(fp,"%d",x); //Output x into the file pointed to by fp
15 }
16 fclose(fp); //Close the file
17 return 0;
18 }
Program result: we can find the newly created file data.txt in the directory of our project after
the program terminates. We will see the 10 records read from keyboard input in it.
We have introduced the three steps of file operations in previous examples. Readers
may have noticed that we could only read the file from the very beginning to the
very end, one byte after another. Is it always acceptable in practice?
Apparently, such a rigid method is not always suitable in real life. Suppose we
have a file of student information, the records are stored in the order of student ID.
We wish to quickly locate a row using its index like we do with arrays. It is obvious
that we cannot do this with sequential access. In response to our needs, C provides
the fseek function that can relocate the file pointer. It is defined as follows:
– Prototype: fseek(FILE pointer, offset, beginning location)
– Functionality: relocate the file pointer. It moves the pointer by “offset” bytes
from the “beginning location” (Value of the beginning location: beginning of
the file is represented by SEEK_SET, whose value is 0; current location is repre-
sented by SEEK_CUR, whose value is 1; the end of the file is represented by
SEEK_END, whose value is 2).
– Return value: 0 is returned upon success, while –1 is returned upon a failure.
250 5 Files: operations on external data
Analysis
Code implementation:
1 //Read from specified location in a file: random access of files
2 #include "stdio.h"
3 #include "stdlib.h"
4
5 struct stu //Structure of student information
6 {
7 char name[10];
8 int num;
9 int age;
10 char addr[15];
11 } boy,*qPtr; //Define a structure variable boy and a structure pointer qPtr
12
13 int main(void)
14 {
15 FILE *fp;
16 char ch;
17 int i=1; //Skip the first i rows
18 qPtr = &boy; //qPtr points to the beginning address of boy
19
20 if ((fp=fopen("stu_list.txt","rb"))==NULL)
21 {
22 printf("Cannot open file!");
23 exit(0);
24 }
25 //Relocate the pointer to the beginning of the file
26 rewind(fp);
27 //Move the pointer by (i*structureSize) bytes
28 fseek(fp,i*sizeof(struct stu),0);
29 //Read the current row from the file, and store into address pointed to by qPtr
30 fread(qPtr, sizeof(struct stu),1,fp);
31 printf("%st%5d %7d %sn", qPtr->name,
32 qPtr->num, qPtr->age, qPtr->addr);
33 fclose(fp);
34 return 0;
35 }
Note: To make this program work, the file has to be written and opened in binary mode. For exam-
ple, we use fopen(“stu_list.txt”,“rb”) on line 20. Only in this case are the contents of the file bi-
nary data stored sequentially. Besides, we cannot use fseek(fp,i*sizeof(struct stu),0) on line 28 to
move the pointer if the file is not accessed in binary mode.
5.6 Discussion on file reading and writing functions 251
When checking the file after we write to it, sometimes we find nothing but garbled
characters. Sometimes, the binary data we read from a file are not what we have
expected. What happened behind the scene?
We shall briefly discuss this problem by introducing several combinations of
file open modes and file operating functions.
In this case, we read the file data.txt in binary mode, use fprintf to write data, and
use fscanf to read data from it.
33
34 //******* Using fscanf to read data ********
35 fscanf(fp,"%d",&x); //Read an int value into x
36 while (!feof(fp) ) //Check whether the file ends
37 {
38 printf("%d ",x);
39 fscanf(fp,"%d",&x);
40 }
41 fclose(fp); //Close the file
42 return 0;
43 }
Program result:
Input: 2 3 4 5 6 7
Output: 2 3 4 5 6 7
If we open data.txt manually, we will find that the contents are 234567, which can be
displayed normally. If we use text editors like EditPlus to open the file in hexadecimal
mode, we will find the bytes being ASCII values “32 0A 33 0A 34 0A 35 0A 36 0A 37 0A.”
In this case, we read the file data.txt in binary mode, use fwrite to write data and
use fread to read data from it. The code implementation can be obtained by replac-
ing the code segments in squares in case 1 with the code segments as follows:
b=5
b=6
b=7
If we manually open data.txt in the operating system, we will find garbled charac-
ters in it. Opening the file in hexadecimal mode, we will see the bytes “02 00 00 00
03 00 00 00 04 00 00 00 05 00 00 0006 00 00 00 07 00 00 00.” These bytes are
precisely the binary byte stream of integers 2 to 7, in which each integer takes up 4
bytes. They are displayed as garbled characters because they are not stored as
ASCII values. In the program above, however, the numbers can be correctly read
because the fread function does read them as binary integer data.
In this case, we read the file data.txt in binary mode, use fprintf to write data,
and use fscanf to read data from it. The code implementation is the same as in
the first case, except the code in the second square is replaced with the following
statements:
Program result:
Input:
234567
Output:
b=0a330a32
b=0a350a34
b=0a370a36
b=0a370a36
b=0a370a36
b=0a370a36
In this case, we read the file data.txt in binary mode, use fwrite to write data and
use fread to read data from it. The code implementation is the same as in the first
case, except the code in the first square is replaced with the following statements:
Program result:
Input:
234567
Output:
Output “7” infinitely
The contents of the data.txt file are the same as in case 2. Nonetheless, the program
runs into exception because fscanf cannot recognize binary bit stream correctly.
We used binary mode to read data in all 4 cases. Will the results be different if we
use text mode? It is not hard to infer from our analysis in these cases that the results
will be similar. Interested readers may try text mode in these 4 cases on their own.
Conclusion
When operating files, we should use matching functions for reading and writing. In this case, we
can guarantee the data are correctly recognized regardless of using binary mode or text mode.
Whether the generated file can be displayed correctly is determined by the writing function,
instead of the file opening mode. When we use fprintf, the file contains ASCII values, which can
be displayed normally. When we use fwrite, data are written into the file as a binary bit stream.
Whether they are normally displayed depends on whether they are valid ASCII values. We see
garbled characters because they are not ASCII values in most cases.
After designing an algorithm and writing the code, we need to use test data to test
the program in the debugging environment. Because we often find bugs in our pro-
grams, we need to rerun them and input test data repeatedly. In programs with
many input data, it takes a long time to type on the keyboard. Is there a better way
to do this? Here come files to save the day.
5.7 Debugging and I/O redirection 255
We can put input data in a file and read them with file reading functions and
write results into specified files with file writing functions. Based on the character-
istics of the test data, we should select suitable file operation functions. There are
two code templates for this process.
#include <stdio.h>
int main(void)
{
FILE *fp1, *fp2;
fp1=fopen("data.in","r"); //Open input file data.in in read-only mode
fp2=fopen("data.out","w"); //Open output file data.out in write-only mode
//We process our data here. Note that we should fscanf to read and fprintf to print
fclose(fp1);
fclose(fp2);
return 0;
}
This program simply uses basic file operations we have learned. Now we are going
to see a program using freopen function.
When a file is not accessible due to some reason, debugging information has to be printed to
the end of output with stderr. This is acceptable when we print to screen. However, it is not accept-
able when we write to files or write to other programs through pipes (a pipe is a buffer of fixed
size). Output to stderr will be displayed on the screen even if we redirect the standard output.
#include <stdio.h>
int main(void)
{
freopen("data.in", "r", stdin);//Redirect input from keyboard to data.in
freopen("data.out", "w", stdout);
//Redirect output from screen to data.out
fclose(stdin);
fclose(stdout);
return 0;
}
Using freopen is as simple as using fprintf and fscanf. Besides, we do not need to
modify our code because we use input/output redirection, which is more conve-
nient than the first template. Here is an example of redirection.
1 #include <stdio.h>
2 int main(void)
3 {
4 int a,b;
5
6 while(scanf("%d %d",&a,&b)!= EOF)
7 {
8 printf("%d\n",a+b);
9 }
10 return 0;
11 }
5.8 Summary 257
Program result:
56
11
^Z
1 #include <stdio.h>
2 int main(void)
3 {
4 int a,b;
5 //Redirect input, read data from in.txt under Debug directory of the
6 //current project
7 freopen("debug\\in.txt","r",stdin);
8 //Redirect output, write data to out.txt under Debug directory of the
9 //current project
10 freopen("debug\\out.txt","w",stdout);
11 while (scanf("%d %d",&a,&b)!= EOF)
12 {
13 printf("%d\n",a+b);
14 }
15 fclose(stdin); //Close the file
16 fclose(stdout); //Close the file
17 return 0;
18 }
Note:
(1) We read input data from in.txt under Debug directory of the current project. Before running
the program, we need to save our data “5 6” in in.txt (beware of the space between 5 and
6. Although the program is still valid if we omit the space, the result will not be what we
expected. Interested readers can try the program without the space, examine the result
and analyze why the result is different using what we have learned).
(2) We save output data to out.txt under Debug directory of the current project. After running the
program, we will find a out.txt file under Debug directory, which contains the number “11.”
5.8 Summary
Figure 5.8 shows main contents of this chapter and relations between them.
Concept
Characteristics: can be stored permanently
Meaning: we store data to be read into a file, use file reading functions to
I/O read them, and use file writing functions to write results into a specified file
Redirection Method 1:use fscanf and fprintf
Method 2: use freopen and fclose
5.9 Exercises
1. [Concept of files]
Which of the following statements is correct about files in C? ( )
A) File is constructed by a series of data. It must be a binary file.
B) File is constructed by a series of structures. It could be a binary file or a text
file.
C) File is constructed by a series of data. It could be a binary file or a text file.
D) File is constructed by a series of characters. It must be a text file.
2. [Opening a file]
Suppose we have the following code segment:
FILE *fp;
if( (fp=fopen("test.txt","w")) == NULL)
{printf("Failed to open file!");
exit(0);}
else
printf("File opened successfully!");
If the file test.txt does not exist and there is no other exception, which of the
following statements is wrong? ( )
A) The output is “Failed to open file!”
B) The output is “File opened successfully!”
C) The system will create a file with the specified name.
D) The system will create a text file for write operation.
3. [fprintf]
Suppose we have the following program.
#include <stdio.h>
int main(void)
{ FILE *f;
f=fopen("filea.txt","w");
fprintf(f,"abc");
fclose(f);
return 0;
}
If the content of filea.txt was originally: hello, then then content of it after run-
ning the program above will be ( )
A) abclo B) abc C) helloabc D) abchello
260 5 Files: operations on external data
#include <stdio.h>
int main(void)
{
FILE *fp;
int i, a[6]={1,2,3,4,5,6},k;
fp = fopen("data.dat", "w+");
fprintf(fp, "%d\n", a[0]);
for (i=1; i<6; i++)
{
fseek(fp, 0L, 0);
fscanf(fp, "%d", &k);
fseek(fp, 0L, 0);
fprintf(fp, "%d\n", a[i]+k);
}
rewind(fp);
fscanf(fp, "%d", &k);
fclose(fp);
printf("%d\n", k);
return 0;
}
5. [End of file]
Suppose fp points to a file and has reached the end of the file. What is the return
value of feof(fp) then? ( )
A) EOF B) –1 C) A nonzero value D) NULL
6. [File buffer]
The prototype of the function that reads a binary file is as follows: fread(buffer,
size,count,fp);What does buffer refers to?
A) Number of bytes in a memory block.
B) An integer variable which represents the number of bytes in the data to be
read
C) A file pointer pointing to the file to be read
D) The beginning address of a memory block, which represents the address of
the data to be read
5.9 Exercises 261
Fill in the tables in Figures 5.9 and 5.10 based on the following programs:
-1 0 1 2 3 a
value
"123abcDEF"
#include <stdio.h>
int main(void)
{
FILE * fp = NULL;
int temp;
fp = fopen("123.dat", "w");
while(scanf("%d",&temp)) //————①
{
fprintf(fp, "%d", temp); //————②
}
fclose(fp);
fp = fopen("123.dat", "r");
while (fscanf(fp,"%d",&temp) != EOF)
{
printf("%5d", temp);
}
fclose(fp);
262 5 Files: operations on external data
return 0;
}
#include<stdio.h>
int main(void)
{
FILE *fp;
char str[100];
int i=0;
if((fp=fopen(“test.txt”, " w " ))= =NULL)
{
printf(“Can’t open this file.\n”);
exit(0);
}
printf(“Input a string: \n”);
gets (str); //——————①
while (str[i])
{
if(str[i]>= ‘a’&&str[i]<=‘z’)
str[i]= str[i]-32 ;
fputc(str[i], fp); //——————②
i++;
}
fclose (fp);
fp=fopen(“test.txt”, "r" );
fgets(str, 100, fp); //——————③
printf(“%s\n”, str);fclose (fp);
return 0;
}
who failed at least one course into the file “bhg.txt.” and data of students who
passed all courses into the file “hg.txt.”
There is more than one method to add multiple files to a project. We shall introduce
one of them through the following example:
Example A C program consists of three files, namely testfile1.cpp, testfile2.cpp, and testfile3.cpp.
We wish to add them to the same project.
testfile1.cpp:
testfile2.cpp:
https://doi.org/10.1515/9783110692303-006
266 Appendix A Adding multiple files to a project
14 {
15 static int j=5; /*Define static variable j*/
16 return ( j+=i);
17 }
testfile3.cpp:
Terms:
– Internal function: an internal function is only accessible by functions in the same file. We
use keyword static to define internal functions. They are also called static functions.
– External function: if we define a function with keyword extern, then this function is an
external function. For example:
Other files can call the function reset. If we omit extern in a function definition, then the function
is by default external.
In the file where we make a call to an external function, we should use extern to indicate that
the function is external.
(1) Create a new project (suppose the name is test) in the IDE, as shown in Figure A.1
(2) Create a new file “testfile1.cpp” in the project test, as shown in Figure A.2.
(3) Create a new file “testfile2.cpp” in the project test, as shown in Figure A.3.
(4) Create a new file “testfile3.cpp” in the project test, as shown in Figure A.4.
(8) Execute “Build” command in the window of the main function to generate an
executable exe file, as shown in Figure A.8.
(9) Run the program in the window of the main function to obtain result.
Program result:
1 1 2 3 7
1 2 4 5 10
1 3 6 7 14
Appendix B Programming paradigms
Imperative Declarative
Functional Dataflow
Object-
Procedural oriented
Logic Constraint
......
......
https://doi.org/10.1515/9783110692303-007
274 Appendix B Programming paradigms
Start Prescription
Registration Payment
Inspection Pharmacy
Y N
Assay Assay End
Figure B.2: The first process of “going to the outpatient department of the university hospital”.
– Module: a module is a collection of statements that has its own name and can
complete specific tasks independently. The internal implementation of a module
is invisible from the outside. A module communicates with the outside through
information interfaces.
– Information interface: an information interface describes how other modules
and programs use this module. Information in an interface includes input/out-
put information.
In the earlier example, “prescription” and “assay results” are the interface informa-
tion. The prescription is used in several modules. We call data that are available to
all modules “global variables.”
B.1 Procedural programming 275
Procedural languages are imperative languages with the addition of child pro-
grams. Because all modern imperative languages have this feature, we often use these
two terms interchangeably. Terms like “procedure,” “child program,” “function,” and
“module” refer to the same thing in programming.
“Procedure-oriented” is a programming paradigm that focuses on modules.
In procedure-oriented programming, we use a top-down stepwise refinement devel-
opment method to divide a complex system into several independent child mod-
ules. Then we determine how these modules are assembled and how they interact
with each other (that is, how they call each other). After designing these child mod-
ules, we combine them together to get the final system. Each module is imple-
mented by fundamental structures like sequential, branch, and loop structure.
The procedure-oriented paradigm got its idea from sequences of computer com-
mands. It converts solutions to problems into conceptualized steps and then trans-
lates these steps into program instruction sets, in which instructions are listed in a
specific order.
A procedural program consists of three components, as shown in Figure B.3.
The calling rules of modules describe the execution order of modules. In a single
run of a program, the control is exchanged between the calling program and the
program being called, as shown in Figure B.4.
Data objects
Code modules
Figure B.3: Structure of a procedural program.
Caller Callee
Execute the
The caller
“procedure”
request a
“procedure”
A function call hands the execution of a program (usually a child program) over to
other modules and preserves the context of the calling program. After the program
being called terminates, it returns to the saved context.
The nature of procedure-oriented programming is dividing problems into mod-
ules. It solves practical problems based on the characteristics of problem-solving
with computers. A multimodule system does module calling following a predeter-
mined routine.
When the scale of such a system is large, it is difficult to modify it if we have
different needs later. In the hospital example above, if we want to move “assay” after
“payment,” we have to update many components of the flow. If the system is exten-
sive, it would be hard to maintain. Besides, if we put no restriction on global data,
errors in them will affect other components of the system. “Prescription” is the global
data in the hospital example. If the pharmacy does not have the medicine listed on
the prescription, exceptions will occur in many modules. A summarization of critical
issues in procedure-oriented programming is given in Figure B.5.
Let us take a look at a story of humans and animals. There is a cage, as shown
in Figure B.6. We can put at most one animal in the cage. If the cage is empty, the
hunter may put a monkey into it and inform the zoo; the farmer may put a pig into
it and inform the restaurant; the zoo would like to purchase the monkey, and the
restaurant would like to buy the pig. Can we simulate this process using a program? We
will find that we never know who is going to act first. In other words, we do not know
the calling order of modules, which is necessary for procedure-oriented programming.
In the procedural paradigm, modules are executed in a predetermined order and
in a flow-driven manner. The steps in the solution designed by programmers should
be executed one after another. The system structure depends on our task. A change
in one module may require changes in all related modules.
B.2 Object-oriented programming 277
In the example above, the execution order of modules depends on the state of
the system. Execution of modules is thus driven by events or messages. “Fetching or
putting animals” are events, and “informing” is a message. Figure B.7 shows an ab-
straction of this example. Procedural thinking does not work in such event-driven
problems, so people have to look for other programming paradigms. Therefore, ob-
ject-oriented programming comes to being.
INITIALIZA- COMPU-
READING WRITING END
TION TATION
FARMER COOK
CAGE
HUNTER ZOO
If we use the event-driven method to describe the hospital example, we will find
that the nature of modules in the system has changed. In Figure B.8, steps that
were initially carried out by patients are now operations carried out after informa-
tion exchange by departments of the hospital. The functionality extracted from the
problem is no longer the core. Instead, the focus has become entities like depart-
ments. The events in this example are the reception of specific certificates, such as
medicare card, registration form, and prescription. A patient must have a medicare
card and money to register. A prescription cannot be filled without the assay sheet
with payment proof. These certificates drive operations and information exchange
among entities.
Start
Registration
Room
Outpatient
Prescription department
Figure B.8: The second process of “going to the outpatient department of the university hospital”.
Entity Object
─ Certain attribute (data) ─ Data
─ Behavior (functionality) ─ Operations on data
Certificate Message
─ Drive operations of entities ─ Communication between
─ Enable information exchange objects
between entities ─ Control of objects
Entities in a hospital can work in parallel. However, modules are executed se-
quentially on computers with a single CPU. How do we execute these parallel oper-
ations on computers? The solution is to store messages in a queue and handle them
sequentially, as shown in Figure B.10. An event generates a message, which is re-
ceived by the system. The event scheduler fetches messages from the message
queue and triggers corresponding procedures. Because the CPU works at high
speed, it seems like the machine is doing multiple works simultaneously.
Consignment
Event
handler A
Request
…
message Dispatch Event
message handler X
“Object-oriented” is an idea that focuses on entities. Data and operations on them are
encapsulated into a single entity: an object. We summarize common features of similar
objects in a class. Most data of a class must be processed using methods in the same
class. Classes interact with the outside world through interfaces. Objects communicate
with each other through messages. Users determine the execution order of programs.
The original intention of introducing “object-oriented” is to isolate “interface”
from “implementation,” so that lower level changes do not affect upper level function-
ality. Object-oriented languages describe systems more naturally. It is easier to reuse
code and update our applications using these languages. Major features of object-
oriented languages include:
(1) Identifiability: basic components in the system can be identified as discrete
objects;
(2) Classifiability: objects with the same data structure and behaviors can form
their own class;
(3) Polymorphism: an object has a unique static type and multiple possible dynamic
types;
(4) Inheritance: data and operations can be shared among classes on different
levels.
The first three are the fundamental features, while the last one makes object-
oriented languages different from others. They (sometimes with dynamic binding as
well) make a strong expression ability possible. The components in an object-
oriented program are objects, each of which has its own attributes and behaviors.
Computation is done by creating new objects and communication between objects.
Function
Input Output
(Black box)
The major use cases of functional languages are in mathematical derivation and
parallel computing. They are suitable for solving mathematics problems of a limited
scale.
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
Functional languages require us to use functions. We can define different operations in the com-
putation as functions, and rewrite the process as follows:
(car 2 3 7 8 11 17 20)-> 2
(cdr 2 3 7 8 11 17 20)-> 3 7 8 11 17 20
We can combine them to obtain the third element of a list: (car (cdr (cdr List))). If the list is 2 3 7
8 11 17 20, then the result is 7.
Logic programming describes facts and makes rules for them. The process of design-
ing programs is constructing a proof. The facts refer to the relations between objects
and attributes. The rules describe relations between facts. The execution of a program
is the process of derivation based on the rules. Logic programming is completely dif-
ferent from other paradigms.
Logic programming uses logic as programming languages, and considers com-
putation as a programming technique of controlled derivation. Users only need to
write the logic of their programs, and the control part is left to the interpreter pro-
gram in the system. In conclusion, the programming process can be represented by
this formula: facts+rules = results.
In 1972, Alain Comerauer’s group invented the first logic programming lan-
guage, Prolog. Prolog is suitable for artificial intelligence programs, which include
expert systems (a program that generates a suggestion or answer using a sophisti-
cated model), natural language processing, theory proving (a program that gener-
ates new theories as an expansion of current ones), and some intelligence games.
Prolog is usually used with some other languages in projects, where logic opera-
tions are done in Prolog and components like computation and user interfaces are
implemented in other languages.
human(John)
mortal(human)
The user may check:
?-mortal(John)
B.4 Logic programming 283
There are many uses of “void type” in C. In different cases, void type has different
meanings.
1.void type
The type specifier is void. void type does not refer to a specific data type. Instead, it
is used when a function has no return value or to represent a generic pointer.
2.void-type functions
When a function is called, it usually returns a value to its caller. The value must
have a data type and should be specified in the function definition and declaration.
However, some functions do not return any value to their callers. These functions
can be defined as “void type.”
https://doi.org/10.1515/9783110692303-008
Index
Array 1, 3, 4, 6, 7, 9–16, 21, 23–26, 29, 30, 37, –pass by value 153–156, 159, 226
43, 45, 46, 48, 55, 64–66, 68–70, 76, –return statement 149, 155, 156, 159, 197, 213,
80–86, 88, 93, 95–97, 99, 100, 108, 115, 226, 248
126, 135, 162, 170, 175, 181, 183, 190, 192, –value-returning 162
205, 216, 231, 232, 246, 248, 251
–array element 1, 7–11, 17, 46, 48, 69, 71, 74, Global variable 202, 204–207, 209–216, 226,
75, 80, 84, 86–88, 136, 137, 171, 174, 227, 230, 268, 276
177, 183
–array name 6, 7, 9, 13, 15, 45, 46, 48, 70, 93, Input/output redirection 258
109, 110, 115, 162, 172, 181
–1-d array 17, 110, 161 Local variable 166, 189, 202, 204, 206–212,
–2-d array 10, 43, 48, 80, 81, 88, 110, 161 215, 216, 226
–index 4–9, 11, 24, 26, 45, 48, 93, 251
Memory unit 8, 13, 59–64, 66, 68, 69, 71,
Enumeration 95, 128, 130, 131 73, 76, 88, 90, 91, 105, 120, 135, 159,
–enumeration type 128, 129, 135, 136 164, 173, 178, 181, 190, 203, 209, 215,
–enumeration variable 128, 129, 131, 135, 136 227, 230
Module 96, 142, 144–146, 149, 155, 166, 198,
File 237–241, 243–246, 248, 251, 253, 203, 205, 226, 227, 276–281
256, 259
–binary file 238, 239, 243, 244, 260 Pointer 55, 61–65, 68–70, 74, 80, 81, 86–88,
–FILE pointer 242, 243, 250, 251, 257, 260 176, 181, 190, 240, 242
–FILE type 242 –address 6, 9, 12, 13, 15, 29, 43, 45, 48, 55, 57,
–text file 238, 239, 243, 244, 247, 260 59–61, 64, 97, 104, 105, 107, 110, 121, 135,
Function 33, 43, 44, 141, 144, 147, 148, 152, 154–157, 159, 164, 189
150–152, 157–159, 161–163, 170, 195, –null pointer 71, 73
198, 200, 201, 213, 221, 226–227, 268, –offset 55, 70–73, 103
277, 283 –pointer type 71, 72, 88
–argument 149, 151–156, 159, 161–163, 170, –reference by address 56–59, 62, 64, 69, 70,
173, 178, 181, 195, 226 74, 75, 81, 88, 89, 108, 115, 121, 134
–call by reference 159, 172, 176, 178, 181 –reference by name 55–58, 62, 75, 80, 88,
–call by value 153, 159, 163, 170, 172, 178, 181 108, 115, 121, 135
–function call 148–151, 153, 159, 161, 162, 166,
175, 179, 185, 226, 278 Redirection 260
–function declaration 149–151, 215, 227
–function definition 148–150, 158, 161, 162, String 10, 11, 13, 14, 37, 39, 40, 43, 44, 46,
201, 227, 268 49, 52, 53, 55, 76, 78, 79, 84, 85, 87, 90,
–function name 149, 156, 198, 226, 283 93, 126, 201, 235, 239, 246, 248, 260
–function type 149, 156, 158, 162, 226 Structure 95, 98, 99, 101, 104, 105, 107, 120,
–nonvalue-returning 162 122, 128, 134
–parameter 141, 149, 152–159, 161–163, 165, –member reference 134, 135
166, 170, 172, 173, 178, 181, 195, 202, 213, –reference member 109
226, 227 –structure array 100–103, 108, 109, 116, 187
–parameter passing 170, 171, 174, 177 –structure member 95, 99, 105, 107, 108
–pass by reference 154, 156, 159, 226 –structure name 98, 99
https://doi.org/10.1515/9783110692303-009
288 Index
–structure type 95, 98–101, 109, 116, 122, 134 Union 95, 120, 122, 128, 135
–structure variable 100–103, 107–109, 134, –union type 120, 122, 135
136, 168, 242 –union variable 121, 135