Chapter – 5
Introduction of C Programming
Introduction
‘C’ is one of the most popular computer languages in today’s computer world. The 'C' programming language was designed and developed by Brian Kernighan and Dennis Ritchie at The Bell Research Labs in 1972.
'C' is a Language specifically created in order to allow the programmer access to almost all of the machine's internals - registers, I/O slots and absolute addresses. At the same time, 'C' allows for as much data handling and programmed text modularization as is needed to allow very complex multi-programmer projects to be constructed in an organized and timely fashion.
Although this language was originally intended to run under UNIX, there has been a great interest in running it under the MS-DOS operating system on the IBM PC and compatibles. It is an excellent language for this environment because of the simplicity of expression, the compactness of the code, and the wide range of applicability.
Also, due to the simplicity and ease of writing a C compiler, it is usually the first high level language available on any new computer, including microcomputers, minicomputers, and mainframes.
Why use C in Data Recovery Programming
In today's world of computer programming, there are many high-level languages are available. These languages are good with many features suited for most programming tasks. Yet, there are several reasons that C is the first choice of the programmers who are willing to do programming for data recovery, system programming, device programming or hardware programming:
- C is a popular language preferred by professional programmers. As a result, a wide variety of C compilers and helpful accessories are available.
- C is a portable language. A C program written for one computer system can be compiled and run on another system with little or no modification. Portability is enhanced by the ANSI standard for C, the set of rules for C compilers.
- C allows a wide use of modules in programming. C code can be written in routines called functions. These functions can be reused in other applications or programs. You need not to do extra efforts in the programming of new application to create the same module that you developed in another application programming before.
You can use this function in new program without any change or some minor changes. In case of data recovery programming you will find this quality very much helping when you need the run the same functions several times in different applications of different program.
- C is a powerful and flexible language. This is the reason why C is used for projects as diverse as operating systems, word processors, graphics, spreadsheets, and even compilers for other languages.
- C is a language of few words, containing only a handful of terms, called keywords, which serve as the base on which the language's functionality is built. These keywords, also called reserved words, make it more powerful and give the wide area of programming and make a programmer feel to do any type of programming in C.
Let me assume that you know nothing in C
I assume that you know nothing about C programming and also not have any idea of programming. I will begin with the most basic concepts of C and take you up to the high level of C programming including the usually intimidating concepts of pointers, structures, and dynamic allocation.
To fully understand these concepts, it will take a good bit of time and work on your part because they are not particularly easy to grasp, but they are very powerful tools.
Programming in C is a tremendous asset in those areas where you may need to use Assembly Language but would rather keep it a simple to write and easy to maintain program. The time saved in coding of C can be tremendous in such cases.
Even though the C language enjoys a good record when programs are transported from one implementation to another, there are differences in compilers that you will find anytime you try to use another compiler.
Most of the differences become apparent when you use nonstandard extensions such as calls to the DOS BIOS when using MS-DOS, but even these differences can be minimized by careful choice of programming constructs.
When it became evident that the C programming language was becoming a very popular language available on a wide range of computers, a group of concerned individuals met to propose a standard set of rules for the use of the C programming language.
The group represented all sectors of the software industry and after many meetings, and many preliminary drafts, they finally wrote an acceptable standard for the C language. It has been accepted by the American National Standards Institute (ANSI), and by the International Standards Organization (ISO).
It is not forced upon any group or user, but since it is so widely accepted, it would be economic suicide for any compiler writer to refuse to conform to the standard.
The programs written in this book are primarily for use on an IBM-PC or compatible computer but can be used with any ANSI standard compiler since it conforms so closely to the ANSI standard.
Let us start
Before you can do anything in any language and start programming, you must know how to name an identifier. An identifier is used for any variable, function, data definition, etc. In the C programming language, an identifier is a combination of alphanumeric characters, the first being a letter of the alphabet or an underline, and the remaining being any letter of the alphabet, any numeric digit, or the underline.
Two rules must be kept in mind when naming identifiers.
- The case of alphabetic characters is significant. C is a case sensitive language. That means Recovery is different from recovery and rEcOveRY is different from both mentioned before.
- According to the ANSI-C standard, at least 31 significant characters can be used and will be considered significant by a conforming ANSI-C compiler. If more than 31 are used, all characters beyond the 31st may be ignored by any given compiler.
Keywords
There are 32 words defined as keywords in C. These have predefined uses and cannot be used for any other purpose in a C program. They are used by the compiler as an aid to compiling the program. They are always written in lower case. A complete list follows:
auto |
break |
case |
char |
const |
continue |
default |
do |
double |
else |
enum |
extern |
float |
for |
goto |
if |
int |
long |
register |
return |
short |
signed |
sizeof |
static |
struct |
switch |
typedef |
union |
unsigned |
void |
volatile |
while |
Here we see the magic of C. The wonderful collection of only 32 keywords gives a wide use in different applications. Any computer program has two entities to consider, the data, and the program. They are highly dependent on one another and careful planning of both lead to a well planned and well written program.
Let us begin with a simple C program:
/* First Program to learn C */
#include <stdio.h>
void main()
{
printf("This is a C program\n"); // printing a message
}
Though the program is very simple, a few points are worthy of note. Let us examine the above program. Everything that is inside /* and */ is considered a comment and will be ignored by the compiler. You should not include comments within other comments, so something like this is not allowed:
/* this is a /* comment */ inside a comment, which is wrong */
There is also a way of documentation that works within a line. By using // we can add small documentation within that line.
Every C program contains a function called main. This is the start point of the program. Every function should return a value. In this program the function main returns no return value therefore we have written void main. We could also write this program as:
/* First Program to learn C */
#include <stdio.h>
main()
{
printf("This is a C program\n"); // printing a message
return 0;
}
Both the program are same and perform the same task. The result of both the program will print the following output on the screen:
This is a C program
#include<stdio.h> allows the program to interact with the screen, keyboard and file system of your computer. You will find it at the beginning of almost every C program.
main() declares the start of the function, while the two curly brackets show the start and finish of the function. Curly brackets in C are used to group statements together as in a function, or in the body of a loop. Such a grouping is known as a compound statement or a block.
printf("This is a C program\n"); prints the words on the screen. The text to be printed is enclosed in double quotes. The \n at the end of the text tells the program to print a new line as part of the output. printf() function is used for monitor display of the output.
Most of the C programs are in lower case letters. You will usually find upper case letters used in preprocessor definitions which will be discussed later, or inside quotes as parts of character strings.
Compiling the program
Let the name of our program is CPROG.C. To enter and compile the C program, follow these steps:
- Make the active directory of your C programs and start your editor. For this any text editor can be used, but most C compilers such as Borland's Turbo C++ has an integrated development environment (IDE) that lets you enter, compile, and link your programs in one convenient setting.
- Write and save the source code. You should name the file CPROG.C.
- Compile and link CPROG.C. Execute the appropriate command specified by your compiler's manuals. You should get a message stating that there were no errors or warnings.
- Check the compiler messages. If you receive no errors or warnings, everything should be okay. If there is any error in typing the program, the compiler will catch it and display an error message. Correct the error, displayed in the error message.
- Your first C program should now be compiled and ready to run. If you display a directory listing of all files named CPROG you will get the four files with different extension described as follows:
- CPROG.C, the source code file
- CPROG.BAK, the backup file of source file you created with editor
- CPROG.OBJ, contains the object code for CPROG.C
- CPROG.EXE, the executable program created when you compiled and linked CPROG.C
- To execute, or run, CPROG.EXE, simply enter cprog. The message, This is a C program is displayed on-screen.
Now let us examine the following program:
/* First Program to learn C */ // 1
// 2
#include <stdio.h> // 3
// 4
main() // 5
{
// 6
printf("This is a C program\n"); // 7
// 8
return 0; // 9
} // 10
When you compile this program, the compiler displays a message similar to the following:
cprog.c(8) : Error: `;' expected
let us break this error message in parts. cprog.c is the name of the file where the error was found. (8) is line number where the error was found. Error: `;' expected is A description of the error.
This message is quite informative, and tells you that in line 8 of CPROG.C the compiler expected to find a semicolon but didn't. However, you know that the semicolon was actually omitted from line 7, so there is a discrepancy.
Why the compiler reports an error in line 8 when, in fact, a semicolon was omitted from line 7. The answer lies in the fact that C doesn't care about things like breaks between lines. The semicolon that belongs after the printf() statement could have been placed on the next line though doing so would be bad programming in practice.
Only after encountering the next command (return) in line 8 is the compiler sure that the semicolon is missing. Therefore, the compiler reports that the error is in line 8.
There may be a number of possibilities of different type of errors. Let us discuss linking error Messages. Linker errors are relatively rare and usually result from misspelling the name of a C library function. In this case, you get an Error: undefined symbols: error message, followed by the misspelled name. Once you correct the spelling, the problem should go away.
Printing Numbers
Let us see the following example:
// How To print the numbers //
#include<stdio.h>
void main()
{
int num = 10;
printf(“ The Number Is %d”, num);
}
The output of the program will be displayed on the screen as follows:
The Number Is 10
The % sign is used to signal the output of many different types of variables. The character following the % sign is a d, which signals the output routine to get a decimal value and output it.
Using Variables
In C, a variable must be declared before it can be used. Variables can be declared at the start of any block of code, but most are found at the start of each function. Most local variables are created when the function is called, and are destroyed on return from that function.
To use variables in your C programs, you must know the following rules when giving the name to variables in C:
- The name can contain letters, digits, and the underscore character (_).
- The first character of the name must be a letter. The underscore is also a legal first character, but its use is not recommended.
- C is case sensitive therefore the variable name num is Different from Num.
- C keywords can't be used as variable names. A keyword is a word that is part of the C language.
The following list contains some examples of legal and illegal C variable names:
Variable Name |
Legal or Not |
Num |
Legal |
Ttpt2_t2p |
Legal |
Tt pt |
Illegal: Space is not allowed |
_1990_tax |
Legal but not advised |
Jack_phone# |
Illegal: Contains the illegal character # |
Case |
Illegal: Is a C keyword |
1book |
Illegal: First character is a digit |
The first new thing that stands out is the first line of the body of main():
int num = 10;
This line defines a variable named 'num' of type int and initializes it with the value 10. This might also have been written as:
int num; /* define uninitialized variable 'num' */
/* and after all variable definitions: */
num = 10; /* assigns value 10 to variable 'num' */
Variables may be defined at the start of a block (between the braces {and}), usually this is at the start of a function body, but it may also be at the start of another type of block.
Variables that are defined at the beginning of a block default to the 'auto' status. This means that they only exist during the execution of the block. When the function execution begins, the variables will be created but their contents will be undefined. When the function returns, the variables will be destroyed. The definition could also have been written as:
auto int num = 10;
Since the definition with or without the auto keyword is completely equivalent, the auto keyword is obviously rather redundant.
However, sometimes this is not what you want. Suppose you want a function to keep count of how many times it is called. If the variable would be destroyed every time the function returns, this would not be possible.
Therefore it is possible to give the variable what is called static duration, which means it will stay intact during the whole execution of the program. For example:
static int num = 10;
This initializes the variable num to 10 at the beginning of the program execution. From then on the value will remain untouched; the variable will not be re-initialized if the function is called multiple times.
Sometimes it is not sufficient that the variable will be accessible from one function only or it might not be convenient to pass the value via a parameter to all other functions that need it.
But if you need access to the variable from all the functions in the entire source file, this can also done with the static keyword, but by putting the definition outside all functions. For example:
#include <stdio.h>
static int num = 10; /* will be accessible from entire source file */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
And there are also cases where a variable needs to be accessible from the entire program, which may consist of several source files. This is called a global variable and should be avoided when it is not required.
This is also done by putting the definition outside all functions, but without using the static keyword:
#include <stdio.h>
int num = 10; /* will be accessible from entire program! */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
There is also the extern keyword, which is used for accessing global variables in other modules. There are also a few qualifiers that you can add to variable definitions. The most important of them is const. A variable that is defined as const may not be modified.
There are two more modifiers that are less commonly used. The volatile and register modifier. The volatile modifier requires the compiler to actually access the variable every time it is read. It may not optimize the variable by putting it in a register or so. This is mainly used for multithreading and interrupt processing purposes etc.
The register modifier requests the compiler to optimize the variable into a register. This is only possible with auto variables and in many cases the compiler can better select the variables to optimize into registers, so this keyword is obsolescent. The only direct consequence of making a variable register is that its address cannot be taken.
The table of variables, given in the next page describes the storage class of five type of storage classes.
In the table we see the keyword extern is placed in two rows. The extern keyword is used in functions to declare a static external variable that is defined elsewhere.
Numeric Variable Types
C provides several different types of numeric variables because different numeric values have varying memory storage requirements. These numeric types differ in the ease with which certain mathematical operations can be performed on them.
Small integers require less memory to store, and your computer can perform mathematical operations with such numbers very quickly. Large integers and floating-point values require more storage space and more time for mathematical operations. By using the appropriate variable types, you ensure that your program runs as efficiently as possible.
C's numeric variables fall into the following two main categories:
- Integer variables
- Floating-point variables
Within each of these categories are two or more specific variable types. Table given next, shows the amount of memory, in bytes, required to hold a single variable of each type.
The type char may be equivalent to either signed char or unsigned char, but it is always a separate type from either of these.
In C there is no difference between storing characters or their corresponding numerical values in a variable, so there is also no need for a function to convert between a character and its numerical value or vice versa. For the other integer types, if you omit signed or unsigned the default will be signed, so e.g. int and signed int are equivalent.
The type int must be greater than or equal to the type short, and smaller than or equal to the type long. If you simply need to store some values which are not enormously large it's often a good idea to use the type int; it usually is the size the processor can deal with the easiest, and therefore the fastest.
With several compilers double and long double are equivalent. That combined with the fact that most standard mathematical functions work with type double, is a good reason to always use the type double if you have to work with fractional numbers.
The following table is to better describe the variable types:
Special-purpose types commonly used:
Variable Type |
Description |
size_t |
unsigned type used for storing the sizes of objects in bytes |
time_t |
used to store results of the time() function |
clock_t |
used to store results of the clock() function |
FILE |
used for accessing a stream (usually a file or device) |
ptrdiff_t |
signed type of the difference between 2 pointers |
div_t |
used to store results of the div() function |
ldiv_t |
used to store results of ldiv() function |
fpos_t |
used to hold file position information |
va_list |
used in variable argument handling |
wchar_t |
wide character type (used for extended character sets) |
sig_atomic_t |
used in signal handlers |
Jmp_buf |
used for non-local jumps |
To better understand these variables let us take an example:
/* Program to tell the range and size in bytes of the C variable */
#include <stdio.h>
int main()
{
int a; /* simple integer type */
long int b; /* long integer type */
short int c; /* short integer type */
unsigned int d; /* unsigned integer type */
char e; /* character type */
float f; /* floating point type */
double g; /* double precision floating point */
a = 1023;
b = 2222;
c = 123;
d = 1234;
e = 'X';
f = 3.14159;
g = 3.1415926535898;
printf( "\nA char is %d bytes", sizeof( char ));
printf( "\nAn int is %d bytes", sizeof( int ));
printf( "\nA short is %d bytes", sizeof( short ));
printf( "\nA long is %d bytes", sizeof( long ));
printf( "\nAn unsigned char is %d bytes",
sizeof( unsigned char ));
printf( "\nAn unsigned int is %d bytes",
sizeof( unsigned int ));
printf( "\nAn unsigned short is %d bytes",
sizeof( unsigned short ));
printf( "\nAn unsigned long is %d bytes",
sizeof( unsigned long ));
printf( "\nA float is %d bytes", sizeof( float ));
printf( "\nA double is %d bytes\n", sizeof( double ));
printf("a = %d\n", a); /* decimal output */
printf("a = %o\n", a); /* octal output */
printf("a = %x\n", a); /* hexadecimal output */
printf("b = %ld\n", b); /* decimal long output */
printf("c = %d\n", c); /* decimal short output */
printf("d = %u\n", d); /* unsigned output */
printf("e = %c\n", e); /* character output */
printf("f = %f\n", f); /* floating output */
printf("g = %f\n", g); /* double float output */
printf("\n");
printf("a = %d\n", a); /* simple int output */
printf("a = %7d\n", a); /* use a field width of 7 */
printf("a = %-7d\n", a); /* left justify in
field of 7 */
c = 5;
d = 8;
printf("a = %*d\n", c, a); /* use a field width of 5*/
printf("a = %*d\n", d, a); /* use a field width of 8 */
printf("\n");
printf("f = %f\n", f); /* simple float output */
printf("f = %12f\n", f); /* use field width of 12 */
printf("f = %12.3f\n", f); /* use 3 decimal places */
printf("f = %12.5f\n", f); /* use 5 decimal places */
printf("f = %-12.5f\n", f); /* left justify in field */
return 0;
}
The Result of program after execution will be displayed as:
A char is 1 bytes
An int is 2 bytes
A short is 2 bytes
A long is 4 bytes
An unsigned char is 1 bytes
An unsigned int is 2 bytes
An unsigned short is 2 bytes
An unsigned long is 4 bytes
A float is 4 bytes
A double is 8 bytes
a = 1023
a = 1777
a = 3ff
b = 2222
c = 123
d = 1234
e = X
f = 3.141590
g = 3.141593
a = 1023
a = 1023
a = 1023
a = 1023
a = 1023
f = 3.141590
f = 3.141590
f = 3.142
f = 3.14159
f = 3.14159 |
Before its use, a variable in a C program, it must be declared. A variable declaration tells the compiler the name and type of a variable and optionally initializes the variable to a specific value.
If your program attempts to use a variable that hasn't been declared, the compiler generates an error message. A variable declaration has the following form:
typename varname;
typename specifies the variable type and must be one of the keywords. varname is the variable name. You can declare multiple variables of the same type on one line by separating the variable names with commas:
int count, number, start; /* three integer variables */
float percent, total; /* two float variables */
The typedef Keyword
The typedef keyword is used to create a new name for an existing data type. In effect, typedef creates a synonym. For example, the statement
typedef int integer;
here we see typedef creates integer as a synonym for int. You then can use integer to define variables of type int, as in this example:
integer count;
So typedef does not create a new data type, it only lets you use a different name for a predefined data type.
Initializing Numeric Variables
When any variable is declared, the compiler is instructed to set aside storage space for the variable. However, the value stored in that space, the value of the variable, is not defined. It might be zero, or it might be some random "garbage" value. Before using a variable, you should always initialize it to a known value. Let us take this example:
int count; /* Set aside storage space for count */
count = 0; /* Store 0 in count */
This statement uses the equal sign (=), which is C's assignment operator. You can also initialize a variable when it's declared. To do so, follow the variable name in the declaration statement with an equal sign and the desired initial value:
int count = 0;
double rate = 0.01, complexity = 28.5;
Be careful not to initialize a variable with a value outside the allowed range. Here are two examples of out-of-range initializations:
int amount = 100000;
unsigned int length = -2500;
The C compiler does not catch such errors. Your program may compile and link, but you may get unexpected results when the program is run.
Let us take the following example to calculate the total number of sectors in a Disk:
// Model Program To Calculate Sectors in A Disk //
#include<stdio.h>
#define SECTOR_PER_SIDE 63
#define SIDE_PER_CYLINDER 254
void main()
{
int cylinder=0;
clrscr();
printf("Enter The No. of Cylinders in the Disk \n\n\t");
scanf("%d",&cylinder); // Get the value from the user //
printf("\n\n\t Total Number of Sectors in the disk = %ld", (long)SECTOR_PER_SIDE*SIDE_PER_CYLINDER* cylinder);
getch();
}
The output of the program is as follows:
Enter The No. of Cylinders in the Disk
1024
Total Number of Sectors in the disk = 16386048
In this example we see three new things to learn. #define is used to use symbolic constants in the program or in some cases to save time by defining long words in small symbols.
Here we have defined the number of sectors per side that is 63 as SECTOR_PER_SIDE to make the program easy to understand. The same case is true for #define SIDE_PER_CYLINDER 254. scanf() is used to get the input from the user.
Here we are taking the number of cylinders as input from the user. * is used to multiply two or more value as shown in the example.
getch() function basically gets a single character input from the keyboard. By typing getch(); here we stop the screen until any key is hit from the keyboard.
Operators
An operator is a symbol that instructs C to perform some operation, or action, on one or more operands. An operand is something that an operator acts on. In C, all operands are expressions. C operators are of following four categories:
- The assignment operator
- Mathematical operators
- Relational operators
- Logical operators
Assignment Operator
The assignment operator is the equal sign (=). The use of equal sign in programming is different from its use in regular mathematical algebraic relations. If you write
x = y;
In a C program, it does not mean "x is equal to y." Instead, it means "assign the value of y to x." In a C assignment statement, the right side can be any expression, and the left side must be a variable name. Thus, the form is as follows:
variable = expression;
During the execution, expression is evaluated, and the resulting value is assigned to variable.
Mathematical Operators
C's mathematical operators perform mathematical operations such as addition and subtraction. C has two unary mathematical operators and five binary mathematical operators. The unary mathematical operators are so named because they take a single operand. C has two unary mathematical operators.
The increment and decrement operators can be used only with variables, not with constants. The operation performed is to add one to or subtract one from the operand. In other words, the statements ++x; and --y; are the equivalents of these statements:
x = x + 1;
y = y - 1;
binary mathematical operators take two operands. The first four binary operators, which include the common mathematical operations found on a calculator (+, -, *, /), are familiar to you. The fifth operator Modulus returns the remainder when the first operand is divided by the second operand. For example, 11 modulus 4 equals 3 (11 is divided by 4, two times and 3 left over).
Relational Operators
C's relational operators are used to compare expressions. An expression containing a relational operator evaluates to either true (1) or false (0). C has six relational operators.
Logical Operators
Logical operators of C let you combine two or more relational expressions into a single expression that evaluates to either true or false. Logical operators evaluate to either true or false, depending on the true or false value of their operands.
If x is an integer variable, expressions using logical operators could be written in the following ways:
(x > 1) && (x < 5)
(x >= 2) && (x <= 4)
Operator |
Symbol |
Description |
Example |
Assignment operators |
equal |
= |
assign the value of y to x |
x = y |
Mathematical operators |
Increment |
++ |
Increments the operand by one |
++x, x++ |
Decrement |
-- |
Decrements the operand by one |
--x, x-- |
Addition |
+ |
Adds two operands |
x + y |
Subtraction |
- |
Subtracts the second operand from the first |
x - y |
Multiplication |
* |
Multiplies two operands |
x * y |
Division |
/ |
Divides the first operand by the second operand |
x / y |
Modulus |
% |
Gives the remainder when the first operand is divided by the second operand |
x % y |
Relational operators |
Equal |
= = |
Equality |
x = = y |
Greater than |
> |
Greater than |
x > y |
Less than |
< |
Less than |
x < y |
Greater than or equal to |
>= |
Greater than or equal to |
x >= y |
Less than or equal to |
<= |
Less than or equal to |
x <= y |
Not equal |
!= |
Not equal to |
x != y |
Logical operators |
AND |
&& |
True (1) only if both exp1 and exp2 are true; false (0) otherwise |
exp1 && exp2 |
OR |
|| |
True (1) if either exp1 or exp2 is true; false (0) only if both are false |
exp1 || exp2 |
NOT |
! |
False (0) if exp1 is true; true (1) if exp1 is false |
!exp1 |
Things to remember about logical expressions
x * = y |
is same as |
x = x * y |
y - = z + 1 |
is same as |
y = y - z + 1 |
a / = b |
is same as |
a = a / b |
x + = y / 8 |
is same as |
x = x + y / 8 |
y % = 3 |
is same as |
y = y % 3 |
The Comma Operator
The comma is frequently used in C as a simple punctuation mark, to separate variable declarations, function arguments, etc. In certain situations, the comma acts as an operator.
You can form an expression by separating two sub expressions with a comma. The result is as follows:
- Both expressions are evaluated, with the left expression being evaluated first.
- The entire expression evaluates to the value of the right expression.
For example, the following statement assigns the value of b to x, then increments a, and then increments b:
x = (a++, b++);
C operator precedence (Summary of C operators)
Rank and Associativity |
Operators |
1(left to right) |
() [] -> . |
2(right to left) |
! ~ ++ -- * (indirection) & (address-of) (type)
sizeof + (unary) - (unary) |
3(left to right) |
* (multiplication) / % |
4(left to right) |
+ - |
5(left to right) |
<< >> |
6(left to right) |
< <= > >= |
7(left to right) |
= = != |
8(left to right) |
& (bitwise AND) |
9(left to right) |
^ |
10(left to right) |
| |
11(left to right) |
&& |
12(left to right) |
|| |
13(right to left) |
?: |
14(right to left) |
= += -= *= /= %= &= ^= |= <<= >>= |
15(left to right) |
, |
() is the function operator; [] is the array operator. |
|
Let us take an example of use of operators:
/* Use Of Operators */
int main()
{
int x = 0, y = 2, z = 1025;
float a = 0.0, b = 3.14159, c = -37.234;
/* incrementing */
x = x + 1; /* This increments x */
x++; /* This increments x */
++x; /* This increments x */
z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */
/* decrementing */
y = y - 1; /* This decrements y */
y--; /* This decrements y */
--y; /* This decrements y */
y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */
/* arithmetic op */
a = a + 12; /* This adds 12 to a */
a += 12; /* This adds 12 more to a */
a *= 3.2; /* This multiplies a by 3.2 */
a -= b; /* This subtracts b from a */
a /= 10.0; /* This divides a by 10.0 */
/* conditional expression */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* This expression */
if (b >= 3.0) /* And this expression */
a = 2.0; /* are identical, both */
else /* will cause the same */
a = 10.5; /* result. */
c = (a > b ? a : b); /* c will have the max of a or b */
c = (a > b ? b : a); /* c will have the min of a or b */
printf("x=%d, y=%d, z= %d\n", x, y, z);
printf("a=%f, b=%f, c= %f", a, b, c);
return 0;
}
and the result of this program will be displayed on the screen as:
x=3, y=1, z=1
a=2.000000, b=3.141590, c=2.000000
Something more about printf() and Scanf()
Consider the following two printf statements
printf(“\t %d\n”, num);
printf(“%5.2f”, fract);
in the first printf statement \t requests for the tab displacement on the screen the argument %d tells the compiler that the value of num should be printed as decimal integer. \n causes the new output to start from new line.
In second printf statement %5.2f tells the compiler that the output must be in floating point, with five places in all and two places to the right of the decimal point. More about the backslash character has been shown in the following table:
Constant |
Meaning |
‘\a’ |
Audible alert (bell) |
‘\b’ |
Backspace |
‘\f’ |
Form feed |
‘\n’ |
New line |
‘\r’ |
Carriage return |
‘\t’ |
Horizontal tab |
‘\v’ |
Vertical tab |
‘\’’ |
Single quote |
‘\”’ |
Double quote |
‘\?’ |
Question mark |
‘\\’ |
Backslash |
‘\0’ |
Null |
Let us consider the following scanf statement
scanf(“%d”, &num);
The data from the keyboard is received by scanf function. In the above format, the & (ampersand) symbol before each variable name is an operator that specifies the address of variable name.
By doing this, the execution stops and waits for the value of the variable num to be typed. When the integer value is entered and return key is pressed, the computer proceeds to the next statement. The scanf and printf format codes are listed in the following table:
Code |
Reads... |
%c |
Single character |
%d |
Decimal integer |
%e |
Floating point value |
%f |
Floating point value |
%g |
Floating point value |
%h |
Short integer |
%i |
Decimal, hexadecimal or octal integer |
%o |
Octal integer |
%s |
String |
%u |
Unsigned decimal integer |
%x |
Hexadecimal integer |
Control Statements
A program consists of a number of statements which are usually executed in sequence. Programs can be much more powerful if we can control the order in which statements are run.
Statements fall into three general types:
- Assignment, where values, usually the results of calculations, are stored in variables.
- Input / Output, data is read in or printed out.
- Control, the program makes a decision about what to do next.
This section will discuss the use of control statements in C. We will show how they can be used to write powerful programs by;
- Repeating important sections of the program.
- Selecting between optional sections of a program.
The if else Statement
This is used to decide whether to do something at a special point, or to decide between two courses of action.
The following test decides whether a student has passed an exam with a pass mark of 45
if (result >= 45)
printf("Pass\n");
else
printf("Fail\n");
It is possible to use the if part without the else.
if (temperature < 0)
print("Frozen\n");
Each version consists of a test, in the bracketed statement following the if. If the test is true then the next statement is obeyed. If it is false then the statement following the else is obeyed if present. After this, the rest of the program continues as normal.
If we wish to have more than one statement following the if or the else, they should be grouped together between curly brackets. Such a grouping is called a compound statement or a block.
if (result >= 45)
{ printf("Passed\n");
printf("Congratulations\n");
}
else
{ printf("Failed\n");
printf("Better Luck Next Time\n");
}
Sometimes we wish to make a multi-way decision based on several conditions. The most general way of doing this is by using the else if variant on the if statement.
This works by cascading several comparisons. As soon as one of these gives a true result, the following statement or block is executed, and no further comparisons are performed. In the following example we are awarding grades depending on the exam result.
if (result <=100 && result >= 75)
printf("Passed: Grade A\n");
else if (result >= 60)
printf("Passed: Grade B\n");
else if (result >= 45)
printf("Passed: Grade C\n");
else
printf("Failed\n");
In this example, all comparisons test a single variable called result. In other cases, each test may involve a different variable or some combination of tests. The same pattern can be used with more or fewer else if's, and the final alone else may be left out.
It is up to the programmer to devise the correct structure for each programming problem. To better understand the use of if else let us see the example
#include <stdio.h>
int main()
{
int num;
for(num = 0 ; num < 10 ; num = num + 1)
{
if (num == 2)
printf("num is now equal to %d\n", num);
if (num < 5)
printf("num is now %d, which is less than 5\n", num);
else
printf("num is now %d, which is greater than 4\n", num);
} /* end of for loop */
return 0;
}
Result of the program
num is now 0, which is less than 5
num is now 1, which is less than 5
num is now equal to 2
num is now 2, which is less than 5
num is now 3, which is less than 5
num is now 4, which is less than 5
num is now 5, which is greater than 4
num is now 6, which is greater than 4
num is now 7, which is greater than 4
num is now 8, which is greater than 4
num is now 9, which is greater than 4
The switch Statement
This is another form of the multi way decision. It is well structured, but can only be used in certain cases where;
- Only one variable is tested, all branches must depend on the value of that variable. The variable must be an integral type. (int, long, short or char).
- Each possible value of the variable can control a single branch. A final, catch all, default branch may optionally be used to trap all unspecified cases.
Example given below will clarify things. This is a function which converts an integer into a vague description. It is useful where we are only concerned in measuring a quantity when it is quite small.
estimate(number)
int number;
/* Estimate a number as none, one, two, several, many */
{ switch(number) {
case 0 :
printf("None\n");
break;
case 1 :
printf("One\n");
break;
case 2 :
printf("Two\n");
break;
case 3 :
case 4 :
case 5 :
printf("Several\n");
break;
default :
printf("Many\n");
break;
}
}
Each interesting case is listed with a corresponding action. The break statement prevents any further statements from being executed by leaving the switch. Since case 3 and case 4 have no following break, they continue on allowing the same action for several values of number.
Both if and switch constructs allow the programmer to make a selection from a number of possible actions. Let us see an example:
#include <stdio.h>
int main()
{
int num;
for (num = 3 ; num < 13 ; num = num + 1)
{
switch (num)
{
case 3 :
printf("The value is three\n");
break;
case 4 :
printf("The value is four\n");
break;
case 5 :
case 6 :
case 7 :
case 8 :
printf("The value is between 5 and 8\n");
break;
case 11 :
printf("The value is eleven\n");
break;
default :
printf("It is one of the undefined values\n");
break;
} /* end of switch */
} /* end of for loop */
return 0;
}
The output of the program will be
The value is three
The value is four
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
It is one of the undefined values
It is one of the undefined values
The value is eleven
It is one of the undefined values
The break Statement
We have already met break in the discussion of the switch statement. It is used to exit from a loop or a switch, control passing to the first statement beyond the loop or a switch.
With loops, break can be used to force an early exit from the loop, or to implement a loop with a test to exit in the middle of the loop body. A break within a loop should always be protected within an if statement which provides the test to control the exit condition.
The continue Statement
This is similar to break but is encountered less frequently. It only works within loops where its effect is to force an immediate jump to the loop control statement.
- In a while loop, jump to the test statement.
- In a do while loop, jump to the test statement.
- In a for loop, jump to the test, and perform the iteration.
Like a break, continue should be protected by an if statement. You are unlikely to use it very often. To better understand the use of break and continue let us examine the following program:
#include <stdio.h>
int main()
{
int value;
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
break;
printf("In the break loop, value is now %d\n", value);
}
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
continue;
printf("In the continue loop, value is now %d\n", value);
}
return 0;
}
The output of the program will be as follows:
In the break loop, value is now 5
In the break loop, value is now 6
In the break loop, value is now 7
In the continue loop, value is now 5
In the continue loop, value is now 6
In the continue loop, value is now 7
In the continue loop, value is now 9
In the continue loop, value is now 10
In the continue loop, value is now 11
In the continue loop, value is now 12
In the continue loop, value is now 13
In the continue loop, value is now 14
Loops
The other main type of control statement is the loop. Loops allow a statement, or block of statements, to be repeated. Computers are very good at repeating simple tasks many times. The loop is C's way of achieving this.
C gives you a choice of three types of loop, while, do-while and for.
- The while loop keeps repeating an action until an associated test returns false. This is useful where the programmer does not know in advance how many times the loop will be traversed.
- The do while loops is similar, but the test occurs after the loop body is executed. This ensures that the loop body is run at least once.
- The for loop is frequently used, usually where the loop will be traversed a fixed number of times. It is very flexible, and novice programmers should take care not to abuse the power it offers.
The while Loop
The while loop repeats a statement until the test at the top proves false. As an example, here is a function to return the length of a string. Remember that the string is represented as an array of characters terminated by a null character '\0'.
int string_length(char string[])
{ int i = 0;
while (string[i] != '\0')
i++;
return(i);
}
The string is passed to the function as an argument. The size of the array is not specified, the function will work for a string of any size.
The while loop is used to look at the characters in the string one at a time until the null character is found. Then the loop is exited and the index of the null is returned.
While the character is not null, the index is incremented and the test is repeated. We’ll go in depth of arrays later. Let us see an example for while loop:
#include <stdio.h>
int main()
{
int count;
count = 0;
while (count < 6)
{
printf("The value of count is %d\n", count);
count = count + 1;
}
return 0;
}
and the result is displayed as follows:
The value of count is 0
The value of count is 1
The value of count is 2
The value of count is 3
The value of count is 4
The value of count is 5
The do while Loop
This is very similar to the while loop except that the test occurs at the end of the loop body. This guarantees that the loop is executed at least once before continuing.
Such a setup is frequently used where data is to be read. The test then verifies the data, and loops back to read again if it was unacceptable.
do
{
printf("Enter 1 for yes, 0 for no :");
scanf("%d", &input_value);
} while (input_value != 1 && input_value != 0)
To better understand the do while loop let us see the following example:
#include <stdio.h>
int main()
{
int i;
i = 0;
do
{
printf("The value of i is now %d\n", i);
i = i + 1;
} while (i < 5);
return 0;
}
The result of the program is displayed as follows:
The value of i is now 0
The value of i is now 1
The value of i is now 2
The value of i is now 3
The value of i is now 4
The for Loop
The for loop works well where the number of iterations of the loop is known before the loop is entered. The head of the loop consists of three parts separated by semicolons.
- The first is run before the loop is entered. This is usually the initialization of the loop variable.
- The second is a test, the loop is exited when this returns false.
- The third is a statement to be run every time the loop body is completed. This is usually an increment of the loop counter.
The example is a function which calculates the average of the numbers stored in an array. The function takes the array and the number of elements as arguments.
float average(float array[], int count)
{
float total = 0.0;
int i;
for(i = 0; i < count; i++)
total += array[i];
return(total / count);
}
The for loop ensures that the correct number of array elements are added up before calculating the average.
The three statements at the head of a for loop usually do just one thing each, however any of them can be left blank. A blank first or last statement will mean no initialization or running increment. A blank comparison statement will always be treated as true. This will cause the loop to run indefinitely unless interrupted by some other means. This might be a return or a break statement.
It is also possible to squeeze several statements into the first or third position, separating them with commas. This allows a loop with more than one controlling variable. The example below illustrates the definition of such a loop, with variables hi and lo starting at 100 and 0 respectively and converging.
The for loop gives a variety of shorthand to be used in it. Watch out the following expression, in this expression the single loop contains two for loops in it. Here hi-- is same as hi = hi - 1 and lo++ is same as lo = lo + 1,
for(hi = 100, lo = 0; hi >= lo; hi--, lo++)
The for loop is extremely flexible and allows many types of program behavior to be specified simply and quickly. Let us see an example of for loop
#include <stdio.h>
int main()
{
int index;
for(index = 0 ; index < 6 ; index = index + 1)
printf("The value of the index is %d\n", index);
return 0;
}
Result of the program is displayed as follows:
The value of the index is 0
The value of the index is 1
The value of the index is 2
The value of the index is 3
The value of the index is 4
The value of the index is 5
The goto Statement
C has a goto statement which permits unstructured jumps to be made. To use a goto statement, you simply use the reserved word goto followed by the symbolic name to which you wish to jump. The name is then placed anywhere in the program followed by a colon. You can jump nearly anywhere within a function, but you are not permitted to jump into a loop, although you are allowed to jump out of a loop.
This particular program is really a mess but it is a good example of why software writers are trying to eliminate the use of the goto statement as much as possible. The only place in this program where it is reasonable to use the goto is, where the program jumps out of the three nested loops in one jump. In this case it would be rather messy to set up a variable and jump successively out of each of the three nested loops but one goto statement gets you out of all three in a very concise manner.
Some persons say the goto statement should never be used under any circumstances, but this is narrow minded thinking. If there is a place where a goto will clearly do a neater control flow than some other construct, feel free to use it, however, as it is in the rest of the program on your monitor. Let us see the example:
#include <stdio.h>
int main()
{
int dog, cat, pig;
goto real_start;
some_where:
printf("This is another line of the mess.\n");
goto stop_it;
/* the following section is the only section with a useable goto */
real_start:
for(dog = 1 ; dog < 6 ; dog = dog + 1)
{
for(cat = 1 ; cat < 6 ; cat = cat + 1)
{
for(pig = 1 ; pig < 4 ; pig = pig + 1)
{
printf("Dog = %d Cat = %d Pig = %d\n", dog, cat, pig);
if ((dog + cat + pig) > 8 ) goto enough;
}
}
}
enough: printf("Those are enough animals for now.\n");
/* this is the end of the section with a useable goto statement */
printf("\nThis is the first line of the code.\n");
goto there;
where:
printf("This is the third line of the code.\n");
goto some_where;
there:
printf("This is the second line of the code.\n");
goto where;
stop_it:
printf("This is the last line of this mess.\n");
return 0;
}
Let us see the results displayed
Dog = 1 Cat = 1 Pig = 1
Dog = 1 Cat = 1 Pig = 2
Dog = 1 Cat = 1 Pig = 3
Dog = 1 Cat = 2 Pig = 1
Dog = 1 Cat = 2 Pig = 2
Dog = 1 Cat = 2 Pig = 3
Dog = 1 Cat = 3 Pig = 1
Dog = 1 Cat = 3 Pig = 2
Dog = 1 Cat = 3 Pig = 3
Dog = 1 Cat = 4 Pig = 1
Dog = 1 Cat = 4 Pig = 2
Dog = 1 Cat = 4 Pig = 3
Dog = 1 Cat = 5 Pig = 1
Dog = 1 Cat = 5 Pig = 2
Dog = 1 Cat = 5 Pig = 3
Those are enough animals for now.
This is the first line of the code.
This is the second line of the code.
This is the third line of the code.
This is another line of the mess.
This is the last line of this mess.
Pointers
Sometimes we want to know where a variable resides in memory. A pointer contains the address of a variable that has a specific value. When declaring a pointer, an asterisk is placed immediately before the pointer name.
The address of the memory location where the variable is stored can be found by placing an ampersand in front of the variable name.
int num; /* Normal integer variable */
int *numPtr; /* Pointer to an integer variable */
The following example prints the variable value and the address in memory of that variable.
printf("The value %d is stored at address %X\n", num, &num);
To assign the address of the variable num to the pointer numPtr, you assign the address of the variable, num, as in the example given next:
numPtr = #
To find out what is stored at the address pointed to by numPtr, the variable needs to be dereferenced. Dereferencing is achieved with the asterisk that the pointer was declared with.
printf("The value %d is stored at address %X\n", *numPtr, numPtr);
All variables in a program reside in memory. The statements given below request that the compiler reserve 4 bytes of memory on a 32-bit computer for the floating-point variable x, then put the value 6.5 in it.
float x;
x = 6.5;
As the address location in memory of any variable is obtained by placing the operator & before its name therefore &x is the address of x. C allows us to go one stage further and define a variable, called a pointer that contains the address of other variables. Rather we can say that pointer points to other variable. For example:
float x;
float* px;
x = 6.5;
px = &x;
defines px to be a pointer to objects of type float, and sets it equal to the address of x. Thus, *px refers to the value of x:
Let us examine the following statements:
int var_x;
int* ptrX;
var_x = 6;
ptrX = &var_x;
*ptrX = 12;
printf("value of x : %d", var_x);
The first line causes the compiler to reserve a space in memory for an integer. The second line tells the compiler to reserve space to store a pointer.
A pointer is a storage location for an address. The third line should remind you the scanf statements. The address "&" operator tells compiler to go to the place it stored var_x, and then give the address of the storage location to ptrX.
The asterisk * in front of a variable tells the compiler to dereference the pointer, and go to memory. Then you can make assignments to variable stored at that location. You can reference a variable and access its data through a pointer. Let us see an example of pointers:
/* illustration of pointer use */
#include <stdio.h>
int main()
{
int index, *pt1, *pt2;
index = 39; /* any numerical value */
pt1 = &index; /* the address of index */
pt2 = pt1;
printf("The value is %d %d %d\n", index, *pt1, *pt2);
*pt1 = 13; /* this changes the value of index */
printf("The value is %d %d %d\n", index, *pt1, *pt2);
return 0;
}
The output of the program will be displayed as follows:
The value is 39 39 39
The value is 13 13 13
Let us see another example to better understand the use of pointers:
#include <stdio.h>
#include <string.h>
int main()
{
char strg[40], *there, one, two;
int *pt, list[100], index;
strcpy(strg, "This is a character string.");
/* the function strcpy() is to copy one string to another. we’ll read about strcpy() function in String Section later */
one = strg[0]; /* one and two are identical */
two = *strg;
printf("The first output is %c %c\n", one, two);
one = strg[8]; /* one and two are identical */
two = *(strg+8);
printf("The second output is %c %c\n", one, two);
there = strg+10; /* strg+10 is identical to &strg[10] */
printf("The third output is %c\n", strg[10]);
printf("The fourth output is %c\n", *there);
for (index = 0 ; index < 100 ; index++)
list[index] = index + 100;
pt = list + 27;
printf("The fifth output is %d\n", list[27]);
printf("The sixth output is %d\n", *pt);
return 0;
}
The output of the program will be like this:
The first output is T T
The second output is a a
The third output is c
The fourth output is c
The fifth output is 127
The sixth output is 127
Arrays
An array is a collection of variables of the same type. Individual array elements are identified by an integer index. In C the index begins at zero and is always written inside square brackets.
We have already met single dimensioned arrays which are declared like this
int results[20];
Arrays can have more dimensions, in which case they might be declared as
int results_2d[20][5];
int results_3d[20][5][3];
Each index has its own set of square brackets. An array is declared in the main function, usually has details of dimensions included. It is possible to use another type called a pointer in place of an array. This means that dimensions are not fixed immediately, but space can be allocated as required. This is an advanced technique which is only required in certain specialized programs.
As an example, here is a simple function to add up all of the integers in a single dimensioned array.
int add_array(int array[], int size)
{
int i;
int total = 0;
for(i = 0; i < size; i++)
total += array[i];
return(total);
}
The program given next will make a string, access some data in it, print it out. Access it again using pointers, and then print the string out. It should print “Hi!” and “012345678” on different lines. Let us see the coding of the program:
#include <stdio.h>
#define STR_LENGTH 10
void main()
{
char Str[STR_LENGTH];
char* pStr;
int i;
Str[0] = 'H';
Str[1] = 'i';
Str[2] = '!';
Str[3] = '\0'; // special end string character NULL
printf("The string in Str is : %s\n", Str);
pStr = &Str[0];
for (i = 0; i < STR_LENGTH; i++)
{
*pStr = '0'+i;
pStr++;
}
Str[STR_LENGTH-1] = '\0';
printf("The string in Str is : %s\n", Str);
}
[] (square braces) are used to declare the array. The line of the program char Str[STR_LENGTH]; declares an array of ten characters. These are ten individual characters, which are all put together in memory into the same place. They can all be accessed through our variable name Str along with a [n] where n is the element number.
It should always be kept in mind when talking about array that when C declares an array of ten, the elements you can access are numbered 0 to 9. Accessing the first element corresponds to accessing the 0th element. So in case of Arrays always count from 0 to size of array - 1.
Next notice that we put the letters "Hi!" into the array, but then we put in a '\0' you are probably wondering what this is. "\0" stands for NULL and represents the end of string. All character strings need to end with this special character '\0'. If they do not, and then someone calls printf on the string, then printf would start at the memory location of your string, and continue printing tell it encounters '\0' and thus you will end up with a bunch of garbage at the end of your string. So make sure to terminate your strings properly.
Character Arrays
A string constant , such as
"I am a string"
is an array of characters. It is represented internally in C by the ASCII characters in the string, i.e., “I”, blank, “a”, “m”,…or the above string, and terminated by the special null character “\0” so programs can find the end of the string.
String constants are often used in making the output of code intelligible using printf:
printf("Hello, world\n");
printf("The value of a is: %f\n", a);
String constants can be associated with variables. C provides the character type variable, which can contain one character (1 byte) at a time. A character string is stored in an array of character type, one ASCII character per location.
Never forget that, since strings are conventionally terminated by the null character “\0”, we require one extra storage location in the array.
C does not provide any operator which manipulates entire strings at once. Strings are manipulated either via pointers or via special routines available from the standard string library string.h.
Using character pointers is relatively easy since the name of an array is a just a pointer to its first element. Consider the program given next:
#include<stdio.h>
void main()
{
char text_1[100], text_2[100], text_3[100];
char *ta, *tb;
int i;
/* set message to be an arrray */
/* of characters; initialize it */
/* to the constant string "..." */
/* let the compiler decide on */
/* its size by using [] */
char message[] = "Hello, I am a string; what are
you?";
printf("Original message: %s\n", message);
/* copy the message to text_1 */
i=0;
while ( (text_1[i] = message[i]) != '\0' )
i++;
printf("Text_1: %s\n", text_1);
/* use explicit pointer arithmetic */
ta=message;
tb=text_2;
while ( ( *tb++ = *ta++ ) != '\0' )
;
printf("Text_2: %s\n", text_2);
}
The output of the program will be as follows:
Original message: Hello, I am a string; what are you?
Text_1: Hello, I am a string; what are you?
Text_2: Hello, I am a string; what are you?
The standard “string” library contains many useful functions to manipulate strings, which we will learn in the string section later.
Accessing the Elements
To access an individual element in the array, the index number follows the variable name in square brackets. The variable can then be treated like any other variable in C. The following example assigns a value to the first element in the array.
x[0] = 16;
The following example prints the value of the third element in an array.
printf("%d\n", x[2]);
The following example uses the scanf function to read a value from the keyboard into the last element of an array with ten elements.
scanf("%d", &x[9]);
Initializing Array Elements
Arrays can be initialized like any other variables by assignment. As an array contains more than one value, the individual values are placed in curly braces, and separated with commas. The following example initializes a ten dimensional array with the first ten values of the three times table.
int x[10] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30};
This saves assigning the values individually as in the following example.
int x[10];
x[0] = 3;
x[1] = 6;
x[2] = 9;
x[3] = 12;
x[4] = 15;
x[5] = 18;
x[6] = 21;
x[7] = 24;
x[8] = 27;
x[9] = 30;
Looping through an Array
As the array is indexed sequentially, we can use the for loop to display all the values of an array. The following example displays all the values of an array:
#include <stdio.h>
int main()
{
int x[10];
int counter;
/* Randomise the random number generator */
srand((unsigned)time(NULL));
/* Assign random values to the variable */
for (counter=0; counter<10; counter++)
x[counter] = rand();
/* Display the contents of the array */
for (counter=0; counter<10; counter++)
printf("element %d has the value %d\n", counter, x[counter]);
return 0;
}
though the output will print the different values every time, result will be displayed something like this:
element 0 has the value 17132
element 1 has the value 24904
element 2 has the value 13466
element 3 has the value 3147
element 4 has the value 22006
element 5 has the value 10397
element 6 has the value 28114
element 7 has the value 19817
element 8 has the value 27430
element 9 has the value 22136
Multidimensional Arrays
An array can have more than one dimension. By allowing the array to have more than one dimension provides greater flexibility. For example, spreadsheets are built on a two dimensional array; an array for the rows, and an array for the columns.
The following example uses a two dimensional array with two rows, each containing five columns:
#include <stdio.h>
int main()
{
/* Declare a 2 x 5 multidimensional array */
int x[2][5] = { {1, 2, 3, 4, 5},
{2, 4, 6, 8, 10} };
int row, column;
/* Display the rows */
for (row=0; row<2; row++)
{
/* Display the columns */
for (column=0; column<5; column++)
printf("%d\t", x[row][column]);
putchar('\n');
}
return 0;
}
The output of this program will be displayed as follows:
1 2 3 4 5
2 4 6 8 10
Strings
A string is a group of characters, usually letters of the alphabet, In order to format your print display in such a way that it looks nice, has meaningful names and titles, and is aesthetically pleasing to you and the people using the output of your program.
In fact, you have already been using strings in the examples of the previous topics. But it is not the complete introduction of strings. There are many possible cases in the programming, where the use of formatted strings helps the programmer to avoid the too many complications in the program and too many bugs of course.
A complete definition of a string is a series of character type data terminated by a null character (‘\0’).
When C is going to use a string of data in some way, either to compare it with another string, output it, copy it to another string, or whatever, the functions are set up to do what they are called to do until a null is detected.
There is no basic data type for a string in C Instead; strings in C are implemented as an array of characters. For example, to store a name you could declare a character array big enough to store the name, and then use the appropriate library functions to manipulate the name.
The following example displays the string on the screen, entered by user:
#include <stdio.h>
int main()
{
char name[80]; /* Create a character array
called name */
printf("Enter your name: ");
gets(name);
printf("The name you entered was %s\n", name);
return 0;
}
The execution of the program will be:
Enter your name: Tarun Tyagi
The name you entered was Tarun Tyagi
Some Common String Functions
The standard string.h library contains many useful functions to manipulate strings. Some of the most useful functions have been exampled here.
The strlen Function
The strlen function is used to determine the length of a string. Let us learn the use of strlen with example:
#include <stdio.h>
#include <string.h>
int main()
{
char name[80];
int length;
printf("Enter your name: ");
gets(name);
length = strlen(name);
printf("Your name has %d characters\n", length);
return 0;
}
And the execution of the program will be as follows:
Enter your name: Tarun Subhash Tyagi
Your name has 19 characters
Enter your name: Preeti Tarun
Your name has 12 characters
The strcpy Function
The strcpy function is used to copy one string to another. Let us learn the use of this function with example:
#include <stdio.h>
#include <string.h>
int main()
{
char first[80];
char second[80];
printf("Enter first string: ");
gets(first);
printf("Enter second string: ");
gets(second);
printf("first: %s, and second: %s Before strcpy()\n "
, first, second);
strcpy(second, first);
printf("first: %s, and second: %s After strcpy()\n",
first, second);
return 0;
}
and the output of the program will be as:
Enter first string: Tarun
Enter second string: Tyagi
first: Tarun, and second: Tyagi Before strcpy()
first: Tarun, and second: Tarun After strcpy()
The strcmp Function
The strcmp function is used to compare two strings together. The variable name of an array points to the base address of that array. Therefore, if we try to compare two strings using the following, we would be comparing two addresses, which would obviously never be the same as it is not possible to store two values in the same location.
if (first == second) /* It can never be done to compare strings */
The following example uses the strcmp function to compare two strings:
#include <string.h>
int main()
{
char first[80], second[80];
int t;
for(t=1;t<=2;t++)
{
printf("\nEnter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
if (strcmp(first, second) == 0)
puts("The two strings are equal");
else
puts("The two strings are not equal");
}
return 0;
}
And the execution of the program will be as follows:
Enter a string: Tarun
Enter another string: tarun
The two strings are not equal
Enter a string: Tarun
Enter another string: Tarun
The two strings are equal
The strcat Function
The strcat function is used to join one string to another. Let us see how? With the help of example:
#include <string.h>
int main()
{
char first[80], second[80];
printf("Enter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
strcat(first, second);
printf("The two strings joined together: %s\n",
first);
return 0;
}
And the execution of the program will be as follows:
Enter a string: Data
Enter another string: Recovery
The two strings joined together: DataRecovery
The strtok Function
The strtok function is used to find the next token in a string. The token is specified by a list of possible delimiters.
The following example reads a line of text from a file and determines a word using the delimiters, space, tab, and new line. Each word is then displayed on a separate line:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *in;
char line[80];
char *delimiters = " \t\n";
char *token;
if ((in = fopen("C:\\text.txt", "r")) == NULL)
{
puts("Unable to open the input file");
return 0;
}
/* Read each line one at a time */
while(!feof(in))
{
/* Get one line */
fgets(line, 80, in);
if (!feof(in))
{
/* Break the line up into words */
token = strtok(line, delimiters);
while (token != NULL)
{
puts(token);
/* Get the next word */
token = strtok(NULL, delimiters);
}
}
}
fclose(in);
return 0;
}
It above program, in = fopen("C:\\text.txt", "r"), opens and existing file C:\\text.txt. If the does not exist in the specified path or for any reason, the file could not be opened, an error message is displayed on screen.
Consider the following example, which uses some of these functions:
#include <stdio.h>
#include <string.h>
void main()
{
char line[100], *sub_text;
/* initialize string */
strcpy(line,"hello, I am a string;");
printf("Line: %s\n", line);
/* add to end of string */
strcat(line," what are you?");
printf("Line: %s\n", line);
/* find length of string */
/* strlen brings back */
/* length as type size_t */
printf("Length of line: %d\n", (int)strlen(line));
/* find occurence of substrings */
if ( (sub_text = strchr ( line, 'W' ) )!= NULL )
printf("String starting with \"W\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( line, 'w' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( sub_text, 'u' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
}
The output of the program will be displayed as follows:
Line: hello, I am a string;
Line: hello, I am a string; what are you?
Length of line: 35
String starting with "w" ->what are you?
String starting with "w" ->u?
Functions
The best way to develop and maintain a large program is to construct it from smaller pieces each of which are easier to manage (a technique sometimes referred to as Divide and Conquer). Functions allow the programmer to modularize the program.
Functions allow complicated programs to be parceled up into small blocks, each of which is easier to write, read, and maintain. We have already encountered the function main and made use of printf from the standard library. We can of course make our own functions and header files. A function has the following layout:
return-type function-name ( argument list if necessary )
{
local-declarations;
statements ;
return return-value;
}
If return-type is omitted, C defaults to int. The return-value must be of the declared type. All variables declared within functions are called local variables, in that they are known only in the function to which they have been defined.
Some functions have a parameter list that provides a communication method between the function, and the module that called the function. The parameters are also local variables, in that they are not available outside of the function. The programs covered so far all have main, which is a function.
A function may simply perform a task without returning any value, in which case it has the following layout:
void function-name ( argument list if necessary )
{
local-declarations ;
statements;
}
Arguments are always passed by value in C function calls. This means that local copies of the values of the arguments are passed to the routines. Any change made to the arguments internally in the function is made only to the local copies of the arguments.
In order to change or define an argument in the argument list, this argument must be passed as an address. You use regular variables if the function does not change the values of those arguments. You MUST use pointers if the function changes the values of those arguments.
Let us learn with examples:
#include <stdio.h>
void exchange ( int *a, int *b )
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf(" From function exchange: ");
printf("a = %d, b = %d\n", *a, *b);
}
void main()
{
int a, b;
a = 5;
b = 7;
printf("From main: a = %d, b = %d\n", a, b);
exchange(&a, &b);
printf("Back in main: ");
printf("a = %d, b = %d\n", a, b);
}
And the output of this program will be displayed as follows:
From main: a = 5, b = 7
From function exchange: a = 7, b = 5
Back in main: a = 7, b = 5
Let us see another example. The following example uses a function called square which writes the square of the numbers between 1 and 10.
#include <stdio.h>
int square(int x); /* Function prototype */
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
printf("Square of %d is %d\n", counter, square(counter));
return 0;
}
/* Define the function 'square' */
int square(int x)
{
return x * x;
}
The output of this program will be displayed as follows:
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Square of 6 is 36
Square of 7 is 49
Square of 8 is 64
Square of 9 is 81
Square of 10 is 100
The function prototype square declares a function that takes an integer parameter and returns an integer. When the compiler reaches the function call to square in the main program, it is able to check the function call against the function's definition.
When the program reaches the line that calls the function square, the program jumps to the function and executes that function before resuming its path through the main program. Programs that do not have a return type should be declared using void. Thus Parameters to the function may be Pass By Value or Pass By Reference.
A Recursive function is a function that calls itself. And this process is called recursion.
Pass By Value Functions
The square function's parameters in the previous example are passed by value. This means that only a copy of the variable has been passed to the function. Any changes to the value will not be reflected back to the calling function.
The following example uses pass-by-value and changes the value of the passed parameter, which has no effect on the calling function. The function count_down has been declared as void as there is no return type.
#include <stdio.h>
void count_down(int x);
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
count_down(counter);
return 0;
}
void count_down(int x)
{
int counter;
for (counter = x; counter > 0; counter--)
{
printf("%d ", x);
x--;
}
putchar('\n');
}
The output of the program will be displayed as follows:
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
6 5 4 3 2 1
7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
Let us see another C Pass By Value Example to better understand it. The following example converts a number between 1 and 30,000 typed by the user into words.
#include <stdio.h>
void do_units(int num);
void do_tens(int num);
void do_teens(int num);
int main()
{
int num, residue;
do
{
printf("Enter a number between 1 and 30,000: ");
scanf("%d", &num);
} while (num < 1 || num > 30000);
residue = num;
printf("%d in words = ", num);
do_tens(residue/1000);
if (num >= 1000)
printf("thousand ");
residue %= 1000;
do_units(residue/100);
if (residue >= 100)
{
printf("hundred ");
}
if (num > 100 && num%100 > 0)
printf("and ");
residue %=100;
do_tens(residue);
putchar('\n');
return 0;
}
void do_units(int num)
{
switch(num)
{
case 1:
printf("one ");
break;
case 2:
printf("two ");
break;
case 3:
printf("three ");
break;
case 4:
printf("four ");
break;
case 5:
printf("five ");
break;
case 6:
printf("six ");
break;
case 7:
printf("seven ");
break;
case 8:
printf("eight ");
break;
case 9:
printf("nine ");
}
}
void do_tens(int num)
{
switch(num/10)
{
case 1:
do_teens(num);
break;
case 2:
printf("twenty ");
break;
case 3:
printf("thirty ");
break;
case 4:
printf("forty ");
break;
case 5:
printf("fifty ");
break;
case 6:
printf("sixty ");
break;
case 7:
printf("seventy ");
break;
case 8:
printf("eighty ");
break;
case 9:
printf("ninety ");
}
if (num/10 != 1)
do_units(num%10);
}
void do_teens(int num)
{
switch(num)
{
case 10:
printf("ten ");
break;
case 11:
printf("eleven ");
break;
case 12:
printf("twelve ");
break;
case 13:
printf("thirteen ");
break;
case 14:
printf("fourteen ");
break;
case 15:
printf("fifteen ");
break;
case 16:
printf("sixteen ");
break;
case 17:
printf("seventeen ");
break;
case 18:
printf("eighteen ");
break;
case 19:
printf("nineteen ");
}
}
and the output of the program will be as follows:
Enter a number between 1 and 30,000: 12345
12345 in words = twelve thousand three hundred and forty five
Call-by-reference
To make a function call-by-reference, instead of passing the variable itself, pass the address of the variable. The address of the variable can be taken by using the & operator. The following calls a swap function passing the address of variables instead of the actual values.
swap(&x, &y);
Dereferencing
The problem we have now is that the function swap has been passed the address rather than the variable, so we need to dereference the variables so that we are looking at the actual values rather than the addresses of the variables in order to swap them.
Dereferencing is achieved in C by using the pointer (*) notation. In simple terms, this means placing a * before each variable before using it in order that it refers to the value of the variable rather than its address. The following program illustrates passing-by-reference to swap two values.
#include <stdio.h>
void swap(int *x, int *y);
int main()
{
int x=6, y=10;
printf("Before the function swap, x = %d and y =
%d\n\n", x, y);
swap(&x, &y);
printf("After the function swap, x = %d and y =
%d\n\n", x, y);
return 0;
}
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
Let us see the output of the program:
Before the function swap, x = 6 and y = 10
After the function swap, x = 10 and y = 6
Functions may be recursive that is a function may call itself. Each call to itself requires that the current state of the function is pushed onto the stack. It is important to remember this fact as it is easy to create a stack overflow, i.e. the stack has run out of space to place any more data.
The following example calculates the Factorial of a number using recursion. A factorial is a number multiplied by every other integer below itself, down to 1. For example, the factorial of the number 6 is:
Factorial 6 = 6 * 5 * 4 * 3 * 2 * 1
Therefore the factorial of 6 is 720. It can be seen from the above example that factorial 6 = 6 * factorial 5. Similarly, factorial 5 = 5 * factorial 4, and so on.
The following is the general rule for calculating factorial numbers.
factorial(n) = n * factorial(n-1)
The above rule terminates when n = 1, as the factorial of 1 is 1. Let us try to better understand it with the help of example:
#include <stdio.h>
long int factorial(int num);
int main()
{
int num;
long int f;
printf("Enter a number: ");
scanf("%d", &num);
f = factorial(num);
printf("factorial of %d is %ld\n", num, f);
return 0;
}
long int factorial(int num)
{
if (num == 1)
return 1;
else
return num * factorial(num-1);
}
Let us see the output of the execution of this program:
Enter a number: 7
factorial of 7 is 5040
Memory Allocation in C
The C compiler has a memory allocation library, defined in malloc.h. Memory is reserved using the malloc function, and returns a pointer to the address. It takes one parameter, the size of memory required in bytes.
The following example allocates space for the string, "hello world".
ptr = (char *)malloc(strlen("Hello world") + 1);
The extra one byte is required to take into account the string termination character, '\0'. The (char *) is called a cast, and forces the return type to be char *.
As data types have different sizes, and malloc returns the space in bytes, it is good practice for portability reasons to use the sizeof operator when specifying a size to allocate.
The following example reads a string into the character array buffer and then allocates the exact amount of memory required and copies it to a variable called "ptr".
#include <string.h>
#include <malloc.h>
int main()
{
char *ptr, buffer[80];
printf("Enter a string: ");
gets(buffer);
ptr = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(ptr, buffer);
printf("You entered: %s\n", ptr);
return 0;
}
The output of the program will be as follows:
Enter a string: India is the best
You entered: India is the best
Reallocating Memory
It is possible many times while you are programming that you want to reallocate memory. This is done with the realloc function. The realloc function takes two parameters, the base address of memory you want to resize, and the amount of space you want to reserve and returns a pointer to the base address.
Suppose we have reserved space for a pointer called msg and we want to reallocate space to the amount of space it already takes up, plus the length of another string then we could use the following.
msg = (char *)realloc(msg, (strlen(msg) + strlen(buffer) + 1)*sizeof(char));
The following program illustrates the use of malloc, realloc and free. The user enters a series of strings that are joined together. The program stops reading strings when an empty string is entered.
#include <string.h>
#include <malloc.h>
int main()
{
char buffer[80], *msg;
int firstTime=0;
do
{
printf("\nEnter a sentence: ");
gets(buffer);
if (!firstTime)
{
msg = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(msg, buffer);
firstTime = 1;
}
else
{
msg = (char *)realloc(msg, (strlen(msg) +
strlen(buffer) + 1) * sizeof(char));
strcat(msg, buffer);
}
puts(msg);
} while(strcmp(buffer, ""));
free(msg);
return 0;
}
The output of the program will be as follows:
Enter a sentence: Once upon a time
Once upon a time
Enter a sentence: there was a king
Once upon a timethere was a king
Enter a sentence: the king was
Once upon a timethere was a kingthe king was
Enter a sentence:
Once upon a timethere was a kingthe king was
Releasing Memory
When you have finished with memory that has been allocated, you should never forget to free the memory as it will free up resources and improve speed. To release allocated memory, use the free function.
free(ptr);
Structures
As well as the basic data types, C has a structure mechanism that allows you to group data items that are related to each other under a common name. This is commonly referred to as a User Defined Type.
The keyword struct starts the structure definition, and a tag gives the unique name to the structure. The data types and variable names added to the structure are members of the structure. The result is a structure template that may be used as a type specifier. The following is a structure with a tag of month.
struct month
{
char name[10];
char abbrev[4];
int days;
};
A structure type is usually defined near to the start of a file using a typedef statement. typedef defines and names a new type, allowing its use throughout the program. typedef usually occur just after the #define and #include statements in a file.
The typedef keyword may be used to define a word to refer to the structure rather than specifying the struct keyword with the name of the structure. It is usual to name the typedef in capital letters. Here are the examples of structure definition.
typedef struct {
char name[64];
char course[128];
int age;
int year;
} student;
This defines a new type student variables of type student can be declared as follows.
student st_rec;
Notice how similar this is to declaring an int or float. The variable name is st_rec, it has members called name, course, age and year. Similarly,
typedef struct element
{
char data;
struct element *next;
} STACKELEMENT;
A variable of the user defined type struct element may now be declared as follows.
STACKELEMENT *stack;
Consider the following structure:
struct student
{
char *name;
int grade;
};
A pointer to struct student may be defined as follows.
struct student *hnc;
When accessing a pointer to a structure, the member pointer operator, -> is used instead of the dot operator. To add a grade to a structure,
s.grade = 50;
You could assign a grade to the structure as follows.
s->grade = 50;
As with the basic data types, if you want the changes made in a function to passed parameters to be persistent, you have to pass by reference (pass the address). The mechanism is exactly the same as the basic data types. Pass the address, and refer to the variable using pointer notation.
Having defined the structure, you can declare an instance of it and assign values to the members using the dot notation. The following example illustrates the use of the month structure.
#include <stdio.h>
#include <string.h>
struct month
{
char name[10];
char abbreviation[4];
int days;
};
int main()
{
struct month m;
strcpy(m.name, "January");
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n", m.name, m.abbreviation, m.days);
return 0;
}
The output of the program will be as follows:
January is abbreviated as Jan and has 31 days
All ANSI C compilers allow you to assign one structure to another, performing a member-wise copy. If we had month structures called m1 and m2, then we could assign the values from m1 to m2 with the following:
- Structure with Pointer Members.
- Structure Initializes.
- Passing a Structure to a Function.
- Pointers and Structures.
Structures with Pointer Members in C
Holding strings in a fixed size array is inefficient use of memory. A more efficient approach would be to use pointers. Pointers are used in structures in exactly the same way they are used in normal pointer definitions. Let us see an example:
#include <string.h>
#include <malloc.h>
struct month
{
char *name;
char *abbreviation;
int days;
};
int main()
{
struct month m;
m.name = (char *)malloc((strlen("January")+1) *
sizeof(char));
strcpy(m.name, "January");
m.abbreviation = (char *)malloc((strlen("Jan")+1) *
sizeof(char));
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n",
m.name, m.abbreviation, m.days);
return 0;
}
The output of the program will be as follows:
January is abbreviated as Jan and has 31 days
Structure Initializers in C
To provide a set of initial values for the structure, Initialisers may be added to the declaration statement. As months start at 1, but arrays start at zero in C, an extra element at position zero called junk, has been used in the following example.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
int main()
{
int counter;
for (counter=1; counter<=12; counter++)
printf("%s is abbreviated as %s and has %d days\n",
month_details[counter].name,
month_details[counter].abbreviation,
month_details[counter].days);
return 0;
}
And the output will be displayed as follows:
January is abbreviated as Jan and has 31 days
February is abbreviated as Feb and has 28 days
March is abbreviated as Mar and has 31 days
April is abbreviated as Apr and has 30 days
May is abbreviated as May and has 31 days
June is abbreviated as Jun and has 30 days
July is abbreviated as Jul and has 31 days
August is abbreviated as Aug and has 31 days
September is abbreviated as Sep and has 30 days
October is abbreviated as Oct and has 31 days
November is abbreviated as Nov and has 30 days
December is abbreviated as Dec and has 31 days
Passing Structures to Functions in C
Structures may be passed as a parameter to a function, just as any of the basic data types. The following example uses a structure called date that has is passed to an isLeapYear function to determine if the year is a leap year.
Normally you would only pass the day value, but the whole structure is passed to illustrate passing structures to functions.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
struct date
{
int day;
int month;
int year;
};
int isLeapYear(struct date d);
int main()
{
struct date d;
printf("Enter the date (eg: 11/11/1980): ");
scanf("%d/%d/%d", &d.day, &d.month, &d.year);
printf("The date %d %s %d is ", d.day,
month_details[d.month].name, d.year);
if (isLeapYear(d) == 0)
printf("not ");
puts("a leap year");
return 0;
}
int isLeapYear(struct date d)
{
if ((d.year % 4 == 0 && d.year % 100 != 0) ||
d.year % 400 == 0)
return 1;
return 0;
}
And the Execution of the program will be as follows:
Enter the date (eg: 11/11/1980): 9/12/1980
The date 9 December 1980 is a leap year
The following example dynamically allocates an array of structures to store students names and grade. The grades are then displayed back to the user in ascending order.
#include <string.h>
#include <malloc.h>
struct student
{
char *name;
int grade;
};
void swap(struct student *x, struct student *y);
int main()
{
struct student *group;
char buffer[80];
int spurious;
int inner, outer;
int counter, numStudents;
printf("How many students are there in the group: ");
scanf("%d", &numStudents);
group = (struct student *)malloc(numStudents *
sizeof(struct student));
for (counter=0; counter<numStudents; counter++)
{
spurious = getchar();
printf("Enter the name of the student: ");
gets(buffer);
group[counter].name = (char *)malloc((strlen(buffer)+1) * sizeof(char));
strcpy(group[counter].name, buffer);
printf("Enter grade: ");
scanf("%d", &group[counter].grade);
}
for (outer=0; outer<numStudents; outer++)
for (inner=0; inner<outer; inner++)
if (group[outer].grade <
group[inner].grade)
swap(&group[outer], &group[inner]);
puts("The group in ascending order of grades ...");
for (counter=0; counter<numStudents; counter++)
printf("%s achieved Grade %d \n”,
group[counter].name,
group[counter].grade);
return 0;
}
void swap(struct student *x, struct student *y)
{
struct student temp;
temp.name = (char *)malloc((strlen(x->name)+1) *
sizeof(char));
strcpy(temp.name, x->name);
temp.grade = x->grade;
x->grade = y->grade;
x->name = (char *)malloc((strlen(y->name)+1) *
sizeof(char));
strcpy(x->name, y->name);
y->grade = temp.grade;
y->name = (char *)malloc((strlen(temp.name)+1) *
sizeof(char));
strcpy(y->name, temp.name);
}
The execution of the output will be as follows:
How many students are there in the group: 4
Enter the name of the student: Anuraaj
Enter grade: 7
Enter the name of the student: Honey
Enter grade: 2
Enter the name of the student: Meetushi
Enter grade: 1
Enter the name of the student: Deepti
Enter grade: 4
The group in ascending order of grades ...
Meetushi achieved Grade 1
Honey achieved Grade 2
Deepti achieved Grade 4
Anuraaj achieved Grade 7
Union
A union allows you a way to look at the same data with different types, or to use the same data with different names. Unions are similar to structures. A union is declared and used in the same ways that a structure is.
A union differs from a structure in that only one of its members can be used at a time. The reason for this is simple. All the members of a union occupy the same area of memory. They are laid on top of each other.
Unions are defined and declared in the same fashion as structures. The only difference in the declarations is that the keyword union is used instead of struct. To define a simple union of a char variable and an integer variable, you would write the following:
union shared {
char c;
int i;
};
This union, shared, can be used to create instances of a union that can hold either a character value c or an integer value i. This is an OR condition. Unlike a structure that would hold both values, the union can hold only one value at a time.
A union can be initialized on its declaration. Because only one member can be used at a time and only one can be initialized. To avoid confusion, only the first member of the union can be initialized. The following code shows an instance of the shared union being declared and initialized:
union shared generic_variable = {`@'};
Notice that the generic_variable union was initialized just as the first member of a structure would be initialized.
Individual union members can be used in the same way that structure members can be used by using the member operator (.). However, there is an important difference in accessing union members.
Only one union member should be accessed at a time. Because a union stores its members on top of each other, it's important to access only one member at a time.
The union Keyword
union tag {
union_member(s);
/* additional statements may go here */
}instance;
The union keyword is used for declaring unions. A union is a collection of one or more variables (union_members) that have been grouped under a single name. In addition, each of these union members occupies the same area of memory.
The keyword union identifies the beginning of a union definition. It's followed by a tag that is the name given to the union. Following the tag are the union members enclosed in braces.
An instance, the actual declaration of a union, also can be defined. If you define the structure without the instance, it's just a template that can be used later in a program to declare structures. The following is a template's format:
union tag {
union_member(s);
/* additional statements may go here */
};
To use the template, you would use the following format:
union tag instance;
To use this format, you must have previously declared a union with the given tag.
/* Declare a union template called tag */
union tag {
int num;
char alps;
}
/* Use the union template */
union tag mixed_variable;
/* Declare a union and instance together */
union generic_type_tag {
char c;
int i;
float f;
double d;
} generic;
/* Initialize a union. */
union date_tag {
char full_date[9];
struct part_date_tag {
char month[2];
char break_value1;
char day[2];
char break_value2;
char year[2];
} part_date;
}date = {"09/12/80"};
Let us better understand it with the help of examples:
#include <stdio.h>
int main()
{
union
{
int value; /* This is the first part of the union */
struct
{
char first; /* These two values are the second part of it */
char second;
} half;
} number;
long index;
for (index = 12 ; index < 300000L ; index += 35231L)
{
number.value = index;
printf("%8x %6x %6x\n", number.value,
number.half.first,
number.half.second);
}
return 0;
}
And the output of the program will be displayed as follows:
c c 0
89ab ffab ff89
134a 4a 13
9ce9 ffe9 ff9c
2688 ff88 26
b027 27 ffb0
39c6 ffc6 39
c365 65 ffc3
4d04 4 4d
A practical use of a union in data recovery
Now let us see a practical use of union is data recovery programming. Let us take a little example. The following program is the small model of bad sector scanning program for a floppy disk drive (a: ) however it is not the complete model of bad sector scanning software.
Let us examine the program:
#include<dos.h>
#include<conio.h>
int main()
{
int rp, head, track, sector, status;
char *buf;
union REGS in, out;
struct SREGS s;
clrscr();
/* Reset the disk system to initialize to disk */
printf("\n Resetting the disk system....");
for(rp=0;rp<=2;rp++)
{
in.h.ah = 0;
in.h.dl = 0x00;
int86(0x13,&in,&out);
}
printf("\n\n\n Now Testing the Disk for Bad Sectors....");
/* scan for bad sectors */
for(track=0;track<=79;track++)
{
for(head=0;head<=1;head++)
{
for(sector=1;sector<=18;sector++)
{
in.h.ah = 0x04;
in.h.al = 1;
in.h.dl = 0x00;
in.h.ch = track;
in.h.dh = head;
in.h.cl = sector;
in.x.bx = FP_OFF(buf);
s.es = FP_SEG(buf);
int86x(0x13,&in,&out,&s);
if(out.x.cflag)
{
status=out.h.ah;
printf("\n track:%d Head:%d Sector:%d Status ==0x%X",track,head,sector,status);
}
}
}
}
printf("\n\n\nDone");
return 0;
}
Now let us see what its output will look like if there are bad sector in the floppy disk:
Resetting the disk system....
Now Testing the Disk for Bad Sectors....
track:0 Head:0 Sector:4 Status ==0xA
track:0 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:4 Status ==0xA
track:1 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:6 Status ==0xA
track:1 Head:0 Sector:7 Status ==0xA
track:1 Head:0 Sector:8 Status ==0xA
track:1 Head:0 Sector:11 Status ==0xA
track:1 Head:0 Sector:12 Status ==0xA
track:1 Head:0 Sector:13 Status ==0xA
track:1 Head:0 Sector:14 Status ==0xA
track:1 Head:0 Sector:15 Status ==0xA
track:1 Head:0 Sector:16 Status ==0xA
track:1 Head:0 Sector:17 Status ==0xA
track:1 Head:0 Sector:18 Status ==0xA
track:1 Head:1 Sector:5 Status ==0xA
track:1 Head:1 Sector:6 Status ==0xA
track:1 Head:1 Sector:7 Status ==0xA
track:1 Head:1 Sector:8 Status ==0xA
track:1 Head:1 Sector:9 Status ==0xA
track:1 Head:1 Sector:10 Status ==0xA
track:1 Head:1 Sector:11 Status ==0xA
track:1 Head:1 Sector:12 Status ==0xA
track:1 Head:1 Sector:13 Status ==0xA
track:1 Head:1 Sector:14 Status ==0xA
track:1 Head:1 Sector:15 Status ==0xA
track:1 Head:1 Sector:16 Status ==0xA
track:1 Head:1 Sector:17 Status ==0xA
track:1 Head:1 Sector:18 Status ==0xA
track:2 Head:0 Sector:4 Status ==0xA
track:2 Head:0 Sector:5 Status ==0xA
track:14 Head:0 Sector:6 Status ==0xA
Done
It may be a little bit difficult to understand functions and interrupts used in this program to verify the disk for bad sectors and resetting the disk system etc. but you need not to worry, we are going to learn all these things in BIOS and interrupt programming sections later in the next coming chapters.
File Handling in C
File access in C is achieved by associating a stream with a file. C communicates with files using a new data type called a file pointer. This type is defined within stdio.h, and written as FILE *. A file pointer called output_file is declared in a statement like
FILE *output_file;
The File Modes of fopen Function
Your program must open a file before it can access it. This is done using the fopen function, which returns the required file pointer. If the file cannot be opened for any reason then the value NULL will be returned. You will usually use fopen as follows
if ((output_file = fopen("output_file", "w")) == NULL)
fprintf(stderr, "Cannot open %s\n",
"output_file");
fopen takes two arguments, both are strings, the first is the name of the file to be opened, the second is an access character, which is usually one of r, a or w etc. Files may be opened in a number of modes, as shown in the following table.
File Modes |
r |
Open a text file for reading. |
w |
Create a text file for writing. If the file exists, it is overwritten. |
a |
Open a text file in append mode. Text is added to the end of the file. |
rb |
Open a binary file for reading. |
wb |
Create a binary file for writing. If the file exists, it is overwritten. |
ab |
Open a binary file in append mode. Data is added to the end of the file. |
r+ |
Open a text file for reading and writing. |
w+ |
Create a text file for reading and writing. If the file exists, it is overwritten. |
a+ |
Open a text file for reading and writing at the end. |
r+b or rb+ |
Open binary file for reading and writing. |
w+b or wb+ |
Create a binary file for reading and writing. If the file exists, it is overwritten. |
a+b or ab+ |
Open a text file for reading and writing at the end. |
The update modes are used with fseek, fsetpos and rewind functions. The fopen function returns a file pointer, or NULL if an error occurs.
The following example opens a file, tarun.txt in read-only mode. It is good programming practice to test the file exists.
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
Closing Files
Files are closed using the fclose function. The syntax is as follows:
fclose(in);
Reading Files
The feof function is used to test for the end of the file. The functions fgetc, fscanf, and fgets are used to read data from the file.
The following example lists the contents of a file on the screen, using fgetc to read the file a character at a time.
#include <stdio.h>
int main()
{
FILE *in;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
/* The last character read is the end of file marker so don't print it */
if (!feof(in))
putchar(key);
}
fclose(in);
return 0;
}
The fscanf function can be used to read different data types from the file as in the following example, providing the data in the file is in the format of the format string used with fscanf.
fscanf(in, "%d/%d/%d", &day, &month, &year);
The fgets function is used to read a number of characters from a file. stdin is the standard input file stream, and the fgets function can be used to control input.
Writing to Files
Data can be written to the file using fputc and fprintf. The following example uses the fgetc and fputc functions to make a copy of a text file.
#include <stdio.h>
int main()
{
FILE *in, *out;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
out = fopen("copy.txt", "w");
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
The fprintf function can be used to write formatted data to a file.
fprintf(out, "Date: %02d/%02d/%02d\n",
day, month, year);
Command Line Arguments with C
The ANSI C definition for declaring the main( ) function is either:
int main() or int main(int argc, char **argv)
The second version allows arguments to be passed from the command line. The parameter argc is an argument counter and contains the number of parameters passed from the command line. The parameter argv is the argument vector which is an array of pointers to strings that represent the actual parameters passed.
The following example allows any number of arguments to be passed from the command line and prints them out. argv[0] is the actual program. The program must be run from a command prompt.
#include <stdio.h>
int main(int argc, char **argv)
{
int counter;
puts("The arguments to the program are:");
for (counter=0; counter<argc; counter++)
puts(argv[counter]);
return 0;
}
If the program name was count.c, it could be called as follows from the command line.
count 3
or
count 7
or
count 192 etc.
The next example uses the file handling routines to copy a text file to a new file. For example the command line argument could be called as:
txtcpy one.txt two.txt
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *in, *out;
int key;
if (argc < 3)
{
puts("Usage: txtcpy source destination\n");
puts("The source must be an existing file");
puts("If the destination file exists, it will be
overwritten");
return 0;
}
if ((in = fopen(argv[1], "r")) == NULL)
{
puts("Unable to open the file to be copied");
return 0;
}
if ((out = fopen(argv[2], "w")) == NULL)
{
puts("Unable to open the output file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
Bitwise Manipulators
At a hardware level, data is represented as binary numbers. The binary representation of the number 59 is 111011. Bit 0 is the least significant bit, and in this case bit 5 is the most significant bit.
Each bit set is calculated as 2 to the power of the bit set. Bitwise operators allow you to manipulate integer variables at bit level. The following shows the binary representation of the number 59.
binary representation of the number 59 |
bit 5 4 3 2 1 0 |
2 power n 32 16 8 4 2 1 |
set 1 1 1 0 1 1 |
With three bits, it is possible to represent the numbers 0 to 7. The following table shows the numbers 0 to 7 in their binary form.
Binary Digits |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
The following table lists the bitwise operators that may be used to manipulate binary numbers.
Binary Digits |
& |
Bitwise AND |
| |
Bitwise OR |
^ |
Bitwise Exclusive OR |
~ |
Bitwise Complement |
<< |
Bitwise Shift Left |
>> |
Bitwise Shift Right |
Bitwise AND
The bitwise AND is True only if both bits are set. The following example shows the result of a bitwise AND on the numbers 23 and 12.
10111 (23)
01100 (12) AND
____________________
00100 (result = 4) |
You can use a mask value to check if certain bits have been set. If we wanted to check whether bits 1 and 3 were set, we could mask the number with 10 (the value if bits 1 and 3) and test the result against the mask.
#include <stdio.h>
int main()
{
int num, mask = 10;
printf("Enter a number: ");
scanf("%d", &num);
if ((num & mask) == mask)
puts("Bits 1 and 3 are set");
else
puts("Bits 1 and 3 are not set");
return 0;
}
Bitwise OR
The bitwise OR is true if either bits are set. The following shows the result of a bitwise OR on the numbers 23 and 12.
10111 (23)
01100 (12) OR
______________________
11111 (result = 31) |
You can use a mask to ensure a bit or bits have been set. The following example ensures bit 2 is set.
#include <stdio.h>
int main()
{
int num, mask = 4;
printf("Enter a number: ");
scanf("%d", &num);
num |= mask;
printf("After ensuring bit 2 is set: %d\n", num);
return 0;
}
Bitwise Exclusive OR
The bitwise Exclusive OR is True if either bits are set, but not both. The following shows the result of a bitwise Exclusive OR on the numbers 23 and 12.
10111 (23)
01100 (12) Exclusive OR (XOR)
_____________________________
11011 (result = 27) |
The Exclusive OR has some interesting properties. If you Exclusive OR a number by itself, it sets itself to zero as the zeros will remain zero and the ones can't both be set so are set to zero.
As a result of this, if you Exclusive OR a number with another number, then Exclusive OR the result with the other number again, the result is the original number. You can try this with the numbers used in the above example.
23 XOR 12 = 27
27 XOR 12 = 23
27 XOR 23 = 12
This feature can be used for encryption. The following program uses an encryption key of 23 to illustrate the property on a number entered by the user.
#include <stdio.h>
int main()
{
int num, key = 23;
printf("Enter a number: ");
scanf("%d", &num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
return 0;
}
Bitwise Compliment
The bitwise Compliment is a one's compliment operator that toggles the bit on or off. If it is 1, it will be set to 0, if it is 0 it will be set to 1.
#include <stdio.h>
int main()
{
int num = 0xFFFF;
printf("The compliment of %X is %X\n", num, ~num);
return 0;
}
Bitwise Shift Left
The Bitwise Shift Left operator shifts the number left. The most significant bits are lost as the number moves left, and the vacated least significant bits are zero. The following shows the binary representation of 43.
0101011 (decimal 43)
By shifting the bits to the left, we lose the most significant bit (in this case, a zero), and the number is padded with a zero at the least significant bit. The following is the resulting number.
1010110 (decimal 86)
Bitwise Shift Right
The Bitwise Shift Right operator shifts the number right. Zero is introduced to the vacated most significant bits, and the vacated least significant bits are lost. The following shows the binary representation of the number 43.
0101011 (decimal 43)
By shifting the bits to the right, we lose the least significant bit (in this case, a one), and the number is padded with a zero at the most significant bit. The following is the resulting number.
0010101 (decimal 21)
The following program uses the Bitwise Shift Right and Bitwise AND to display a number as a 16-bit binary number. The number is shifted right successively from 16 down to zero and Bitwise ANDed with 1 to see if the bit is set. An alternative method would be to use successive masks with the Bitwise OR operator.
#include <stdio.h>
int main()
{
int counter, num;
printf("Enter a number: ");
scanf("%d", &num);
printf("%d is binary: ", num);
for (counter=15; counter>=0; counter--)
printf("%d", (num >> counter) & 1);
putchar('\n');
return 0;
}
Functions for Binary – Decimal Conversions
The two functions given next are for Binary to Decimal and Decimal to Binary conversion. The function given next to convert a decimal number to corresponding binary number supports up to 32 – Bit Binary number. You can use this or program given before for conversion as per your requirements.
Function for Decimal to Binary conversion:
void Decimal_to_Binary(void)
{
int input =0;
int i;
int count = 0;
int binary [32]; /* 32 Bit, MAXIMUM 32 elements */
printf ("Enter Decimal number to convert into
Binary :");
scanf ("%d", &input);
do
{
i = input%2; /* MOD 2 to get 1 or a 0*/
binary[count] = i; /* Load Elements into the Binary Array */
input = input/2; /* Divide input by 2 to decrement via binary */
count++; /* Count how many elements are needed*/
}while (input > 0);
/* Reverse and output binary digits */
printf ("Binary representation is: ");
do
{
printf ("%d", binary[count - 1]);
count--;
} while (count > 0);
printf ("\n");
}
Function for Binary to Decimal conversion:
The following function is to convert any Binary number to its corresponding Decimal number:
void Binary_to_Decimal(void)
{
char binaryhold[512];
char *binary;
int i=0;
int dec = 0;
int z;
printf ("Please enter the Binary Digits.\n");
printf ("Binary digits are either 0 or 1 Only ");
printf ("Binary Entry : ");
binary = gets(binaryhold);
i=strlen(binary);
for (z=0; z<i; ++z)
{
dec=dec*2+(binary[z]=='1'? 1:0); /* if Binary[z] is
equal to 1,
then 1 else 0 */
}
printf ("\n");
printf ("Decimal value of %s is %d",
binary, dec);
printf ("\n");
}
Debugging and Testing
Syntax Errors
Syntax refers to the grammar, structure and order of the elements in a statement. A syntax error occurs when we break the rules, such as forgetting to end a statement with a semicolon. When you compile the program, the compiler will produce a list of any syntax errors that it may encounter.
A good compiler will output the list with a description of the error, and may provide a possible solution. Fixing the errors may result in further errors being displayed when recompiled. The reason for this is that the previous errors changed the structure of the program meaning further errors were suppressed during the original compilation.
Similarly, a single mistake may result in several errors. Try putting a semicolon at the end of the main function of a program that compiles and runs correctly. When you recompile it, you will get a huge list of errors, and yet it's only a misplaced semicolon.
As well as syntax errors, compilers may also issue warnings. A warning is not an error, but may cause problems during the execution of your program. For example assigning a double-precision floating point number to a single-precision floating point number may result in a loss of precision. It is not a syntax error, but could lead to problems. In this particular example, you could show intent by casting the variable to the appropriate data type.
Consider the following example where x is a single-precision floating point number, and y is a double-precision floating point number. y is explicitly cast to a float during the assignment, which would eliminate any compiler warnings.
x = (float)y;
Logic Errors
Logic errors occur when there is an error in the logic. For example, you could test that a number is less than 4 and greater than 8. That could not possibly ever be true, but if it is syntactically correct the program will compile successfully. Consider the following example:
if (x < 4 && x > 8)
puts("Will never happen!");
The syntax is correct, so the program will compile, but the puts statement will never be printed as the value of x could not possibly be less than four and greater than eight at the same time.
Most logic errors are discovered through the initial testing of the program. When it doesn't behave as you expected, you inspect the logical statements more closely and correct them. This is only true for obvious logical errors. The larger the program, the more paths there will be through it, the more difficult it becomes to verify that the program behaves as expected.
Testing
In software development process, errors can be injected at any stages during development. This is because of verification methods of earlier phases of development of software are manual. Hence the code developed during the coding activity is likely to have some requirement errors and design errors, in addition to errors introduced during the coding activity. During testing, the program to be tested is executed with a set of test cases, and the output of the program for the test cases is evaluated to determine if the programming is performing is expected.
Thus, testing is the process of analyzing a software item to detect the difference between existing and required conditions (i.e., bugs) and to evaluate the features of the software items. So, Testing is the process of analyzing a program with the intent of finding errors.
Some testing principles
- Testing can not show the absence of defects, only their presence.
- The earlier an error is made, the costlier it is.
- The later an error is detected, the costlier it is.
Now let us discuss some testing techniques:
White Box Testing
White box testing is a technique whereby all paths through the program are tested with every possible value. This approach requires some knowledge of how the program should behave. For example, if your program accepted an integer value between 1 and 50, a white box test would test the program with all 50 values to ensure it was correct for each, and then test every other possible value that an integer may take and test that it behaved as expected. Considering the number of data items a typical program may have, the possible permutations make white box testing extremely difficult for large programs.
White box testing may be applied to safety critical functions of a large program, and much of the rest tested using black box testing, discussed below. Because of the number of permutations, white box testing is usually performed using a test harness, where ranges of values are fed to the program rapidly through a special program, logging exceptions to the expected behavior. White box testing is sometimes referred to as structural, clear, or open box testing.
Black Box Testing
Black box testing is similar to white box testing, except rather than testing every possible value, selected values are tested. In this type of test, the tester knows the inputs and what the expected outcomes should be, but not necessarily how the program arrived at them. Black box testing is sometimes referred to as functional testing.
The test cases for black box testing are normally devised as soon as the program specifications are complete. The test cases are based on equivalence classes.
Equivalence Classes
For each input, an equivalence class identifies the valid and invalid states. There are generally three scenarios to plan for when defining equivalence classes.
If the input specifies a range or a specific value, there will be one valid state, and two invalid states defined. For example, if a number must be between 1 and 20, the valid state is between 1 and 20, there will be an invalid state for less than 1, and an invalid state greater than 20.
If the input excludes a range or specific value, there will be two valid states, and one invalid state defined. For example, if a number must not be between 1 and 20, the valid states are less than one and greater than 20, and the invalid state is between 1 and 20.
If the input specifies a Boolean value, there will be just two states, one valid and one invalid.
Boundary Value Analysis
Boundary value analysis only considers the values at the boundary of the inputs. For example, in the case of a number being between 1 and 20, the test cases may be 1, 20, 0, and 21. The thinking behind it is that if the program works as expected with these values, the other values will also work as expected.
The following table gives an overview of the typical boundaries you may want to identify.
Testing Ranges |
Input type |
Test Values |
Range |
- x[lower_bound]-1
- x[lower_bound]
- x[upper_bound]
- x[upper_bound]+1
|
Boolean |
|
Devising a Test Plan
Identify the equivalence classes, and for each class identify the boundaries. Having identified the boundaries for the class, write a list of valid and invalid values on the boundary, and what the expected behavior should be. The tester can then run the program with the boundary values, and indicate what happened when the boundary value was tested against the required outcome.
The following might be a typical test plan used to check for an age being entered where the acceptable values are in the range of 10 to 110.
Equivalence Class |
Valid |
Invalid |
Between 10 and 110 |
> 110 |
|
< 10 |
Having defined our equivalence class, we can now devise a test plan for, age.
Test Plan |
Value |
State |
Expected Result |
Actual Result |
10 |
Valid |
Continue execution to get name |
|
110 |
Valid |
Continue execution to get name |
|
9 |
Invalid |
Ask for age again |
|
111 |
Invalid |
Ask for age again |
|
The "Actual Result" column is left blank, as it will be completed when testing. If the result is as expected, the column will be ticked. If not, a comment indicating what occurred should be entered.
Page Modified on: 07/01/2022