Data Types and Declarations

 

This section summarizes the basic data types, derived data types, enumerated data types, and typedef.Also summarized in this section is the format for declaring variables.

Declarations

When defining a particular structure, union, enumerated data type, or typedef, the compiler does not automatically reserve any storage.The definition merely tells the compiler about the particular data type and (optionally) associates a name with it. Such a definition can be made either inside or outside a function or method. In the former case, only the function or method knows of its existence; in the latter case, it is known throughout the remainder of the file.

After the definition has been made, variables can be declared to be of that particular data type.A variable that is declared to be of any data type will have storage reserved for it, unless it is an extern declaration, in which case it might or might not have storage allocated (see the section “Storage Classes and Scope”).

The language also enables storage to be allocated at the same time that a particular
structure, union, or enumerated data type is defined.This is done by simply listing the
variables before the terminating semicolon of the definition.

Basic Data Types

The basic Objective-C data types are summarized in Table B.3.A variable can be declared to be of a particular basic data type using the following format:

type name = initial_value;

The assignment of an initial value to the variable is optional and is subject to the rules summarized in the section “Variables.” More than one variable can be declared simultaneously using the following general format:

type name = initial_value, name = initial_value, .. ;

Before the type declaration, an optional storage class can also be specified, as summarized in the section “Variables.” If a storage class is specified and the type of the variable is int, then int can be omitted. For example

static counter;

declares counter to be a static int variable.
Table B.3 Summary of Basic Data Types


TypeMeaning
intInteger value; that is, a value that contains no decimal point; guaranteed to contain at least 32 bits of accuracy
short int Integer value of reduced accuracy; takes half as much memory as an
int on some machines; guaranteed to contain at least 16 bits of accuracy
long intInteger value of extended accuracy; guaranteed to contain at least 32 bits of accuracy
long long int Integer value of extra-extended accuracy; guaranteed to contain at least 64 bits of accuracy
unsigned int Positive integer value; can store positive values up to twice as large as an int; guaranteed to contain at least 32 bits of accuracy
float Floating-point value; that is, a value that can contain decimal places; guaranteed to contain at least six digits of precision
double Extended accuracy floating-point value; guaranteed to contain at least 10 digits of precision
long double Extra-extended accuracy floating-point value; guaranteed to contain at least 10 digits of precision
char Single character value; on some systems, sign extension can occur when used in an expression
unsigned char Same as char, except it ensures that sign extension will not occur as a result of integral promotion
signed char Same as char, except it ensures that sign extension will occur as a result of integral promotion
_BoolBoolean type; large enough to store the value 0 or 1
float _Complex Complex number
double _ComplexExtended accuracy complex number
long double_ComplexExtra-extended accuracy complex number
void No type; used to ensure that a function or method that does not return a value is not used as if it does return one, or to explicitly discard the results of an expression; also used as a generic pointer type (void *)

Note that the signed modifier can also be placed in front of the short int, int, long int,
and long long int types. Because these types are signed by default anyway, this has no effect.

_Complex and _Imaginary data types enable complex and imaginary numbers to be declared and manipulated, with functions in the library for supporting arithmetic on these types. Normally, you should include the file <complex.h> in your program, which defines macros and declares functions for working with complex and imaginary numbers. For example, a double_Complex variable c1 can be declared and initialized to the value 5 + 10.5i with a statement such as follows:

double _Complex c1 = 5 + 10.5 " I;

Library routines such as creal and cimag can then be used to extract the real and imaginary parts of c1, respectively.

An implementation is not required to support types _Complex and _Imaginary, and it can optionally support one but not the other.

Derived Data Types

A derived data type is one that is built up from one or more of the basic data types. Derived data types are arrays, structures, unions, and pointers (which include objects). A function or method that returns a value of a specified type is also considered a derived data type. Each of these, with the exception of functions and methods, is summarized in the following paragraphs. Functions and methods are separately covered in the sections
“Functions” and “Classes,” respectively.

Arrays

Single-Dimensional Arrays
Arrays can be defined to contain any basic data type or any derived data type.Arrays of functions are not permitted (although arrays of function pointers are). The declaration of an array has the following basic format:

type name[n] = { initExpression, initExpression, .. };

The expression n determines the number of elements in the array name and can be omitted, provided a list of initial values is specified. In such a case, the size of the array is determined based on the number of initial values listed or on the largest index element referenced if designated initializers are used.

Each initial value must be a constant expression if a global array is defined. Fewer values
can exist in the initialization list than there are elements in the array, but more cannot
exist. If fewer values are specified, only that many elements of the array are initialized—
the remaining elements are set to 0.

A special case of array initialization occurs in the case of character arrays, which can be
initialized by a constant character string. For example

char today[] = “Monday”;

declares today as an array of characters.This array is initialized to the characters ‘M’,‘o’,
'n', 'd', 'a', 'y', and '\0', respectively.

If you explicitly dimension the character array and don’t leave room for the terminating null, the compiler doesn’t place a null at the end of the array:

char today[6] = “Monday”;

This declares today as an array of six characters and sets its elements to the characters 'M', 'o', 'n', 'd', 'a', and 'y', respectively.

By enclosing an element number in a pair of brackets, specific array elements can be
initialized in any order. For example

int x = 1233;
int a[] = { [9] = x + 1, [2] = 3, [1] = 2, [0] = 1 };

defines a 10-element array called a (based on the highest index into the array) and initializes the last element to the value of x + 1 (1234) and the first three elements to 1, 2, and 3, respectively.


Variable-Length Arrays
Inside a function, method, or block, you can dimension an array using an expression containing variables. In that case, the size is calculated at runtime. For example, the function

int makeVals (int n)
{
int valArray[n];
...
}

defines an automatic array called valArray with a size of n elements, where n is evaluated
at runtime and can vary between function calls.Variable-length arrays cannot be initialized.

Multidimensional Arrays
The general format for declaring a multidimensional array follows:

type name[d1][d2]...[dn] = initializationList;

The array name is defined to contain d1 x d2 x...x dn elements of the specified type. For example

int three_d [5][2][20];

defines a three-dimensional array, three_d, containing 200 integers. A particular element is referenced from a multidimensional array by enclosing the desired subscript for each dimension in its own set of brackets. For example, the statement

three_d [4][0][15] = 100;

stores 100 into the indicated element of the array three_d.

Multidimensional arrays can be initialized in the same manner as one-dimensional arrays. Nested pairs of braces can be used to control the assignment of values to the elements in the array.

The following declares matrix to be a two-dimensional array containing four rows and three columns:

int matrix[4][3] =
{ { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };

Elements in the first row of matrix are set to the values 1, 2, and 3, respectively; in the second row they are set to 4, 5, and 6, respectively; and in the third row they are set to 7, 8, and 9, respectively. The elements in the fourth row are set to 0 because no values are specified for that row. The declaration

int matrix[4][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

initializes matrix to the same values because the elements of a multidimensional array are initialized in dimension order—that is, from leftmost to rightmost dimension.

The declaration

int matrix[4][3] = { { 1 }, { 4 }, { 7 } };

sets the first element of the first row of matrix to 1, the first element of the second row to 4, and the first element of the third row to 7.All remaining elements are set to 0 by default.

Finally, the declaration

int matrix[4][3] = { [0][0] = 1, [1][1] = 5, [2][2] = 9 };

initializes the indicated elements of the matrix to the specified values.

Structures

General Format:

struct name
{

memberDeclaration
memberDeclaration
...

} variableList;

The structure name is defined to contain the members as specified by each memberDeclaration. Each such declaration consists of a type specification followed by a
list of one or more member names.

Variables can be declared at the time that the structure is defined simply by listing them before the terminating semicolon, or they can subsequently be declared using the following format:

struct name variableList;

This format cannot be used if name is omitted when the structure is defined. In that case, all variables of that structure type must be declared with the definition.

The format for initializing a structure variable is similar to that for arrays. Its members can be initialized by enclosing the list of initial values in a pair of curly braces. Each value in the list must be a constant expression if a global structure is initialized.

The declaration

struct point
{
  float x;
  float y;
} start = {100.0, 200.0};

defines a structure called point and a struct point variable called start with initial values as specified. Specific members can be designated for initialization in any order with the notation

.member = value

in the initialization list, as in

struct point end = { .y = 500, .x = 200 };

The declaration

struct entry
{
  char *word;
  char *def;
} dictionary[1000] = {
  { “a”, “first letter of the alphabet” },
  { “aardvark”, “a burrowing African mammal” },
  { “aback”, “to startle” }
};

declares dictionary to contain 1,000 entry structures, with the first 3 elements initialized to the specified character string pointers. Using designated initializers, you could have also written it like this:

struct entry
{
  char *word;
  char *def;
} dictionary[1000] = {
  [0].word = “a”, [0].def = “first letter of the alphabet”,
  [1].word = “aardvark”, [1].def = “a burrowing African mammal”,
  [2].word = “aback”, [2].def = “to startle”
};

or equivalently like this:

struct entry
{
  char *word;
  char *def;
} dictionary[1000] = {
  { {.word = “a”, .def = “first letter of the alphabet” },
    {.word = “aardvark”, .def = “a burrowing African mammal”} ,
    {.word = “aback”, .def = “to startle”}
};

An automatic structure variable can be initialized to another structure of the same type
like this:

struct date tomorrow = today;

This declares the date structure variable tomorrow and assigns to it the contents of the
(previously declared) date structure variable today.

A memberDeclaration that has the format

type fieldName : n

defines a field that is n bits wide inside the structure, where n is an integer value. Fields
can be packed from left to right on some machines and right to left on others. If
fieldName is omitted, the specified number of bits is reserved but cannot be referenced. If
fieldName is omitted and n is 0, the field that follows is aligned on the next storage unit
boundary, where a unit is implementation-defined.The type of a field can be int,
signed int, or unsigned int. It is implementation-defined whether an int field is
treated as signed or unsigned.The address operator (&) cannot be applied to a field, and
arrays of fields cannot be defined.

Unions

General Format:
union name
{

memberDeclaration
memberDeclaration
...

} variableList;
This defines a union called name with members as specified by each memberDeclaration. Each member of the union shares overlapping storage space, and the compiler ensures that enough space is reserved to contain the largest member of the union.

Variables can be declared at the time that the union is defined, or they can be subsequently declared using the notation

union name variableList;

provided the union was given a name when it was defined.

It is the programmer’s responsibility to ensure that the value retrieved from a union is consistent with the last value stored inside the union.The first member of a union can be initialized by enclosing the initial value, which, in the case of a global union variable, must be a constant expression, inside a pair of curly braces:

union shared
{
long long int l;
long int w[2];
} swap = { 0xffffffff };

A different member can be initialized instead by specifying the member name, as in

union shared swap2 = {.w[0] = 0x0, .w[1] = 0xffffffff};

This declares the union variable swap and sets the l member to hexadecimal ffffffff.

An automatic union variable can also be initialized to a union of the same type, as in

union shared swap2 = swap;

Pointers

The basic format for declaring a pointer variable is as follows:

type *name;

The identifier name is declared to be of type “pointer to type,” which can be a basic
data type or a derived data type. For example

int *ip;

declares ip to be a pointer to an int, and the declaration

struct entry *ep;

declares ep to be a pointer to an entry structure. If Fraction is defined as a class, the
declaration

Fraction *myFract;

declares myFract to be an object of type Fraction—or more explicitly, myFract is used to hold a pointer to the object’s data structure after an instance of the object is created and assigned to the variable.

Pointers that point to elements in an array are declared to point to the type of element contained in the array. For example, the previous declaration of ip would also be used to declare a pointer into an array of integers.

More advanced forms of pointer declarations are also permitted. For example, the declaration

char *tp[100];

declares tp to be an array of 100 character pointers, and the declaration

struct entry (*fnPtr) (int);

declares fnPtr to be a pointer to a function that returns an entry structure and takes a single int argument.

A pointer can be tested to see whether it’s null by comparing it against a constant expression whose value is 0. The implementation can choose to internally represent a null pointer with a value other than 0. However, a comparison between such an internally represented null pointer and a constant value of 0 must prove equal.

The manner in which pointers are converted to integers and integers are converted to pointers is machine-dependent, as is the size of the integer required to hold a pointer.

The type “pointer to void” is the generic pointer type.The language guarantees that a pointer of any type can be assigned to a void pointer and back again without changing its value.

The type id is a generic object pointer.Any object from any class can be assigned to
an id variable, and vice versa.

Other than these two special cases, assignment of different pointer types is not permitted
and typically results in a warning message from the compiler if attempted.

Enumerated Data Types

General Format:

enum name { enum_1, enum_2, .. } variableList;

The enumerated type name is defined with enumeration values enum_1,enum_2,...,
each of which is an identifier or an identifier followed by an equals sign and a constant
expression.variableList is an optional list of variables (with optional initial values) declared
to be of type enum name.

The compiler assigns sequential integers to the enumeration identifiers starting at 0. If an identifier is followed by = and a constant expression, the value of that expression is assigned to the identifier. Subsequent identifiers are assigned values beginning with that constant expression plus one. Enumeration identifiers are treated as constant integer values by the compiler.

If you want to declare variables to be of a previously defined (and named) enumeration type, you can use the following construct:

enum name variableList;

A variable declared to be of a particular enumerated type can be assigned only a value of the same data type, although the compiler might not flag this as an error.

typedef

The typedef statement is used to assign a new name to a basic or derived data type. The typedef does not define a new type but simply a new name for an existing type. Therefore, variables declared to be of the newly named type are treated by the compiler exactly as if they were declared to be of the type associated with the new name.

In forming a typedef definition, proceed as though a normal variable declaration were being made.Then, place the new type name where the variable name would normally appear. Finally, in front of everything, place the keyword typedef. As an example,

typedef struct
{
  float x;
  float y;
} POINT;

associates the name POINT with a structure containing two floating-point members called
x and y. Variables can subsequently be declared to be of type POINT, like so:

POINT origin = { 0.0, 0.0 };

Type Modifiers: const, volatile, and restrict

The keyword const can be placed before a type declaration to tell the compiler the value cannot be modified. So, the declaration

const int x5 = 100;

declares x5 to be a constant integer. (That is, it won’t be set to anything else during the program’s execution.) The compiler is not required to flag attempts to change the value of a const variable.

The volatile modifier explicitly tells the compiler that the value changes (usually dynamically). When a volatile variable is used in an expression, its value is accessed each
place it appears.

To declare port17 to be of type “volatile pointer to char,” you would write this line:

char *volatile port17;

The restrict keyword can be used with pointers. It is a hint to the compiler for optimization (similar to the register keyword for variables). The restrict keyword specifies
to the compiler that the pointer will be the only reference to a particular object—that is, it will not be referenced by any other pointer within the same scope. The lines

int * restrict intPtrA;
int * restrict intPtrB;

tell the compiler that, for the duration of the scope in which intPtrA and intPtrB are defined, they will never access the same value.Their use for pointing to integers (in an array, for example) is mutually exclusive.