nixawk / hello-c

c programming
9 stars 11 forks source link

c - DATA TYPES, OPERATORS, AND EXPRESSIONS #17

Open nixawk opened 7 years ago

nixawk commented 7 years ago

There are several general guidelines to follow when working with types:

nixawk commented 7 years ago

Variables

When declaring variables of the same type, declare each on a separate line unless the variables are self-explanatory and related, for example:

int year, month, day;

Add a brief comment to variable declarations:

int x; /* comment */
int y; /* comment */

Group related variables. Place unrelated variables, even of the same type, on separate lines.

int x, y, z;
int year, month, day;
nixawk commented 7 years ago

Constants

When defining constants, capitalize constant names and include comments. In constant definitions, align the various components, as shown in the examples below. In ANSI C, there are several ways to specify constants: const modifier, #define command, and enumeration data types.

Const Modifier

Use the const modifier as follows:

const int SIZE 32;      /* size in inches */
const int SIZE 16 + 16; /* both evaluate to the number 32 */

define Command

The #define preprocessor command instructs the preprocessor to replace subsequent instances of the identifier with the given string of tokens. It takes the form:

#define IDENTIFIER token-string

In general, avoid hard-coding numerical constants and array boundaries. Assign each a meaningful name and a permanent value using #define. This makes maintenance of large and evolving programs easier because constant values can be changed uniformly by changing the #define and recompiling.

#define NULL  0
#define EOS   '\0'
#define FALSE 0
#define TRUE  1

Using constant macros is a convenient technique for defining constants. They not only improve readability, but also provide a mechanism to avoid hard-coding numbers.

Enumeration Types

Enumeration types create an association between constant names and their values. Using this method (as an alternative to #define), constant values can be generated, or you can assign the values. Place one variable identifier per line and use aligned braces and indentation to improve readability. In the example below showing generated values, low would be assigned 0, middle 1, and high 2. When you assign values yourself, align the values in the same column, as shown in the second example.

Example: generated values

enum position
{
    LOW,
    MIDDLE,
    HIGH
};

Example: assigned values

enum stack_operation_result
{
FULL      = -2,
BAD_STACK = -1,
OKAY      = 0,
NOT_EMPTY = 0,
EMPTY     = 1
};

Simple Constants

Use the const modifier instead of the #define preprocessor to define simple constants. This is preferable because #define cannot be used to pass the address of a number to a function and because #define tells the preprocessor to substitute a token string for an identifier, which can lead to mistakes (as illustrated in the example below).

Example: using #define

#define SIZE 10 + 10 /* 10 + 10 will be substituted for SIZE */
...
area = SIZE * SIZE; /* this evaluates to 10 + 10 * 10 + 10 */
                    /* which is 10 + (10 * 10) + 10 = 120 */

Example: using the const modifier

const int SIZE = 10 + 10; /* SIZE evaluates to the number 20 */
...
area = SIZE * SIZE; /* this evaluates to 20 * 20 = 400 */
nixawk commented 7 years ago

Variable Definitions and Declarations

Numbers

Floating point numbers should have at least one number on each side of the decimal point:

0.5 5.0 1.0e+33

Start hexadecimal numbers with 0x (zero, lower-case x) and upper case A-F:

0x123 0xFFF

End long constants in upper-case L:

123L

Qualifiers

Always associate qualifiers (e.g., short, long, unsigned) with their basic data types:

short int x;
long int y;
unsigned int z;

Structures

The use of structures is one of the most important features of C. Structures enhance the logical organization of your code, offer consistent addressing, and will generally significantly increase the efficiency and performance of your programs.

Using common structures to define common elements allows the program to evolve (by adding another element to the structure, for example), and lets you modify storage allocation. For example, if your program processes symbols where each symbol has a name, type, flags, and an associated value, you do not need to define separate vectors.

Example: structures

typedef struct symbol
{
    char *name;
    int type;
    int flags;
    int value;
} symbol_type;
symbol_type symbol_table[NSYMB];

Automatic Variables

An automatic variable can be initialized either where it is declared or just before it is used. If the variable is going to be used close to where it is declared (i.e., less than one page later), then initialize it where it is declared. However, if the variable will be used several pages from where it is declared, then it is better practice to initialize it just before it is used.

Example: variable initialized where declared

int max = 0;
/* use of max is within a page of where it is declared */
for (i=0; i<n; i++)
    if (vec[i] > max)
        max = vec[i];

Example: variable initialized where used

Use an assignment statement just before the for loop:

int max;
...
/* several pages between declaration and use */
...
max = 0;
for (i=0 ; i<n ; i++)
    if (vec[i] > max)
        max = vec[i];

Or use the comma operator within the for loop:

int max;
...
/* several pages between declaration and use */
...
for (max = 0, i=0; i<n; i++)
    if (vec[i] > max)
        max = vec[i];
nixawk commented 7 years ago

Type Conversions and Casts

Type conversions occur by default when different types are mixed in an arithmetic expression or across an assignment operator. Use the cast operator to make type conversions explicit rather than implicit.

Example: explicit type conversion (recommended)

float f;
int i;
...
f = (int) i;

Example: implicit type conversion

float f;
int i;
...
f = i;
nixawk commented 7 years ago

Pointer Types

Explicitly declare pointer entities (variables, function return values, and constants) with pointer type. Put the pointer qualifier (*) with the variable name rather than with the type.

Example: pointer declaration

char *s, *t, *u;
nixawk commented 7 years ago

Pointer Conversions

Programs should not contain pointer conversions, except for the following:

nixawk commented 7 years ago

Operator Formatting

p->m    s.m    a[i]
exp(2, x)
!p    ~b    ++i    -n    *p    &x
(long) m
c1 = c2
z = (a > b) ? a : b;
strncat(t, s, n)
for (i = 0; i < n; ++i)
x + y        a < b && b < c
printf(fmt, a+1)
if ((a < b) && (c==d)) ...

If a is not < b, the compiler knows the entire expression is false so (c == d) is never evaluated. In this case, (c == d) is just a test/relational expression, so there is no problem. However, if the code is:

if ((a < b) && (c==d++))

d will only be incremented when (a < b) because of the same compiler efficiency demonstrated in the first example.

CAUTION: Avoid using side-effect operators within relational expressions. Even if the operators do what the author intended, subsequent reusers may question what the desired side-effect was.

for (i = 0, j = 1; i < 5; i++, j++);
if (p->next == NULL &&
    (total_count < needed) &&
    (needed <= MAX_ALLOT) &&
    (server_active(current_input)))
{
    statement_1;
    statement_2;
    statement_n;
}
nixawk commented 7 years ago

Assignment Operators and Expressions

C is an expression language. In C, an assignment statement such as “a = b” itself has a value that can be embedded in a larger context. We recommend that you use this feature very sparingly. The following example shows a standard C idiom with which most C programmers are familiar.

Example: embedded assignments

while ((c = getchar()) != EOF)
{
    statement_1;
    statement_2;
    statement_n;
}

Try to avoid assignments inside if-conditions (assignments inside while-conditions are ok). For example, don’t write this:

if ((foo = (char *) malloc (sizeof *foo)) == NULL)
  fatal ("virtual memory exhausted");

instead, write this:

foo = (char *) malloc (sizeof *foo);
if (foo == NULL)
  fatal ("virtual memory exhausted");

However, do not overdo embedding of multiple assignments (or other side-effects) in a statement. Consider the tradeoff between increased speed and decreased maintainability that results when embedded statements are used in artificial places.

Example: nonembedded statements

total = get_total ();
if (total == 10)
    printf(“goal achieved\n”);

Example: embedded statements (not recommended)

if ((total = get_total() == 10)
    printf(“goal achieved\n”)

References

  1. https://www.gnu.org/prep/standards/standards.html#Syntactic-Conventions
nixawk commented 7 years ago

Conditional Expressions

In C, conditional expressions allow you to evaluate expressions and assign results in a shorthand way. For example, the following if then else statement

if (a > b)
    z = a;
else
    z = b;

could be expressed using a conditional expression as follows:

z = (a > b) ? a : b;    /* z = max(a, b) */

While some conditional expressions seem very natural, others do not, and we generally recommend against using them. The following expression, for example, is not as readable as the one above and would not be as easy to maintain:

c = (a == b) ? d + f(a) : f(b) - d;

Do not use conditional expressions if you can easily express the algorithm in a more clear, understandable manner. If you do use conditional expressions, use comments to aid the reader’s understanding.

nixawk commented 7 years ago

Precedence and Order of Evaluation

Rather than trying to memorize the rules or look them up every time you need them, remember these simple guidelines from Steve Oualline’s C Elements of Style: