Table of Contents
CS50 Notes -- mode: org --
1 Data Types
1.1 Further reading:
- Wikipedia entry: https://en.wikipedia.org/wiki/Data_type
1.2 CS50 Short
1.2.1 Data types and variables:
Data types are a unique characteristic of C(?). In other higher level languages, you don't usually have to declare the type of a variable - you just declare it and start using it.
In C we need to specify the data type of every variable we create the first time we use that variable.
1.2.2 C built-in data types:
- Qualifiers
Some qualifiers are: unsigned, short, long, const.
- int
- Characteristics
It is used for variables that will store integers. Integers always take up 4 bytes of memory (32 bits). The implication is that the range of values they can store is limited to 32 bits of information.
- Range
By convention, it was decided that this range of 32 bits would be split into two, and half of that range allocated to positive ints, and the other to negative ints. The range is -2(31st power) to 2(31st power)-1 (-1 due to make space for the zero). Roughly, that's from -2,000,000,000 to 2,000,000,000.
- Qualifier "unsigned"
Unsigned int is not a separate type of variable, but rather a qualifier.
Unsigned means that it doubles the range of values an integer can take at the expense of no longer allowing to take on negative integers.
Range
Useful for cases where we know that the value will never be negative, and is less than 4,000,000,000 (0 to 2 to the 32nd power -1).
- Characteristics
- char
- Characteristics
The char data type is used for variables that will store single characters.
Characters always take 1 byte of memory (8 bits). This means that the range of values store is limited to 8 bits worth of information.
ASCII provides a mapping of characters to numeric values (positive side of range only)
Useful link: https://www.asciichart.com/
- Range
-128 to 127
- Characteristics
- float
- Characteristics
The float (floating point number) data type is used for variables that will store floating-point values, also known as real numbers.
Floats always take up 4 bytes of memory (32 bits).
- Range
The precision of a float is 32 bits(?). There is a precision problem, and we are limited in how precise we can be with our decimal part. For instance, we cannot have a precise value where the decimal part is 100 or 200 digits long, due to us having only 32 bits to work with.
This can be remedied by using the double data type.
- Characteristics
- double
- Characteristics
The double data type is used for variables that will store floating-point values, like the float data type.
- Range
The difference between double and float is that doubles are double precision. They always take up 8 bytes of memory (64 bits). With more bits to work with, we can specify more precise real numbers.
- Characteristics
- void
1.2.3 CS50 library data types
1.2.4 Other
- structures (structs)
- defined types (typedefs)
1.2.5 How to work with variables
- First, assign a variable a type
- Secondly, give it a name.
Examples:
int number; char letter; int height, width; float sqrt2, sqrt3, pi;
In general, it's good practice to only declare variables when they're needed.
Using a variable:
- After a variable has been declared it is no longer necessary to specify that variable's type - this would probably result in an error while compiling.
int number; // declaration number = 17; // assignment int number = 17; // initialization
2 Operators
2.1 CS50 Short
2.1.1 Arithmetic Operators
We can add (+), subtract (-), multiply (*) and divide (/) numbers, as expected.
We also have the modulus (%) operator, which gives us the remainder when the number on the left of the operator is divided by the number on the right.
2.1.2 Boolean Expressions
Boolean expressions are used for comparing values.
All boolean expressions can only evaluate to true or false.
Boolean expressions can be used for decisions in conditionals or loops.
In C, every nonzero value is equivalent to TRUE, and zero is FALSE.
- Logical operators
Logical AND (&&):
x y (x && y) true true true true false false false true false false false false Logical OR (||):
x y (x || y) true true true true false true false true true false false false Logical NOT (!):
x !x true false false true / /
- Relational operators
- Less than (x < y)
- Less than or equal to (x <= y)
- Greater than (x > y)
- Greater than or equal to (x >= y)
Equality and Inequality
- Equality (x == y)
- Inequality (x != y)
3 Conditional Statements
3.1 CS50 Short
3.1.1 Conditionals
- If, Else If, Else statements
Conditional expressions allow programs to make decisions and take different paths depending on the values of variables or user input.
C provides a few different ways to implement conditional expressions (branches).
if (boolean-expression) { }
If the boolean expression above evalues to true, all lines of code between the curly braces will exexute from top to bottom. However, if it evaluates to false, the lines of code will not execute.
if (boolean-expression) { } else { }
In the same way, if the boolean expression evalues to false, all lines of code between the else curly braces will execute from top to bottom.
If else chains - mutually exclusive branches:
if (boolean-expression1) { // first branch } else if (boolean-expression2) { // second branch } else { // third branch }
Non-mutually exclusive branches:
if (boolean-expression1) { // first branch } if (boolean-expression2) { // second branch } else { // third branch }
Caution: the else will only bind to the nearest if statement.
- switch()
The switch statement allows specification of distinct cases instead of relying on boolean expressions to make decisions.
int x = GetInt(); switch(x) { case 1: printf("One!\n"); break; case 2: printf("Two!\n"); break; case 3: printf("Three!\n"); break; default: printf("Sorry!\n"); }
However, if we intend to fall through one of more cases, we can:
int x = GetInt(); switch(x) { case 3: printf("Three!\n"); case 2: printf("Two!\n"); case 1: printf("One!\n"); default: printf("Boom!\n"); }
This will take input e.g. 2 and then print:
Two! One! Boom!
- Ternary operator (?:)
int x; if (expr) { x = 5; } else { x = 6 } // Instead, we could do: int x = (expr) ? 5 : 6;
- Those two snippets of code act identically.
- The ternary operator (?:) is mostly a cute trick, but is useful for writing trivially short conditional branches. A good thing to be familiar with, even if I prefer not to use it.
- Use cases
- if (and if else, if-…-else)
Use Boolean expressions to make decisions
- switch
Use discrete cases to make decisions
- ?:
Use to replace a very simple if-else (fancy points)
4 Loops
4.1 CS50 Short
4.1.1 Loops
- Infinite loop
while (true) { }
In this case, the lines of code between the curly braces will execute repeatedly from top to bottom, until and unless we break out of it (as if with a break; statement) or otherwise kill our program.
- Evaluate w/ boolean expression
while (boolean-expr) { }
If the boolean-expr evaluates to true, all lines of code will be executed until boolean-expr evaluates to false.
- do while loop
do { } while (boolean-expr);
In this case, the code will always execute once, and then check if the boolean expression is true. The code will repeat while boolean-expr is true, and stop once it evaluates to false.
- for loop
for loops are syntactically unattractive, but for loops are used to repeat the body of a loop a specified number of times.
for (int i = 0; i < 10; i++) { } for (start; expr; increment) { }
The process:
- The counter variable(s) (i) is set.
- The boolean expression is checked.
– If it evaluates to true, the body of the loop executes – If it evaluates to false, the body of the loop does not execute
- The counter variable is increment and then the Boolean expression is checked again, etc.
- Use cases
- while
Use when we want a loop to repeat an unknown number of times, and possibly not at all. Example: control flow for a game; we do not know how long the user is going to keep playing, but we may want to perform some actions while the game is running, such as keeping track of positions, etc.
- do while
Use when we want a loop to repeat a number of times, but at least once. Example: asking user for input that matches certain criteria.
- for
Use when we want a loop to repeat a discrete number of times, though we do not know the number at the time of compilation.
Loops are interchangeable, but they should be used in the right context/use case.
5 Functions
5.1 CS50 Short
Smaller programs can be written all inside of main(). However, as programs become more complex (running into the thousands and tens of thousands of lines of code), it isn't a very good idea to keep everything inside of main.
C and most programming languages allow us to write functions.
Functions are also known as procedures, methods (particularly in OOP/audit-oriented languages), or subroutines.
5.1.1 What is a function?
- A black box with a set of 0+ inputs and 1 output.
a b ---> [func(a,b,c)] ---> z c
The function func() takes a, b, and c as inputs, processes them in a certain way, and finally returns a single output - z.
3 6 ---> [add(a,b,c)] ---> 16 7
We call it a black box because we do not necessarily know how the function is implemented.
It is a good practice to name functions descriptively, as well as having good documentation.
5.1.2 Why use functions?
- Organization
Functions help us break up a complicated problem into more manageable subparts.
- Simplification
Smaller components tend to be easier to design, implement, and debug.
- Reusability
Functions can be recycled; you only need to write them once, but can use them as often as you need.
5.1.3 Function declarations
- Function declarations
The first step to creating a function is to declare it. This gives the compiler a heads-up that a user-written function appears in the code.
- Function declarations should always go atop the code, before main()
There is a standard form that every function declaration follows:
return-type name(argument-list);
- The return-type is what kind of variable the function will output.
- The name is what we choose to call our function.
- The argument-list is the comma-separated set of inputs to the function, each of which has a type and a name.
Examples:
int add_two_ints(int a, int b); // function declaration float mult_two_reals(float x, float y) // function definition { float product = x * y; return product; } Or: return x * y;
5.1.4 Function Calls
To call a function, pass an appropriate argument and assign its return value to something of the correct type.
// includes #include <cs50.h> #include <stdio.h> // declare functions int add_two_ints(int a, int b); int main(void) { // ask user for input printf("Integer: "); int x = GetInt(); printf("Second integer: "); int y = GetInt(); // add the two numbers together via a function call int z = add_two_ints(x, y); // output the result printf("Result: %i\n", z) } int add_two_ints(int a, int b) { return a + b; }
5.1.5 Misc
- Functions can sometimes take no inputs, for which we have to declare the function as having a void argument list, as in int main(void).
- Function sometimes do not have an output - in that case we declare the function as having a void return type.
6 Variables & Scope
6.1 CS50 Short
6.1.1 Scope
Scope is a characteristic of a variable that defines from which functions that variable can be accessed.
- Local variables
- Local variables can only be accessed within the functions in which they are created. So, if a variable is created within main(), then it can be accessed from anywhere within main(). However, if we have a custom function and try to use a variable defined in main(), this will not work.
int main (void) { int result = triple(5) } int triple(int x) { result x * 3; }
- Here, the x is local to the function triple(). No other function can refer to that variable, not even main(). result is local to main().
- Global variables
– Global variables can be accessed by any function in the program, because they are declared outside of functions.
#include <stdio.h> float global = 0.5050; int main(void) { triple(); printf("%f\n", global); } void triple(void) { global *= 3; }
- Here, global is global to all functions in the program, and we can refer to it from any part of the program.
Caution: a function can change the value of a variable before it is expected to be changed.
- Why it matters?
- For the most part, local variables in C are passed by value in function calls.
- When a variable is passed by value, the callee received a copy of the passed variable, not the variable itself.
- This means that the variable in the caller is unchanged unless overwritten.
Examples:
- No effect on foo:
int main(void) { int foo = 4; triple(foo); } int triple(int x) { return x *= 3; }
- Overwrites foo:
int main(void) { int foo = 4; foo = triple(foo); } int triple(int x) { return x *= 3; }
- Things can get particularly insidious if the same variable name appears in multiple functions, which is perfectly okay as long as the variables exist in different scopes.
7 Arrays
7.1 CS50 Short
7.1.1 Definition
- Arrays are a fundamental data structure.
- Arrays hold values of the same type at contiguous memory locations.
7.1.2 Structure
- In C, the elements of an array are indexed starting from 0!
- If an array consists of n elements, the first element is located at index 0, and the last element is located at inded (n-1).
- C is very lenient, and will not prevent us from going "out of bounds" of our array - caution, possible segmentation fault.
7.1.3 Array declaration
type name[size];
- Type is what kind of variable each element of the array will be.
- Name is what we wish to call the array.
- The size is how many elements we want our array to contain.
- Declaring an array:
int student_grades[40];
- Using an array:
// Create an array of 10 booleans bool truthtable[10]; truthtable[2] = false; if (truthtable[7] == true) { printf("TRUE!\n"); } /* An array of 10 booleans should end at truthtable[9]. C won't stop us from checking what's on truthtable[10], but we risk running into segmentation faults. */ truthtable[10] = true;
- Declaring & initializing an array:
When declaring and initializing an array simultaneously, there is a special syntax that may be used to fill up the array with its starting values:
// instantiation syntax (ideal for small arrays) bool truthtable[3] = { false, true, true }; // instantiation syntax without needing to declare size of array bool truthtable[] = { false, true, true }; // individual element syntax bool truthtable[3]; truthtable[0] = false; truthtable[1] = true; truthtable[2] = true; // populating an array using a for loop for (i = 0; i < 2; i++) { truthtable[i] = i; } // multiple dimensions in array bool battleship[10][10]; // We can choose to conceptualize the above as a grid, though in memory it's still just a 100-element one dimensional array. // Multi-dimensional arrays are great abstractions to help visualize boards or other complex representations.
7.1.4 Copying elements from one array to another
- While we can treat individual elements of arrays as variables, we cannot treat entire arrays themselves as variables.
- For instance, we cannot assign one array to another using the assignment operator like so: array1 = array2. To do this, we must use a loop to copy over the elments one at a time.
#+BEGINSRC C
for (i = 0; i < length; i++) { array1[i] = array2[i]; }
#+ENDSRC C
7.1.5 Variable scope
- When calling a variable in a C function, most of them are passed by value (meaning, the function receives a copy of the variable).
- Passing by reference instead of by value
- Arrays are not passed by value. Rather, they are passed by reference. The callee receives the actual array, NOT a copy of it. This means that when the callee manipulates the elements of an array, the array itself is changed. This will be explained later.
What happens here?
void set_array(int array[4]); void set_int(int x); int main(void) { int a = 10; int b[4] = { 0, 1, 2, 3 }; set_int(a); set_array(b); printf("%d %d\n", a, b[0]); } void set_array(int array[4]) { array[0] = 22; } void set_int(int x) { x = 22; }
- int a gets 10;
- int b is an array with an index of 4, already initialized;
- A function setint() is called and gets a as input, but has no output;
- setint() received A COPY (passing by value) of int a, but returns nothing (void)
- setarray takes the b array and passes it by reference to the function. Even though nothing is returned (void output), the array itself is changed.
- Output is 10, 22.