W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library

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

🔙 Previous Part | Next Part 🔜

↩️ Go Back

Table of Contents:

↩️ Go Back


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.

|250


A.1) Writing Good Code with Correctness, Design, and Style

When writing code, three key principles help guide us:

  1. Correctness: Does the code work as intended?
  2. Design: How efficient is the code? Does it avoid unnecessary repetition?
  3. 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/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:

This setup combines both:

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.

|400

By typing commands, we replace GUI functions, adding speed and versatility to coding tasks.

B.2.1) Trying some of the Commands ⌨️

  1. Commands Overview:

    • ls: Lists files in the current directory, providing an overview of your working folder.

Example: rename meow.c to woof.c

replacing the GUI’s "rename" function.
|440

  1. 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.

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.

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:

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

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.



________________________________________________________________



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:

C.1) Creating, Compiling and Running Code (CLI commands)⌨️

To write and execute code in C, we’ll use three main commands:

  1. code – Opens or creates a new file, such as hello.c.
  2. make – Compiles the code, transforming it from C to machine language.
  3. ./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: the compiler and machine code

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,

tip - use the 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.

Note: how to print \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....

|600

Or return values that we can store,

|600

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:

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.

|400

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.

|200

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)


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,


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 answervariable.

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).

tip - use the clear command

you can type clear and press enter to clean your terminal window. Or pressing Ctrl + L

Starting in the "hello.c" file,

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:

This formatting helps visually distinguish between different types of elements in the code.

missing library

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:

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:

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);
Note: %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:

You can even use:

Important Note: Notice that in C, == is used to check equality, whereas a single = assigns a value.


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:

Within the file, define the main function:

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:

    if (x < y)
    {
        printf("x is less than y\n");
    }

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:

  1. Add an else Condition:

With this addition, the [[Flowchart]] now looks like this:
|420

  1. Add More Specific Conditions with else if:

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)
|500

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:

Relational Operators:

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
}
  1. Initialize: int counter = 3; creates a variable counter starting at 3.
  2. Condition: while (counter > 0) checks if counter is greater than 0.
  3. Loop Body: Each time the loop runs, it prints "meow" and then decreases counter by 1.
  4. 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.

Note: We can have loops that run forever! 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");
}
  1. Initialization: int i = 0; starts a counter at 0.
  2. Condition: i < 3 runs the loop while i is less than 3.
  3. Update: i++ increments i 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:

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,

|600

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.

|600

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) 🧮➕ ➖ ✖️ ➗

  1. Creating the Program: We’ll start by initializing a file, calculator.c, where we’ll add our code.

  2. Adding Libraries: Including CS50.h for convenience, as well as stdio.h for standard input/output, sets up our program.

  1. Prompting for Input: We’ll request two integers, x and y, from the user using get_int to store their responses.

  1. Using printf with a Placeholder: Printing x + y as the result requires a formatted output, where %i stands in as a placeholder for the integer sum of x and y.

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
  • /: Division – Divides the numerator by the denominator. Example: a / b divides a by b. (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 when a is divided by b.
  1. Compiling and Running: After writing the program, we compile it using make calculator, then execute it, where 1 and 2 as inputs return 3 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,

|400

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.

|300

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,
|520

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:

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:

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.

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.

About 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?

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 🌎🐛

  1. 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.

|200

|200

  1. 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.

|300

|300

  1. 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:
|200

  1. 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.


🔙 Previous Part | Next Part 🔜

↩️ Go Back


Z) 🗃️ Glossary

File Definition
Uncreated files Origin Note
characters W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
compiler W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
data type W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
data types W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Docker container W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
floating point imprecision W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Floating Point Range versus Precision W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
floating-point imprecision W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
floating-point imprecision W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
floating-point numbers W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Flowchart W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Flowchart W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Flowchart W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
header files W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
higher-level languages W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
↩️ Go Back W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
integer overflow W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
integer overflow W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
integers W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Libraries W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
line break W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
long integers W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
machine code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
machine code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
machine code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
machine code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
magic numbers W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
Negative Binary Numbers W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
newline character W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
open-source W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
placeholder W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
RAM (Random Access Memory) W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
scope W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
source code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
source code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
source code W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
strings W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library
truncated W1 - 👨‍🏫 Lecture - Intro to C Language and CS50 Library