Вы находитесь на странице: 1из 245

1

Table of Contents

Unit 1: The Fundamentals of C 3


Unit 2: Variables and Operations 28
Unit 3: Conditions and Repetition 57
Unit 4: Modularity 98
Unit 5: Arrays & Structures 124
Unit 6: Dynamic Allocation 147
Unit 7: Sorting 175
Unit 8: Searching 194
Unit 9 Data Structures 198
Unit 10 C and Persistence 229

2
Unit 1

3
The Fundamentals of C

What is C?
C and Other Languges

Starting with this unit, you will explore the origins, syntax, and usage of the C languages\. A
study of C’s history is a worthy endeavor. For example you may fully appreciate the architectural
beauty of a brand new house that you have just seen completed. However, it is the underlying
structure of the edifice that will determine whether or not the building will still be standing in
fifty years. Likewise, to recognize the valid popularity of the C language you need to understand
its fundamental strengths.

What is C?
A history of the C language begins with a discussion of the UNIX operating system, since both
the system and most of the programs that run on it are written in C. However, C is not tied to
UNIX or any other operating system or machine. This code development environment has given
C a reputation for being a system programming language because it is useful for writing
compilers and operating systems. It can also write major programs in many different domains.

4
UNIX was originally developed in 1969, on what would now be considered a small DEC
PDP-7 at Bell Laboratories in New Jersey. UNIX was written entirely in PDP-7 assembly
language. By design, this operating system was intended to be “programmer-friendly”,
providing useful development tools lean commands, and a relatively open environment. Soon
after the development of UNIX, Ken Thompson implemented a compiler for a new language
called B.
At this point we need to digress to the origins and history behind Ken Thompson's B
language. A true C ancestry would look like this:

Algol 60 Designed by an international committee in early 1960


(Combined Programming Language) developed at both Cambridge and the
CPL
University of London in 1963
(Basic Combined Programming Language) developed at Cambridge, by
BCPL
Martin Richards, in 1967
B Developed by Ken Thompson, Bell Labs, in 1970
C Developed by Dennis Ritchie, Bell Labs, in 1972
The American National Standards Institute committee is formed for the
ANSI C
purpose of standardizing the C language, in 1983

Algol appeared only a few years after FORTRAN was introduced. This new language was more
sophisticated and greatly influenced the design of future programming languages. Its authors
paid careful attention to the regularity of syntax, modular structure, and other features associated
with high-level structured languages. Unfortunately, Algol never really caught on in the United
States, perhaps because of its abstractness and generality.

The inventors of CPL intended to bring Algols’s lofty intent down to the realities of an actual
computer. But like Algol, CPL was big. This made the language hard to learn and difficult to
implement and explains its eventual downfall. Still clinging to the best of what CPL had to offer,
BCPL 's creators wanted to boil CPL down to its basic good features.
Bringing the discussion back to the origins of B, when Ken Thompson designed the B language
for an early implementation of UNIX, he was trying to further simplify CPL, He succeeded in
creating a very sparse language that was well suited for use on the hardware available to him.
However, both BCPL and B may have carried their streamlining attempts a bit too far. They

5
became limited languages, useful only for certain kinds of problems.

For example, shortly after Ken Thompson implemented the B language, a new machine was
introduced, the PDP-11. UNIX and the B compiler were immediately transferred to this machine.
While the PDP-11 was larger than its PDP-7 predecessor, it was still quite small by today's
standards. It had only 24K of memory, of which the system used 16K, and one 512K fixed disk.
Some considered rewriting UNIX in B, but the B language was slow due to its interpretive
design. There was another problem: B was word-oriented while the PDP-11 was byte-oriented.
For these reasons, work began in 1971 on a successor to B, appropriately named C.

Dennis Ritchie is credited with creating C, which restored some of the generality lost in BCPL
and B. He accomplished this with his shrewd use of data types, while maintaining the simplicity
and computer contact that were the original design goals of CPL.

Many languages that have been developed by a single individual (C, Pascal, LISP, and APL)
have a cohesiveness missing from languages developed by large programming teams (Ada ,
PL/I, and Algol 68). In addition, a language written by one person typically reflects the author's
field of expertise. Dennis Ritchie was noted for his work in systems software computer
languages, operating systems, and program generators. With C having a genetic link to its
creator, one can quickly understand why C is a language of choice for systems software design.
C is a relatively low-level language that lets you specify every detail in an algorithm's logic to
achieve maximum computer efficiency. But C is also a high-level language that can hide the
details of the computer's architecture, thereby increasing programming efficiency.

Relationship to Other Languages


You may be wondering about C's relationship to other languages. Here is a possible continuum:
Direct Neural Path Communication
.
.
.
Artificial Intelligence
Operating System Command Languages
Problem-Oriented Languages
Machine-Oriented Languages
.

6
.
Assembly Language
Actual Hardware
Starting at the bottom of the continuum and moving upward, the languages go from the tangible
and empirical to the elusive and theoretical. The dots represent major advancements, with many
steps left out. Early ancestors of the computer, like the Jacquard loom (1805) or Charles Babbage
' s "analytical engine" (1834), were programmed in hardware. The day may well come when you
will program a machine by plugging a neural path communicator into a socket implanted into the
temporal lobe (language memory) or Broca’s area (language motor area) of the brain's cortex.

Assembly languages provide a fairly painless way for programmers to work directly with a
computer's built-in instruction set and go back to the first days of electronic computers.
Assembly languages forced you to think in terms of the hardware; you had to specify every
operation in the machine's terms. You were always moving bits in or out of registers, adding
.them, shifting register contents from one register to another, and finally storing the result in
memory. This was tedious and error-prone endeavor.

The first high-level languages, such as FORTRAN, were created as alternatives to assembly
languages. High-level languages were by design much more general and abstract, and they
allowed the programmer to think in terms of the problem at hand rather than in terms of the
computer’s hardware.

Unfortunately, the creators of high-level languages made the fallacious assumption that everyone
who had been driving a standard would always prefer driving an automatic! Excited about
providing ease in programming, they left out some necessary options. FORTRAN and Algol are
too abstract for system-level work; they are problem-oriented languages, the kind used for
solving problems in engineering, science, or business. Programmers who wanted to write
systems software still had to rely on their machine’s assembler.

Out of this frustration, a few systems software developers took a step backwards or lower in
terms of the continuum and created the category of machine-oriented languages. As you saw in
C’s genealogy, BCPL and B fit into this class of very low-level software tools. Theses languages

7
were excellent for a specific machine, but not much use for anything else-they were too closely
related to a particular architecture. The C language is one step above machine-oriented languages
yet is still a step below most problem-oriented languages. It is close enough to the computer to
give you great control over the details of an application’s implementation, yet far enough away
to ignore the details of the hardware. This is why C language is both a high- and low-level
language.

8
Characteristics of C

• Small Size
• Languge Command Set
• Speed
• Not Strongly Typed
• Structured Language
• Memory Efficiency
• Easy Interface to Assembly Language Routines
• Bit Manipiulation

All computer languages have a particular look. APL has its hieroglyphic appearance, assembly
language has its columns of mnemonics, Pascal has its easily read syntax, and then there’s C.
Many programmers new to C will find its syntax cryptic and perhaps intimidating. C contains
few of the familiar and friendly English-like syntax structure found in many other programming
languages. Instead, C has unusual-looking operators and a plethora of pointers. You will
quickly grow used to C’s syntax. New C programmers will soon discover a variety of language
characteristics whose roots stem back to its original hardware, software progenitor. The
following sections highlight the strengths of the C language.

Small Size
There are fewer syntax rules in C than in many other languages, and you can write a top-quality
C compiler that will operate in only 256K of total memory. There are actually more operators
and combinations of operators in C than there are keywords.

Language Command Set

9
As you would therefore expect, C is an extremely small language. In fact, the original C
language contained a mere 27 keywords. The ANSI C standard (discussed later in the Unit) has
an additional 5 reserved words. Borland C added 45 more keywords. This brings the total
keyword count to 77.

C does not include many of the functions commonly defined as part of other programming
languages. For example, C does not contain any built-in input and output capabilities, nor does
it contain any arithmetic operations (beyond those of basic addition or subtraction) or string-
handling functions. since any language lacking these capabilities is of little use, C provides a
rich set of library functions for input/output, arithmetic operations, and string manipulation.
This agreed-upon library set is so common that it is practically part of the language. One of the
strengths of C, however, is its loose structure, which enables you to recode these functions
easily.

Speed
The C code produced by most compilers tends to be very efficient. The combination of a small
language, a small .run-time system, and a language close to the hardware makes many C
programs run at speeds close to their assembly language equivalents.

Not Strongly Typed


Unlike Pascal, which is a strongly typed language, C treats data types somewhat more loosely.
This is a carryover from B, which was also an untyped language. This flexibility allows you to
view data in different ways.. For example, at one point in a
program the application may need to see a variable as a character. and yet for purpose of
upcasing (by subtracting 32) may want to see the same memory cell as the ASCII equivalent of
the character.

Structured Language
C includes all of the control structures you would expect of modern languages. This is
impressive when considering that C predated formal structured programming. C incorporates

10
for loops, if and if-else constructs, case (switch) statements, and while loops. C also enables you
to compartmentalize code and data by managing their scope. For example, C provides local
variables for this purpose and call-by-value for subroutine data privacy.

Support of Modular Programming


C supports the concept of separate compilation and linking, which allows you to recompile only
the parts of a program that have been changed during development. This feature can be
extremely important when you are developing large programs, or even medium-sized programs
on slow systems. Without support for modular programming, the amount of time required to
compile a complete program can make the change, compile, test, and modify cycle
prohibitively slow.
Easy Interface to Assembly Language Routines
There is a well-defined method of calling assembly language routines from most C compilers.
Combined with the separation of compilation and linking, this makes C a strong contender in
applications that require a mix of high-level and assembler routines. You can also integrate C
routines into assembly language programs on most systems.

Bit Manipulation
In systems programming, you often need to manipulate objects at the bit level. Because C's
origins are so closely tied to the UNIX operating system, the language provides a rich set of bit
manipulation operators.

11
Characteristics of C (continued)

Pointer Variables
Flexible Structures
Memory Efficiency
Special Function Libraries
Portability

Pointer Variables
An operating system must be able to address specific areas of memory. This capability also
enhances the execution speed of a program. The C language meets these design requirements by
using pointers. While other languages implement pointers, C is noted for its ability to perform
pointer arithmetic. For example, if the variable index points to the first element of an array
student _records[index+1] will be the address of the second element of student records.

Flexible Structures
In C all arrays are one-dimensional. Multidimensional arrangements are built from
combinations of these one-dimensional arrays. You can join arrays and structures (records) in
any manner, creating database organizations that are limited only by your ability.

Memory Efficiency
C programs tend to be very memory efficient for many of the reasons that they tend to be fast.
The lack of built-in functions saves programs from having to include support for functions that

12
are not needed by a particular application.

Portability
Portability is a measure of how easy it is to convert a program that runs on one computer or
operating system to run on another computer or operating system. Programs written in C are
currently among the most portable in the computer world. This is especially true for mini- and
microcomputers.

Special Function Libraries


There are many commercial function libraries available for all popular C compilers. There are
libraries for graphics, file handling, database support, screen windowing, data entry,
communications, and general support functions. By using these libraries, you can save a great
deal of development time.

13
Weaknesses of C

Not Strongly Typed


Lack of Run-Time Checking

There are no perfect programming languages. Different programming problem’s require different
solutions. It is the software engineer's task to choose the best language for a project. On any
project, this is one of the first decisions that you need to make, and it is nearly irrevocable once
you start coding. The choice of a programming language can also make the difference between a
project's success or failure. This section covers some of the weaknesses of the C language so that
you will have a better idea of when and when not to use C for a particular application.

Not Strongly Typed


The fact that it is not strongly typed is one of C's strengths but is also one of its weaknesses.
Technically, typing is a measure of how closely a language enforces the use of variable types
(for example, integer and floating point are two different types of numbers). In some languages,
you cannot assign one data type to another without invoking a conversion function. This protects
the data from being compromised by unexpected round offs
As mentioned, C will allow an integer to be assigned for a character variable or vice versa- This
means that you have to manage your variables properly. For experienced programmers, this task

14
presents no problem. However, novice program developers may want to remind themselves that
mismatched data type assignments can be the source of side effects.

A side effect in a language is an unexpected change to a variable or other item. Because C is a


weakly typed language, it gives you great flexibility to manipulate data. For example, the
assignment operator, =, can appear more than once in the same expression. This feature, which
you can use to your advantage, means that you can write expressions that have no definite value.
If C had restricted the use of the assignment and similar operators, or had eliminated all side
effects and unpredictable results, C would have lost much of its power and appeal as a high-level
assembly language.

Lack of Run-Time Checking


C's lack of checking in the run-time system can cause many mysterious and transient problems to
go undetected. For example, the run-time system would not warn you if your application
exceeded an array's bounds. This is one of the costs of streamlining a compiler for the sake of
speed and efficiency.

15
Why C?

Consistency between different platform


Standard Programming iscpline
ANSI C Standard

C's tremendous range of features - from bit-manipulation to high-level formatted I/O - and its
relative consistency from machine to machine have led to its acceptance in science, engineering,
and business applications. It has directly contributed to the wide availability of the UNIX
operating system on computers of all types and sizes.

Like any other powerful tool, however, C imposes a heavy responsibility on its users. C
programmers quickly adopt various rules and conventions in order to make their programs
understandable both to themselves and to others. In C, programming discipline is essential. The
good news is that it comes almost automatically with practice.

The ANSI C Standard


The ANSI (American National Standards Institute) committee has developed standards for the C
language. This section describes some of the significant changes suggested by the committee. A
number of these changes are intended to increase the flexibility of the language while others
attempt to standardize features previously left to the discretion of the compiler implement.

16
Previously, the only standard was The C Programming Language by Brian Kerning Ham and
Dennis Ritchie. This book was not specific on some language details, which led to a divergence
among compilers. The ANSI standard strives to remove these ambiguities. Although a few of the
proposed changes could cause problems for some previously written programs, they should not
affect most existing code.
The ANSI C standard provides an even better opportunity to write portable C code. The standard
has not corrected all areas of confusion in the language, however, and because C interfaces
efficiently with machine hardware, many programs will always require some revision when you
move them to a different environment. The ANSI committee adopted as guidelines several
phrases that collectively have been called the "spirit of C/7 Some of those phrases are
• Trust the programmer.
• Don't prevent the programmer from doing what needs to be done.
• Keep the language small and simple.

17
Basics of C Environment

• Four phases of C Program


o Edit
o Compile
o Link
o Execution

C programs typically go through four phases to be executed. These are: edit, compile, link, and
execute. The first phase consists of editing a file. This is accomplished with an editor program.
The programmer types a C program with the editor The program is then stored on a secondry
storage device such as a disk. The file created is named the ‘source file’ which is usually has ‘.c’
extension.
Next, the programmer gives the command to compile the program. The compiler translates the C
program into machine language code (object code), the generated file normally has ‘.obj’ file.
The next phase is called linking, C programs typically contain references to functions defined
elsewhere, such as in the standard libraries or in the private libraries of groups of programmers
working on a particular project. A linker links the object code with the code of functions to
produce an executable file.
Finally, the computer, under the control of its CPU, executes the program one instruction at a
time.

18
The Basic Components of a Program

Obtain information from some input source.


How this input is to be arranged and stored.
Instructions to manipulate the input
Report the results of the data manipulation.
Incorporates all of the fundamentals just listed, expressed
using good modular design, self-documenting code, and a
good indentation scheme.

In one of your introductory programming courses, you may have learned a problem-solution
format called an IPO diagram. IFO diagrams were a stylized approach to the
input/process/output-programming problem. The following list elaborates on these three
fundamentals and encapsulates the entire application development cycle. All programs must
address the following nine components;
1. Programs must obtain information from some input source.
2. Programs must decide how this input is to be arranged and stored.
3. Programs must use a set of instructions to manipulate the input. These instructions can be
divided into four major categories:
a. Single statements
b. Conditional statements
c. Loops
d. Subroutines
4. Programs must report the results of the data manipulation.

19
5. A well-written application incorporates all of the fundamentals just listed, expressed
using good modular design, self-documenting code (for instance, meaningful variable
names), and a good indentation scheme.

20
Your First C Program

/*
* Your first example C program.
*/
#include <stdio.h>
int main()
{
printf(“ HELLO World! “);
return 0;
}

The following C program will illustrate the basic components of a C application. You should
enter each example as you read about it
/*
* Your first example C program.
*/
#include <stdio.h>
int main()
{
printf(“ HELLO World! “);
return 0;
}

There is a lot happening in this short piece of code- First is the comment block:
/*
* Your first example C program.
*/
All well-written source code includes meaningful comments. A meaningful comment respects
the intelligence of the programmer while not assuming too much. In C, comments begin with

21
the /* symbols and end with */. The compiler ignores anything between these unique symbol
pairs. The next statement

#include <stdio.h>

represents one of C's unique features known as a preprocessor statement. A preprocessor


statement is like a precompiled instruction. In this case, the statement instructs the compiler to
retrieve the code stored in the predefined stdio.h file into the source code on the line requested,
stdio.h is called a header file. Header files can include symbolic constants, identifiers, and
function prototypes and have these declarations pulled out of the main program for purposes of
modularity. Following the #include statement is the main function declaration:
int main()
{
.
.
.

return 0;
}

All C programs are made up of function calls. Every C program must have one called main. The
main function is usually where program execution begins and ends with a return 0 from main. A
0 return value indicates that the program terminated without any errors. Following the main
function header is the body of the function itself. Notice the { and } symbol pairs. These are
called braces. Technically, braces are used to encapsulate multiple statements. These statements
may define the body for a function, or they may bundle statements that are dependent on the
same logic control statement, as is the case when several statements are executed based on the
validity of an if statement. In this example, the braces define the body of the main program.
The line

printf(" HELLO World! ");

is the only statement in the body of the main program and is the simplest example of an output
statement. The printf function has been previously prototyped in stdio.h. Because no other
parameters are specified, the sentence will be printed to the display monitor.

22
Your Second C Program
#include <stdio.h>
int main()
{
int yard, feet ,inch;

printf("Please enter the length to be converted: ");


scanf(“%d”,&yard);
while(yard > 0 )
{
inch=36*yard;
feet=3*yard;
printf("%d yard(s) = \n",yard);
printf("%d feet \n",feet);
printf("%d inches \n",inch);
printf("Please enter another length to be \n");
printf("converted (0 stops program): ");
scanf("%d",&yard);
}
printf(“>>> End of Program <<<”);
return 0;
}

At this point, you are probably waiting for a slightly more meaningful example. The following
program not only outputs information but also prompts the user for input.
/*
* This C program prompts the user for a specified length,
* in yards, and then outputs the value converted to
* feet and inches,
*/
#include <stdio.h>
int main()
{
int yard, feet ,inch;

printf("Please enter the length to be converted: ");


scanf(“%d”,&yard);
while(yard > 0 )
{
inch=36*yard;
feet=3*yard;
printf("%d yard(s) = \n",yard);
printf("%d feet \n",feet);
printf("%d inches \n",inch);
printf("Please enter another length to be \n");

23
printf("converted (0 stops program): ");
scanf("%d",&yard);
}
printf(“>>> End of Program <<<”);
return 0;
}

Data Declarations The first new thing in the program is the declaration of three variables:
int yard, feet, inch;

All C variables must be declared before they are used. The syntax for declaring variables in C
requires the definition of the variable's type before the name of the variable. One of the standard
data types supplied by the C language is integer. In this example, the integer type is represented
by the key word int, and the three variables yard, feet, and inch are denied.

User Input The next unconventional statement is used to input informal ton from the keyboard:

printf("Please enter the length to be converted: ");


scanf("%d",&yard);

The scanf function has a requirement that is called a format string, format strings define how the
input data is to be interpreted and represented internally. The "%d" function parameter instructs
the compiler to interpret the input as integer data (in Borland C++ an integer occupies 2 bytes

Address Operator In the previous listing, the integer variable yard was preceded by an
ampersand symbol, &. The & is known as an address operator. Whenever a variable is preceded
by this symbol, the compiler uses the address of the specified variable instead of the value stored
in the variable. The scanf function has been written to expect the address of the variable to be
filled.

24
Loop Structure One of the simplest loop structures to code in C is the while loop:
while(yard > 0)
{
.
.
}

This pretest loop starts with the reserved word while followed by a Boolean expression that
evaluates to cipher TRUE or FALSE. The opening brace, {, and closing brace, }, are optional
and are only needed when more than one executable statement is to be associated with the loop
repetition. Braced statements are sometimes referred to as compound statements, compound
blocks, or code blocks.
If you are using compound blocks, make certain that you use the agreed-upon brace style. While
the compiler doesn't care where you place the braces (skipped spaces/lines), programmers
reading your code will certainly appreciate the style and effort. You place opening loop braces at
the end of the test condition, and the closing brace in the same column as the first character in the
test condition.

Formatted Output The second program contains more complex printf function calls:
printf("%d yard(s) = \n", yard);
printf("%d feet \n",feet);
printf("%d inches \n",inch);
printf("Please enter another length to be \n");
printf("converted (0 stops program): ");

If you are familiar with the PL/I language developed by IBM, you will be at home with the
concept of a control string. Whenever a printf function is invoked to print not only literal strings
(any set of characters between double quotation marks) but also values, a format string is
required. The format string represents two things: a picture of how the output string is to look,
and the format interpretation for each of the values printed. Format strings are always between
double quotation marks.
The following table breaks down the first printf format string

("%d yard(s) = \n",yard)

25
into its separate components:
Control Action
%d Takes the value of yard, interprets it as an integer, and prints it
yard(s) After printing the integer yard, skips one blank space and then prints the literal
'yard(s) =’
\n Once the line is complete, executes a newline feed
, In this example, the comma separates the format string from the variable
name(s) used to satisfy all format descriptors (in this case, only one %d)

The next two-printf statements are similar in execution. Each statement prints a formatted integer
value, followed by a literal string, and ending with a new line feed. If you ran the program, your
output would look much like this:
Please enter the length to be converted: 4
4 yard(s) =
12 feet
144 inches
Please enter another length to be
converted (0 stops program): 0

The next table lists all of the output control symbols and describes how they can be used in
format strings

Sequences Function
\a Bell
\b Backspace
\f Formfeed
\n Newline
\r Carriage return
\t Tab
\v Vertical tab
\\ Backslash
\’ Single quote
\” Double quote
\? Question mark
\O Octal number
\H Hexadecimal number

26
The next table lists all of the C language value formatting controls. As you learn more about the
various C data types, you can refer back to these two tables for a reminder of how the various
controls affect input and output.

Control Data type


%c Character
%d Signed integer
%u Unsigned integer
%f Float in decimal form
%e Float in exponential form
%O Octal
%x Hexadecimal
%s String
%ld Long integer
%lf double
%Lf Long double

27
Unit 2

28
Variables and Operations

Identifiers
Keywords

What you have learned so far of C is only the tip of the iceberg. Starting with this unit, you will
explore the underlying structures of the C. The great stability of C comes from the standard data
types and the modifiers and operations that you can perform on them.

Identifiers
Identifiers are the names that you use to represent variables, constants, types, functions, and
labels in your program. You create an identifier by specifying it in the declaration of a variable,
type, or function. You can then use the identifier in later program statements to refer to the
associated item.

An identifier is a sequence of one or more letters, digits, or underscores that begins with a letter
or underscore. Identifiers can contain any number of characters, but only the first 32 characters
are significant to the compiler. (Other programs that read the compiler output, such as the Linker,
may recognize even fewer characters.)

29
C is case sensitive. This means that the compiler considers uppercase and lowercase letters to be
distinct characters. For example, the compiler sees the variables MAX and max as two unique
identifiers representing different memory cells. This feature enables you to create distinct
identifiers that have the same spelling but different cases for one or more of the letters.

The selection of case can also help a programmer understand your code. For example, identifiers
declared in include header files are often created with only uppercase letters. Because of this,
whenever you encounter an uppercase identifier in the source file, you can assume that
identifier's definition is in an include header file.

Although it is syntactically legal, you should not use leading underscores in identifiers that you
create. Often, identifiers beginning with an underscore can cause conflicts with the names of
system routines or variables, and produce errors. As a result, programs that contain names
beginning with leading underscores are not guaranteed to be portable.
Here are some sample identifiers:
k
count
templ
reservations_planel
fathom_6_ft

See if you can determine why the following identifiers are illegal:

1st_place
#1bs
action_taken!

The first identifier is illegal because it begins with a decimal number. The second identifier
begins with a # symbol, and the last identifier ends with an illegal character.
Since uppercase and lowercase letters are considered distinct characters, each of the following
identifiers is unique:
SIZE
size
Size
siZe

30
The C compiler's case sensitivity can create tremendous headaches for the novice C programmer.
For example, trying to reference the printf function, when it was typed PRINTF, will invoke
unknown identifier complaints from the compiler. In Pascal, however, a writeln is a WRITELN
is a WriteLn.

Keywords
Keywords are predefined identifiers that have special meanings to the C compiler. You can use
them only as denied. Remember, the name of a program identifier cannot have the same spelling
and case as a C keyword.
You cannot redefine keywords. However, you can specify text that can be substituted for
keywords before compilation by using C preprocessor directives.

31
Standard C Data Types

char
int
float
double
enum
void
unsigned modifier
long modifier
When you write a program, you are working with some kind of information that you can usually
represent by using one of the seven basic C types: text or char, integer values or int, floating-
point values or float, double floating-point values or double, enumerated or enum, valueless or
void, and pointers.
• Text (data type char) is made up of single characters (a, Z, ?, 3) and strings ("He who
has an ear to hear, let him hear"), usually, 8 bits, or 1 byte, with the range of 0 to 255.
• Integer values are those numbers you learned to count with (1, 2, 7, -45, and 1345),
usually, 16 bits wide, 2 bytes, or 1 word, with the range of -32,768 to 32,767.
• Floating-point values are numbers that have a fractional portion such as π (3.14159),
and exponents (7.563X1021). These are also known as real numbers (usually, 32 bits,
4 bytes, or 2 words, with the range of 3.4E-38 to 3.4E+38).
• Double floating-point values have an extended range (usually, 64 bits, 8 bytes, or 4
words, with the range of 1.7E-308 to 1.7E+308).
• Enumerated data types allow for user-defined types.
• The type void signifies values that occupy 0 bits and have no value. You can also use
this type to create generic.

32
• The pointer data type doesn't hold information as do the other data types. Instead,
each pointer contains the address of the memory location holding the actual data.

Characters
Every language uses a set of characters to construct meaningful statements. For instance, all
books written in English use combinations of 26 letters of the alphabet, the 10 digits, and the
punctuation marks. Similarly, C programs are written with a set of characters consisting of the 26
lowercase letters of the alphabet,
abcdefghijklmnopqrstuvwxyz

the 26 uppercase letters of the alphabet,


ABCDEFGHIJKLMNOPQRSTUVWXYZ

the 10 digits,
0123456789

and the following symbols:


+-*/=,._:;?\"'~|!#%$&()[ ]{}A@

C also use the blank space, sometimes referred to as white-space. Combinations of symbols,
with no intervening blank space, are also valid C and C++ characters. In fact, the following
code is a mixture of valid C and C++ symbols:
++ -- == && || << >> >= <= += -= *= /= ?: :: /* */ //
The following C program illustrates how to declare and use char data types:
/*
* AC program demonstrating the char data type and showing
* how a char variable can be interpreted as an integer.

*/
#include <stdio.h>
int main()
{
char uppercase_A='A' , lowercase_a='a';
printf(“The character \‘%c \‘ has a decimal ASCII" \
" value of %d \n" ,uppercase_A,uppercase_A) ;
printf(“The ASCII value represented in hexadecimal"\

33

is %X\n”,uppercase_A);
printf(“If you add sixteen will you get \‘%c\’\n”,
uppercase_A+16);
printf(“The calculated ASCII value in hexadecimal” \
" is %X\n",(uppercase_A+16));
printf(“The character \’%c\’ has a decimal ASCII" \
“value of %d\n",lowercase_a,lowercase_a);
return 0;
}

The output from the program looks like this:

The character 'A' has a decimal ASCII value of 65


The ASCII value represented in hexadecimal is 41
If you add sixteen will you get 'Q'
The calculated ASCII value in hexadecimal is 51
The character 'a' has a decimal ASCII value of 97

The %X format control instructs the compiler to interpret the value as a hexadecimal number.

Three Integers
C actually supports three types of integers. Along with the standard type int, the compiler
supports short int and long int. These are most often abbreviated to short and long. Since the C
language is so tied to the hardware, the actual sizes of short, int, and long depend upon the
implementation. However, a variable of type short will not be larger than one of type long.
Borland C allocates 2 bytes for both types short and int. The type long occupies 4 bytes of
storage.

Unsigned Modifier
C also allows you to declare certain types as unsigned. Currently, you can apply the unsigned
modifier to four types: char, short int, int, and long int. When one of these data types is modified
to be unsigned, you can think of the range of values it holds as representing the numbers
displayed on a car odometer. An automobile odometer starts at 000..., increases to a maximum of
999..., and then recycles back to 000 it also only displays positive whole numbers. In a similar
way, an unsigned data type can hold only positive values from 0 to the maximum number that

34
can be represented.

For example, suppose that you are designing a new data type called tiny, and decide that tiny
variables can hold only 3 bits. You also decide that the data type tiny is signed by default. Since
a variable of type tiny can only contain the bit patterns 000 through 111 (or 0 to 7 decimal), and
you want to represent both positive and negative values, you have a problem. You can't have
both positive and negative numbers in the range 0 to 7 because you need one of the 3 bits to
represent the sign of the number. Therefore, tiny's range is a subset. When the most significant
bit is 0, the value is positive. When the most significant bit is 1, the value is negative. This gives
a tiny variable the range of -4 to +3, as shown in the next table.

Unique Combinations of O's and 1's Decimal Equivalent


000 +0
001 +1
010 +2
011 +3
100 -1
101 -2
110 -3
111 -4

However, applying the unsigned data type modifier to a tiny variable would yield a range of 0 to
7, since the most significant bit can be combined with the lower 2 bits to represent a broader
range of positive values instead of identifying the sign of the number see next table.

Unique Combinations of 0's and 1's Decimal Equivalent


000 +0
001 +1
010 +2
011 +3
100 +4
101 +5
110 +6
111 +7

This simple analogy holds true for any of the valid C data types defined to be of type unsigned-
The storage and range for the fundamental C data types are summarized in the following tables.

Fundamental Type Storage and Range of Values

35
Type Storage Range of Values (Internal)
char 1 byte -128 to 127
int 2 byte -32,768 to 32,767
short 2 bytes -32,768 to 32,767
long 4 bytes -2,147,483,648 to 2,147,483,647
unsigned char 1 byte 0 to 255
unsigned int 2 bytes 0 to 65,535
unsigned short 2 bytes 0 to 65,535
unsigned long 4 bytes 0 to 4,294,967,295
float 4 bytes 3.4E-38 to 3.4E+38
double 8 bytes 1.7E-308 to l.7E+308
long double 10 bytes 3.4E-4932 to 1.1E+4932
pointer 2 bytes (near,_cs,_ds,_es,_ss pointers)
pointer 4 bytes (far,huge pointers)

Valid Data Type Modifier Abbreviations

Type Modifier Abbreviations


signed char Char
signed int signed, int
signed short int short, signed short
signed long int long, signed long
unsigned char no abbrv.
unsigned int unsigned
unsigned short int unsigned short
unsigned long int unsigned long

Floating Point
C uses the three floating-point types: float, double, and long double. While the ANSI C standard
does not specifically define the values and storage to be allocated for each of these types, the
standard does require each type to hold a minimum of any value in the range 1E-37 to 1E+37. As
you see in previous table 6-4, the Borland C++ environment has greatly expanded upon this
minimum requirement. Most C compilers have always had the types float and double. The ANSI
C committee added the third type long double. Here are some examples of floating-point
numbers:

float body_temp = 98.6;


double my_balance;
long double IRS_balance;

36
You can use long double on any computer, even one that has only two types of floating-point
numbers. However if the computer does not have a long double data type the data item will have
the same size and storage capacity as a double.

The following C program illustrates how to declare and use float variables:
//
// A C program demonstrating use of the float data type.

#include <stdio.h>
int main()
{
float float1 = 3601.234, float2 = 0.0028, float3 = -142.1;

printf(“\t\t %f\t”, float1);


printf(“%e\n\n”, float1);

printf(“\t\t %f\t”, float2);


printf(“%e\n\n”, float2);

printf(“\t\t %f\t”, float3);


printf(“%e\n\n”, float3);

return(0);
}

The output looks like this:

3601.233887 3.601234e+03
0.00028 2.800000e-03
-142.100006 -1.421000e+02

Enumerated
When an enumerated variable is defined, it is associated with a set of named integer constants
called the enumeration. The variable can contain any one of the constants at any time, and you
can refer to the constants by name. For example, the definition
enum tank__pressure{ OK,
LOW,
GULP=5 } bills_tank;

37
creates the enum. type of tank_pressure, the enum constants of OK, LOW, and GULP, and the
enum variable of bills_tank. All the constants and variables are of type int, and each constant is
automatically provided a default initial value unless another value is specified. In the preceding
example, the constant name OK has the int value 0 by default since it is the first in the list and
was not specifically overridden. The value of LOW is 1 since it occurs immediately after a
constant with the value 0. The constant GULP, was specifically initialized to the value 5. If
another constant were included after GULP, it would have the int value of 6.

Having created tank_pressure, you can later define another variable, chriss_tank, as follows:

enum tank_pressure chriss_tank;


After this statement, it is legal to say
bills_tank = OK;
chriss_tank = GULP;

which will place the value 0 into the variable bills_tank, and the value 5 into the variable
chriss_tank.

One common mistake is to think that tank_pressure is a variable. It is a "type" of data that you
can use later to create additional enum variables, such as chriss_tank.

Since the name bills_tank is an enumeration variable of type tank_pressure, you can use
hills_tank on the left of an assignment operator and it can receive a value. This occurred when
the enema constant OK was explicitly assigned to it. The names OK LOW, and GULP are names
of constants; they are not variables and you cannot change their values. You can perform tests on
the variables in conjunction with the constants. The following C program shows a complete
program that uses the preceding definitions:
/*
* AC program demonstrating the use of enumeration variables
*/
#include <stdio.h>
int main()
{
enum tank_pressure { OK,
LOW,

38
GULP=5 } bills_tank;
enum tank_pressure chriss_tank;
bills_tank = OK;
chriss_tank = GULP;
printf("The value of bills_tank is %d\n”,bills_tank);
if (chriss_tank == GULP)
printf("The value of chriss_tank is %d\n”,chriss_tank);
if (bills_tank == chriss_tank)
printf("bills_tank equals chriss_tank");
else
printf("bills_tank does not equal chriss_tank");
return(0);
}

39
Constant Data

const Modifier
#define Constants

const Modifier
At times, you may need to use a value that docs not change throughout the program. Such a
quantity is called a constant. For example, if a program deals with the area and circumference of
a circle, it will frequently use the constant value pi=3.14159. In a financial program, an interest
rate might be a constant. In such cases, you can improve the readability of the program by giving
the constant a descriptive name. Using descriptive names can also help prevent errors. Suppose
that you use a constant value (not a constant variable) at many points throughout the program.
Suppose also that you type the wrong value at one or more of these points. If the constant has a
name, the compiler would then detect a typographical error because you probably didn't declare
the incorrect name.

Suppose that you are writing a program that repeatedly uses the value π. You might think that
you should declare a variable called pi with an initial value of 3.14159. However, the program
should not be able to change the value of a constant. For instance, if you inadvertently wrote pi
to the left of an equal sign, the value of pi would be changed, causing all subsequent calculations

40
to be in error. C provides mechanisms that prevent such errors from occurring-that is, you can
establish constants whose values cannot be changed.
In C, you declare a constant by writing const before the keyword (for instance, int., float, double)
in the declaration. For example:
const int MAX=9,INTERVAL=15;
const float rate=0.7;
int indexed=0,count=10,obj ect:
double distance=0.0,velocity;

Because a constant cannot be changed, it must be initialized in its declaration. The int constants
MAX and INTERVAL are declared with values 9 and 15, respectively. The constant rate is of
type float and has been initialized to 0.7. In addition, the int (nonconstant) variables index, count,
and object have been declared. Initial values of 0 and 10 have been established for index and
count, respectively. Finally, distance and velocity have been declared to be (nonconstant)
variables of type double. An initial value of 0.0 has been established for distance.

You use constants and variables in the same way in a program. The only difference is that you
cannot change the initial values assigned to the constants that is, the constants are not lvalues;
they cannot appear to the left of an equal sign.

Normally, the assignment operation assigns the value of the right-hand operand to the storage
location named by the left-hand operand. Therefore, the left-hand operand of an assignment
operation (or the single operand of a unary assignment expression) must be an expression that
refers to a modifiable memory location.

Expressions that refer to memory locations are called lvalue expressions. Expressions referring
to modifiable locations are modifiable lvalues. One example of a modifiable lvalue expression is
a variable name declared without const.

#define Constants
C provide another method for establishing constants-the #define compiler directive. Suppose that
you have the following statement at the beginning of a program:

41
#define VOLUME 10

The form of this statement is #define followed by two strings of characters separated by spaces.
When the program is compiled, several passes are made through it. First, the compiler
preprocessor carries out the #include and #define directives. When the preprocessor encounters
the #define directive, it replaces every occurrence of VOLUME in the source files with the
number 10.

In general, when the preprocessor encounters a #define directive, it replaces every occurrence
of the first string of characters (VOLUME) in the program with the second string of characters
(10). Additionally, no value can be assigned to VOLUME because it has never been declared as
a variable. As a result of the syntax, VOLUME has all the attributes of a constant. Note that the
#define statement is not terminated by a semicolon. If a semicolon followed the value 10, every
occurrence of VOLUME would be replaced with 10;. The directive replaces the first string with
everything in the second string.

The keyword const and the #define compiler directive, in many programs, the action of each of
these two methods is essentially the same. On the other hand, the use of the modifier keyword
const results in a "variable" whose value cannot be changed.

42
Data Type Conversion

Implicit Conversion
Explicit Conversion

In the programs so far, the variables and numbers used in any particular statement were all of the
same type-for example, int or float. You can write statement that performs operations involving
variables of different types. These operations are called mixed mode operations. Unlike some
other programming languages, C performs automatic conversions from one type to another.
Data of different types are stored differently in memory. Suppose that the number 10 is being
stored. Its representation will depend upon its type; that is, the pattern of 0’s and 1’s in memory
will be different when 10 is stored as an int or when it is stored as a float.
Suppose that the following operation is executed,

float_result = float_value2 * int_value;

where both float_result and float_value2 are of type float, and the variable int_value is of type
int. The statement is therefore a mixed mode operation.

When the statement is executed, the value of int_value will be converted into a floating-point
number before the multiplication takes place. The compiler recognizes that a mixed mode
operation is taking place. Therefore, it generates code to perform the following operations. The

43
integer value assigned to int_value is read from memory. This value is then converted to the
corresponding float value, which is then multiplied by the real value assigned to float_value2,
and the resulting float value is assigned to float_result. In other words, the compiler performs the
conversion automatically. Note that the value assigned to int_value is unchanged by this process
and remains of type int.

There is a hierarchy of conversions, in that the object of lower priority is temporarily converted
to the type of higher priority for the performance of the calculation. Here is the hierarchy of
conversions, from highest to lowest priority:
double
float
long
int
short
For example, the type double has a higher priority than type int. When a type is converted to one
that has more significant digits, the value of the number and its accuracy are unchanged.

Look at what happens when a conversion from type float to type into takes place. Suppose that
the variables int_value1 and int_value2 have been defined to be of type int, while float_value and
float_result have been defined to be of type float. Consider the following sequence of statements:
int_value1 = 3;
int_value2 = 4;
float_value = 7.0;
float_result = float_value + int_valuel/int_value2;

The division of int_value1/int_value2 is not a mixed mode operation; instead, it represents the
division of two integers, and its result is zero since the fractional part, 0.75 in this case, is
discarded when integer division is performed. Therefore, the value stored in float_result is 7.0.

What if int_value2 had been defined to be of type float? In this case, float_result would have
been assigned the floating-point value 7.75, since the division of int_valuel/int _value2 was a
mixed mode operation. Under these circumstances, the value of int_value1 is temporarily
converted to the floating-point value 3.0, and the result of the division is 0.75. When added to
float_value, the result is 7.75.

44
The type of the value to the left of the equal sign determines the type of the result of the
operation. For example, suppose that float_x and float_y were declared to be of type float and
int_result was declared to be of type int. Consider the following statements:

float_x = 7.0;
floaty = 2.0;
int_result = 4.0 + float_x/float_y

The result of the division float_x/float_y is 3.5; when this is added to 4.0, the floating-point value
generated is 7.5. However, this value cannot be assigned to int_result because int_result is of
type int. The number 7.5 is therefore converted into an integer. When this is done, the fractional
part is truncated. The resulting whole number is converted from a floating-point representation to
an integer representation, and the value assigned to int_result is the integer number 7.

Explicit Type Conversions Using the Cast Operator


You have seen that the C compiler automatically changes the format of a variable in mixed mode
operations using different types. How ever, under certain circumstances, type conversions would
be desirable although automatic conversion is not performed. For those occasions, you must
specifically designate that a change of type is to be made. These explicit specifications also
clarify to other programmers the statements involved. The C language provides several
procedures that allow you to indicate that type conversion must occur.

One of these procedures is called the cast operator. Whenever you want to change the format of
a variable temporarily, you simply precede the variable's identifier with the type (in parentheses)
that you want it converted to. For example, if int_value1 and int_value2 were defined as type int,
and float_value and float_result were defined as type float, the following three statements would
perform the same operation:

float_result = float_value + (float)int_value1/int_value2;


float_result = float_value + int_valuel/(float)int_value2;
float_result = float_value + (float)int_value1/(float)int_value2;
All three statements would perform a float conversion and division of the variables int_value1
and int_value2. Due to the usual rules of mixed mode arithmetic, if either variable is cast to type

45
float, a float division occurs. The third statement explicitly highlights the operation to be
performed.

46
OPERATORS

Bitwise Operators
Increment and Decrement
Arithmetic Operators
Assignment Operator
Relational and Logical Operators
Conditional Operator

C has many operators not found in other languages. These include bitwise operators, increment
and decrement operators, conditional operators, the comma operator, and assignment and
compound assignment operators.

Bitwise Operators
Bitwise operators treat variables as combinations of bits rather than as numbers. They are useful
for accessing the individual bits in memory, such as the screen memory for a graphics display.
Bitwise operators can only operate on integral data types, not on floating-point numbers. Three
bitwise operators act just like the logical operators, but they act on each bit in an integer. These
are the AND &, OR |, and XOR ^. An additional operator is the one's complement ~, which
simply inverts each bit.

AND
The logical AND operation compares two bits. If both bits are a 1 the result is a 1. Note that this
is different from binary addition, where the comparison of two 1 bits results in a sum flag set to 0
and the carry flag set to 1.

47
Logical AND

Bit 0 Bit 1 Result


0 0 0
0 1 0
1 0 0
1 1 1

Very often, the AND operation is used to select out, or mask, certain bit positions.

OR
The logical OR operation compares two bits and generates a 1 result if either or both bits are a 1.
The OR operation is useful for setting specified bit positions.
Logical OR

Bit 0 Bit 1 Result


0 0 0
0 1 1
1 0 1
1 1 1

XOR
The exclusive OR operation (XOR) compares two bits and returns a result of 1 only when the
two bits are complementary. This logical operation can be useful when you need to complement
specified bit positions, as with computer graphics applications.
Exclusive OR (XOR)

Bit 0 Bit 1 Result


0 0 0
0 1 1
1 0 1
1 1 0

The following example uses these operators with the hexadecimal and octal representation of
constants. The bit values are shown for comparison.
0xFl & 0x35 yields 0x31 (hexadecimal)
0361 & 0065 yields 061 (octal)
11110011 & 00110101 yields 00110001 (bitwise)

48
0xFl | 0x35 yields 0xF5 (hexadecimal)
0361 | 0065 yields 0365 (octal)
11110011 | 00110101 yields 11110111 (bitwise)

0xFl ^ 0x35 yields 0xC4 (hexadecimal)


0361 ^ 0065 yields 0304 (octal)
11110011 ^ 00110101 yields 00000000 11000110 (bitwise)

~0xFl yields 0XFF0E (hexadecimal)


~0361 yields 7777416 (octal)
~11110011 yields 11111111 00001100 (bitwise)

Left Shift and Right Shift


C incorporates two shift operators: the left shift, <<, and the right shift, >>.The left shift moves
the bits to the left and sets the rightmost bit (least significant bit) to 0. The leftmost bit (most
significant bit) shifted out is discarded.

With unsigned int numbers, shifting the number one position to the left and filling the LSB with
a 0 will double the number's value. The following C code demonstrates how you would code
this:

unsigned int valuel = 65;


value1 = value1 << 1;
printf(“%d”, valuel);

In memory, examining the lower byte, you would see the following bit changes performed:

<< 0100 0001 (65 decimal)


1000 0010 (130 decimal)

The right shift operator, >>, moves bits to the right. The lower order bits shifted out are
discarded. Halving an unsigned int number is as simple as shifting the bits one position to the
right, filling the MSB position with a 0. A C-coded example would look similar to the preceding
example:

unsigned int valuel = 10;


valuel = value1 >> 1;
printf("%d",value1);

49
Examining just the lower byte of the variable valued would reveal the following bit changes:

>> 0000 1010 (10 decimal)


0000 0101 (5 decimal)

Increment and Decrement


Adding one to or subtracting one from a number is so co`mmon in programs that C has a special
set of operators to do this. They are the increment ++ and decrement - - operators.

You must place the two characters next to each other without any whitespace. You can only
apply them to variables, not to constants. Instead of coding

Value1 = valuel +1;

you can write

value1++;

or

++ valuel;

When these two operators are the sole operators in an expression, you don't have to worry about
the different syntax. A for loop very often uses this type of increment for the loop control
variable:
total = 0;
for (i = 1; i <= 10; i++)
total = total + i;
A decrement loop would be coded as:
total = 0;
for (i = 10; i >= 1; i - -)
total = total + i;
If you use these operators in complex expressions, you have to consider when the increment or
decrement actually takes place. The postfix increment, for example i++, uses the value of the
variable in the expression first, and then increments its value. However, the prefix increment, for
example ++i, increments the value of the variable first, and then uses the value in the expression.
Assume the following data declarations:

50
int i=3,j,k=0;

See if you can determine what happens in each of the following statements. For simplicity,
assume the original initialized values of the variables for each statement:

k = ++i; // i = 4, k = 4
k = i++; // i = 4, k = 3
k = --i; // i = 2, k = 2
k = i--; // i = 2, k = 3
i = j = k--; // i = 0, j = 0, k = -1

Arithmetic Operators
The C language incorporates the standard set of arithmetic operators for addition +, subtraction -,
multiplication *, division /, and modulus %. The first four operators need no explanation.
However, the following example will help you understand the modulus operator:
int a=3,b-=8,c=0,d;

d = b % a; // returns 2
d = a % b; // returns 3

d = b % c; // return an error message

The modulus operator returns the remainder of integer division. The last assignment statement
attempts to divide 8 by 0, resulting in an error message.

Assignment Operator
The assignment operator in C is unlike the assignment statement in other languages. It is
performed by an assignment operator, rather than an assignment statement. As with other C
operators the result of an assignment operator is a value that is assigned. An expression with an
assignment operator can be used in a large expression such as:
value1 = 8 * (value2 = 5);

Here, value2 is first assigned the value 5. This is multiplied by 8, with value receiving a final
value of 40.

51
If you overuse this feature, you can wind up with unmanageable expressions. There are two
places in which this feature is normally applied. First you can use it to set several variables to a
particular value, as in:
Value1 = value2 = value3 = 0;

The second use is most often seen in the condition of a while loop, for example:
while ((c = getchar ()) != EOF) {



}

This assigns the value that getchar returned to c and then tests the value against EOF. If it is
EOF, the loop is not executed. The parentheses are necessary because the assignment operator
has a lower precedence than the nonequality operator. Otherwise, the line would be interpreted
as:
c = (getchar() != EOF) {
.
.
.
}
The variable c would be assigned a value of 1 (TRUE) each time getchar returned EOF.

Compound Assignment Operators


The C language also incorporates an enhancement to the assignment statement used by other
languages. This additional set of assignment operators allows for a more concise way of
expressing certain computations. The following code segment shows the standard assignment
syntax applicable in many high-level languages:
result = result + increment;
depth = depth – one_fathom;
cost = cost * 1.07;
square_feet = square_feet / 9;

The C language compound assignment statements would look like this:

result += increment;
depth -= one_fathom;

52
cost *= 1.07;
square_feet /=9;

Looking closely at these two code segments, you will quickly see the required syntax. If you use
a C compound assignment operator, you must remove the redundant variable reference from the
right-hand side of the assignment operator and place the operation to be performed immediately
before the =.

Relational and Logical Operators


All relational operators are used to establish a relationship between the values of the operands.
They always produce a value of 1 if the relationship evaluates to TRUE or a value of 0 if the
relationship evaluates to FALSE. The next table lists the C relational operators.

C Relational Operators
Operator Meaning
== Equal (not assignment)
!= Not equal
> Greater than
< Less than
>= Greater than or equal
<= Less than or equal

The logical operators AND &&, OR ||, and NOT ! produce a TRUE (1) or FALSE (0) based on
the logical relationship of their arguments. The simplest way to remember how the logical AND
&& works is to say that an ANDed expression will only return a TRUE (1) when both arguments
are TRUE (1). The logical OR || operation in turn will only return a FALSE (0) when both
arguments are FALSE (0). The logical NOT ! simply inverts the value. The next table lists the C
logical operators.

C Logical Operators
&& AND
|| OR
! NOT

Have some fun with the following C program as you test the various combinations of relational
and logical operators. See if you can predict the results.

53
/*
* A C program demonstrating some of the subtleties of logical
* and relational operators.
*/

#include <stdio.h>

int main()
{
float value1, value2;
printf("\nPlease enter a valuel: “ );
scanf(“%f”,&value1);
printf("Please enter a value2: ");
scanf(“%f”,&value2);
printf(“\n”);
printf(“ valuel > value2 is %d\n”, (value1 > value2));
printf(“ valuel < value2 is %d\n”, (value1 < value2));
printf(“ valuel >= value2 is %d\n”, (value1 >= value2));
printf(“ valuel <= value2 is %d\n”, (value1 <= value2));
printf(“ valuel == value2 is %d\n”, (value1 == value2));
printf(“ valuel !^ value2 is %d\n”, (value1 != value2));
printf(“ valuel && value2 is %d\n”, (value1 && value2));
printf(“ valuel || value2 is %d\n”, (value1 || value2));

return 0;
}

You may be surprised at the results obtained for some of the logical comparisons. Remember,
however, a strict comparison occurs for both data types float and double when values of these
types are compared with zero - a number that is very slightly different from another number is
still not equal. Also, a number that is just slightly above or below zero is still TRUE (1).

Conditional Operator
The operator has the syntax:
condition ? true-expression : false-expression

If the condition is TRUE, the value of the conditional expression is true-expression. Otherwise, it
is the value of false-expression. For example, you could rewrite the following statement
if('A' <= c && c <= 'Z')
printf(“%c”,'a' + c - 'A');

54
else
printf("%c",c);

using the conditional operator:

printf("%c",('A' <= c && c <= 'Z') ? ('a' + c - 'A') : c );

Both statements will make certain that the character printed, c, is always lowercase.

Operator Precedences
The compiler determines the order of evaluation of an expression in C. This normally does not
alter the value of the expression, unless you have written one with side effects. Side effects are
those operations that change the value of a variable while yielding a value that is used in the
expression, as seen with the increment and decrement operators. The other operators that have
side effects are the assignment and compound assignment. Calls to functions that change values
of external variables also are subject to side effects. For example:

value1 = 3;
result = (valuel = 4) + valuel;

This could be evaluated in one of two ways: valuel is assigned 4, and result is assigned 8 (4+4);
value of 3 is retrieved from valuel, and 4 is then assigned to valuel, with result being assigned a
7.
There are, however, four operators for which the order of evaluation is guaranteed to be left-to-
right: logical AND (&&), logical OR (II), the comma operator, and the conditional operator.
Because of this default order of evaluation, you can specify a typical test as

while((c=getchar() != EOF) && (c!='\n'))

and know that the second part of the logical AND (&&) is performed after the character value is
assigned to c.
The next table lists all of the C operators from highest to lowest precedence and describes how
each operator is associated (left-to-right or right-to-left)- All operators between lines have the
same precedence level.

55
Operator Precedence Levels

Description Operator Associates from Precedence


Function expr () left Highest
Array expr [] Left
Struct indirection -> Left
Struct member . Left
Incr/decr ++, -- Right
One's complement ~ Right
Unary NOT ! Right
Address & Right
Dereference * Right
Cast (type) Right
Unary minus - Right
Size in bytes sizeof Right
Multiplication * Left
Division / Left
Remainder % Left
Addition + Left
Subtraction - Left
Shift left << Left
Shift right >> Left
Less than < Left
Less than or equal <= Left
Greater than > Left
Greater than or equal >= Left
Equal == Left
Not equal != Left
Bitwise AND & Left
Bitwise XOR ^ Left
Bitwise OR | Left
Logical AND && Left
Logical OR || Left
Conditional ?: Right
Assignment = %= += -= -= *= /= Right
>>= <<= &= ^= |=
Comma , left lowest

56
Unit 3

57
Conditions and Repetition

Conditional Statements
if
switch
Repetition Statements
for
while
do…while

In this Unit you will learn about two of C’s most important program control statements: if and
for. In general, program control statements control your program's flow of execution. As such,
they form the backbone of your programs- In addition to these, you will also learn about blocks
of code and the relational and logical operators. You will also learn more about the print ()
function.

58
The if Statement

if (expression) statement;
if {expression) statement1;
else statemanat2;
if (expression) {
statement1;
statement N;
}
else {
statements1;
statement N;
}
The if statement is one of C's selection statements (sometimes called conditional statements). Its
operation is governed by the outcome of a conditional test that evaluates to either true or false.
Simply put, selection statements make decisions based upon the outcome of a condition. In its
simplest form, the if statement allows your program to conditionally execute a statement. This
form of the if is shown here

.if{expression) statement;

The expression may be any valid C expression. If the expression evaluates as true, the statement
will be executed. If it does not, the statement is bypassed, and the line of code following the if is
executed. In C, an expression is true if it evaluates to any non-0 value. If it evaluates to 0, it is
false. The statement that follows an if is commonly referred to as the target of the if statement.
Commonly, the expression inside the if compares one value with another using a relational
operator. Although you will learn about all the relational operators later in this Unit, three are
introduced here so that we can create some example programs. A relational operator tests how
one value relates to another. For example, to see if one value is greater than another, C uses the >

59
relational operator. The outcome of this comparison is either true or false. For example, 10 > 9 is
true, but 9 > 10 is false. Therefore this if will cause the message true to be displayed.

if (10 > 9) print("true");

However, because the expression in the following statement is false, the if does not execute its
target statement,

if(l > 2) print ("this will not print");

C uses < as its less than operator. For example,10 < 11 is true. To test for equality, C provides
the == operator. There may be no space between the two equal signs. Therefore/10 == 10 is true,
but 10 == 11 is not-
Of course, the expression inside the if may involve variables. For example, the following
program tells whether a number entered from the keyboard is positive or negative.

#include "stdio .h"

int main( )
{

int num.;

print("enter a number: ") ;

scanf("%d", &num) ;

if(num > 0) printf("number is positive");

if(num < 0) printf("number is negative") ;


}
Remember, in C, true is any non-0 value and false is 0. Therefore, it is perfectly valid to have
an if statement such as the one shown here,
if(count +l) print C' not zero") ;

The next example forms the basis for an addition drill. It displays two numbers and asks the user
what the answer is. The program then tells the user if the answer is right or wrong.
#include "stdio .h"

60
int main( )
{
int answer;
printf (“What is 10 + 14? “) ;

scanf ("%d", &answer) ;


if (answer == 10+14) printf ("Right!");
return 0;
}

This following program either converts feet to meters or meters to feet, depending upon what the
user requests.

#include "stdio.h"
int main( )
{
float num;
int choice;
printf("enter value: ") ;
scanf("%f", &num);
printf ("l: feet to meters/ 2: meters to feet ") ;
printf ("enter choice: ") ;
scanf ("%d", &choice);
if(choice == 1) printf ("%f", num / 3.28);
if(choice == 2) printf ("%f", num * 3.28);
return 0;
}

Add The else


You can add an else statement to the if. When this is done, the if statement looks like this:

if {expression) statement1;
else statemanat2;

If the expression is true, then the target of the if will be executed, and the else portion will be
skipped. However if the expression is false, then the target of the if is bypassed, and the target of
the else will be executed. Under no circumstances will both statements execute. Thus, the
addition of the else provides a two-way decision path,

61
You can use the else to create more efficient code in some cases. For example, here the else is
used in place of a second if in the program from the preceding section, which determines
whether a number is positive or negative.
#include "stdio.h"
int main( )
{
int num;
printf("enter a number: ") ;
scanf ("%d", &num) ;
if(num < 0) printf("number is negative") ;
else printf("number is positive");
return 0;
}

Since there are only two possibilities, there is no reason to test num. again to see if it is 0 or
greater the original version of this program explicitly tested , for num. to be greater than 0
using a second if statement. Because of the way a C compiler generates code, the else requires
far fewer machine instructions than an additional if.

The following program prompts the user for two numbers, divides the first by the second, and
displays the result. However/ division by 0 is undefined, so the program uses an if and an else
statement to prevent division by 0 from occurring.

#include "stdio.h”
int main( )
{
int num1, num2 ;
printf("enter first number: ") ;
scanf("%d", &num1) ;
printf("enter second number: ");
scanf("%d", &num2) ;
if(num2==0) printf("cannot divide by zero");
else printf ("answer is: %d", num1 / num2) ;

return 0;
}

62
Create Blocks of Code
In C, you can link two or more statements together. This is called a block of code or a code
block. To create a block of code, you surround the statements in the block with opening and
closing curly braces. Once this is done, the statements form one logical unit, which may be used
anywhere that a single statement may.

For example, the general form of the if using blocks of code is

if (expression) {
statement1;
statement 2;
.
.
.

statement N;
}
else {
statements1;
statement2;
.
.
.

statement N;
}

If the expression evaluates to true, then all the statements in the block of code associated with the
if will be executed. If the expression is false, then all the statements in the else block will be
executed. (Remember, the else is optional and need not be present.) For example this fragment
prints the message This is an example of a code block if the user enters any positive number.
scanf ("%d", & nun ) ;
if (num. > 0) {
printf("This is ") ;
printf("an example of ") ;
printf("a code block");
}

63
Keep in mind that a block of code represents one indivisible logical unit. This means that under
no circumstances could one of the printf() statements in this fragment execute without the others
also executing.
In the example shown, the statements that appear within the block of code are indented.
Although C does not care where a statement appears on a line/ it is common practice to indent
one level at the start of a block- Indenting makes the structure of a program easier to understand.
Also, the placement of the curly braces is arbitrary. However, the way they are shown in the
example is a common method .

This program is an improved version of the fact -to-meters meters-to-feet conversion program.
Notice how the use of code blocks allows the program to prompt specifically for each unit.

#include “stdio.h”
int main( )
{
float num;
int choice;

printf ("1: feet to meters, 2: meters to feet ") ;


printf("enter choice: ") ;
scanf("%d", &choice) ;
if (choice == 1) {
printf("enter number of feet: ") ;
scanf ("%f", &num) ;
printf("meters: %f", num / 3.28);
}
else {
printf("enter number of meters: ") ;
scanf ("%f", &num) ;
printf("feet: %f", num * 3.28);
}
return 0;
}

Using code blocks, we can improve the addition drill program so that it also prints the correct
answer when the user makes a mistake.

#include "stdio.h"

int main( )

64
{
int answer;

printf("What is 10 +• 14? ") ;


scanf("%d", &answer) ;
if(answer == 10+14) printf ("Right!");
else {
printf ("Sorry, you're wrong, ") ;
printf("the answer is 24") ;
}
return 0;
}

This example illustrates an important point: it is not necessary for targets of both the if and the
else statements to be blocks of code- In this case, the target of if is a single statement, while the
target of else is a block. Remember, you are free to use either a single statement or a code block
at either place.

65
Use The for Loop

for (initialization; conditional test; increment) statement;


for (initialization; conditional test; increment) { statement1;
statement n;
}

The for loop is one of C's three loop statements. It allows one or more statements to be repeated.
If you have programmed in any other computer language, such as BASIC or Pascal, you will be
pleased to learn that the for behaves much like other languages.
The for loop is considered by many C’ programmers to be its most flexible loop. Although the
for loop allows a large number of variations/ we will examine only its most common form in this
section.
In its most common form, the for loop is used to repeat a statement or block of statements a
specified number of times. Its general form for repeating a single statement is shown here.

for (initialization; conditional test; increment) statement;

The initialization section is used to give an initial value to the variable that controls the loop.
This variable is usually referred to as the loop-control variable. The initialization section is
executed only once/ before the loop begins. The conditional-test portion of the loop tests the
loop-control variable against a target value each time the loop repeats. If the conditional test
evaluates true, the loop repeats. If it is false, the loop stops, and program execution picks up with

66
the next line of code that follows the loop. The conditional test is performed at the start or top of
the loop each time the loop is repeated.
The increment portion of the for is executed at the bottom of each loop. That is/ the increment
portion is executed after the statement or block has been executed, but before the conditional
test. The purpose of the increment portion is to increase (or decrease) the loop-control value by a
certain amount.
As a simple first example, this program uses a for loop to print the numbers 1 through 10 on the
screen.

#include "stdio.h"

int main( )
{
int num;
for(num=l; num<ll; num++) printf("%d ", num) ;
printf("terminating") ;
return 0;
}
This program produces the following output:
1 2 3 4 5 6 7 8 9 10 terminating

The program works like this. First, the loop control variable num. is initialized to 1. Next, the
expression num. < 11 is evaluated. Since it is true, the for loop begins running. After the number
is printed/ num. is incremented by one and the conditional test is evaluated again. This process
continues until num equals 11. When this happens/ the for loop stops, and terminating is
displayed. Keep in mind that the initialization portion of the for loop is only executed once,
when the loop is first entered.
As stated earlier/ the conditional test is performed at the start of each iteration. This means that if
the test is false to begin with, the loop will not execute even once For example, this program only
displays terminating because num. is initialized to 11, causing the conditional test to fail.

#include "stdio.h"
int main( )
{
int num;
/* this loop will not execute */
for(num=ll; num<ll; num=num++) printf("%d ", num) ;
printf("terminating") ;

67
return 0;
}

To repeat several statements, use a block of code as the target of the for loop. For example, this
program computes the product and sum of the numbers from 1 to 10.
#include "stdio.h"
int main( )
{
int num, sum, prod;
sum = 0;
prod = 1;
for (num=1; num<11; num++) {
sum += num;
prod *= num;
}
printf("product and sum: %d %d", prod, sum);
return 0;
}

A for loop can run negatively. For example, this fragment decrements the loop-control variable-

for(num=20; num>0; num--) ….

Further, the loop-control variable maybe incremented or decrement by more than one. For
example, this program counts to "100 by fives.

#include "stdio.h"

main( )
{
int i ;
for(i=0; i<101, I+=5) printf("%d ", i) ;
return 0;
}

The addition-drill program created earlier can be enhanced using a for loop. The version shown
here asks for the sums of the numbers between 1 and 10. That is it asks for 1 + , then 2 + 2, and
so on. This program would be useful to a first grader who is learning addition.

#include "stdio.h"

68
int main( )
{
int answer, count;
for (count=1; count < 11; count++) {
printf ("What is %d + %d? ", count, count);
scanf("%d", &answer) ;
if(answer == count+count) printf("Right! ") ;
else {
printf ("Sorry, you're wrong ") ;
printf("the answer is %d. ", count+count) ;
}
}
return 0;
}

Notice that this program has an if statement as part of the for block. Notice further that the target
of else is a block of code. This is perfectly valid. In C, a code block may contain statements that
create other code blocks. Notice how the indentation adds clarity to the structure of the program.
We can use a for loop to create a program that determines if a number is prime. The following
program asks the user to enter a number and then checks to see if it has any factors.
/* Prime number tester. */
#include "stdio.h"
int main ( )
{
int num, i, is_prime;
printf ("Enter the number to test: ") ;
scanf("%d", &num);

/* now test for factors */


is_prime = 1;
for(i=2; i<num/2; i++)
if((num%i)==0) is_prime = 0;
if(is_prime==1) printf("The number is prime.") ;
else printf("The number is not prime -") ;
return 0;
}

69
Input Character

#include “stdio.h”

int main ( )
{
char ch ;
ch = getchar ( ); /* read a char */
printf ( “ you typed : % c”, ch) ;
return 0;
}

Although numbers are important, your program will also need to read characters from the
keyboard, in C you can do this in a variety of ways. Unfortunately, this conceptually simple task
is complication by some baggage left over from the origins of C. However, let’s begin with the
traditional way characters are read from the keyboard.

The original version of C defined a function called getchar () , which returns a single character
typed on the keyboard when called , the function waits for a key to be pressed then getchar()
echoes the keystroke to the screen and returns the value of the key to the caller the getchar()
function is defined by the ANSI C standard and requires the header file stdio.h. This program
illustrates its use by reading a character and then telling you what it received. (Remember, to
display a character, use the % c prinf() format specifier)

#include “stdio.h”

int main ( )
{

70
char ch ;
ch = getchar ( ); /* read a char */
printf ( “ you typed : % c”, ch) ;
return 0;
}
If you try this program, it may behave differently than you expected. The trouble is this: in many
C compilers, getchar () is implemented in such a way that it line buffers input . This means that
even though it will return one character to your program , it waits until you press ENTER before
doing so . the reason for this is that the early version of UNIX, for which C was developed , line
buffered input. When C compilers were created for other interactive environments, developers
had to decide how to make getchar() behave. Many C compiler developers have decided, for the
sake of compatibility, to keep getchar () line –buffered , even though there is no reason for it. (in
fact, the ANSI standard states that getchar () need not be line – buffered . ) when getchar() is
implemented in a line–buffered fashion In modern interactive environment, it is rendered nearly
useless.

Because many implementations have implemented line – buffered versions of getchar() , most C
compilers supply another function to perform interactive console input Although it is not defined
by the ANSI C standard, most compilers call this function getche(). You use it just like getchar( )
, except that it will return its value immediately after a key is pressed ; it does not line buffer
input . For most compilers, this function requires a header file called conio.h, but it might be
called something different in your compiler.

Virtually all computers use the ASCII character codes when representing characters . Therefore,
characters returned by either getchar() or getche() will be represented by their ASCII codes . This
is useful because the ASCII characters codes are an ordered sequence; each letter’s code is one
greater than the one it precedes. In C, you may compare characters just like you compare
numbers. This manes that “a” is less than “b,” and so on. For example,

ch = getche( ) ;
If (ch < ‘f‘ ) printf (“character is less than f “ ),

is a perfectly valid fragment that will display its message if the user enters any character that
comes before f.

71
The folowing program reads a character and displays its ASCII code . This illustrates an
important feature of C: you can use a character as if it were a “little integer “

#include “conio.h”
#include “stdio.h”
int main ( )
{
char ch ;

printf (“enter a character: “) ;


ch = getche ( ) ;
printf (“\nits ASCII code is %d”, ch);
return 0;
}

One of the most common uses of getche( ) is to return a selection . For example, this program
allows the user to add, subtract, multiply, or divide two numbers,

# include “conio.h“
# include “stdio.h“

main ( )
{

int a, b;
char ch ;

printf ( “ Do you want to : \n “ ) ;


printf ( “ Add , subtract , Multiply , or Divide ?\ n “ ) ;
printf ( “ Enter first letter : “ ) ;
ch = getche ( ) ;
printf ( “ \ n “ ) ;

printf ( “ \ Enter first number : “ ) ;


scanf ( “ % d “ , & a ) ;
printf ( “ Enter second number : “ ) ;
scanf ( “ %d “ , & b ) ;

if ( ch == ‘A‘ ) printf ( “%d “ , a + b ) ;


if ( ch == ‘S’ ) printf ( “%d” , a – b ) ;
if ( ch == ‘M‘ ) printf ( “ %d “ , a * b) ;

72
if ( ch == ‘D‘ && b != 0 ) printf ( “ % d “ , a / b ) ;
return 0;
}

One point to keep in mind is that C makes a distinction between upper-and lowercase. So, if the
user enters an s, the program will not recognize it as a request to subtract.

Another common reason that your program will need to read a character form the keyboard is to
obtain a yes / no response from the user . For example, this fragment determines if the user wants
to proceed.

printf ( “ Do you which to continue ? ( Y / N ) : “ ) ;


ch = getche ( ) ;
if ( ch = = ‘ Y ‘ ) {
/ * continue with something * /
.
.
.
}

73
Nest if Statements

if (expression1) statement 1
if (expression 2) stsement 2;
if( expression) statement ;
else if (expression) statement ;
else if (expression) statement ;
.
. else statement

When an if statement is the target of another if or else, it is said to be nested within the outer if.
Here is a simple example of a nested if.

if ( count > max ) / * outer if * /


if ( error ) printf ( “ error , try again “ ) ; / * nested if * /

Notice how the nested if is indented This is common practice it enables anyone reading your
program to know quickly that the if is nested and what action are nested .A nested if may appear
inside a block of statements that are the target of the outer if .An ANSI –standard compiler will
allow you to nest ifs at least 15 levels deep
One confusing aspect of nested ifs is illustrated by the following fragment.

if (p)
if (q) printf (“ a and b are true “) ;
else printf (“to which statement does this else apply? “);

The question, as suggested by the second printf (), is: which if is associated with the else?
Fortunately, the answer is quite easy. An else always associates with the nearest if within the
same block in this examples, the else is associated with the second if

74
it is possible to string together several ifs and elses into what is sometime called an if- else –if
ladder or if –else staircase because of its visual appearance . in this situation a nested if has as is
target another if the general form of the if –else –is ladder is shown here .

if( expression) statement ;


else
if (expression) statement ;
else
if (expression) statement ;
.
.
.
.
else statement

The conditions are evaluated from the top downward. As soon as a true conditions found, the
statement associated with it is executed, and the rest of the ladder is bypassed. If none of the
conditions is true, the final else will be executed. That is, if all other conditional test fail, the last
else statement is performed. If the final else is not present, no action will take place if all other
conditions are false.
Although the indention of the general form of the if-else-if ladder is technically correct, it can
lead to overly deep indentations. Because of this, the if-else-if ladder is generally indented like
this:

if ( expression )
statement ;
else if (expression )
statement ;
else if (expression )
statement ;
.
.
.
else
statement;

We can improve the arithmetic program developed previously by using an if –else-if ladder, as
shown here.

75
# include “conio.h “
# include “stdio.h “

int main ( )
{
int a, b;
char ch;

printf (“Do you want to :\n”) ;


printf (“Add, Subtract , Multiply , or Divide ? \ n “ );
printf (“Enter first letter : “) ;
ch = getche ( ) ;
printf (“\n”);
printf (“Enter first number : “ );
scanf (“%d”, &a ) ;
printf (“Enter second number : “ ) ;
scanf ( “%d”, &b ) ;

if (ch == ‘A’)printf(“%d” , a+b);


else if (ch == ‘S’) printf(“%d”,a-b);
else if (ch == ‘N’) printf(“%d”,a*b);
else if (ch == ‘D’ && b != 0) printf(“%d”, a/b);
return 0;
}

This is an improvement over the original version because once a match is found, any remaining
if statements are skipped this means that the program isn’t wasting time on needless operation
whiles this not too import in this example, you will encounter situations where it will be.

Nested if statements are very common in programming for example, here is a further
improvement to the addition drill program developed in the preceding Unit it lets the user have a
second try at getting the right answer.

#include “conio.h”
#include “stdio.h”

int main ()
{
int answer , count ;
char again ;

for (count = 1 ; count <11; count ++) {


printf (“what is %d+%d? “, count ,count );

76
scanf (“%d” , & answer ) ;
if ( answer == count + count ) printf ( “ Right ! \ n “ ) ;
else {
printf (“Sorry , you ‘re wrong \ n”) ;
printf (“would you like to try again ? ( Y/N ) “) ;
again = getche ( ) ;
printf ( “\n” );

/* nested if */
if (again == ‘Y‘) {
printf ( “ \ nWhat is %d + %d ? “ , count , count ) ;
scanf ( “%d “, & answer is % d \ n” , count + count );
}
else
printf ( “the answer is %d \ n” , count + count );
}
}
return 0;
}

77
Examine for Loop Variations

no limits on the types of expressions


initialization section is not mandatory
control variable is optional
one or more of the expressions inside it may be empty

The for loop in C significantly more powerful and flexible then in most other computer
languages. when you were introduced to the for loop in previously, you were only shown the
from similar to that used by other languages . However, you will see that for is much more
flexible.
The reason that of is so flexible is that the expressions we called the for loop are not limited to
these narrow roles The C for loop places no limits on the types of expressions that occur inside it
for example you do not have it use the initialization section initialize a loop- control variable
Further, there does not need to be any loop –control variable because the conditional test
expression that is evaluated each time the loop iterates it does not have to increment or
decrement variable.
Another important reason that for is so flexible is that one or more of the expressions inside it
may be empty. For example, if the loop control variable has already been initialized outside for,
there is no need for an initialization expression.

This program continues to loop unit a q is entered at the keyboard. Instead of testing a loop
control variable, the conditional test in this for checks the value of a character entered by the

78
user.

#include “stdio.h”
#include “conio.h”

int main ( )
{
int i ;
char ch;

ch = ‘a’ ; /*give ch initial value */

for (i =0; ch !=’q’ ; i++) {


printf (“pass: %d\n”, i);
ch = getche () ;
}
return 0;
}

Here, that condition that control variable The reason ch is given an initial value is to prevent it
from accidentally containing a q when the program begins

As stated earlier, it is possible to leave an expression in a loop empty. For example, this program
asks the user for a value and then counts down to 0 from this number. Here, the loop –control
variable is initialized by the user outside the loop, so the initialization portion of the loop isempty

#include “stdio.h”

int main ()
{
int i;

printf(“Enter an integer: “);


scanf (“%d” , & i ) ;

for ( ; i; i-- ) printf (“%d” , &i ) ;


return 0;
}

Another variation to for is that its target may be empty. For example, this program simply keeps
inputting characters until the user types q .

79
#include “stdio.h”
#include “conio.h”

int main()
{
char ch ;
for (ch=getche(); ch !=’q’ ; ch = getche ()) ;
printf (‘found the q”);
return 0;
}

Notice that statements assigning ch a value have been moved into the loop. This means that
when the loop starts, getche() is called . then , the value of ch is tested against q next ,
conceptually, the nonexistent target of the increment portion of the loop is executed. This process
repeats until the user enters a q .
The reason the target of the for can be empty is because C allows null statements .

Using the for, it is possible to create a loop is usually called an infinite loop. Although accident
an infinite loop is a bug , you will sometimes want to create one on purpose. To create an infinite
loop, use a for construct like this :

for ( ; ; ) {
.
.
.
}

As you can see, there are no expressions in the for when there is no expression in the conditional
portion, the compiler assumes that it is true therefore, the loop continues to run .

In C, unlike most other computer languages, it is perfectly valid for the loop control variable to
be altered outside the increment section for example, the following program manually
increments i at the bottom of the loop.

#include “stdio.h”

int main ()
{

80
int i;

for ( i = 0 ; i < 10; ) {


printf(“%d “, i);
i++;
}
return 0;
}

81
C’s while Loop

while loop is precondition iteration statement


while( expression ) statement ;
while(expression) {
statement1;

Statementn;
}

Another of C’s loops is while it has this general from :

while( expression ) statement ;

Of course, the target of while may also be a block of code the while loop works by repeating its
target as long as the expression is true when it becomes false, the loop stops. The value of the
expression is checked at the top loop. This means that if the expression is false to begin with, the
loop will not execute even once.

Even though the for is flexible enough to allow itself to be controlled by factors not related to its
traditional use, you should generally select the loop that best fits the needs of the situation. For
example, a better way to wait for the letter ‘q’ to be typed is shown here using while if you
compare it to the previous example, you will see how much clearer this version is.

#include “stdio.h”
#include “conio.h”

int main()

82
{
char ch ;
ch = getche( );
while ( ch!=’q’) ch = getche ( ) ;
printf ( “found the q” ) ;
return 0;
}

The following program is a simple code machine. It translates the characters you type into a
coded from by adding 1 to each letter. That is, ‘A’ becomes ‘B’ and so forth. The program stops
when you press ENTER (the gtche() function returns \r when ENTER is pressed.)

#include “stdio.h”
#include “conio.h”

int main()
{
char ch ;

printf(“Enter your massage. \n”);

ch = getche() ;
while (ch!=’\r’) {
printf(“%c”, ch+1) ;
ch = getche () ;
}
return 0;
}

83
Use do Loop

do ..while loop is postcondition iteration statement


do{
Statements
} while (expression);

C’s final loop is do, which has this general form:

do{
Statements
} while (expression);

if only one statement is begin repeated, the curly braces are not necessary. Most programmers
include them. However, so that ends the do is part of a do loop, not the beginning of a while
loop.
The do loop repeats the statement or statement while the expression is true. It stops when the
expression becomes false. The do loop is unique because it will always execute the code within
the loop at least ones, since the expression controlling the loop is tested at the bottom of the loop.

The fact do will always execute the body of its loop at least once makes it perfect for checking
menu input. For example, this version of the arithmetic program reports the user until a valid
response is entered

#include “stdio.h”
#include “conio.h”

84
int main()
{
int a, b;
char ch;

printf(“do you want to :\n”);


printf(“add, subtract, multiply, or divide?\n”);

/* force user to enter a valid response */


do {
printf ( “Enter first letter : “);
ch=getche ();
printf (“\n”);
} while (ch!=’A’ && ch !=’S’ && ch!=’M’ && ch!=’D’ );
printf(“Enter first number: “);
scanf(“%d”,&a);
printf(“Enter second number : “);
scanf(“%d”, &b);

if (ch == ‘A’) printf (“%d” , a+b);


else if (ch==’S’ ) printf (“%d” , a-b);
else if (ch==’M’ ) printf (“%d” , a*b);
else if (ch==’D’ && b!=0 ) printf (“%d” , a/b);
return 0;
}

The do loop is especially useful when your program is waiting for some event to occur. For
example, this program waits for the user to type a q. Notice that it contains one less call to getche
() then the equivalent program described in the section on the while loop.

#include “stdio.h”
#include “conio.h”

int main()
{
char ch ;

do {
ch = getche ();
}while (“ch ! = ‘q’);
printf(“found the q”);
return 0;
}

85
Since the loop condition is tested at the bottom, it is not necessary to initialize ch prior to
entering the loop.

86
Loops Features

Loops may be nested for 15 levels(A NSI Standard)


You may exit loop using break ststement
Control the flow of execution using continue statement

Create Nested Loops


When the body of one loop contains another, the second is said to be nested inside the first. Any
of C’s loops may be nested within any other loop. The ANSI C standard specifies that loops may
be nested for 15 levels deep. However, most compilers allow nesting to virtually any level. As a
simple example of nested fors, this fragment prints the numbers 1 to 10 on the screen ten times.

for (i=0; i<10; i++) {


for (j= 1; j < 11; j++) printf(“%d “, j) ;
printf(“\n”) ;
}

You can use a nested for to make another improvement to the arithmetic drill. In the version
shown below, the program will give the user three chances to get the right answer. Notice the use
of the variable right to stop the loop early if the correct answer is given.

#include “stdio.h”
#include “conio.h”

int main()

87
{
int answer, count, chances, right ;

for (count = 1 ; count <11 ; count ++ ) {


printf (“what is %d + %d? “,count, count) ;
scanf(“%d”, &answer );
if (answer == count + count ) printf (“Right ! \n”) ;
else {
printf (“sorry, you’re wrong\n”);
printf(“Try again \n”);

right = 0;
/* nested for */
for (chances = 0 ; chances <3 && ! right ; chances ++) {
printf( “\nwhat is %d + %d? “, count, count);
scanf (“%d”, & answer );
if (answer == count + count ) {
printf(“ Right! \n”) ;
right = 1 ;
}
}
/* if answer still wrong, tell user */
if (!right )
printf (“the answer is %. \n”, count + count );
}
}
return 0;
}

This program uses three for loops to print the alphabet three times, each time printing each letter
twice

#include “stdio.h”
int main()
{
int i, j, k;
for (i = 0 ; i <3; i ++)
for (j=0 ; j <26; j++)
for (k=0; k<2; k++) printf(“%c”, ‘A’ +j ) ;
return 0;
}
The statement
Printf (“%c”, ‘A’+j) ;
Works because ASCII codes for the letters of the alphabet are strictly ascending, each one
greater than the one that precedes it.

88
Use break to Exit a Loop
The break statement allows you to exit a loop from any point within its body, bypassing its
normal termination expression. When the break statement is encountered inside a loop, the loop
is immediately terminated following the loop. For example, this loop prints only the numbers 1
to 10

#include “stdio.h”

int main()
{
int i ;

for (i = 1; i <100; i ++) {


printf(“%d “, i);
if (i == 10) break ; /* exit the loop */
}
return 0;
}

The break statement can be used with all three of C’s loops.
You can have as many break statements within a loop as you desire. However, since too many
exit points from a loop tend to distracter your code, it is generally best to use the break for
special purposes, not as your normal loop exit.

The break statement is commonly used in loops in which a special can cause immediate
termination. This is an example of such a situation, where a keypress can stop the execution of
the program

#include “stdio.h”
#include “conio.h”

int main()
{
int i;
char ch;

/* display all numbers which are multiples of 6 */


for (i= 1; i< 1000; i++) {

89
if (!(i%6)) {
printf (“%d more ? (y/n)”, i) ;
ch = getche ( ) ;
if (ch ==’N’) break ;
printf (“\n”) ;
}
}
return 0;
}

A break will cause an exit from only the innermost loop. For example, this program prints the
numbers 1 to 5 five times.

#include “stdio.h”

int main ( )
{
int i, j;
for (i=0 ; i<5; i++) {
for (j=0; j<100; j++) {
printf (“%d”, j);
if (j == 5 )break ;
}
printf (“\n”);
}
}

The continue Statement


The continue statement is somewhat the opposite of the break statement. It forces the next
iteration of the loop to take place, skipping any code in between itself and the test condition of
the loop. For example, this program never displays output.

#include “stdio.h”

int main ( )
{
int x;
for (x=0; x<100; x++) {
continue ;
printf (“%d ”, x) ; /* this is never is executed */
}
}

90
Each time the continue statement is reached, it causes the loop to repeat, skipping the printf ()
statement. In while and do-while loop, a continue statement will cause control to go directly to
the test condition and then continue the looping process. In the case of for, the test is executed,
and the loop continues.
Poor practice to use it, but simply because good applications for it are not common.

One good use continue is to restart a statement sequence when an error occurs. For example, this
program computes a running total of number entered by the user. Before adding a value to the
running total, it verifies that the number was correctly entered. If it wasn’t, it uses continue to
restart the loop.

#include “stdio.h”
#include “conio.h”

int main()
{
int total, i;
char ch;

total = 0 ;
do {
printf(“Enter next number (0 to stop ) :”);
scanf(“%d”, &i) ;
printf(“is %d correct? (y/n) : “, i);
ch = getche ( ) ;
printf(“\n”);
if (ch == ‘N’) continue;
total = total + i;
} while (i) ;
printf(“total is %d\n”, total );
return 0;
}

91
Select Paths with he switch Statement
switch is a multibranch conditional statement
switch (variable) {
case constant1 :
Statement sequence; break;
case constant2;
Statement sequence; break;.
default:
Statement sequence
}
While if is good for choosing between two alternatives, it quickly becomes cumbersome when
several alternatives are needed. C’s solution to this problem is the switch statement. The switch
statement is C’s multiple selection statement. It is used to select one of several alternative paths
in program execution and works like integer or character constants. When a match is found, the
statement sequence associated with that match is executed. The general from of the switch
statement is

switch (variable) {
case constant1 :
Statement sequence
break;
case constant2;
Statement sequence
break;
case constant3;
Statement sequence
break;
.
.
.
default:

92
Statement sequence
}

where the default statement sequence is performed if no match are found. The default is optional.
If all matches fail and default is absent, no action takes place. When a match is found, the
statements associated with that case are executed until break is encountered or, in the case of
default or the test case, the end of the switch is reached.
As a very simple example, this program recognizes the numbers 1,2,3,and 4 and prints the name
of the one you enter. That is, if you enter 2, the program displays two.

#include “stdio.h”

int main()
{
int i ;

printf(“Enter a number between 1 and 4: “);


scanf (“%d”, & i );
switch (i) {
case 1:
printf(“one”);
break ;
case 2 :
printf(“two”);
break ;
case 3 :
printf(“three”);
break ;
case 4 :
printf(“four”);
break;
default :
printf(“unrecognized number “) ;
}
return 0;
}

The switch statement differs from if, in that switch can only test for equality, whereas the if
conditional expression can be of any type. Also, switch will work with only int or char types.
You cannot, for example, use floating-point numbers.

93
The statement sequences associated with each case are not blocks; they are not enclosed by curly
braces.

The ANSI standard states that maximum 257 case statements will be allowed. In practice, you
should limit the amount of case statements to a much smaller number for efficiency reasons.
Also, no two case constants in the same switch can have identical values.

It is possible to have a switch as part of the statement sequence of an outer switch. This is called
a nested switch. If the case constants of the inner and outer switch contain common values, no
conflicts will arise. For example, the following code fragment is perfectly acceptable.

switch (a) {
case 1:
switch(b) {
case 0: printf(“b is false “);
break;
case 1: printf (“b is true”);
}
break;
case 2 :
.
.
.
An ANSI-standard compiler will allow at least 15 levels of nesting switch statements .

The switch statement is often used to process menu commands. For example, the arithmetic
program can recoded as shown here. This version reflects the way professional C code is
written .

#include “conio.h”
#include “stdio.h”

int main()
{
int a, b ;
char ch;
printf (“do you want to : \n”) ;
printf (“Add, subtract, multiply, or divide? \n”) ;
/* force user to enter a valid response */

94
do {
printf (“Enter first letter : “) ;
ch = getche( ) ;
printf(“\n”);
} while (ch !=’A’ && ch != ‘S’ && ch !=’M’ && ch != ‘D’) ;

printf (“Enter first number : “) ;


scanf (“%d”, & a);
printf (“Enter second number : “) ;
scanf (“%d”, & b);

switch (ch) {
case ‘A’ : printf(“%d”, a+b);
break;
case ‘S’ : printf(“%d”, a-b);
break;
case ‘M’ : printf(“%d”, a*b);
break;
case ‘D’ : if (b!=0 ) printf (“%d”, a/b);
}
return 0;
}

Technically, the break statement is optional. The break statement, when encountered within a
switch causes the program flow to exit from the entire switch statement and continue on to the
next statement outside the switch. This is much the way it works when breaking out of a loop.
However, if a break statement is omitted, the execution continue into the following case or
default statement (if either exists). That is, when a break statements or the end of the switch is
encountered. For example, study this program carefully.
#include “stdio.h”
#include “conio.h”

int main()
{
char ch;

do {
printf(“\nEnter a character , q to quite : “ );
ch = getche ( ) ;
printf ( “\n”) ;

switch (ch) {
case ‘a’ :

95
printf (“new is “) ;
case ‘b’ :
printf (“the time “) ;
case ‘c’ :
printf (“for all good men “) ;
case ‘d’ :
printf (“the summer “) ;
case ‘e’ :
printf (“soldier “) ;
}
}while (ch != ‘q’) ;
return 0;
}

if the user types a, the entire phrase New is the time for all good men. As you can see, once
execution begins inside a case, it continues until a break statement or the end of the switch is
encountered.

The statement sequence two or more case to share a common statement sequence without
duplication of code. For example, here is a program that categorizes letters into vowels and
consonants.

#include “stdio.h”
#include “conio.h”

int main()
{
char ch;

printf(“\Enter the letter : “ ) ;


ch = getche ( ) ;
switch ( ch) {
case ‘a’ :
case ‘e’ :
case ‘i’ :
case ‘o’ :
case ‘u’ :
case ‘y’ :
printf(“is a vowel \n “ ) ;
break ;
default :
printf(“is a consonant “ ) ;

96
}
}

97
Unit 4

98
Modularity

Modularity and Functions


Function Prototyping
Function Arguments
Function Type

Functions form the cornerstone of C programming. As you expand your programming skills,
your programs will take on a modular appearance when you begin programming with functions.
You do all C programming within a function. This is because all programs must include main,
which is itself a function. If you have programmed in other languages, you will find C functions
similar to modules in other languages. Pascal uses procedures and functions, FORTRAN uses
just functions, and assembly language uses just procedures. How functions work determines to a
large degree the efficiency, readability, and portability of C program code.

This Unit includes numerous C examples that illustrate how to write simple functions to perform
specific tasks. The functions are short to make the concepts easier to understand and to prevent
you from being lost in reams of code. Many of these examples use functions contained in the
standard C libraries. If you learn to write good functions, you are well on your way to becoming
a power C programmer.

211
Function Style and Prototyping
C functions changed greatly during the ANSI standardization process. This new C standard is

99
largely based on the function prototype used in C++.

Function Prototyping
Function declarations begin with the C function prototype. The function prototype is simple and
is included at the start of program code to notify the compiler of the type and number of
arguments that a function will use. It also enforces a stronger type checking than was possible
when C was not standardized.

Although other variations are legal, whenever possible you should use the function prototype
form that is a replication of the function's declaration line. For example,

return_type function_name(argument_type(s) argument_name(s) );

The function can be of type void, int, float, and so on. The return_type gives this specification.
The function_name is any meaningful name you choose to describe the function. If any
information is passed to the function, you should give an argument_type followed by an
argument_name. Argument types may also be of type void, int, float, and so on. You can pass
many values to a function by repeating the argument type and name separated by a comma. It is
also correct to list just the argument type, but that prototype form is not used as frequently.

The function itself is an encapsulated piece of code that usually follows the main function
definition. The function can take on the following form:

return_type function_name(argument_types and names)


{
.
.
(data declarations and body of function)
.
.
return();
}

100
Notice that the first line of the function is identical (except for the missing ;) to the prototype that
is listed at the beginning of a program. An actual function prototype and function, used in a
program, is shown in the following C example:

/*
* C program to illustrate function prototyping.
* Function subtracts two integers and returns an integer
* result.
*/
#include <stdio.h>
int subtractor(int x,int y); /* function return int type */

int main()
{
int a=5;
int b=93;
int c;
c=subtractor(a,b);
printf(“The difference is: %d\n”, c);
return (0);
}
int subtractor(int x,int y) /* function declaration */
{
int z;

z=y-x;
return(z);
}

The function is called subtractor. The prototype states that the function will accept two int
arguments and return an int type. Actually, the ANSI standard suggests that every function be
prototyped in a separate header file. As you might guess, this is how header files are associated
with their appropriate C libraries. For simple programs, you can include the function prototype
within the body of the program.

Function Arguments
The following sections cover function arguments, which are arguments or parameters that are
passed to the function. Function arguments are optional; some functions may receive no
arguments while others may receive many. Function arguments can be mixed-that is, you can use
any of the standard data types.

101
Formal and Actual Function Arguments

void as an Argument
Characters as Arguments
Integers as Arguments
Floats as Arguments
Doubles as Arguments

The function definition contains an argument (or parameter) list called the formal argument list.
The list may be empty or may contain any combination of types, such as integer, float, or
character. When the function is actually called from within the body of the program, an argument
list is also passed to the function. This list is called the actual argument list. When you write
ANSI C code, there is usually a one-to-one match between the formal and actual argument lists.

Using void as an Argument


In ANSI C, you must use void to state explicitly the absence of function arguments. The
following program has a simple function named printer that receives no arguments and does not
return a value. The main function calls the function printer, when printer has completed its task,
control is returned to the main function.

/*
* C program will print a message with a function,
* Function uses a type void argument and sqrt function
* from the standard C library.
*/

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

void printer(void);
int main()
{
printf("This program will extract a square root. \n\n");
printer();
return (0);
}

void printer(void)
{
double z=5678.0;
double x;

x=sqrt(z);
printf("The square root of %1f is %f \n",z,x);
}

Notice that the printer function calls a C library function named sqrt. The prototype for this
library function, contained in math.h, accepts a double and returns the square root as a double
value.

Characters as Arguments
Characters can also be passed to a function. In the next example, a single character is intercepted
from the keyboard in the function main and passed to the function printer. The getch function
intercepts the character. In the standard C library, these other character functions are closely
related to getch: getc, getchar, and getche. The function intercepts a character from the standard
input device (keyboard) and returns a character value, without echo to the screen.

103
/*
* C program will accept a character from keyboard,
* pass it to a function and print a message using
* the character,
*/
#include <stdio.h>
#include <conio.h>

void printer(char ch);


int main()
{
char mychar;

printf("Enter a single character from the keyboard, \n”);


mychar=getch();
printer(mychar);
return (0);
}

void printer(char ch)


{

int i;
for(i=0;i<10;i++)
printf(“The character is %c \n”,ch);
}

Note that a single character is passed to the function. The function then prints a message and the
character ten times. The %c in the printf function specifies that a single character is to be printed.

Integers as Arguments
In the next example, a single int will be read from the keyboard with C’s scanf function. That int
will be passed to the function radius. The radius function uses the supplied radius to calculate
and print the area of a circle, the volume of a sphere, and the surface area of a sphere.

104
/*
* C program will calculate values given a radius.
* Function uses a type int argument, accepts radius
* from keyboard with scanf function*
*/
#include <stdio.h>

const float PI=3.14159;

void radius(int r);

int main()
{
int myradius;
printf("Enter the radius, as an integer,\n");
printf("from the keyboard. \n");
scanf (“%d”, &myradius);
radius(myradius);
return (0);
}

void radius(int r)
{
float area,volume,sarea;

area=PI*(float) (r*r);
volume=PI*4.0/3.0*(float) (r*r*r);
sarea=PI*4.0*(float) (r*r);

printf(“The radius is %d \n\n”,r);


printf(“A circle would have an area of %f \n”,area);
printf(“A sphere would have a volume of %f \n",volume);
printf(“The surface area of the sphere is %f \n",sarea);
}

While the value of radius is an int type, the calculations are cast to float. Notice that PI was
defined as a const.

Floats as Arguments
Floats are just as easy to pass as arguments as are integers. In the following C example, two float
values are passed to a function called hypotenuse. scanf intercepts both float values from the
keyboard.

105
/*
* C program will find hypotenuse of a right triangle.
* Function uses a type float argument and accepts
* input from the keyboard with the scant function.
*/
#include <stdio.h>
#include <math.h>

void hypotenuse(float x, float y);


int main()
{
float ylength,xlength;

printf("Enter the height of a right triangle. \n");


scanf(“%f",&ylength);
printf("Enter the base of a right triangle, \n”);
scanf("%f”,&xlength);
hypotenuse(ylength,xlength);
return (0);
}
void hypotenuse(float x,float y)
{
double myhyp;

myhyp=hypot((double) x,(double) y);


printf(“The hypotenuse of the triangle is %g \n”,myhyp);
}

Notice that both arguments received by the hypotenuse are cast to doubles when used by the
hypot function from math.h. All math.h functions accept and return double types. The next able
shows other mathematical functions that your programs can use.

106
Mathematical Functions Described in math.h

Function Name Description


abs Absolute value of a number
acos Arc cosine
asin Arc sine
atan Arc tangent
atan2 Arc tangent of two numbers
atof ASCII string to type float
cabs Absolute value of complex number
ceil Largest integer in list
cos Cosine
cosh Hyperbolic cosine
exp Exponential value
fabs Absolute value of float
floor Smallest integer in list
fmod Floating-point mod
hypot Hypotenuse of right triangle
log Natural logarithm
log10 Common logarithm
modf Return mantissa and exponent
poly Create polynomial
pow Raise n to power x
pow10 Raise 10 to power x
sin Sine
sinh Hyperbolic sine
sqrt Square root
srand Random number initializer
tan Tangent
tanh Hyperbolic tangent

Doubles as Arguments
The double type is a very precise float value. As you have learned all math.h functions accept
and return double types. The following program will accept two double values from the
keyboard. The function will raise the first number to the power specified by the second number.
Now you can find out that 146.63.2 is really equal to 8358270.07182.

107
/*
* C program will raise a number to a power,
* Function uses a type double argument and the pow function.
*/
#include <stdio.h>
#include <math.h>
void power(double x,double y);
int main()
{
double xnum,ynum;

printf("Enter the number to be raised to a power. \n");


scanf(“%1f”,&xnum);
printf("Enter the power. \n");
scanf(“%1f”,&ynum);
power(xnum,ynum);
return (0);
}
void power(double x,double y)
{
double result;

result=pow(x,y);
printf(“The result is %1f \n",result);
}

This function uses the pow function prototyped in math.h.

108
Function Types

Function Type void


Function Type char
Function Type int
Function Type long
Function Type float
Function Type double

This section will illustrate numerous function types. A function type is the type of value returned
by the function. None of the previous examples have returned information from the function and
thus were of type void.

Function Type void


You have already learned about void function types so the next example will be dressed up a bit.
C permits numeric information to be formatted in hexadecimal, decimal, and octal, but not
binary. Specifying data in a binary format is useful when you are doing binary arithmetic or
developing bit masks. The function binary will convert a decimal number entered from the
keyboard to a binary representation. The binary digits are not packed together as a single binary
number but are stored individually in an array. To view the binary number, you must print the
contents of the array.

/*
* C program illustrates the void function type.
* Program will print the binary equivalent of a number.
*/
#include <stdio.h>

109
void binary(int number);
int main()
{
int number;
printf("Enter a decimal number for conversion to binary.\n");
scanf("%d",&number);
binary(number);
return (0);
}

void binary(int number)


{
int i=0;
int myarray[40];

while (number !=0) {


myarray[i]=(number % 2);
number/=2;
i++;
}
i--;
for(;i>=0;i--)
printf("%1d",myarray[i]);
printf ("\n”);
}

You can convert base ten numbers to another base by dividing the number by the new base a
successive number of times. In the case of conversion to a binary number, a two is repeatedly
divided into a base ten number. The base ten number becomes the quotient from the previous
division. The remainder, after each division, is either a one or a zero. The remainder becomes the
binary digit. For example, to convert 10 to binary:

quotient remainder - - - >1 0 1 0 (binary)


10/2 5 0 (lsb) (msb) (lsb)
5/2 2 1
2/2 1 0
1/2 0 1 (msb)

In the function, a while loop performs the arithmetic as long as number has not reached zero. The
modulo operator determines the remainder and saves the bit in the array. Division is then

110
performed on number, saving only the integer result. This process is repeated until the quotient
(also number in this case) is reduced to zero.

The individual array bits, which form the binary result, must be unloaded from the array in
reverse order, as you can see from the preceding numeric example. Study the/or loop used in the
function.

Function Type char


Here is a minor expansion to an earlier example. The C function uppercase accepts a char
argument and returns the same. For this example, a lowercase letter received from the keyboard
is passed to the function. The function uses the toupper function (which is from the standard
library and is prototyped in ctype.h) to convert the character to uppercase. Functions related to
toupper include toascii and tolower.

/*
* C program illustrates the character function type.
* Function receives lowercase character and
* converts it to uppercase.
*/

#include <stdio.h>
#include <ctype.h>

char uppercase(char letter);


int main()
{
char lowchar,hichar;
printf("Enter a lowercase character. \n");
lowchar=getchar();
hichar=uppercase(lowchar);
printf("%c\n",hichar);
return (0);
}
char uppercase(char letter)
{
return(toupper(letter));
}

111
Function Type int
The following function accepts and returns int types. The function cube-number accepts a
number generated in main (0,2,4,6,8,10...),.cubes the number, and returns the int value to main.
The original number and the cube are printed to the screen.

/*
* C program illustrates the integer function type.
* Function receives integers, one at a time, and
* returns the cube of each, one at a time.
*/
#include <stdio.h>

int cubenumber(int number);

int main()
{

int i,cube;

for (i=0;i<20;i+=2) {
cube=cubenumber(i);
printf(“The cube of %d is %d \n”,i,cube);
}
return (0);
}

int cubenumber(int number)


{
return (number*number*number);
}

Function Type long


The following C program accepts an int value as an argument and returns a long. The function
will raise the number 2 to an integer power.
//
// C program illustrates the long integer function type.
// Function receives integers, one at a time, and
// returns 2 raised to that integer power.
//

112
#include <stdio.h>
long twopower(int number);
int main()
{
int i;
long weight;

for (i=0;i<31;i++) {
weight=twopower(i);
printf(“2 raised to the %d power is %ld\n”, I, weight);
}
return (0);
}
long twopower(int number)
{
int t;
long value=1l;

for (t=0;t<number;t++)
value*=2;
return (value);
}

The function simply multiplies the original number by the number of times it is to be raised to
the power. For example, if you want to raise 2 to the fourth power (24), the program will perform
the following multiplication equation:

2 * 2 * 2 * 2 = 16

Function Type float


The next C example will find the product of all the elements in an array. The array contains
floats and will return a float product.

// C program illustrates the float function type.


// Function receives an array of floats and returns
// their product as a float.

113
#include <stdio.h>
float times(float floatarray[ ]);

int main()
{
float myarray[5] ={1.2,4.5,7.05,6.14,0.09876};
float product;

product=times(myarray);
printf(“The product of the array's numbers is: %f \n“, product);
return (0);
}

float times(float floatarray[ ])


{
int t;
float temp;

temp=floatarray[0];
for (t=1;t<5;t++)
temp*=floatarray[t];
return (temp);
}

Since the elements are multiplied together, the first element of the array must be loaded into
temp before the for loop is entered.

Function Type double


The following C example accepts and returns a double type. The function trigsine will convert an
angle, expressed in degrees, to its sine value.
/*
* C program illustrates the double function type,
* Function receives integers from 0 to 90, one at a
* time, and returns the sine of each, one at a time,
*/
#include <stdio.h>
#include <math.h>

const double PI=3.14159265359;

double trigsine(double angle);

int main()

114
{
int i;
double sine;

for (i=0;i<91;i++) {
sine=trigsine((double) i);
printf(“The sine of %d degrees is %20.181f \n",i,sine);
}
return (0);
}

double trigsine(double angle)


{
double temp;
temp=sin((PI/180.0)*angle);
return (temp);
}

Notice that the sin function described in math.h is utilized by trigsine to obtain the answer.
Angles must be converted from degrees to radians for all trigonometric functions. Recall that PI
radians equal 180 degrees.

115
Storage Classes and Functions

extern Storage Class


static Storage Class

Functions can also use extern and static storage class types. A function is declared with an extern
storage class when it has been defined in another file, external to the present program. In a
somewhat related manner, a function can be declared static when external access apart from the
present program, is not permitted.

116
Variable Scope

Global Scope
File Scope
Local Scope

A local variable may be used completely within a function definition. Its scope is then limited to
the function. The variable is said to be accessible or visible within the function and has a local
scope.

Variables with a file scope are declared outside of individual functions. They have visibility or
accessibility throughout the whole file. Variables of this type are global in range.

The same variable may be used with a file scope and later within a function definition with a
local scope. In this case, the local scope takes precedence

117
Recursion

C Language supports recursive functions


Local variable is essintial to implment recursive
Recursion is another type of iteration
Some algorithms can be more easiar using the recursion
technique

Recursion occurs when a function calls itself. Recursion is permitted in C.


You can generate the factorial of a number with recursion, (The factorial of a number is defined
as the number multiplied by all successively lower integers.) For example:
6! =6*5*4*3*2*1 =
=720

Take care when choosing data types, since the product increases very rapidly. As an example, the
factorial of 14 is 87178291200.

/*
* C program illustrates recursive function calls.
* Calculation of the factorial of a number.
* Example: 5! =5x4x3x2x1- 120
*/
#include <stdio.h>
double factorial(double answer);

118
int main()
{
double number=20.0;
double fact;
fact=factoria(number) ;

printf(“The factorial is: %15.01f \n",fact);


return (0);
}
double factorial(double answer)
{
if (answer <= 1.0)
return(1.0);
else
return(answer*factorial(answer-1.0));
}

Notice that the function includes a call to itself. Also notice that the printf function uses a new
format code for printing a double value, %…If. Here, the l is a modifier to the f and specifies a
double instead of a float.

119
Function Arguments for main

Passing values from command line


Passing strings
Passing integers
Passing floats

C can accept command-line arguments. Command-line arguments are passed when the program
is called from DOS. For example,
C>MYPROGRAM Bill Chris Jeff Cindy Herb 10 20 30

In this example, eight values are passed from the command line to myprogram. Actually, it is
main that is given specific information. One argument received by main, argc, is an int giving
the number of command-line terms plus one. The program title is counted as the first term. The
second argument is a pointer to the strings called argv. All arguments are strings of characters, so
argv is of type char *[argc]. Since all programs have a name, argc is always one or greater. The
following examples explain various techniques for retrieving information from the command line

Strings
Since the arguments are passed as strings of characters, they are the easiest to work with. In the
following example, the program anticipates that the user will enter several names on the
command line. In fact, if argc isn't greater than two, the user will be returned to the command
linewith a reminder to try again and enter several names.

120
/*
* C program illustrates how to read string data
* into the program with a command-line argument.
*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[ ])
{
int i;
double sine;

if(argc<2) {
printf(“You must enter several names on the command\n”);
printf(“line when executing this program! Try again.\n”);
exit(1);
}

for (i=1; i<argc; i++)


printf(“Name #%d is %s\n”,i,argv[i]);
return (0);
}

The program is completely contained in main, with no additional functions. The names are
received on the command line and printed to the screen in the same order. If numbers are entered
on the command line, they will be interpreted as ASCII strings and must be printed as character
data.

Integers
This C example will accept a single int number on the command line. Since the number is
actually a character string, it must be converted to an integer via the atoi function. Then number
is passed to the binary function. The function will convert the value in number to a string of
binary digits be printed in octal and hexadecimal formats.

// C program illustrates how to read an integer


// into the program with a command-line argument.
//
#include <stdio.h>
#include <stdlib.h>

121
void binary(int digits);
int main(int argc, char *argv[ ])
{

int number;

if(argc!=2) {
printf("Enter a decimal number on the command line.\n");
printf("It will be converted to binary, octal and\n");
printf("hexadecimal.\n");
exit(1);
}

number=atoi(argv[1]);
binary(number);
printf(“The octal equivalent is: %od\n“, number);
printf(”The hexadecimal equivalent is: %x\n“, number);
return (0);
}

void binary(int digits)


{
int i=0;
int myarray[40];

while (digits != 0) {
myarray[i]=(digits % 2);
digits/=2;
i++;
}
i--;
printf(“The binary equivalent is: ");
for(;i>=0;i--)
printf(“%d”, myarray[i]);
printf(“\n”);
}

Floats
As you can imagine, floats will not be any more difficult to intercept than integers. The
following C example allows several angles to be entered on the command line. The sine of the
angles will be extracted and printed to the screen. Since the angles are of type float, they can take
on values such as 45.0, 76.32, or 0.02345.

122
/*
// C program illustrates how to read float data types ,
// into the program with a command-line argument,
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

const double PI=3.14159265359;

int main(int argc, char *argv[ ])


{
int i;
double angle;
if(argc<2) {
printf(“Enter several angles on the command line.\n”);
printf(“Program will return the sine of the angles.\n”);
exit(1);
}

for (i=1; i<argc; i++) {


angle=(double) atof(argv[i]);
printf(“The sine of %f is %15.141f\n”,
angle,sin((PI/180.0)*angle));
}
return (0);
}

The atof function converts the command-line string argument to a float type. The program
uses the sin function within the printf function to retrieve the sine information.

123
Unit 5

124
Arrays

Arrays is a set of hemogenous data


Arrays can be one dimentional or multidimensional
Strings in C are array of characters

In this Unit you will learn about arrays. An array is essentially a list of related variables and can
be very useful in a variety of situations. Since in C strings are simply arrays of characters, you
will also learn about strings and several of C’s string functions.

125
Declare One Dimensional Arrays

One dimensional array is a list of hemogenous variabls


Array element is an indivedual variable.
Array decleration has the general form
Type varName[Size]
Size of array must be constant value
Array index starts from 0

In C, a one-dimensional array is a list of variables that are all of the same type and are referenced
through a common name. An individual variable in the array is called an array element. Arrays
form a convenient way to handle groups of related data.
To declare a one-dimensional array, use the general form

Type var_name[size];

Where type is a valid C data type, var_name is the name of the array, and size specifies the
number of elements in the array. For example, to declare an integer array with 20 elements called
myarray, use this statement.

int myarray [20];

An array element is accessed by indexing the array using the element. In C, all arrays begin at 0.
This means that if you want to access the first element in array, use 0 for index. To index an

126
array, specify the index of the element you want inside square brackets. For example, the
following statement accesses the second element of myarray.

myarray[1];

Remember, arrays start at 0, so an index of 1 reference the second element.


To assign an array element a value, put the array on the left side of an assignment statement. For
example, this gives the first element in myarray the value 100.

myarray[0] = 100;

C stores one-dimensional arrays in one contiguous memory location with the first element at the
lowest address. For example, after this fragment executes,

int I [5] ;
int j ;
for (j=0; j<5; j++) I [j] =j ;

Array I will look like this:

0 1 2 3 4
0 1 2 3 4

You may use the value of an array element anywhere you would use a simple variable or
constant. For example the following program loads the sqrs array with the squares of the number
1 through 10 and then displays them.

#include “stdio.h”
int main()
{
int sqrs[10];
int i ;
for (i =1 ; i<11; i++) sqrs[i-1] = i*i ;
for (i =0 ; i<10; i++) printf(“%d “, sqrs [i] ) ;

return 0;

127
}

When you want to use scanf () to input a numeric value into an array element, simply put the &
in front of the array name. For example, this scanf () call reads an integer into count[9] .

scanf (“%d”, &count[9] ) ;

C does not perform any bounds checking on array indexes. This means that it is possible to
overrun the end of an array. For example, if an array called a is declared as having five elements,
the compiler will still let you access the (non-existent) tenth element with a statement like a[9].
Of course, attempting to access non-existent elements will generally have disastrous results,
often causing the program even the computer to crash. It is up to you, the programmer, to make
sure that the ends of arrays are never overrun.
In C, you may not assign one entire array to another. For example, this fragment is incorrect.

char a1 [10], a2[10];


.
.
.
a2 = a1 ; /* this is wrong */

If you wish to copy the values of all the elements of one array to another, you must do so by
copying each element separately.

Arrays are very useful in programming when lists of information need to be managed. For
example, this program reads the noonday temperature for each day of a month and then reports
the month’s average temperature, as will as its hottest and coolest days .

#include “stdio.h”
int main()
{
int temp[31], i , min, max, avg;
int days ;

printf (“How many days in the month ? “ ) ;


scanf (“%d”, &days ) ;

128
for (i =0 ; i <days ; i++) {
printf (“Enter temperature for day %d : “, i +1 );
scanf (“%d”, &temp [i]);
}
/* find average */
avg = 0 ;
for (i=0 ; i<days ; i++) avg += temp [i] ;
printf (“Average temperature : %d \n “, avg / days ) ;

/* find min and max */


min =200 ; /* initialize min and max */
max = 0 ;
for (i =0 ; i< days ; i++ ) {
if (min > temp[i] ) min = temp [i] ;
if (max < temp [i]) max =temp [i] ;
}
printf (“Minimum temperture : %d\n”, min ) ;
printf (“Maximum temperture : %d\n “, max) ;
return 0;
}

As stated earlier, to copy the contents of one array to another, you must explicity copy each
element separately. For example, this program loads a1 with the numbers 1 through 10 and then
copies them into a2 .

#include “stdio.h”
int main()
{
int a1[10], a2 [10];
int i ;
for (i =1 ; i< 11; i++ ) a1 [i] =i ;
for (i =1 ; i< 11; i++ ) a2 [i] =a1 [i] ;
for (i =1 ; i< 11; i++ ) printf (“%d “, a2[i] ) ;
return 0;
}

Arrays are especially useful when you want to sort information. For example, this program lets
the user enter up to 100 numbers and then sorts them. The sorting algorithm is the bubble sort.
The general concept behind the bubble sort, indeed how it got its name, is the repeated
comparisons and, if necessary, exchanges of adjacent elements. This is a little like bubbles in a
tank of water with each bubble, in turn, seeking its own level.

129
#include “stdio.h”
#include “stdlib.h”

int main()
{
int item [100];
int a , b, t ;
int count ;

/* read in numbers */
printf(“How many numbers? “);
scanf(“%d”, & count );
for (a=0 ; a <count ; a++ ) scanf (“%d”, &item [a]);

/* now, sort them using a bubble sort */


for (a=1 ; a <count ; a++ )
for (b= count –1; b>=a ; --b ) {
/* compare adjacent elements */
if (item [b-1] > item [b] ) {
/* exchange elements */
t = item [b-1];
item [b-1] = item [b];
item [b] = t;
}
}
/* display sorted list */
for (t =0 ; t<count ; t++ ) printf (“%d”, itemp [t]) ;
return 0;
}

130
Use Strings

String is an array of characters


A null character detrmines the end of string
Some string functions
Strcpy
Strcat
Strcmp
strlen

The most common use of the one-dimensional array in C is the string. Unlike most other
computer language C has built in string data type. Instead, C supports strings using one
dimensional character arrays. A string is defined as a null terminated array. In C, a null is 0. The
fact that you must define the array that is going to hold a string to be one byte lager than the
largest string it will be required to hold, to make room for the null. A string constant is also null
terminated by the compiler automatically.
To read a string from the keyboard you must use another C’s standard library function, gets(),
which requires the STDIO.H header file. To use gets(), call it using the name of a character array
without any index. The gets() function reads characters until you press RETURN. The carriage
return is not stored, but is replaced by null, which terminates the string. For example, this
program reads and writes a string entered at the keyboard.

#include “stdio.h”

int main()
{

131
char star[80] ;
int i ;

printf(“Enter a string (less then 80 chars ): \n”);


gets(str) ;
for (i =0 ; str[i] ; i++ ) printf(“%c”, str [i] );
return 0;
}

Notice how the program uses the fact that a null is false to control the loop that outputs the
string. The gets() function performs no bounds checking, so it is possible for the user to enter
more characters than the string that gets() is called with can hold.

Therefore, be sure to call it with an array large enough to hold the expected input.
In the user was output to the screen a character at a time. There is however, a much easier way to
display a string, using printf (). Here is the previous program rewritten.

#include “stdio.h”

int main()
{
char star[80] ;

printf(“Enter a string (less then 80 chars ): \n”);


gets(str) ;
printf (str); /* output the string */
return 0;
}

Since the first argument to printf () is a string, you simply use str without any index as the first
argument to printf(). If you wanted to output a newline, you could output str like this :

printf( ‘%s\n “, str ) :

This method uses %s format specifier followed by the newline character and uses the array as a
second argument to be matched by the %s specifier .
The C standard library supplies many string related strcmp(), and strlen(). These functions
require the header file STRING.H. Let’s look at each now.

132
The strcpy() function has this general form.

strcpy (to, from);

It copies the contents of from to to. The contents of from are unchanged. For example, this
fragment copies the string “hello str and displays it on the screen.

char str [80] ;

strcpy (str, “hello”);


printf(“%s”, str);

The strcpy() function performs no bounds checking, so you must make sure that the array on the
receiving end is large enough to hold what is being copied, including the null terminator .
The strcat() function adds the contents of one string to another. This is called concatenation. Its
general form is

strcat(to, from);

It adds the contents of from to the contents of to. It performs no bounds checking, so you much
make sure that to is large enough to hold its current contents plus what it will be receiving. This
fragment displays hello there.

char str [80];

strcpy(str, “hello”);
strcat(str, “there );
printf (str);

The strcmp() function compares two strings. It takes the general form.

strcmp(s1, s2);

It returns 0 if the strings are the same. It returns less then 0 if s1 is less s2 and greater then 0 if s1
is greater than s2. The strings are compared lexicographically; that is, in dictionary order.
Therefore, a string is less then another when it would appear before the other. The comparison is
not based upon the length of the string. Also, the comparison is case sensitive, lowercase
characters being greater than uppercase. This fragment prints 0, because the strings are the same.

133
printf(“%d”, strcmp(“one “, “one”));

The strlen() function returns the length, in characters, of a string. Its general form is

strlen(str) ;

The strlen() function does not count the null terminator. This means that if strlen() is called
using the string “test”, it will return 4 .

This program requests input of two strings, then demonstrates the four string function with them.

#include “string.h”
#include “stdio.h”

int main()
{
char str1[80] , str2 [80] ;
int i;

printf(“Enter the first string : “) ;


gets(str1);
printf(“Enter the second string : “);
gets ( str2);

/* see how long the strings are */


printf(“%s is %d chars long \n “, str1, strlen(str1)) ;
printf(“%s is %d chars long \n “, str2 , strlen(str2));

/* compare the string */


i = strcmp (str1, str2 ) ;
if (!i) printf(“the strings are equal .\n”);
else if(i< 0) printf (“%s is less than %s\n “, str1, str2 );
else printf (“%s is greater than %s\n “, str1, str2 );

/* Concatenate str2 to end of str1 if


there is enough room */
if (strlen (str1 ) + strlen (str2 )< 80 ) {
strcat (str1, str2 );
printf(“%s\n”, str1);
}
/* copy str2 to str1 */
strcpy (str1, str2);
printf(“%s \n”, str1 , str2);

134
return 0;
}

One common use of strings is to support a command-based interface. Unlike a menu, which
allows the user to make a selection, a command-based interface displays a prompting message,
waits for the user to enter a command, and then does what the command requests. Many
operating system use command line interfaces, for example. The following program is similar
to a program developed in previously. It allows the user to Add, subtract, multiply, or divide, but
does not use a menu. Instead, it uses a command line interface.

#include “string.h”
#include “stdio.h”
#include “stdlib.h”

int main()
{
char command[80] , temp [80] ;
int i , j;
for ( ; ; ) {
printf(“Operation ? “);
gets (command);

/* see if user wants to stop */


if (!strcmp (command, “quit”)) break;

printf(“Enter first number: “);


gets(temp);
i= atoi (temp);

printf (“Enter second number : “) ;


gets(temp);
j= atoi (temp);

/* Now, perform the operation */


if (! strcmp (command, “add”))
printf(“%d\n “, i+j );
else if (!strcmp (command, “ subtract”))
printf(“%d\n “, i-j );
else if (!strcmp (command, “devide”)) {
if (j) printf(“%d\n “, i/j );
}
else if (!strcmp (command, “multiply”))
printf(“%d\n “, i*j );

135
else printf( “ unknown command. \n “);
}
return 0;
}
Notice that this example also introduces another of C’s standard library function: atoi(). The
atoi() function returns the integer equivalent of the number represented its string argument. For
example, atoi(“100”) returns the value 100. The atoi () function uses the header file STDLIB.H.

You can create a string of length 0, using a strcpy() statement like this :

strcpy(str, “”);

Such a string is called a null string. It contains only one element : the null terminator .

136
Create Multidimensional Arrays

Array may be multidimensional


Two dimension array decleration has the general form
Type varName[RowSize][ColSize];
Array element can be determined by two indexes
varName[Row][Col]
An array of strings is a two dimension array of characters

In addition to one-dimensional arrays, you create arrays of two or more dimension. For example,
to create a 10x12 two dimensional integer array called count, you would use this statement.

int count [10] [12];

As you can see in the example, to add a dimension, you simply specify its size inside square
brackets. A two dimensional array is essentially an array of one-dimensional arrays and is most
easily thought of in a row, column format. For example, given a 4x5 integer array called two_d,
you can think of it looking like that shown in the following figure. Assuming this rightmost
index will change most quickly when the array is accessed sequentially from the lowest to
highest memory address.
0 1 2 3 4
0
1
2
3

137
Two-dimensional arrays are used like one-dimensional ones. For example, this program loads a
4x5 arrays with the products of the indices, then displays the array in row, column format .

#include “stdio.h”

int main()
{
int twod [4] [5] ;
int i, j;
for (i= 0; i < 4; i++)
for (i= 0; i < 5; i++)
twod [i] [j] = i * j ;

for (i= 0 ; i < 4 ; i ++ ) {


for (j= 0; j < 5; j++)
printf (“%d “ , twod [i] [j]) ;
printf (“\n”) ;
}
return 0;
}

The program output looks like this:

00000
01234
02468
036912

To create arrays of three dimensions and greater, simply add the additional. For example, the
following statement creates a 10x12x8 three dimensional array.

float values [10] [12] [8] ;

A three dimensional array is essentially an array of two-dimensional arrays.

A good use of a two dimensional array is to manage lists of numbers. For example, you could
use this two dimensional array to hold the noontime temperature for each day of the year,
grouped by month .

138
float yeartemp [12] [31] ;

In the same vein, the following program can be used to keep track of the number of points scored
per quarter by each member of a basketball team.

#include “stdio.h”

int main()
{
int bba11 [5] [4] ;
int i, j;

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


for (i= 0; i < 5; i++) {
printf (“Quarter %d, player %d, “, i+1 , j+1 );
printf (“Enter number of points : “ );
scanf (“%d “, & bba11 [i] [j] ;
}
/* display results */
for ( i=0 ; i<4 ; i++)
for (j=0; j < 5; j++) {
printf(“Quarter %d, player %d, “, i+1 , j+1 );
printf (“%d\n”, bba11 [i] [j] ;
}
return 0;
}

Initialize Arrays
Like other types of variables, you can give the elements of arrays initial values. This is
accomplished by specifying a list of values the array elements will have. The general form of
array initialization for one dimensional arrays is shown here:

Type array name [size] = {value-list } ;

The value-list is a comma-separated list of constants that are type compatible with the base type
of the array. The first constant will be placed in the first position of the array the constant in the
second position, and so on. Note that a semicolon follows the }. In the following example, a five
element integer array is initialized with the squares of the numbers 1 through 5 .

139
int I [5] = { 1, 4 , 9, 16 , 25 };

This means that I [0] will have the value 1 and I [4] will have the value 25.
You can initialize character arrays two ways. First, if the array is not holding a null terminated
string, you simply specify each character using a comma-separated list. For example, this
initializes a with the letter ‘A’ , ‘B’, and ‘C’ .

char a [3] = {‘A’ , ‘B’ , ‘C’ } ;

If the character array is going to hold a string , you can initialize the array using a quoted string,
as shown here .

char name [5] = “Herb”;

Notice that no curly braces surround the string. They are not used in this form of initialization.
Because strings in C must end with a null, you must make sure that the array you declare is long
enough to include the null. This is why name is 5 character long, even though “Herb” is only 4.
When a string constant is used, the compiler automatically supplies the null terminator.
Multidimensional arrays are initialized in the same way as one sqr is initialized with the values 1
through 9 , using row order .

Int sqr [3] [3] = {


1, 2, 3,
4, 5, 6,
7, 8, 9
};

This initialization causes sqr[0] [0] to have the value 1, sqr[0] [1] to contain 2, sqr [0] [2] to hold
3, and so forth.
If you are initializing a one-dimensional array, you need not specify the size of the array simply
put nothing inside the square brackets. if you don’t specify the size, the compiler simply counts
the number of initialization constants and uses that value as the size of the array. For example,

int pwr[] = {1, 2, 4, 8, 16, 32, 64, 128};

140
Causes the compiler to create an initialized array eight elements long. Array that don’t have their
dimensions explicitly specified are called unsized arrays. An unsized array is useful because it is
easier for you to change the size of the initialization list without having to count it and then
change the array dimension. This helps avoid counting errors on long lists, which is especially
important when initializing string. Here, an unsized array is used to hold a prompting message.

char prompt[ ] = “enter your name : “ ;

If, at a later date, you wanted to change the prompt to “Enter your last name: “ you would not
have to count the characters and then change the array size.
Unsized array initializations are not restricted to only singly dimensioned array. For
multidimensional array you must specify all but the leftmost dimensional to allow C to index the
array properly. In this way you may build tables of varying lengths with the compiler allocating
enough storage for them automatically. For example, the declaration of sqr as an unsized array is
shown here .
Int sqr[ ] [3] = {
1, 2, 3,
4, 5, 6,
7, 8, 9
};

The advantage to this declaration over the sized version is that tables may be lengthened or
shortened without changing the array dimensions.

A common use of an initialized array is to crate a lookup table. For example, in this program a
5x2 two-dimensional array is initialized so that the first element in each row is the number of a
CPU, and the second element contains its average speed rating. The program allows a user to
enter the number of a processor, and then it looks it up the table and reports its average speed.

141
#include “stdio.h”

int main ( )
{
long cpu [5] [2] = {
8088, 4,
8086, 4,
80286, 10,
80386, 20,
80486, 40 };
long processor ;
int i ;

printf (“Enter the number of the processor : “) ;


scanf(“%d”, & processor ) ;

/* look it up in the table */


for (i =0 ; i < 5 ; i ++ )
if (processor == cpu [i] [0] ) {
printf (“Average speed is %d mhz . \n “, cpu [i] );
break ;
}
/* report error if not found */
if (i == 5 ) printf(“processor not found . \n “) ;
return 0;
}

Even though an array has been given an initial value, its contents may be changed . for example
this program prints hello on the screen .

#include “stdio.h”
#include “string.h”

int main( )
{
char str [80] = “ I like C” ;
strcpy (str, “hello”) ;
printf(str ) ;
return 0;
}

142
Build Arrays of Strings
Arrays of strings, often called string tales, are very common in C programming. A two-
dimensional string table is created like any other two dimensional array. However, the way you
think about it will be slightly different. For example, here is a small string table. What do you
think it defines?

char names [10] [40];

This statement specifies a table that can contain 10 strings, each up to 40 character long
(including the null terminator). To access a string within this table, specify only the first index.
For example, to read a string from the keyboard into the third string in names, use this statement.

gets(names [2]) ;

By the same token, to output the first string, use this printf ( ) statement .

printf( names [0]);

The declaration that follows creates a three dimensional table with three lists of strings . Each list
is five string long, and each string can hold 80 characters.
char animals [3] [5] [80];

To access a specific string in this situation, you must specify the first two dimensions. For
example, to access the second string in the third list, specify animals [2] [1].

This program lets you enter ten strings, then lets you display them, one at a time, in any order
you choose .
To stop the program, enter a negative number .

143
#include “stdio.h”

int main( )
{
char text [10] [80] ;
int i ;
for (i =0 ; i <10; i++){
printf (“%d : “, i + 1 );
gets (text [i]);
}
do {

printf (“Enter number of string (1-10): “) ;


scanf(“%d”, &i);
i --; /* adjust value to match array index */
if (i >=0 && i < 10) printf (“%s\n”, text [i] );
} while (i>0) ;
return 0;
}

You can initialize a string table as you would any other type of array. For example, the following
program uses an initialized string table to translate between German and English. Notice that
curly braces are needed to surround the list. The only time they are not needed is when a single
string is being initialized.

/*English to German Translator. */

#include “stdio.h”
#include “string.h”

int main( )
{
char words [ ] [2][40] = {
“dog”, “Hund”,
“no”, “nein”,
“year”, “Jahr”,
“child”, “Kind”,
“I”, “Ich”,
“drive “, “ fahren “,
“house “, “Haus”,
“to”, “zu”,
“”, “”
};

144
char english [80] ;
int i ;

printf(“Enter English word: “) ;


gets (english );

i=0;
/* Search while null string not yet encountered */
while (strcmp (words [i] [0] , “” )) {
if (!strcmp (english , words [i] [0] )) {
printf (“German translation : %s “, words [i] [1] ) ;
break ;
}
i++ ;
}

if (!strcmp (words [i] [0] , “” ))


printf (“Not in dictionary \n “ ) ;

return 0;
}

You can access the individual characters that comprise a string table by using the rightmost
index. For example, the following program prints the string in the table one character at a time.

#include “stdio.h”
int main( )
{
char text [] [80] = {
“when “, “in”, “the “,
“course “, “of “, “human “ ,
“events “ , “”
};
int i, j;

/* now , display them */


for ( i = 0 ; text [i] [0] ; i++) {
for ( j = 0 ; text [i] [j] ; j++)
printf(“%c”, text [i] [j] ) ;
printf (“ “);
}
return 0;
}

145
Structures

A Structure decleration is of the following form:


struct structure_name {
type_1 field_1;
type_2 field_2;
:
:
type_n field_n:
};

Here struct specifies that the following decleration is a structure, structure_name is the name
of the structure, field_i is the name of the ith field of the structure and its type is type_i.
Some important points about structures:
• The field names of a structure must all be distinct.
• However, two different structure types may share a field name. No ambiguity arises since
we specify the structures type also while accessing any field.
• For the same reason, a field may have the same name as some variable in the program.
• It is possible that a field of a structure is of another structure type.
• To copy the value of one structure variable to another, one can use the assignment
operator.

146
Unit 6

147
Dynamic Allocation

Pointers is C variable Type


Pointers carries an address of another variable or defined
memory location
Some Arithmatic operations can act on a pointer
A special relation between pointers and arrays

This unit covers one of C's most important and sometimes its most troublesome feature: the
pointer. A pointer is basically the address of an object. One reason that pointers are so
important is that much of the power of the C language is derived from the unique way in which
they are implemented in C. You will learn about the special pointer operators, pointer
arithmetic, and how arrays and pointers are related. Also, you will be introduced to using
pointers as parameters to functions.

148
Understand Pointer Basics

Pointer decleration
type *varName;
The & operator returns the address of a variable
The * operator returns the value stored at the address
Pointers can deal with only the addition and subtraction
operations.

A pointer is a variable that holds the memory address of another object. For example, if a
variable called p contains the address of another variable called q, then p is said to point to q.
Therefore if q is at location 100 in memory, then p would have the value 100.
To declare a pointer variable/ use this general form.

type *var name;

Here, type is the base type of the pointer. The base type specifies the type of the object that the
pointer can point to. Notice that an asterisk precedes the variable name. This tells the computer
that a pointer variable is being created. For example the following statement creates a pointer to
an integer

int *p;

C contains two special pointer operators: * and &.

149
The & operator returns the address of the variable it precedes. The * operator returns the value
stored at the address that it precedes. (The * pointer operator has no relationship to the
multiplication operator, which uses the same symbol.) For example examine this short program.

#include "stdio.h"

int main( )
{
int *p, q;
q = 100; /* assign q 100 */
p = &q; /* assign p the address of q */

printf("%d”, *p); /* display q's value using pointer */

return 0;

This program prints 100 on the screen. Lets see why. First, the line
int *p, q;
Defines two variables: p, which is declared as an integer pointer, and q, which is an integer.
Next, q is assigned the value 100. In the next line, p is assigned the address of q. You can
verbalize the & operator as "address of." Therefore this line can be read as "assign p the address
of q." Finally, the value is displayed using the * operator applied to p. The * operator can be
verbalized as "at address." Therefore, the printf() statement can be read as "print the value at
address q" which is 100.

When a variable's value is referenced through a pointer, the process is called indirection is
possible to use the *operator on the left side of an assignment statement in order to assign a
variable a new value using a pointer to it. For example this program assigns q a value indirectly
using the pointer p.

#include "stdio.h"

int main( )
{

150
int *p, q;

p = &q; /* get q's address */

*p = 199; /* assign q a value using a pointer */

printf("q's value is %d", q) ;

return 0;
}

In the two simple example programs just shown, there is no reason to use a pointer. However, as
you learn more about C, you will understand why pointers are important. Pointers are used to
support linked lists and binary trees, for example. The base type of a pointer is very important-
Although C allows any type of pointer to point anywhere in memory, it is the base type that
determines how the object pointed to will be treated. To understand the importance of this,
consider the following fragment.
.
int q;
float *fp;
fp = &q;
/* what does this line do? •*-/
*fp = 100.23;

Although not syntactically incorrect, this fragment is wrong. The pointer fp is assigned the
address of an integer. This address is then used on the left side of an assignment statement to
assign a floating-point value. However, ints are shorter than floats, and this assignment statement
causes memory adjacent to q to be overwritten. That is, assuming 2-byte ints and 4-byte floats,
the assignment statement uses the 2 bytes allocated to q as well as two adjacent bytes, which will
most likely be where fp is stored, thus causing an error.

In general, the C compiler uses the base type to, determine how many bytes are in the object
pointed to by the pointer. This is how C knows how many bytes to copy when an indirect
assignment is made, or how many bytes to compare if an indirect comparison is made. Therefore,
it is very important that you always use the proper base type for a pointer. Never use a pointer to
one type to point to an object of a different type.

151
If you attempt to use a pointer before it has been assigned the address of a variable, your
program will probably crash. Remember, declaring a pointer variable simply creates a variable
capable of holding a memory address. It does not give it any meaningful initial value. This is
why the following fragment is incorrect.

main( )
{int *p;
*p == 10; /* incorrect - p is not pointing to anything */

As the comment notes, the pointer p is not pointing to any known object. Hence, trying to
indirectly assign a value using p is meaningless and dangerous

As pointers are defined in C, a pointer that contains a null value (0) is assumed to be unused and
pointing at nothing. In C, 0 is, by convention, assumed to be an invalid memory address.
However, the compiler will still let you use a null pointer, usually with disastrous results.

To graphically illustrate how indirection works, assume these declarations


int *p, q;
Further assume that q is located at memory address 102 and that p is right before it, at location
100. After this statement
p = &q;
the pointer p contains the value 102. Therefore, after this assignment, memory looks like this:

Location Contents

100 102

102 unknown

After the statement


*p = 1000;
executes, memory looks like this

152
Location Contents

100 102

102 1000

Remember, the value of p has nothing to do with the value of q. It simply holds q's address, to
which the indirection operators may be applied.
To illustrate why you must make sure that the base type of a pointer is the same as the object
it points to, try this incorrect but benign program

/* This program is wrong, but harmless- */


#include "stdio.h"
int main( )
(
int *p;
float q, temp;
temp = 1234.34F;
p = &temp; /* attempt to assign q a value */
q = *p; /* using indirection through an integer pointer */
printf("%f", q); /* this will not print 1234.34 */
return 0;
}

Even though p points to temp, which does, indeed, hold the value 1234.34, the assignment
q = *p;
Fails to copy the number because only 2 bytes (assuming 2-byte integers) will be transferred.
Since p is an integer pointer, it cannot be used to transfer a 4-byte quantity (assuming 4-byte
floats)

Restrictions to Pointer Expressions


In general, pointers may be used like other variables. However, you need to understand a few
rules and restrictions

153
In addition to the * and & operators, there are only four other operators that may be applied to
pointer variables: the arithmetic operators +, ++, - , and --. Further, you may add or subtract only
integer quantities You cannot, for example, add a floating-point number to a pointer.
Pointer arithmetic differs from "normal" arithmetic in one very important way: it is performed
relative tom the base type of the pointer. Each time a pointer is incremented, it will point to the
next item, as defined by its base type, beyond the one currently pointed to. For example, assume
that an integer pointer called p contains the address 200. After the statement

p++;

Executes, p will have the value 202, assuming integers are two bytes long. By the same token, if
p had been a float pointer (assuming 4-byte floats), then the resultant value contained in p would
have been 204.
The only pointer arithmetic that appears as "normal" occurs when char pointers are used.
Because characters are one byte long, an increment increases the pointer's value by one, and a
decrement decreases its value by one.
You may add or subtract any integer quantity you want to or from a pointer. or example, the
following is a valid fragment.

int *p
.
.
.
p = p + 200;

This statement causes p to point to the 200th integer past the one to which p was currently
pointing.

Aside from addition and subtraction of an integer, you may not perform any other type of
arithmetic operations you may not multiply, divide, or take the modulus of a pointer.

It is possible to apply the increment and decrement operators to either the pointer itself or the
object to which it points. However, you must be careful when attempting to increment the object
pointed to by a pointer. For example, assume that p points to an integer that contains the value 1.
What do you think the following statement will do?

154
*p++;

Contrary to what you might think,, this statement first increments p and then obtains the value at
the new location. To increment what is pointed to by a pointer, you must use a form like this:

(*p)++;

The parentheses cause the value pointed to by p to be incremented.


You may compare two pointers using the relational operators. However, pointer comparisons
only make sense if the pointers relate to each other if they both point to the same object, for
example. Shortly you will see an example of pointer comparisons.

At this point you might be wondering what use there is for pointer arithmetic. You will shortly
see, however that it is one of the most valuable components of the C language.

You can use printf() to display the memory address contained in a pointer by using the %p
format specifier, We can use this printf capability to illustrate several aspects of pointer
arithmetic. The following
Program, for example, shows how all pointer arithmetic is relative to the base type of the pointer.

#include "stdio.h"
int main( )
{
char *cp, ch;
int *ip, i;
float *fp, f;
double *dp, d;
cp = &ch;
ip = &i;
fp = &f;
dp = &d;
/* print the current values */
printf("%p %p %p %p\n", cp, ip, fp, dp);
/* now increment them by one */
cp++;
ip++ ;
fp++;
dp++ ;

155
/* print their new values */
printf("%p %p %p %p\n", cp, ip, fp, dp);
return 0;
}

Although the values contained in the pointer variables in this program will vary widely between
compilers and even between versions of the same compiler, you will see that the address pointed
to by ch will be incremented by one byte. The others will be incremented by the number of bytes
in their base types, typically 2 for ints, 4 for floats, and 8 for doubles.

The following program illustrates the need for parentheses when you want to increment the
object pointed to by a pointer instead of the pointer itself.
#include "stdio.h"
int main( )
{
int *p, q;
p = &q;
q = 1;
print£("%p ", p) ;.
*p++; /* this will not increment q */
printf("%d %p", q, p) ;
return 0;
}

After this program has executed, q still has the value,, but p has been incremented. However, if
the program is written like this
#include "stdio.h"
int main( )
{
int *p, q;
p = &q;
q = 1;
printf("%p ", p);
(*p)++; /* now q is incremented and p is unchanged •*/
printf("%d %p", q, p) ;
return 0;
}
q is incremented to 2 and p is unchanged

156
Use Pointers with Arrays

Relation between pointers and arrays


name without an index is a pointer to the start of the array
Using pointer arithmetic you can access array elements
Pointers to String Constants
Arrays of Pointers
Multiple Indirection

In C, pointers and arrays are closely related. In fact, they are often interchangeable. It is this
relationship between the two that makes their implementation both unique and powerful.
When you use an array name without an index, you are generating a pointer to the start of the
array. This is why no indexes are used when you read a string using gets(), for example. What is
being passed to gets() is not an array, but a pointer. In fact, you cannot pass an array to a
function in C; you may only pass a pointer to the array. This important point was not mentioned
in the preceding Unit on arrays because you had not yet learned about pointers- However, this
fact is crucial to understanding the C language. The gets() function uses the pointer to load the
array it points to with the characters you enter at the keyboard. You will see how this is done
later.
Since an array name without an index is a pointer to the start of the array, it stands to reason that
you can assign that value to another pointer and access the array using pointer arithmetic. And, in
fact, this is exactly what you can do. Consider this program.

#include "stdio.h"
int main( )
{

157
int a[10] = {1, 2, 3, 4, 5, 6, 7/ 8, 9, 10};
int *p;
p = a; /* assign p the address of start of a */
/* this prints a's first/ second and third elements */
printf("%d %d %d\n", *p, *(p+1), *(p+2));
/* this does the same thing using a */
printf("%d %d %d", a[0], a[1], a[2]);
return 0;
}

Here, both printf() statements display the same thing. The parentheses in expressions such as
*(p+2) are necessary because the * has a higher precedence than the + operator.

Now you should be able to fully understand why pointer arithmetic is done relative to the base
type it allows arrays and pointers to relate to each other.

To use a pointer to access multidimensional arrays, you must manually do what the compiler
does automatically. For example, in this array

float balance [10] [5];

each row is five elements long. Therefore, to access balance[3][l] using a pointer, (assume p is a
float pointer) you must use a fragment like this

* (p + (3*5) + 1)

To reach the desired element, you must multiply the row number by the number of elements in
the row and then add the number of the element within the row. Generally, with
multidimensional arrays it is easier to use array indexing rather than pointer arithmetic.

Pointers and arrays are linked by more than the fact that by using pointer arithmetic you can
access array elements. You might be surprised to learn that you can index a pointer as if it were
an array. The following program, for example, is perfectly valid.
#include "stdio.h"
int main( )
{
char str[ ] = "Pointers are fun";
char *p;
int i;
p = str;
/* loop until null is found */
for(i=0; p[i]; i++)
printf("%c", p[i]) ;

158
}

Keep one point firmly in mind; you should only index a pointer when that pointer points to an
array. While the following fragment is syntactically correct, it is wrong; if you tried to execute it,
you would probably crash your computer.

char *p ch;
int i;
p = &ch; /* wrong */
for(i=0; i<10; i++)
p[i] = 'A'+i;

Since ch is not an array it cannot be meaningfully indexed. Although you can index a pointer as
if it were an array, you will seldom want to do this. The reason is that, in general, using pointer
arithmetic is faster than using array indexing. For somewhat complex reasons a C compiler will
generally create faster executable code for an expression such as

*(p+3)

than it will for the comparable array index

p[3J

Because an array name without an index is a pointer to the start of the array, you can if you
choose, use pointer arithmetic rather than array indexing to access elements of the array. For
example, this program is perfectly valid and prints c on the screen.

#include "stdio.h"
int main( )
{

char str[80];
*(str+3) = ‘c’;
printf ("%c", *(str+3)) ;
}

You cannot, however, modify the value of the pointer generated by using an array name. For
example, assuming the previous program, this is an invalid statement:

str ++;

159
The pointer that is generated by str must be thought of as a constant that always points to the
start of the array. Therefore, it is invalid to modify it and the compiler will report an error.

Two of C's library functions/ toupper() and tolower(), are called using a character argument. In
the case of toupper(), if the character is a lowercase letter, the uppercase equivalent is returned;
otherwise the character is returned unchanged. For tolower(), if the character is an uppercase
letter, the lowercase equivalent is returned; otherwise the character is returned unchanged. These
functions use the header file CTYPE.H. The following program requests a string from the user
and then prints the string, first in uppercase letters and then in lowercase- This version uses array
indexing to access the characters in the string so they can be converted into the appropriate case.

#include "ctype.h"
#include "stdio.h"
int main( )
{
char str[80];
int i ;
printf("Enter a string: ") ;
gets (str) ;
for(i=0; str[i]; i++)
str[i] = toupper(str[i]);
printf ("%s\n", str); /* uppercase string */
for(i=0; str[i]; i++)
str[i] = tolower(str[i] ) ;
printf ("%s\n", str); /* lowercase string */
return 0;
}

The same program is shown below, only this time, a pointer is used to access the string. This
second approach is the way you would see this program written by professional C programmers
because incrementing a pointer is generally faster than indexing an array.
#include "ctype.h"
#include "stdio.h"
int main ( )
{
char str [80], *p;
printf("Enter a string: ");
gets(str) ;
p = str;

160
while(*p) {
*p = toupper(*p);
p++;
}
printf("%s\n", str); /* uppercase string */

p = str; /* reset p */
while(*p) {
*p = tolower(*p) ,
p++;
}
printf("%s\n", str); /* lowercase string */
return 0;
}

Before leaving this example, a small digression is in order. The routine


while(*p) {
*p = toupper (*p) ;
p++;
}

will generally be written like this by experienced programmer

while(*p)

*p++ = toupper(*p);

Because the ++ follows the p, the value pointed to by p is first obtained and then p is
incremented to point to the next element
Remember that, although most of examples have been incrementing pointers, you can decrement
a pointer as well. For example, the following program uses a pointer to copy the contents of one
string into another in reversed order.
#include "stdio.h"
#include "string.h"
int main( )
{
char strl[ ] = "Pointers are fun to use";
char str2 [80], *p1, *p2;
/* make p1 point to end of strl */
p1 = strl + strlen(strl) - 1;
p2 = str2;
while(p1 >= strl) *p2++ = *p1--;
/* null terminate str2 */

161
*p2 = '\0' ;
printf("%s %s", strl, str2) ;
return 0;
}

This program works by setting p1 to point to the end of str1, and p2 to the start of str2. It then
copies the contents of strl into str2 in reverse order. Notice the pointer comparison in the while
loop. It is used to stop the copying process when the start of strl is reached.
Also, notice the use of the compacted forms *p2++ and *pl--. The loop is the equivalent of this
one.
while (pi >= strl) {
*p2 = *pl;
p1--;
p2++;
}

Use Pointers to String Constants


As you know, C allows string constants enclosed between double quotes to be used in a program.
When the compiler encounters such a string, it stores it in the program's string table and
generates a pointer to the string. For this reason, the following program is correct and prints one
two three on the screen.
#include "stdio.h"
int main ( )
{
char *p;
p = "one two three";
printf(p) ;
return 0
}

Let's see how this program works. First, p is declared as a character pointer. This means that it
may point to an array of characters. When the compiler compiles the line
p = "one two three";'
it stores the string in the program's string table and assigns to p the address of the string in the
table. Therefore, when p is used in the printf() statement, one two three is displayed on the
screen.
This program can be written more efficiently, as shown here.

162
#include "stdio.h"
int main ( )
{
char *p = "one two three";
printf(p) ;
return 0;
}

Here, p is initialized to point to the string.

This program continues to read strings until you enter stop.


#include "stdio.h"
#include "string.h"
int main( )
{
char *p = "stop";
char str[80] ;
do{
printf("Enter a string: ") ;
gets (str) ;
} while(strcmp(p, str)) ;
return 0;
}

Using pointers to string constants can be very helpful when those constants are quite long. For
example, suppose that you had a program that at various different points would prompt the user
to insert a diskette into drive A. To save typing, you might elect to initialize a pointer to the
string and then simply use the pointer when the message needed to be displayed; for example:
char *p = "Insert disk into drive A, then press ENTER";
printf (p) ;
printf(p) ;
Another advantage to this approach is that to change the prompt, you only need to change it
once, and all references to it will reflect the change.

Create Arrays of Pointers


Pointers may be arrayed like any other data type. For example, the following statement declares
an integer pointer array that has 20 elements.
int *pa[20];

163
The address of an integer variable called myvar is assigned to the ninth element of the array as
follows:

pa[8] = Smyvar;

Because pa is an array of pointers, the only value that the array elements may hold are the
addresses of integer variables. To assign the integer pointed to by the third element of pa the
value 100, use the statement:
*pa[2] = 100;

Probably the single most common use of arrays of pointers is to create string tables in much the
same way that unsized arrays were used in the previous Unit. For example, this function displays
an error message based upon the value of its parameter err_.num.
char *p[ ] = {
"Input exceeds field width",
"Out of range",
"Printer not turned on",
"Paper out",
"Disk full",
"Disk write error"

};
void error (int err_num)
{
printf(p[err_num]) ;
}

The following program uses a two-dimensional array of pointers to create a string table that links
apple varieties with their colors. To use the program, enter the name of the apple, and the
program will tell you its color.

#include "stdio.h"
#include "string.h"
char *p[][2] = {
"Red Delicious", "red",
"Golden Delicious", "yellow",
"Winesap", "red.",
"Gala", "reddish orange",

164
"Lodi", "green",
"Mutsu", "yellow",
"Cortland", "red",
"Jonathan", "red",
"", “” /* terminate the table with null strings*/
};
int main ( )
{
int i;
char apple[80];
printf("enter name of apple: ");
gets(apple) ;
for(i=0; *p[i][0]; i++) {
if{!strcmp(apple, p[i][0] ) )
printf("%s is %s\n"/ apple, p[i][1]);
}
return 0;
}

Look carefully at the condition controlling the for loop. The expression *p[il[0] gets the value of
the first byte of the ith string. Since null strings terminate the list, this value will be 0 (false)
when the end of the table is reached. In all other cases it will be non-0, and the loop will repeat.

Become Acquainted with Multiple Indirection


It is possible in C to have a pointer point to another pointer. This is called multiple indirection.
When a pointer points to another pointer, the first pointer contains the address of the second
pointer, which points to the location containing the object.
To declare a pointer to a pointer an additional asterisk is placed in front of the pointer's name.
For example, this declaration tells the compiler that mp is a pointer to a character pointer

.char **mp;

It is important to understand that mp is not a pointer to a character, but rather a pointer to a


character pointer. Accessing the target value indirectly pointed to by a pointer to a pointer
requires that the asterisk operator be applied twice. For example,

char **mp, *p, ch;

p = &ch; /* get address of ch */


mp = &p; /* get address of p */

165
**mp = 'A'; /* assign ch the value A using multiple indirection */

As the comments suggest, ch is assigned a value indirectly by using two pointers.


Multiple indirection is not limited to merely "a pointer to a pointer." You can apply the * as often
as needed. However, multiple indirection beyond a pointer to a pointer is very difficult to follow
and is not recommended.

The following program assigns val a value using multiple indirection. It first displays
the value directly, then through the use of multiple indirection.

#include "stdio.h"
return main( )
{
float *fp, **mfp, val;
fp = &val;
mfp = &fp;
**mfp = 123.903;
pr.i.ntf("%f %f", val, mfp) ;
return 0;
}

This program shows how you can input a string using gets() by using a pointer to a pointer to the
string.

#include "stdio.h"
int main( )
{
char *p, **mp, str[80];

p = str;
mp -= &p;
printf (‘Enter your name: “);
gets (*mp):
printf (“ Hi %s”, mp );

Notice that when mp is used as an argument to both gets() and printf ( ) , only one * is used.
This is because both of these functions require a pointer to a string for their operation.

166
Remember, **mp is a pointer to p. However, p is a pointer to the string str. Therefore, *mp is a
pointer to str.

167
Use Pointers and Function Parameters

Pointers as function parameters


Call-By-Value and Call-By-Reference
Arrays as Arguments

Pointers may be passed to functions. For example,, when you call a function like strlen() with
the name of a string, you are actually passing a pointer to a function. When you pass a pointer to
a function, the function must be declared as receiving a pointer of the same type. In the case of
strlen(), this is a character pointer. When you pass a pointer to a function, the code inside that
function has access to the variable pointed to by the parameter- This means that the function can
change the variable used to call the function- This is why functions like strcpy ( ), for
example ,can work. Because it is passed pointers, the function is able to modify the array that
receives the string. This is why you need to precede a variable's name with an & when using
scanf( ). In order for scanf ( ) to modify the value of one of its arguments, it must be passed a
pointer to that argument.

Examples
Another of C's standard library functions is called puts( ); it writes its string argument to the
screen followed by a newline. The program that follows creates its own version of puts() called
myputs( ).

168
#include "stdio.h"

void myputs(char *p);

int main ( )
{
myputs("this is a test") ;
return 0;
}
void myputs(char *p)
{
while(*p) {
printf("%c", *p) ;
p++;
}
printf("\n") ;
}

This program illustrates a very important. When the compiler encounters a string constant, it
places it into the program's string table and generates a pointer to it. Therefore, the myputs( )
function is actually called with a character pointer, and the parameter p must be declared as a
character pointer in order to receive it.
The following program shows one way to implement the strcpy( ) function, called
mystrcpy().

#include "stdio.h"

void mystrcpy (char *to, char *from);


int main( )
{
char str [80] ;
mystrcpy (str, "this is a test");
printf(str) ;
return 0;
}

void mystrcpy (char *to, char *from)


{
while(*from) *to++ = *from++;
*to = '\0'; /* null terminates the string */
}

Call-By-Value and Call-By-Reference

169
When variables are passed to function, a copy of the variable's value is actually passed to the
function. Since a copy is passed, the variable in the calling program is not altered. Calling a
function by value is a popular means of passing information to a function and is the default
method in C. The major limitation to the call-by-value technique is that typically only one value
is returned by the function.

In a call-by-reference, the address of the argument, rather than its value, is passed to the function.
This approach requires less program memory than a call-by-value. When you use a call-by-
reference, the variables in the calling program can be altered. Additionally, more than one value
can be returned by the function; but more on that later, The next example uses the subtracter
function from the previous Unit. The arguments are now passed as a call-by-reference. In C, you
achieve a call-by-reference by using a pointer as an argument.

/*
* C program to illustrate a call-by-reference.
*/
#include <stdio.h>
int subtractor(int *x,int *y);
int main()
{
int a=5;
int b=93;
int c;

c=subtractor(&a,&b);
printf(“The difference is: %d\n”, c);
return (0);
}
int subtractor(int *x, int *y)
{
int z;
z=*y-*x;
return(z);
}

Arrays as Arguments

170
In the following example, the contents of an array are passed to a function as a call-by-reference.
Actually, the address of the first array element is passed via a pointer.

/*
* C program will call a function with an array,
* Function uses a pointer to pass array information-
*/
#include <stdio.h>

void printer(int *data);

int main()
{
int myarray[5]={5,8,20,21,78};
printf(“Send information to function. \n");
printer(myarray);
return (0);
}
void printer(int *data)
{
int i;
for(i=0;i<5;i++)
printf(“The result is %d \n”,data[i]);
}

Notice that when the function is called, only the name myarray is specified. In this case, by
specifying the name of the array, you are providing the address of the first element in the array.
Since myarray is an array of integers, you can pass an array by specifying a pointer of the
element type.

You can also pass the address information by using an unsized array, as you can see in the C
example. The information in myarray is transferred by passing the address of the first element.

171
//
// C program will call a function with an array.
// Function passes array information, and calculates
// the average of the numbers.
//
#include <stdio.h>
void average(float data[ ]);

int main()
{
float myarray [10] ={70.0,23.5,67.2,4.1,0.0,
1.25,8.0,3.14,141.0,78.234};
printf(“Send information to averaging function. \n”);
average(myarray);
return (0);
}

void average(float data[ ])


{
int i;
float total=0.0;
float avg;

for(i=0;i<10;i++) {
total+=data[i];
printf(“number %d is %f \n“, i+1, data[i]);
}
avg=total/i;
printf (“\nThe average is %f\n“, avg);
}

The average is determined by summing each of the terms together and dividing by the total
number of terms.

172
Dynamic storage allocation

Static allocation and dynamic allocation


malloc function
free function

We have met four classes of data: external, static, auto and register. Each has an associated
storage mechanism, which defines its scope rules, duration and the forms allowed for
initializations. The standard functions, malloc and realloc, use yet another storage mechanism,
the heap, which is memory that can be dynamically allocated at runtime and is accessed using
pointers. (Of course, the pointers themselves can be stored by any of the stated mechanisms.)
We illustrate the way the heap operates in terms of the following code segment:

int main()
{
char *y;
char *z = " arbitrary string";
char A[4];

y = "this is a constant string" ;


...
y = (char *)malloc(200 * sizeof(char));
...
free(y);
y = (char *)malloc(20 * sizeof(char*));

173
….
Now the data declared in main is stored in the stack. So the pointers y and z and the array A are
stored on the stack. In addition to the data on the stack, the constant strings are stored in a
persistent memory area (just like external and static data.) The compiler generates code that
allocates storage for constant strings and also initializes it. At the point where main does the
assignment

y = " this is a constant string";

all that actually happens is that y takes the value of the address of the first letter in the constant
string. You can also allocate and deallocate heap memory using the standard functions, malloc,
realloc and free. We do this later in main with the statement

y = (char *)malloc(200 * sizeof(char));

where malloc allocates space for 200 characters on the heap. After using this space, we use free
to make that space available for reuse in future calls to malloc. It is quite possible that the 20
locations allocated on our second call to malloc may reuse some of the 200 that were allocated
on the first call. Since malloc does not initialize the memory it allocates, you cannot rely on its
initial value.

The different memory mechanisms are that external and static forms exist throughout the
program's execution, which means that an identifier is associated with the same storage
throughout the program execution. We also classify the heap as a persistent form of storage
because the same locations are allocated from the time of the malloc call until the storage is
explicitly freed by the programmer (using a call to free or realloc) or the program completes.

By contrast, the stack grows as each function is invoked and shrinks as it completes execution.
Finally, the register data exists only while the function in which it is declared remains active and
the same registers are also heavily used by the code otherwise generated by the compiler.

174
Unit 7

175
Sorting

Sorting categories
Sorting arrays
Sorting sequential disk files
Classes of Sort Algorithms
By exchange
By selection
By insertion
Judging Sorting Algorithms
Sorting is the process of arranging a set of similar information into an increasing or decreasing
order. Specifically, given a sorted list i of n elements then :

i1 <= i2 <=….<= in

Though C supplies the standard qsort() function as part of the standard library, the study and
understanding of sorting is important, for three maim reasons. First, a generalized function like
qsort() cannot be applied to all situations. Second, because qsort() is parameterized to operate on
a wide variety of data, it will run slower than a similar sort that operates on only one type of data.
(The generalization process inherently increases run time because of the extra processing time
needed to handle various data types.) Finally, the Quicksort algorithm (used by qsort()).
Although very good for the general case, may not be the best type of sort for specialized
situations.

The two general categories of sorting algorithms are the sorting of arrays (both in memory and in
random access disk files), and the sorting of sequential disk files.

176
The main difference between sorting arrays and storing sequential files is that each element of
the array is available all the time. That is, any element may be compared or exchanged with any
other element at any time. With a sequential file, only one: element is available at any one time.
Because of this difference, sorting techniques differ greatly between the two.

Generally, when information {such as a mailing list) is sorted, only a portion of that information
is used as the sort key. This key is used in comparisons, but when an exchange is made the entire
data structure is swapped. In a mailing list for example, the zip code field might be used as the
key, but the entire address is sorted

Classes of Sort Algorithms

The three general methods that can be used to sort arrays are
• By exchange
• By selection
• By insertion
To understand these three methods, imagine, a deck of cards. To sort the cards using exchange,
you would spread the cards, face up, on a table and then proceed to exchange out-of-order cards
until the deck is ordered.
To sort by selection, you would spread the cords on the table, select the lowest-value card, and
take it out of the deck. And hold it in your hand. Then you would select from the remaining cards
on the table the lowest card and place it behind the one already in your hand. This process would
continue until all the cards were in your hand. Because you always select the lowest card from
those remaining on the table, the cards in your hand would be sorted when the process was
complete,
To sort the cards using insertion, you would hold the cards in your hand and would take one at a
time. As you took cards from the deck, you would place them into a new deck on the table
always inserting them in the correct position. The deck would be sorted when you had no cards
in your hand,

Judging Sorting Algorithms

177
Many different algorithms exist for each of the three sorting methods. Each algorithm has its
merits, but the general criteria for judging a sorting algorithm are based on the following
questions:
• How fast, can it sort information in an average case?
• How fast is its best and worst case?
• Does it exhibit natural or unnatural behavior?
• Does it rearrange elements with equal keys?
How fast a particular algorithm sorts is of great concern. The speed with which an array can be
sorted is directly related to the number of comparisons and the number of exchanges (exchanges
take more time), A comparison occurs when one array element is compared to another. An
exchange happens when two elements are swapped in the array. Later you will see that some
sorts require an exponential amount of time per element to sort and some require logarithmic
time.
The best and worst-case run times are important if you expect to encounter one of these
situations frequently. Often a sort will have a good average case, but a terrible worst case
A sort is said to exhibit natural behavior if it works least when the list is already in order,
harder as the list becomes less ordered, and hardest when a list is in inverse order. How hard a
sort works is based on the number of comparisons and moves that must be executed,
To understand the importance of rearranging elements with equal keys, imagine a database
that is sorted on a main key and a sub-key, for example, a mailing list with the Zip code as, the
main key and the last name within the same ZIP code as the subkey. When a new address is
added to the list, and the list sorted again, you do not want the subkeys (that is, last names within
ZIP codes) to be rearranged. To guarantee this, a sort must not exchange main keys of equal
value .
In the following section representative sorts from each class of sorting algorithms are
analyzed to judge their efficiency. Later, improved sorting methods are studied.
The. Bubble Sort The best-known (and most infamous) sort is the bubble, that its popularity is
derived from its catchy name and its, simplicity. For reasons that well become evident, this is
one of the worst sorting ever conceived.
The Bubble sort uses the exchange method of sorting. The general concept behind the Bubble
sort is the repealed comparisons and, if necessary, exchanges of adjacent elements. Its name

178
comes from the method's similarity to bubbles in a tank of water, whore each bubble seeks its
own level. In this simplest form of the Bubble sort
void bubble(char *item, int count) /* bubble sort */
{
register int a, b ;
register char t ;

for(a=1 ; a <count; ++a )


for (b=count - 1 ; b >= a; --b )
{
if(item [b-1 ] > item [b] ) /* exchange elements */
{
t= item [b-1] ;
item [b-1] = item [b] ;
item [b] =t ;
}
}
}

item is a pointer to the character array to be sorted and count is the number of elements in the
array.

The- Bubble sort is driven by two loops Given that there are count elements in the array, the
outer loop causes the array to be scanned count-1 times This ensures that, in the worst case,
every element is in its proper position when the function terminates. The inner loop performs the
actual comparisons and exchanges. (A slightly optimized version of the Bubble sort will
terminate if no exchanges occur, but this also adds another comparison to each pass through the
inner loop.)
This version of the Bubble sort can be used to sort a character array into ascending order. For
example, this short program sorts a string typed in from the keyboard:

void bubble(char *item, int count);


int main () /* sort a string from the keyboard */
{
char s[80;]
printf( “enter a string “ ) ;
gets (s) ;
bubble (s , strlen (s) ) ;
printf( “the sorted string is : %s \n “ ,s );
return 0;

179
}

To illustrate how the Bubble sort works, here are the passes used to sort dcab . .
Initial dcab
pass 1 adcb
pass 2 abdc
pass 3 abcd

When analyzing any sort, you must determine how many comparisons and exchanges will be
performed for the best, average and worst case. With the Bubble sort, the number of comparisons
is always the same because the two for loops will still repeat the specified number of times,
whether or not the list is initially ordered. This means that the Bubble sort will always perform ½
( n2-n ) comparisons where n is the number of elements to be sorted. This formula is derived
from the FACT that the outer loop executes n-1 times and the inner loop n/2 times. Multiplying
these together gives the formula.

The number of exchange; is. 0 for the best case -an already sorted list. The numbers are ,3/4 (n2-
n) for the average case and 3/2(n2-n} for the worst case, as you can see that as the list becomes
less ordered, the number of elements that are out of order approaches the number of
comparisons. (Remember, there are three exchanges in a Bubble sort for every element out of
order.) The Bubble sort is sort is said to be an squared algorithm because its execution time is a
multiple if the square of the number of elements. A Bubble sort. is bad for a large number of
elements because execution time is directly related the number of comparisons and exchanges.

For example, if you ignore the time it takes to exchange any out-of-position element and if each
comparison takes 0,001 seconds , then sorting 10 elements will take about 0.05 seconds, sorting
100 elements will take about 5 seconds, and sorting 1000 elements will take about 500 seconds.
A 100,000-element sort (the size of a small telephone book) would take about 5,000,000 second
or about 1400 hours—about two months of continuous sorting. You can make same alight
improvements to the Bubble sort to speed it up and to help its image. For example, the Bubble
sort has one peculiarity an out-of-order element at the large end (such as the a in the decab array

180
example) will go to its proper position in one pass, but a misplaced element in the small end
(such as the d) will rise very slowly to its proper place. This suggests an improvement to the
Bubble sort.

Instead of always reading the array in the same direction, subsequent passes could reverse
direction, Greatly out-of-place elements will travel more quickly to their correct positions-
Shown here, this version of the Bubble sort is called the Shaker sort because of its shaking
motion over the array:

void shaker (char *item, int count) /* shaker sort, an improved bubble sort */

{
register int a, b, c, d ;
char t;
c = 1;
b= count - 1;
d = count - 1;
do
{
for (a = d; a >= c; --a )
{
if (item [a-1 ] > item [a] )
{
t = item [a-1];
item [a-1] = item [a] ;
item [a] = t;
b = a;
}
}
c=b +1;
for(a = c; a < d+1; ++a)
{
if (item [a-1] > item [a] )
{
t = item [a-1];
item [a-1 ] = item [a] ;
item [a] = t ;
b=a;
}
}

181
d = b-1;
} while (c <= d);
}
Although the Shaker sort does improve the Bubble sort, it still executes on the order of n2
because the number of comparisons is unchanged- and because the number of exchanges has
only been reduced by a relatively small constant. Although the Shaker sort is better than the
Bubble sort, better sort do exist.

Sorting by Selection A Selection sort selects the element with the lowest value and exchange
that with the first element- Then from the remaining n-1 elemints, the element with the least key
is found and exchange with the second element, and so forth, up to the last two elements For
example, if the selection method were to be used on the array b d a c, each pass would look like
this:

Initial bdac
Pass 1 adbc
Pass 2 abdc
Pass 3 abcd
The basic Selection sort is shown here:
void select(char* item, int count) /* selection sort */
{
register int a, b, c ;
char t ;
for(a=0; a < count –1 ; ++ a)
{
c=a ;
t= item [a];
for(b = a + 1; b<count; ++b)
{
if(item[b] <t)
{
c=b;
t = item [b] ;
}
}

item[c] = item [a] ;


item [a] = t ;

182
}
}

Unfortunately, like the Bubble sort, the outer loop executes n - 1 items and The inner- loop
1/2(n) times. This means that the Selection sort requires 1/2(n2-n) comparisons, which makes it.
To slow for a larger number of items The number of exchanges for the best case is 3(n-l) and for
the worst case is n2/4+3(n-1).

For the best case, if the list is ordered; then only n-1 elements need to be moved, and each move
requires three exchanges. The worst case approximates the number of comparison, The average
case is is n(ln n+y) where y is Euler's constant(about 0,577216). This means that. Although the
number of comparisons for both the Bubble sort and the Selection sort. is the flame. the number
of exchanges in the average case is far less for the Selection sort. Sorting by Insertion The
insertion sort is the last of the simple sorting algorithms the Insertion sort initially sorts the first
two member of the array. Next, the algorithm inserts the third member into its sorted position in
relation to the first two members. Then the fourth element is inserted into the list of three
elements. The process continues until all-elements have been sorted. For example, given the
array d c a b, each pass of the insertion sort would look like this;

Initial dcab
pass 1 cdab
pass 3 acdb
pass 4 abcd

A version of the Insertion sort is shown here:

void insert (char* item , int count ) /* sorting by insertion */


{
register int a,b ;
char t ;
for (a=1; a < count ; ++a )
{
t= item [a] ;
b = a - 1;

183
while (b>=0 && t < item [b] )
{
item [b+1] = item [b] ;
b--;
}
item [b+1] =t ;
}
}
Unlike the Bubble sort and the Selection sort, the number of comparisons that occur while the
Insertion sort is used will depend on how the list is initially ordered. If the list is in order, then
the number of comparisons is n-1. If the list is in inverse order, then the number of comparisons
is l/2(n2+n)-1, while its average number of comparisons is l/4(n2+n-2 )
The number of exchanges for each case is as follows:

Best 2(n-I)

average 1/4(n2+9n - 10)

worst l/2(n2+ 3n - 4)
Therefore, the number for the worst. case is as bad as those for the Bubble and Selection sort and
for the average case it is only slightly better.
The Insertion sort does have two advantages, howevsr, First, i). behaves naturally it works the
least when the array is already sorted and the hardest when the array is sorted in inverse order.
This makes the Insertion sort useful for lists that are almost in order. Second, it leaves the order
of equal keye unchanged: if a list is sorted using two keys, then it remains sorted for both keys,
after an Insertion sort.
Even though the comparisons may be fairly good for certain sets of data, the fact that the
array must always he shifted over each rime an element is placed in its. proper location means
that the number of moves can he very significant. However:, the Insertion sort still behaves
naturally, with the least exchanges occurring for an almost sorted list and the most exchanges for
an inversely order array

Improved Sorts

184
All of the algorithms thus far bad the fatal flaw of executing in n2 time- For large amounts of
data. the sorts would be slow, in fact at some point, too slow to use.

When a sort takes too long, it may be the fault of the underlying algorithm. However, if the
underlying algorithm is bad the sort will be slow no matter- how optimal the coding. Remember,
when the run time of a routine is relative to n2 increasing speed of the coding or of the computer
will only cause a slight improvement because the rate at which the run time increases; changes
exponentially.

In this section, two excellent aorta are developed. The first is the Shell sort, and the second is the
Quicksort, which is generally considered the best routine. These sorts run so .fast. that if you
blink, you will miss them,

The Shell Sort The Shell sort is named after its inventor, D.L. Shell However, the name seems
to have stuck because its method of operation resembles sea shells piled upon one another.
The general method, derived from the Insertion sort. is based on diminishing increment.
Previous figure gives a diagram of a Shell sort on the array f d a c b e. First, all elements that are
three positions apart. are sorted Then all elements that are two positions apart are sorted-Finally,
all those adjacent to each other are sorted,

185
It may not be obvious that this method yields good results, or even that it will sort the array,
but it does. both. This algorithm is efficient because each sorting pass involves relatively few
elements, or elements that are already in reasonable order; therefore each pass increases the
order of the data.
The exact sequence for the increments can be changed. The only rule is that the last increment
must be 1. For sample, the sequence 9, 5, 3, 2, 1 works well and is used in the Shell sort shown
here. Avoid sequences that are power of 2 because, for mathematically comply reasons, they
reduce the efficiency of the sorting algorithm

void shell (char* item, int count) /* a shell sort */


{
register int i,j,k,s,w;
char x, a[5] ;
a[0]=9; a [1]=5; a[2]=3; a[3]=2; a[4]=1 ;
for (w=0; w<5; w++)
{
k=a [w] ; s=-k;
for(i=k; i<count ; ++i)
{
x=item[i] ;
j=i-k;
if (!s)
{
s= -k;
s++;
item [s]=x;
}
while (x<item [j] && j>=0 && j <=count)
{
item [j+k]=item [j];
j=j-k;
}
item [j+k] = x;
}
}
}

186
You may have noticed that the inner while loop has three test. conditions. The x<item[j] is a
comparison necessary for the sorting process. The tests j>=0 and j<-=count are used to keep the
sort. from overrunning the boundary of the array item These extra checks degrade the
performance of Shell sort to some extent. Slightly different versions of the shell sort employ
special array elements, called sentinels which are not actually part of the array to be sorted.
Sentinels hold special termination values that indicate the least and the greatest possible
elements. In this way the boundary checks are unnecessary. However using sentinels requires a
specific knowledge of the data, which limits the generality of the sort function,

The. quicksort, The quicksort, invented and named by C.A.R- Hoare, is generally considered
the best. sorting algorithm currently available. It is based on the exchange method of sorting.
This is surprising if you consider the terrible performance of the Bubble sort, which is also based
on the exchange method.

The Quicksort is built on the idea of partitions. The general procedure is to select a value (called
the comparand) and then to partition the array into two parts with all elements greater than or
equal to the partition value on One side and those less than the partition value on the other. This
process is then repeated for each remaining part until the array is sorted. For example, given the
array f e d a c b and using the value d, the first pass of the Quicksort
would rearrange the array like this:

initial fedacb

pass. 1 bcadef

This process is then repeated for each half (b c a and d e f). The process is essentially recursive;
indeed, the cleanest implementations of Quicksort are recursive algorithms

The selection of the middle comparand value can be accomplished two ways, The value can be
chosen either at random or by averaging a small sat of values taken from the array for the
optimal sorting it is desirable to select a value that is precisely in the middle of value range
however this not easy to do for most sets of data. Even in the worst case the value Chosen is at
one extremity—quicksort still performs well

187
the following version of Quicksort selects the middle element of the array. Although this may not
always result in a good choice, the sort still performs correctly.
void quick (char* item, int count ) */qutcksort set up */
{
qs(item , 0, count –1);
}
void qs (char* item , int left , int right ) /* quicksort */
{
register int i, j;
char x,y;

i = left; j = right ;
x = item [(left+ right) /2] ;
do
{
while (item [i] <x && i < right ) i++ ;
while (x < item[j] && j < left) j--;
if (i<=j)
{
y= item [i];
item [i]=item [j];
item [j]=y;
i++; j -- ;
}
} while (i<=j);
if (left <j) qs (item ,left, j);
if (i <right) qs (item ,i, right );
}

In this version, the function quick() sets up a call to main sorting function, called qs() While this
maintains the same common interface of item and count, it is not essential because qs() count
have been called directly using three arguments

The number of comparisons is n log n and that the number of exchanges is approximately n/6
log n- These are significantly better than any of the previous sorts discussed so far.

This means, for example, that if 100 elements were to be sorted, Quicksort would require 100 *
2, or 200, comparisons because log 100 is 2. Compared with the Bubble sort's 910 comparisons,
this number is quite good.

188
You should be aware of one particularly nasty aspect to Quicksort. If the comparand value for
each partition happens to be the largest value, then Quicksort degenerates into "slowsort” with
an n2- run time. Generally, however, this does not happen.
You must carefully choose a method of determining the value of the comparand. Often the
value is determined by the actual data you are sorting. In large mailing lists where sorting is
often by zip code, the selection is simple, because the zip codes are fairly evenly distributed and
a simple algebraic function can determine a suitable comparand. However, in certain databases,
the sort keys may be so close in value (with many being the same value) that a. random is often
the best method available. A common and fairly effective method is to sample three elements
from a partition and take the middle value

Choosing a Sort
Generally, the Quicksort is the sort of choice because it is so fast. However, when only very
small lists of data are to be sorted (less than 100), the overhead created by Quicksort's recursive
calls may offset the benefits of a superior algorithm. In rare cases like this, one of the simpler
sorts (perhaps even the bubble sort) will be quicker.

189
Sorting Other Data Structures

Sorting Strings
Sorting Structures

Until now, you have only been sorting arrays of characters. This has made it easy to present each
of the sorting routines. Obviously, arrays of any of the built-in data types can be sorted simply by
changing the data types of the parameters and variables to the sort function. However, generally
complex data -typed like strings, or groupings of information like structures, need to be sorted.
Most, sorting involves a key and information linked to that key. To adapt the algorithms to sort
other structures, you need to alter either the comparison section or the exchange section, or both-
The algorithm itself will remain unchanged.
Because Quicksort is one of the- best general-purpose routines available at this time, it will be
used in the following, examples. The same techniques will apply to any of the sorts described
earlier.
Sorting strings The easiest way to sort strings is to create an array of character pointers to those
strings. This allows you to maintain easy indexing and keeps the basic Quicksort algorithm
unchanged. The string version of Quicksort shown here will accept an array of char pointers that
point to the strings to be sorted. The sort rearranges the pointers to the strings not the actual
strings in memory. This version sorts the strings in alphabetical order,
void quick_string (char *item[], int count)

190
{
qs_string (item , 0 , count –1);
}
void qs_string (char* item[], int left , int right ) // quick sort for strings
{
register int i , j ;
char *x,*y ;
i = left ; j = right ;
x= item[ (left + right ) / 2] ;
do {
while (strcmp (item [i] ,x ) <0 && i<right ) i++;
while (strcmp (item [j] ,x )>0 && j > left ) j--;
if (i <= j) {
y = item[i];
item [i] = item [j];
item[j] = y;
i++, j--;
}
} while (i<=j);
if (left <j) qs_string( item , left ,j ) ;
if (i <right) qs_string( item, i, right ) ;
}

The comparison step has been changed to use the function strcmp(), which returns a negative
number if the first string is lexicographically less than the second, 0 if the strings are equal, and
a positive number if the first string is lexicographically greater than the second. The exchange
part the routine has been left unchanged because only the pointers are- being exchanged not the
actual strings. To exchange the actual strings, you would have to use the function strcpy().

The use of strcmp() will slow down the sort for reasons. First, it involves a function call,
which always takes time second, the strcmp() Function itself performs several comparisons to
determine the relationship of the two strings It speed is absolutely critical the code strcmp() can
be duplicated in line inside the routine, However, there is no way to avoid comparing the strings
since this is by definition what. the task involves.

Sorting structures Most application programs that require a sort will need to have a grouping of
data sorted. A mailing list is an excellent example because a name, street, city, state, and ZIP
code are all linked together. When this conglomerate unit of data is sorted, a sort key is used, but

191
the entire structure is exchanged. To see how this is done, you first need to create a structure.
For- the mailing-list example, a convenient structure is
struct address {
char name [40] ;
char street[40] ;
char city [20] ;
char state [3] ;
char zip[10] ;
};

The state is three characters long and zip is ten characters long because a string array always
needs to be one character longer than the maximum length of any string in order to store the null
terminator.
Since it is reasonable to arrange a mailing list. as an array of structures assume for this example
that the sort routine sorts an array of structures of type address, as shown here.

void quick_struct (struct address item[], int count ) // quick sort for structures
{
qs_struct(item, 0, count-1);
}
void qs_struct(struct address item[] , int left , int right ) /* quick sort for structres */
{
register int i, j ;
char *x, *y;

i = left ; j =right ;
x = item [(left + right )/2].zip ;
do {
while (strcmp (item [i].zip , x)< 0 && i<right ) i++;
while (strcmp (item [j].zip , x)> 0 && j<left) j--;
if (i <= j) {
swap_all_fields (item , i, j );
i++; j-- ;
}
} while (i<= j) ;
if (left <j ) qs_struct (item ,left ,j );
if (i<right ) qs_struct (item ,i ,right ) ;
}

Notice that both the comparison code and the exchange code needed to be altered. Because so
many fields needed to be exchanged, a separate function, swap_all_fields(), was created to do

192
this. You will need to create swap_all_fields() in accordance with the nature of the structure
being sorted.

193
Unit 8

194
Searching

Searching Methods
The Sequential Search
The binary search

Databases of information exist so that, from time to time, a user can locate a record by knowing
its key. There is only one method of finding information in an unsorted file or array, and another
for a sorted file or array. Many compilers supply search functions as part of the standard library.
However, as with sorting general-purpose routines sometimes are simply too inefficient for use
in demanding situations because of the extra overheads created by the generalization .

Searching Methods
Finding information in an unsorted array requires a sequential search starting at the first element
and stopping either when a match is found or when the end of the array is reached. This method
must be used on unsorted data, but can also be applied to sorted data, if the data has been sorted,
then a binary search can be used, which greatly speeds up any search.

The Sequential Search


The sequential search is easy to code. The following function searches a character array of known
length until a match is found with the specified key,

195
sequential_search(char* item , int count , char key )
{
register int t ;

for (t =0 ; t < count ;++t )


if (key == item [t]) return t ;

return –1; /* no match */


}

This function will return the index number of the matching entry if there is one, or -1 if there is
not.
A straight sequential search will, on the average, teat n/2 elements. In the best case, it will test
only one element and in the worst case n elements. If the information is stored on disk, the
search time can be vary long, but if the data is unsorted, this is the only method available.

The binary search


If the data to be searched is in sorted order, then a superior method, called the binary search, can
be used to find a match. The method uses the “divide-and-conquer” approach. It first tests the
middle element; if the element is lager than the key, it then tests the middle element of the first
half; otherwise, it. Test the middle element of the second half. This process is repeated until
either a match is found, or there are no more elements to test.

For example, to find the number 4 in the array 1 2 3 4 5 6 7 6 9, the binary search would first test
the middle, which is 5, Since this is greater than 4, the search would continue with the first half.
or
12345

In this example, the middle element is 3. This is less than 4, so the first half is discarded and the
search continues with
45

This time the match is found.

196
In the binary search, the number of comparisons given the worst case is log2n. With average
cases, the number is somewhat better; in the best case, the number is 1.

A binary search function for character arrays is shown here. You can make this search any
arbitrary data structure by changing the comparison portion of the routine.
binary (char* item, int count, char key)
{
int low, high, mid;
low =0; high = count –1;
while (low <= high ) {
if (key <item [mid]) high= mid-1;
else if (key > item [mid]) low=mid+1;
else return mid; /* found */
}
return –1 ;
}

197
Unit 9

198
Data Structures

Data structure is information organization


The four types of data engines
Qeue
Sack
Linked list
Bnary tree

Programs consist of algorithms and data structure- The good program is a blend of both. Choosing
and implementing a data structure are as important as the routines that manipulate the data. The
way that information is organized and accessed is usually determined by the nature of the
programming problem. Therefore, as a programmer, you must have in your "bag of tricks" the
right storage and retrieval method for a variety of situations.

How closely the logical concept of an item of data is bound with its physical machine
representation is in inverse correlation to its abstraction. That is, as data types become more
complex, the way the programmer thinks of them bears an ever-decreasing resemblance to the
way they are actually represented in memory. For example, simple types such as char and int are
tightly bound to their machine representation. In this case, the value that an integer has in its
machine representation closely approximates that which the programmer conceives of it having.

Simple arrays, which are organized collections of the simple data types, are not quite as tightly
bound as the simple types themselves because an array may not appear in memory the way the
programmer thinks of it. Less tightly bound yet are floats because the actua1 representation inside
the machine is unlike the average programmer's conception of a floating-point number. The

199
structure, which is a conglomerate data type accessed under one name is even more abstracted
from the machine representation.

The final level of abstraction transcends the mere physical aspects of the data and concentrates
instead on the sequence in which the data will be accessed (that is, stored and retrieved). In
essence, the physical data is linked with a "data engine” that controls the way information can be
accessed by your program. The four types of these engines are

• A queue
• A stack
• A Linked list
• A binary tree

Each method provides a solution to a class of problems each is essentially a "device" that
performs a specific storage and retrieval operation on the given information according to the
requests it receives. The methods share two operations, store an item and retrieve an item, in
which an item is one informational unit.

200
Queues

A queue is a linear list of information


The queue is accessed in FIFO order
The retrival process is destructive retrival
Queue may be linear or circular one

A queue is a linear list of information that is accessed in first-in, first-out order (sometimes called
FIFO). The first item placed on the queue is the first item retrieved. The second item put in is the
second item retrieved, and so on. This order is the only means of storage and retrieval; a queue
does not allow random access of any specific item.

Queues are very common in everyday life. For example, lines at a bank or a fast-food
restaurant are queues. To visualize how a queue works, consider-two-functions: qstore() and
qretrieve(). qstore() places an item onto the end of the queue and qretrieve( ) removes the first
item from the queue and returns its value

Keep in mind that a retrieve operation removes an item from the queue, and if it is not stored
elsewhere, destroys it. Therefore, even though the program is still active, a queue may be empty
because all of its items have been removed.

Queues are used in many types of programming- situations such as simulations,event or


appointment scheduling (such as in a PERT or Gant chart), and I/O buffering.

201
For example, consider a simple appointment-scheduler program that allows you to enter a
number of events. As each appointment is performed it is taken off the list. You might use a
program like this to organize a day's appointments. For the sake of simplicity, the program uses
an array of pointers to the event strings. It limits each appointment description to 256 characters
and the number of entries to 100. First, here are the functions qstore() and qretriev() that will be
used for the simple scheduling program:
/*sort an appointment */
void qstore (char* q)
{
if (spos==MAX ) {
printf(“list full\n”) ;
return;
}
p[spos]=q;
spos ++;
}

/* retrive an appointmen */
char *qretrieve ()
{
if (rpos==spos) {
printf(“no (more ) appointments. \n”) ;
return NULL
}
rpos++ ;
return p[rpos-1];
}

These functions require two global variables: spos, which holds the index of the next free storage
location, and rpos, which holds the index of the next item to retrieve. It is possible to use these
functions to maintain a queue of other data types by simply changing the base type of the array
on which they operate,

The function qstore() places pointers to new events on the end of the list and checks whether the
list is full. qretrieve() takes events off the queue as they occur. With each new appointment
scheduled, spos is incremented, and with each appointment removed, rpos is incremented in
essence, rpos "chases" spos through the queue. The next figure shows the way this may appear in

202
memory as the program executes, if rpos and spos are equal, no events are left in the schedule.
Keep in mind that even though the information stored in the queue is not actually destroyed by
the qretrieve() function, it can never be accessed again and is in effect destroyed.

Here is the entire program for this simple appointment schedule. You may want to enhance
this program for your own use.
#include “stdlib.h”
#include “stdio.h”
#include “ctype.h”
#include “string.h”

#define MAX 100


char * p[MAX];

int spos;
int rpos;

char * qretrieve ();


void enetr();
void qstore(char*);
void review ();

203
void delete();

int main() //Mini appointment – scheduler


{
char s[80];
register int t ;

for (t=0; t < MAX ; ++t)


p[t] = NULL;
spos = 0; rpos = 0;
for (; ; )
{
printf(“enter ,left ,remove ,quit : “);
gets (s) ;
*s = toupper (*s);

switch (*s)
{
case ‘E’ :
enter();
break;
case ‘L’:
review ();
break;
case ‘R’:
delete ();
break;
case ‘q’:
exit (0) ;
}
}
return 0;
}

/* enter appointments */
void enter ()
{
char s [256] , *p ;
do
{
printf(“enter appointment %d : “, spos+1);
gets (s);
if (*s == 0) break ; /*no entry */
p = (char*) malloc(strlen(s));
if (!p)
{

204
printf(“out of memory , \n” ) ;
return ;
}
strcpy (p, s) ;
if (*s) qstore (p);
}
while (*s) ;
}

/* see what’s in the queue */


void review ()
{
register int t ;

for (t = rpos ;t <spos ;++t)


printf (“%d. %s \n”,t+1 ,p [t] );
}

/* delete an appointment from the queue */


void delete ()
{
char *p ;

if (! (p=qretrieve () )) return ;
printf(“%s\n “,p);
}

/* store an appointment */
void qstore (char* q)
{
if (spos == MAX ) {
printf(“list full\n”) ;
return;
}
p[spos]=q;
spos ++ ;
}

/* retrieve an appointment */
char *qretrive ()
{
if (rpos == spos ) {
printf(“no (more ) appointments .\n”) ;
return NULL;

205
rpos ++ ;
return p [rpos-1];
}

A sample run of the appointment scheduler is shown here:

Enter , list , remove , quit : E


Enter appointment 1: Jon at 9 about the phone system
Enter appointment 2: Ted at 10:30 – wants that raise …humm .
Enter appointment 3: lunch with Mary and Tom at Harry’s
Enter appointment 4: <cr>
Enter , list , remove , quit : L
1- Jon at 9 about the phone system
2- Ted at 10:30 – want that raise …humm.
3- lunch with Mary and Tom at Harry’s
Enter ,list ,remove , quit :R
Jon at 9 about the phone system
Enter , list , remove , quit : L
2- Ted at 10:30 – want that raise ….humm
3- lunch with Mary and Tom at Harry’s
enter , list , remove , quit :q

The Circular Queue


In studying the appointment-scheduler program in the previous section, an improvement may have
occurred to you. Instead of having the program stop when the limit of the array used to store the
queue was reached, you could have both the store index (spos) and the retrieve index (rpos) loop
back to the start of the array. This would allow any number of items to be placed on the queue, as
long as items were also being taken off. This method of implementing a queue is called a circular
queue because it uses its storage array as if it were a circle a instead of a linear list.
To create a circular queue for use in the appointment-scheduler program, the qstore() and
qretrieve() functions need to be changed, as shown here:

void qstore (char* q)

206
{
/* the queue is full if either spos is one less then rpos or if spos is at the end of the queue
array and rpos is at the beginning .*/

if (spos +1 == rpos || (spos + 1 == MAX && ! rpos ) )


{
printf (“ list full \n ) ;
retutn ;
}
p[spos] =q ;
spos ++ ;
if (spos == MAX ) spos =0 ; /* loop back */
}
char * qretrieve ( )
{
if (rpos == MAX ) rpos =0 ; /* loop back */
if (rpos == spos )
{
printf (“No (more ) appointments .\n “) ;
return NULL ;
}
rpos ++
return p[rpos –1 ];
}

In essence, the queue is only full when both the store index and the retrieve index are equal;
otherwise, the queue has room for another event. However, this means that when the program
starts, the retrieve index (rpos) must not be set to 0, but rather to MAX so that the first call to
qstore() does not produce the queue full message. It is important to note that the- queue will hold
only MAX-1 elements because rpos and spos must always be at least one element apart;
otherwise it would be impossible to know whether the queue was full or empty.

Perhaps the most common use of a circular queue is in operating systems that buffer the
information read from and written to disk files or the console. Another common use of the
circular queue is in real-time application programs that must continue to process information
while buffering I/O requests. Many word processors do this when they reformat a paragraph or
justify a line; a brief period elapses during which what is being typed is not displayed until after
the other process the program is working on is completed. To accomplish this, the application
program must continue to check for keyboard entry during the execution of the other process. If

207
a key has been typed, it is quickly placed in the queue, and the process continues. Once the
process is complete, the characters are retrieved from the queue.

To see how this can be done, consider the simple program presented next, which contains two
processes. The first process in the program will print the numbers 1 through 32,000 on the
screen. The second process places characters in a circular queue as they are typed, without
echoing them on the screen until a semicolon is struck. The characters you type will not be
displayed because the first process is given priority over the screen at this time. After the
semicolon has been struck, the characters in the queue are retrieved and printed.

The short program shown here works with the IBM PC and uses kbhit() to determine the
keyboard status and getch() to read a character without echoeing it to the screen.

#include “stdio.h”
#include “conio.h”
#define MAX 80
char buf [MAX +1] ;
int spos = 0 ;
int rspos=MAX;

void qstore(char cg) ;


char qretrieve () ;

int main() /* circular queue example - keyboard buffer */


{
register char ch ;
int t ;
buf [80] = NULL;
for (ch =’ ‘, t=0; t < 32000 && ch != ‘;’; ++t)
{
printf (“%d”, kbhit ());
if (kbhit ())
{
ch=getch ();
qstore (ch);
}
printf(“%d”, t);
}
while ((ch = qretrieve ()) != NULL ) putchar (ch); /* display buf */
return 0;

208
}

/* store character in queue */


void qstore (char q)
{
if (spos+1 == rpos || (spos+1 == MAX && !rpos))
{
printf (“list full\n”);
return ;
}
buf[spos] = q ;
spos ++;
if (spos == MAX ) spos =0 ; /* loop back */
}

/* retrieve a character */
char qretrieve ()
{
if (rpos == MAX ) rpos =0 ; /* loop back */
if (rpos == spos ) {
return NULL;
}
rpos ++ ;
return buf[rpos –1 ] ;
}

209
Stacks

A stack linear list of information


The stackccessed in LFO order
The retrival process is destructive retrival

A stack is the opposite of a queue because it uses last-in, first-out accessing (sometimes called
LIFO). Imagine a stack of plates. The bottom plate in the stack is the last to be used, and the top
plate (the last plate placed on the stack) is the first to be used. Stacks are used a great deal in
system software including compilers and interpreters. In fact, C uses the computer's stack when
passing arguments in functions.

For historical reasons, the two basic operations—store and retrieve — are usually called push
and pop, respectively. Therefore, to implement a stack you need two functions: push() which
places a. value on the- stacks and pop(), which retrieves a value from the stack, you also need a,
region of memory to use as the stack you could either use an array, or you could allocate a region
of memory using C's dynamic memory allocation functions Like the queue- the retrieval function
takes a value off the list and, if the value is not stored elsewhere, destroys it. The general forms
of push() and pop() using an integer array are shown here. You may maintain stacks of' other
data types by changing the base type of the array on which the push() and pop() functions
operate.

210
int stack [MAX ];
int tos =0 ; /* top of stack */
void push (int i) /* place element on the stack */
{
if (tos> =MAX ) {
printf(“stack full \n “);
}
else
{
stack [tos] =i ;
tos ++;
}
}

int pop () /* retrieve top element from the stack */


{
tos -- ;
if (tos <0) {
printf (“stack underf low –n”) ;
return HUGH_VAL ;
}
return stack [tos] ;
}

The variable tos is the index of the next open stack location. When implementing these
functions, always remember to prevent overflow and underflow. In theses routines, if tos equals
0 the stack is empty; if tos is greater than the last storage function, the stack is full.

An excellent sample of the use of a slack is a four-function calculator, Most calculators


today accept a standard form of expression called infix notation- which takes the general form
operand. For example, to add 100 to 200, you would enter 100 than press + enter 200, and press
the equal-sign key. However, many early calculators in an attempt to save memory, used a form
of expression evaluation called postfix notation in which both operands are entered before the
operator is entered. For example, using postfix notation to add 100 to 200, you would first enter
100,then enter 200, and then press the + key. As operands are entered, they are placed on a stack;
when an operator is entered, two operands are removed from the stack, and the result is pushed
back on the stack. The advantage of the postfix form is that the calculator without much code can
evaluate expressions easily.

211
Before developing the full four-function calculator for postfix expressions, you need to modify
the basic push() and pop() functions C.'s dynamic allocation routines provide memory for the
stack- These functions, as used in the calculator example later, are shown here:

int *p; /*will point to a region of free memory */


int *tos ; /* points to top of stack */
int *bos ; /* points to bottom of stack */

void push (int i) /* place element on the stack */


{
if (p> bos ) {
printf (“stack full\n”) ;
return ;
}
*p =i;
p++;
}

int pop () /* retrieve top element from the stack */


{
p-- ;
if (p<tos) {
printf (“stack underflow \n “);
return NULL;
}
return *p;
}

Before you can use these functions, you must allocate a region of free memory using
malloc(). You must also assign the address of the beginning of that region to tos and assign the
address of the end to bos,

The entire calculator program is shown here. In addition to the operators plus, minus, times,
and divide, you may also enter a period, which causes the current value on the top of the stack to
be displayed.

212
/* a simple four –function calculator */
#include “stdlib.h”
#include “stdio.h”
#define MAX 100
int *p; /* will point to a region of free memory */
int *tos ; /* points to top of stack */
int *bos ; /* points to bettom of stack */
void push (int i) ;
int pop(void);

int main ()
{
int a,b;
char s [80] ;

p= (int *) malloc (MAX * sizeof (int )) ; /* get stack memory */


if (!p) {
printf (“allocation failure \n”) ;
exit (1) ;
}
tos =p ;
bos =p +MAX –1 ;
printf(“four function calculater \n”) ;
do {
printf (“:”);
gets(s) ;
switch(*s) {
case ‘+’ :
a = pop();
b = pop();
printf (“%d \n “, a+b ) ;
push (a+b);
break;
case ‘-’:
a = pop();
b = pop();
printf (“%d \n “, a-b ) ;
push(a-b);
break;
case ‘*’:
a = pop();
b = pop();
printf (“%d \n “, a*b ) ;
push(a*b);
break;
case ‘/’:

213
a = pop();
b = pop();
if (a==0){
printf (“divide by 0\n “) ;
break;
}
printf(“%\n” ,b/a);
push (b/a);
break;
case ‘.’ : /* show contents of top of stack */
a=pop () ;
push (a) ;
printf (“current value on top of stack ; %d\n “, a) ;
break;
default :
push(atoi(s));
}
} while (* s !=’q’) ;
return 0;
}

void push (int i) /* place element on the stack */


{
if (p>bos ) {
printf (“stack full\n”) ;
return ;
}
*p=i;
p++ ;
}

int pop (void) /* retrieve top element from the stack */


{
p--;
if (p< tos) {
printf(“Stack underflow”);
return 0;
}
return *p;
}

214
A sample session at. the calculator is shown here:

Four Function Calculator


: 10<cr>
: 10<cr>
; +<cr>
20
:5<cr>
: /cr>
4
: . <cr>
Currnt value on top at of stack : 4
:q <cr>

215
Linked Lists

Each piece of information carries with it a link to the next


data
A linked list may access its storage in a random fashion
A linked list retrieval is no n-destructive
Dynamic arrays can be constructed using the linked list
Linked list can be single or double list

Queues and stacks share two common traits. First, both have strict rules for referencing the data
stored in them- Second, the- retrieval operations are, by nature, consumptive that is, accessing an
item in a stak or queue requires its removal, and, unless stored elsewhere. Its destruction both
stacks and queues also require, at least conceptually, a contiguous region of memory to operate

Unlike a stack or a queue a linked list may access its storage in a random fashion, because
each piece of information carries with it a link to the next data, item in the chain. A linked list
requires complex data structure, whereas a stack or queue can operate on both simple and
complex data item. A linked-list retrieval operation does not remove and destroy an item from
the list; a specific deletion operation must be added to do this,

Linked lists are used for two main purpose. The First purpose is to create arrays of unknown
size in memory. If you know the amount of storage in advance, you can use an array; but if you
do not know the actual size of a. list, you must use a linked list. The second main usage is for
disk-file storage of databases. The linked list allows you to insert and delete items quickly and

216
easily without rearranging- the entire disk file. For these reasons, linked lists are used
extensively in database managers.

Linked lists can be either singly linked or doubly linked, A singly linked list contains a link to
the next data item A doubly linked list contains link to both next and the previous element in the
list The type you use depends upon your application.

Doubly Linked Lists

Doubly linked lists consist of data and links to both the next item and the preceding item.
Previous figure shows how these links are arranged. A list that has two links instead of just one
has two major advantages. First, the list can be read in either direction. This not only simplifies
sorting the list but also, the case of a database, allows a user to scan the list in either direction.
Second, because either a forward link or a backward link can read the entire list, if one link
becomes invalid, the list can be reconstructed using the other link. This is meaningful only in the
case of equipment failure.

Three primary operations can be performed on a doubly linked list: in-start a new first element,
insert a new middle element, and insert a new last element. These operations are shown in the
following figure.

217
Building a doubly linked list is similar to building a. singly linked list, except that the structure
must have room to maintain two links.
here to accommodate this:

struct Employee
{
int Code;
char Name[20];
char Address[40];
int Age;
float Salary;
float OTime;
float Deduct;
struct Employee *pNext;
struct Employee *pPrevious;
};

218
struct Employee *pStart;
struct Employee *pLast;

Using structure employee as the basic data item, the AddList() function builds a doubly linked list

void AddList(struct Employee *pItem)


{

if (!pStart)
{
pItem->pNext = NULL;
pItem->pPrevious = NULL;
pStart = pLast = pItem;
}
else
{
pLast->pNext = pItem;
pItem->pPrevious = pLast;
pItem->pNext = NULL;
pLast = pItem;
}
}

This function places each new entry on the end of the list.

The following function searches for a specified employee code.

struct Employee * SearchList(int Code)


{
struct Employee * pItem;

pItem = pStart;

while(pItem && pItem->Code != Code)


{
pItem = pItem->pNext;
}

return pItem;
}

219
There are three cases to consider when deleting an element from a doubly linked list: deleting
the first item, deleting a middle item, and deleting the last item. The following figure how the
links are rearranged.

The following function will delete an item of type Employee from a doubly linked list;
int DeleteList(int Code)
{
struct Employee *pEmp;
int RetFlag = 1;

pEmp = SearchList(Code);

if (!pEmp)
{
RetFlag = 0;
}
else
{
if (pStart == pLast)
{
pStart = pLast = NULL;
}
else if(pEmp == pStart)

220
{
pStart = pStart->pNext;
pStart->pPrevious = NULL;
}
else if(pEmp == pLast)
{
pLast = pLast->pPrevious;
pLast->pNext = NULL;
}
else
{
pEmp->pPrevious->pNext = pEmp->pNext;
pEmp->pNext->pPrevious = pEmp->pPrevious;
}

free(pEmp);
}

return RetFlag;
}

In order to remove the linked list from memory you have to write the following function.

void FreeList(void)
{
struct Employee * pItem;

while(pStart)
{
pItem = pStart;
pStart = pStart->pNext;
free(pItem);
}

pLast = NULL;
}

221
Binary Trees

Each item in a binary tree consists of information with a


link to the left member and a link to the right member
Binary trees are used in rapid search
The root is the first item in the tree
Tree is a recursive data structure
The retrieval operation is nondestructive

The fourth data structure is the binary tree. Although there can be many different types of trees
binary trees are special because, when they are sorted, they lend themselves to rapid searches,
insertions, and deletions. Each item in a binary tree consists of information with a link to the left
member and a link to the right member. The following figure shows a small tree.

222
The special terminology needed to discuss trees is a classic case of mixed metaphors. The root
is the first item in the tree. Each data item is called a node (sometimes called a leaf) of the tree,
and any piece of thy tree is called a subtree. A node that has no subtrees attached to it is called a
terminal node. The height of the tree equals the number of layers deep that its root grows.
Throughout this discussion, think of binary trees as appearing in memory as they do on paper,
but remember that a tree is only a way to structure data in memory, which is linear in form,

The binary tree is a special form of linked list- Items can be inserted, deleted, and accessed in
any order. Also, the retrieval operation is nondestructive. Although trees are easy to visualize,
they present some very difficult programming problems that this section will only introduce.

Most functions that use trees are recursive because the tree itself is a recursive data structure;
that is, each subtree is a tree. Therefore the routines that are developed are recursive as well.
Non-recursive versions of these functions do exist, but code For them is much more difficult to
understand

223
The order of a tree depends on how the tree is going to be referenced- The process of accessing
each node in a tree is called a tree traversal Consider the following tree:

There are three ways to traverse a tree: inorder, preorder, and postorder. Using inorder, you visit
the left subtree, visit the root, and then visit the right subtree. In preorder, you visit the root, then
the left subtree, and then the right subtree. With postorder, you visit the left subtree, then the
right subtree, and then the root. The order of access for the tree just shown, using each method, is
as follows:

inoder abcdefg
preorder dbacfeg
postorder acbegfd

Although a tree need not be sorted, most uses require it. What constitutes a sorted tree depends
on how you will be traversing the tree. The examples in the rest of this section access the tree
inorder. In a sorted binary tree, the subtree on the left contains nodes that are less than or equal to
the root, while those on the right are greater than the root. The following function, stree() builds a
sorted binary tree:

224
strcut tree stree (struct tree root, struct tree *r, char info )
{
if (!r) {
r = (struct tree *) malloc(sizeof(struct tree ));
if(!r) {
printf (“out of memory \n “) ;
exit (0) ;
}
r-> left =NULL ;
r-> right=NULL ;
r-> info = info ;
if (!root) return r; /* first entry */
if (info < root -> info ) root -> left =r ;
else root -> right =r ;
return r ;
}
if (info <r-> info ) stree(r,r -> left , info ) ;
else
if (info > r- > info ) stree (r –r -> right , info ) ;
}

This algorithm simply follows the links through the tree, going left of right based on the info field.
To use this function you need a global variable that holds the root of the tree. This global must be
set initially to NULL, and a pointer to the root will be assigned on the first call to stree().
Subsequent calls will not need to reassign the root. If you assume the name of this global is rt then
to call the stree() function, you would use

/* call stree () */
if (!rt) rt= stree (rt ,rt , info );
else stree (rt ,rt , info);

in this way both the first and subsequent elements can be inserted correctly,
The stree() function is a recursive algorithm, as are most tree routines. The same routine would be
several times longer if straight iterative methods were employed The function must be called with
a pointer to the root, the left or right node, and information. For simplicity, only a single character
is used here as the information, but you can substitute any simple or complex data type you like,

225
To traverse the tree built using stree() inorder , and to print the Info field of each node, you could
use the inorder() function shown here:

void inorder (struct tree* root)


{
if (! root ) return ;
inorder (root -> left );
printf(“% c “, root -> info ) ;
inorder (root -> right );
}

This recursive function returns when it encounters a terminal node (a null pointer). The functions
to traverse the tree in preorder and in postorder are shown here

void preorder (struct tree* root )


{
if (!root ) return ;
printf (“%c “, root -> info );
preoder (root -> left );
preoder (root -> right);
}

void postorder (struct root* root)


{
if (!root ) return ;
postrder (root -> left );
postrder (root -> right );
printf (“%c “, root -> info );
}

Search functions are easy to implement for binary trees. The following function returns a
pointer to the node in the tree that matches the key, otherwise it returns NULL:
struct tree *search_tree (struct tree root , char key )
{
if (!root ) return root ; /*empty tree */
while (root -> info !=key ){
if (key <root -> info ) root =root -> left ;
else root = root->right;
if (root == null ) break ;
}
return root ;
}

226
Unfortunately, deleting a node from a tree is not as simple as searching the tree. The deleted
node may be either the root, a. left node, or a right node. The node may also have from zero to
two subtrees attached to it. The process of rearranging the pointers lends itself to a recursive
algorithm, as the next program shows.

struct tree dtree(struct tree root , char key)


{
struct tree *p , *p2 ;
if (root -> info == key ) { /* delete root */
/* this means an empty tree */
if (root -> left == root -> right ){
free (root ) ;
return NULL
}
/* or if one subtree is NULL */
else if (root -> left == NULL ) {
p=root->right;
free (root ) ;
return p;
}
else if (root -> right == NULL ) {
p= root -> left ;
free (root);
return p;
}
/* or both tree present */
else {
p2= root -> right ;
p= root->right;
while(p->left) p = p->left;
p->left = root -> left;
free (root) ;
return p2 ;
}
}
if (root -> info <key ) root -> right =dtree (root -> right , key );
else root -> left = dtree (root -> left , key );
return root ;
}
Remember to update the pointer to the root- in the rest of your program, because the node
deleted could be the root of the tree.

227
Binary trees offer tremendous power. flexibility, and efficiency when used with database
management programs because the information for these data-bases must reside on disk and
because access times are important Because a balanced binary tree has. as a worst cases, log2n
comparisons in searching it performs for better than a linked list, which must rely on a sequential
search.

228
Unit 10

229
C and Persistence

What is streams?
File-System Basics
Open file
Close File
Read from file
Write to file

Although C does not have any built-in method of performing file I/O, the C standard library
contains a very rich set of I/O functions.
Most C compilers supply two complete sets of disk I/O functions. One is called the ANSI file
system (sometimes called the buffered file system). This file system is defined by the ANSI C
standard. The second file system is based upon the original UNIX operating environment and is
called the UNIX- like file system (sometimes called the unbuffered file system). This file system
is not defined by the ANSI standard. The ANSI standard only defines one file system because
the two file systems are redundant. Further, not all environments may be able to adapt to the
UNIX-like system. Oue course objectives covers only the buffered file system.

Streams
In C the stream is a common logical interface to the various devices that comprise the computer.
In its most common form a stream is a logical interface to a file. As C defines the term “file” it
can refer to a disk file, the screen, the keyboard, a port, a file on tape, and so on. Although files
differ in form and capabilities, all streams are the same. The advantage to this approach is that
any hardware device will look much like any other. The stream provides a consistent interface.

230
A stream is linked to a file using an open operation. A stream is disassociated from a file using a
close operation.
There are two types of streams: text and binary. A text stream is used with ASCII characters.
When a text stream is being used, some character translations may take place. For example,
when the newline character is output, it is converted into a carriage-return/linefeed sequence. For
this reason, there may not be a one-to-one correspondence between what is sent to the stream and
what is written to the file. A binary stream may be used with any type of data. No character
translations will occur, and there is a one-to-one correspondence between what is sent to the
stream and what is actually contained in the file.
One final concept we need to declare is that of the current location. The current location, also
referred to as the current position, is the location in a file where the next file access will occur.
For example, if a file is 100 bytes long and half the file has been read, the next read operation
will occur at byte 50, which is the current location.

File-System Basics
The file system basics cover the following:
• Open file
• Close File
• Read from file
• Write to file

To open a file and associate it with a stream, use fopen(). Its prototype is.
FILE* fopen(char fname, char *mode);
The fopen() function, like all the file-system functions, uses the header STDIO.H. The name of
the file to open is pointed to by fname. It must be a valid file name, as defined by the operating
system. The string pointed to by mode determines how the file may be accessed. The legal values
for mode are shown in the following table
Mode Meaning
r Open a text file for reading
w Create a text file for writing
a Append to a text file
rb Open a binary file for reading

231
wb Create a binary file for writing
ab Append to a binary file
r+ Open a text file for read/write
w+ Create a text file for read/write
a+ Append or create a text file for read/write
r+b Open a binary file for read/write
w+b Create a binary file for read /write
a+b Append a binary file for read/write

.
If the open operation is successful, fopen() returns a valid file pointer. The type FILE is defined
in STDIO.H. It is a structure that holds various information about the file.
If the fopen() function fails, it returns a null pointer.
For example, the proper way to open a file called myfile for text input is shown in this fragment.
FILE *fp;
if((fp = fopen("myfile", "r"))==NULL)
{
printf("Error opening file\n");
exit(1);
}

To close a file, use fclose(), whose prototype is


int fclose(FILE *fp),
The fclose() function closes the file associated with fp, which must be a valid file pointer
previously obtained using fopen(), and disassociates the stream from the file. In order to improve
efficiency, most file system implementations write data to disk one sector at a time. Therefore,
data is buffered until a sector's worth of information has been output before the buffer is
physically written to disk. When you call fclose(), it automatically writes any information
remaining in a partially full buffer to disk. This is often referred to as flushing the buffer.
The fclose() function returns 0 if successful. If an error occurs, EOF is returned.
Once a file has been opened, depending upon its mode, you may read and/or write bytes to or
from it using these two functions.
int fgetc(FILE *fp);
int fputc(int ch, FILE *fp)',

232
The fgetc() function reads the next byte from the file described by fp as an unsigned char and
returns it as an integer. The reason that it returns an integer is that if an error occurs, fgetc()
returns EOF, which is an integer value. The fgetc() function also returns EOF when the end of
the file is reached. Your routine can assign the fgetc(}'s return value to a char. The fputc()
function writes the byte contained in ch to the file associated with fp as an unsigned char.
Although ch is defined as an int, you may call it using simply a char, which is the common
procedure. The fputc() function returns the character written if successful or EOF if an error
occurs.
The following program demonstrates the basics of file system.
#include "stdio.h"
#include "stdlib.h"
void main(void)
{
FILE *fp;
char str[80] = "This is a file system test";
char *p;
int i;
/* open myfile for output */
if((fp = fopen("myfile", "w"))==NULL) {
printf("Cannot open file\n") ;
exit (1) ;
}
p = str;
while (*p)
{
if(fputc(*p, fp)==EOF) {
printf("Error writing file\n") ;
exit (1) ;
}
p++;
}
fclose(fp) ;
/* open myfile for input */
if((fp = fopen("myfile", "r"))==NULL) {
printf ("Cannot open file\n") ;
exit (1) ;
}

233
/*• read back the file */
for(; ;) {
i = fgetc (fp) ;
if (i == EOF) break;
putchar(i) ;
}
fclose (fp) ;
}

The following program takes two command-line arguments. The first is the name of a file, the
second is a character. The program searches the specified file, looking for the character. If the
file contains at least one of these characters, it reports this fact. Notice how it uses argv to access
the file name and the character for which to search.

/* Search specified file for specified character. */


#include "stdio.h"
#include "stdlib.h"
void main(int argc, char *argv[])
{
FILE *fp;
char ch;
/* see if correct number of command line arguments */
if(argc!=3) {
printf("Usage: find. <filename> <ch>\n");
exit(1) ;
}
/* open file for input */
if((fp = fopen(argv[1], "r"))==NULL) {
printf ("Cannot open file\n") ;
exit(1) ;
}
/* look for character */
while((ch = fgetc(fp)) != EOF}
if(ch == *argv[2] ) {

234
printf ("%c found", ch) ;
break;
}
fclose (fp) ;
}

235
Other Text Functions

Text file functions


int fputs(char *str, FILE *fp);
char* fgets(char *str, int num, FILE *fp);
int fprintf(FILE *fp, char *control-string,...);
int fscanf(FILE *fp, char *control-string,...);

When working with text files, C provides four functions which make file operations easier. The
first two are called fputs() and fgets(), which write a string and read a string from a file
respectively. Their prototypes are
int fputs(char *str, FILE *fp);
char* fgets(char *str, int num, FILE *fp);
The fputs() function writes the string pointed to by str to the file associated with fp. It returns
EOF if an error occurs and a non-negative value if successful. The null that terminates str is not
written. Also unlike its related function puts() it does not automatically append a carriage-return/
linefeed sequence.
The fgets() function reads characters from the file associated with fp into the string pointed to by
str until num-1 characters have been read, a newline character is encountered, or the end of the
file is reached. In any case, the string is null-terminated. Unlike its related function gets(), the
newline character is retained. The function returns str if successful and a null pointer if an error
occurs.
The C file system contains other two very powerful functions. They are fprint() and fscanf().
These functions operate exactly like printf() and scanf() except that they work with files. Their

236
prototypes are
int fprintf(FILE *fp, char *control-string,...);
int fscanf(FILE *fp, char *control-string,...);
Instead of directing their I/O operations to the console, these functions operate on the file
specified by fp. Otherwise their operations are the same as their console-based relatives.
The advantage to fprintf() and fscanft) is that they make it very easy to write a wide variety of
data to a file using a text format.
This following program demonstrates fputs() and fgets(). It reads lines entered by the user and
writes them to the file specified on the command line. When the user enters a blank line, the
input phase terminates, and the file is closed. Next, the file is reopened for input, and the
program uses fgets() to display the contents of the file.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

void main(int argc, char *argv[])


{
FILE *fp;
char str[80] ;
/* check for command line arg */
if(argc!=2) {
printf("specify file name\n") ;
exit(1) ;
}
/* open file for output */
if((fp = fopen(argv[1], "w"))==NULL) {
printf("Cannot open file\n");
exit(1) ;
}
printf("Enter a blank line to stop\n");
do {
printf (" : " ) ;
gets(str) ;

237
strcat(str, "\n"); /* add newline */
if(*str != '\n') fputs(str, fp) ;
} while(*str != '\n') ;
fclose (fp) ;
/* open file for input */
if((fp = fopen(argv[1], "r"))==NULL) {
printf ("Cannot open file\n") ;
exit(1) ;
}
/* read back the file */
do {
fgets(str, 79, fp) ;
printf(str) ;
} while(!feof(fp) ) ;
fclose(fp) ;
}

This following program demonstrates fprintf() and fscanf(). It first writes a double, an int,
and a string to the file specified on the command line.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

void main(int argc, char *argv[])


{
FILE *fp;
double ld;
int d;
char str[80] ;
/* check, for command line arg */
if(argc != 2) {
printf("specify file name\n");
exit(1) ;
}

238
/* open file for output */
if((fp = fopen(argv[1] , "w" ) ) ==NULL) {
printf("Cannot open file\n");
exit (1) ;
}
fprintf(fp, "%lf %d %s", 12345.342, 1908, "hello");
fclose(fp) ;
/* open file for input */
if((fp = fopen(argv[1], "r")) == NULL) {
printf("Cannot open file\n");
exit(1) ;
}
fscanf(fp, "%lf %d %s", &ld, &d, str) ;
printf("%lf %d %s", ld, d, str);
fclose (fp) ;
}

239
Read and Write Binary Data

Binary File functions


size_t fread(void ^buffer, sizeJ: size, size_t num, FILE *fp);
size_t fwrite(void ^buffer, size_t size, size_t num, FILE
*fp};

The C file system includes two important functions; fread() and fwrite(). These functions can
read and write any type of data/ using any kind of representation- Their prototypes are
size_t fread(void ^buffer, sizeJ: size, size_t num, FILE *fp);
size_t fwrite(void ^buffer, size_t size, size_t num, FILE *fp};
The fread() function reads from the file associated with fp, num number of objects, each object
size bytes long, into the buffer pointed to by buffer. It returns the number of objects actually
read. If this value is 0, no objects have been read/ and either the end of the file has been
encountered or an error has occurred.
The fwrite() function is the opposite of fread(). It writes to the file associated with fp, num
number of objects, each object size bytes long, from the buffer pointed to by buffer. It returns the
number of objects written. This value will be less than num only if an output error has occurred.
As a simple example, the following program writes an integer value to a file called MYFILE
using its internal, binary representation.

#include "stdio.h"
#include "stdlib.h"

240
void main(void)
{
FILE *fp;
int i ;
/* open file for output */
if((fp = fopen("myfile", "w"))==NULL) {
printf("Cannot open file\n") ;
exit (1) ;
}
i = 100;
if(fwrite(&i, sizeof(int), 1, fp)!=1) {
printf("Write error occurred");
exit(1) ;
}
fclose(fp) ;
/* open file for input */
if((fp = fopen("myfile", "r")) == NULL) {
printf("Cannot open file\n") ;
exit (1) ;
}
if(fread(&i, sizeof(int), 1, fp)!=1)
{
printf("Read error occurred");
exit(1) ;
}
printf("i is %d", i) ;
fclose(fp) ;
}

The following program fills a ten-element array with floating point numbers/ writes them to a
file, and then reads them back. The file must be opened for binary I/O operations.

#include "stdio.h"
#include "stdlib.h"

double d[10] = {
10.23, 19.87, 1002.23, 12.9, 0.897, 11.45, 75.34, 0.0, 1.01, 875.875
};
void main(void)

241
{
int i ;
FILE *fp;

if((fp = fopen("myFile", "wb"))==NULL) {


printf("cannot open file") ;
exit (1) ;
}
/* write the entire array in one step */
if(fwrite(d, sizeof (d), 1, fp)!=1) {
printf("write error") ;
exit (1);
}
fclose(fp) ;

if((fp = fopen("myfile", "rb"))==NULL) {


printf ("cannot open file") ;
exit (1) ;
}
/* clear the array */
for(i=0; i<10; i++) d[i] = -1.0;
/* read the entire array in one step */
if(fread(d, sizeof (d), 1, fp)!=1) {
printf("read error") ;
exit (1) ;
}
fclose(fp) ;
/* display the array */
for(i=0; i<10; i++) printf ("\n%lf ", d[i] ) ;
}

242
Random Access

Files in C can be accessed randomly using the functions


int fseek(FILE *fp, long offset, int origin);
long ftell(FILE *p);

We can access any point in the file using another C's file system functions. The function that lets
you do this is called fseek(), and its prototype is
int fseek(FILE *fp, long offset, int origin);
fp is associated with the file being accessed. The value of offset determines the number of bytes
from origin to make the new current position. Origin must be one of these macros, shown here
with their meanings.
Value of Origin Meaning
SEEK_SET Seek from start of file
SEEK_CUR Seek from current location
SEEK_END Seek from end of file
These macros are defined in STDIO.H. For example, if you wanted to make the current location
"100 bytes from the start of the file/ then origin will be SEEK_SET and offset will be 100.
The fseek() function returns 0 when successful and non-0 if a failure occurs
You can determine the current location of a file using ftell(), another file system functions. Its
prototype is
long ftell(FILE *p);
It returns the location of the file position indicator within the file associated with fp. If a failure

243
occurs, it returns -1L.
In general, you will want to use random access only on binary files.

The following program uses fseek() to report the value of any byte within the file specified on
the command line.
#include "stdio.h"
#include "stdlib.h"
void main(int argc, char *argv[])
{
long loc;
FILE *fp;

if (argc !=2) {
printf(“File name missing”);
exit(1);
}

if ((fp = fopen(argv[1], “rb”)) == NULL) {


printf(“cannot open file”);
exit(1);
}

printf("Enter byte to seek to: ") ;


scanf("%ld", &loc) ;
if(fseek(fp, loc, SEEK_SET)) {
printf("seek error");
exit(1) ;
}
printf("Value at loc %ld is %d", loc, fgetc(fp));
fclose (fp) ;
}

The following program uses ftell( ) and fseek() to copy the contents of one file into another in
reverse order.

244
#include "stdio.h"
#include "stdlib.h"

void main(int argc, char *argv[])


{
long loc;
FILE *in, *out;
char ch;

/* see if filename is specified */


if(argc!=3) {
printf("File name missing");
exit (1) ;
}

if((in = fopen(argv[1], "rb")) == NULL) {


printf("cannot open file") ;
exit(1);
}
if((out = fopen(argv[2], "wb")) ==NULL) {
printf("cannot open file");
exit (1) ;
}
/* find end of source file */
fseek(in, 0L, SEEK_END);
loc = ftell(in) ;
/* copy file in reverse order */
loc = loc-2; / * back up past end-of-file mark */
while (loc >= 0L) {
fseek(in, loc, SEEK_SET) ;
ch = fgetc (in) ;
fputc(ch, out) ;
loc--;
}
fclose (in) ;
fclose (out) ;
}

245