CSC 161 Grinnell College Fall, 2013
 
Imperative Problem Solving and Data Structures
 
 

Laboratory Exercise: Types and Variables

Goals

The purpose of this lab is to explore types and variables in C.

Introduction

In Scheme, a variable (or identifier or parameter) can represent any type of data, and the type of a variable may change from one call of a procedure to another. For example, consider the following Scheme procedure that adds all numbers on a list or its sublists:

   (define sum-numbers
      (lambda (ls)
          (cond ((number? ls) ls)
                ((not (list? ls))  0)
                ((null? ls) 0)
                (else (+ (sum-numbers (car ls)) 
                         (sum-numbers (cdr ls))))
          )
      )
   )
  

When sum-numbers is called:

    (sum-numbers '((1) 2.0 four ((3/4) (five) 6)))
  

the parameter ls starts as a list, but various calls will give ls various values, including sublists, integers (e.g., 1, 6), real numbers (2.0), fractions (3/4), and symbols (e.g., four, five). At the end of this example, sum-numbers returns 9.75.

In C, the data type of each variable must be declared before the variable is used in a program (or procedure). Also, once declared, the data type cannot change within the procedure. Thus, when you start programming in C, you need to be familiar with the few primitive types of variables provided to manage data with. Also, in some types of processing, C allows for many implicit and explicit type conversions between these storage classes. In this lab you will perform different legal, illegal, and unusual operations, and examine the results.

Primitive Types

In C there are 4 primitive types:

        char     /* a character */
        int      /* an integer  */
        float    /* a real number (single-precision)*/
        double   /* a real number that needs twice the space as a float (double-precision) */
      

In reviewing these data types,

As we shall discuss in several weeks, these types of data are represented in different ways within a computer.

Computations with ints and doubles

When the numbers in an expression are all ints, then the arithmetic operations (e.g., +, -, *, / (quotient), and % (integer remainder) are all performed as integers (without decimal points).

  1. Include the following code segment in a C program, compile the program, and observe the results:

            int a = 5;
            int b = 6;
            int c = 7;
            int d = a + b + c;
            int e = (a + b + c) / 10;
            int f = (a + b + c) % 10;
            printf ("a=%d, b=%d, c=%d, d=%d, e=%d, f=%d\n", a, b, c, d, e, f);
          

    Review what is printed and explain each result.

  2. Change the declarations above, so that int e and int f are declared as doubles like this:

              int a = 5;
              int b = 6;
              int c = 7;
              int d = a + b + c;
              double e = (a + b + c) / 10;
              double f = (a + b + c) % 10;
              printf ("a=%d, b=%d, c=%d, d=%d, e=%lf, f=%lf\n", a, b, c, d, e, f);
            

    Compile and run the revised program, and explain what happens.

  3. Change the division in step 2, so that division is by 10.0 rather than 10.

    1. Does the program compile? If not, explain why, and then remove any offending line.
    2. After making any adjustments needed to allow compilation, run the program, and explain the output.
  4. Write a simple C program that declares a an int and a double . See what happen when you try to assign the value of each one to one of the other types.

    1. Assign an int (example 70) to an double.
    2. Assign double (example 7.12) to an int.

You may recall there is a fourth type, float, that you didn't use in the last few steps. doubles are almost always preferred to floats, because a double is stored more accurately than a float. Also, constants, such as 3.14 are almost always stored as doubles in C. We will discuss details of the various data types, including doubles and floats, in a few weeks.

Conversion Errors

  1. Download the program conversion-issues.c. Compile it and run it. It declares one of each of the primitive types of variables and displays their contents before and after initialization. How did not initializing effect the values?

Arithmetic and Operators

Operator Precedence

After deciding what operator you will use, you need to put them in certain order or in parentheses because the program will be read in a certain order and so the operators will be applied in a certain order.

  1. Copy this code in a program. What do you think the result will be? Test this out by printing the value.
        	int x;
    	x = 5 + 4 * 3 + 1
    
  2. Copy this code in the same program. Think aboout your expected result and then see what the program prints.
    	int y;
    	y = (5 + 4) * (3 + 1)
    	
    

Truncation

An integer is a whole number. This is why whenever you try to assign an integer a number with a decimal part, for example by diving 5 by 2 , the program will truncate this value. This means that it will throw the digits after the decimal point away. The rounding will always be down.

  1. Copy the following code. Predict what it will do and print the result to check your predictions.
    	int a = 13;
    	int b = 3;
    	int c;
    	
    	c = a/b;
    
  2. If we didn't want the result to be truncated, here is what we could've done. Copy this code into your program above.
     	double x;	
      	x = 13.0/3;
      	printf("%lf\n", x);
    

Assignment of Integers

Let's say you have a counter that counts down the number of days you have left for the summer. Let's call the counter days. Every day that passes, you need to decrement the number of days to get your countdown going. You could do it like this:

	days = days - 1;

This would mean that I am taking the number of days, subtracting 1 from it, and setting that as my new days value.

There is a shortcut to saying that the new value of days is the old value of days -1. Here it is:

	days -= 1

You can do a variety of operations just like this:

Incrementing

  1. There are two ways to increment a variable: ++a (called pre-increment) and a++ (called post-increment). These should not be confused with each other.

    1. Here is an example program that uses the increment operators in statements that are separate from other activities. Examine the output to determine whether or not there is a difference between the two increment operators in this context.

        int a,b,c;
        a = 0;
        b = a++;
        c = ++a;
        printf(" a = %d b = %d c = %d\n", a,b,c);
        a = 0;
        b = ++a;
        c = a++;
        printf(" a = %d b = %d c = %d\n", a,b,c);	
      
    2. The next block combines the increment operators within a print. Again, examine the output to determine whether or not there is a difference between the two increment operators in this context.

        int r, s;
        r = 5;
        s = 7;
        printf(" r = %d s = %d\n", ++r, s++);
        printf(" r = %d s = %d\n", r, s);	
      

    Conclusion: When combined with other operations, the increment operations ++a (pre-increment) and a++ (post-increment) cause different values to be used in processing. This observation yields the following programming observation and suggestion:

    • The use of pre- and post-increment operators in the context of other operations is widely observed to be error prone.
    • Avoid combining pre- and post-increment operators with activities in the same statement.

Unexpected Results

Adding ints to chars

In C, it is possible to add an integer value to a char value.

  1. Include the following lines of code within a program:

                char ch = 'a';
                ch = ch + 7;
                printf("ch + 7  = %c\n", ch);
            
    1. Save, compile, and run the program again.
    2. What is the value of ch?
  2. Add 7 to ch again (you can just copy the last two lines again) and a print out the value.

    1. What is printed?
    2. Is that what you expected?
  3. In C, a char is considered a type of small int, so it is possible to assign a char variable an integer value. Set ch to zero and print out the result:

                ch = 0;
                printf("char ch = %c\n", ch);
              

    You may notice the print statement doesn't print out ch as being zero.

    Why is it blank to the right of the equals sign? Behind the scenes, a char is really just a small integer. This is why an expression such as: ch + 7; is legal. The only difference is how printf interprets the value stored in the char variable ch.

  4. Set ch equal to 48 and print it again.

    Note what happens..

Casting

As you may have encountered in the previous exercise, C is pretty forgiving as to what types can be assigned to each other. Whether you realized it or not, the assignment operator, =, converts between certain types silently because they are easily compatible -- such as chars and ints. Even in the case of types that are less compatible such as ints and doubles, if you try to assign a double value to an int variable, the compiler does not complain. It simply drops the floating point (the decimal).

  1. If you didn't try that above, try it now.

                      int i = 2.7;
                      printf("int i = %d\n", i);
                    

    The process of converting between variable types is called casting. If you don't have to say anything for the process to take place, it is called implicit casting. This is the case with all the math operations in c. The +, -, *, /, and = operators all implicitly cast across primitive types. The rules for implicit casting can be found in K&R appendix A6 (specifically section 5).

  2. Now try explicit casting. This is where you tell c exactly what you want to happen. The line:

                      int x = (int) 3.14;
                  

    tells the compiler (but more importantly anyone reading the code) that it is explicitly intended to convert the double (3.14) to an integer and store it in the space x which is available for integer data.

  3. Why do you think explicit casting is necessary if the compiler already does it implicitly?

    As hinted at, it is usually an effective way of informing anyone reading your code that you mean to do something that may be questionable such as dropping the decimal from a number. The second reason is that casting is necessary when dealing with more complicated data such as pointers and structs. The compiler will not let you convert between certain types unless you explicitly tell it that is what you want to do.

    Finally, there is a danger in casting. Types have sizes. If you try to put a number that is bigger than the size of the type you are casting it to, you will get undesired results.

ASCII Table -- If time permits..

Recall earlier that setting a char equal to 0 is not the same as setting it equal to '0'. As you may know, this is because the zero in single quotes is actually interpreted as the character zero. The character zero has the integral value 48. That means setting a char equal to 48 is the same as setting it equal to '0'.

Here is a simple program, ascii-print.c , that prints out all the ASCII values along with their corresponding characters. It uses a for loop, which we will be considering later this module.* Note that while chars can range from 0 to 127, the printable characters start at 32.

  1. What is the corresponding character to the ASCII value 85?
  2. What about 32?
  3. What is the ASCII value for the character 'A'?
  4. What is the ASCII value for the character 'a'?
  5. What is the integer difference between the values of 'A' and 'a' ?

Feedback Welcome

Development of laboratory exercises is an interative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.