Monday, April 6, 2009

Declarations and declarators

A declaration is a list of names. The names are sometimes referred to as declarators or identifiers. The declaration begins with optional storage class specifiers, type specifiers, and other modifiers. The identifiers are separated by commas and the list is terminated by a semicolon.

Simple declarations of variable identifiers have the following pattern:

data-type var1” <=init1>, var2 <=init2>, ...;”

where var1, var2,... are any sequence of distinct identifiers with optional initializers. Each of the variables is declared to be of type data-type. For example,

int x = 1, y = 2;

creates two integer variables called x and y (and initializes them to the values 1 and 2, respectively).

These are all defining declarations; storage is allocated and any optional initializers are applied.

The initializer for an automatic object can be any legal expression that evaluates to an assignment-compatible value for the type of the variable involved. Initializers for static objects must be constants or constant expressions.

In C++, an initializer for a static object can be any expression involving constants and previously declared variables and functions

The format of the declarator indicates how the declared name is to be interpreted when used in an expression. If type is any type, and storage class specifier is any storage class specifier, and if D1 and D2 are any two declarators, then the declaration

storage-class-specifier type D1, D2;

indicates that each occurrence of D1 or D2 in an expression will be treated as an object of type type and storage class storage class specifier. The type of the name embedded in the declarator will be some phrase containing type, such as "type
," "pointer to type," "array of type," "function returning type," or "pointer to function returning type," and so on.

For example, in Declaration syntax examples each of the declarators could be used as rvalues (or possibly lvalues in some cases) in expressions where a single int object would be appropriate. The types of the embedded identifiers are derived from their declarators as follows

Declaration syntax examples

Declarator syntax Implied type of name Example

type name; type int count;
type name[]; (open) array of type int count[];
type name[3]; Fixed array of three elements, int count[3];
all of type (name[0], name[1], and name[2]
type *name; Pointer to type int *count;
type *name[]; (open) array of pointers to type int *count[];
type *(name[]); Same as above int *(count[]);

type (*name)[]; Pointer to an (open) array of type int (*count) [];
type &name; Reference to type (C++ only) int &count;
type name(); Function returning type int count();
type *name(); Function returning pointer to type int *count();
type *(name()); Same as above int *(count());
type (*name)(); Pointer to function returning type int (*count) ();
Storage class specifiers
Storage classes specifiers are also called type specifiers. They dictate the location (data segment, register, heap, or stack) of an object and its duration or lifetime (the entire running time of the program, or during execution of some blocks of code). Storage class can be established by the declaration syntax, by its placement in the source code, or by both of these factors.
The keyword mutable does not affect the lifetime of the class member to which it is applied.

The storage class specifiers in C++Builder are:

auto register
__declspec static
extern typedef
mutable

Arrays, structures, and unions

You initialize arrays and structures (at declaration time, if you like) with a brace-enclosed list of initializers for the members or elements of the object in question. The initializers are given in increasing array subscript or member order. You initialize unions with a brace-enclosed initializer for the first member of the union. For example, you could declare an array days, which counts how many times each day of the week appears in a month (assuming that each day will appear at least once), as follows:

int days[7] = { 1, 1, 1, 1, 1, 1, 1 }

The following rules initialize character arrays and wide character arrays:

You can initialize arrays of character type with a literal string, optionally enclosed in braces. Each character in the string, including the null terminator, initializes successive elements in the array. For example, you could declare

char name[] = { "Unknown" };

which sets up an eight-element array, whose elements are 'U' (for name[0]), 'n' (for name[1]), and so on (and including a null terminator).

You can initialize a wide character array (one that is compatible with wchar_t) by using a wide string literal, optionally enclosed in braces. As with character arrays, the codes of the wide string literal initialize successive elements of the array.

Here is an example of a structure initialization:

struct mystruct {

int i;
char str[21];
double d;

} s = { 20, "Borland", 3.141 };

Complex members of a structure, such as arrays or structures, can be initialized with suitable expressions inside nested braces.

Initializers

Initializers set the initial value that is stored in an object (variables, arrays, structures, and so on). If you don't initialize an object, and it has static duration, it will be initialized by default in the following manner:

To zero if it is an arithmetic type
To null if it is a pointer type

Note: If the object has automatic storage duration, its value is indeterminate.

Syntax for initializers

initializer
= expression
= {initializer-list} <,>}
(expression list)
initializer-list
expression
initializer-list, expression
{initializer-list} <,>}

Rules governing initializers

The number of initializers in the initializer list cannot be larger than the number of objects to be initialized.
The item to be initialized must be an object (for example, an array).
For C (not required for C++), all expressions must be constants if they appear in one of these places:

In an initializer for an object that has static duration.
In an initializer list for an array, structure, or union (expressions using sizeof are also allowed).

If a declaration for an identifier has block scope, and the identifier has external or internal linkage, the declaration cannot have an initializer for the identifier.
If a brace-enclosed list has fewer initializers than members of a structure, the remainder of the structure is initialized implicitly in the same way as objects with static storage duration.

Scalar types are initialized with a single expression, which can optionally be enclosed in braces. The initial value of the object is that of the expression; the same constraints for type and conversions apply as for simple assignments.

For unions, a brace-enclosed initializer initializes the member that first appears in the union's declaration list. For structures or unions with automatic storage duration, the initializer must be one of the following:

An initializer list (as described in Arrays, structures, and unions).
A single expression with compatible union or structure type. In this case, the initial value of the object is that of the expression.

The Fundamental Types

The fundamental type specifiers are built from the following keywords:

char __int8 long
double __int16 signed
float __int32 short
int __int64 unsigned

From these keywords you can build the integral and floating-point types, which are together known as the arithmetic types. The modifiers long, short, signed, and unsigned can be applied to the integral types. The include file limits.h contains definitions of the value ranges for all the fundamental types.



Integral types

char, short, int, and long, together with their unsigned variants, are all considered integral data types. Integral types shows the integral type specifiers, with synonyms listed on the same line.

Integral types

char, signed char Synonyms if default char set to signed.
unsigned char
char, unsigned char Synonyms if default char set to unsigned.
signed char
int, signed int
unsigned, unsigned int
short, short int, signed short int
unsigned short, unsigned short int
long, long int, signed long int
unsigned long, unsigned long int

Note: These synonyms are not valid in C++. See The three char types.

signed or unsigned can only be used with char, short, int, or long. The keywords signed and unsigned, when used on their own, mean signed int and unsigned int, respectively.

In the absence of unsigned, signed is assumed for integral types. An exception arises with char. C++Builder lets you set the default for char to be signed or unsigned. (The default, if you don't set it yourself, is signed.) If the default is set to unsigned, then the declaration char ch declares ch as unsigned. You would need to use signed char ch to override the default. Similarly, with a signed default for char, you would need an explicit unsigned char ch to declare an unsigned char.

Only long or short can be used with int. The keywords long and short used on their own mean long int and short int.

ANSI C does not dictate the sizes or internal representations of these types, except to indicate that short, int, and long form a nondecreasing sequence with "short <= int <= long." All three types can legally be the same. This is important if you want to write portable code aimed at other platforms.

In a C++Builder 32-bit program, the types int and long are equivalent, both being 32 bits. The signed varieties are all stored in two's complement format using the most significant bit (MSB) as a sign bit: 0 for positive, 1 for negative (which explains the ranges shown in 32-bit data types, sizes, and ranges). In the unsigned versions, all bits are used to give a range of 0 - (2n - 1), where n is 8, 16, or 32.

Floating-point types

The representations and sets of values for the floating-point types are implementation dependent; that is, each implementation of C is free to define them. C++Builder uses the IEEE floating-point formats.See the topic on ANSI implementation-specific.

float and double are 32- and 64-bit floating-point data types, respectively. long can be used with double to declare an 80-bit precision floating-point identifier: long double test_case, for example.

The table of 32-bit data types, sizes, and ranges indicates the storage allocations for the floating-point types

Standard arithmetic conversions

When you use an arithmetic expression, such as a + b, where a and b are different arithmetic types, C++Builder performs certain internal conversions before the expression is evaluated. These standard conversions include promotions of "lower" types to "higher" types in the interests of accuracy and consistency.

Here are the steps C++Builder uses to convert the operands in an arithmetic expression:

1. Any small integral types are converted as shown in Methods used in standard arithmetic conversions. After this, any two values associated with an operator are either int (including the long and unsigned modifiers), or they are of type double, float, or long double.
2. If either operand is of type long double, the other operand is converted to long double.
3. Otherwise, if either operand is of type double, the other operand is converted to double.

4. Otherwise, if either operand is of type float, the other operand is converted to float.
5. Otherwise, if either operand is of type unsigned long, the other operand is converted to unsigned long.
6. Otherwise, if either operand is of type long, then the other operand is converted to long.
7. Otherwise, if either operand is of type unsigned, then the other operand is converted to unsigned.
8. Otherwise, both operands are of type int.

The result of the expression is the same type as that of the two operands.

Methods used in standard arithmetic conversions

Type Converts to Method

char int Zero or sign-extended (depends on default char type)
unsigned char int Zero-filled high byte (always)
signed char int Sign-extended (always)
short int Same value; sign extended
unsigned short unsigned int Same value; zero filled
enum int Same value

Special char, int, and enum conversions

Note: The conversions discussed in this section are specific to C++Builder.

Assigning a signed character object (such as a variable) to an integral object results in automatic sign extension. Objects of type signed char always use sign extension; objects of type unsigned char always set the high byte to zero when converted to int.

Converting a longer integral type to a shorter type truncates the higher order bits and leaves low-order bits unchanged. Converting a shorter integral type to a longer type either sign-extends or zero-fills the extra bits of the new value, depending on whether the shorter type is signed or unsigned, respectively.

Type categories

The four basic type categories (and their subcategories) are as follows:

Aggregate

Array
struct
union
class (C++ only)

Function
Scalar

Arithmetic
Enumeration
Pointer
Reference (C++ only)

void

Types can also be viewed in another way: they can be fundamental or derived types. The fundamental types are void, char, int, float, and double, together with short, long, signed, and unsigned variants of some of these. The derived types include pointers and references to other types, arrays of other types, function types, class types, structures, and unions.

A class object, for example, can hold a number of objects of different types together with functions for manipulating these objects, plus a mechanism to control access and inheritance from other classes

Given any nonvoid type type (with some provisos), you can declare derived types as follows
Declaring types

Declaration Description

type t; An object of type type
type array[10]; Ten types: array[0] - array[9]
type *ptr; ptr is a pointer to type
type &ref = t; ref is a reference to type (C++)
type func(void); func returns value of type type
void func1(type t); func1 takes a type type parameter
struct st {type t1; type t2}; structure st holds two types

Note: type& var, type &var, and type & var are all equivalent.


Void
Syntax

void identifier

Description

void is a special type indicating the absence of any value. Use the void keyword as a function return type if the function does not return a value.

void hello(char *name)

{
printf("Hello, %s.",name);

}

Use void as a function heading if the function does not take any parameters.

int init(void)

{
return 1;

}


{

return 1;

}





Void Pointers

Generic pointers can also be declared as void, meaning that they can point to any type.

void pointers cannot be dereferenced without explicit casting because the compiler cannot determine the size of the pointer object.

Wednesday, April 1, 2009

Type Specifiers

The type determines how much memory is allocated to an object and how the program interprets the bit patterns found in the object's storage allocation. A data type is the set of values (often implementation-dependent) identifiers can assume, together with the set of operations allowed on those values.

The type specifier with one or more optional modifiers is used to specify the type of the declared identifier:

int i; // declare i as an integer

unsigned char ch1, ch2; // declare two unsigned chars

By long-standing tradition, if the type specifier is omitted, type signed int (or equivalently, int) is the assumed default. However, in C++, a missing type specifier can lead to syntactic ambiguity, so C++ practice requires you to explicitly declare all int type specifiers.

The type specifier keywords in C++Builder are:
char, float , signed, wchar_t,
class, int, struct,
double, long, union,
enum, short, unsigned,

Use the sizeof operators to find the size in bytes of any predefined or user-defined type.

Introduction to declaration syntax

All six interrelated attributes (storage classes, types, scope, visibility, duration, and linkage) are determined in diverse ways by declarations.

Declarations can be defining declarations (also known as definitions) or referencing declarations (sometimes known as nondefining declarations). A defining declaration, as the name implies, performs both the duties of declaring and defining; the nondefining declarations require a definition to be added somewhere in the program. A referencing declaration introduces one or more identifier names into a program. A definition actually allocates memory to an object and associates an identifier with that object.
Tentative definitions
The ANSI C standard supports the concept of the tentative definition. Any external data declaration that has no storage class specifier and no initializer is considered a tentative definition. If the identifier declared appears in a later definition, then the tentative definition is treated as if the extern storage class specifier were present. In other words, the tentative definition becomes a simple referencing declaration.

If the end of the translation unit is reached and no definition has appeared with an initializer for the identifier, then the tentative definition becomes a full definition, and the object defined has uninitialized (zero-filled) space reserved for it. For example,

int x;

int x; /*legal, one copy of x is reserved */
int y;
int y = 4; /* legal, y is initialized to 4 */
int z = 5;

int z = 6; /* not legal, both are initialized definitions */

Unlike ANSI C, C++ doesn't have the concept of a tentative declaration; an external data declaration without a storage class specifier is always a definition.
Possible declarations
The range of objects that can be declared includes

Variables
Functions
Classes and class members (C++)
Types
Structure, union, and enumeration tags
Structure members
Union members
Arrays of other types
Enumeration constants
Statement labels
Preprocessor macros

The full syntax for declarations is shown in Tables 2.1 through 2.3. The recursive nature of the declarator syntax allows complex declarators. You'll probably want to use typedefs to improve legibility.