Saturday, December 10, 2011

C Programming Lecture 6


Control
Very few C programs follow exactly one control path and have each instruction stated explicitly. In order to program effectively, it is necessary to understand how one can alter the steps taken by a program due to user input or other conditions, how some steps can be executed many times with few lines of code, and how programs can appear to demonstrate a rudimentary grasp of logic. C
constructs known as conditionals and loops grant this power.
From this point forward, it is necessary to understand what is usually meant by the word block. A block is a group of code statements that are associated and intended to be executed as a unit. In C, the beginning of a block of code is denoted with { (left curly), and the end of a block is denoted with }. It is not necessary to place a semicolon after the end of a block. Blocks can be empty, as in {}. Blocks can also be nested; i.e. there can be blocks of code within larger blocks.
Conditionals
There is likely no meaningful program written in which a computer does not demonstrate basic decision­making skills. It can actually be argued that there is no meaningful human activity in which some sort of decision­making, instinctual or otherwise, does not take place. For example, when driving a car and approaching a traffic light, one does not think, "I will continue driving through the intersection." Rather, one thinks, "I will stop if the light is red, go if the light is green, and if yellow go only if I am traveling at a certain speed a certain distance from the intersection." These kinds of processes can be simulated in C using conditionals.
A conditional is a statement that instructs the computer to execute a certain block of code or alter certain data only if a specific condition has been met. The most common conditional is the If­Else statement, with conditional expressions and Switch­Case statements typically used as more
shorthanded methods.
Before one can understand conditional statements, it is first necessary to understand how C
expresses logical relations. C treats logic as being arithmetic. The value 0 (zero) represents false, and all other values represent true. If you chose some particular value to represent true and then compare values against it, sooner or later your code will fail when your assumed value (often 1) turns out to be incorrect. Code written by people uncomfortable with the C language can often be identified by the usage of #define to make a "TRUE" value.




Because logic is arithmetic in C, arithmetic operators and logical operators are one and the same. Nevertheless, there are a number of operators that are typically associated with logic:

Relational and Equivalence Expressions:
a < b
1 if a is less than b, 0 otherwise.
a > b
1 if a is greater than b, 0 otherwise.
a <= b
1 if a is less than or equal to b, 0 otherwise.
a >= b
1 if a is greater than or equal to b, 0 otherwise.
a == b
1 if a is equal to b, 0 otherwise.
a != b
1 if a is not equal to b, 0 otherwise

New programmers should take special note of the fact that the "equal to" operator is ==, not =. This
is
 the cause of numerous coding mistakes and is often a difficult­to­find bug, as the statement (a =
b) sets a equal to b and subsequently evaluates to b, while (a == b), which is usually intended,
checks if a is equal to b. It needs to be pointed out that, if you confuse = with ==, your mistake will
often not be brought to your attention by the compiler. A statement such as if ( c  =  20)  { is
considered perfectly valid by the language, but will always assign 20 to c and evaluate as true.
A note regarding testing for equality against a truth constant: never do it.
#define  TRUE  42
if  (SomethingsAfoot()  ==  TRUE)       //  bad  code     :^(

Instead it is much safer and more elegant to just write
if  (SomethingsAfoot())             //  good  code    :^)
This is because someone could have defined TRUE erroneously such that an expression such as (A <  B)  ==  TRUE would actually evaluate to FALSE when A is indeed less than B. So let's repeat: Avoid testing for equality against TRUE.
One other thing to note is that the relational expressions do not evaluate as they would in
mathematical texts. That is, an expression myMin                     <  value  <  myMax does not evaluate as you
probably think it might. Mathematically, this would test whether or not value is between myMin and myMax. But in C, what happens is that value is first compared with myMin. This produces either a 0 or a 1. It is this value that is compared against myMax. Example:
int  value  =  20;
if  (  0  <  value  <  10)  {
/*  do  some  stuff  */ }
Because value is greater than 0, the first comparison produces a value of 1. Now 1 is compared to be less than 10, which is true, so the statements in the if are executed. This probably is not what the programmer expected. The appropriate code would be
int  value  =  20;





if  (  0  <  value  &&  value  <  20)  {             //  the  &&  means  "and"
/*  do  some  stuff  */
}
If you're looking for a programming language that matches the mathematical notation, try Python.

Logical Expressions:
a || b
1 if either a or b is not zero, 0 otherwise.
a && b
1 if both a and b are not zero, 0 otherwise.
!a
1 if a is 0, 0 if a is not zero.

Here's an example of a logical expression. In the statement:
e  =  ((a  &&  b)  ||  (c  >  d));

e is set equal to 1 if a and b are non­zero, or if c is greater than d. In all other cases, e is set to 0.
C uses short circuit evaluation of logical expressions. That is to say, once it is able to determine the
truth
 of a logical expression, it does no further evaluation. This is often useful as in the following:
int  myArray[12];
if  (  i  <  12  &&  myArray[i]  >  3)  {

In the snippit of code, the comparison of i with 12 is done first. If it evaluates to 0 (false), i would be out of bounds as an index to myArray. In this case, the program never attempts to access
myArray[i] since the truth of the expression is known to be false. Hence we need not worry here about trying to access an out­of­bounds array element if it is already known that i is greater than or equal to zero. A similar thing happens with expressions involving the or || operator.
while(  doThis()  ||  doThat())  ...

DoThat() is never called if doThis() returns a non­zero (true) value.

Bitwise Boolean Expressions
The bitwise operators work bit by bit on the operands. The operands must be of integral type (one of
the types used for integers). The six bitwise operators are & (AND), | (OR), ^ (exclusive OR,
commonly called XOR), ~ (NOT, which changes 1 to 0 and 0 to 1), << (shift left), and >> (shift
right). The negation operator is a unary operator which preceeds the operand. The others are binary
operators which lie between the two operands. The precedence of these operators is lower than that
of the relational and equivalence operators; it is often required to parenthesize expressions involving
bitwise operators.
For this section, recall that a number starting with 0x is hexadecimal, or hex for short. Unlike the
normal decimal system using powers of 10 and digits 0123456789, hex uses powers of 16 and digits 0123456789abcdef. It is commonly used in C programs because a programmer can quickly convert it to or from binary (powers of 2 and digits 01). C does not directly support binary notation, which would be really verbose anyway.
a & b





bitwise boolean and of a and b
0xc & 0xa produces the value 0x8 (in binary, 1100 & 1010 produces 1000)

a | b
bitwise boolean or of a and b
0xc | 0xa produces the value 0xe (in binary, 1100 | 1010 produces 1110)

a ^ b
bitwise xor of a and b
0xc ^ 0xa produces the value 0x6 (in binary, 1100 ^ 1010 produces 0110)

~a
bitwise complement of a.
~0xc produces the value ­1­0xc (in binary, ~1100 produces ...11110011 where "..." may be many more 1 bits)
a << b
shift a left by b (multiply a by 2b)
0xc << 1 produces the value 0x18 (in binary, 1100 << 1 produces the value 11000)

a >> b
shift a right by b (divide a by 2b)
0xc >> 1 produces the value 0x6 (in binary, 1100 >> 1 produces the value 110)

The If­Else statement
If­Else provides a way to instruct the computer to execute a block of code only if certain conditions have been met. The syntax of an If­Else construct is:
if  (/*  condition  goes  here  */)
{
/*  if  the  condition  is  non­zero  (true),  this  code  will  execute  */
}
else
{
/*  if  the  condition  is  0  (false),  this  code  will  execute  */
}
The first block of code executes if the condition in parentheses directly after the if evaluates to non­ zero (true); otherwise, the second block executes.
The else and following block of code are completely optional. If there is no need to execute code if a condition is not true, leave it out.
Also, keep in mind that an if can directly follow an else statement. While this can occasionally be useful, chaining more than two or three if­elses in this fashion is considered bad programming practice. We can get around this with the Switch­Case construct described later.
Two other general syntax notes need to be made that you will also see in other control constructs: First, note that there is no semicolon after if or else. There could be, but the block (code enclosed in { and }) takes the place of that. Second, if you only intend to execute one statement as a result of an if or else, curly braces are not needed. However, many programmers believe that inserting curly braces anyway in this case is good coding practice.




The following code sets a variable c equal to the greater of two variables a and b, or 0 if a and b are
equal.
if(a  >  b)
{
c  =  a;
}
else  if(b  >  a)
{
c  =  b;
}
else
{
c  =  0;
}

Consider this question: why can't you just forget about else and write the code like:
if(a  >  b)
{
c  =  a;
}
if(a  <  b)
{
c  =  b;
}
if(a  ==  b)
{
c  =  0;
}
There are several answers to this. Most importantly, if your conditionals are not mutually exclusive, two cases could execute instead of only one. If the code was different and the value of a or b
changes somehow (e.g.: you reset the lesser of a and b to 0 after the comparison) during one of the blocks? You could end up with multiple if statements being invoked, which is not your intent. Also, evaluating if conditionals takes processor time. If you use else to handle these situations, in the case above assuming (a > b) is non­zero (true), the program is spared the expense of evaluating
additional if statements. The bottom line is that it is usually best to insert an else clause for all cases in which a conditional will not evaluate to non­zero (true).

The conditional expression
A conditional expression is a way to set values conditionally in a more shorthand fashion than If­ Else. The syntax is:
(/*  logical  expression  goes  here  */)  ?  (/*  if  non­zero  (true)  */)  :  (/*  if  0 (false)  */)

The logical expression is evaluated. If it is non­zero (true), the overall conditional expression
evaluates to the expression placed between the ? and :, otherwise, it evaluates to the expression after the :. Therefore, the above example (changing its function slightly such that c is set to b when a and b are equal) becomes:
c  =  (a  >  b)  ?  a  :  b;

Conditional expressions can sometimes clarify the intent of the code. Nesting the conditional
operator should usually be avoided. It's best to use conditional expressions only when the





expressions for a and b are simple. Also, contrary to a common beginner belief, conditional
expressions do not make for faster code. As tempting as it is to assume that fewer lines of code result in faster execution times, there is no such correlation.

The Switch­Case statement
Say you write a program where the user inputs a number 1­5 (corresponding to student grades, A(represented as 1)­D(4) and F(5)), stores it in a variable grade and the program responds by printing to the screen the associated letter grade. If you implemented this using If­Else, your code would look something like this:
if(grade  ==  1)
{
printf("A\n");
}
else  if(grade  ==  2)
{
printf("B\n");
}
else  if  /*  etc.  etc.  */
Having a long chain of if­else­if­else­if­else can be a pain, both for the programmer and anyone
reading the code. Fortunately, there's a solution: the Switch­Case construct, of which the basic
syntax is:
switch(/*  integer  or  enum  goes  here  */)
{
case  /*  potential  value  of  the  aforementioned  int  or  enum  */:
      /*
  code  */
case  /*  a  different  potential  value  */:
     
/*  different  code  */
/*  insert  additional  cases  as  needed  */ default:
/*  more  code  */
}

The Switch­Case construct takes a variable, usually an int or an enum, placed after switch, and
compares it to the value following the case keyword. If the variable is equal to the value specified after case, the construct "activates", or begins executing the code after the case statement. Once the construct has "activated", there will be no further evaluation of cases.
Switch­Case is syntactically "weird" in that no braces are required for code associated with a case.
Very important: Typically, the last statement for each case is a break statement. This causes
program execution to jump to the statement following the closing bracket of the switch statement,
which
 is what one would normally want to happen. However if the break statement is omitted,
program execution continues with the first line of the next case, if any. This is called a fall­through.
When a programmer desires this action, a comment should be placed at the end of the block of
statements indicating the desire to fall through. Otherwise another programmer maintaining the
code
 could consider the omission of the 'break' to be an error, and inadvertently 'correct' the
problem. Here's an example:
switch  (  someVariable  )  { case  1:
printf("This  code  handles  case  1\n");
break;
case  2:
printf("This  prints  when  someVariable  is  2,  along  with...\n"); /*  FALL  THROUGH  */





case  3:
printf("This  prints  when  someVariable  is  either  2  or  3.\n"  );
break;
}
If a default case is specified, the associated statements are executed if none of the other cases match. A default case is optional. Here's a switch statement that corresponds to the sequence of if ­ else if
statements above.
Back to our example above. Here's what it would look like as Switch­Case:
switch  (grade)
{
case  1:
printf("A\n");
break;
case  2:
printf("B\n");
break;
case  3:
printf("C\n");
break;
case  4:
printf("D\n");
break;
default:
printf("F\n");
break;
}

A set of statements to execute can be grouped with more than one value of the variable as in the
following example. (the fall­through comment is not necessary here because the intended behavior is obvious)
switch  (  something)
{
case  2:
case
  3:
case  4:
/*  some  statements  to  execute  for  2,  3  or  4  */
break;
case  1:
default:
/*  some  statements  to  execute  for  1  or  other  than  2,3,and  4  */
break;
}



Switch­Case constructs are particularly useful when used in conjunction with user defined enum data types. Some compilers are capable of warning about an unhandled enum value, which may be helpful for avoiding bugs. 

0 comments: