W1 - Shorts 📚🔍 - Intro to C Language and CS50 Library
Read more here
CS50 IDE: https://cs50.dev/ (Online VSCode)
CS50 Library Documentation: https://manual.cs50.io/#cs50.h
#CS50x #C #Computer_Science #VisualStudioCode
Table of Contents:
A) Introduction to Data Types and Variables in C 🎦
Video: https://youtu.be/Fc9htmvVZ9U
A.0) Overview of Data Types (Modern Languages vs C)
In modern programming languages like PHP and JavaScript, you don’t need to specify [[data types]] when declaring variables; the language infers the type based on the value.
Example - JavaScript:
let number1 = 2, number2 = 23, number3 = 99;
console.log(number1); // 2
console.log(number2); // 23
console.log(number3); // 99
However, C, being an older language, requires that you explicitly specify the data type of a variable the first time you declare it.
Example - C:
int number = 17;
char letter = 'H';
Read more:
A.1) Common Data Types in C (int, char, float, etc)
-
int
(Integer): 32 bits- Used for variables that store whole numbers (e.g.,
1
,-2
,1000
). - Takes up 4 bytes of memory, which is 32 bits.
- The range of values for
int
is approximately -2 billion to +2 billion (-2^31
to2^31 - 1
). - Half of the range is for negative numbers, and half is for positive numbers, with one spot reserved for
0
.
- Used for variables that store whole numbers (e.g.,
-
unsigned int
: 32 bits- A modifier (qualifier) for
int
that allows only positive values. - Doubles the positive range (approximately
0
to4 billion
, or2^32 - 1
) by eliminating negative numbers.
- A modifier (qualifier) for
-
char
(Character): 8 bits- Used for storing single characters (e.g.,
'A'
,'b'
,'3'
). - Takes up 1 byte (8 bits) of memory.
- The range of values is -128 to +127 (
-2^7
to2^7 - 1
). - Uses ASCII encoding to map numbers to characters (e.g.,
65
for'A'
,97
for'a'
, and48
for'0'
).
- Used for storing single characters (e.g.,
-
float
(Floating Point): 32 bits- Used for numbers with decimal points (e.g.,
3.14
,-0.001
). - Takes up 4 bytes (32 bits) of memory.
- Precision is limited due to the finite number of bits; decimal precision may be limited to about 7 significant digits.
- Used for numbers with decimal points (e.g.,
-
double
(Double Precision Floating Point): 64 bits- Similar to
float
but with double the precision, taking up 8 bytes (64 bits). - Offers higher precision and can represent more digits after the decimal, useful for tasks that require greater accuracy (e.g.,
pi
with more than 15 digits).
- Similar to
A.1.1) Special Type: void
void
is a type, not a data type.- It indicates "no value" and is often used:
- As a return type for functions that do not return any value (
void functionName()
). - In the parameter list of functions (
int main(void)
) to signify that the function takes no arguments.
- As a return type for functions that do not return any value (
A.1.2) CS50-Specific Data Types (bool and string)
-
bool
:- A data type introduced by CS50's library (
#include <cs50.h>
). - Stores Boolean values (
true
orfalse
). - In modern languages,
bool
is standard, but in C, it’s not built-in.
- A data type introduced by CS50's library (
-
string
:- Represents a sequence of characters (words, sentences).
- Not a native type in C but provided by CS50's library.
- Used for storing text data (e.g.,
string name = "CS50";
).
A.1.3) In the next Lectures: "Structures" and "Defined Types"
A.2) Working with Variables in C (declaration, assignment and initialization)
-
Declaring a variable: Specify the type followed by the variable name, ending with a semicolon.
int number; char letter;
-
Assigning values: Use the assignment operator
=
to store a value in a variable.number = 17; letter = 'H';
-
Initialization: Combine declaration and assignment in one line.
int number = 17; char letter = 'H';
-
Multiple variable declarations: Declare multiple variables of the same type in one line.
int height, width; // Equivalent to: int height; int width;
A.2.1) Example of Variable Use and Best Practices
- Best Practices:
- Declare variables only when needed to maintain clean code and proper scope management.
- Avoid re-declaring a variable, as it can lead to unintended behaviors.
Example - using printf()
#include <stdio.h>
#include <cs50.h>
int main(void)
{
// Declare and initialize variables
int number = 42;
char letter = 'A';
bool isCS50Fun = true;
// Print variables
printf("Number: %d\n", number);
printf("Letter: %c\n", letter);
printf("CS50 is fun: %s\n", isCS50Fun ? "true" : "false");
}
A.3) Key Takeaways: Data Types
- C requires explicit data type declarations.
- Understanding the size and range of data types is important for effective memory management.
- The CS50 library expands C’s capabilities by providing more user-friendly types like
bool
andstring
. - Proper variable initialization and careful attention to type handling help avoid common pitfalls in programming with C.
This overview should give you a clear idea of data types, their usage, and how to work with variables in C programming.
B) Operators in C 🎦
Video: https://youtu.be/f1xZf4iJDWE
B.1) Assignment Operator ( = )
The assignment operator (=
) allows you to assign a value to a variable.
For instance:
int x = 10; // Assigns the value 10 to the variable x
In this example, =
assigns 10
to x
.
B.2) Arithmetic Operators (+, -, *, /
)
C supports basic arithmetic operations:
- Addition (
+
) - Subtraction (
-
) - Multiplication (
*
) - Division (
/
)
Example:
int y = 10;
int x = y + 1; // x is now 11
x = x * 5; // x is now 55
Note: Assignment in C works such that the right-hand side of the equation is evaluated first and then assigned to the left-hand variable. For instance, x = x * 5
means x
is multiplied by 5
, and the result (55
in this case) is stored in x
, overwriting its previous value.
B.2.1) Modulus Operator explained (%)
The modulus operator (%
) calculates the remainder when one integer is divided by another.
int m = 13 % 4; // m is now 1, because 13 divided by 4 gives a remainder of 1
Use Case: Modulus is useful for:
- generating specific ranges of numbers (random number generators)
- determining even and odd numbers.
B.3) Shorthand Assignment Operators (combine assignment with arithmetic)
C provides shorthand operators for common arithmetic tasks:
x += 5
is equivalent tox = x + 5
x -= 5
is equivalent tox = x - 5
x *= 5
is equivalent tox = x * 5
x /= 5
is equivalent tox = x / 5
x %= 5
is equivalent tox = x % 5
These make the code more concise and readable.
Increment and Decrement Operators:
- Increment (
++
):x++
or++x
increasesx
by 1. - Decrement (
--
):x--
or--x
decreasesx
by 1.
Example:
int x = 10;
x++; // x is now 11
x--; // x is back to 10
B.4) Boolean Expressions and Logical Operators (true, false)
Boolean expressions evaluate to either true
or false
.
These are used to compare values or make decisions in code.
True and False in C:
- True: Any non-zero value (e.g.,
1
,100
, etc.) - False:
0
Logical Operators:
-
AND (
&&
): True only if both operands are true.if (x && y) { /* Code runs only if both x and y are true */ }
-
OR (
||
): True if at least one operand is true.if (x || y) { /* Code runs if either x or y or both are true */ }
-
NOT (
!
): Inverts the truth value.if (!x) { /* Code runs if x is false */ }
Example Truth Tables:
- For AND (
&&
):true && true
→true
true && false
→false
false && true
→false
false && false
→false
- For OR (
||
):true || true
→true
true || false
→true
false || true
→true
false || false
→false
B.5) Relational Operators (greater than, less than, equal, etc)
Relational operators are used to compare two values:
- Less than (
<
) - Less than or equal to (
<=
) - Greater than (
>
) - Greater than or equal to (
>=
) - Equal to (
==
) - Not equal to (
!=
)
Example:
int x = 5, y = 10;
if (x < y) { /* This block runs because 5 is less than 10 */ }
if (x == y) { /* This block does not run because 5 is not equal to 10 */ }
Important Note:
A common beginner mistake is using =
(assignment) instead of ==
(equality check) when testing for equality.
if (x = y) { /* This assigns y to x and always evaluates to true if y is non-zero */ }
Always use ==
for comparison:
if (x == y) { /* Correct: checks if x is equal to y */ }
B.6) Conclusion: Operators in C
Understanding these operators is essential for:
- manipulating data,
- making comparisons,
- and controlling the flow of a program in C.
While the syntax might initially seem complex, with practice, these concepts become second nature and help make code more efficient and expressive.
C) Conditional Statements in C 🎦
Video: https://youtu.be/1wsaV5nVC7g
Conditional statements in C allow programs to make decisions and take different paths based on certain conditions. They enable programs to execute specific blocks of code when conditions are met or bypass them otherwise. Let's break down the main types of conditional statements in C:
C1. if
Statement
The if
statement is one of the simplest forms of conditionals. It checks a condition, and if the condition is true, it executes the code block within the curly braces.
Syntax:
if (condition) {
// Code to execute if the condition is true
}
Behavior: If the condition is true
, the code within the braces runs. If false
, it is skipped.
Example:
if (x < 10) {
printf("x is less than 10.\n");
}
C2. if-else
Statement
The if-else
statement allows a program to take one path if a condition is true and another if it is false.
Syntax:
if (condition) {
// Code to execute if the condition is true
} else {
// Code to execute if the condition is false
}
Behavior: Only one block of code (either if
or else
) runs, depending on the condition.
Example:
if (x > 0) {
printf("x is positive.\n");
} else {
printf("x is not positive.\n");
}
C3. if-else if-else
Chain
The if-else if-else
chain allows multiple conditions to be checked in sequence. Only one block of code runs—the first block with a true condition.
Syntax:
if (condition1) {
// Code for condition1
} else if (condition2) {
// Code for condition2
} else {
// Code if none of the above conditions are true
}
Behavior: Only one block runs, even if multiple conditions are true
. The first true
condition’s block is executed.
Example:
if (x > 10) {
printf("x is greater than 10.\n");
} else if (x == 10) {
printf("x is exactly 10.\n");
} else {
printf("x is less than 10.\n");
}
Key Point: Each block in an if-else if-else
chain is mutually exclusive—only one block can execute.
C4. Non-Mutually Exclusive if
Statements
In some cases, it’s possible to have separate if
statements that aren't part of an if-else if-else
chain, meaning multiple blocks of code could potentially execute if their conditions are true.
Example:
if (x > 5) {
printf("x is greater than 5.\n");
}
if (x < 15) {
printf("x is less than 15.\n");
}
Behavior: Both conditions are checked independently. If both are true
, both blocks will run.
C5. switch
Statement
The switch
statement is an alternative to if-else
chains when dealing with discrete values. It allows you to specify distinct cases for a variable or expression.
Syntax:
switch (variable) {
case value1:
// Code for value1
break;
case value2:
// Code for value2
break;
// Add more cases as needed
default:
// Code if none of the cases match
}
Behavior: The matching case
runs, and then break
stops the execution from continuing to subsequent cases.
Example:
int x = GetInt();
switch (x) {
case 1:
printf("You entered one.\n");
break;
case 2:
printf("You entered two.\n");
break;
default:
printf("You entered a number other than one or two.\n");
}
Important: Always use break
to prevent fall-through behavior, where the program continues to execute subsequent cases. However, you can intentionally omit break
to let cases fall through, which can be useful in specific situations (e.g., cascading output).
Fall-Through Example (without break
):
switch (x) {
case 4:
printf("Four.\n");
case 3:
printf("Three.\n");
case 2:
printf("Two.\n");
case 1:
printf("One.\n");
printf("Blast off!\n");
break;
}
Behavior: If x
is 4
, it will print "Four," "Three," "Two," "One," and "Blast off!" due to fall-through.
C6. Ternary Operator (?:
)
The ternary operator is a compact way to write if-else
statements with simple expressions.
It is structured as condition ? expression1 : expression2
.
Syntax:
variable = (condition) ? value_if_true : value_if_false;
Example:
int x = 10;
int y = (x > 5) ? 20 : 30; // y will be 20 if x > 5, otherwise y will be 30.
Explanation:
- The condition before
?
is checked. - If true, the value after
?
is assigned toy
. - If false, the value after
:
is assigned toy
.
Use Case: This operator is great for simple conditional assignments and can make code look concise.
C7. Summary: Conditional Statements in C
if
,if-else
, andif-else if-else
: Use these for flexible and complex conditional branching.switch
: Best for cases involving discrete, distinct values.- Ternary Operator (
?:
): Ideal for simple, single-line conditional assignments.
These conditional statements provide powerful tools for controlling the flow of a program, allowing different code paths based on dynamic conditions and user input.
D) Loops in C 🎦
Video: https://youtu.be/WgX8e_O7eG8
Loops are essential in programming because they allow you to execute a block of code multiple times without duplicating the code. This makes your programs more efficient and easier to read.
In C, there are three main types of loops, each suited for different use cases:
while
Loopdo while
Loopfor
Loop
D.0) Use Cases (how to pick a Loop)
while
loop: When you don’t know in advance how many times you need to loop.do while
loop: When you need to run the loop at least once, such as getting user input.for
loop: When you know how many times the loop should run, such as iterating over a range or array.
D1. while
Loop
A while
loop runs as long as a specified condition is true
. If the condition is false
when the loop is first encountered, the code inside will not run at all.
Syntax:
while (condition) {
// Code to run while the condition is true
}
Example:
int x = 0;
while (x < 5) {
printf("%d\n", x);
x++;
}
- Use Case: Use a
while
loop when you need to repeat code an unknown number of times, such as keeping a game running while the user has lives left.
D.1.1) Note: Infinite Loops (while (true)
)
An infinite loop runs indefinitely. This can be intentional (e.g., running a server) or unintentional (a common bug).
Note: using a while (true)
loop,
Example:
while (true) {
// This will run forever
}
Stopping an Infinite Loop:
- Use
break
to exit an infinite loop:while (true) { printf("Running...\n"); break; // Exits the loop }
Tip: Press Ctrl + C to force-stop a running program from the command line.
D2. do while
Loop
A do while
loop is similar to a while
loop, but it guarantees that the code inside runs at least once before the condition is checked.
Syntax:
do {
// Code to run at least once
} while (condition);
Example:
int num;
do {
printf("Enter a positive number: ");
scanf("%d", &num);
} while (num <= 0);
- Use Case: A
do while
loop is perfect for scenarios where the code must run at least once, such as prompting a user for input.
D3. for
Loop
A for
loop is used when you know in advance how many times you need to repeat a block of code.
It consists of three main parts:
- initialization,
- condition,
- and increment/decrement.
Syntax:
for (initialization; condition; increment) {
// Code to run a specific number of times
}
Example:
for (int i = 0; i < 10; i++) {
printf("Iteration %d\n", i);
}
- Breakdown:
- Initialization:
int i = 0
sets up the loop variable. - Condition:
i < 10
is checked before each iteration. Iffalse
, the loop stops. - Increment:
i++
runs after each iteration.
- Initialization:
- Use Case: Use a
for
loop when you need to run code a set number of times, like looping through an array.
D4. Loop Control Statements (brake and continue)
break
: Exits the loop immediately.continue
: Skips the rest of the code in the loop for the current iteration and moves to the next iteration.
Example of continue
:
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue; // Skip printing when i is 2
}
printf("%d\n", i);
}
D5. General Advice: using Loops in C
- Interchangeability: While loops can often be replaced with each other (e.g., a
for
loop can be used in place of awhile
loop), it is best practice to use the loop that most clearly expresses your intent. - Readability: Use the loop type that makes your code more understandable and maintainable.
- Avoid infinite loops: Ensure that your loop conditions are correct to avoid unintentional infinite loops.
Loops are powerful and essential for efficient programming. Using them correctly will make your code more flexible and reduce redundancy.
E) Basic Linux Command Line Operations 🎦
Video: https://youtu.be/BnJ013X02b8
The Linux command line is a powerful tool that allows programmers to interact directly with the operating system.
Although many Linux distributions offer Graphical User Interfaces (GUIs) similar to those found in Windows and macOS,
the command line provides an efficient way to execute commands using only a keyboard.
This is especially useful in programming environments like the CS50 IDE, which runs on Ubuntu, a popular Linux distribution.
E.1) Basic Command Line Commands
1. ls
("List" contents)
-
Purpose: Lists the contents of the current directory, showing all files and folders.
-
Command:
ls
-
Example: Typing
ls
in a terminal might output:
Here, files are displayed in black, executables in green, and directories in blue.
2. cd
("Change Directory")
-
Purpose: Navigates between directories.
-
Command:
cd directory_name
-
Special Cases:
cd .
refers to the current directory (no action taken).cd ..
moves up one directory level (to the parent directory).cd
with no arguments returns to the home directory (~
).
Usage Example:
cd pset1 # Moves into the 'pset1' directory
cd .. # Moves back to the parent directory
cd # Moves to the home directory (equivalent to cd../..)
- Example:
3. pwd
(Show "Present Working Directory")
-
Purpose: Displays the full path of the current directory.
-
Command:
pwd
-
Example:
4. mkdir
("Make Directory")
-
Purpose: Creates a new directory.
-
Command:
mkdir new_directory
-
Example:
mkdir pset2
# Creates a directory named 'pset2'
5. cp
("Copy" and Copy Directory -r
)
-
Purpose: Copies files or directories.
-
Command:
cp source_file destination_file
-
Example 1:
cp hello.txt hi.txt
# Copies 'hello.txt' to 'hi.txt'
-
Copying Directories:
- To copy a directory and its contents, use the
-r
flag for recursion:
cp -r source_directory destination_directory
- To copy a directory and its contents, use the
-
Example 2:
cp -r pset0 pset3
# Recursively copies the 'pset0' directory to 'pset3'
6. rm
("Remove" and Remove Directory -r
- with force option -f
)
-
Purpose: Deletes files or directories.
-
Command:
rm file_name
-
Example:
rm hi.txt
# Deletes the file 'hi.txt'
-
Command (Force):
rm -f file_name
-
Example:
rm -f hello.txt
# Forcibly Deletes the file 'hi.txt' without confirmation
-
Deleting Directories:
- Use the
-r
flag to delete a directory and its contents recursively. - Use the
-f
flag to force deletion without prompts:
- Use the
- Command:
rm -r directory_name
- Example:
rm -r pset2
# Deletes the 'pset2' directory and its contents
- Command (Force):
rm -rf directory_name # Deletes without confirmation
- Example:
rm -rf pset3
# Forcibly deletes 'pset3' without confirmation
7. mv
("Move"/Rename)
-
Purpose: Moves files or directories or renames them.
-
Command:
mv source_file destination_file
-
Example (rename):
mv greddy.c greedy.c
# Renames 'greddy.c' to 'greedy.c'
-
Example (move):
mv file.txt ../
# Moves 'file.txt' to the parent directory`
mv file.txt ../ # Moves 'file.txt' to the parent directory
E.2) Practical Tips for Command Line Usage
- Clearing the Screen: Use
Ctrl + L
to clear the terminal screen and start fresh. - Control and Navigation:
- Infinite Loops: If you accidentally create one, stop it with
Ctrl + C
.
- Infinite Loops: If you accidentally create one, stop it with
- Caution with
rm -rf
: This command can permanently delete files and directories without confirmation, so use it carefully to avoid unintentional data loss.
E.3) Summary: using the Command Line
These basic commands are fundamental for navigating and managing files in any Unix-based system, including Linux and macOS. Mastery of these will enable you to work efficiently in a coding environment, such as the CS50 IDE, and allow you to perform common tasks directly from the terminal.
- chmod: Changes the permissions of a file or directory.
- ln: Creates hard or symbolic links to files or directories.
- touch: Creates an empty file or updates the timestamp of an existing file.
- rmdir: Removes empty directories.
- man: Displays the manual or help information for other commands.
- diff: Compares the contents of two files line by line.
- sudo: Executes commands with superuser (root) privileges.
- clear: Clears the terminal screen.
- telnet: Connects to remote computers using the Telnet protocol, mainly for network troubleshooting and testing.
Read more here
F) Magic Numbers and Symbolic Constants in C 🎦
Video: https://youtu.be/vK_naJkrtjc
F.1) What Are Magic Numbers?
Magic numbers refer to the use of unexplained numerical constants directly within code. These numbers often seem arbitrary and provide no immediate context or explanation for their values.
For example, in the Mario problem, you may have seen a hard-coded limit such as:
if (height > 23)
Here, 23
is a "magic number" because its purpose isn't clear to someone reading the code without additional context.
F.1.1) Why Avoid Magic Numbers?
Including magic numbers in your code is generally considered a poor practice because:
- Lack of clarity: Readers of your code may not understand the significance of the number.
- Maintainability issues: If you need to change the value, you may have to do so in multiple places throughout your code, increasing the risk of errors.
F.2) Example of a Magic Number (playing with a deck of cards)
Consider the following example:
for (int i = 0; i < 52; i++) {
// Deal a card
}
In this code, the number 52
is a magic number.
While a seasoned programmer might recognize this as the number of cards in a standard deck,
it's not immediately clear to everyone, and if the code were part of a larger suite involving card manipulation, it could lead to confusion.
F.2.1) Addressing Magic Numbers with Symbolic Constants
Method 1: Using Variables (not recommended)
One way to improve clarity is to use a named variable:
int deck_size = 52;
for (int i = 0; i < deck_size; i++) {
// Deal a card
}
This makes the code more readable, but there is a potential issue: if deck_size
is accidentally modified in another part of the program, it could cause unintended behavior.
Method 2: Using define
for Symbolic Constants
A more robust solution in C is to use a preprocessor directive called #define
to create symbolic constants:
#define DECK_SIZE 52
for (int i = 0; i < DECK_SIZE; i++) {
// Deal a card
}
- How it works: The preprocessor replaces all instances of
DECK_SIZE
with52
during compilation. This is similar to using the "find and replace" function in a text editor. - Benefits:
- Clarity: The meaning of the number is now obvious.
- Immutability: The value of
DECK_SIZE
cannot be changed during program execution, preventing accidental modification.
Syntax Reminder:
#define NAME REPLACEMENT
Note: Do not place a semicolon at the end of the #define
directive.
F.3) Examples of Symbolic Constants (use it with floats, strings, etc)
- Defining a mathematical constant:
#define PI 3.14159265
Example: This makes your code more readable:
float circumference = 2 * PI * radius;
- Defining a string:
#define COURSE "CS50" printf("Welcome to %s!\n", COURSE);
F.3.1) Note: Conventions for Symbolic Constants (use All-Uppercase Names)
It is common practice to use all-uppercase names for symbolic constants:
- Helps distinguish constants from variables.
- Reduces confusion and improves code readability.
F.4) Advantages of Using Symbolic Constants
- Easy modifications: Changing a constant's value requires updating only the
#define
line at the top of the code, which then propagates throughout the program. - Portability: For applications that need to adapt to different settings or regions, such as varying deck sizes in card games (e.g., 52 cards in the US and 32 cards in Germany), you can simply change one line:
After updating and recompiling the code, the new value is applied wherever#define DECK_SIZE 32
DECK_SIZE
is used.
Conclusion: avoid Magic Numbers
Magic numbers should generally be avoided in code due to their potential to confuse and complicate code maintenance. Replacing them with symbolic constants using #define
makes your code clearer, safer, and easier to modify.
Z) 🗃️ Glossary
File | Definition |
---|
Uncreated files | Origin Note |
---|---|
data types | W1 - Shorts 📚🔍 - Intro to C Language and CS50 Library |