Previous | Table of Contents | Next |
When the storage-class typedef appears in a declaration, as in
typedef int integer;
it indicates that the identifier, in this case, integer, is not to be declared as an object or function, but is rather to become a synonym for the type described by the rest of the declaration. In this example, the identifier integer becomes a synonym for the type int, and can thereafter (as long as the name integer remains in scope) be used in other declarations and elsewhere, just as a built-in type could:
integer i; integer a[10];
Type definitions are most useful in building complicated derived types (see section 3.6.7). They may also be useful for documentation and software engineering purposes.
Type definitions create synonyms or aliases; they do not create new types, and the compiler is not obligated to diagnose mismatches. This means that when they are used for documentation and software engineering purposes, they are essentially advisory, not enforced. For example, if the programmer wishes to define two distinct classes of integers named pyrus and citrus
typedef int pyrus; typedef int citrus;
and then to declare variables that are to hold values of these classes
pyrus apple; citrus orange;
an assignment such as
apple = orange;
though presumably meaningless and undesirable in the programmers type scheme, would not necessarily be diagnosed by the compiler.
The language predefines a few auxiliary types that are typically implemented using typedef and that provide good examples of its use. size_t, for instance, is an unsigned integer type used for holding the size, in bytes, of objects and other memory regions. It might be implemented as
typedef unsigned int size_t;
on some machines, and as
typedef unsigned long int size_t;
on others, depending on architectural considerations. However, code that declares variables, function parameters, and so on as being of type size_t should work without change even when compiled on different machines. Other examples of predefined types are FILE, time_t, ptrdiff_t, and wchar_t (see sections 3.10.1, 3.10.6, 3.10.11, and 3.10.17).
As far as the compiler is concerned, the primary purpose of an expression in C is to compute a new value. The programmer is more concerned with side effects; in the process of computing a value, most expressions assign new values to variables or other objects, or call functions that perform I/O or have other lasting, visible effects.
Cs expression syntax is relatively rich, with a large number of operators. It is also quite general: With a few exceptions, anywhere that an expression may appear, it may be an arbitrarily complicated expression. C programmers routinely make use of this flexibility, preferring to construct large expressions that compute values and work with them immediately rather than, say, assigning them to temporary variables to be used in succeeding expressions.
So as to appreciate the potential complexity of C expressions, neither getting lost in them nor falsely imagining any unnecessary restrictions, it is useful to think of them recursively: An expression is either a simple, primitive expression, or it is an expression built up from subexpressions (which are, of course, themselves expressions) and operators. The size and complexity of an expression is therefore, in principle, infinite: Any expression, no matter how complicated, can always be treated as a subexpression and combined with more operators and other subexpressions to form an even larger expression.
The first expressions to look at are the primary expressions, which can be thought of as beginning the recursive process of building expressions. (Roughly speaking, a primary expression is one of the simple, primitive expressions informally referred to previously.) Next, we cover most of the operators that can be used to build expressions out of primary expressions or subexpressions. (The remaining few operators are discussed in later sections on pointers and data structures.)
The most basic, primitive expression, perhaps the most fundamental input for any computation, is a constant, of any of the forms described in section 3.2.2. That is, the forms
1 023 0x45 678L 9 ten
which you might first think of as constants, are also simple expressions, and can be used as such.
Almost as basic as a primary expression is the name (or identifier) of a variable. When the name of a variable appears in an expression in a context in which its value is needed, the interpretation is the obvious one: The value of the variable is fetched.
Also in the class of primary expressions are array references. The syntax a[0] refers to element number 0 of the array a; a[1] refers to element number 1; a[i] refers to element number i, and so on. Like constants and simple variable references, these array references can appear anywhere in expressions, and when they appear in contexts in which a value is required, the indicated element of the array is fetched. The subscript within the brackets can itself be an arbitrarily complicated expression; we may refer to not only a[0] or a[i] but also more complicated references such as a[b + f(c*d + 1, e)] or even a[b[c]].
A function call is also a primary expression, and contributes to the surrounding expression the value returned by the function. The arguments passed to the function, if any, may also be arbitrarily complicated subexpressions.
Previous | Table of Contents | Next |