C PROGRAMMING (PART – 1)
1.1 History of Programming Languages 1.2 C Standards 1.3 Facts about C 1.4. Program Structure 1.5 Program Development Life Cycle (PDLC) 1.6 C Program Execution in detail 1.7 Commands to execute a C Program using gcc 1.8 Errors in C
Chapter 2: Basic Constructs in C
2.1. Identifiers 2.2. Keywords 2.3. Variables 2.4. Data Types 2.5 Formatted Output and Input 2.6 Character Input and Output – Unformatted functions
3.1. Classification of operators 3.2 Evaluation of an expression 3.3 Sequence Point Operation 3.4. Ternary/Conditional operator
4.1. Looping 4.2 Selection 4.3 Nested control structures
Chapter 5: Solution for the Problem : counting
C is a general-purpose, high-level language that was originally developed by Dennis M.
Ritchie to develop the UNIX operating system at Bell Labs. C was originally first implemented
on the DEC PDP-11 computer in 1972.
1.1 History of Programming Languages
Links for reference:
https://cs.brown.edu/~adf/programming_languages.html
https://en.wikipedia.org/wiki/History_of_programming_languages
1.2 C Standards
A language should be understood the same way by compiler writers and programmers
all over the world. To facilitate this, the languages are standardized by international organizations
like the ISO. C has been standardized by the American National Standards Institute (ANSI) since
1989 and subsequently by the International Organization for Standardization (ISO).
The language ‘C’ has 4 historical land marks.
- K & R ‘C’ : defined by the seminal book on ‘C’ by Kernighan and Ritchie
- C 89
- C 99
- C 11
1.3 Facts about C
• C was invented to write an operating system called UNIX.
• C is a successor of B language which was introduced around the early 1970s.
• Applications include
o Adobe Systems: Includes Photoshop and Illustrator
o Mozilla: Internet Browser Firefox Uses C++.
o Bloomberg: Provides real time financial information to investors
o Callas Software: Supports PDF creation, optimization, updation tools and
plugins
• Today C is the most widely used and popular System Programming Language.
• Most of the state-of-the-art software have been implemented using C.
• Today's most popular Linux OS and RDBMS MySQL have been written in C
1.4. Program Structure
Let us summarize the characteristics of a program in ‘C’.
• The language is case sensitive. The uppercase and lowercase are distinguished.
• The program is immune to indentation. ‘C’ follows the free format source code
concept. The way of presentation of the program to the compiler has no effect on the
logic of the program.
However, we should properly indent the program so that the program becomes readable
for humans. When you prepare a new dish, I am sure you will also consider how you
present it to your kith and kin.
• The program has a well-defined entry point. Every program has to have an entry point
called main, which is the starting point of execution.
int main()
// execution begins here
{
printf("Hello Friends");
return 0;
}
• We add comments for documentation. There are two types of comments.
Line comment : starts with // and ends at the end of the line
Block comment : start with * and ends with * and can span multiple lines
Comments are ignored by the pre-processor.
• A program in ‘C’ normally has one or more pre-processor directives. These start with
the symbol #. One of the directives is “include”.
• #include <stdio.h>
The above line asks the translator to find the file whose name is stdio.h at some
location in our file system and read and expand it at that point in the ‘C’ code.
• A program in ‘C’ has to have a function called main. The main function returns an int.
The function may take a couple of arguments – will discuss this later. A function
definition has a return type, then the function name and declaration of parameters with
parentheses and a block.
• The main function is invoked(called) from some external agency – like our shell. It is
the duty of this function to tell the caller whether it succeeded or failed. So, by
convention, main returns 0 on success and non-zero on failure.
• printf is a library function to display. The first argument is always a string. In ‘C’,
string constants or literals are always enclosed in double quotes.
• A block contains a number of statements. Each statement ends in a semicolon. This
symbol ; plays the role of statement terminator.
1.5 Program Development Life Cycle (PDLC)
Phases involved in PDLC are Editing, Pre-processing, Compilation, Linking, Loading
and execution.
Program to be executed is first.c
1.6 C Program Execution in detail
Compiler used to execute the C program is gcc[GNU Compiler Collections] through
Command Line Interface in Windows or Terminal in Linux based Systems.
GCC is available in Linux based systems by default.
One of the Link for GCC installation on Windows through MinGW package:
https://www.youtube.com/watch?v=DHekr3EtDOA
using c99:
gcc -std=c99 program.c
using c11:
gcc -std=c99 program.c
Using __STDC_VERSION__ we can get the standard C version
The stages for a C program to become an executable are the following:
1. Editing: We enter the program into the computer. This is called editing. We save the source
program.
$ gedit first.c // press enter key
#include <stdio.h>
int main()
{
// This is a comment
/* Helps to understand the code
Used for readability
*/
printf(“Hello Friends”);
return 0;
}
// Save this code
2. Pre-Processing
In this stage, the following tasks are done:
a. Macro substitution – To be discussed in detail in Unit-5
b. Comments are stripped off
c. Expansion of the included files
The output is called a translation unit or translation.
To understand Pre-processing better, compile the above ‘first.c’ program using flag -E, which will
print the pre-processed output to stdout.
$ gcc -E first.c // No file is created. Output of pre – processing on the standard output-terminal
......
# 846 "/usr/include/stdio.h" 3 4
# 886 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));
......
# 916 "/usr/include/stdio.h" 3 4
# 2 "first.c" 2
int main()
{
printf("Hello Friends");
return 0;
}
......
In the above output, you can see that the source file is now filled with lots of information, but still
at the end of it we can see the lines of code written by us. Some of the observations are as below.
i) The first observation is that the comment that we wrote in our original code is not there. This
proves that all the comments are stripped off.
ii) The second observation is that beside the line ‘#include’ is missing and instead of that we see
whole lot of code in its place. So it is safe to conclude that stdio.h has been expanded and literally
included in our source file.
3. Compiling
Compiler processes statements written in a particular programming language and
converts them into machine language or "code" that a computer's processor uses. The translation
is compiled. The output is called an object file. There may be more than one translation. So, we
may get multiple object files.
gcc -c first.c
This command does pre-processing and compiling. Output of this command is first.o -> Object
file.
4. Linking
It is a computer program that takes one or more object files generated by a compiler
and combines them into one, executable program. Computer programs are usually made up
of multiple modules that span separate object files, each being a compiled computer program
This is the stage at which all the linking of function calls with their definitions are
done. Till stage, gcc doesn’t know about the definition of functions like printf(). Until the
compiler knows exactly where all of these functions are implemented, it simply uses a place-
holder for the function call. The definition of printf() is resolved and the actual address of the
function printf() is plugged in. We put together all these object files along with the predefined
library routines by linking. The output is called an image or a loadable image.
4. Executing the loadable image
Loader loads the image into the memory of the computer. This creates a process when
command is issued to execute the code. We execute or run the process. We get some results.
In ‘C’, we get what we deserve!
Below code is self explanatory.
#include<stdio.h>
int main()
{
printf("C standard is %d\n",__STDC_VERSION__); // It is a macro
return 0;
}
/*Use below commands to compile and execute the code. It doesn’t work for C89
C:\Users\codemummy>gcc program.c
C:\Users\codemummy>a
C standard is 201112
C:\Users\codemummy>gcc -std=c99 program.c
C:\Users\codemummy>a
C standard is 199901
C:\Users\codemummy>
1.7 Commands to execute a C Program using gcc
Step1: gcc -c 1_intro.c
outputs object file --- > intro.o
Step2: gcc 1_intro.o
outputs loadable image --- > a.out or a.exe
Step3: ./a.out and press enter in ubutnu. you get what you deserve!
Or
a.exe and press enter in windows, you get what you deserve!
Note: You can use -o option to create the loadable image with your own name.
gcc 1_intro.o -o myName // press enter
./myName // press enter to see the output
1.8 Errors in C
Error is an illegal operation performed by the programmer which results in abnormal
working of the program. Error is also known as bugs in the world of programming that may occur
unwillingly which prevent the program to compile and run correctly as per the expectation of the
programmer.
• Compile Time Error
• Link Time Error
• Run time Error
• Logical Error
Compile time Error
Deviation from the rules of the Language or violating the rules oof the language
results in CT Error. Whenever there is Compile Time Error, object file(.o file) will not
be created. Hence linking is not possible.
#include<stdio.h>
int main()
{
printf("hello friends); // " missing at the end
printf("%d",12/2) // ; missing at the end of statement
return 0;
}
Link time Error
Errors that occur during the linking stage of a C program and this usually
happens due to mismatch between Function call and function definitions. When you compile
the code, object file gets created. Sometimes with a warning. During linking, definition of
Printf is not available in included file. Hence Link Time Error. No loadable object will be
created due to this error.
#include<stdio.h>
int main()
{
Printf("hello friends"); // P is capital
printf("%d",12/2);
return 0;
}
Run time Error
Errors that occur during the execution of a C program and generally occur due
to some illegal operation performed in the program. When the code is compiled, object file(.o
file) will be created. Then during linking, executable will be created. When you run a loadable
image, Code terminates throwing an error.
Examples of some illegal operations that may produce runtime errors are:
Dividing a number by zero
Trying to open a file which is not created
Lack of free memory space
#include<stdio.h>
int main()
{
printf("hello friends");
printf("%d",12/0); // Observe 12/0
printf("bye friends");
return 0;
}
Logical Error:
Occurs when the code runs completely but the expected output is not same as
actual output.
Chapter 2: Basic Constructs in C
C program consists of various tokens and a token is either an identifier, a keyword,
variable, constant, a string literal, or a symbol. For example, the following C statement
consists of five tokens→ printf("Hello Friends");
The individual tokens are −
printf
(
"Hello Friends"
)
;
2.1. Identifiers
An identifier is a name used to identify a variable, function, or any other user-defined item. An
identifier starts with a letter A to Z, a to z, or an underscore '_' followed by zero or more letters,
underscores, and digits (0 to 9). C does not allow few punctuation characters such as @ and %
within identifiers. As C is a case-sensitive programming language, Manpower and manpower are
two different identifiers in C.
Here are some examples of acceptable identifiers −
mohd zara myname50 _temp j a23b9
abc move_name a_123 retVal
2.2. Keywords
The following list shows some of the reserved words in C. The keywords are identifiers
which have special meaning in C and hence cannot be used as constants or variables.
auto else long switch
break enum register typedef
case extern return union
char float short unsigned
const for signed void
continue goto sizeof volatile
default if static while
do int struct double
2.3. Variables
This is the most important concept in all languages. Variable is a name given to a
storage area that our programs can manipulate. It has a name, a value, a location and a type.
It also has something called life, scope, qualifiers etc. We will discuss the second part later in
the course.
Sometimes, the runtime makes variables with no names. These are called temporary variables.
We cannot access them by name as we have no name in out vocabulary. A variable has a type.
In ‘C’, type should be specified before a variable is ever used. We declare the variable with
respect to type before using it. We cannot declare the variable more than once.
Example:
int a; double b;
The type of a variable can never change during the program execution. The type decides what
sort of values this variable can take and what operations we can perform. Type is a compile
time mechanism. The size of a value of a type is implementation dependent and is fixed for a
particular implementation.
We can initialize a variable at the point of declaration. An uninitialized variable within a block
has some undefined value. ‘C’ does not initialize by any default value.
int c = 100;
int d; // undefined value !!
int c; // declaration again, throws an Error
A variable can be assigned a value later in the code.
c = 200;
d = 300;
Assignment is considered an expression in ‘C’ .
2.4. Data Types
The amount of storage to be reserved for the specified variable.
Significance of data types are given below:
1) Memory allocation
2) Range of values allowed
3) Operations that are bound to this type
4) Type of data to be stored
Data types are categorized into primary and non-primary (secondary) types.
Primary ---> int, float, double, char
Secondary--> Derived and User-defined types
Derived-->Arrays
User-defined--> struct, union, enum, typedef
Data types can be extended using qualifiers – No much discussion
---> Size Qualifiers
---> Sign Qualifiers
The size of a type – size required to represent a value of that type – can be found using an
operator called sizeof. The sizeof a type depends on the implementation. We should never
conclude that the size of an int is 4 bytes. Can you answer this question – How many pages a
book has? What is the radius of Dosa we get in different eateries?
The size of a type is decided on an implementation based on efficiency.
C standards follow the below Rules.
sizeof(short int)<=sizeof(int)<=sizeof(long int)<=sizeof(long long int)<=
sizeof(float)<=sizeof(double)<=sizeof(long double)
2.5 Formatted Output and Input
For interactive output and input, printf()[formatted output]
scanf()[formatted input] functions are used.
2.5.1 Formatted Output function in C
Let us understand the output function printf in detail. The C library function printf()
sends formatted output to stdout. A predefined function in "stdio.h" header file. By using this
function, we can print the data or user defined message on console or monitor. On success, it
returns the number of characters successfully written on the output. On failure, a
negative number is returned.
int printf(const char *format, ...)
• printf("hello Friends");
The first argument to printf is a string. The output depends on this string. String literals
in ‘C’ are enclosed in double quotes.
• printf("hello ", "friends"); // hello
Output depends on the first string. As there is no interpretation of the second
parameter, it would not appear in the output.
• printf("hello %s\n", "friends\n"); // hello world
The presence of %s in the first string makes the function printf look for the next
parameter and interpret it as a string.
• printf("%s %s %s %s\n", "one", "two", "three", "four");
The function printf takes variable number of arguments. All arguments are
interpreted as the number of %s matches the number of strings following the first
string.
• printf("%s %s %s %s\n", "one", "two", "three");
NO! we are in for trouble. There is no argument for the 4th %s in the format string. So,
printf tries to interpret as a string. If we are lucky, the program will crash. We have an
“undefined behaviour”. C does no checking at runtime!
• printf("%5d and %5d is %6d\n", 20, 30, 20 + 30);// Discussed in detail: Format String
Arguments to printf can be expressions – Then the expressions are evaluated and
their values are passed as arguments.
%d : indicates to printf to interpret the next parameter as an integer.
%5d : tells printf to display the integer using 5 character width.
• int a = 20; int n = printf("%d\n",a); // printf executes and returns # of characters
successfully printed on the terminal
printf(“n is %d”,n);
// 2
• printf("what : %d\n", 2.5);
GOD if any should help the programmer who writes such code!! undefined behaviour.
Let us digress to discuss the last statement in detail.
A few points to note about totally compiled languages like ‘C’.
• There is no translator at runtime in ‘C’.
• In ‘C’, type is a compile mechanism and value is a runtime mechanism. As the goal of
‘C’ is efficiency, only values are stored at runtime. There is no way to infer the type by
looking at the bit pattern.
• All the code that executes at runtime should be compiled and cannot be added at
runtime.
Let us look at our present example.
• printf("what : %d\n", 2.5);
The function printf takes varying number of arguments. In such cases, the compiler cannot
match arguments and parameters for type. So, the compiler might emit some warning (if it
knows what printf expects) – but cannot throw errors.
As the compiler does not know that 2.5 should be converted to an integer, it does not do any
conversion. So, the compiler puts the value of 2.5 the way it is expected to be stored – as per
the standard IEEE 754. This standard uses what is called mantissa exponent format to store
fractional (floating point) values.
At runtime, printf tries to interpret the bit pattern as an integer when it encounters the format
%d. At runtime, no conversion can occur – we do not even know that what is stored as a bit
pattern is a floating point value – Even if we know, there is no way to effect a conversion as
we do not have a compiler at that point. We end up getting some undefined value.
So, in ‘C’, you get what you deserve.
Format string
Used for the beautification of the output. The format string is of the form
% [flags] [field_width] [.precision] conversion_character
where components in brackets [] are optional. The minimum requirement is % and a
conversion character (e.g. %d).
Below conversion characters for each type of data.
%d : dec int
%x : hex int
%o : oct int
%f : float
%c : char
%p : address
%lf : double
%s : string – [Will be discussed in detail in Unit-2]
float c = 2.5;
printf("%4.2f\n",c); // width.precission , f is type
printf("%2.1f\n",c); // data will not be lost
printf("%-5d %d\n",16,b); - for left justification
Escape sequences
Represented by two key strokes and represents one character.
\n newline -- move cursor to next line
\t tab space
\r carriage return -- Move cursor to the beginning of that line
\a alarm or bell
\" double quote
\' single quote
\\ black slash
\b back space
printf("rama\nkrishna\bhaama\n"); // rama
krishna
bheema
printf("rama\rkri\rbh"); // bhia \r works differently in different
platforms. We will not discuss more about this
printf("rama\tbheema\n"); // rama bheema
printf("\"mahabharatha\""); // "mahabharatha"
printf("rama\b\bbheema"); // rabheema
2.5.2 Formatted Input function in C
scanf like printf takes a format string as the first argument. The input should match
this string.
int scanf( const char *format, ... );
where
int (integer) is the return type
format is a string that contains the type specifier(s).
"..." (ellipsis) indicates that the function accepts a variable number of arguments; each
argument must be a memory address where the converted result is written to. On success, the
function writes the result into the arguments passed.
• scanf( "%d%d", &var1, &var2 ); // & - address operator is compulsory in scanf
for all primary types
When the user types, 23 11, 23 is written to var1 and 11 is to var2.
• scanf("%d,%d", &a, &b);
scanf has a comma between two format specifiers, the input should have a comma
between a pair of integers.
• scanf("%d%d\n", &a, &b);
It is not a good practice to have any kind of escape sequence in scanf. In the above
code, it expects two integers. Ex: 20 30. Then if you press enter key, scanf does not
terminate. You need to give some character other than white space.
The function returns the following value:
>0 — The number of items converted and assigned successfully.
0 — No item was assigned.
<0 — Read error encountered or end-of-file (EOF) reached before any assignment was
made.
• n = scanf("%d",&a); // If user enters 20, a becomes 20 and 1 is returned by the
function.
n = scanf(“%d,%d”,&a,&b); //If user enters 20 30, a becomes 20, value of b is
undefined and 1 is returned by the function.
2.6 Character Input and Output – Unformatted functions
A character in programming refers to a single symbol. The characters in ‘C’
have no relationship with characters in the plays of Shakespeare even though those characters
utter characters we could process in programming! A character in ‘C’ is like a very small
integer having one of the 256 possible values. It occupies a single byte. We code English
alphabets A as 65, B as 66, Z as 90, a as 97 and so on in a coding scheme called ASCII.
To read a character from the keyboard, we could use scanf(“%c”, &x);
We could also use x = getchar();
We prefer the second as it is developed only for handling a single char and therefore more
efficient even though it is not generic.
Similarly we prefer putchar(x) over printf(“%c”, x);
This code shows how to read two characters and display them.
int main()
{
char ch = 'p'; // ch is a variable of type char and it can store only one character at a time.
//Value for a character variable must be within single quote. 'p'
printf("Ch is %c\n",ch); // p
//char ch1 = 'pqrs'; // TERRIBLE CODE
printf("Ch is %c\n",ch); // s
/*
char x; char y;
scanf("%c", &x);
scanf("%c", &y);
printf("x:%c y:%c\n", x, y);
*/
x = getchar(); y = getchar();
putchar(x);
putchar(y); printf("%d", y);
return 0;
}
// If I enter P<newline>, x becomes P and y becomes newline
// scanf and printf : generic; not simple
// getchar and putchar : not generic; read /write a char; simple
// if I enter p<space>q, q will not be stored in y. Only space is stored. This can be avoided
using fflush(stdin) function between two getchar function calls. This function clears the key
board buffer. fflush(stdin) - windows
//__fpurge(stdin) - ubuntu. include <stdio_ext.h> in Linux based systems.
Let us try to read a line and display.
char ch;
while(ch != '\n')
{
ch = getchar();
putchar(ch);
}
This is a terrible code. The first time we enter the loop, ch is not initialized. If you are lucky,
the loop is never entered!
In these cases, we require reading before the loop and reading at the end of the body of the
loop.
ch = getchar();
while(ch != '\n')
{
putchar(ch); }
Infinite loop unless the first character is new line!!
ch = getchar();
while(ch != '\n')
{
putchar(ch);
ch = getchar();
}
This is fine. But we can as well use assignment expression in the while. This is ‘C’ code!!
while( (ch = getchar()) != '\n')
{
putchar(ch);
}
putchar('\n');
A few points to observe. The input from the file or the keyboard is stored in something called
the buffer. That is transferred to getchar only after enter key is pressed.
An operator is a symbol that tells the compiler to perform specific mathematical
or logical functions. Operator is a symbol used for calculations or evaluations
3.1. Classification of operators
Based on the number of operands.
1) Unary
2) Binary
3) Ternary
1) Unary
+, -, ++, --, sizeof , & , *
2) Binary
+, -, *, /, %
3) Ternary
?:
Based on the operation on operands. Few of these are self-explanatory. We will see few others.
1) Arithmetic Operators
+, -, *, /, %
2) Increment and Decrement Operators
++, --
3) Relational
==, !=, >=, >, <=, <
4) Logical
&&, ||, !
5) Bitwise
&, |, ^, >>, <<
6) Address Operator and Dereferencing Operator
&, *
7) Assignment
=
8) Short-hand
+=, -=, *=, /=, %=
9) misc operators
sizeof
1) Arithmetic operators
There is no exponentiation operator.
All are binary operands – require two operands.
+ and – can also be used as unary operators – in such case, the operator is prefixed – appears to
the left of the operand.
If the operands are int for /, the result is int quotient obtained truncation of the result. The
operator % is defined on integral types only.
If the operands are mixed types(int and double) in operations + - * /, the result is of type
double.
2) Increment and Decrement Operators
There are two types of Increment (++) and Decrement (--) Operators.
1. Pre - increment and Pre-decrement operators: First increment or decrement the value and then use it in the expression.
2. Post - increment and Post - decrement operators: First use the value in the
expression and then increment or decrement.
When ++ and -- are used in stand-alone expression, pre and post increment and decrement
doesn't make difference. Refer the below code which demonstrate stand-alone expressions
#include<stdio.h>
int main()
{
int i=5;
int j=5;
printf("Beginning i value is %d\n",i); //5
printf("Beginning j value is %d\n",i); //5
++i; // i++;
printf("Later i value is %d\n",i); //6
j--; // --j;
printf("Later j value is %d\n",j); //4
return 0;
}
Refer the below code which demonstrates the usage of ++ and -- in an expression.
#include<stdio.h>
int main()
{
int a=34; int b;
printf("Beginning a value is %d\n",a); // 34
b=a++; // b is 34 and a is incremented to 35
// b=++a; // First increment and then use the value in the expression
// b is 35 and a is 35
printf("b is %d and a is %d\n",b,a);
// same works for pre and post decrement operators
}
3) Relational operators
< > <= >= == !=
The result of relational operation is 1 or 0 – 1 stands for true and 0 for false.
The relational operators should not be cascaded – not logically supported in C.
5 == 5 == 5 is false !!
5 == 5 == 5 becomes 1 == 5 ; this becomes 0 !!
printf(“%d”, 5 == 5 == 5); // results in 0
4) Logical operators
! stands for not
&& stands for and
|| stands for or.
In ‘C’, 0 is false and any non-zero value is true.
C follows short circuit evaluation. Evaluation takes place left to right and evaluation stops as
soon as the truth or falsehood of the expression is determined.
&& and || are sequence points – Explained in detail in the later part
5) Bitwise operators
<< >> & | ^ ~
The first 5 are binary operators and the last one is unary operator.
These operators are used on unsigned int only.
Common bitwise operations:
* multiply variable n by 2
n << 1
* check whether n is odd
n & 1
* swap two variables a b
a = a ^ b;
b = a ^ b;
a = a ^ b;
* check whether ith bit in variable n is 1
n & (1 << I) : 0 implies not set and non 0 implies set
* set the ith bit in n
n = n | 1 << i;
* clear the ith bit in n
n = n & ~(1 << I)
6) Address Operator and Dereferencing Operator
Address Operator - &
Dereferencing Operator - *
This block of code also describe Lvalue and Rvalue concept:
int a = 10;
int *p = &a;
printf("pointer to a : %p value \n", p, *p); // hex number 10
*p = 30;
printf("a : %d\n", *p); // a: 30
int b = *p;
printf("b : %d\n", b); // b: 30
int b = 20;
p = &b;
printf("pointer to b : %p value \n", p, *p); // hex number 20
The variable a is of int type and (int *p;) p is a pointer to int. We always read a declaration
right to left. A pointer variable of some particular type can hold the address of a variable of the
same type.
& : in unary form – gives the address of the variable.
p = &a; assigns the address of a to p.
If we assume the address of a as 0x5555, then &a is stored in p as p is a pointer to integer
variable. Refer the below diagram.
*p ---> value at p ---> value at 0x5555 ----> 10 ----> a
We can get back a through p by using * : in unary form - dereferencing operator.
*p is same as a.
We can change a by assigning to *p.
*p = 30;
We can also change p itself by assigning address of b to p.
p = &b;
We talk about l-value and r-value with respect to assignment operator =.
r-value refers to whatever that could occur to the right of assignment operator.
L-value refers to whatever that could occur to the left of assignment operator.
A constant has only r-value. An initialized variable has both l and r value. An expression of the
form a + b is only a r-value. The only operator which gives a value back is the dereferencing
operator *
3.2 Evaluation of an expression
An expression consists of operands and of operators. How is an expression
evaluated? There are two parts in it.
1. Evaluation of operands:
This is fetching the operands from the memory of the computer to the registers of the
CPU. All the evaluations happen within the CPU. This order is not defined.
The idea is to allow the compiler to optimize fetching of the operands. The compiler may
generate code such a way that if an operand is used more than once, it may get fetched only
once.
2. Evaluation of operators:
This follows the rules of precedence and if more than one operator has the same level
of precedence, follows the rules of association.
Link for reference: http://web.cse.ohio-state.edu/~babic.1/COperatorPrecedenceTable.pdf
Does the first rule have any effect on our programs? Yes. If affects our programs if the
expressions we use are not pure and have side effects.
All assignments have side effects.
Let us consider an example.
a = 10;
b = a * a++;
The value of a++ is the old value of a and therefore 10. What is the value of the leftmost a? Is
the value evaluated before incrementing or after incrementing? It depends on the compiler and
the compiler options. So value of leftmost a is undefined. So, value of b is undefined.
// 10 * 10 => 100
// 11 * 10 => 110
// undefined; depends on the order of evaluation of operands
// bad code
3.3 Sequence Point Operation
How do we write safe and proper code in ‘C’ if operators have side effects and
the variable values are not defined in such cases? The language ‘C’ also specifies points in the
code beyond which all these effects will definitely be complete. These are called sequence
points.
// a = 10;
// a + a++ undefined
// to support short ckt evaluation, && becomes a sequence point
// expression before &&, || will be completely evaluated.
// a++ == 10 && a == 11 // ok; will be true 1
a + a++ is undefined as the value of left a is not defined.
a++ == 10 && a == 11 will be true.
The value a++ is 10 and a becomes 11 before the evaluation moves across the sequence point
&&.
3.4. Ternary/Conditional operator
?: - Conditional Operator
Requires three operands.
E1 ? E2 : E3
where E1, E2 and E3 are expressions.
The expression E1 is first evaluated. If it is true, then the value of the expression is E2 else it is
E3.
? of this expression acts like a sequence point.
Below code is self-explanatory.
int a = 10; int b = 20;
printf("%d\n",(a>b)?a:b); //20
(a>b)? printf("%d",a):printf("%d",b); //20
C programming language supports a few looping and a few selection structures.
It may also support some unconventional control structures.
1) Looping structure:
- while statement
- for statement
- do … while statement
2) Selection structure:
- if statement
- switch statement
4.1. Looping
Let us start the discussion with the while statement. The following statement captures
the structure of the while statement.
The while statement starts with the keyword while and is followed by an expression in
parentheses and is followed by a statement or a block.
The expression of the while is evaluated. It could be non-zero or zero. If it is non-zero, the
body of the while is executed and again the control comes back to the expression of the while.
If the expression of the while is false, the loop is exited.
It is a good programming practice to always use a block even if only one statement need be in
the loop.
The body of the loop is executed 0 or more times as the condition is checked top tested.
/<while stat>::= while (<expr>) [<stat>|<block>]
// expr : 0 is false; not 0 is true
//no data structure
// no indentation
// body: single statement;
// # of statements : grouped under { }
// top testing; execute body 0 or more times; conditional looping structure
// version 1
int n = 5;
while(n)
printf("%d ", n);
n = n – 1;
This is an infinite loop as n remains to be 5 – is always true!
// version 2
// terrible
int n = 5;
while(n){printf("%d ", n);
n = n – 1;}
This would display 5 4 3 2 1. But the program layout does not indicate the logic structure of
the program. Always indent the code to indicate the logic structure.
// version 3
// indent your program
// always use a block
int n = 5;
while(n)
{
printf("%d ", n);
n = n - 1;
}
// output : 5 4 3 2 1 This is nice.
// version 4
int n = 5;
while(n)
{
printf("%d ", n--); // 5 4 3 2 1
}
Observe the value of n-- is the old value of n. When n is 1, the value of n becomes 0 but the
decrement operator returns 1.
// version 5
int n = 5;
while(n)
{
printf("%d ", --n); // 4 3 2 1 0
}
Here, pre-decrement operator returns the changed value.
// version 6
int n = 5;
while(n--)
{
printf("%d ", n); // 4 3 2 1 0
}
Observe that the old value of n is used to check whether the condition is true or false and by
the time, the body of the loop is entered, n would have the decremented value. The expression
of while acts like a sequence point.
// version 7
int n = 5;
while(--n)
{
printf("%d ", n); // 4 3 2 1
}
In this case, the decremented value is checked for the truth of the while expression.
Compare the last two cases. In the first case, the loop is executed n times and in the second the
loop is executed n – 1 times. In the first case, the value of n is -1 and in the second case, it is 0.
// version 8
int n = 5;
int f = 1;
while(n--)
{
f *= n;
}
printf("what : %d\n", f);
The loop is executed n times. Each time, we multiply f with n. Would this find n power n? Or
is it n!? You find out it yourself.
Our next requirement is to find the greatest common divisor(GCD) of given two
numbers. How do we proceed to solve this problem?
Let us make our first attempt. Clearly the greatest common divisor of two numbers cannot be
greater than the smaller of the two numbers.
Let us start our search with the smaller of the two numbers.
factor = (a < b) ? a : b;
We shall check if the factor divides both. If it does, our search is over. Otherwise, we shall
decrease factor by 1 and try again.
while (! (a % factor == 0 && b % factor == 0))
{
--factor;
}
A few questions to think.
● Can we rewrite the expression of the while using DeMorgan Theorem?
● Is this loop guaranteed to terminate?
● How many times will the loop execute if the numbers are relatively prime?
● Can we make the program more efficient?
Instead of decreasing factor by 1, can we conceptually decrease by a bigger size. This
algorithm is called the Euclid’s algorithm – supposed to be the oldest algorithm ever known.
Let us have a look at this program.
while (a != b)
{
if(a > b)
{
a -= b;
}
else
{
b -= a;
}
}
This algorithm states that if the two numbers are equal, that number itself is the GCD.
Otherwise subtract the smaller from the bigger and repeat the exercise. This would definitely
converge to the GCD faster than the earlier algorithm.
In this program, we are also using if statement.
If is followed by an expression within parentheses and then a statement or a block and then
optionally followed by else and then a statement or a block.
If the expression of if is true we execute the block following if, otherwise execute the else part
if any. It is always preferred to use a block even if there is a single statement in the if and else
portions.
// selection:
// <if stat> ::= if (<expr>) <stat>|<block>
// <if stat> ::= if (<expr>) <stat>|<block> else <stat>|<block>
Division is repeated subtraction. Can we make this algorithm faster by using division in stead
of subtraction. Here is the next version.
rem = a % b;
while(rem != 0)
{
a = b;
b = rem;
rem = a % b;
}
Divide a by b. If the remainder is 0, then b is the GCD. Otherwise, replace a by b and b by the
remainder and repeat.
A point for you to think. Would this work if b is greater than a?
Observe one other point in this code. We have the statement rem = a % b; repeated before the
loop and at the end of the body of the loop. This acts like a guard. Can we avoid repeating the
code? If for some reason, we change the code, we should remember to change at both the
places.
Here the ‘C’ way of writing the code.
while(rem = a % b)
{
a = b;
b = rem;
rem = a % b;
}
We can have assignment expression as the expression of the loop. The loop is exited when rem
becomes 0.
Our next requirement is to find the sum of numbers from 1 to n.
We can use the following while loop.
// n is a variable given small value.
int i = 1;
int sum = 0;
while(i <= n)
{
sum += i++;
}
This looping construct has clearly some initialization, some processing and some modification
at the end of the loop. In such cases, we may want to use the for statement.
// <for stmt>:: for(e1; e2; e3) <block>|<stmt>
// e1, e2, e3 : expressions
// e1 : initialization
// e2 : condition
// e3 : modification
For loop in Detail:
e1 is executed once. e2 is checked for condition. If it is non-zero, execute the body of
the loop and then execute e3. Again, check for e2 and thus the process continues till e2 results
in Zero Value. Once e2 is Zero, come out of the loop and execute the statement outside the
body of the loop.
The semantics of the for statement is same as the while statement given below.
e1;
while(e2)
{
<block>|<stmt>
e3;
}
Few questions to Answer:
• Can you have initialization outside the for loop?
• Can you skip condition in for loop?
• Can you have only ;; in for loop?
What does this code? Why it is wrong?
int sum = 0;
// wrong code
for(int i = 1; i <= n; sum += i)
{
++i;
}
Observe closely. You will find that the summation starts from 2 and not from 1. We will also
end up adding n + 1. Check the way a for statement executes.
This is the corrected code.
int sum = 0;
for(int i = 1; i <= n; ++i)
{
sum += i;
}
‘C’ for statement is same as the ‘C’ while statement – we say they are isomorphic. It is a
question of choice and style while writing programs in ‘C’.
Next our requirement is to find what is called the digital root of a given number. The idea is to
add the digits of a given number. If that sum exceeds a single digit, repeat the operation until
the sum becomes a single digit. This is useful in what is called as parity checking.
for(s = 0; n; n /= 10)
{
s += n % 10;
}
This loop finds the sum of digits of n. The result is in s and n would have become 0. If s
exceeds 9, we would like to copy s to n, repeat this operation.
Can we put this code under a while(s > 9) { <this code> }?
The answer is a clear NO as the value of s is not initialized until we enter the inner loop? Shall
we initialize to 0? Then the loop itself is not entered. How about making s 10? Looks very
unnatural.
Can you realize here that the sum becomes available only after doing the summation once? We
require in this case, a bottom tested looping structure which executes the code at least once.
Here is the solution.
do
{
for(s = 0; n; n /= 10)
{
s += n % 10;
}
n = s;
} while(s > 9);
// ; at the end of do - while is compulsory
4.2 Selection
Let us turn our attention to the selection structure. We would to classify triangles given
the 3 sides (which do form a triangle) as equilateral, isosceles or scalene. This is one possible
solution.
int count = 0;
scanf("%d %d %d", &a, &b, &c);
if(a == b) ++count;
if(b == c) ++count;
if(c == a) ++count;
if(count == 0) printf("scalene\n");
if(count == 3) printf("equi\n");
if(count == 1) printf("iso\n");
Compare every pair of sides and increment the count each time – initialized to 0 on start.
Can the count be 2?
Observe a few points here.
● We are comparing integers (integral values)
● We are comparing a variable(an expression) with constants
● We are comparing for equality (no > or < )
● In all comparisons, the same variable is used.
In such cases, we may use switch statement.
switch(count)
{
case 0: printf("scalene\n"); break;
case 3: printf("equi\n"); break;
case 1: printf("iso\n"); break;
}
The value of count is compared with case clauses. This comparison decides the entry into the
switch and not exit from there. To avoid the code fall through, we use break statement. The
rule of ‘C’ is “break if switch”.
We can also use default to capture when all other comparisons fail.
switch(count)
{
case 0: printf("scalene\n"); break;
case 3: printf("equi\n"); break;
default : printf("iso\n"); break;
}
These switch statements are more efficient compared normal if statements. Note that not all
nested if statements can become switch statements.
4.3 Nested control structures
We may have loops and selection nested. Here is an example to generate all Armstrong
numbers between 0 and 999. The sum of cubes of digits is same as the number.
In this example, we have a loop on hundredth digit, a loop on tenth digit and a loop on unit
digit. We cube the digits, find the sum, form the number using these digits and compare the
sum with the number formed.
This program shows how not to write programs.
for(h = 0; h < 10; ++h)
{
for(t = 0; t < 10; ++t)
{
for(u = 0; u < 10; ++u)
{
hc = h * h * h;
tc = t * t * t;
uc = u * u * u;
n = h * 100 + t * 10 + u;
s = hc + tc + uc;
if(n == s)
{
printf("%d\n", n);
}
}
}
}
Think how many multiplications do we do for cubing? Should we repeatedly evaluate cube of t
in the inner loop when it is not changing. Rearrange the code. Put the statements in the right
blocks. You will be able to reduce the number of multiplications for cubing to approximately
1/3rd of what it is now.
Chapter N: Solution for the Problem
We are required to find the number of characters, number of words and number
of lines in an user input and in a given file. We want to simulate a program in Unix command
called wc – word count .
We have not learnt yet how to play with files in our programs. It is a bit too
early in this course. We do know how to read from the keyboard. Can the operating system
open a file for me and make it logically available on the keyboard? It can. This concept is
called input redirection. When we run our command cmd from the unix shell and specify
filename on the same line, our program cmd reads from the file whenever it reads from the
keyboard.
input redirection:
$ cmd <filename
It is similarly possible to collect the output of the program which would appear on the screen
in a file using output redirection.
output redirection:
$ cmd >filename
To solve this problem stated, we should know
a) how to read and display a character? - Discussed in Character I/O
b) how to read a line? – Discussed in Character I/O
c) how to make out when we reach the end of file?
d) how to break a given sequence of characters into words?
Our hurdle is to read the whole file. How do we know we have reached the end of file. ‘C’
handles this in a very interesting way. When the end of file is reached – which the operating
system can make out – a particular value gets is returned by getchar. This value is decided by
‘C’ and not by the operating system. This value is associated with the name EOF. So, we keep
reading until getchar returns EOF.
while( (ch = getchar()) != EOF)
{
putchar(ch);
}
First attempt to count the number of lines and number of characters.
int nl = 0;
int nc = 0;
char ch;
while ((ch = getchar())!=EOF)
{
nc++;
while((ch=getchar())!= '\n')
{
nc++;
}
nc++;nl++;
}
printf("number of characters %d\n",nc);
printf("number of lines %d\n",nl);
This could work but not preferred. Observe that we are taking input in two places. What if we
reach EOF in the inner loop?
It is always preferred to read at only one place.
It is always preferred to have a while with an if over while with a while within.
Second Attempt for the same problem.
Count the character each time we enter the loop.
Count the lines if the character is newline.
Count the words assuming that the words on a newline or space or tab – any white space.
char ch;
int nl = 0;
int nw = 0;
int nc = 0;
while( (ch = getchar()) != EOF)
{
++nc;
if(ch == '\n')
{
++nl;
}
if(ch == ' ' || ch == '\n' || ch == '\t')
{
++nw;
}
}
//putchar('\n');
printf("# of char : %d\n", nc);
printf("# of words : %d\n", nw);
printf("# of lines : %d\n", nl);
What if there are multiple spaces between the words? Then we are in for trouble.
Can we use a boolean variable to indicate whether we are in a word or not. If we are in a word
and we reach a space, the word ends and we count. If we are not in a word and we encounter a
space, we ignore it. We set in word when we encounter a non- white-space.
Final Attempt to solve the problem and we got it!
int inword = 0; // not in a word so far
while( (ch = getchar()) != EOF)
{
++nc;
if(ch == '\n')
{
++nl;
}
if(inword && (ch == ' '|| ch == '\t' || ch == '\n'))
{
inword = 0; ++nw;
}
else if (!(ch == ' '|| ch == '\t' || ch == '\n'))
{ //avoid recomputation of white space concept
inword = 1;
}
}
You may compile this and run this against any text file say myfile.txt.
# ./a.out < myfile.txt
Compare with
# wc myfile.txt
Both should give the same result.
We have tried on the collection of Shakespeare’s works.
$ wc s.txt
124210 899680 5447737 s.txt
$ gcc program.c
$ ./a.out <s.txt
# of char : 5447737
# of words : 899680
# of lines : 124210
No comments