Expressions
Expressions cover assigning or computing values and use a sequence of operators and operands. The operators and operands are combined in such a way as to obtain the desired results.
There are an unlimited number of ways to build expressions, but there are some basic rules that are fairly easy to follow. Most of these rules in C are similar to those in Visual Basic. C has some "shortcut" operators that make statements shorter, like the ++ operator that can increment a variable.
This section covers the operators, precedence, conversions, and type casts. This gives you the building blocks for expressions.
Operators and Precedence
In this section I discuss operators, their uses, and their precedence. This provides much of the information you need in order to successfully use these operators within expressions.
Unary Operators
The unary operators in C include *, &, sizeof, -, ~, !, ++, and --. You've seen and used the indirection (*) and address-of operators (&) earlier in this chapter in the section on pointers:
-
Indirection operatorThis is used to assign a value to the variable that a pointer variable is pointing to.
-
Address-of operatorThis operator is used to assign a variable's address to a pointer. These are demonstrated following:
int x=10;
int *px = &x; //address-of operator to assign pointer
*px = 20; //indirection to assign value
Sizeof operatorThis operator provides the amount of storage required to store the item in the operand. This is expressed in bytes. Len in Visual Basic roughly equates to this operator.
Negation operator (-)This operator does what you might expect: It returns the negative of the operand. Visual Basic has the same operator.
Bitwise not (~)This operator produces the bitwise complement of the operand. The logical not (!) produces the logical opposite of the operand. Visual Basic uses the NOT keyword for the logical not.
Increment (++)and decrement (--)operatorsThese operators can be used in a couple ways. When used within an expression, it is significant if these operators are used as prefix operators or suffix operators. Basically they increment or decrement the variable they are affixed to. Used creatively they can save code. The code following shows how to use these operators.
x = 0;
item[++x] = 1; //item[1]
x = 0;
item[x++] = 1; //item[0]
Using the increment operator as a prefix changes the value before the assignment. Using it as a suffix operator increments the value after the assignment.
Binary Operators
The binary operators include *, /, %, +, -, <<, >>, <, >, <=, >=, ==, !, =, &, |, ^, &&, ||, and ,; only some of these have counterparts in Visual Basic. And unlike VB, C also allows a combination of the assignment operator and other binary operators.
The multiplicative operatorsthe multiplication (*), division (/), and remainder (%) all have counterparts in Visual Basic. Only the remainder operator is different in VB, the Mod operator is used in VB instead of the %. The multiplication and division operators are the same in C and VB. These three operators work pretty much like you'd expect.
The additive operators plus(+) and minus (-) have direct counterparts in Visual Basic and work just as you'd expect.
The bitwise shift operators shift their operand left(<<) or right (>>) by the number of positions specified. Visual Basic can simulate this by multiplying by a number, but the shift still does things a bit differently than a multiplication does and works across the entire unsigned number. Because of signed numbers, bit shifts in Visual Basic are fairly complex issues.
Logical comparison operators are the same as Visual Basic in many cases, but different enough in a couple of cases to be aggravating. The <, >, <=, and >= operators are all the same as VB and work like you would expect them. The equivalent comparison is different (==) in C than in VB (=). Also the not operator in C (!) is different than VB (NOT). A common way of using the not operator (!) in C has a different counterpart in VB: not equal in C is !=, in VB it is <>.
Bitwise operators AND (&), OR (|), and exclusive OR (^) compare to the Visual Basic keywords AND, OR, and XOR. The problem with the Visual Basic operators is that context decides whether they are bit operators or logical operators and this can produce unexpected results in some cases. In addition you have the fact that there are no unsigned types in Visual Basic which hinder the use of bitwise operators.
Logical operators AND (&&) and OR (||) compare to the Visual Basic operators AND and OR. Again in VB, context determines which operation is performed, logical or bitwise.
The == operator is worth further mention. This is an equal comparison operator. In Visual Basic the equal operator does double duty, both as assignment and comparison. Not so in C. This has both good points and bad points and it can be especially troublesome to a VB programmer.
VB programmers are used to using the equal sign for comparison and so typically make the mistake of trying to use it in C. This is a simple thing, but sometimes habit takes over. Many times misuse of this operator will not produce an error, just an unwanted result.
C operators are summarized in Table 3.2.
Table 3.2 C Operators
Operator |
Basic Use |
Visual Basic |
Unary |
|
|
* |
Indirection |
None |
& |
Address of |
Varptr |
sizeof |
Size of |
Len(x)sort of |
- |
Negation |
- |
~ |
Bitwise not |
NOT |
! |
Logical not |
NOT |
++ |
Increment |
x = x + 1 |
-- |
Decrement |
x = x - 1 |
Binary |
|
|
* |
Multiplication |
* |
/ |
Division |
/ |
% |
Remainder |
MOD |
+ |
Addition |
+ |
- |
Subtraction |
- |
<< |
Left bit shift |
None |
>> |
Right bit shift |
None |
< |
Less than |
< |
> |
Greater then |
> |
<= |
Less then or equal |
<= |
>= |
Greater than or equal |
>= |
== |
Logical equal |
= |
! |
Not |
NOT |
= |
Equal assignment |
= |
& |
Bitwise AND |
AND |
| |
Bitwise inclusive OR |
OR |
^ |
Bitwise exclusive or |
XOR |
&& |
Logical AND |
AND |
|| |
Logical OR |
OR |
, |
Sequential evaluation |
None |
? : |
Conditional Evaluator |
IIF |
Assignment |
|
|
= |
Simple assignment |
= |
*= |
Multiplication assignment |
x = x * y |
/= |
Division assignment |
x = x / y |
%= |
Remainder assignment |
x = x MOD y |
+= |
Addition assignment |
x = x + y |
<<= |
Left shift assignment |
None |
>>= |
Right shift assignment |
None |
&= |
Bit AND assignment |
x = x AND y |
|= |
Bit OR assignment |
x = x OR y |
^= |
Bit Exclusive OR assignment |
x = x XOR y |
Precedence
Operators listed at the same level have the same precedence. As in Visual Basic, grouping statement segments within parentheses can alter precedence.
Table 3.3 summarizes operator precedence.
Table 3.3 C Operator Precedence
Symbols |
Basic Use |
Associativity |
[]().-> postfix ++ postfix -- |
Expression |
Left to Right |
Prefix ++ prefixsizeof & * - ~ ! |
Unary |
Right to Left |
Type casts |
Unary |
Right to Left |
* / % |
Multiplicative |
Left to Right |
+ - |
Additive |
Left to Right |
<< >> |
Bitwise shift |
Left to Right |
< > <= >= |
Relational |
Left to Right |
== != |
Logical Equality |
Left to Right |
& |
Bitwise AND |
Left to Right |
^ |
Bitwise Exclusive OR |
Left to Right |
| |
Bitwise Inclusive OR |
Left to Right |
&& |
Logical-AND |
Left to Right |
|| |
Logical-OR |
Left to Right |
?: |
Conditional-expression |
Right to Left |
= *= /= %= += -= <<= >>= &= ^= |= |
Assignment |
Right to Left |
, |
Sequential evaluation |
Left to Right |
There are subtle problems that can creep into expressions with multiple operators depending on your compiler and the current compilation flags. For example, logical expressions will often "shortcut" the latter part of the expression if the first part is enough to determine the outcome of the logical expression.
Tip
Precedence is difficult to deal with in complex expressions. My advice is to group your expressions so there will be little ambiguity.
Conversions and Type Casts
Conversions and type casts follow a set of rules. These rules involve a couple of definitions that you need to know to understand the conversions involving numeric data:
Sign extendThis rule dictates how smaller integral types are converted to larger ones. The number must be represented in a different manner because the bit that denotes the negativity is in a different spot.
Preserve low order word or byteThis rule describes how a larger integral type is converted to a smaller one. The high order word or byte will be ignored, effectively it is dropped.
Loss of precisionThis rule governs what happens when a number is converted to float and the significant digits are larger than 7. It can also happen with a double after 14 digits.
From Signed Integral Types
When converting from signed integral types, the main point to remember in integral conversions is that there is a loss of the upper bytes when going from a larger integral to a smaller one, like a long to a short. If there is information contained in the upper bytes, it will therefore be lost. Converting from a smaller int to a larger one involves no real problems. Converting a signed integer to an unsigned one doesn't change the data as long as they are the same size. It may change the way the bits are interpreted if the signed number is negative, but the in memory representation will remain unchanged as long as they are the same size.
Converting an integral type to a decimal type (float or double) should leave the number unchanged, unless the number is longer than the decimal type's significant digits, in which case a loss of precision will occur. A summary of signed integral conversions is shown in Table 3.4. Also a floating-point representation has gaps in its coverage of all decimal numbers (which is infinite).
Note
Because the long will be converted to a mantissa and an exponent (for example, 1000 becomes 1E+3), you can actually end up with a different number than what you started with. This has nothing to do with loss of precision that results from too few significant digits.
Table 3.4 Signed Integral Conversions
From |
To |
Method |
char |
short |
Sign-extend |
char |
long |
Sign-extend |
char |
unsigned char |
Preserve pattern; high-order bit loses function as sign bit |
char |
unsigned short |
Sign-extend to short; convert short to unsigned short |
char |
unsigned long |
Sign-extend to long; convert long to unsigned long |
char |
float |
Sign-extend to long; convert long to float |
char |
double |
Sign-extend to long; convert long to double |
char |
long double |
Sign-extend to long; convert long to double |
short |
char |
Preserve low-order byte |
short |
long |
Sign-extend |
short |
unsigned char |
Preserve low-order byte |
short |
unsigned short |
Preserve bit pattern; high-order bit loses function as sign bit |
short |
unsigned long |
Sign-extend to long; convert long to unsigned long |
short |
float |
Sign-extend to long; convert long to float |
short |
double |
Sign-extend to long; convert long to double |
short |
long double |
Sign-extend to long; convert long to double |
long |
char |
Preserve low-order byte |
long |
short |
Preserve low-order word |
long |
unsigned char |
Preserve low-order byte |
long |
unsigned short |
Preserve low-order word |
long |
unsigned long |
Preserve bit pattern; high-order bit loses function as sign bit |
long |
float |
-Represent as float. If long cannot be represented exactly, some precision is lost. |
long |
double |
-Represent as double. If long cannot be represented exactly as a double, some precision is lost. |
long |
long double |
-Represent as double. If long cannot be represented exactly as a double, some precision is lost. |
From Unsigned Integral Types
The concerns with unsigned types are the same as with signed integral types. Again, going from unsigned to signed may change the way the bits are interpreted, but in memory it will still be the same if the types are the same size. A summary of unsigned integral conversions is shown in Table 3.5.
Table 3.5 Unsigned Integral Conversions
From |
To |
Method |
unsigned char |
char |
Preserve bit pattern; high-order bit becomes sign bit |
unsigned char |
short |
Zero-extend |
unsigned char |
long |
Zero-extend |
unsigned char |
unsigned short |
Zero-extend |
unsigned char |
unsigned long |
Zero-extend |
unsigned char |
float |
Convert to long; convert long to float |
unsigned char |
double |
Convert to long; convert long to double |
unsigned char |
long double |
Convert to long; convert long to double |
unsigned short |
char |
Preserve low-order byte |
unsigned short |
short |
Preserve bit pattern; high-order bit becomes sign bit |
unsigned short |
long |
Zero-extend |
unsigned short |
unsigned char |
Preserve low-order byte |
unsigned short |
unsigned long |
Zero-extend |
unsigned short |
float |
Convert to long; convert long to float |
unsigned short |
double |
Convert to long; convert long to double |
unsigned short |
long double |
Convert to long; convert long to double |
unsigned long |
char |
Preserve low-order byte |
unsigned long |
short |
Preserve low-order word |
unsigned long |
long |
Preserve bit pattern; high-order bit becomes sign bit |
unsigned long |
unsigned char |
Preserve low-order byte |
unsigned long |
unsigned short |
Preserve low-order word |
unsigned long |
float |
Convert to long; convert long to float |
unsigned long |
double |
Convert directly to double |
unsigned long |
long double |
Convert to long; convert long to double |
From Floating Point Types
Floating point conversions can result in loss of precision or can even be undefined if, for example, the float value is converted to a short and the float value is out of the short's range. A summary of floating point conversions is shown in Table 3.6.
Table 3.6 Floating Point Conversions
From |
To |
Method |
Float |
char |
Convert to long; convert long to char |
Float |
short |
Convert to long; convert long to short |
Float |
Llong |
Truncate at decimal point. If result is too large to be represented as long, result is undefined. |
Float |
unsigned short |
Convert to long; convert long to unsigned short |
Float |
unsigned long |
Convert to long; convert long to unsigned long |
Float |
double |
Change internal representation |
Float |
long double |
Change internal representation |
Double |
char |
Convert to float; convert float to char |
Double |
short |
Convert to float; convert float to short |
Double |
long |
-Truncate at decimal point. If result is too large to be represented as long, result is undefined. |
Double |
unsigned short |
Convert to long; convert long to unsigned short |
Double |
unsigned long |
Convert to long; convert long to unsigned long |
double |
float |
Represent as a float. If double value cannot be represented exactly as float, loss of precision occurs. If value is too large to be represented as float, the result is undefined. |
long double |
char |
Convert to float; convert float to char |
long double |
short |
Convert to float; convert float to short |
long double |
long |
Truncate at decimal point. If result is too large to be represented as long, result is undefined. |
long double |
unsigned short |
Convert to long; convert long to unsigned short |
long double |
unsigned long |
Convert to long; convert long to unsigned long |
long double |
float |
Represent as a float. If double value cannot be represented exactly as float, loss of precision occurs. If value is too large to be represented as float, the result is undefined. |
long double |
double |
The long double value is treated as double. |
To and From Pointers
Pointers can be converted to integral types and back to pointers. Here you simply need to be aware of the size of the pointer and the size of the integral, which may be determined by the memory-addressing model of your operating system (for example, 32 bit for WIN32).
Generally speaking pointers can be converted without loss of data or any consequences. However there can be some alignment issues with some types of pointers, as well as other issues with the data.
Note
The biggest issue in converting a pointer has to do with accessing the data after the pointer is converted. Usually this is why you convert the pointer, so you can access the data. However, there is no protection in C if you want to convert a pointer to a double to a pointer to a long and then try to use the data like it contains long data. This gives erroneous results, but you can do it.
Other Types
Because an enum is an integer, conversions for it work the same as for an integer. For the Microsoft compiler, an integer is the same as a long.
Void types have no value and cannot be convertedonly void pointers may be converted to other pointer types with no loss of information.
Type Casts
Type casts are the way you change one variable type to another. Obviously some type casts are allowable and others are not. The conversion rules you saw in the previous sections apply to type casts as well as implicit conversions. The code following shows a few examples of type casts.
//type casts //int to short int i=2; short sval = (short) i; //long to pointer common in handling lParam of SendMessage char *p; p = (char *)lParam;
Table 3.7 summarizes the allowable type casts.
Table 3.7 Type Cast Conversions
Destination Types |
Potential Sources |
Integral types |
Any integer type or floating-point type, or pointer to an object |
Floating-point |
Any arithmetic type |
A pointer to an object, or (void *) |
Any integer type, (void *), a pointer to an object, or a function pointer |
Function pointer |
Any integral type, a pointer to an object, or a function pointer |
A structure, union, or array |
None |
Void type |
Any type |