Remember to create a new directory in your repository for this lab. (Refer to previous labs for how if you have forgotten)
You have a few tasks to complete before your lab. First, read about another way to use the #define preprocessor command. Next, refresh yourself on 2d arrays. Then, answer the questions in lab3questions.html. Print them out and bring them to lab.
Finally, create the header file and skeleton implementation file for the functions you will implement in the warmup during lab. More detailed directions follow.We have seen #define now a few times. There are two aspects to it: Exactly how it works and the proper use in C.
#define (and other #commands) are commands for the C pre-processor. This is the first pass that happens in C compilation. The pre-processor goes through the set of compilation files looking for those commands. Right now, we will focus on the #define command.
#define HOWDY 5
This command tells the pre-processor to go through all of the files (after it reads #define) and replace every string "HOWDY" with the number 5. The reason we use all caps is that we don't want to accidentally have "HOWDY" in something else. For example, if we did the following:
#define a 5If we had the following code:
char my_function(char x, char y)
The pre-processor would replace all fo the 'a''s with 5. This would break all of our code. Therefore, pre-processor commands are both powerful and very, very stupid. They are only to be used in particular circumstances.
Use 1: Constants. The use above was as a constant. If you want to define a constant that will be used throughout the file, this is a great use of #define. int scores[MAX_STUDENTS] is a great use of a constant. We might use that several times in the file. If we use a pre-processor command, then we can define it once at the top of the file. If the number of students ever changes, we change it in only one location. The advantage of using #define over a variable is that the compiler is able to optimize constants better than variables - it doesn't need to read in the value.
Use 2: Defining conditions. We see this in the .h file. We see
#ifndef HW2_H #define HW2_H ... rest of file #endif
In this case, we're not defining HW2_H to be a value - we're just defining it. We only use this when using #ifdef / #ifndef paired with #endif, which is a special if statement for the preprocessor. You can read #ifndef HW2_H as 'if not defined HW2_H,' or 'if HW2_H is not already defined.' When used this way, the if statement ensures that the same code isn't read through twice in the same compilation step.
Use 3: debug code. It is common for programmers to insert many print statements when debugging the code. When they fix the bug, then they want to remove the prints, but if they find another bug, it is useful to put them back in again. The quickest way to do this is to put your print statements inside #ifdef statements like this:
#ifdef DEBUG printf("Line 74: scores[%d] has value %f\n",i,scores[i]); #endifThen, if you want the debugging statements to print out, you put #define DEBUG at the top of the file. To remove the print statements, remove the #define line.
In this assignment, we are going to add some #define constants to the .h file. The purpose is so that our testing infrastructure can change those constants in order to test that your code has not been hard-coded to particular values. This is used in exercises 3 and 4.
Imagine you had a .h file with the following code in it:
// set the height and width of our 2-d arrays #define ROWS 50 #define COLS 10 // prototypes of some functions we might implement that show how to // declare 2-d arrays as input parameters. // there would normally be a function header here for each one. int row_max(int array[ROWS][COLS], int row); int col_max(int array[ROWS][COLS], int col); float row_avg(int array[ROWS][COLS], int row); float col_avg(int array[ROWS][COLS], int col);
int my_array[50][100];or
int my_array[ROWS][COLS];
int a[3][4] = { {0, 1, 2, 3} , /* initializers for row indexed by 0 */ {4, 5, 6, 7} , /* initializers for row indexed by 1 */ {8, 9, 10, 11} /* initializers for row indexed by 2 */ };
There are two ways that arrays are different when passing to and receiving as return values from normal variables. You have been introduced to pointers. Arrays are not copied into functions (since they are so long). Instead, the location of the first element is passed in. This has two implications.
First, they can be out parameters (which means that you can use them to pass information back from the function, like return values). That is, if you make any changes to the array within the function, those changes were made to the original array, so those changes will "stick" after the function call.
Second, when declared statically within a function, as seen above, they are local variables. Local variables are destroyed once a function call ends. Therefore, they may not be the return value of a function. We will learn how to make an array that you want to return in a week or so.
During the warmup, you are going to implement several functions that exercise arrays and pointers.
In order to spend your time efficiently in lab, you need to start with a skeleton that compiles. Create these files, put in skeleton code based on the prototypes shown below, and verify that your project compiles. You will need to place a main in warmup3_main.c that contains a single call to each function, a prototype of each function in warmup3.h, and a skeleton implementation of each function in warmup3.c. In addition, copy and paste the print_tic_tac_toe_board implementation into warmup3.c (and put its prototype into warmup3.h).
Fill in this function in warmup3.c:
void print_tic_tac_toe_board( char board[3][3] ) { unsigned int i, j; for(i=0;i<3;i++) { for(j=0;j<3;j++) { printf("%c ",board[i][j]); } printf("\n"); } }
In this problem, remove_max receives an array of the specified length, where the input argument length holds the length of input argument array. The function is supposed to find the maximum item, remove all instances of that value from the array, and return the value.
For example, if the length begins at 5 and array contains: 3, 2, 8, 5, 4, then the function will return 8 and, upon return from the function, array will contain: 3, 2, 5, 4. Note that we maintain the relative order between the elements even though we have removed the largest one. Also note that the array actually still has 5 things in it - we will just ignore whatever is in the 5th spot.
unexpected conditions: If the array is passed with a length of 0, then return INT_MIN. #include <limits.h> to be able to use it. Do not print out an error message. If there are multiple identical max items, remove all items.
In this function, initialize the board to all '*' characters, indicating all squares are empty.
In this function, check to see if that spot is a valid spot on the board and that the spot on the board is available. If so, put the character in that spot and return 1. If not, leave the board unchanged, and return 0. Do not print out an error message.
This function calculates both the area and perimeter of a rectangle. This means the function wants to produce two return values. One way to do this is to use out parameters. With out parameters, the calling function provides the space, through pointers, for the function to place its results.
In this case, height and length are input parameters, and area and perimeter are output parameters. Your job is to read in height and length, then place the calculated values of area and perimeter in the locations pointed to by the pointers area and perimeter.
The remove_max function above is not robust. It removes items from the array and compacts the ones that are left. However, it does not have a way of communicating how many items it removed. This can lead to unexpected behavior if the caller assumes that only one item will be removed each time it is called, but there were multiple items at the maximum value.
However, the same parameter can be used as both an in parameter (providing information to the function) and an out parameter (providing a location to pass information out of the function).
In this problem, remove_max_int_out receives an array of the specified length, where length is now the address of the location holding the length. The function is supposed to find the maximum item(s) and remove it/them. By removing it, we mean to modify the array and update the length.
For example, if the length begins at 6 and array contains: 3, 2, 8, 5, 8, 4, then upon return from the function, length will contain 4 and the array will contain: 3, 2, 5, 4. Note that the array allocation does not change. There are still 5th and 6th spot, but we don't care what they hold. We are (incorrectly) reporting the length to be 4, so those are the only locations that matter. It would be the caller's responsibility to keep around the actual allocated length (rather than the number of items being held in it currently).
In this case, both array and length are in and out parameters.