You are expected to complete this assignment individually. If you need help, you are invited to come to office hours and/or ask questions on piazza. Clarification questions about the assignments may be asked publicly. Once you have specific bugs related to your code, make the posts private.
This homework has several problems. We are also providing you with some resources on printf and error handling for this assignment.
Homework 2, which includes warmup 2, will be collected from your subversion repository on Wednesday, April 13th, at 11:59pm.
You should submit five files for this assignment (duet.txt., hw2.h, hw2.c, hw2_main.c, and Makefile) in your subversion repository as directed below.
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 define HW2_H to be a value - we're just defining it. This is only useful for #ifndef through #endif. This is a special if statement for the preprocessor. In this case, we are using the if statement to make sure the same code is not compiled twice.
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 problem 4.
Because you are adding a second set of files to your directory, you need to add a second target to your Makefile. In your makefile, add another two lines (with a space between these and the ones already there).
hw2: hw2.h hw2.c hw2_main.c clang -Wall -o hw2 hw2.c hw2_main.c
Now you need to make the skeleton code so that your program will minimally execute.
/* surface_area_cylinder * Calculates the surface area of a cylinder given height and radius * inputs: height, radius * output: double */ double surface_area_cylinder(double height, double radius);
double surface_area_cylinder(double height, double radius) { return 0.0; }
int main() { surface_area_cylinder(1.0, 5.0); // add the rest of the function calls here }
You are going to create a library that prints out letters with asterisks. Each letter will be 5 asterisks high and 4 asterisks wide. A user can ask to print out just a single letter or a set of letters all in one line.
Here is what it looks like to print a single letter at a time:** * * **** * * * * *** * * *** * * ***
The function headers must be as follows:
The complete alphabet is here. You are not required to implement the entire alphabet - this course is about the beauty and joy of computing, not the drudgery of making an entire alphabet. Therefore, you will only be required to implement the letters shown in that file. You may assume that all characters in the array are capital letters and within the range shown.void print_one_letter(char letter);
Before writing this portion, read problem 2 and think about a solution that works for both.
Don't forget to complete this portion, test it, and commit it before moving on! You are allowed (and encouraged) to further break this problem down into smaller functions.* * ** * * **** ** ** *** ** * * * * * * ** * * * * * * * * * * **** ** * **** * ** * * * * * * * * ** * * * * * * * * ** ** * * **** ** * * ** **There is a single space between each letter (or column of spaces, to be more accurate).
void print_word(char word[], int length);
Think hard about function decomposition. The way you break down this problem is going to greatly influence the readability of your solution. Think about how the computer is going to print it out, then think about giving that information in such a way that divides nicely into functions.
There are many ways to encode messages so they are slightly harder to read, including mappings from one letter to another letter or rotations from one letter to some other letter (A=C, B=D, C=E, ... Z=B for rot_factor 2).
Write a function encode_rotate that rotates each letter by a constant amount.
void encode_rotate(char msg[], int length, int rot_factor);
For every letter in the message that is a letter from 'a' to 'z' and 'A' to 'Z', rotate it by rot_factor letters. Do not modify punctuation or any other character that is in the message.
You may modify the input array. For primitive types such as int, float, char, double, the input parameter is a copy of what was passed in. For arrays, because the system doesn't know their length, the array itself is not copied - only the location of the beginning of the array is copied. This means that if you modify the array within the function, the change is reflected in the calling code.
Don't forget to complete this portion, test it, and commit it before moving on!In problem 4, you are going to write a few functions that calculate pertinent information about a 2-d array. These are generic functions that could be used in a variety of applications.
To pass a 1-d array to a function, you don't need to tell the compiler its dimensions. For a 2-d array, however, the compiler needs to know the dimensions in order to produce correct code. We are going to use #define to define the array sizes. In hw2.h, put the following lines:
#define ROWS 50 #define COLS 10 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);
Hint: Be careful with types. Note that the 2-d array is ints, yet the averages are floats. Be careful with the division - you want an accurate answer.
Hint: To declare and initialize a 2-d array in a single command, you can use this syntax:
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 */ };
$ svn add hw2.h hw2.c hw2_main.c
$ svn commit -m "hw2 complete"