Loops The loop blocks you've used in Scratch can all be recreated in C! Loops allow for the repetition of code until a condition is met. There are three types of loops commonly used in C: for while do while
Initialize variable(s) For Loops for (initialization; condition; update) { execute this code } Check condition Update variable(s) Execute code in body Initialize variable(s) if true if false Exit loop Here's how a for loop works: Variables are initialized. The condition is evaluated. If the condition is true, the code in the loop's body runs, variables are updated, and the condition is reevaluated. If the condition is false, the loop terminates.
Prints “This is CS50!” ten times Example #1 Prints “This is CS50!” ten times for (int i = 0; i < 10; i++) { printf("This is CS50!\n"); } This for loop in C is analogous in functionality to the scratch blocks above it. i is initialized to 0. "This is CS50!" prints. i increments. The loop terminates when i is equal to 10, and "This is CS50!" will be printed a total of 10 times.
Converts a lowercase string to uppercase Example #2 Converts a lowercase string to uppercase char name[] = "milo"; for (int i = 0, j = strlen(name); i < j; i++) { name[i] = toupper(name[i]); } This for loop iterates through the length of a string, capitalizing all alphabetic characters. Note how we've initialized two variables, i and j, through the use of a comma.
While Loops while (condition) { execute this code } if false Check condition if true Here's how a while loop works: The condition is evaluated. If the condition is true, the code in the loop's body runs, and the condition is reevaluated. If the condition is false, the loop terminates. Execute code in body Exit loop
Example #3 Counts down from 10 to 0 int count = 10; while (count >= 0) { printf("%i\n", count); count--; } This while loop in C is analogous in functionality to the scratch blocks on the left. The condition is evaluated. count will be printed and decremented. The loop terminates when count < 0, and the numbers 10 through 0 have been printed out in descending order.
Calculates string length Example #4 Calculates string length string s = GetString(); int length = 0; while (s[length] != '\0') length++; This while loop iterates through a string, counting each character until the null terminator (\0) is reached.
Do While Loops do { execute this code } while (condition); if true Here's how a do while loop works: The code in the loop's body runs once. The condition is evaluated. If the condition is true, the code in the loop's body runs again, and the condition is reevaluated. If the condition is false, the loop terminates. Check condition if true if false Exit loop Execute code in body
Reprompts until user enters a positive number Example #5 Reprompts until user enters a positive number int input; do { printf("Enter a positive number: "); input = GetInt(); } while (input < 1); Do while loops are ideal for vetting user input. The user is prompted to enter a positive number. If the number is less than 1, the user will be reprompted to enter a positive number.
Math in C The math blocks you've used in Scratch can all be recreated in C!
Numerical Variables int float double long long Numerical data in C is classified into several different types. From a variable’s type, you’ll be able to tell what kind of data it can store, how much data it can store, and which operations can be performed on this data. An int is a whole number. The appliance uses 32 bits to store an int. A float is a number with a decimal point. The appliance uses 32 bits to store a float. A double is a number with a decimal point, but with more space for precision after the decimal point. The appliance uses 64 bits to store a double. A long long is a whole number that is twice as big in memory as an int. The appliance uses 64 bits to store a long long.
Let’s add some ints! // declare x int x; // initialize x x = 2; // declare and initialize y int y = x + 1; Here, we sum two variables of type int. Let's think very carefully about what's happening as these three lines of code execute: The first line declares an int named x. More precisely, it asks the operating system for enough memory to store an int and gives that chunk of memory the name x. The second line initializes x by assigning it a value of 2. Remember that in C, a single equal sign is called an assignment operator. Whatever's to the right of the equal sign will be stored in the variable to the left of the equal sign. The third line declares and initializes an int named y in a single line of code.
Division int main(void) { // declare and initialize answer float answer = 1 / 10; // print answer to two decimal places printf("%.2f\n", answer); } Let’s try some division next, and store the result of our division in a float because we know it will be a decimal value. %.2f tells the OS to print a floating-point number, but only to two decimal places. What will print? 0.00 Why? 1 and 10 are ints. In C, an int divided by an int is an int and any decimal places will be truncated off.
Fixed version: Typecasting int main(void) { // declare and initialize answer float answer = (float) 1 / (float) 10; // print answer to two decimal places printf("%.2f\n", answer); } We can fix this! We can use this syntax (desired data type within parentheses) to cast the ints 1 and 10 to floats. This is called typecasting. A float divided by a float is a float, which preserves decimal places! What will print this time? 0.10
Another way int main(void) { // declare and initialize answer float answer = 1.0 / 10.0; // print answer to two decimal places printf("%.2f\n", answer); } Another way to accomplish the same thing is to replace the ints 1 and 10 with floats 1.0 and 10.0. This method also results in the printing of 0.10.
Operator Precedence What is x? 1. int x = 2 * 10 + 10 / 2 + 2; Remember "order of operations" from elementary school? The same concept applies in programming and is called operator precedence. 1. 27 2. 22 3. 10
Modulo 1. 55 % 10 2. 3 % 5 3. 8 % 8 4. 16 % 15 The modulo operator gives you the remainder of the division of the first number by the second. 1. 5 2. 3 3. 0 4. 1
What will print? int main(void) { // declare and initialize x, y, z int x = 1; int y = 2; int z = (x + y) * y % y + y; // print z printf("%i\n", z); } What will be printed out? 2
Floating Point Imprecision int main(void) { // initialize x and y float answer = 1.0 / 10.0; // print answer to two decimal places printf("%.20f\n", answer); } Why is it that 0.10000000149011611938 will print rather than simply 0.10000000000000000000? Some fractions like 0.1 can't be represented precisely using a finite number of binary bits. This is known as floating point imprecision and can cause significant rounding errors if you're not careful!
Everything that goes on under the hood of a computer is done in binary -- the language of 0s and 1s. If we have only two numbers, it's very easy to represent them in the physical world using electricity. You can think of each binary digit as a switch or a light bulb that can be either on or off, where by convention 0 is thought of as "off" and 1 is "on".
We are used to decimal notation: 1 6 3 102 101 100 1*102 + 6*101 + 3*100 = 163 Let's think more carefully about decimal notation. To represent the number 163, we've got a 3 in the 1s place (100), a 6 in the 10s place (101), and a 1 in the 100s place (102). You get 163 when you multiply these digits by their respective powers of 10 and sum them.
Computers store and process data via binary notation: 1 0 1 0 0 0 1 1 27 26 25 24 23 22 21 20 1*27 + 0*26 + 1*25 + 0*24 + 0*23 + 0*22 + 1*21 + 1*20 = 163 To represent this same number in binary, you'll need a 1 in the 1s place (20), a 1 in the 2s place (21), a 1 in the 32s place (25), and a 1 in the 128s place (27). You get 163 when you multiply these digits by their respective powers of 2 and sum them.
Converting Binary to Decimal (and vice versa) 1 = 1*20 = 1 10 = 1*21 + 0*20 = 2 11 = 1*21 + 1*20 = 3 100 = 1*22 + 0*21 + 0*20 = 4 101 = 1*22 + 0*21 + 1*20 = 5 Let's count to 5 in binary! Note how we can multiply the binary digits by their respective powers of two and sum them to convert to a decimal value.
Addition and Subtraction (Don’t forget to carry your 1s) 1 1 10 010 0 - 0 0 0 1 0 1 1 0 1 0 1 0 1 01 11 1 + 0 1 0 0 0 1 1 1 1 1 0 0 Addition and subtraction in binary works the same way as in decimal. Start on the right, and carry the 1s as needed.
Characters must also be encoded in binary Everything that goes on under the hood of a computer is done in binary -- the language of 0s and 1s. In order to use binary to express alphabetic, numeric, and other characters, we need some kind of mapping between characters and numbers.
ASCII maps characters to numbers ASCII is an encoding system that maps alphabetic, numeric, and other characters to numbers. For example, uppercase 'A' is represented by the number 65, and lowercase 'a' is represented by the number 97.
AAS ASCII Math What will print? printf("%d\n", 'a' – 'A'); printf("%c\n", 'B' + ('a' – 'A')); printf("%c\n", 'b' – ('a' – 'A')); printf("%c\n", 'B' + 1); printf("%c\n", ('z' – 'a' + 1) % 26 + 'a'); The following will print out: 32 b B C a
AAS Example #1 Prints Z through A for (int i = 'Z'; i >= 'A'; i--) printf("%c\n", i); This program will print the uppercase letters of the alphabet in reverse alphabetical order. Note how you can use 'Z' instead of the magic number 90, and 'A' instead of the magic number 65. Definitely makes the code more readable!
Converts a lowercase string to uppercase AAS Example #2 Converts a lowercase string to uppercase char name[] = "milo"; for (int i = 0, j = strlen(name); i < j; i++) name[i] = name[i] + ('A' - 'a'); f This program converts each letter of a lowercase string to uppercase. Take some time to think about why adding ('A' - 'a') to a lowercase letter capitalizes it.
Arrays 1 2 3 4 5 Arrays are data structures that allow us to store data of the same type in contiguous memory locations. That was a pretty dense statement! To better understand the concept of an array, think back to the last time you picked up mail in the Science Center basement or your house's mailroom. An array is simply a block of contiguous space in memory (the mail center) that has been partitioned into identically-sized chunks (mailboxes). Each chunk can store a certain amount of data (mail) that can be accessed by an index number (mailbox number). Note that array indices in C always start at 0!
65 87 30 Creating an Array 1 2 <data type> name[<size>]; Example: int temperature[3]; temperature[0] = 65; temperature[1] = 87; temperature[2] = 30; OR int temperature[] = { 65, 87, 30 }; 1 2 65 87 30 Declare an array by specifying the data type it will store, its name, as well as its size. Here, we declare an array of 3 ints called temperature and load it with values. Alternatively, you can declare and initialize the array in a single step (in which case stating the size is optional). Because arrays store data contiguously in memory, array size is fixed after array declaration. You are effectively asking the OS to reserve the appropriate number of contiguous chunks of memory for the array's elements. There's no guarantee that more memory adjacent to your array will be available for use later, so arrays cannot easily grow.
Accessing Array Elements 1 2 65 87 30 for (int i = 0; i < 3; i++) { printf("%d\n", temperature[i]); } Because each element is associated with an array index, we have random access to all of the elements in an array. In other words, we can access any element in a single step by indexing into the array. This is a big deal because algorithms like binary search depend on random access. This code prints out all elements of the temperature array.
#include <stdio.h> #include <cs50.h> #define CLASS_SIZE 30 int main(void) { // declare array int scores_array[CLASS_SIZE]; // populate array for (int i = 0; i < CLASS_SIZE; i++) printf("Enter score for student %d: ", i); scores_array[i] = GetInt(); } } Here's an example of the usage of an array to keep track of student scores. This code prompts the user to enter a score for each student. Scores are stored in scores_array.
Where's the bug? string class[3] = { "Sam", "Jess", "Kim" }; for (int i = 0; i <= 3; i++) { printf("%s\n", class[i]); } What's the index number of the last element in an array of size n?
Multidimensional Arrays 0,0 0,1 0,2 x x 1,0 1,1 1,2 char board[3][3]; board[1][1] = 'o'; board[0][0] = 'x'; board[2][0] = 'o'; board[0][2] = 'x'; o Arrays can be multidimensional. You can think of multidimensional arrays as "arrays of arrays." Here, we declare and initialize a 2-dimensional array tic-tac-toe board. Note that dimensions must be declared explicitly for multidimensional arrays! 2,0 2,1 2,2 o
Accessing Multidimensional Array Elements // print out all elements for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) printf("%c", board[i][j]); printf("\n"); } This code uses nested for loops to print out all elements of the tic-tac-toe board array.
Functions Inputs Output You've probably used plenty of C functions already like printf() from the stdio library or GetInt() from CS50's library, but now we'll learn how to write our own functions and incorporate them into our code. Basically the point of functions in C is to help you to partition your code into manageable pieces. Think of a function as a black box with a set of inputs and a single (optional) output. The rest of the program does not need to know what goes on inside the function, but the program can call the function, provide it with inputs, and use the output. This is a bit of an oversimplification as functions are also useful for the side effects they cause. For example, you might write a function called print_instructions(), which doesn't return anything but executes a series of printf() statements.
Why Functions? - Organization - Simplification - Reusability The main reasons for using functions in programming are: Organization. Functions help to break up a complicated problem into more manageable subparts and help to make sure concepts flow logically into one another. Simplification. Smaller components are easier to design, easier to implement, and far easier to debug. Good use of functions makes code easier to read and problems easier to isolate. Reusability. Functions only need to be written once, and then can be used as many times as necessary, so you can avoid duplication of code.
A Function Definition int cube(int input) { int output = input * input * input; return output; } Here's an example of the structure of a function definition. A function definition gives all the information that the compiler needs to know about a function. It describes how the function should be used, how its body should be translated into machine-readable language that the computer can execute, and what operations the code in its body will perform. This function, cube(), takes an int as input and returns the cubed value of that int. We'll next cover the components of a function definition in more detail.
Header Body int cube(int input) { int output = input * input * input; function name int cube(int input) { int output = input * input * input; return output; } return type parameter list Body A function definition has a header and a body. The header always contains these parts: Return type. The type of value that the function will output. In our example, it is int. Function name. The name that is used to call this function. In our example, it is cube. Parameter list. The parameters are the types of arguments the function expects as input, and the names by which the function will refer to those inputs. cube() takes an int as its only argument, and this int will be referred to as input in the function's body. Note that the parameter's list can be empty, which would indicate that the function doesn't expect any arguments. The body is the code within the curly braces that is executed when the function is called. It's important to remember that variables declared in the body of a function are local in scope to that function and cannot be used in other functions! One or more of the statements in the function's body can be return statements. A return statement ends the execution of the function and passes the return value back to the function that called it.
#include <stdio.h> int cube(int input); int main(void) { int x = 2; printf("x is %i\n", x); x = cube(x); } int cube(int input) int output = input * input * input; return output; Let's put everything together now! We've got our familiar main() function which calls cube() using the function's name and the argument we want to pass. Note that here the argument to cube() happens to be a variable, but more generally arguments can be constants or other expressions. But what is that line above main() that we've highlighted in pink? This is called a function prototype. As you can see, the function prototype matches the header of the function definition except that it ends with a semicolon. Why do we need this prototype before main()? The function prototype lets us get away with calling a function when the compiler hasn't yet seen the function's full definition (the C compiler reads from top to bottom) by providing the function’s type signature. In other words, the function prototype defines what variable types the function accepts as input and returns as output so that the compiler understands how the function should be used in main(). If you removed the prototype, you'd get a compiler error "implicit declaration of function 'cube' is invalid in C99". So you have two choices: move the definition of cube() above the definition of main() or declare a function prototype for cube() above main() as we did in this slide.
Now that you have a basic idea of how to create your own functions, let's make sure you understand how these functions are represented in memory: At the top of the program's memory is the text segment, which contains the actual 0's and 1's that make up your program. The sections for initialized and uninitialized data are used for storing global variables if your program has any. Most of a program's memory is reserved for the stack and heap. We won't talk about the heap today, but the stack consists of chunks of memory piled on top of each other. Just like the stack of trays in the dining hall, the stack of a program requires that the one on top be taken off before any of the others can be taken off.
cube()'s locals cube()'s parameters main()'s locals Let's zoom in on the stack. The memory at the bottom of the stack is first used for main(). Any functions that main() calls have their memory stacked on top, so we see our function cube() right above main(). As functions return, their chunks of memory are popped off the stack. The important function implication for us is that when main() calls cube(), cube() creates copies of the variables passed to it which are stored in cube()'s stack frame. We'll see in the next example why this is such a big deal. cube()'s parameters main()'s locals main()'s parameters
#include <stdio.h> void swap(int a, int b); int main(void) { int x = 1; int y = 2; swap(x, y); printf("x is %i\n", x); printf("y is %i\n", y); } void swap(int a, int b) { int tmp = a; a = b; b = tmp; } This program attempts to swap the values of x and y. Let's think through the logic of that swap function: Take the value of a and store it in a temporary variable tmp. Store the value of b in a. Store the value of tmp in b. Seems perfectly reasonable, right? Why doesn't this work? Well, swap() has made copies of x and y in a different stack frame and is calling those copies a and b. Whatever operations swap() performs on a and b have no bearing on our original x and y variables in main()'s stack frame. Discuss ways to fix this program!
Command-line Arguments int main(void) int main(int argc, string argv[]) Programs, like functions, can take in arguments. Thus far, we haven't passed any arguments to main, and have declared it like this: int main(void) However, we can also declare main like this: int main(int argc, string argv[]) The parameters argc and argv provide a representation of the program's command line. argc is the number of strings that make up the command line (including the program name), and argv is an array that contains those strings.
Test Yourself jharvard@appliance (~): ./copy infile outfile 1. What is argc? 2. What is argv[0]? 3. What is argv[1]? 4. What is argv[2]? 5. What is argv[3]? 6. What is argv[4]? jharvard@appliance (~): ./copy infile outfile 1. 3 2. ./copy 3. infile 4. outfile 5. NULL 6. undefined
Mario Revisited jharvard@appliance (~): ./mario 10 Remember mario.c from pset 1? What if we modified the problem specification so that the program must read the pyramid height from the command line? Now, instead of prompting the user to enter a height using printf() and GetInt(), the user simply enters height on the command line. ./mario 10 should result in the printing of a pyramid of height 10.
int main(int argc, string argv[]) { if (argc != 2) printf("Usage: mario height"); return 1; } int height = atoi(argv[1]); // etc . . . First, check for correct usage by ensuring that only two command line arguments were entered (the program name and the pyramid's height). If argc != 2, instruct the user regarding correct usage and quit the program. Note the use of the function atoi(). atoi() converts ASCII strings to integers. That is, the command line string "10" becomes the integer value 10. So instead of relying on GetInt() to provide user input, we can now take the value supplied by the user on the command line to use as height.