[Phy 405/905 Home Page] [ Lecturer ]
Now we're getting into the meat and potatoes of C/C++. Statements
are the building blocks of source code; statements in turn contain
expressions. We've already seen a simple example of a statement
(variable declaration/definition) and of an expression (primary
expression, in particular, literals). In this section we'll survey the
majority of the diverse kinds of statements and expressions. Together with a
future lectures on pointers and structures, the material up to this point
covers the basics of the C-subset of C++ (and after those lectures, we move on
to object-oriented programming ... finally!)
- statement
- what we usually mean by a "line" of code. Actually, possibly several
lines, because one type of statement, a function definition statement,
obviously includes a group of statements itself.
- expression
- From ARM [[section]]5, "an expression is a sequence of operators and
operands that specifies a computation. An expression may result in a value and
may cause side effects." [my emphasis - the "side effects" are a curious
convenience in C/C++]
I'll introduce the different kinds of statements and
expressions in a somewhat circular fashion, but you can check out the textbook
for a more straightforward presentation.
Simplest (and most basic) kind of statement, i.e., the simplest line of
code is the
- expression statement
- a line of code of the form:
expression ;
where out of all
the different kinds of expressions, the simplest is the primary
expression, which is:
- primary expression
- a literal, a name, or a "global-scoped" name like ::my_global_var.
- So some simple expression statements:
// some statements
; // null statement! do nothing! have as many of these as you like!!
4; // my compiler doesn't complain:
// ... an expression with *no* "side-effect"
some_var;
(some_var); // exactly identical to the previous line
Before
we get too far, here's a very important kind of expression:
- assignment expression
- an expression involving one of the assignment operators (e.g. '=' or
'*='), of the form "lhs = rhs", where 'lhs' is a modifiable lvalue (object) of
the same type as the expression 'rhs', and ''=" signifies any assignment
operator
Because rhs in this definition is an expression, here's an
interesting (assignment) expression statement:
a = b = c = d = 0.0; // note: the = operator groups from right to left
The
meaning of this is (d=0.0), then (c=d), then (b=c), then (a=b). If you have
ever programmed in assembly language, you can see how a statement like this
could be easily translated into very tight machine code.
- expressions themselves include other expressions, which in other languages
(Fortran, Basic) would look like writing "several lines of code" as "one line."
- all the expressions with the exception of primary expressions involve an
operator (like =, or +, or ++) with one operand (unary operator)
or two (binary operator). Since most of these operators are true
"operators" in the linear algebra sense (they map a space into itself), it
makes sense to consider something like the "chain assignment" line of code
above.
- Each operator has its own grouping (apparently right-to-left, or
RL, for assignment operators). The text (p. 87) has a nice table listing
the grouping for each operator.
- there's also a relative precedence among the different operators,
e.g. 4*3-5+6 is interpreted - by precedence - as
(4*3)-5+6 , which by grouping is the ultimately interpreted as
((4*3)-5)+6. The same table shows the precedence also.
- Suffice it to say here that the arithmetic operations have the right
precedence defined, so don't clutter your code with needless parentheses (which
as noted above, don't change the value of an expression, but is as always used
to change the precedence and/or grouping.
Avery rapid overview of some of the important operators and the type of
expressions they yield:
- + - * / %
These binary operators yield arithmetic expressions,
with the type (i.e. int, long, ...), depend on the left and right-hand
operand types, and are determined by the rules given in the last lecture for
arithmetic conversion.
- both operands must be of arithmetic type (integer/floating pt), and in
particular for %, both must be of integer type.
- % is the modulo, or remainder operator (7%2 is 1, 5%3 is
2).
- dividing or modulo'ing by 0 is undefined, and 5/10 is 0
- implementation dependency (check your compiler/library manual!) for cases
like 23 % -2 (what's the sign?) and -23/4 (-5 or -6)
- no exponentiation operator. Will not exist in C++. There's
library functions pow(double,double) and (maybe powf(float,float)) that are
defined in the standard math library (#include <math.h>).
- if you're worried about round-off in /, check out floor() and ceil() and
other such functions defined in <math.h>
- unary + and -
- These group RL, but ++i does not mean +(+i) because there's a++
operator (next).
- The unary + is "a historical accident and generally useless" (ARM
[[section]]5.3)
- the operand for - must be arithmetic, and integral promotion will
be carried out.
- ++ and -- (increment and decrement) in two flavors:
- prefix ++i
increment i (i = i+1); this expression
equals the new value of i
- and postfix q--
decrement q by 1 (q=q-1);
this equals the old value of q.
These operators
definitely have side-effects, which can be strung together:
a = i++ + j++ + k++; // well-defined
b = (i++ + (j++ + i++)); // no particular result guaranteed
The
second line is ambiguous because the parentheses do not change the order of
evaluation of each subexpression (when, precisely, is each of the values of
i in the second line determined?). The compiler will likely warn you
if it looks like you've introduced this kind of ambiguity.
"Side-effect" operators such as these are very useful (and very often used).
Some other types of expressions:
- function call. This groups LR:
f(g(h(x))) is (h(x) is the
argument of g(),g(h(x)) is the argument of f)
- relational and equality and logical operators
- relational: > >= < <= (greater than,
greater/equal to, ...)
- groups LR, but don't be fooled: a<b<c is NOT "(a<b)
AND (b<c)"
- both operands are arithmetic type
- resulting expression is of type int, and is equal to 1 (if the
expression is true) and 0 otherwise.
- there is a type bool in the proposed standard, implemented in the
GNU compiler, but not on the VAX compiler.
- there can be side effects in either operand
i++ > 0
The
conciseness of this expression can either increase or decrease the readability
of the code.
- > is not the same as >>. The latter is the shift operator
(bitwise-shift), or if overloaded for iostreams, the output operator.
- equality: == != (equal to, not equal to)
- groups LR, lower precedence than the relational operators, so
a < b
== c < d yields 1 iff both a<b and c<d are
true or false
- the single = is the assignment operator. the single ! is
the negation operator (below), so be careful of what you type (mistyping == as
= is a very common mistake, so compilers often flag warning errors).
- logical: && || (AND OR)
- && yields 1 if both its arithmetic arguments are non-zero, 0
otherwise
- || yields 1 if either operand is non-zero, 0 otherwise
- they group LR, lower precedence than relational/equality operators, and
the right operand of && is only evaluated if the left operand is
non-zero:
q > 0 && q != r && r/(q*(q-r)) >
0
nicely avoids a zero in either denominator factor. The operator
&& also works this way for efficiency - why evaluate the 2nd argument
if the 1st argument tells you the final result (0).
- there are bitwise operators & and | and ^, which are bitwise AND, OR,
and EOR. Be careful of leaving off the 2nd & or |.
- negation: !
- unary, groups RL, operand must be arithmetic
- argument type is arithmetic. Result is int, and is 1 if operand
is zero, 0 otherwise
With the relational, equality, and logical
operators defined, we can consider further types of statements.
- compound statement
- one or more statements enclose in {}. Defines its own local scope in the
enclosed block.
- selection statement
- one of:
if (expression) statement
if
(expression) statement else
statement
switch(expression) statement
where
statement can be a single line (but must not be a declaration), or any group of
lines in a compound statement.
Expression must be arithmetic type, and the statement following will be
executed iff expression is non-zero. Here's an example:
if (a > 0)
--q;
if (b > 0)
{
i= j+1;
f(i);
}
Exactly what you expect. You can chain these together:
if (a > 0)
;
else if (a==0)
{ // block statement!
f(a);
q(a);
}
else if (b > a)
f(b);
else
foo_func(a,b);
The
final "else" is not mandatory, since if it were absent, that would simply make
the preceeding "else if" be interpreted as "else (if (expression)
statement)".
I'll defer discussion of the switch statement for the moment; let's first check
out two of the three kinds of iteration statement, while and
do...while
The expression must be of arithmetic type. The statement gets executed as long
as the expression is non-zero, where the test is carried out before the
statement gets executed:
while (1)
; // infinite loop!
while (a > 0 && --a)
;
while (notdone())
{
// do stuff, hopefully someday affecting the return value of notdone()
}
Like the previous while loop, except that the test occurs after each
time the statement gets excecuted, guaranteeing that statement will be carried
out at least once. Note the final ';'
do
transform(a);
while (a > 0);
do
{
double q = sin(4.0);
// do other stuff
} while ( !done()) // note use of !
[ Phy 405/905 Home Page] [Lecturer ]