C Tutorial - Chapter 4


Throughout this chapter, references are given to various ranges of variables. This refers to the range of values that can be stored in any given variable. Your compiler may use a different range for some of the variables since the ANSI standard does not define specific limits for all data types. Consult the documentation for your compiler for the exact range of each of the variable types.


Example program ------> INTASIGN.C

Load the file named INTASIGN.C and display it for an example of assignment statements. Three variables are defined for use in the program and the remainder of the program is merely a series of illustrations of various kinds of assignment statements. All three variables are defined on one line and have unknown values stored in them initially.

The first two lines of the assignment statements, lines 8 and 10, assign numerical values to the variables named a and b, and the next five lines illustrate the five basic arithmetic functions and how to use them. The fifth is the modulo operator and gives the remainder if the two variables were divided. It can only be applied to integral type variables, which will be defined later. Lines 15 and 16 illustrate how to combine some of the variables in relatively complex math expressions. All of the above examples should require no comment except to say that none of the equations are meant to be particularly useful except as illustrations.

Precedence of operators is a very important topic that you will need to study in detail at some point, but for now we will only need a few rules. When you have mixed arithmetic expressions, the multiplication and division operations are completed before the addition and subtraction operations when they are all at the same logical level. Therefore when evaluating a * b + c / d, the multiplication and division are done first, then the addition is performed. However in the expression a * (b + c / d), the addition follows the division, but preceeds the multiplication because the operations are at two different logical levels as defined by the parentheses.

The expressions in lines 17 and 18 are perfectly acceptable as given, but we will see later in this chapter that there is another way to write these for more compact code.


This brings us to lines 20 and 21 which may appear to you as being very strange. The C compiler scans the assignment statement from right to left, (which may seem a bit odd since we do not read that way), resulting in a very useful construct, namely the one given here. The compiler finds the value 20, assigns it to c, then continues to the left finding that the latest result of a calculation should be assigned to b. Thinking that the latest calculation resulted in a 20, it assigns that value to b also, and continues the leftward scan assigning the value 20 to a also. This is a very useful construct when you are initializing a group of variables. The statement in line 21 illustrates that it is possible to actually do some calculations to arrive at the value which will be assigned to all three variables. The values of a, b, and c, prior to the beginning of the statement in line 21 are used to calculate a value, which is then assigned to each of the three variables.

As an aid to understanding, line 23 is given which contains parentheses to group the terms together in a meaningful way. Lines 20 and 23 are identical statements.

The program has no output, so compiling and executing this program will be very uninteresting. Since you have already learned how to display some integer results using the printf() function, it would be to your advantage to add some output statements to this program to see if the various statements do what you think they should do. You will need to add #include <stdio.h> to the beginning of the program if you are going to add printf() statements to the program.

You can add your own assignment statements also to gain experience with them.


This would be a good time for a preliminary definition of a rule to be followed in C. The variable definitions are always given before any executable statements in any program block. This is why the variables are defined at the beginning of a block in this program and in every C program. If you try to define a new variable after some executable statements, your compiler will issue an error. A program block is any unit of one or more statements surrounded by braces. Actually, the block can even be empty but then there is no real need for it, except as a placeholder in early phases of code development. More will be said about blocks later.


Example program ------> MORTYPES.C

Loading and editing MORTYPES.C will illustrate how some additional data types can be used. Once again we have defined a few integer type variables which you should be fairly familiar with by now, but we have added two new types, the char, and the float.

The char type of data is nearly the same as the integer except that it can only be assigned numerical values between -128 and 127 on most microcomputer implementations of C, since it is usually stored in one byte of memory. Some implementations of C use a larger memory element for a char and will therefore cover a wider range of usable values. The char type of data is usually used for ASCII data, more commonly known as text. The text you are reading was originally written on a computer with a word processor that stored the words in the computer one character per byte. In contrast, the int data type is stored in two bytes of computer memory on nearly all microcomputers, but can be larger on some machines. In fact, most modern microcomputers are 32 bit machines that store an int in four bytes.

Keep in mind that, even though the char type variable was designed to hold a representation of an ASCII character, it can be used very effectively to store a very small value if desired. Much more will be discussed on this topic in chapter 7 when we discuss strings.


It would be profitable at this time to discuss the way C handles the two types char and int. Most operations in C that are designed to operate with integer type variables will work equally well with character type variables because they are an integral variable, which means that they have no fractional part. Those operations, when called on to use a char type variable, will actually promote the char data into integer data before using it. For this reason, it is possible to mix char and int type variables in nearly any way you desire. The compiler will not get confused, but you might. It is good not to rely on this too much, but to carefully use only the proper types of data where they should be used.

The second new data type is the float type of data, commonly called floating point data. This is a data type which usually has a very large range, a relatively large number of significant digits, and a large number of computer words are required to store it. The float data type has a decimal point associated with it and several bytes of memory are required to store a single float type variable.


The first three lines of the program assign values to all nine of the defined variables so we can manipulate some of the data between the different types.

Since, as mentioned above, a char data type is in reality an integral data type which is automatically promoted to int when necessary, no special considerations need be taken to promote a char to an int, and a char type data field can be assigned to an int variable. When going the other way, an int type variable can be assigned to a char type variable and will translate correctly to a char type variable if the value is within the range of the char, possibly -128 to 127. If the value is outside of the range of char, most C compilers simply truncate the most significant bits and use the least significant bits.

Line 16 illustrates the simplicity of translating an int into a float. Simply assign it the new value and the system will do the proper conversion. When converting from float to int however, there is an added complication. Since there may be a fractional part of the floating point number, the system must decide what to do with it. By definition, it will truncate it and throw away the fractional part.

This program produces no output, and we haven't covered a way to print out char and float type variables, so you can't really get in to this program and play with the results. The next program will cover these topics for you.

Be sure to compile and run this program after you are sure you understand it completely. Note that the compiler may issue warnings about type conversions when compiling this program. They can be ignored because of the small values we are using to illustrate the various type conversions.


This list gives you some typical values for the various types available in C. Your compiler may offer different limits and sizes since there is a lot of latitude in what a compiler may offer. The values in this list are for Microsoft Visual C++ version 1.5 (16 bits) and Visual C++ version 2.0 (32 bits).

Type Name         Bytes   Range
  ------------- 16 bit system -------------
char              1       -128 to 127
signed char       1       -128 to 127
unsigned char     1       0 to 255
short             2       -32,768 to 32,767
unsigned short    2       0 to 65,535
int               2       -32,768 to 32,767
unsigned int      2       0 to 65,535
long              4       -2,147,483,648 to 2,147,483,647
unsigned long     4       0 to 4,294,967,295
float             4       3.4E+/-38 (7 digits)
double            8       1.7E+/-308 (15 digits)
long double      10       1.2E+/-4932 (19 digits)

  ------------- 32 bit system -------------
char              1       -128 to 127
signed char       1       -128 to 127
unsigned char     1       0 to 255
short             2       -32,768 to 32,767
unsigned short    2       0 to 65,535
int               4       -2,147,483,648 to 2,147,483,647
unsigned int      4       0 to 4,294,967,295
long              4       -2,147,483,648 to 2,147,483,647
unsigned long     4       0 to 4,294,967,295
float             4       3.4E+/-38 (7 digits)
double            8       1.7E+/-308 (15 digits)
long double      10       1.2E+/-4932 (19 digits)

The diligent student will notice that the only difference in these two lists are in the sizes and ranges of the int type variables, both signed and unsigned. The ANSI-C standard says that an int type has the "natural size suggested by the architecture of the execution environment", so the ranges for the compiler listed above matches the standard exactly.

One other point about the above table must be made at this time. The unadorned char is permitted to be either signed or unsigned at the discretion of the compiler writer. The writers of the Microsoft compiler chose to make char default to a signed char, as do most compiler writers, but you have a choice since most compilers provide a switch to select the default to unsigned char.

Some useful constants are available for your use in determining the range limits of the standard types. For example, the names INT_MIN and INT_MAX are available in the file "limits.h" as constants which can be used in your code. INT_MAX is the largest possible number that can be stored in an int type variable using the compiler that you are currently using. When you switch to a new compiler, which you will almost certainly do someday, INT_MAX will refer to the largest value that can be stored with that compiler. Even if you switch to a new operating system with 64 bits or even 128 bits, INT_MAX will still refer to the largest int available on your new system. The file "limits.h" contains a large number of such limits, all of which are available for your use simply by including the file in your program. It is a text file which can be opened in any editor and studied, a highly recommended exercise for you at this time.


Example program ------> LOTTYPES.C

Load the file LOTTYPES.C and display it on your screen. This file contains most of the standard simple data types available in the programming language C. Consult your compiler documentaion for a complete list of all types avialable with your compiler. There are other types, but they are the compound types (ie - arrays and structures) that we will cover in due time in this tutorial.

Observe the file. First we define a simple int, followed by a long int . Next we have a short int which has a range that may be identical to that for the int variable. The unsigned is next and is defined as the same size as the int but with no sign. It should be pointed out that when the long, short, or unsigned is desired, the int is optional and is left out by most experienced programmers. Your compiler may differ significantly from the ranges given in the above table, so you should check the documentation for your compiler for the exact ranges for each type.

The double is a floating point number but covers a greater range than the float and has more significant digits for more precise calculations. It also requires more memory to store a value than the simple float. The long double will cover a much larger range and store more significant digits, but it will also take longer to do calculations because of the increased size of data being used.

Another diversion is in order at this point. Your compiler probably has no provision for floating point math, only double floating point math. It will promote a float to a double before doing calculations and therefore only one math library will be needed. Of course, this is transparent to you, so you don't need to worry about it. Because of this, you may think that it would be best to simply define every floating point variable as double, since they are promoted before use in any calculations, but that may not be a good idea. A float variable may require only 4 bytes of storage and a double may require 8 bytes of storage, so if you have a large volume of floating point data to store, the double will obviously require much more memory. If you don't need the additional range or significant digits, you should use the float type rather than the double. The compiler makes all floating point literals, such as the value 3.14159 in line 19, double constants by default. Some compilers will then issue a warning about line 19 because we are assigning a double to a float. You can safely ignore the warning at this time.

After defining the data types in the program under consideration, a numerical value is assigned to each of the defined variables in order to demonstrate the means of outputting each to the monitor.


As any programming language evolves, additional constructs are added to fill some previously overlooked need. Two new keywords have been added to C with the release of the ANSI-C standard. They are not illustrated in example programs, but they will be discussed here. The two new keywords are const and volatile and are used to tell the compiler that variables of these types will need special consideration. A constant is declared with the const keyword and declares a value that cannot be changed by the program. If you inadvertently try to modify an entity defined as a const, the compiler will generate an error. This is an indication to you that something is wrong. Declaring an entity as const allows the optimizer to do a better job which could make your program run a little faster. Since constants can never have a value assigned to them in the executable part of the program, they must always be initialized. If volatile is used, it declares a value that may be changed by the program but it may also be changed by some outside influence such as a clock update pulse incrementing the stored value. This prevents the optimizer from getting too ambitious and optimizing away something that it thinks will never be changed.

Examples of use in declaring constants of these two types are given as;

     const int index1 = 2;
     const index2 = 6;
     const float big_value = 126.4;
     volatile const int index3 = 12;
     volatile int index4;


Following is a list of some of the conversion characters and the way they are used in the printf() statement. A complete list of all of the conversion characters should be included with the documentation for your compiler. You do not need to understand all of these at this time, but you should know that there is a lot of flexibility available when you are ready to use it.

      d    decimal notation
      i    decimal notation (new ANSI standard extension)
      o    octal notation
      x    hexadecimal notation
      u    unsigned notation
      c    character notation
      s    string notation
      f    floating point notation

Each of these is used following a percent sign to indicate the type of output conversion desired. The following fields may be added between those two characters.

     -     left justification in its field
     (n)   a number specifying minimum field width
     .     to separate n from m
     (m)   significant fractional digits for a float
     l     to indicate a long

These are all used in the examples which are included in the program named LOTTYPES.C, with the exception of the string notation which will be covered later in this tutorial. Lines 33 through 35 illustrate how to set the field width to a desired width, and lines 39 and 40 illustrate how to set the field width under program control. The field width for the float type output in lines 43 through 47 should be self explanatory. Compile and run this program to see what effect the various fields have on the output.

You now have the ability to display any of the data fields in the previous programs and it would be to your advantage to go back and see if you can display some of the fields anyway you desire.


Example program ------> COMBINE.C

Examine the file named COMBINE.C for examples of combining variables of the various types in a program. Many times it is necessary to multiply an int type variable times a float type variable and C allows this by providing a strict set of rules it will follow in order to do such combinations.

Five variables of three different types are declared in lines 4 through 6, and three of them are initialized so we have some data to work with. Line 8 gives an example of adding an int variable to a float variable and assigning the result to a char type variable. The cast is used to control the type of addition and is indicated by defining the desired type within parentheses in front of the variable as shown. This forces each of the two variables to the char type prior to doing the addition. In some cases, when the cast is used, the actual bit patterns must be modified internally in order to do the type coercion. Lines 9 through 11 perform the same addition by using different kinds of type casting to achieve the final result. Note that the addition is not the same in all three cases because the addition is done using different types, so could conceivably result in different answers.

Lines 13 through 15 illustrate the use of the cast to multiply two float variables. In two of the cases the intermediate results are cast to the int type, with the result being cast back to the float type. The observant student will notice that these three lines will not necessarily produce the same result.

Be sure to compile and execute this program. When you do, you may get a lot of type conversion warnings which can be ignored at this point. In this program, we are illustrating things that can be done with no regard to whether it is good to do so in a production program. Note that all of the warnings can be eliminated by including the proper cast when we use different types.


Example program ------> COMPARES.C

Load and view the file named COMPARES.C for many examples of compare statements in C. We begin by defining and initializing nine variables to use in the following compare statements.

The first group of compare statements represents the simplest kinds of compares because they simply compare two variables. Either variable could be replaced with a constant and still be a valid compare, but using two variables for the compare is the general case. The first compare checks to see if the value of x is equal to the value of y and it uses the double equal sign for the comparison. Since x is equal to y, the variable z will be assigned the value of -13. A single equal sign could be used here but it would have a different meaning as we will see shortly. The second comparison checks to see if the current value of x is greater than the current value of z.

The third compare introduces the not operator, the exclamation, which can be used to invert the result of any logical compare. The fourth checks for the value of b less than or equal to the value of c, and the last checks for the value of r not equal to the value of s. As we learned in the last chapter, if the result of the compare is true, the statement following the if clause will be executed and the results are given in the comments.

Note that "less than" and "greater than or equal to" are also available, but are not illustrated here.

It would be well to mention the different format used for the if statement in this example program. A carriage return is not required as a statement separator and by putting the conditional clause on the same line as the if, it adds to the readability of the overall program in this case.


The compares in the second group are a bit more involved. Starting with the first compare, we find a rather strange looking set of conditions in the parentheses. To understand this we must understand just what a true or false is in the C language. A false is defined as a value of zero, and true is defined as any non-zero value. Any integer or character type of variable can be used for the result of a true/false test, or the result can be an implied integer or character.

Look at the first compare of the second group of compare statements. The conditional expression "r != s" will evaluate as a true since the value of r was set to 0.0 in line 13, so the result of the compare will be a non-zero value. With all ANSI-C compilers, it will be set to a 1. Good programming practice would be to not use the resulting 1 in any calculations, but only for logical control. Even though the two variables that are compared are float variables, the logical result will be of type int. There is no explicit variable to which it will be assigned so the result of the compare is an implied int. Finally, the resulting number, is assigned to the integer variable x. If double equal signs were used, the phantom value, namely 1, would be compared to the value of x, but since the single equal sign is used, the value 1 is simply assigned to the variable named x, as though the statement were not in parentheses. Finally, since the result of the assignment in the parentheses was non-zero, the entire expression is evaluated as true, and z is assigned the value of 1000. Thus we accomplished two things in this statement, we assigned x a new value, and we assigned z the value of 1000. We covered a lot in this statement so you may wish to review it before going on. The important things to remember are the values that define true and false, and the fact that several things can be assigned in a conditional statement. The value assigned to the variable x was probably a 1, but remember that the only requirement is that it is nonzero. The ANSI-C standard says that the result of a comparison operation, ( >, >=, <, or <=) must be 1 or 0, but does not state the result of an equality operation. If you assume 0 or 1 will be returned, and only use it for control, you will not get into trouble.

The example in line 20 should help clear up some of the above in your mind. In this example, x is assigned the value of y, and since the result is 11, the condition is non-zero, which is true, and the variable z is assigned 222.

The third example of the second group in line 21, compares the value of x to zero. If the result is true, meaning that if x is not zero, then z is assigned the value of 333, which it will be. The last example in this group illustrates the same concept, since the result will be true if x is non-zero. The compare to zero in line 21 is not actually needed and the result of the compare is true. The third and fourth examples of this group are therefore logically identical. Of course we assign a different value to z in each case.


The third group of compares will introduce some additional concepts, namely the logical "and" and the logical "or" operators. We assign the value of 77 to the three integer variables simply to get started again with some defined values. The first compare of the third group contains the new control &&, which is the logical "and" which results in a true if both sides of the "and" are true. The entire statement reads, if x equals y and if x equals 77 then the result is true. Since this is true, the variable z is set equal to 33. Note that only integral types can be "anded", so float and double types cannot be used here.

The next compare in this group introduces the || operator which is the logical "or" operator which results in a true if either side of the "or" is true. The statement reads, if x is greater than y or if z is greater than 12 then the result is true. Since z is greater than 12, it doesn't matter if x is greater than y, because only one of the two conditions must be true for the result to be true. The result is true, therefore z will be assigned the value of 22. Once again, float and double cannot be "ored".


When a compound expression is evaluated, the evaluation proceeds from left to right and as soon as the result of the outcome is assured, evaluation stops. Therefore, in the case of an "and" evaluation, when one of the terms evaluates to false, evaluation is discontinued because additional true terms cannot make the result ever become true. In the case of an "or" evaluation, if any of the terms is found to be true, evaluation stops because it will be impossible for additional terms to cause the result to be false. In the case of additionally nested terms, the above rules will be applied to each of the nested levels. This is called short-circuit evaluation since the remaining terms are not evaluated.

Going on to the next example in group three in line 29, we find three simple variables used in the conditional part of the compare. Since all three are non-zero, all three are true, and therefore the "and" of the three variables is true, leading to the result being true, and z is assigned the value of 11. Note that since the variables, r, s, and t are float type variables, they could not be used this way.

Continuing on to line 30 we find three assignment statements in the compare part of the if statement. If you understood the above discussion, you should have no difficulty understanding that the three variables are assigned their respective new values, and the result of all three are non-zero, leading to a resulting value of true.


The last example of the third group contains a bit of a trick, but since we have covered it above, it is nothing new to you. Notice that the first part of the compare evaluates to false since x is not currently 2. The remaining parts of the compare are not evaluated, because it is a logical "and" so it will definitely be resolved as a false because the first term is false. If the program was dependent on the value of y being set to 3 in the next part of the compare, it will fail because evaluation will cease following the false found in the first term. Likewise, the variable named z will not be set to 4, and the variable r will not be changed. This is because C uses short circuit evaluation as discussed earlier.


The last group of compares illustrate three possibilities for getting into a bit of trouble. All three have the common result that the variable z will not be handled properly, but for different reasons. In line 37, the compare evaluates as true, but the semicolon following the second parentheses terminates the if clause, and the assignment statement involving z is always executed as the next statement. The if therefore has no effect because of the misplaced semicolon. This is actually a null statement and is legal in C, but the programmer probably did not intend to include the extra semicolon.

The statement in line 38 is much more straightforward because the variable x will always be equal to itself, therefore the inequality will never be true, and the entire statement will never do a thing, but is wasted effort. The statement in line 39 will always assign 0 to x and the compare will therefore always be false, never executing the conditional part of the if statement.

The conditional statement is extremely important and must be thoroughly understood to write efficient C programs. If any part of this discussion is unclear in your mind, restudy it until you are confident that you understand it thoroughly before proceeding onward. Compile and run this program. You may gets lots of conversion warnings which you can either ignore or fix up the code with casts to eliminate. Add some printout to see the results of some of the operations.


Example program ------> CRYPTIC.C

There are three constructs used in C that make no sense at all when first encountered because they are not intuitive, but they may increase the efficiency of the compiled code and are used extensively by experienced C programmers. You should therefore be exposed to them and learn to use them because they will appear in most, if not all, of the programs you see in the publications. Load and examine the file named CRYPTIC.C for examples of the three new constructs.

In this program, some variables are defined and initialized in the same statements for use later. The statement in line 8 simply adds 1 to the value of x, and should come as no surprise to you. The next two statements also add one to the value of x, but it is not intuitive that this is what happens. It is simply by definition that this is true. Therefore, by definition of the C language, a double plus sign either before or after a variable increments that variable by 1. Additionally, if the plus signs are before the variable, the variable is incremented before it is used, and if the plus signs are after the variable, the variable is used, then incremented. In line 11, the value of y is assigned to the variable z, then y is incremented because the plus signs are after the variable y. In the last statement of the incrementing group of example statements, line 12, the value of y is incremented then its value is assigned to the variable z. To use the proper terminology, line 9 uses the postincrement operator and line 10 uses the preincrement operator.

The next group of statements illustrate decrementing a variable by one. The definition works exactly the same way for decrementing as it does for incrementing. If the minus signs are before the variable, the variable is decremented, then used, and if the minus signs are after the variable, the variable is used, then decremented. The proper terminology is the postdecrement operator and the predecrement operator.

You will use this construct a lot in your C programs.


Another useful but cryptic operator is the arithmetic operator. This operator is used to modify any variable by some constant value. The statement in line 23 adds 12 to the value of the variable a. The statement in line 24 does the same, but once again, it is not intuitive that they are the same. Any of the four basic functions of arithmetic, +, -, *, or /, can be handled in this way, by putting the operation desired in front of the equal sign and eliminating the second reference to the variable name. It should be noted that the expression on the right side of the arithmetic operator can be any valid expression, the examples are kept simple for your introduction to this new operator.

Just like the incrementing and decrementing operators, the arithmetic operator is used extensively by experienced C programmers and it would pay you well to understand it thoroughly.


The conditional expression is just as cryptic as the last two, but once again it is very useful so it would pay you to understand it. It consists of three expressions separated by a question mark and a colon. The expression prior to the question mark is evaluated to determine if it is true or false. If it is true, the expression between the question mark and the colon is evaluated, and if the compare expression is not true, the expression following the colon is evaluated. The result of one of the evaluations is used for the assignment as illustrated in line 30. The final result is identical to that of an if statement with an else clause. This is illustrated by the example in lines 32 through 35 of this group of statements. The conditional expression has the advantage of more compact code that may compile to fewer machine instructions in the final program.

Lines 37 and 38 of this example program are given to illustrate a very compact way to assign the greater of the two variables a or b to the variable c, and to assign the lessor of the same two variables to the variable c. Notice how efficient the code is in these two examples.


Several students of C have stated that they didn't like these three cryptic constructs and that they would simply never use them. This would be fine if they never have to read anybody else's program, or use any other programs within their own. You will find many functions that you wish to use within a program but need a small modification to use it, requiring you to understand another person's code. It would therefore be to your advantage to learn these new constructs, and use them. They will be used in the remainder of this tutorial, so you will be exposed to them.

This has been a long chapter but it contained important material to get you started in using C. In the next chapter, we will go on to the building blocks of C, the functions. At that point, you will have enough of the basic materials to allow you to begin writing meaningful programs.


We have no specific issues of style in this chapter other than some of the coding styles illustrated in the example programs. Most of these programs are very nontypical of real C programs because there is never a need to list all of the possible compares in a real program, for example. You can use the example programs as a guide to good style even though they are not real programs.

WHAT IS AN l-value AND AN r-value?

Figure 4-1You will sometimes see a reference to an l-value or a r-value in writings about C or in the documentation for your C compiler. Every variable has an r-value which is defined as the actual value stored in the variable, and it also has an l-value which is defined as the name of the variable. Therefore, the variable depicted graphically in figure 4-1 has an l-value of index, and an r-value of 137 since 137 is the value stored in the variable at this time.

The definition for this variable would be given as follows;

int index = 137; 


  1. Write a program that will count from 1 to 12 and print the count, and its square, for each count.
  2. Write a program that counts from 1 to 12 and prints the count and its inversion to 5 decimal places for each count. This will require a floating point number.
  3. Write a program that will count from 1 to 100 and print only those values between 32 and 39, one to a line. Use the incrementing operator for this program.

Return to Table of Contents

Advance to Chapter 5

Copyright © 1988-1997 Coronado Enterprises - Last update, March 15, 1997
Gordon Dodrill - dodrill@swcp.com -
Please email any comments or suggestions.