CHAPTER 3
Types, Operators, and Expressions; While and For
Reference: Brooks, Chapter 3 (3.2  3.3)

It is possible to mix data types in arithmetic expressions in C

The following example is perfectly legal in C
/*
* File : main.c
* Author: Robert C. Carden IV
*/
#include
void main(void)
{
int hours;
double speed, distance;
printf("Enter the number of hours: ");
scanf("%d", &hours);
printf("Enter the speed in MPH: ", &speed);
scanf("%lf", &speed);
distance = hours * speed;
printf("You travelled %.2f miles "
"at %.2f MPH in %d hours\n",
distance, speed, hours);
}


In this example, we are multiplying an integer with a double

C mandates that all expressions be computed using operand of the same data type

In this example, C has rules for converting one or both numerical operands to a common type

Since double is considered to be a higher type than int, the integer operand is converted (promoted) to double and double multiplication is performed
Implicit type conversions
· Performed when an operator has operands of different types
· The operands are converted to a common type
General rules
· In general, the only automatic conversions are those that convert a narrower operand into a wider one without losing information.
· Assignments of a wider type to a narrower type loses information but is allowed.
· A char is treated as a small integer.
· They may be used freely within arithmetic expressions.
· The following example shows how characters and integers are used freely within expressions.
/*
* ctoi: if ch is a numerical character, return
* the numerical equivalent
* if ch is not, return 1
*/
int ctoi(char ch)
{
int digit = 1;
if (ch >= '0' && ch <= '9')
{
/*compute integer value*/
digit = ch  '0';
}
return(digit);
}

Implicit type conversions  example
/**
** lower: convert c to lower case (ASCII only)
**
** For portability reasons, we pass in
** characters as integers. See later
** discussion on this point.
**/
int lower(int c)
{
if (c >= 'A' && c <= 'Z')
/*
* uppercase letter  convert it
* to lower case
*/
return c + 'a'  'A';
else
/* not an uppercase letter */
return c;
}

· This function works for ASCII but fails for EBCDIC.
· In ASCII, corresponding upper and lower case letters are a fixed distance apart as numeric values
· Letters are also contiguous
Portable character functions and macros
example functions

semantics of function or macro

isdigit(int c)

returns 1 <=> c is a digit

isupper(int c)

returns 1 <=> c is uppercase

toupper(int c)

converts c to uppercase

tolower(int c)

converts c to lowercase

· To overcome this common problem, the header is available (and required under ANSI).
· This header defines a family of functions and macros for doing character tests and conversions that are independent of the character set
· Each installation will provide its own to do whatever is necessary
· Now we can rewrite our lower function using these macros:
#include
int lower(int c)
{
if ( isupper(c) )
return tolower(c)
else
return c;
}

· The C language does not specify whether variables of type char are signed or unsigned quantities.
· The C definition guarantees that any character in a machine's standard character set will never be negative.
· For portability, if you use characters as integers to store noncharacter data, specify whether it is signed or unsigned.
· When passing or receiving characters to and from functions, people often use int to store characters.
· Problems sometimes arise when users must be able to store an arbitrary character and EOF (1) in a character variable. In the EBCDIC character set, it already needs all 256 possible values but adding EOF requires 257.
Type conversions  A.6, p.198, K&R
· The following conversions are performed in an arithmetic expression of mixed types
· If either operand is long double, the other is converted to long double
· Otherwise, if either is double, convert the other to double
· Otherwise, if either is float, convert the other to float
[Integral promotions]
· Otherwise if either is unsigned long int, convert the other to unsigned long int
· Otherwise, if one operand is long int and the other is unsigned int, the effect depends on whether long int can represent all values of an unsigned int; if so, the unsigned int operand is converted to long int; if not, both are converted to unsigned long int
· Otherwise, if one operand is long int, convert the other to long int
· Otherwise, if either operand is unsigned int, convert the other to unsigned int
· Otherwise both operands have type int, i.e. smaller operands are promoted to int.
· Under ANSI, unsignedness of operands is not propagated.
Type conversion  the distance program
void main(void)
{
int hours;
double speed, distance;
printf("Enter the number of hours: ");
scanf("%d", &hours);
printf("Enter the speed in MPH: ", &speed);
scanf("%lf", &speed);
distance = hours * speed;
printf("You travelled %.2f miles "
"at %.2f MPH in %d hours\n",
distance, speed, hours);
}


The mixed mode expression in question is hours * speed

Variable hours is of type int

Variable speed is of type double

Because they are of different types, an arithmetic type conversion must be applied

Neither variable is of type long double so that rule does not apply

Variable speed is of type double and since hours is not, hours shall be promoted to double and double (floating point) multiplication shall be performed

No information is lost here because of the nature of double

Note from earlier discussions that double has a 53 bit mantissa (on our Intel systems)

By the same token, int is 32 bits and can easily fit into the integer part of the double

The result of this expression is a double and the variable it is being assigned to is double

These are the same so no conversion for the assignment is required
Type conversion  the distance program using floats
void main(void)
{
int hours;
float speed, distance;
printf("Enter the number of hours: ");
scanf("%d", &hours);
printf("Enter the speed in MPH: ", &speed);
scanf("%lf", &speed);
distance = hours * speed;
printf("You travelled %.2f miles "
"at %.2f MPH in %d hours\n",
distance, speed, hours);
}


The mixed mode expression in question is hours * speed

Variable hours is of type int

Variable speed is of type float

Because they are of different types, an arithmetic type conversion must be applied

Neither variable is of type long double so that rule does not apply

Neither variable is of type double so that rule does not apply

Variable speed is of type float and since hours is not, hours shall be promoted to float and float (floating point) multiplication shall be performed

In this example, information might be lost if hours is a fairly large integer

Note from earlier discussions that float has a 24 bit mantissa (on our Intel systems)

By the same token, int is 32 bits and may not be able to fit into the integer part of the float

The excess, low order digits in the floating point number will be truncated

The result of this expression is a float and the variable it is being assigned to is float

These are the same so no conversion for the assignment is required
Integer constants revisited  some subtle points
· The type of integer constant is the first of the corresponding list in which its value can be represented (ANSI draft, section 3.1.3.2)
unsuffixed decimal:

int, long, unsigned long

unsuffixed octal or hexadecimal:

int, long, unsigned long

suffixed by letter u or U:

unsigned int,
unsigned long

suffixed by letter l or L:

long, unsigned long

suffixed by both u or U and l or L:

unsigned long

#define LONG_MIN (2147483647L  1)
#define LONG_MAX 2147483647L
#define ULONG_MAX 0xffffffffUL

Consider the rather arcane definition of LONG_MIN

Suppose we had rewritten it as
#define LONG_MIN 2147483648L

This would not work at all

First, the compiler would determine the type of the constant 2147483648L

Because it is suffixed with an L, the compiler may choose the first of long and unsigned long which can represent this integer

However, this integer requires a full 32 bits and cannot be represented as a long

Therefore, it must be an unsigned long

Then, we negate the unsigned long 2147483648

That will produce another unsigned long and not the minimum signed long number
Mixed mode expressions  evaluating from the bottom up
#include
void main(void)
{
double result1, result2;
result1 = 2.56 + (3 / 2);
result2 = 2.56 + (3.0 / 2);
printf("result1 = 2.56+(3 /2) = %.2f\n", result1);
printf("result2 = 2.56+(3.0/2) = %.2f\n", result2);
}


The example above illustrates a very common pitfall in C

Many people are aware that integer division truncates and would expect 3/2 to produce a result of 1

Even more people, though, get confused when they cleverly observe that the result is being assigned to a double and therefore conclude that the entire expression is a double expression. Wrong.

C evaluates expressions from the bottom up and determines the type and value of each subexpression as it goes, each step of the way

In the case of computing result1, C first considers 3/2

Both operands are integers, therefore we do integer division and note that the result is an int

Notice at this point there is no concern or care about the larger expression

Notice that C does not care about the context

Then C considers adding 2.56 to the integer result of computing 3/2

Now we are adding a double to an int, the int result is promoted to double, and the result is double

Finally, we have a double which we are assigning to a double

No conversion is necessary and the C compiler is happy with life
Mixed mode expressions  evaluating from the bottom up
#include
void main(void)
{
double result1, result2;
result1 = 2.56 + (3 / 2);
result2 = 2.56 + (3.0 / 2);
printf("result1 = 2.56+(3 /2) = %.2f\n", result1);
printf("result2 = 2.56+(3.0/2) = %.2f\n", result2);
}

Here is the actual result of running this program:
Explicit type conversion  assignments
· The expression x = y forces y to be converted into the type on the left
· Consider the following C fragment:
float f;
int i;
/* converts int to float */
f = i;
/*
* converts float to int,
* truncating fractional part
*/
i = f;

· Converting float to int causes the truncation of the fractional part
· Longer integers are converted to shorter ones or to chars by dropping the excess highorder bits.
int i;
char c;
i = c; /* widen c to fit in i */
c = i; /* value originally in c is unchanged */

[Instructor: give examples on the board of these scenarios]
Explicit type conversions  coerced

The cast operator is of the form
( )

It is a unary operator with the effect that it converts its operand to the desired type.

The cast operator is an operator equal in precedence to unary minus ()
float f;
int i, j;
f = i / j; /* integer division */
f = (float) i / j; /* real division */
i = f % j; /* syntax error */
i = (int) f % j; /* f is converted to int */


A cast is an expression: it involves a cast operator and an operand to be cast

it does not modify the operand

In the first example, it converts the int i to float

The result of this is divided by j using floating point division

In the second example, it converts the float back to int, truncating the fractional part

That integer result is then modulo with j
Using casts to get the correct type of expression
#include
void main(void)
{
result1 = 2.56 + (double) 3 / 2;
result2 = 2.56 + (double)(3 / 2);
printf("result1 = 2.56+(double)3 /2 = %.2f\n",
result1);
printf("result2 = 2.56+(double)(3/2) = %.2f\n",
result2);
}


It is important to understand why the computation of result1 uses floating point division while the computation of result2 uses integer division

Once again, all expressions are evaluated from the simplest, innermost expression outward
Increment and decrement operators
· Unary operators ++ and  increment and decrement their respective operands
· highest precedence (same as unary )
· right to left associativity
Operand

Equivalent C code

y = ++ x;

x = x + 1; /*increment*/
y = x; /*use result*/

y = x ++;

y = x; /*use value*/
x = x + 1; /*increment*/

y =  x;

x = x  1;
y = x;

y = x ;

y = x;
x = x  1;

z++;

z = z + 1;

· When ++ or  are placed before the operand, they are called preincrement and predecrement respectively.
· the return value of the expression is the new value of the operand
· When ++ or  are placed after the operand, they are called postincrement and postdecrement respectively
· the return value of the expression is the original value of the operand
· Cannot apply ++ or  to something cannot be an lvalue
· An lvalue is anything that can be updated, for instance on the left hand side of an assignment statement
· The expression

is illegal.
· However, the expression

is legal and equivalent to

ANSI Note (p. 39)
"Between the previous and next sequence point, an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored."
· Thus, if a variable is updated more than once within an expression between two consecutive sequence points, the behavior is undefined.
example

· What gets assigned to i?
· Two assignments are being requested, but ANSI specifies that i shall have its stored value modified at most once.
· The resulting value of ++i will propagate through the expression, but the store may or may not occur.
· Consequently, the compiler may choose to allow the ++i to actually store into i or let the assignment statement store into i.
· Thus, only one update to i will win and thus i may be either 2 or 3.
Example expressions
· In the table below, assume that the following declarations are in scope.

int a = 1, b = 2, c = 3, d = 4;

· The following table gives an expression, the equivalent fully parenthesized expression, the value of the expression, and then the subsequent values of a, b, c, and d after its evaluation.
· Assume that these expressions are evaluated independent of each other.
Expression

Equivalent Expr

Value

a

b

c

d

a * b / c

(a * b) / c

0

1

2

3

4

a * b % c + a

((a * b) % c) + a

3

1

2

3

4

++ a * b  c 

((++a) * b)  (c)

1

2

2

2

4

7   b * ++ d

7  ((b) * (++d))

17

1

2

3

5

Illustration of third row
Assignment operators and expressions
· In C, any expression can be a statement.
· In Pascal, one uses an assignment statement.
· In C, assignments are simply expressions.
· As a result, it is perfectly legal to write the following meaningless code in C.

3; /* valid */
4+5; /* valid */

Assignments are expressions
· The return value of an assignment expression is the value that just got assigned
· The object getting updated by the assignment is called an lvalue
· The object (expression) being assigned to it is called an rvalue
example

· In this example, x is the lvalue, 5+7 is the rvalue, and the return value of the entire expression is 12.
Example of assignments being expressions
· The following three statements

may be written as one statement

example
· Assignment operators associate right to left
· Thus, the following expression

is equivalent to writing

which in turn is equivalent to writing

· Note that because assignment statements associate right to left, variables a, b, and c are all lvalues.
· C also provides operators such as += and = to write expressions such as

instead of

· In Algol, the equivalent expression would be

· The following list contains all the possible assignment operators:
= += = *= /= %= >>= <<= &= ^= =

Assignment operators
· The semantics of a general assignment operator is specified by
variable op= expression
being equivalent to
variable = variable op (expression)
· In the table below, assume that the following declarations are in scope.

int i = 1, j = 2, k = 3, m = 4;

· The following table gives an expression, the equivalent fully parenthesized expression, and the value of the expression.
· Assume that these expressions are evaluated independent of each other.
Expression

Equivalent Expr

Equivalent Expr

Value

j *= k + 3

j *= (k + 3)

j = j * (k + 3)

12

i += j + k

i += (j + k)

i = i + (j + k)

6

j *= k = m + 5

j *= (k=(m+5))

j = j * (k = (m + 5))

18

example
· The expression

is equivalent to writing

· Similarly, the expression

y = (x1 += 1) * (x2 = 2);

is roughly equivalent to writing

x1 = x1 + 1;
x2 = x2  2;
y = x1 * x2;

· Note, however, that because C does not enforce the order of evaluation of the multiplicative operands, the first and second statements may be interchanged
example  computing the powers of two

/* some powers of two are printed */
#include
int main (void)
{
int power = 1;
printf ("%6d", power *= 2);
printf ("%6d", power *= 2);
printf ("%6d", power *= 2);
printf ("%6d", power *= 2);
printf ("%6d", power *= 2);
printf ("%6d", power *= 2);
printf ("%6d\n", power *= 2);
}

The output of this program is
Conditional expressions
Share with your friends: 