Chapter 22. C Programming Basics

CONTENTS
  •  Introduction
  •  Format of a C Program
  •  Good Programming Practices
  •  Uppercase and Lowercase
  •  C Language Nuts and Bolts
  •  Arithmetic Expressions
  •  Looping
  •  Choice
  •  Functions
  •  Arrays
  •  Strings
  •  Structures
  •  Pointers
  •  More Data Types
  •  Dynamic Memory Allocation

Introduction

There are many programming basics that span both C and C++. These basics will be introduced using C language. The upcoming programming sections are only an introduction. They don't provide enough of information to be used as a reference for the C language. You want to supplement this section with a book dedicated to the C language.

C programs are written in terms of functions. A program will go into functions, execute the logic and leave. Frequently, functions are made up of other functions and statements. Statements are lines of code that carry out a task.

A C program must have a function called main(). The main function is the starting point of all C and C++ programs. Within the main() function is the body of the program as shown in Figure 22-1. The body consists of statements and functions. Preprocessor directives are listed outside and above the main() function.

Figure 22-1. C Program main( )

graphics/22fig01.gif

Many find it helpful to look at a simple but complete C program to see all of its parts.

A complete C program consists of at least the stdio.h preprocessor directive and a main() function with the body of the program between the main() brakets. The program must be in a file with a ".c" extension.

The C program TestAverage.c in Figure 22-2 is a complete C program. It will perform arithmetic operations and print the results. Once this program is compiled successfully, it can be executed. This program takes test scores, adds them up, then divides them to find the average, or final grade. The the test scores and final grade is then incorporated into a sentence and printed to the screen.

Figure 22-2. C Program TestAverage.c

graphics/22fig02.gif

Any comments in the program are between the /* and */ symbols. Comments are ignored by the compiler and are for readability. It is considered good programming practice to include comments.

Preprocessor directives are listed first, followed by the main() function. The left curly brace ({) indicates the beginning of the block of code known as the body of the function. Variables representing the grades are assigned data types and are populated with values. Additional variables are declared to hold the sum and the average of the grades. The grades are added together, and the average is computed. The printf() function is part of the standard C libraries and will print to the screen. The last line of the source code in the body of the function is the return statement. A return statement allows a function to send back a value to the function from which it was invoked. If the main() function is returning a value, it is sent to the UNIX shell. All functions

should end with a return statement. The right curly brace (}) denotes the end of the block of code.

For now, review the program to become familiar with its overall format. The output of the program is minimal. The test scores are printed along with the final grade, which is the average of the three test scores.

Format of a C Program

C is known as a free-form language. That is, the programming statements can start at any column of any line. Blank lines are allowed and are recommended for readability. Interestingly enough, C language does not require each statement to be on a separate line. All statements can be one continuous line with semicolons (;) denoting the end of a statement. This style is not recommended, however. A C program can look confusing enough. Programmers need to be concerned with maintainability. If the program is not easy to read, it will not be easy to maintain.

Good Programming Practices

There are a few things that can be done to keep a C programreadable.

First, use comments to explain the functionality. It is a good idea to put a comment in the very beginning of the source file, explaining what the functions in the file do. If the source file contains the main() function, describe the program. Some suggested information to include is the source file name, the author, the date it was created, and what the program does.

Use variable names that are descriptive of the data they hold. Function names should also be descriptive, giving some idea of their purpose and/or behavior.

Put C statements on individual lines.

Make good use of white space; separating lines and statements with spaces or tabs helps clarity. Surround functions with white space. This helps the function stand out as a block of code so that the reader's eye will drop to it.

Indent the source code inside the braces:

function_1()  {       (body of function is indented)  } 

Use comments throughout the program.

Comments

Comments help readability and therefore, maintainability. Comments start with /* and end with */. Everything between is considered a comment and will be ignored by the compiler.

A comment can be on one line of code or can span multiple lines. Comments can be inserted before a statement on a line or appended to the end.

You want to comment as you program while the thought is fresh in your mind. Some programmers may decide to fill in comments after the source code is complete but these are frequently neglected due to lack of time. Also, comments inserted after the fact may not be as clear or as helpful.

Uppercase and Lowercase

Upper- and lowercase letters are more significant in C language than most other programming languages. You may have noticed that most of the program in TestAverage.c is in lowercase. No commands in C language have uppercase letters. Preprocessor directives are the one exception. They will be covered later in this chapter.

C Language Nuts and Bolts

Now that you've had the chance to look at a C program and become familiar with some good programming practices, it is time to discuss the C language itself. We will start with the basics and begin to develop an understanding of some key concepts, using C programs or fragments of programs to illustrate various topics.

Standard Library

The standard library comes with the compiler and contains a substantial number of functions. These functions are pre-written and tested and provide standard functionality for reading data from a keyboard, reading information from files, displaying information to a screen, and mathematical routines, to name only a few.

It would take a chapter of significant size to address all the functionality supplied by the standard library. Books dedicated to teaching C programming address a majority of the most popular functions. Also, they usually supply a comprehensive list of header files with a brief description of the functions contained within.

To use the standard library facilities, standard header files need to be included in the source file using the preprocessor directive #include.

Constants

We've previously discussed the concept of a variable, a place in the computer memory that has a name and contents that can be changed. A constant is also a value stored in computer memory, but the value cannot be changed.

A constant can be assigned to a variable, but you cannot assign new values to a constant. Below is an example of how a constant can be assigned to variables:

pens = 10; 

This shows how the constant 10 can be assigned to the variable pens. Essentially, the value of 10 has been placed in the pens variable.

There are a few types of constants. An integer constant can be formed using the digits 0 through 9. The character constants are enclosed within single quotes ('Z'). Floating-point constants use the digits 0 through 9 and a decimal point.

Symbolic Constants

Symbolic constants are names or identifiers that can be used in a program instead of the actual value of a constant. When the program is compiled, the preprocessor searches the source code for every occurrence of the symbolic constant identifier and substitutes it with the actual constant value.

The define directive is used to create a symbolic constant. The define directive is a preprocessor directive that assigns a name or identifier to the constant. Preprocessor directives are statements beginning with a pound sign (#). The define directive consists of #define followed by an identifier and then a constant. The following directive defines a symbolic constant for the value of pi:

#define PI 3.14 

Symbolic constants are useful in situations where a constant is used throughout a program and may change. A good example is a program that includes calculations for mileage reimbursements. The permile reimbursement value may change occasionally. If the reimbursement value changes from .32 to .33, a programmer would have to search the program for every occurrence of .32 and change it to .33.

A better method is to assign the constant to an identifier using the define directive. The directive

#define PER_MILE .32 

appears once, either at the top of the source file or in a header file. The programmer uses the identifier PER_MILE instead of the actual constant value of .32 through out the program. When the reimbursement value changes, the programmer only updates the define directive.

Escape Sequences

Using certain character constants can be problematic. Expressing 'newline' or 'tab' as character constants cannot be done directly, when entered, they will go to a new line or a tab within the source file.

These control characters can be entered as constants by means of an escape sequence. An escape sequence is an indirect way of specifying these control characters. Escape sequences always begin with a backslash (\).

The escape sequences are shown in Table 22-1.

Table 22-1. Escape Sequences

Control Character

Escape Sequence

new line

\n

horizontal tab

\t

vertical tab

\v

backspace

\b

carriage return

\r

form feed

\f

alert - bell

\a

backslash

\\

single quote

\'

double quote

\"

question mark

\?

Data Types

There are four basic data types in C: integer, floating point, double precision floating point, and character data. Another data type discussed will be the void data type. C language needs to know what type of data it is manipulating. Data types are used to define the type of data a variable will be used for and the type of data returned from a function. Data types and functions require a more comprehensive discussion and will be addressed in the "Functions" section.

For example, a program is created to give directions to the Statue of Liberty from anywhere in the country. The program has a variable called road_exit to indicate which numbered exit should be used when entering or leaving a particular highway. This variable contains whole numbers, because exit numbers can be 12, but not 12.5. The program defines the variable road_exit to contain only data of type integer.

The definitions of variables are found before the programming statements in the body of a function.

void directions_home()  {      int road_exit;  /* Variable is defined with data type and goes here */       /* Programming statements inserted below */  } 

When the program knows what type of data it is dealing with, it will know how to treat the data, and what operations are legal and which are illegal. Data types are assigned to variables before they are used in the body of the function. The format for declaring a variable is data type, variable name followed by a semicolon:

data_type   variable_name; 

To declare multiple variables of the same data type on a single line, separate the variable names with a comma:

data_type   variable_name1, variable_name2; 

A constant can be assigned to a variable at the time the data type isdeclared. This action is referred to as initializing thevariable, or giving it a starting value:

data_type  variable_name1 = constant; 

Variables can also be initialized after they are declared, in the body of the function, by assigning it a value.

variable_name1 = constant; 

Each basic data type is described in the upcoming sections.

Integer

An integer is any positive or negative number that does not have a decimal point. Integers are also known as whole numbers. C refers to these numbers as integer constants. A variable of type integer is denoted by int within a C program:

int grade1; 

Integers may be preceded by a "+" or "-" sign. Integers cannot contain commas, decimal points, or special symbols such as a "$". Examples of valid integers are:

10  -15  +999  256  -26112 

Each computer system has its own internal limit on the size of a number that can be used in a variable of type integer. This is dependent upon the storage allocated by the computer for each type of data. A computer will allocate storage by bytes. Each byte consists of bits. A bit is the smallest piece of information a computer can handle, and will contain the value of either 0 or 1. A byte consists of eight bits, and can represent a single character, digit, or symbol.

To determine the storage allocation for your computer system, refer to the computer's reference manual. Some common storage allocations are listed in Table 22-2.

Table 22-2. Common Storage Allocations

Storage Allocated

Max Integer Value

Min Integer Value

1 byte

127

-128

2 bytes

32767

-32768

4 bytes

2147483647

-2147483648

If the integer maximum for your machine is too small, the qualifier unsigned can be used:

unisigned int my_bank_acct; 

Unsigned means that all numbers in the variable will be a positive number, negatives are not allowed. Unsigned int would change the storage table to look like that of Table 22-3.

Table 22-3. Unsigned Storage Allocations

Storage Allocated

Max Integer Value

Min Integer Value

1 byte

255

0

2 bytes

65535

0

4 bytes

4294967295

0

Floating Point

Floating-point numbers are any numbers containing decimal points. Variables containing floating-point numbers are denoted as type float in C language:

float acct_total; 

Valid floating point numbers are:

+16.776  5.  -6.2  3244.25 0.0  0.222  11.0 

As with integers, commas and special symbols are not allowed. The number of digits stored in a variable of type float is dependent upon your computer system.

Floating-point variables can represent extremely large and extremely small numbers, but there are limits, just as there are with integer numbers. The minimum positive value that a variable of type float can have is 1.0e-37, or a 1 preceded by a decimal point and 36 zeros. The maximum value is 1.0e38, or a 1 followed by 38 zeros.

Double Precision

Double-precision numbers are similar to type float. Double precision is denoted with double:

double acct_total; 

It is used whenever the accuracy provided by a float is not sufficient. Variables declared to be of type double might store roughly twice as many significant digits as can a variable of type float. The precise number of digits stored depends upon the computer system. Like variables of type float, variables of type double do not allow commas and special symbols.

The minimum and maximum values for a double-precision number are dependent upon the computer system. They can greatly extend the maximum and minimum found in floating-point number variables.

Exponential Notation

Floating-point and double-precision numbers can be written in exponential notation. Exponential notation is commonly used to express either very large or very small numbers using a compact notation.

In exponential notation, the letter e is used to indicate the exponent. The number following the e represents a power of 10, which indicates the number of places the decimal point should be moved to obtain the standard format for a decimal number. If the number after the e is a positive number, the decimal point should be moved that many places to the right. If the number is negative, the decimal point is moved to the left.

For example, e5 tells us to move the decimal point five places to the right, and the number 5.897e5 becomes 589700. The e-3 in number 1.664e-3 tells use to move the decimal point three places to the left, and the number becomes .001664.

Character

The fourth basic data type recognized by the C language is the character type. Characters can be letters of the alphabet, one of the ten digits 0 through 9, and special symbols such as ! *$,+@. A single character consists of any one letter, digit, or special symbol enclosed by single quotes. For example:

'A'    '9'    'r'    '!'    '+'    'c' 

A variable of type character is declared as follows:

char  letter_grade; 

Do not confuse single characters with a string of characters.

For example, the single characters 'B', 'e', 'a', 'c', 'h' are different from the string "Beach." Each of the letters represented as 'B' , 'e', 'a', 'c', 'h' is viewed by the computer as a separate letter, in spite of the fact that we may automatically read the word Beach. The 'B' may be stored in one place in memory, the 'e' may be somewhere else, and so on. The computer sees each letter as its own logical unit.

Because humans read the word Beach, C language gave us a way to logically lump the characters together into a "word" or string. A string consists of a series of characters, digits between 0 and 9, and special characters, surrounded by double quotes (" "). The characters in the "Beach" string are actually stored contiguously inside the computer, or one next to the other. C language makes our job a little easier by supplying a standard set of string functions for use, since it would be difficult to deal with each character individually. We will learn more about strings a little later.

Void

The data type of void is a generic data type that has no specific size or range of values associated with it. A void value is nonexistent; values cannot be stored in a variable of the data type void. If you attempt to assign a value to it, the compiler will issue an error message.

This data type seems a little ridiculous, but there are uses for it. Void can signal to the compiler that a function will not be returning a return value.

main()  {    void function_1();  } 

This topic will be discussed in greater depth in the "Functions" section.

Arithmetic Expressions

Integers, floating-point, and double-precision numbers can be added (+), subtracted (-), multiplied (*), and divided (/). I recommend that math operations be performed on like data types, although math operations between different data types are allowable. Arithmetic operations are also allowed on character data types.

Each arithmetic operator (+, -, *, /) is a binary operation, requiring two operands. This means that there must be something on each side of the arithmetic operator to perform the arithmetic operation on. They associate from left to right. This means the operator (+ - * /) uses the left operand first, and then applies the right. Examples of arithmetic expressions are shown in Figure 22-3.

Figure 22-3. Arithmetic Operators

graphics/22fig03.gif

Spaces around the operators are not required but may be included for clarity.

There are two basic rules governing the type of data used and the resulting data type for these arithmetic expressions. They are as follows:

  1. If all operands are integers (int), the resulting value is an integer.

  2. If any operand is a floating-point (float) or a double precision (double) data type, the result is a double-precision number.

The fifth arithmetic operator, the modulus (%) operator, is also a binary operation. This operator results in the remainder of the first operand after it is divided by the second. As an example, the result of 7%4 is 3 because seven divided by four yields a dividend of 1 with a remainder of 3. The dividend is ignored, and the remainder is the resulting value.

The modulus operator only works with integer numbers. If a variable of any other data type is used, the compiler will generate an error.

Figure 22-4 is a program using the modulus operator to calculate a leap year.

Figure 22-4. C Program leapyear.c

graphics/22fig04.gif

The program will print "Enter the year: " on the screen of the computer. This is called prompting for input. The user then enters a year.

The scanf() function allows a value for the year to be accepted from the keyboard while the program is running. Notice the if..else statement is used. This helps us print the correct message, determining whether the year evaluated is a leap year. We check the result of the modulus operator, which resides in the leap_year variable, to determine which message to print. The if...else statement will be further addressed in the "Choice" section.

The \n in the printf() function will generate a new line after the message is printed. The printf() function also uses %d to print integer variables, %f for floating-point variables, and %c for character variables.

Unary arithmetic operators have only one operand. The result of applying a unary minus(-) operator is the negative of the operand value.

For example, the unary minus turns a positive number into a negative and a negative number into a positive. The applying the plus (+) operator has no effect on the operand value.

Below is a fragment of C code illustrating this:

count = 10;  a_difference = -count; /* a _difference now contains -10 because the value                          of count was changed by the unary minus operator */  no_difference = +a _difference;                         /* no_difference contains -10 because the unary plus                        operator had no effect on the value of a_difference */  no_difference = -a_difference;                         /* no_difference now contains a 10 because the unary                          minus operator changed the value of a_difference */ 

Increment and Decrement Operators

The increment (++) operator adds one to the value of an operand, and the decrement (--) operator will subtract one. These are unary operators because they require one operand. You can think of these operators as shorthand notation for adding or subtracting 1 from a value.

For example, to add one to a number using the addition operator, you would do the following:

counter = counter + 1; 

This same calculation can be expressed more easily using the increment operator:

counter++; 

Both statements do the same thing: Add 1 to a value in the counter variable. You can use the increment and decrement operators with any numeric operand, such as integer, floating point and double precision numbers.

There are two variations on the increment and decrement operators: when the operator is used in front of the operand, known as a prefix increment (++counter) or decrement (--counter), and when the operator follows the operand, known as a postfix increment (counter++) or decrement (counter--). A prefix operator changes the operand before it is used, and the postfix operator changes the operand after it is used.

To illustrate this situation, below is a fragment of C code:

int bottles = 3;       /* declare variable and initialize */  int cans = 2;  bottles++;             /* bottles is now 4 */  cans = bottles++;      /* after cans is assigned 4 from bottles,                            bottles is incremented to 5.                            cans now has 4                            bottles now has 5 */  bottles++;             /* bottles now has 6 */  cans = --bottles;      /* bottles is decremented first to 5,                             and then 5 is assigned to cans                            bottles now has 5                            cans now has 5 */ 

Assignment Operators

Assignment operators allow a value to be given to a variable. The most basic assignment operator is the equal sign (=). To assign a value, place the variable on the left and the value to be assigned on the right.

dalmatians = 101; 

The variable dalmatians contains 101.

In addition to =, C language has other assignment operators that combine assignment with another operation for a shorthand notation.

A statement normally written:

biscuits = biscuits * 2; 

can be written with an assignment operator combined with the multiplication operator as:

biscuits *= 2; 

A slightly more involved expression is:

biscuits /= kibbles + treats; 

which would be identical to:

biscuits = biscuits / ( kibbles + treats ); 

The valid combinations involving assignment operators are:

+=  -=  *=  /=  %= 

Type Conversion

C allows type conversion, which is a temporary change in the data type of a variable, constant, or expression after it has been declared, usually to help match up different data types between operands. Conversion of data types either occurs automatically in the compiler according to compiler rules, or when a programmer explicitly requests it with a cast operator.

Whenever an expression consists of variables, constants, or expressions of different data types, the compiler must decide what to do. For example, a floating point of 3.0 divided by an integer of 2 will result in 1.5. This number will not fit into an integer, but it will fit into a floating point number. The best solution for the compiler is to put the result into a floating point number and continue evaluating the expression.

The general rule is that a compiler will convert the expression to a common data type before the calculation is done. The compiler will promote or convert the number to the largest data type present. For example, if an expression consists of integers and floating point numbers, all numbers will be promoted to floating-point numbers. If an expression contains integer, floating-point and double-precision numbers, all numbers will be promoted to double-precision numbers. If a demotion were to occur, part of the larger number would be discarded to fit into a smaller data type.

A demotion can occur when the left operand of the assignment operator is a smaller data type than the right operand. For example, if an expression consists floating-point numbers, the resulting value will also be a floating-point number. If this number is stored in an integer variable, a demotion occurs. The accuracy or precision of the floating point number is lost.

float data1=3.0, data2=2.0;  int results;  results = data1/data2; 

The right operand of the assignment operator, the expression data1/data2, evaluates to the floating-point number 1.5. This number is assigned to the left operand, the integer variable results. The number actually stored in the integer variable results is 1, because the floating-point number 1.5 is truncated and precision is lost.

C provides the cast operator, allowing programmers to convert a variable, constant, or expression to another data type after the original declaration has occurred. Casting does not permanently affect the original variable or its contents; it temporarily casts the variable to the data type requested.

The cast operator has the form of (data type), a data type enclosed by parentheses. The cast operator will convert the constant, variable, or expression immediately following it into the data type of the cast operator.

The format for casting is as follows:

( data type ) variable 

or

( data type ) constant 

or

( data type ) (expression) 

The example below shows the casting of a single variable in an expression to a floating-point number.

float results, data_3 = 5.5;  int data_1 = 3, data_2 = 2;  results = ((float)data_1/data_2) * data_3; 

By casting the data_1 variable to float in the expression data_1/ data_2, the compiler is forced to promote the integer variable data_2 to a floating-point number. This promotion ensures that the precision is not lost when dividing the integer numbers. The resulting floating-point number is not truncated; it is used in evaluating the rest of the expression.

Precedence

It is very important to understand the concept of precedence when evaluating complex expressions. C language has rules of precedence and associativity. These rules determine the order of evaluation of expressions. The rules are fairly simple. Apply operators with the highest precedence first; if two or more operators are of equal precedence, then the associativity determines the order.

Table 22-4 lists operators in their order of precedence, starting with the highest precedence.

Table 22-4. C Operators in Order of Precedence

Operator

Associativity

( )

Left to right

unary +, unary -, sizeof(), ++, --, (type)

Right to left

*, /, %

Left to right

binary +, binary

Left to right

=, +=, -=, *=, /=, %=

Right to left

The compiler evaluates operators with a higher precedence first. If two operators of the same precedence are to be evaluated, the associativity tells the compiler how to "break the tie" and which one to process first. Depending upon the associativity, the compiler may start with the right-most operator and evaluate moving left, or vice versa.

Pay particular attention to the parentheses operator on the top line of the table. This operator forces evaluation of an enclosed expression first, because it has the highest priority.

total =2 +8 *8 /2      /* Theresult evaluates to 34 */ 

In the above example, the * and / operators are of equal precedence. Associativity tells us to evaluate left to right. The * operator is evaluated first, 8 *8, resulting in 64. Next the /operator is evaluated, 64 / 2, resulting in 32. The binary + is evaluated next, because it is of lower precedence, 2 + 32, resulting in 34.

When the parentheses operator is used, the answer is different.

total = (2 + 8)* 8 / 2  /* The result evaluates to 40 */ 

The parentheses are of highest precedence; therefore, anything inside is evaluated first. The expression 2 + 8 results in 10. Next, because the * and / operators are of equal precedence, they are evaluated left to right; 10 * 8 is 80 and 80 / 2 is 40. The final result of this equation is different from the previous example, due to the higher precedence of the parentheses operator.

Parentheses can also be nested, and the compiler always evaluates the parentheses within the innermost parentheses first, and then proceeds to the next innermost parentheses and so on until the last expression is evaluated. Below is a logical breakdown of how an expression with nested parentheses is evaluated:

graphics/22icon01.gif

Looping

Now that you've been exposed to some of the foundation needed for C language programming, it is time to investigate some of the control statements needed to build more complex programs. C language has a small but powerful set of control statements.

In this section, you will learn how to perform repetitive actions by executing some statements more than once. This is referred to as looping.

There are two principal types of loops: while and for, with a variation of the while loop called do while. The basic constructs of loops and their behaviors were previously discussed in general terms in the "Programming Constructs" section. This section will discuss C looping syntax.

When working with or describing loops, I make references to return values from expressions. These values are either TRUE or FALSE, where TRUE is a nonzero number and FALSE is a zero. For simplicity, the words TRUE and FALSE will be used. A programmer can use define directives for these values in a program.

For Loops

For loops are used to control definite looping, when we know how many times we want to loop. The syntax for this statement is:

for ( initialize expression; loop condition; update expression)                statement; 

The three expressions, initialize expression, loop condition, and update expression, set up and control the environment for the loop. The statement that immediately follows is the body of the loop consisting of any valid C statement.

If the body of the loop consists of more than one statement, curly braces ({ }) are used to define the body.

for ( initialize expression; loop conditon; update expression)  {               statement;                statement;  } 

The first component of the for loop is labeled initialize expression. This is used to set the initial value of the control variable before the loop begins. An example of a valid expression is x = 0;. The variable x is assigned the value of zero before the loop begins. The initialize expression is performed once, at the start of the loop.

The second component, loop condition, specifies the condition or set of conditions that are necessary for the loop to continue. After the loop condition is satisfied, the looping stops. The loop condition is specified with a relational expression. A relational expression compares variables using relational operators.

Relational operators provide a means to compare variables, constants and expressions. There are six operators in C that can test the relationships between two values, as shown in Table 22-5:

Table 22-5. Relational Operators

Relational Operator

Meaning

Example

<

Less than

j < 19

>

Greater than

counter > 10

==

Equal to

flag == DONE

!=

Not equal to

dog != cat

<=

Less than or equal to

temp <= freezing

>=

Greater than or equal to

air_miles >= premier

Expressions using these operators will evaluate to either TRUE or FALSE. Relational operators have a lower precedence than arithmetic operators.

One word of caution: do not confuse the relational operator "is equal to" (==) with the assignment operator (=). The expression

count == 2; 

tests whether the value of count is equal to 2 and evaluates to either TRUE or FALSE. The expression

count = 2; 

assigns the value 2 to the variable count.

The choice of relational operator for a loop will depend upon the particular test to be made. Do I want to stop when a variable equals a value? Or do I want to stop only when it is greater than? By asking these types of questions, you can figure out which operator makes the most sense.

The looping condition is tested immediately after the initialize expression. If the looping condition evaluates to FALSE, the loop will perform the statements in the body of the loop. If the looping condition evaluates to TRUE, the loop will stop, leave the loop, and go to the next statement in the program after the body of the loop.

The third component, update expression, is performed after the statements in the body of the loop are executed. It allows the controlling variables to be updated. After the update expression is performed, the looping condition is re-tested to determine whether the loop should continue.

The following programming fragment illustrates the for loop:

int x;      /* control variable used in the for loop */  for (x = 0; x < 3; x++ )/* loop exactly 3 times */  {        printf("%d ", x);/* print the control variable, x */  }  printf("\nThe loop is complete.\n"); 

In the example above, x is declared as an integer and used in the for loop. The following is a step-by-step list, demonstrating how the loop is executed:

  1. The first time the loop is reached, the initialize expression assigns 0 to x.

  2. Next, the looping condition asks whether x is less than 3. Since this is a TRUE statement,

  3. The printf() function prints 0 to the screen.

  4. The update expression is evaluated and x is incremented by one. The value of x is now 1.

  5. The looping condition asks whether x is less than 3. Because this is a TRUE statement,

  6. The printf() function prints 1 to the screen.

  7. The update expression is evaluated and x is incremented by one. The value of x is now 2.

  8. The looping condition asks whether x is less than 3. Since this is a TRUE statement,

  9. The printf() function prints 2 to the screen.

  10. The update expression is evaluated and x is incremented by one. The value of x is now 3.

  11. The looping condition asks whether x is less than 3. Since this is a FALSE statement, all looping is discontinued and

  12. The printf() function immediately below the body of the loop prints "The loop is complete." to the screen.

While Loops

The while loop allows you to repeat execution of the body of the while loop until some predetermined condition occurs. This condition is known as indefinite looping. The while loop is not looping a predetermined number of times, but instead will continue looping until a predetermined condition occurs.

The syntax of a while loop is:

while ( loop condition )          statement; 

If the loop has more than one statement to execute, the body of the loop is surrounded by curly braces ({ }):

while ( loop condition )  {              statement_1;               statement_2;  } 

The loop condition or expression in the parentheses is evaluated first. If the results of the expression are TRUE, then the body of the while loop is executed. After the execution of the body of the loop, the expression is evaluated again. If the expression is still TRUE, the body of the loop will be executed again. If the expression is FALSE, the looping will stop.

As an example of a while loop's use, the following fragment of source code will count up to the number 5:

int count = 1;  while ( count <= 5 )     /* loop while count is <= to 5 */  {              printf ("%d ", count );     /* print count */               count++;                /* increment count */  } 

The output is:

1 2 3 4 5 

The while loop stops when the expression evaluates to FALSE. We are not telling it how many times to loop; we are only saying "loop until count is greater than 5, and then stop." To illustrate this point, let's use the same while loop, but initialize count to equal 3:

int count = 3;  while ( count <= 5 )     /* loop while count is <= to 5 */  {              printf ("%d ", count );     /* print count */               count++;                /* increment count */  } 

The output is:

3 4 5 

This is the same while loop, with different results because we started with another number.

Do While Loops

Both loops discussed previously test the loop condition before executing the loop. A do while loop will execute the loop first and then test the loop condition. The syntax for a do while loop is as follows:

do  {              statement_1;               statement_2;  }  while ( loop condition ); 

The execution is in the following order: The body of the loop is executed first, and then the loop condition is tested. If the loop condition is TRUE, the body of the loop is executed again; if it is FALSE, the loop will stop.

Break Statement

At times you may want to leave a loop if a certain condition occurs in the body of the loop. A break statement is used for this purpose. Execution of a break statement will cause the loop to immediately stop execution and leave the loop body. The break statement is as follows:

break; 

Continue Statement

A continue statement is similar to the break statement, except that it doesn't cause the loop to terminate. Any statements after the continue statement in the body of the loop will not be executed; they are actually skipped. The execution of the loop otherwise continues as normal. The continue statement is as follows:

continue; 

Logical Operators

Logical operators can be used to create compound relational tests for complex looping conditions. There are three logical operators in C, as shown in Table 22-6.

Table 22-6. Logical Operators

Operator

Meaning

!

NOT

&&

AND

||

OR

The logical operators will return TRUE or FALSE. NOT is a unary operator that returns FALSE if its operand is TRUE and TRUE if its operand is FALSE. For instance, if an integer variable z has a value of zero (representative of FALSE), then !z is TRUE. If z has any other value (representative of TRUE), then !z is FALSE.

AND and OR logical operators are binary operators, using two operands. AND returns TRUE if both operands are TRUE, and returns FALSE otherwise. OR returns TRUE if either operand is TRUE; otherwise, FALSE is returned.

For example, the following compound relational expression is using logical AND. The expression returns TRUE because both operands are TRUE.

Since checking = 100 and savings = 100, the following statement is true:

checking > 50 && savings < 200 

Because both sides of the equation are TRUE, the AND evaluates to TRUE. If one or both operands were FALSE, the AND operator would evaluate to FALSE.

For an OR operator, only one operand needs to be TRUE. For example, the following expression will evaluate to TRUE:

Because checking = 100 and savings = 100, the following statement is false:

checking > 500 || savings < 200 

Only one operand needs to be TRUE for the whole expression to be TRUE. An expression using the OR operator will also evaluate to TRUE if both operands are TRUE. If both operands are FALSE, the OR operator will return FALSE.

Expressions with logical operators can be used in a loop for the looping condition.

Nested Loops

Loops can be nested, with one loop in the body of another. A while loop can be inside a for loop and vice versa. The outer loop is executed once, the inner loop is executed until completion, control is passed back to the outer loop, and the process is continued until the outer looping condition is FALSE. Below is a fragment of C source code and its output, illustrating nested loop behaviors:

int outer, inner;  for ( outer = 0; outer <= 4; outer++ )  /*outer loop prints the letter 'A' and a new line */  {              printf( "%c\n", 'A' );               for ( inner = 0; inner < 4; inner++ )                        /* inner loop prints the letter 'b' */               {              printf( " %c ", 'b' );               }               printf("\n"); /* new line for end of b's line */  } 

The output for this code fragment is:

A  b   b   b  A  b   b   b  A  b   b   b  A  b   b   b 

Notice that the outer loop is executed for a single loop, and then the inner loop is executed to completion. The outer loop is executed once again for a single loop, and the inner loop is restarted and executed to completion. The nested loops are complete after the looping condition for the outermost loop is FALSE.

Choice

The capability to make decisions is another fundamental property needed to program. The control statements introduced in this section allow the flow of a program to be modified via decisions.

The if Statement

One of the programming constructs that provides general decision-making capability is the if statement. The format of an if statement is as follows:

if ( expression )               statement; 

If there is more than one statement to perform, curly braces ({ }) can be used to denote the body of the if statement.

if ( expression )  {              statement_1;               statement_2;  } 

An if statement can be translated into a statement such as "If the car is $100, then I will buy it." This could be written as:

if (car == 100 )               printf( "I'll buy it!\n" ); 

The printf() function is executed only if the expression evaluates to be TRUE. If it evaluates to FALSE then there is no action taken.

The if...else Statement

An if...else statement allows an action to be taken if the expression evaluates to TRUE, and allows another action to be taken if the expression evaluates to FALSE. The syntax for this construct is:

if ( expression )               statement;  else               statement; 

If multiple statements need to be executed, the curly braces ({ }) can be used:

if ( expression )  {              statement_1;               statement_2;  }  else  {              statement_1;               statement_2;  } 

An example of an if...else statement is as follows:

I want to buy a car if it is $100 or less. If the car is more than $100, I want to negotiate with the seller. This can be expressed as:

if ( car <= 100 )               printf( "I'll take it!\n");  else               printf( "Let's talk about the price.\n"); 

Nested if and if...else and else if Statements

Nested if and if...else statements can be constructed by placing one if or if...else statement inside another. Be sure to manage the indenting of statements correctly. If the indenting of an if does not match appropriately with its corresponding else, it can become very confusing. The following is a properly constructed example:

if ( expression_1 )  {              if (expression_2 )                    statement_1;  }  else  {            if ( expression_3 )                    statement_2;  } 

Statement_1 is only reached if expression_1 in the outer if...else statement and its imbedded if statement's expression_2 evaluates to TRUE.

Statement_2 is reached if the outer if statement's expression_1 evaluates to FALSE and the outer else's imbedded if statement's expression_3 evaluates to TRUE.

The else if statement can help simplify the nested structure above:

if ( expression_1 )  {              if (expression_2 )                    statement_1; }  else if (expression_3 )               statement_2; 

The else and its nested if statement are combined on one line.

Logical Operators

If statements used thus far set up simple relational tests between two values. Logical operators can be used to create compound relational tests, as described under "Logical Operators" in the "Loops" section earlier in this chapter. An example of this is:

if ( car <= 100 && car_year  > 1992)               printf ("I'll take it!\n"); 

The car is purchased when both of the expressions are TRUE: the car is $100 or less and the car was made after 1992.

The switch Statement

The switch statement is an alternative to the if...else statements for creating multiple branches. Its general format is:

switch ( expression )  {              case value_1:                    statement;                    statement;                    ...                    break;               case value_2:                    statement;                    statement;                    ...                    break;               case value_n:                    statement;                    statement;                    ...                    break;               default:                    statement;                    statement;                    ...                    break;  } 

The expression enclosed within parentheses is compared against value_1, value_2, through value_n, which must be constants or constant expressions. If a value is found to match the value of the expression, the statements associated with it are executed. If no matches are found, control flows to default and its statements are executed.

If I am willing to pay for a car based upon the year it was made, the following switch statement can be used:

switch ( car_year )  {                case 1999:                      printf("I'll offer you $8,000 for the car.\n");                      break;                 case 1998:                      printf("I'll offer you $6,000 for the car.\n");                      break;                 case 1997:                      printf("I'll offer you $3,500 for the car.\n");                      break;                 case 1996:                      printf("I'll offer you $2,100 for the car.\n");                      break;                 case 1995:                      printf("I'll offer you $1,000 for the car.\n");                      break;                 case 1994:                      printf("I'll offer you $800 for the car.\n");                      break;                 case 1993:                      printf("I'll offer you $400 for the car.\n");                      break;                 default:                      printf("No thank you, the car is too old.\n");                      break;  } 

When a match is found, the statements associated with the match are executed. If the year of the car is earlier than 1993, no match is found and the switch statement executes the default case.

Functions

Functions are the basic building blocks of C programs. We have already encountered some functions, such as the printf() for printing to a screen and scanf() to read data into the program from the keyboard. And we have seen that every C program must begin with a function called main().

When do you use a function? If the operation has already been created as a function and tested, use it. There is no sense in recreating it.

When do you create a function? In a few cases. First, if an operation is used in two or more places within a program, it is a candidate for becoming a function. Second, a function should be created if an operation is used between two or more programs. Third, when an operation yields a single result, it can become a function. Fourth, an operation should be a function if it is a separate or unique task, even if it is used only once. Fifth, if a program is too long, it should be broken up into functions for readability.

When functions are used in a program, they are first defined and then called. A function is defined by writing it as a separate piece of source code, and it is called by referencing the name within other functions.

Many useful functions are already part of a standard library that comes with the C compiler. You want to become familiar with them. They will make programming easier.

Function Call

The syntax to call a function is:

function_name ( arg_1, arg_2, ...) 

A function (called function) is called from within another function (calling function) when the operation contained in the called function is needed as part of the operation of the calling function.

To call a function, the function's name is used as part of the format of the call. The function name uses the same rules as for naming a variable. A function name begins with a letter and contains only letters, digits, or underscores. The parentheses following the function name contain arguments. Arguments are references to values, and these values are passed into the function for use. Arguments are separated by commas, and can be constants, variables, or expressions. If a function does not need any values passed into it, the arguments can be omitted.

A function calls another function by referring to the function's name and including the necessary arguments between the parentheses. There are two things accomplished by a function call. First, the function is executed. Second, a value is returned to the calling function.

Defining a Function

The syntax for defining a function is:

return_type function_name( parameter list)  {              statement(s);  } 

A function has four elements:

  1. Return type: Because a function can return a value, the data type for that value must be specified. If no value is to be returned by the function, the data type void is used. The data type precedes the function name with a type declaration. For example:

    int price_service (int service_type, float cost_per_service )  {        /* Statements to calculate the price of a service */  } 
  2. Function name: The maximum length of a function name in C is 31 characters. The function name can be longer, but most compilers will only recognize the first 31 characters.

    int price_service(int service_type, float cost_per_service )  {          /* Statements to calculate the price of a service */  } 
  3. Parameter list: The parameter is optional, but the parentheses are required. The term's parameter and argument refer to the same value in different places. An argument is a value in the function call statement that equates to a parameter in the function definition. Parameters are variables that are separated by commas inside the parentheses of the function definition. A parameter is a combination of a data type and the name of a variable. Any arguments sent to the function via a function call statement must match in number, data type, and order with the parameters in the function definition.

    int price_service(int service_type, float cost_per_service )  {          /* Statements to calculate the price of a service */  } 
  4. Function body: The body of a function is enclosed with curly braces ({ }). Statements to perform the operation required of the function are contained here.

    int price_service(int service_type, float cost_per_service )  {        /* Statements to calculate the price of a service */  } 

The relationship between a function call and a function definition is fairly straightforward.

When defining a function, you decide what to call the function (function name), the operations it will perform (body), the value to be returned from the function (return value), and what type of information needs to be sent into the function (parameter list) to perform the operation.

The body of a function definition contains the source code for that function. The parameters in the parameter list describe what data type the arguments must be, in what order, and how many. By making a call to a function, you are passing the arguments that correspond to the function definition's parameters and executing the code found in the body of the function definition. These concepts are shown in Figure 22-5.

Figure 22-5. Anatomy of a Function

graphics/22fig05.gif

Prototypes

When calling a function, the compiler associates parameters in the function definition with arguments when calling a function. The compiler will assign the first argument to the first parameter, the second argument to the second parameter, and so on. The number of arguments must match the number of parameters. If the arguments are not matched appropriately with the parameters, errors can occur.

Prototypes are used to allow compilers to check argument types against parameters. A function prototype is basically a restatement of the first line of the function definition, ended with a semicolon (;). The prototype for the function price_service() in the above example would be:

int price_service ( int service_type, float cost); 

The variable names are not necessary in the prototype and can be excluded:

int price_service ( int, float ); 

A prototype provides information about a function to the compiler so that the compiler can verify the correctness of data types in function calls.

Arrays

Arrays allow programmers to define a large amount of memory at one time. If a program is dealing with a calendar or calculating bowling scores, there is a fair amount of similar data needed. By declaring an array, a programmer is setting up a series of like data types.

When individual pieces of data are declared in a program, the computer assigns memory to it. Where the data are stored does not follow a pattern; the information could be anywhere, as shown in Figure 22-6.

Figure 22-6. Information Anywhere

graphics/22fig06.gif

When an array is used, the information is stored contiguously, or one next to the other, as shown in Figure 22-7.

Figure 22-7. Array of Information

graphics/22fig07.gif

In the example above, there are five elements in the grades array, all of type integer.

Arrays allow the declaration of large amounts of data with the same data type in one statement. This arrangement is much easier than typing in a separate statement for each data type and variable. Accessing and using the information are simplified and less confusing. Arrays also help enhance readability of source code.

The syntax for declaring arrays is as follows:

data_type array_name[array_size]; 

Only integers are used to specify the size of the array. Constants, symbolic constants, or constant expressions can all be used when specifying the size, but they must all result in an integer value.

Each member of an array is called an element. Each elementisa variableofthe same data typeasthe array.

Elements within an array can be referenced using an integer index. Arrays in C always start with an index of zero. This arrangement is referred to as zero-based indexing. Figure 22-8 shows an array with index numbers.

Figure 22-8. Arrays

graphics/22fig08.gif

To reference an element in the array, the index number is used. To refer to a specific element, use the array name followed by the index number between square brackets ([ ]). The index number is referred to as a subscript when accessing information in an array. The code fragment below is declaring an array called grades and placing a value into the first and third elements of the array:

int grades[ 5 ];  . . .  grades[ 0 ] = 100;  grades[ 2 ] = 87; 

Loops are very useful when dealing with arrays. If a program has populated the array called grades with five student test scores, the sum of the grades in the array can be found with the following for loop:

for( x = 0; x < 5; x ++ )          /* loop five times */                sum += grades[x];    /* short hand for sum =                                          sum + grades[x] */ 

The for loop traverses the array, starting with the first element, grades[0]. The loop occurs five times, once for each element in the array. The x variable is used as a subscript to reference the elements in the array.

One of the more common errors in C is when the index is out of range. If x <= 5 were used in the conditional expression of the loop, it would have looped a sixth time and tried to access grades[5], which does not exist. This error would not show up at compile time; it would be seen only during the execution of the program. You may get unpredictable results, or it could result in a segmentation violation and core dump.

An array can be initialized with values at the time it is declared by using:

int grades[5] =  {100, 98, 97, 87, 99}; 

The compiler will map the values into the array. You can initialize using less values than the number of elements in the array, but never more.

Multidimensional Arrays

The arrays discussed thus far have been linear arrays, dealing with a single dimension. Arrays can be multidimensional. One of the more common uses for multidimensional arrays is as a two-dimensional array for matrixes or tables as shown in Figure 22-9.

Figure 22-9. Multidimensional Array

graphics/22fig09.gif

A 3 by 5 matrix can be declared as

int array_m[3][5]; 

If you think of this in terms of rows and columns, the first square bracket represents the number of rows, and the second represents the number of columns.

To access elements, both indexes must be used.

The reference array_m[0][4] is referencing the first row, last column, or the element in the upper right corner, 9.

Passing Arrays to Functions

It is possible to pass the value of an element or an entire array as an argument to a function.

To pass in a single element, a function call and function definition would appear as follows:

#include <stdio.h>  void print_grade(int);               /* function prototype */  main()  {                int grades[5] = {100, 98, 97, 87, 99};                                               /* declare array */                 . . .                 print_grade( grades[0] );                 /* send the first element of the array grades into                                     the function as an argument */                 . . .                 return;  }  . . .  void print_grade( int a_grade)  {                printf("The grade is %d.\n", a_grade);  } 

The output of the program would be:

The grade is 100. 

To access a whole array inside a function, it is only necessary to list the array name without any square brackets ([ ]) or subscripts. To find the lowest grade, the whole array is passed into the function minimum_grade():

lowest = minimum_grade(grades); 

The function definition looks like:

int minimum_grade(int all_grades[5])  {              /* statements */               . . .               return ( minimum );  } 

The function definition does not need the array size included. The array in the function definition can also be declared as int all_grades[].

Arrays are useful structures; this section covered a beginner's view.

Strings

As previously discussed in the "Data Types" section of this chapter, strings allow us to "string" together characters into words or sentences. A string is not one of the basic data types, but is a rather special type of character array.

A string and a character array are not the same. A string is a character array containing a sequence of character values that end with a null value ('\0'). A character array does not have to contain a string; it can be an array of character values, none of which are null. The following is an array of characters:

char array_of_chars[5] = { 'h', 'e', 'l', 'l', 'o' }; 

The following is an array containing a string:

char string_array[6] = "hello"; 

Strings can be string constants and string arrays. A string constant is a sequence of characters enclosed by double quotes (" "). The null character at the end is implied in a string constant. A string array is a character array that contains a string.

A character array containing four letters can be declared as char letters[3];. A string containing a four-letter word can be declared as char words[4];. The character array and the string both contain four letters, but the string needs one more spot in the character array for the null value.

A string can be declared and initialized in one statement with a string constant as follows:

char student_name[10] = "Carly"; 

The string does not have to take up the whole character array, but it must be long enough for each character in the string plus the null.

Initializing a string with a string constant using the equals assignment operator is allowable only when declaring the character array:

char student_name = "Carly" 

Initializing a string or changing its value in the body of a program using this method is not allowable:

NOT ALLOWED

student_name = "Carly"; 

To do this, C provides a library function called strcpy(). A standard set of string functions is provided with the compiler. To correctly copy a string, use the following:

ALLOWED

strcpy( student_name, "Carly"); 

The prototype for this function can be found in the string.h header file. When using any string library function, be sure to include the following processor directive at the top of your source file:

#include <string.h> 

There are many more string functions not addressed in this section.

Structures

Arrays allow information of the same data type to be grouped together and the contents referenced using subscripts.

C also provides a tool called for grouping information together called structures. Structures provide a way to group information of different data types together so that they can be handled as a single entity.

What are the reasons for using a structure? They are several. Structures allow a programmer to organize logically related data items under a common name in a common memory structure. For example, grouping together personal information regarding an employee makes sense. An employee has a name, address, salary, benefits, job title, and so on. Each piece of data is logically related to an employee, but is made up of different data types.

Structures also minimize the number of arguments passed into a function. If a function wanted to print all the information it has on an employee, each piece of data would require an argument; name, address, salary, and so on. When all this information is grouped under a structure, only the structure is passed into a function.

Structures are great tools for enhancing readability. If a program grouped all its employee information into one data structure, it would be easy to find.

A structure is declared using the following syntax:

struct tag              {             member_list;              } name_list; 

The keyword struct tells the compiler that this is a structure declaration, with its member list enclosed in curly braces ({ }).

The member_list contains information of other data types that are part of the structure, and can also include other structures.

The tag is an identifier or name for the structure's template. A structure's template describes the layout or design of the structure; the tag is used to identify thattemplate.

The name_list is a list of identifiers or names for the structure's variables. In other words, the variable names used to reference this type of structure are listed in the name_list. The variable name of a structure represents the computer memory allocated according to the template design.

Structures are declared in two steps. First, the layout or template of the structure is declared; second, the variable is declared and memory is allocated. These steps can be accomplished in two steps or in the same statement. The example shows declaration of a structure in two steps:

struct employee_jobs  {              char job_title[30];               float monthly_salary;               int job_level;  }; 

This contains all the elements we want to have associated with the structure. This declaration does not include the variable name; this is done by using the tag employee_jobs in the following format:

struct employee_jobs manager; 

The variable manager is of type struct employee_jobs. Other types of employee_jobs can be declared:

struct employee_jobs analyst;  struct employee_jobs ceo; 

Referencing an element in a structure is slightly different. Using the variable name of the structure followed by a period, and then the member name accesses a member of a structure. To set the CEO's salary, use the following statement:

ceo.monthly_salary = 60000.00; 

A structure can also be passed in its entirety to a function with one argument. Consider the example in Figure 22-10.

Figure 22-10. Passing a Structure into a Function

graphics/22fig10.gif

Notice that the time_now structure passed into the function time_in_minutes is declared as variable t in the function declaration and used as t within the function body. Also, notice that the information was only read from the time structure; the contents of the time structure was not altered. If the function time_in_minutes() did attempt to alter the time structure's contents, it would not be seen in the main() function. This is because a copy of the structure was passed to the function. If any changes were made to the structure in the time_in_minutes() function, they would only be made to the copy, and the copy is destroyed after we return to the main() function.

To pass the actual structure so the contents can be altered within a function, a pointer must be used. Pointers are discussed in the next section.

Structures can be a detailed topic; this section has provided a brief overview. For more examples and information regarding structures, refer to the books recommended at the end of this chapter.

Pointers

The last topic covered in this section is pointers. Pointers are a fairly sophisticated topic and one of the most powerful in C. Pointers can be used instead of indexes to access array elements or allow arguments to be altered within a function. Pointers also allow C to work with dynamically allocated memory. This means that the size of the variable is not included in the declaration. It is dealt with in the body of the function.

A pointer is a spot in memory containing an address to something else, or pointing to another spot in memory. Pointer contents are similar to integer data previously used, except that the data can be used to access other data.

Pointers can take the form of a constant or a variable in computer memory. Pointer variables can be defined and initialized. The values in pointer variables can be changed. The values in pointer constants, like other constants we've seen, cannot be changed.

A pointer variable can be defined using the following syntax:

data_type *pointer_name; 

The data_type is the same as the data types seen previously. The data type indicates what type of information to which the pointer will be pointing. If a pointer is to point to a float, the pointer should be declared as float *salary_ptr;. Figure 22-11 depicts this concept.

Figure 22-11. Pointer

graphics/22fig11.gif

A variable declared as a pointer contains the address of the variable to which it is "pointing." The pointer salary_ptr must be declared as type float since it is pointing to the variable salary of type float.

Pointer Operators

There are two operators often used with pointers. The address operator (&) allows the addresses to be obtained. The indirection operator (*) allows you to access values at address locations. These operators perform opposite operations. If placed next to each other, they cancel each other out. When assigning an address to a pointer, use the address operator (&). When accessing the data using a pointer, use the indirection operator (*).

Figure 22-12 illustrates the use of pointers.

Figure 22-12. Pointer Operations in Program

graphics/22fig12.gif

The printf() statement produces the following output:

a_number = 10, num_from_ptr = 10 

If we were to print the value in num_ptr, the output would be the address of the a_number variable.

Figure 22-13 is a graphical representation of the program.

Figure 22-13. Graphical Representation of Program's Pointer Operations

graphics/22fig13.gif

Pointers and Structures

Pointers can point to structures as well. Consider the following structure declaration:

struct time  {             int hour;              int minute;  }; 

Previously, we defined variables as type struct time:

struct time time_now; 

We can also define a variable to be a pointer to a struct time variable:

struct time *time_pointer; 

Using the following statement, we can assign the address of the structure variable time_now to the pointer variable time_pointer:

time_pointer = &time_now; 

After the assignment is made, we can indirectly access the members of the time structure via the time_pointer:

(*time_pointer).hour = 16; 

Structures can also contain pointers, as in the example:

struct time  {             int hour;              int minute;              int *seconds_ptr;  } 

The structure time can have a pointer, called seconds_ptr, point to an integer containing a number of seconds.

Pointers and Functions

Pointers can also be passed as arguments to a function. When this occurs, we are not passing a copy of the variable, as previously shown, but we are passing a pointer to the actual variable. If any changes are made to the variable contents within the function, this time they will be retained.

Arguments in function calls and parameters in function definitions and prototypes need to be defined to accept pointers.

A prototype defined to accept two integer pointers is as follows:

int time_in_minutes( int *hour_ptr, int *minute_ptr ); 

The function definition would look like:

int time_in_minutes( int *hour_ptr, int *minute_ptr )  {              /* statements */  } 

The fragment of code below illustrates a call to a function that requires integer pointers. In this example, the variables are defined as integers. To use the function, the variable addresses need to be sent into the function using the address operator (&):

int hour, minute, total_min;  hour = 10;  minute = 33;  total_min = time_in_minutes( &hour, &minute ); 

In the example below, additional variables are declared as integer pointers. The integer variables are populated with values, and their addresses are assigned to the integer pointer variables. Because the integer pointer variables already contain addresses, they can be used without any of the pointer operators:

int hour, minute, total_min;  int *hour_ptr, *minute_ptr;  hour = 10;  minute = 33;  hour_ptr = &hour;  minute_ptr = &minute;  total_min = time_in_minutes( hour_ptr, minute_ptr ); 

Pointer variables can also be declared as pointers to structures. Either the address to the structure can be sent into a function using the address operator (&), or a structure pointer variable can be used.

Referencing the member variables of a pointer to a structure is different from referencing member values directly from a structure.

Referencing member variables directly from a structure is done with a period (.) between the variable name of the structure and the member variable name. We have already seen examples of this, similar to the code fragment below:

struct time  {             int hour;              int minute;  };  main()  {             . . .              struct time mytime;              . . .              mytime.hour = 9;              mytime.minute = 45;              . . .  } 

When referencing member values from a pointer to a structure, instead of a period, use the pointer symbol (->) between the variable name of the pointer to the structure and the member variable name. This symbol is a combination of two characters, the dash (-) and the greater-than sign (>).

Figure 22-14 contains examples of referencing member variables from both a structure and a pointer to a structure. The function time_in_minutes() has been modified to accept a pointer to the structure time.

Figure 22-14. Referencing Member Variables

graphics/22fig14.gif

In the main() function, the pointer to the time structure, time_ptr is declared. Notice that when referencing the member variables of time_ptr, the period (.) is no longer used, but the -> symbol is.

Because the function time_to_minutes() has been modified to accept a structure pointer, the pointer symbol (->) is now used within the function to access the member variables. Also, the function call has changed when using the variable of a structure. Because the function now accepts a pointer to the structure, the structure variable must send its address using the address operator (&). When sending the pointer to the structure, no pointer operator is needed. It is also worth noting that any changes to the contents of the member variables within a function defined in this manner will be retained after leaving the function.

More Data Types

Each variable that is declared in a C program has a data type and a storage class. The data type determines how much storage a variable will use, what kind of data it can hold, where the variable can be used, when it exists, and how it is initialized.

C language allows a programmer to assign a name to a data type with the typedef data type.

Storage Classes

There are three properties determined by the storage class of a variable. The compiler may or may not initialize a variable with a starting value when it allocates space for it; this action is referred to as initialization. The lifetime of a variable is when a program creates and destroys a variable during execution. A variable may be created within a function and destroyed after the function is complete. The third property, scope, determines through which programming area a variable is accessible. A variable may be globally accessible, which means that it was declared outside a function and is accessible by all the functions, or a variable may be local, accessible only from within a function.

Automatic Storage Class

Variables in the automatic storage class are local to the function in which they are defined. This means the lifetime and scope of the variable are limited to the function. In other words, a variable in the automatic storage class comes into existence when the function is executed; it is only used within the function, and is automatically destroyed when leaving a function. The format is:

auto data_type variable_name; 

Static Storage Class

Variables of the static storage class are sometimes referred to as global, because their scope and lifetime are not restricted to a function. The existence of a static variable is the lifetime of the program. The scope of the variable depends upon how it is defined. The format is:

static data_type variable_name; 

There are three ways to declare a static variable, starting with a narrow scope and moving to wider scopes:

  1. A static variable declared inside a function restricts the static variable to that function. A non-static variable declared in a function is destroyed after the function execution is complete. A static variable will not be destroyed after the function execution is complete. If a value is assigned to the variable, the value will still be there the next time the function is executed.

  2. A static variable declared outside all functions in a file has a scope that includes all functions in that source file. Using static variables prohibits functions in other source files from accessing the variable.

  3. A variable can be accessible from other source files if it is declared outside all functions, static is not used, and it is accessed from functions in other source files using the qualifier extern. The variable needs a declaration in each of the source files. One will have the variable declaration, and all others will have the declaration with the extern qualifier.

Typedef

Typedef allows a programmer to assign their names to data types. The convention is uppercase letters are used for names so that they are easily distinguishable. For example, some programs may use bytes in their logic. A programmer may want to declare every variable used as a byte as an unsigned char. It would be easier to use the typedef keyword and assign the word BYTE to the data type unsigned char with the following statement:

typedef unsigned char BYTE; 

Variables can be declared as:

BYTE intial_byte;  BYTE new_byte; 

Essentially, any variable declared as type BYTE is really declared as unsigned char. This will help with organization and program readability.

Enumerations

Enumerations provide the ability to create a variable that has a limited set of possible values that can be referred to by name. When defining an enumeration, the result will be a list of named integer constants. These constants can be used anywhere an integer is used. The format for declaring an enumeration is as follows:

enum enum_type_name { enumeration list } variable_list; 

For example, a variable can be created that will only accept the days of the week. This is defined as follows:

enum Week { Sunday, Monday, Tuesday, Wednesday, Thursday, Fri day, Saturday }; 

The enumerated data type called Week and variables of this type can only have the values listed between the curly braces ({ }). If the variable Week is set to any other value, an error is generated.

Each day of the week is automatically defined as representing a fixed integer value. For example, Sunday will have a value of 0, Monday has a value of 1, and so on.

The variable yesterday can be declared as an instance of the enumeration type Week with an initial value of Thursday, using the following statement:

enum Week yesterday = Thursday; 

Dynamic Memory Allocation

There are times when a programmer does not know how much memory a program will need for its data. For example, a program accepts input from a keyboard, perhaps a sentence. A program can allocate a large piece of memory to put the sentence in, but the programmer really doesn't know how long it will be. If the programmer allocates a large amount of memory and the sentence is short, memory is wasted. If the programmer makes an assumption that the sentence will only be 100 words, there is always the possibility of a 101-word sentence being entered. A solution is to allocate memory while a program is executing.

A standard library function, malloc(), allows a programmer to allocate only the amount of memory needed and only when it is needed. When the memory is no longer needed, it can be released or freed with a call to another standard library function called free(). Dynamic memory allocation allows a programmer to control the lifetime of the memory usage.

Pointers play an essential role in dynamic memory allocation. Pointers contain the address of the memory allocated. The pointer is defined in advance, but the memory that the pointer will point to is made available while the program is running.

When memory is needed, a programmer can use malloc(), specifying the size of memory needed, and the computer will populate the pointer with the address of the assigned memory. A call can be made to malloc() as follows:

char * ptr;  ptr = ( char * )malloc( size ); 

Malloc() locates a block of contiguous memory of the requested size and returns a void pointer to the first byte in the block. The results of malloc() should be cast to match the type of pointer on the left side of the assignment operator. For example, a call to create a block to be used for integers would be:

int * ptr;  ptr = (int * )malloc(size ); 

The malloc() function will return a NULL pointer if there was a failure. NULL is a pointer with a value of zero that points to type void. The return value of malloc() needs to be tested for a NULL value to determine whether it was successful.

if ( ptr == NULL)  { printf("Memory allocation error.\n");  return;  } 

After the memory block is no longer needed, release it back to the operating system by calling the free() function as follows:

free( ptr ); 

The memory will now be available for reuse. After the program ends, all memory is automatically freed. It is good programming practice to free memory within the program.

_________________________________

The C programming section was intended to give you a quick overview of the language and a basis for understanding C++. The C language section should be supplemented with a C programming book to provide more detail and comprehensive coverage of the topics we've just touched upon.

CONTENTS


UNIX User's Handbook
UNIX Users Handbook (2nd Edition)
ISBN: 0130654191
EAN: 2147483647
Year: 2001
Pages: 34

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net