While waiting for lab to start, here's a quick comment on Input/Output in Scheme: As mentioned on the project handout, read and display are I/O functions. For example,
(define x (read))
(begin (display x)
       3
       (display "Hurry, lab is about to start.")
       (reverse (list 2 'x 'y))
       17)
You may want to take a moment before lab starts, to try this out. How would you read values into a vector?

By the way, Dr. Scheme can also display pictures.

Comp210 Lab 13: Vectors and Strings in C

Table of Contents

  • Vectors in C
  • Index Out-of-Bounds
  • Passing Vectors as Arguments
  • Strings in extended C
  • Vectors are to be thought of as structures whose parts are accessed by number, instead of by name.

    Vectors in C

    In lecture, you've already seen the C syntax for vectors: Here are some Scheme expressions, and the corresponding C expressions.
    (define size 3)                         const int size = 3;
    (define nums (make-vector size))        int nums[ size ];
    (define nums (vector 8 9 10))           int nums[] = { 8, 9, 10 };
    (vector-ref nums 2)                     nums[2];
    (vector-set! nums 2 1000)               nums[2] = 1000;
    (vector-length nums)                    (no equivalent)
    
    In both cases, we have one vector, which contains three elements. The elements are indexed zero through two. Saying (vector-ref nums 3) or nums[3] is an error. We'll have more to say about this below.

    for loops are idiomatic for accessing each element of a vector.

    const int size = 100;       // "const" means you can't assign to this var
    int   vec[size];            // a global array, indexed 0..size-1.
    
    // init()
    // Initialize the vector "vec" to be all zeroes.
    //
    void init() {
    
      for ( int i = 0;  i < size;  i = i + 1 ) {
        vec[ i ] = 0;
        }
      return;
      }
    

    Exercise: Create an array of size booleans, and initialize them to true, except for entries 0 and 1, which should be false

    If you want, write a function to print out your vector, so you can confirm your initialization. Does your code work even if size==1?!

    Index-out-of-bound Errors

    We mentioned earlier, that if a vector nums has three elements, then they are indexed zero through two, and it is a (common) error to refer to (vector-ref nums 3) or nums[3].

    However, the Scheme and C handle this error in very different ways. Scheme lets you know immediately that there is an error; C doesn't! Instead, C might:

  • Immediately produce the cryptic error "segmentation fault" and crash the program (without telling you where the problem occurred).
  • Change the value of other random placeholders without telling you, and continue merrily on, which causes your program to crash five minutes later. Even if you could find out where this crash occurred, it wouldn't help you find the original bug.
  • Because of change the value of other placeholders, it might even finish but give you a wrong answer (without alerting you to this likelihood).
  • Any of the above, varying from time-to-time of running your program.
  • This is drain-bamaged, and as a C programmer you must just live with it. In short, remember to access arrays from 0 to one less than their declared size!.

    Here is a correct program; in a moment you'll introduce an error to see what happens. For the moment, understand, compile and run it. (Remember to copy it into a file whose names in .cc.)

    #include <iostream.h>
    
    int main() {
      const int arrSize = 3;
      int before = 0;
      int nums[ arrSize ] = { 97, 98, 99 };
      int after = 1;
    
      // In a moment, you'll add some code here.
    
      cout <<  "Before should be 0: " << before << ".\n";
    
      for ( int i = 0;  i < arrSize;  i++ ) {
        cout << "nums[" << i << "] is " << nums[i] << ".\n";
        }
    
      cout << "After should be 1: " << after << ".\n";
    }
    
    Notice the construct i++, which is just shorthand for i = i + 1 (just as Scheme defines (add1 i) as a shorthand for (+ i 1)). Of course, add1 and + aren't like set!, while C's assigment operater = (pronounced "gets") and ++ are like set!.

    One word of warning about using ++ to increment an int: Feel free to use this by itself, but do not combine ++ with other statements. For instance, j = i++; should be re-written as two seperate statements, j = i; i++;. (Just because something is legal C doesn't mean it's good C.) There are common, hard-to-find errors which can occur when combining expressions.

    Now, add the following code at the indicated place:

      // ILLEGAL, OUT-OF-BOUND INDICES!
      nums[ arrSize+1 ] = 17;
      nums[ -1 ] = 19;
    
    What happens this time? What happens if you change arrSize+1 to arrSize+365?

    Passing Arrays as Arguments

    So far, our examples have all declared a vector globally, and our functions all referred directly to that particular vector. However, more often you'll want to write a function which takes an array as an argument.

    Passing a vector to a function is straightforward, except for one thing: keeping track of the size of the vector. (Remember that C doesn't have a vector-length function.) You can write a function taking in a vector, but it is up to that function to never index past the array bounds. The usual solution is to pass the function an additional argument, which tells the function how big the vector is.

    Example:

    #include <iostream.h>
    
    // sumVec
    // A function to add the first "size" entries
    // of the vector "data".
    //
    double sumVec( double data[], int size ) {
      double sum = 0;
    
      for ( int i = 0;  i < size;  i++ ) {
        sum += data[ i ];      // this is same as: sum = sum + data[i]
        }
    
      return sum;
      }
    
    
    int main() {
      const int N = 30;
      double myVec[ N ];
    
      cout << "The sum of entries 0 through " << N-1 << " is "
           << sumVec( myVec, N ) << ".\n";
      }
    
    It's worth noting that we could have written sumVec recursively.

    Exercise: Copy the above into a file whose name ends in .cc, and compile it. What is the result? Is it the same as your neighbor's?

    Exercise: Write the function readVec, which (like sumVec) takes a vector of doubles and a number N, and uses cin to read a number from the keyboard into the first N elements fo the vector. Call readVec from main and run your program again.

    (You may wish to set N = 4 or so, to save typing. Or, use UNIX's input redirection to have your program take input from a file instead of the keyboard. For example,

      owl%   readVec < ~comp210/Labs/lab13/30nums.txt
    
    Or as a third option, you could of course cut-and-paste numbers from somewhere else. )

    Functions that Modify Their Arguments

    Whoah Nelly! What new phenomonen happens with this problem, that we've never seen before? The changes which readVec makes to the array passed to it are seen back in main's vector myVec!

    But what if readVec had assigned to size--would main's variable N be modified? No! That's because there is a new rule: When passing structures or vectors to a function, any changes made to them are seen by the caller, which is not hte case for simple ints or bools, etc. (This same difference occurs in Scheme as well as C--try it!)

    If interested in why, ask a labbie after class, or see last lab's digression on hat variables for structures. Or, think about, in Jam2000, how would you effect passing an array to a function? Hint: if vec[0] is in memory location 2000, where is vec[1], or vec[17]? What about the general case of vec[i], for any i? So if you need to tell a function how to access elements of some vector vec, what is the only piece of information you really need to give to the function? Now, what if the function tries to change an element of the vector?

    Two-dimensional Arrays

    As seen in lecture, two-dimensional arrays can be useful in different applications (like, say, the final project?).

    In Scheme, you can use vectors-of-vectors to serve as two-dimensional vectors, writing your own functions make-array, array-ref, and array-set!.

    You could take this same approach in C, but the functionality is also built-in (at least, for arrays of a fixed size). Here is some C syntax for declaring such arrays.

    const int numRows = 20;
    const int numCols = 10;
    
    const char plains = '.';
    const char water = '~';
    const char mountain = '^';
    
    void flood( char map[ numRows ][ numCols ] ) {
    
      for ( int r = 0; r < numRows; r++ ) {
        for ( int c = 0; c < numCols; c++ ) {
          map[r][c] = water;
          }
        }
    
      // build Mt. Ararat?
      map [ numRows/2 ][ numCols/2 ] = mountain;
      // Is this valid for all possible numRows, numCols?
      // No, not if either are zero.  A bizarre case, but good code would check!
                                         
      return;
      }
    
    
    int main() {
      char earth[ numRows ][ numCols ];
      flood( earth );
      }
    
    Unlike our function sumVec, which could accept one-dimensional vectors of all different sizes, we can't do that with two-dimensional arrays. The function flood specific takes an array of only a certain size.

    Why the inconsistency between one- and two-dimensional arrays? Well actually the unifying rule is that when passing an array, you must include all but the first dimension. (If you do provide a number for that dimesion in the formal parameter, it is ignored.) This is a limitation of C.

    Why does C have this limitation? Ask your labbie, or again go back to the Jam2000 model, and think about what's happening. How is the two-dimensional array stored in memory? If you tell the function where v[0][0] resides in memory, but not the dimensions of v, how can the function deduce where v[0][17] resides? But what about v[2][17]? Exercise: Write a function printMap which takes a numRows$\times$numCols array of char, and prints its contents (returning void). Use this function to display the earth.

    Strings in Extended C

    The term "string" refers to a bunch of characters strung together: e.g., "hello", "?" and "Caught you red-handed!".

    If you are using Extended C (well, actually, C++) then we'll show you a nice simple way to use strings. However, it's worth mentioning that in plain old non-extended C, strings are handled as vectors of characters. This approach has its own ways of handling strings, which you should learn if and only if you plan on using non-extended C.

    If you want to use regular old strings, you'll #include <Strings.h>, and then pretend that the type String is built-in just like int and char, etc.

    #include <String.h>
    #include <iostream.h>
    
    void threaten( String target ) {
      cout << "Death to " << target << ".\n";
      }
    
    int main() {
      String myName = "Santa Claus";
      String nemesisName;
      nemesisName = "The Easter Bunny";
    
      threaten( nemesisName );
    
      cout << "My name has " << myName.length() << " letters.\n";
      return 0;
      }
    
    (Remember to capitalize String.h.)

    This is all okay, except that there are now some fancy things we can do. In particular, go back and add the following line before the call to threaten:

      nemesisName[11] = 'f';
    
    Yes, we can consider a String as an array char[]! Actually this isn't quite true--what happens if we try to fiddle with an invalid index, such as assigning to nemesisName[17]? Try it. This is different than what happens in regular ol' C's strings-as-character-arrays.

    Finally, what's with this myName.length()? It turns out myName is an object which not only has a string value, but also knows how to respond to certain messages, like length()! That's your preview of C++ proper (or Java); take Comp212! :-)


    Back to Comp 210 Home