W1 - 👨🏫 Lecture - Intro to C Language and CS50 Library
Video: https://youtu.be/zYierUhIFNQ
Find your old progress here:
https://github.com/me50/Maurilio97-P/branches/yours
Video: https://youtu.be/cwtpLIWylAw
#CS50x #C #Computer_Science #VisualStudioCode
Read more here
CS50 IDE: https://cs50.dev/ (Online VSCode)
CS50 Library Documentation: https://manual.cs50.io/#cs50.h
Table of Contents:
A) Introduction to C Programming
This week, we dive into the programming language C, transitioning from last week's Scratch—a playful, graphical language—to a more traditional, text-based environment.
Unlike Scratch, C is more complex, using precise syntax that may initially feel overwhelming,
However, despite the differences, the core programming concepts are the same. C, like Scratch, uses functions, conditions, Boolean expressions, and loops; the syntax is just different.
A.1) Writing Good Code with Correctness, Design, and Style
When writing code, three key principles help guide us:
- Correctness: Does the code work as intended?
- Design: How efficient is the code? Does it avoid unnecessary repetition?
- Style: Is the code easy to read? This includes formatting, punctuation, and organization, similar to using good grammar in an essay.
A.2) Introducing the Compiler: Bridging Source Code and Machine Code
In programming, we work with what’s called [[source code]]—the human-readable instructions that we write and understand.
However, computers don’t “speak” our language; they process [[machine code]]—binary instructions in zeros and ones.
These binary patterns dictate every letter, number, and function the computer displays or performs.
Translating from source code to machine code is essential because, while you and I work comfortably in [[source code]], computers rely on [[machine code]] for all operations.
Writing [[machine code]] by hand would be tedious, even impractical, which is why we use [[higher-level languages]] like C.
These languages enable us to work at a “high level” without manually creating binary instructions.
Since we write in [[source code]] but need [[machine code]] for execution, a special tool—the [[compiler]]—does the translation for us.
A [[compiler]] is a program designed to take our code and convert it to machine-readable instructions.
Numerous companies provide free compilers, making this process more accessible than ever.
B) 📝Using CS50 IDE for C Programming (online VS Code) 💻🌐
C programming involves a tool called an IDE (Integrated Development Environment),
Video: https://youtu.be/2x9S2Rtc5WY
Video: https://youtu.be/NRPdh20kyEc
for this course we will be using the CS50 IDE ,
In CS50, we use a cloud-based platform, https://cs50.dev to streamline coding.
This platform runs on Visual Studio Code (VS Code), a popular industry-standard tool used by professional programmers.
Our cloud-based setup minimizes technical hurdles associated with different operating systems, enabling you to focus on coding.
By the course’s end, we’ll transition from the cloud environment to using local versions of these tools on your own computer.
B.1) Navigating the CS50 Dev Environment
When you log into CS50.dev, you’ll see an interface with three main areas:
- Code Editor (top) – Where you write and edit code in tabbed files, similar to text documents.
- Terminal Window (bottom) – Used to enter commands for compiling and running code.
- File Explorer (left) – A place to manage and organize your files and folders within CS50.
- To the side, we have the Activity Bar with features specific to VS Code as well as unique tools for CS50. If you're using your own version of CS50.dev, you can access additional options by clicking the ... menu or by zooming out to view everything. You'll also see CS50’s virtual Rubber Duck—a helpful guide available throughout the course to answer any questions you may have. More on how to use it soon!
This setup combines both:
- a Graphical User Interface (GUI), which is visual and click-based,
- and a Command-Line Interface (CLI), which relies on typing commands.
The CLI may feel like a step back from modern visual interfaces, but it enhances productivity by allowing for efficient command-based workflows, a skill commonly used by developers.
B.2) Linux and Command-Line Basics (ls, mv, rm, cd) 🐧
Introducing Linux, an operating system used in CS50’s servers,
we highlight Command-Line Interface (CLI) interactions that allow for efficient text-based operations without a graphical interface.
By typing commands, we replace GUI functions, adding speed and versatility to coding tasks.
cd
: Change Directory – Used to navigate between different directories or folders in the file system.cp
: Copy – Copies files or directories from one location to another.ls
: List – Displays the contents of a directory.mkdir
: Make Directory – Creates a new directory.mv
: Move – Moves or renames files or directories.rm
: Remove – Deletes files from the file system.rmdir
: Remove Directory – Deletes an empty directory from the file system.
- etc....
Exploring Linux Containers: Through CS50.dev, each student works within a [[Docker container]], a virtual Linux environment with pre-configured tools, including a compiler. These containers allow you to code with isolated, safe workspaces connected directly to VS Code’s interface.
B.2.1) Trying some of the Commands ⌨️
-
Commands Overview:
ls
: Lists files in the current directory, providing an overview of your working folder.
mv
: Moves or renames files,
Example: rename meow.c
to woof.c
replacing the GUI’s "rename" function.
rm
: Removes files, but cautiously, as deletion is permanent.
-
Navigating Directories: In a command line environment, managing directories (folders) and files is essential for organizing your work. Let’s break down how to create, move, and remove directories, as well as some common navigation commands.
cd
: it allow us to change directories, while the prompt updates to show our current location.
mkdir
: it allow us To create a directory (or folder)
This creates a directory named lecture
. If you check your file tree, you’ll see the new folder appear. It will be empty until you move files into it.
mv
: Moves or renames files. To move a file into the directory, use themv
(move) command:
Moving a File Back to the Parent Directory:
Let's say we change directory to have access to hello.c
And now we want To move hello.c
back to its original location (one level up), use:
The ..
notation represents the parent directory. This command moves hello.c
up one level. You will now see hello.c
outside of the lecture
folder in your file tree. When you type ls
inside lecture
, it will be empty.
Navigating Up One Directory Level
To move yourself up one directory level, use:
This changes your current directory to the parent directory, indicated in the prompt as ~/
, which is your home directory.
Removing a Directory
To remove an empty directory, use rmdir
:
This command deletes the lecture
directory. If you list the files in the current directory with ls
, you’ll see that the lecture
folder is gone.
Other Useful Commands:
cp
(Copy): Copies files or directories.
For example,
cp file1 file2
makes a duplicate of file1
named file2
.
Summary:
These standard commands are widely used in command line interfaces across operating systems like Linux, macOS, and Windows (with some variations). Mastering them enhances your ability to manage files and directories efficiently in a non-GUI environment.
Command-line practices might seem unfamiliar initially, but they enable a powerful, efficient workflow for navigating, editing, and managing files across environments. We’ll see more of these tools as we progress, reinforcing their role in a streamlined coding process.
Read more here
B.2.2) Explanation of . and .. Notation
.
(Single Dot): Refers to the current directory. For example, running./hello
is a way of telling the system to execute thehello
program located in the current directory.
The ./
notation helps specify that you want to run a program located in the current directory. This is similar to double-clicking an executable file in a GUI environment.
..
(Double Dot): Refers to the parent directory, allowing you to move up the directory hierarchy.
________________________________________________________________
C) Example 1: Creating Our First C Program (hello.c) 👋🌎
Here is the code we will try to implement today:
#include <stdio.h>
int main(void) {
printf("hello, world\n");
}
Here’s a quick breakdown:
#include <stdio.h>
imports a library with standard input-output functions.int main(void)
is the entry point of the program.printf("hello, world\n");
displays “hello, world” on the screen.
C.1) Creating, Compiling and Running Code (CLI commands)⌨️
To write and execute code in C, we’ll use three main commands:
code
– Opens or creates a new file, such ashello.c
.make
– Compiles the code, transforming it from C to machine language../hello
– Executes the compiled code, like double-clicking an icon on your desktop.
Let's try it:
Notice the dollar sign ($
) in the CLI—it represents the prompt, where commands are entered.
After we write the command we can press Enter, notice that we have created a new file where we can write code,
Note: As a convention, file names are typically in lowercase with no spaces, ending with .c
to denote C language files.
Now we can write the code,
Disclaimer Note: we will omit the \n
I showed you before, just to see what happens if we do not use it.
Once we write our code, compiling it with make hello
, then press Enter,
Note: a common mistake is to write make hello.c
, the .c
is not used since we already created the C file (the source code).
Now, the [[compiler]] will created the machine code as a new file named hello
Then, if nothing failed during the compilation of the program. We can try to run the program using ./hello
, this will print “hello, world”
on the screen.
Notice the Mistake: Now, I see the output, “hello, world$
,” but the dollar sign from my prompt appears immediately after, which doesn’t look right.
This minor aesthetic issue is caused by not having a [[line break]] in my output.
C.1.2) Using the newline character (\n)
In C, the [[newline character]] \n
does the job. Adding \n
inside the quotes will move the cursor to a new line.
After adding \n
,
- I run
make hello
again to recompile, - then
./hello
to execute it, and there it is—our desired output, “hello, world
,” with the proper line break.
clear
command
you can type clear
and press enter to clean your terminal window
Summary:
For now, you don’t need to worry about the #include
or <stdio.h>
parts or what int
, main
, void
, and curly braces do.
Focus mainly on line 5, where most of the action happens with printf
. Over time, we’ll break down these other components, but for now, just note that line 5 is the key to displaying output.
\n
The \n
we used is known as an escape sequence—a special way to add characters that can’t be directly typed.
For example, \n
tells printf
to move to the next line. If you actually want to print \n
, you’d type \\n
to treat it as literal text.
C.2) Understanding Functions: Comparing Scratch to C ✨➡️🌟
In Scratch, we saw how functions act as building blocks for implementing step-by-step solutions to problems—these are called algorithms.
Functions take in inputs, or arguments (sometimes known as parameters), and can produce visible side effects....
Or return values that we can store,
For example, in Scratch, the Say block produced a side effect: it displayed a speech bubble next to the cat, showing text provided as input.
In adapting these concepts to C, we’ll work with printf
, which serves a similar role to the Say block in Scratch.
Key differences to keep in mind:
- Parentheses in C function similarly to Scratch’s ovals, enclosing function inputs.
- Text output:
printf
in C outputs text like Scratch’s Say block but requires double quotes and\n
for new lines. - Semicolon: Each command ends with a semicolon, marking the line’s end.
These syntax specifics in C are easy to pick up, becoming automatic with practice, allowing for more efficient coding.
C.3) Including Libraries with Header Files - Example: stdio.h and printf() 📚🏛️
In C, a lot of functionality is built into external files.
So if you want to use certain functions, you’ll need to tell the [[compiler]],
“Hey, I want to use some standard input and output features”—like printing text on the screen.
In our case, printf()
is located in a file called stdio.h
.
Using #include <stdio.h>
tells the [[compiler]] to look for this file on the local drive and include its content, allowing us to use printf()
.
In programming, [[header files]] (like stdio.h
) usually end in “.h” and are part of what we call libraries.
[[Libraries]] are collections of code that someone else wrote, and they’re typically available for free and [[open-source]] or through purchase and proprietary licenses. They let us perform tasks without rewriting every bit of code ourselves, so we can solve higher-level problems without getting stuck on basic ones.
Even though I’ve been using printf()
for over 25 years, I don’t need to know every detail of how it works internally. The C library that printf()
is part of takes care of the background work, like displaying each character on various screens.
Libraries let us build on each other's efforts, using code that’s already written so we can focus on solving more unique problems.
D) Accessing Documentation and CS50 "Easy" Manual Pages 📄✨
If you’re interested in learning more about these libraries and their functions, there are manual pages. These are documentation files that describe how each function works and how to use its arguments or inputs.
But manual pages are usually geared towards people who have been programming for years, which makes them a bit tricky to follow if you’re new to coding.
For this reason, CS50 has its own version of these manuals at https://manual.cs50.io/
Our manual provides user-friendly explanations for various functions, including those found in stdio.h
. At https://manual.cs50.io/#stdio.h
For instance, you can look up printf()
in our CS50 manual, where you’ll find more accessible descriptions and examples to help you understand how it works.
So, use this site to look up functions and their parameters when you’re starting out.
D.1) Introducing CS50-Training Library 👶📚
For CS50, we’ve included our own header file called cs50.h
with a set of functions designed to simplify tasks in the first few weeks of the course.
Read more at https://manual.cs50.io/#cs50.h
These are like training wheels that we’ll eventually take off.
For example, C can be a bit challenging when it comes to getting input from users (like asking for a word or a number), but cs50.h
makes this process a little easier.
Inside cs50.h
, you’ll find functions like get_string
, which prompts a user for a string (text input).
There is also get_int
for integers, get_float
for real numbers with decimals, and more.
These user-friendly functions help us focus on the key concepts in C without getting bogged down in technical details, which we’ll eventually explore more in-depth.
D.1.2) Note: 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";
).
E) Example 2: Writing a Program with User Input in C (hello.c)👋😊
So let’s see how we can use one of these functions, like get_string
.
Last week in Scratch, we built a program that didn’t just say “hello, world,” but also said “hello” to someone specific—like "hello, David” or “hello, Julia.”
We used the Ask block to get user input and then combined it with the Say and Join blocks.
In C, we’ll build a similar program, but with a slightly different model. We’ll still have a function that acts as an algorithm, along with arguments as inputs. And previously, we saw that Say and printf
produced side effects, displaying output directly. But not all functions display information immediately. Some functions have what’s known as a return value, which is like writing an answer on a slip of paper for you to use later.
In Scratch, when we used the Ask block, the answer initially went to an “answer” block (represented by a special oval), which we could then use elsewhere.
This “answer” block serves as a return value, and we’ll want something similar in C so we can save the input and decide how to use it.
To replicate Scratch’s Ask functionality in C, we can use the get_string
function,
- which will prompt a user with a question like “What’s your name?”.
- To store this input, I’ll use an equals sign (
=
) to assign the return value ofget_string
to a variable. This variable, namedanswer
in this case, will hold the user’s response. - In C, we also need to specify the data type for each variable, so I’ll start this line with string to let C know we’re dealing with text.
- Finally, I end the line with a semicolon, done.
E.1) Formatting Strings in C using printf() and %s
To say “hello” and address the user by name, similar to our join block in Scratch,
C uses printf()
along with format codes like %s
.
The %s
is a [[placeholder]] that tells printf()
where to insert a string variable, for example, a string like our answer
variable.
To plug in the answer variable for %s
, we simply add a comma after the quotes in printf
and place answer after it.
This lets printf
know that answer should replace the %s
placeholder in the output.
Note: To clarify, the comma inside the quotes is part of the text, while the one outside is part of C’s syntax—context helps us distinguish these differences.
E.2) Running the Code and get User Input (String Example)
Now, let’s take the code we've been discussing and actually run it in Visual Studio Code (VS Code).
clear
command
you can type clear
and press enter to clean your terminal window. Or pressing Ctrl + L
Starting in the "hello.c" file,
- I’ll remove what we do not need....
- Next, we’re going to create a variable called
answer
of typestring
. This variable will store the return value from theget_string
function, which will prompt the user with “what’s your name?”
Here’s the revised code:
string answer = get_string("What's your name? ");
printf("hello, %s\n", answer);
Notice that VS Code uses syntax highlighting to automatically color code different parts of the code:
- functions like
printf
andget_string
appear in purple, - while strings such as “what’s your name?” are light blue.
This formatting helps visually distinguish between different types of elements in the code.
Note: we are missing the cs50.h
library to be able to use the get_string
function.
If we try to compile this modified code by running make hello
in the terminal. we will see an error,
VS Code will show a detailed message. For instance, if we see a message like “use of undeclared identifier ‘string’,” it indicates the compiler doesn’t recognize string
or get_string
. This is because we need to include the CS50 library for these functions.
To fix this, add the following line to the top of your file: #include <cs50.h>
After including this library, rerun make hello
. With no further errors, you should now see the prompt “What’s your name?"
We can type a Name and press enter,
And finally, we see the personalized greeting once you input a name.
F) Introducing Additional Data Types (int, float, etc)
Let’s expand on the code with other [[data types]].
In addition to string
, C offers types like:
int
for integersfloat
for decimal numbers.- etc
We’ll use get_int
to capture an integer input and get_float
for a floating-point number.
F.1) Using More Format Codes (with %)
To output these different data types, we use format codes within printf
.
Here are the most common codes:
%c
for [[characters]]%s
for [[strings]]%i
for [[integers]]%li
for [[long integers]]%f
for [[floating-point numbers]]
These codes are placed within printf
to indicate the type of data being displayed.
For example:
string answer = get_string("What's your name? ");
printf("hello, %s\n", answer);
%i
vs%d
Both %d
and %i
can be used with printf
to print integers, but there are some subtle differences in how they behave in certain contexts:
%d
is specifically for decimal integers. (base 10 only)- It ensures proper formatting and type matching during output.
- If a non-integer is passed for
%d
, behavior is undefined (may result in errors or incorrect output).
EXAMPLE:
____________________________________________________________
G) Conditionals and Decision-Making ⬅️❓➡️
Let’s use conditionals in C, similar to the "if" blocks in Scratch. In Scratch, you might check if one variable is less than another and display a message accordingly.
In C, this looks like:
Note: in C when the there is only one line of code after the conditional, we can omit the {}
For Example:
if (x < y)
printf("x is less than y\n");
You can use:
if
,- and
else
You can even use:
if
,else if
,- and
else
to create multiple conditions,
Note: when using else if
, if the first condition (e.g., x < y
) is true, the program skips the remaining conditions. So it doesn't matter if we have other conditions that could be TRUE
in the lines below, if one of the conditions is satisfied first, we run the code inside that condition and then move on.
Example:
Important Note: Notice that in C, ==
is used to check equality, whereas a single =
assigns a value.
Notice how the question else if (x == y)
is unnecessary, since we already asked for greater than and smaller than, there is no other posible comparison to make, the numbers must be equal if they failed the previous comparisons.
Just write it like this:
H) Declaring and Modifying Variables 📦
Let's compare the variable declaration between Scratch and C,
In C, declare variables by specifying their type, like int counter = 0;
. This tells C to create an integer variable named counter
and initialize it to zero.
To increment the variable in Scratch, we used this block,
But in C, you can increment the counter
variable with different syntaxes:
counter = counter + 1; // Adds 1 to counter
counter += 1; // Adds 1 using shorthand notation
counter++; // Increment by 1 (commonly used)
These methods all perform the same operation but vary in efficiency and simplicity.
Similarly, use counter--
to decrease counter
by one (we can use which ever syntax option we want).
I) Example 3: Building a Comparison Program in C (compare.c)
In this section, let’s create a simple C program that compares two integers and gives feedback based on the values.
I.1) Part 1: Create a Comparison program with an IF Conditional
Start by opening Visual Studio Code and creating a new file called compare.c
.
As a best practice, include the necessary libraries right from the start:
Note: Ensure you’re typing stdio.h
rather than “studio.h,” which is a common mistake. stdio.h
stands for “standard input-output” and provides essential functions like printf()
and scanf()
.
Within the file, define the main function:
- Here, we’re using
get_int
to prompt the user for two integers,x
andy
.
int main(void)
{
int x = get_int("What's x? ");
int y = get_int("What's y? ");
Now, let’s introduce conditional logic to compare these integers:
- This simple check prints "x is less than y" if
x
is indeed smaller thany
.
if (x < y)
{
printf("x is less than y\n");
}
-
Compile this code by running
make compare
in your terminal,
-
then execute the program with
./compare
.
./
Note: ./
mean to run the file "from your current folder"
If we use ../
that would mean it is in the "parent folder"
Input values for x
and y
to see the result.
I.2) Part 2: Enhancing the Program with Conditional Branches (using else if and else)
Currently, the program only outputs a message when x
is less than y
.
You can see it in this [[Flowchart]]:
Example - no print if x>y
To handle additional scenarios, let’s add more conditions:
- Add an
else
Condition:
- This covers cases where
x
might be equal to or greater thany
.else { printf("x is not less than y\n"); }
With this addition, the [[Flowchart]] now looks like this:
- Add More Specific Conditions with
else if
:
-
To refine our feedback further, let’s add more detailed conditions:
else if (x > y) { printf("x is greater than y\n"); } else if (x == y) { printf("x is equal to y\n"); }
-
Combine conditions with
else if
: This way, if the first condition (e.g.,x < y
) is true, the program skips the remaining conditions. -
Logical Flow Diagram: Visualize this decision-making process in a flowchart. By structuring it efficiently, you reduce the number of checks the program must make, saving computation time.
We can run it again and test the result,
I.3) Note: Optimizing Conditional Logic (avoid repeated "Ifs")
We could get lazy and try to simplify the code by writing it like this...
With these conditions, the program is correct and gives feedback for all cases.
However, it is NOT efficient. Currently, the code asks each condition individually, which can lead to redundant checks.
With this bad example, the [[Flowchart]] now looks like this (those are always 3 questions before ending the program):
We need to be efficient when we write code!
This was better... (else if
)
and this was even better! (else if and else
)
Code:
______________________________________________________________________
J) Example 4: Working with Single Characters (agree.c) 🔠🔡
Let’s create another program, agree.c
, to introduce char
, a [[data type]] for single characters.
We will use get_char
from the CS50 Library!
This next program will prompt the user for a “yes” or “no” response to a simple question.
Here’s the setup:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
char c = get_char("Do you agree? ");
To check the user’s input, let’s write conditions that use if
statements:
Note: In this example, we avoid repeating the same logic for different Upper Case or Lower Case inputs by using ||
(OR symbol) within a single if
statement.
if (c == 'y' || c == 'Y')
{
printf("Agreed\n");
}
else if (c == 'n' || c == 'N')
{
printf("Not agreed\n");
}
The goal of efficient coding is to reduce redundancy and avoid repeating similar lines. This approach minimizes potential errors and keeps your code easier to maintain and expand upon.
Note: Use single quotes ('y'
or 'n'
) for char
values, which are single characters, whereas strings use double quotes "like this"
.
Other ideas...
Alternatively, convert the input to lowercase or uppercase before evaluating, though we’ll explore that functionality later in C.
J.1) Note: Basic Logical and Relational Operators in C Programming
Logical Operators:
||
(OR): Evaluates toTRUE
if at least one of the conditions isTRUE
.&&
(AND): Evaluates toTRUE
only if both conditions areTRUE
.!
(NOT): Reverses the logical state of its operand. If a condition isTRUE
, using!
makes itFALSE
and vice versa.
Relational Operators:
==
(Equal to): Checks if two values are equal. ReturnsTRUE
if they are equal.!=
(Not equal to): Checks if two values are not equal. ReturnsTRUE
if they are not equal.<
(Less than): Checks if the left operand is less than the right.>
(Greater than): Checks if the left operand is greater than the right.<=
(Less than or equal to): Checks if the left operand is less than or equal to the right.>=
(Greater than or equal to): Checks if the left operand is greater than or equal to the right.
Usage Examples:
int x = 5, y = 10;
if (x != y) {
// This block runs because x is not equal to y
printf("x is not equal to y.\n");
}
if (x < y) {
// This block runs because x is less than y
printf("x is less than y.\n");
}
if (!(x > y)) {
// This block runs because x is NOT greater than y
printf("x is not greater than y.\n");
}
These logical and relational operators are essential for controlling the flow of the program by making comparisons and combining conditions.
________________________________________________________________________
K) Understanding Loops in C: Repetition from Scratch to C Code 🔁
Loops allow us to execute a set of instructions multiple times, which is essential for efficient programming.
In Scratch, we might repeat a sound like "meow" three times with a simple block.
In C, we implement loops using different syntax that may look complex at first, but each part serves a purpose.
K.1) Using a while Loop for Repetition
A while
loop in C runs as long as a condition remains true.
Here’s an example that prints "meow" three times:
Copy Code:
int counter = 3;
while (counter > 0)
{
printf("meow\n");
counter--; // Decrement the counter by 1 each time
}
- Initialize:
int counter = 3;
creates a variablecounter
starting at 3. - Condition:
while (counter > 0)
checks ifcounter
is greater than 0. - Loop Body: Each time the loop runs, it prints "meow" and then decreases
counter
by 1. - Termination: Once
counter
reaches 0, the loop stops.
This approach avoids duplication; rather than copy-pasting printf("meow\n");
three times, we create a loop to handle the repetition.
while (true)
It might be intentional, for example to run code continuously in a robot, or it might be an accident that will get the computer stuck in a loop.
Just know that some loops can be made to run forever,
Example:
K.2) Simplifying with a for Loop
A for
loop in C combines initialization, condition, and update in one line, making it a preferred choice for loops with a set number of repetitions.
Here’s an example that prints "meow" three times:
Copy Code:
for (int i = 0; i < 3; i++)
{
printf("meow\n");
}
- Initialization:
int i = 0;
starts a counter at 0. - Condition:
i < 3
runs the loop whilei
is less than 3. - Update:
i++
incrementsi
by 1 after each iteration.
Using for
loops helps reduce clutter in code and keeps all loop-related details in one place, which is both efficient and standard.
K.3) Avoiding Redundancy with Functions ✨➡️🌟
Creating reusable functions in C further enhances code efficiency.
For example, a custom function meow()
can handle the printf
command.
Note: the firstvoid
means that the functions has no return value, and the second void
means the function takes no argument as input.
Here’s how:
- We have a custom function
meow()
can handle theprintf
command.
void meow(void)
{
printf("meow\n");
}
Then, call this function in a loop:
for (int i = 0; i < 3; i++)
{
meow();
}
K.3.1) Function Prototypes: Defining Functions for the Compiler
Example: What happens if we define the function after Main?
The [[compiler]] won't know where the function is, and it will go into an error...
but there are a few options to solve this:
Option 1: define the function before you use it.
Option 2: use function prototypes
If meow()
is defined after main()
, a function prototype at the top of the file helps the compiler recognize it:
This line,
void meow(void);
provides the compiler with essential information about meow()
even before it’s fully defined.
K.4) Enhancing the meow() Function with Inputs
To add flexibility, modify meow()
to accept a parameter defining the number of repetitions:
Copy code:
void meow(int n)
{
for (int i = 0; i < n; i++)
{
printf("meow\n");
}
}
Now, in main()
, you can call meow(3);
to print "meow" three times or any other number as desired.
_____________________________________________________________________
L) Creating Functions with Return Values ✨➡️📦
So far, we’ve mainly dealt with side effects in our functions,
which, like the speech bubble effect in Scratch, change the screen’s output directly.
Now, we’ll shift our attention to return values in C, which, instead of affecting the screen directly, pass data back for further use.
In Scratch, we executed tasks directly, but C allows us to make functions that return values, adding new possibilities.
For instance, let’s build a basic calculator to add two numbers.
We'll write a function that, rather than merely outputting results, returns a value we can use.
L.1) Building an Addition Function (calculator.c) 🧮➕ ➖ ✖️ ➗
-
Creating the Program: We’ll start by initializing a file,
calculator.c
, where we’ll add our code.
-
Adding Libraries: Including
CS50.h
for convenience, as well asstdio.h
for standard input/output, sets up our program.
- Prompting for Input: We’ll request two integers,
x
andy
, from the user usingget_int
to store their responses.
- Using
printf
with a Placeholder: Printingx + y
as the result requires a formatted output, where%i
stands in as a placeholder for the integer sum ofx
andy
.
Note: These operators are fundamental for performing arithmetic calculations in C.
+
: Addition – Adds two operands. Example:a + b
-
: Subtraction – Subtracts the second operand from the first. Example:a - b
*
: Multiplication – Multiplies two operands. Example:a * b
/
: Division – Divides the numerator by the denominator. Example:a / b
dividesa
byb
. (Note: If both operands are integers, it performs integer division and truncates any remainder.)%
: Modulo – Returns the remainder of the division between two integers. Example:a % b
gives the remainder whena
is divided byb
.
- Compiling and Running: After writing the program, we compile it using
make calculator
, then execute it, where1
and2
as inputs return3
as expected.
This straightforward program shows us how printf
combines formatting and placeholders, with %i
expecting integer data.
But it would be better if I could write this program as a function instead, so that I can use it whenever I need it.
L.2) Understanding Function Scope and Arguments 🔭✨
Moving forward, we aim to create a reusable addition function that can handle this task whenever needed.
In our add
function, we encounter [[scope]]—the specific context where a variable is accessible.
Our main function’s variables, x
and y
, exist only within that function.
Thus, add
doesn’t inherently recognize them.
By defining add
to accept parameters (such as a
and b
), we make the function flexible enough to take in any two integers and output their sum.
This setup enables add
to receive x
and y
as arguments from the main function and return their sum.
L.2.1) Tip: Streamlining Code with Nesting and Substitution
To avoid unnecessary variables, we simplify our code by nesting functions:
which means, replacing intermediate steps like storing a sum in z
by directly calling add(x, y)
inside printf
.
This direct approach can sometimes reduce readability but shows how C allows for efficient code.
L.3) Note: Main’s Return Value
Our main
function traditionally ends with int
, allowing it to return 0
by default to signify successful execution.
This pattern echoes operating system error messages, often represented by non-zero codes.
Read more here
_______________________________________________________________
O) Programming Mario's Bricks in C: Loops, Grids, and Comments 🧱🍄🎮
We’re now exploring how some aspects of the classic game Super Mario Brothers can serve as programming exercises,
specifically recreating a grid of bricks or question marks similar to those Mario interacts with in the game.
Although we’re not building a full game, we’ll see how loops and control structures in C help us replicate basic patterns like grids and rows.
O.1) Example 1: Printing Four Question Marks in a Row (inefficient)
To start, we’ll create a simple program
This program outputs four question marks on a single line. This first attempt uses direct print statements:
When compiled and run (make mario && ./mario
), this outputs four question marks. However, typing them manually is inefficient, especially if we want to extend the pattern to other dimensions.
O.2) Example 2: Replacing Redundancy with a Loop (Smarter)
Instead of writing out each question mark, we can use a loop to repeat the printf("?")
command.
We’ll remove the newline (\n
) from the for
loop to avoid unwanted line breaks:
This loop prints the question marks horizontally, and the final printf("\n");
ensures the program’s output ends neatly.
O.3) Example 3: Printing 3x3 Bricks (inefficient)
To start, we’ll create a simple program
This program outputs 3x3 bricks. This first attempt uses direct print statements.
Again, typing them manually is inefficient, especially if we want to extend the pattern and make a bigger brick structure.
O.4) Example 4: Expanding to a 3x3 Grid Using Nested Loops (Smarter)
Next, we’ll build a 3x3 grid, representing a basic structure seen in Mario’s underground levels.
We can get some instinct on how the terminal window works, it is similar to a Typewriter, where we can print one row at a time and jump to the next row once we are finished.
So this time, we will use two for
loops, one to handle the horizontal prints, and one to deal with how many new rows we introduce.
Copy Code: Here, each row of #
symbols prints in a single line due to the inner loop, while the outer loop handles the new lines to form the rows.
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) { // Outer loop for rows
for (int j = 0; j < 3; j++) { // Inner loop for columns
printf("#");
}
printf("\n"); // Newline after each row
}
return 0; //default return value for successful execution
}
O.5) Example 5: Making the Grid Dynamic with Variables and Constants (avoid magic numbers)
In a program that prints a grid (e.g., 3-by-3), using hard-coded values directly in the code is known as using [[magic numbers]]. These are values that appear without context, making code harder to maintain.
If you need to change the grid size, you have to update multiple places in your code, increasing the risk of inconsistencies.
Solution: Replace these magic numbers with a single variable
To make our grid size adjustable, we can define an integer n
to represent the grid’s dimensions. This approach allows easy changes to the grid size without adjusting multiple parts of the code:
Copy Code:
#include <stdio.h>
int main(void) {
int n = 3;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("#");
}
printf("\n");
}
return 0;
}
Alternatively, by adding const int n = 3;
at the beginning, we make n
a constant, preventing accidental changes and ensuring consistency if working with a collaborator.
Copy Code:
#include <stdio.h>
int main(void) {
const int n = 3;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("#");
}
printf("\n");
}
return 0;
}
O.6) Example 6: Making the Grid Interactive (user input)
We can make this grid dynamic by asking the user for input using get_int
from CS50’s library:
Copy Code:
#include <stdio.h>
#include <cs50.h>
int main(void) {
const int n = get_int("Size: "); // Get grid size from the user
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("#");
}
printf("\n");
}
return 0;
}
Running this code prompts the user to enter a size, which dynamically adjusts the grid.
O.7) Example 7: Validating User Input with a do-while Loop
We may want to prevent the user from entering invalid values (e.g., 0
or negative numbers).
We can use another type of loop called do-while
,
A do-while
loop prompts the user until they provide a positive integer:
Copy Code:
#include <stdio.h>
#include <cs50.h>
int main(void) {
int n; //declare n here so that it is available for the While Condition
do {
n = get_int("Size: ");
} while (n < 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("#");
}
printf("\n");
}
return 0;
}
This loop ensures that the program only continues once n
is a positive integer.
O.8) Note: Commenting the Code for Clarity
Comments are essential for making code readable and maintainable. In C, we use //
for single-line comments:
Copy Code:
#include <stdio.h>
#include <cs50.h>
int main(void) {
// Prompt user for positive integer
int n;
do {
n = get_int("Size: ");
} while (n < 1);
// Print n-by-n grid of bricks
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("#");
}
printf("\n");
}
return 0;
}
Each comment provides a clear overview of the function or purpose of each code section, helping collaborators or future users understand the intent.
Summary: This Mario-inspired exercise showcases core programming concepts:
- Loop structures for repeating tasks.
- Nested loops for creating grids.
- Constants and variables for flexible program design.
- User input to make code interactive.
- Validation to ensure input accuracy.
- Comments to clarify code functionality.
These building blocks provide a foundation for more complex programs and are key tools in C programming.
____________________________________________________________
P) Limitations of C in the Real-World (Integer Overflow and Memory Constraints) 💾🧠
While C is a powerful language, it has some notable limitations, especially when it comes to managing large numbers or achieving precision in calculations.
These limitations stem from the way computers use finite memory to store data, leading to issues like integer overflow and [[floating-point imprecision]].
P.1) Understanding Computer Memory and Its Limitations
All devices, whether a phone, PC, or server, rely on [[RAM (Random Access Memory)]] for storing active data.
This memory has physical limits, meaning that even if high-end devices contain a lot of memory, it's still finite.
This restricts how high we can count and the number of files or data points that can be stored.
Consider the implications for counting in base-2 (binary).
With three digits (or bits), we can count from 000 to 111 (or 7 in decimal). But if we try to count beyond that, we "overflow" and wrap back to 0.
Computers use bits, where a single bit represents a 0 or 1.
Commonly, a 32-bit system stores values in 32 bits, which allows counting up to roughly 4 billion in decimal (specifically 4,294,967,295) for positive values.
P.2) Integer Overflow in Computers
If all 32 bits are used to store a number, and that number reaches its maximum value (4,294,967,295),
counting one more will cause it to wrap around back to 0.
If negative numbers are involved, half of the 32-bit range is used for positive numbers and the other half for negative ones, resulting in a maximum value of around 2 billion.
This constraint is why we often see data types like:
int
for 32-bit numbers- and
long
for 64-bit numbers in C.
Doubling the number of bits from 32 to 64 greatly increases the range of values that can be represented but still doesn’t escape the limitations of finite memory.
P.3) Integer Truncation and Floating-Point Precision
Remember... Arithmetic Operators in C
Note: These operators are fundamental for performing arithmetic calculations in C.
-
+
: Addition – Adds two operands. Example:a + b
-
-
: Subtraction – Subtracts the second operand from the first. Example:a - b
-
*
: Multiplication – Multiplies two operands. Example:a * b
-
%
: Modulo – Returns the remainder of the division between two integers. Example:a % b
gives the remainder whena
is divided byb
. -
/
: Division – Divides the numerator by the denominator. Example:a / b
dividesa
byb
.
For example, dividing 1 by 3 should ideally yield a repeating decimal (0.333...), but...
If both operands are integers, it performs integer division and truncates any remainder.
Even if we try to use floating-point numbers, we need to be sure we are using them right, otherwise we get wrong results... due to memory constraints when working with integers, the result will be [[truncated]] to something close but not exact.
float
and double
When working with decimal floating-point numbers, C provides two main types:
- Float: Uses 32 bits and has limited precision.
- Double: Uses 64 bits for higher precision.
P.3.1) Type Casting to Avoid Integer Truncation
In C, when dividing integers, we can "cast" them to floats to ensure we get a floating-point result:
This converts x
and y
to floats, avoiding truncation and preserving decimal precision.
float result = (float)x / (float)y;
P.3.2) About floating-point imprecision
Formatting Floating Point Numbers in printf
:
To display floating-point numbers with specific precision in C, you can use a modified format specifier in printf
.
For example:
printf("%.5f", number);
This tells printf
to display the number with five decimal places.
If you want six, you simply adjust it.
For Example,
printf("%.6f", number);
this ensures that only the specified number of decimal places are shown.
Floating Point Imprecision Explained:
When testing how precise a floating-point number can be by printing more decimal places, you might notice strange, unexpected numbers.
For instance, printing 1 / 3
with many decimal places won’t show infinite 3's, but rather a close approximation:
For example,
printf("%.20f", 1.0 / 3.0);
This result is wrong,
Why the discrepancy?
- Computers have finite memory and cannot represent every possible number precisely, especially real numbers that require infinite decimal places. This leads to [[floating-point imprecision]], where the number displayed is the closest approximation the computer can manage.
Using Doubles for More Precision
A double
data type, which uses 64 bits instead of the 32 bits used by float
, offers more precision.
For example,
double result = 1.0 / 3.0;
printf("%.20f", result); // More precise, but still not infinite 3's
However, even double
types cannot perfectly represent all real numbers:
Conclusion:
Both floating point imprecision and [[integer overflow]] are limitations that stem from finite computer memory. These issues can have significant implications, requiring developers to write code carefully and consider alternative solutions, like using larger data types (double
or 64-bit integers) or applying fixes to legacy systems to avoid failures.
P.4) Real-World Cases of Integer Overflow 🌎🐛
- The Y2K Bug: Software from the 1970s often represented years with two digits (e.g., '99' for 1999). As 2000 approached, systems misinterpreted '00' as 1900, leading to widespread concern that systems would fail.
- The 2038 Problem: Unix-based systems commonly store time as the number of seconds since January 1, 1970. This value is kept as a 32-bit integer, allowing for roughly 2 billion seconds. By 2038, the count will exceed the 32-bit limit, potentially causing systems to interpret the date as 1901 instead of 2038. The solution is to switch to 64-bit integers, which extend the system’s accuracy for millions of years.
- Video Game Bugs: The original Pac-Man game only counted up to level 255 due to an 8-bit limit. At level 256, an integer overflow led to graphical glitches, filling the screen with random symbols.
Donkey Kong similarly encountered an overflow that reduced the timer to just 4 seconds on a high level, making it nearly impossible to beat.
Math broke the Game:
- Boeing 787 Bug: In 2015, Boeing discovered a software bug causing the plane's power systems to shut down if left on for 248 days continuously. This was due to a 32-bit integer keeping track of time in hundredths of a second, causing the number to reset. The temporary solution? Power cycle the plane every 248 days.
Summary:
Understanding limitations like integer overflow and floating-point precision helps us better manage data storage and handle large or complex numbers. While programming in C, such challenges are reminders of the fundamental constraints of memory and computation.
Z) 🗃️ Glossary
File | Definition |
---|