CS 235: Object-Oriented Programming with C++ (Spring 2024 version) Setting up and using third party libraries

Table of Contents

\newpage

topic-corecompsci.png

Rachel Wil Sha Singh's Object Oriented Programming Course Β© 2024 by Rachel Wil Sha Singh is licensed under CC BY 4.0. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/

These course documents are written in emacs orgmode and the files can be found here: https://gitlab.com/moosadee/courses

Dedicated to a better world, and those who work to try to create one.

\newpage

l1-contents.png

This section contains each of the units we will be covering in the class, including the reading material, review, and assignment documentation. Add your own notes and highlight things as you go through!

WEEK 1 - JAN 16

topic-setup.png

UNIT 00: Welcome & setup

πŸ“–οΈ Reading - Welcome (U00.READ)

  • Welcome!

    It feels weird to start a collection of notes (or a "textbook") without some sort of welcome, though at the same time I know that people are probably not going to read the introduction (Unless I put some cute art and interesting footnotes, maybe.)

    I think that I will welcome you to my notes by addressing anxiety.

    Belonging

    Unfortunately there is a lot of bias in STEM fields and over decades there has been a narrative that computer science is for a certain type of person - antisocial, nerdy, people who started coding when they were 10 years old.

    Because of this, a lot of people who don't fit this description can be hesitant to get into computers or programming because they don't see people like themselves in media portrayals. Or perhaps previous professors or peers have acted like you're not a real programmer if you didn't start programming as a child

    If you want to learn about coding, then you belong here.

    There are no prerequisites.

    I will say from my own experience, I know developers who fell in love with programming by accident as an adult after having to take a computer class for a different degree. I know developers who are into all sorts of sports, or into photography, or into fashion. There is no specific "type" of programmer. You can be any religion, any gender, any color, from any country, and be a programmer.

    u00_WelcomeAndSetup_gatekeepingbaby.png

    Figure 1: Don't be like Gatekeeper Baby. You can begin coding at any age!

    Challenge

    Programming can be hard sometimes. There are many aspects of learning to write software (or websites, apps, games, etc.) and you will get better at it with practice and with time. But I completely understand the feeling of hitting your head against a wall wondering why won't this work?! and even wondering am I cut out for this?! - Yes, you are.

    u00_WelcomeAndSetup_justwork.png

    I will tell you right now, I have cried over programming assignments, over work, over software. I have taken my laptop with me to a family holiday celebration because I couldn't figure out this program and I had to get it done!!

    All developers struggle. Software is a really unique field. It's very intangible, and there are lots of programming languages, and all sorts of tools, and various techniques. Nobody knows everything, and there's always more to learn.

    Just because something is hard doesn't mean that it is impossible.

    It's completely natural to hit roadblocks. To have to step away from your program and come back to it later with a clear head. It's natural to be confused. It's natural to not know.

    But some skills you will learn to make this process smoother are how to plan out your programs, test and verify your programs, how to phrase what you don't know as a question, how to ask for help. These are all skills that you will build up over time. Even if it feels like you're not making progress, I promise that you are, and hopefully at the end of our class you can look back to the start of it and realize how much you've learned and grown.

    Learning

    First and foremost, I am here to help you learn.

    My teaching style is influenced on all my experiences throughout my learning career, my software engineer career, and my teaching career.

    I have personally met teachers who have tried to scare me away from computers, I've had teachers who really encouraged me, I've had teachers who barely cared, I've had teachers who made class really fun and engaging.

    I've worked professionally in software and web development, and independently making apps and video games. I know what it's like to apply for jobs and work with teams of people and experience a software's development throughout its lifecycle.

    And as a teacher I'm always trying to improve my classes - making the learning resources easily available and accessible, making assignments help build up your knowledge of the topics, trying to give feedback to help you design and write good programs.

    As a teacher, I am not here to trick you with silly questions or decide whether you're a real programmer or not; I am here to help guide you to learn about programming, learn about design, learn about testing, and learn how to teach yourself.

    u00_WelcomeAndSetup_buildingblocks.png

    Roadmap

    u00_WelcomeAndSetup_roadmap.png

    When you're just starting out, it can be hard to know what all you're going to be learning about. I have certainly read course descriptions and just thought to myself "I have no idea what any of that meant, but it's required for my degree, so I guess I'm taking it!"

    Here's kind of my mental map of how the courses I teach work:

    CS 200: Concepts of Programming with C++
    You're learning the language. Think of it like actually learning a human language; I'm teaching you words and the grammar, and at first you're just parroting what I say, but with practice you'll be able to build your own sentences.
    CS 235 Object-Oriented Programming with C++
    You're learning more about software development practices, design, testing, as well as more advanced object oriented concepts.
    CS 250 Basic Data Structures with C++
    You're learning about data, how to store data, how to assess how efficient algorithms are. Data data data.

    In addition to learning about the language itself, I also try to sprinkle in other things I've learned from experience that I think you should know as a software developer (or someone who codes for whatever reason), like

    • How do you validate that what you wrote actually works? (Spoilers: How to write tests, both manual and automated.)
    • What tools can you use to make your programming life easier? (And are used in the professional world?)
    • How do you design a solution given just some requirements?
    • How do you network in the tech field?
    • How do you find jobs?
    • What are some issues facing tech fields today?

    Something to keep in mind is that, if you're studying Computer Science as a degree (e.g., my Bachelor's degree is in Computer Science), technically that field is about "how do computers work?", not about "how do I write software good?" but I still find these topics important to go over.

    u00_WelcomeAndSetup_search.png

    That's all I can really think of to write here. If you have any questions, let me know. Maybe I'll add on here.

    \vspace{0.2cm}


  • What is this weird webpage/"book"?

    In the past I've had all my course content available on the web on separate webpages. However, maintaining the HTML, CSS, and JS for this over time is cumbersome. Throughout 2023 I've been adapting my course content to emacs orgmode documents, which allows me to export the course content to HTML and PDF files. I have a few goals with this:

    1. All course information is in one singular place
    2. At the end of the semester, you can download the entire course's "stuff" in a single PDF file for easy referencing later on
    3. Hopefully throughout this semester I'll get everything "moved over" to orgmode, and in Summer/Fall 2024 I can have a physical textbook printed for the courses, which students can use and take notes in so as to have most of their course stuff in one place as well
    4. It's important to me to make sure that you have access to the course content even once you're not my student anymore - this resource is available publicly online, whether you're in the course or not. You can always reference it later.

    I know a lot of text can be intimidating at first, but hopefully it will be less intimidating as the semester goes and we learn our way around this page.

    \vspace{0.2cm}


  • What is "Object Oriented Programming"?

    hit-computer.png

    In CS 200 you learn about many of the core, basic features of C++. We work on basic assignments but there are some topics missing: Building bigger programs, and additional C++ language features and techniques that help with building those bigger programs while reducing duplicate code.

    In this class, we're going to continue learning about features of C++ as well as how programs are often structured using Object Oriented Programming for maintainability and scalability. I try to add as much context from my real-world software development experience in this course to help you learn about software development as a whole, to hopefully better prepare you for working as a software developer.

    Object Oriented Programming is just one paradigm (or style) of designing software. It is probably the most popular way to structure most types of software you think of - a grocery store website, bank accounts, social media, etc. - but it's not the only style of programming. Here's a bit of background information for context.

    Early programming

    c3_u00_asm.png

    Early programming in Assembly or Machine Code didn't have the concepts of "classes", and at a very basic level, even "functions" aren't a feature. Programs were built mostly of instructions that flow from top-down, utilizing variables with branching (if statements) and looping.

    Structured programming

    c3_u00_flowchart.png

    Early programming languages built on top of the basic commands to add more functionality, such as subroutines (basically, functions), but were still without classes and objects.

    Object-oriented programming

    c3_u00_oop.png

    Object oriented programming relies on classes to create objects that interact with each other. This helps us conceptualize each working part of a program as a discrete "thing" with its own attributes and functionality, that interacts with other "things".

    Others
    You can get an overview about other Programming Paradigms on Wikipedia: https://en.wikipedia.org/wiki/Programming_paradigm

    Example: Video game

    It might help to investigate how we might design a system around OOP. Let's say we have a video game, since that's a visual example.

    c3_u00_game.png

    In a video game, usually you have different types of objects on the screen, with different types of behaviors. Some of those behaviors are in common, and we would use inheritance or composition to reduce duplicate code by having them share these aspects. Otherwise, each type of "object" defines the attributes and functionality that it requires.

    • Character:
      • Attributes (variables):
        • x, y coordinates - where is the character on the screen?
        • width, height - what is the size of the character?
        • image - What image file is used to represent the character?
        • healthPoints - How much remaining health the character has.
      • Functions:
      • Move() - move the character along the x axis (left/right) or y axis (up/down).
      • IsHit() - Checks if character is hit by projectile, affect health.
    • PlayerCharacter might inherit from Character, inheriting its variables and functions. Then we add onto it:
      • score - A variable to store player score.
      • GetInput() - Check for keyboard input, such as arrow keys to tell the player to move.
    • NonPlayerCharacter might inherit from Character, and have its own special functions:
      • DecideMovement() - Instead of input from the keyboard, the NPC would use its "AI" to figure out where to go.

    Example: Food delivery

    If you were working somewhere that created software that dealt with food delivery, the classes defined in the code base might look something like this:

    c3_u00_oopdiagram.png

    • Inheritance:
      • Driver and Customer inherit from Person (anything in common goes in the "Person" class)
    • Composition:
      • A Restaurant has an array of FoodItem s offered.
      • An Order contains an array of FoodItem s ordered.
      • A driver has a single Order they're currently delivering. (Though this might be an array in modern apps.)
      • A customer has an array of Order s that make up their order history.

    This is just a really basic example that highlights the objects (classes) and their attributes (variables), but I've left off functionality here for simplicity. The core idea here is that we think of real world items, such as a "Restaurant", and that can be modeled as an object. OOP code bases are built up around this concept, of interconnected objects working together.


  • Review questions:

    Answer these questions in your notes, then check your work by completing the related Concept Intro assignment on Canvas.

    1. Object oriented programming is about using objects to…
    2. Inheritance and composition allow us to…
    3. An object's "attributes" refer to…

    After the reading, complete the assignment:

    "πŸ”Ž Unit 00 Concept Intro - Welcome/setup (U00.CIN)"

    \newpage

πŸ§‘β€πŸ”¬ Lab - Set up (U00.LAB)

About:
Source control is an important tool in software development. GitLab is a service that allows us to host git repositories. Our own computer will use the git software to interact with the GitLab server.
Goals:
  • Create a GitLab account
  • Set up git software on your computer
  • Clone a repository
  • Make changes to a repository
  • Deal with merge conflicts
  • Create branches and merge requests
Turn-in:
  • 1. Tell me what your GitLab username is using the "❓ Unit 00 Setup - GitLab Username" assignment on Canvas. Then I can give you access to your repository for the semester.
  • 2. Turn in a URL to a merge request in the "πŸ§‘β€πŸ”¬ Unit 00 Lab - Set up (U00.LAB.202401CS250)" assignment on Canvas.
    • Should contain an example program and your project files.
Links (CS 235)
Links (CS 250)

  • Installing an IDE

    Download and install an IDE (Integrated Development Environment) on your system. Here are some suggested ones, but you can use whatever tools you'd like.

    IDE Link Platforms Info
    Visual Studio Community https://visualstudio.microsoft.com/vs/community/ Windows only (C++ version) During install, make sure to select Desktop development with C++!!
    Code::Blocks https://www.codeblocks.org/downloads/binaries/ Windows/Linux Lightweight, better for older machines. (WINDOWS: Download the mingw-setup version!)
    XCode https://developer.apple.com/xcode/ Mac  

    We'll use our IDE more once we set up git and clone our repository…


  • Installing git

    Starting off, you will need to install the git program to your computer. Go to https://git-scm.com/ and select Download for your OS. Make sure to read through the setup settings here to prevent headaches in the future. (In particular, not setting Vim as the default text editor… unless that's something you want.)

    Most of the default settings are fine, but take note:

    • Select Components: Make sure that "Git Bash Here" is checked.
    • Choosing the default editor used by Git: Choose something like Notepad or if you have a preferred text editor, use that.

  • Configuring git

    After git is installed, we will need to do a one-time configuration. Open up Git Bash (in Windows) or use git from your terminal (Linux/Mac).

    You will need to configure your name:

    git config --global user.email "you@example.com"
    

    Replace "you@example.com" with your email (such as your student email).

    And your email address:

    git config --global user.name "Your Name"
    

    Replace "Your Name" with your name, which is what will show up when you submit code file edits.

    Finally, we're setting up a default merge resolution scheme, which you don't really need to worry about the details right now:

    git config --global pull.rebase false
    

  • Registering a GitLab account

    1. Go to the sign up page: Start by going to https://gitlab.com/users/sign_up and fill out the form to create your account.

    creating_a_gitlab_account_register.png

    Fill out your first and last name, a username, email address, and password. Note that if you want to maintain your privacy you might use an alternative email/name, but make sure to let the instructor know who you are!

    2. Navigate to your profile page: After registering, going to https://gitlab.com/ should take you to a "Projects" page. You will be able to view your repositories here later on. On the right-hand sidebar, click on your profile image (it might be some default picture), then click on the top section to go to your profile.

    creating_a_gitlab_account_gotoprofile.png

    3. Bookmark your profile page: Once you're on your profile page, the URL will look something like this: https://gitlab.com/YOURUSERNAME Take note of this URL and bookmark this page. You may also need to give this link to the instructor for further setup.

    creating_a_gitlab_account_viewprofileurl.png


  • Make sure the instructor knows your GitLab username

    Post your GitLab username in the "❓ Unit 00 Setup - GitLab Username" assignment. Once you have access to your repository, you can continue.


  • Cloning the repository

    Your operating system: Navigate to the folder where you're storing your class projects. From here, right-click in the empty space and select Git Bash Here to open Git Bash.

    Repository webpage: On the repository webpage (gitlab.com) click on the blue Clone button and copy the URL listed under the Clone with HTTPS header. (You can use SSH if you know how to set it up, but we aren't going to cover that here.)

    c3_u00_clone.png

    Git Bash: Use the git clone URL command in Git Bash. This will clone the repository within the folder you opened Git Bash in. (Windows: To use PASTE, right-click in Git Bash… CTRL+V will not work here.)

    $ git clone https://gitlab.com/rsingh13-student-repos/2024-01_cs250/YOURSSO-cs250.git
    Cloning into 'YOURSSO-cs250'...
    remote: Enumerating objects: 3, done.
    remote: Counting objects: 100% (3/3), done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
    Receiving objects: 100% (3/3), done.
    

    (This will be "cs235" or "cs250" based on the course you're in.)

    Now that the folder is on your computer, you will need to change directory into the folder. Use cd FOLDERNAME (in the example above, the folder name would be YOURSSO-cs235 or YOURSSO-cs250) to enter the repository folder in Git Bash.

    Changing directory in Git Bash:

    c3_u00_clonedrepo_bash.png

    Viewing the folder in Windows Explorer:

    c3_u00_clonedrepo.png


  • Creating a branch
    Command Description
    git branch View available branches
    git checkout -b BRANCHNAME Create a new branch with the given BRANCHNAME
    git checkout BRANCHNAME Checkout an existing branch whose name matches the BRANCHNAME

    When working on an assignment, you will need to create a new branch each time.

    To view available branches, use the git branch command:

    $ git branch
    * main
    

    To create a new branch, use the git checkout -b BRANCHNAME command. The branch name needs to be unique. I suggest putting the Unit # in the branch name at least.

    $ git checkout -b u00_setup
    Switched to a new branch 'u00_setup'
    

    Now if you type in git branch again you will see two branches with the asterisk marking which branch you're currently on:

    $ git branch
      main
    * u00_setup
    

    Each branch basically has its own code space. If you have work-in-progress code happening on separate branches, they will only be available on that specific branch. If you switch to another branch you'll only see code for that branch, but you can always keep switching between to get the code back. (When starting a new assignment, make sure to go back to the main branch and use git pull to grab latest code before then creating a new branch. I'll try to put a reminder in future assignments.)

    If you want to switch between existing branches, use git checkout BRANCHNAME:

    $ git checkout main
    Switched to branch 'main'
    Your branch is up to date with 'origin/main'.
    
    $ git checkout u00_setup
    Switched to branch 'u00_setup'.
    

    (Note: The main branch might be named "master" so if your git branch shows "master" instead of "main", use that.)

    Stay on your new branch for now.


    Command Description
    ls List out the files and folders in the current directory
    touch FILENAME Create a new file
    echo "text" >> FILENAME Append text to a file
    git status View files that have changed
    git add FILENAME Add a specific file to the changeset
    git add *.cpp Add all files that end with ".cpp" to the changeset
    git add . Add all changed files to the changeset
    git commit Create a snapshot of currently added changes
    git commit -m "text" Create a snapshot of currently added changes, setting commit message at the same time
    git push -u origin BRANCHNAME Push the changes in the branch to the server

    Starting off, your repository may only have a README file in the folder:

    $ ls
    README.md
    

    In Windows you can create a new text file in here by opening notepad and saving a new file in this directory.

    In Git Bash you can create a file by using the touch command: Use the touch FILENAME command to create a new file. Name your file YOURNAME.txt . You can open the file from your OS and edit the text, or you can use the following command to put the text "Hello, I'm NAME!" in place:

    $ echo "Hello, I'm Rachel!" >> u00_hello.txt
    

    Next, use the git status command to view the changed file:

    $ git status
    On branch u00_setup
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
      u00_hello.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    We need to use the git add FILENAME command to mark the file for the changeset.

    $ git add u00_hello.txt
    

    It won't show any confirmation message if there is no error.

    Next, we use the git commit -m "COMMIT MESSAGE" command to make a snapshot of the file's changes at this point:

    $ git commit -m "Created my text file"
    [rsingh13_u04ex 9eb8e5c] Created my text file
     1 file changed, 1 insertion(+)
     create mode 100644 u00_hello.txt
    

    Finally, use the git push -u origin BRANCHNAME command to push your changes to the GitLab server:

    $ git push -u origin u00_setup
    Enumerating objects: 4, done.
    Counting objects: 100% (4/4), done.
    Delta compression using up to 12 threads
    Compressing objects: 100% (2/2), done.
    Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote:
    remote: To create a merge request for u00_setup, visit:
    remote:   (A URL)
    remote:
    To gitlab.com:(REPOSITORY URL).git
     * [new branch]      u00_setup -> u00_setup
    Branch 'u00_setup' set up to track remote branch 'u00_setup' from 'origin'.
    

  • Pulling remote changes
    Command Description
    git pull Pull latest changes from the server

    GitLab webpage: On the webpage your branch will now show up in the dropdown menu.

    c3_u00_gitlab_branch.png

    Select your branch and you'll be able to see the last commit and your file here:

    c3_u00_gitlab_files.png

    Open up your text file here and click the blue Edit button and select an editor. Add another line to the text file, then click the Commit changes button at the bottom of the page.

    c3_u00_gitlab_commit.png

    Git Bash: Use the git pull command here to pull all changes, which will grab the changes that you just made from the server.

    $ git pull
    remote: Enumerating objects: 5, done.
    remote: Counting objects: 100% (5/5), done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (3/3), 517 bytes | 517.00 KiB/s, done.
    From gitlab.com:(URL)
       9eb8e5c..90394af  u00_setup -> origin/u00_setup
    Updating 9eb8e5c..90394af
    Fast-forward
     u00_hello.txt | 2 ++
     1 file changed, 2 insertions(+)
    

    When you open the text file in Notepad or a text editor, you will now see the second line of text.


  • Dealing with merge conflicts

    This time we're going to purposefully create a merge conflict. This happens when changes are made to the same file from different locations (such as on your computer and the web, or between different developers) and git can't figure out how to automatically merge the changes together. It can be intimidating to deal with, but it isn't too bad.

    Computer: Edit your text file to add some text in the middle. Then use git add FILENAME , git commit -m "Edited file", but don't push yet.

    GitLab: Edit your text file to add a different line of text in the middle.

    c3_u00_gitlab_twochanges.png

    Git Bash: Now that you have two sets of changes, try to push your changes to the server. It will be rejected:

    $ git push
    To gitlab.com:(URL).git
     ! [rejected]        u00_welcome -> u00_welcome (fetch first)
    error: failed to push some refs to 'git@gitlab.com:(URL).git'
    hint: Updates were rejected because the remote contains work that you do
    hint: not have locally. This is usually caused by another repository pushing
    hint: to the same ref. You may want to first integrate the remote changes
    hint: (e.g., 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    It gives you a hint - you need to use git pull to grab any latest changes from the server:

    $ git push
    To gitlab.com:(URL).git
     ! [rejected]        u00_welcome -> u00_welcome (non-fast-forward)
    error: failed to push some refs to 'git@gitlab.com:(URL).git'
    hint: Updates were rejected because the tip of your current branch is behind
    hint: its remote counterpart. Integrate the remote changes (e.g.
    hint: 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    Use git pull, which will work, but result in a merge conflict:

    $ git pull
    Auto-merging u00_welcome.txt
    CONFLICT (content): Merge conflict in u00_welcome.txt
    Automatic merge failed; fix conflicts and then commit the result.
    

    Open the text file in your editor and you'll see some additions to the file:

    Hello, I'm Rachel!
    <<<<<<< HEAD
    This is my third edit.
    =======
    Fourth update!!!
    >>>>>>> a0c7bd54e2c5c19d1ebd41a84e7a6b8820573cf9
    This is a second update!
    

    There are two regions, and we need to manually decide how we want to merge the text. You could just remove the markers if you wanted both changes. Make the update and save the file:

    Hello, I'm Rachel!
    This is my third edit.
    Fourth update!!!
    This is a second update!
    

    Then do your add, commit, and push to push the merged updates to the server.

    Protip: You can combine multiple commands together with &&:

    $ git add . && git commit -m "manual merge" && git push

    GitLab: On the website, you can select the "Commits" link under the "Code" section to view all the changes you've made to the code:

    c3_u00_gitlab_commitlist.png


  • Creating an example program to test our IDE

    Lastly we're going to create a basic C++ project and make sure everything builds correctly. The project and code you create here should be stored within your repository.

    • If you're using VS Code, please check the reference portion of the book for the section labeled VS Code - Open projects, building, and running.
    • If you're using another code editor and wish to build from the terminal using the Makefile, look in the reference portion of the book for the section labeled Terminal - Building and running

    Visual Studio

    1. From the starting page, select "Create a new project".

      vs_newproject_01_splash.png

    2. Select "Empty project" and click "Next".

      vs_newproject_02_emptyproject.png

    3. Set your Project name. Make sure to set the project Location to somewhere within your repository directory.

      vs_newproject_03_namelocation.png

    4. Right-click on your empty project and select Add then New item…

      vs_newproject_04_addfile.png

    5. Type in "main.cpp" then click "Add".

      vs_newproject_04_addfile2.png

    6. Add a simple starter C++ program into main.cpp.

      vs_newproject_05_program.png

    7. Go to the "Build" dropdown menu and select "Build Solution".

      vs_newproject_06_build.png

    8. It should show a success in the Output window.

      vs_newproject_07_buildsuccess.png

    9. Then, click the "(play) Local Windows Debugger" button to run the program.

      vs_newproject_08_run.png

    10. Your program text will show up in a console.

      vs_newproject_08_run2.png

    11. Your files will be in the location you set up. The .cpp file is your source code, and the .vcxproj is the project file.

      vs_newproject_09_files.png

    Code::Blocks

    1. From the starting page, select "Create a new project".

      cb_newproject_01_splash.png

    2. Select "Empty project" and click "Go".

      cb_newproject_02_emptyproject.png

    3. Set your Project name. Make sure to set the project Location to somewhere within your repository directory.

      cb_newproject_03_namelocation.png

    4. Go to the "File" dropdown menu and select "New", then "Empty file".

      cb_newproject_04_addfile.png

    5. It will ask you to save the file first, name it main.cpp. Default options are fine.

      cb_newproject_04_addfile2.png

    6. Add a simple starter C++ program into main.cpp.

      cb_newproject_05_program.png

    7. Go to the "Build" dropdown menu and select "Build".

      cb_newproject_06_build.png

    8. The program should build without any errors.

      cb_newproject_07_buildsuccess.png

    9. Click on the "play" button in the toolbar to run the program.

      cb_newproject_08_run.png

    10. Your program text should show up in the console.

      cb_newproject_08_run2.png

    11. Your files will be in the location you set up. The .cpp file is your source code and the .cbp file is the project file.

      cb_newproject_09_files.png

    Afterwards, add, commit, and push your changes:

    git add .
    git commit -m "Example project"
    git push -u origin u00_setup
    

  • Creating a merge request

    Once changes are made, you'll create a merge request. This is how you can have your code reviewed, and the instructor will sign off on it and merge it to the main branch, where all the code will live together. This is similar to in software development, where everybody will work on their own features on their own branches, create a merge request, get their code reviewed, then a senior developer will merge the code into the main project.

    GitLab: On the main repository page you will see a message saying that there are changes in your branch, with a button "Create merge request":

    c3_u00_gitlab_mergenotif.png

    You can mostly leave the defaults unless you want to add notes. Scroll down and click the blue "Create merge request" button.

    There will be a merge request page generated:

    c3_u00_gitlab_mergerequest2.png

    The URL of this page is what you will usually turn in as your Lab assignments

    Once you've created a merge request, copy the URL of the merge request on the GitLab.com webpage:

    merge_request_url.png

    Back on the Canvas page, locate the assignment and click the link:

    canvas_assignment.png

    Then click on "Start Assignment":

    canvas_start_assignment.png

    Paste in the URL to your Merge Request in the "Website URL:" textbox, then click "Submit Assignment" to finish the turn in.

    canvas_submit_mergerequest.png

UNIT 01: Exploring software design

πŸ“–οΈ Reading - Exploring software design (U01.READ)

reading_u01_ExploringDesign_Visicalc_Wikipedia_Gorthu.png

Figure 2: Screenshot of VisiCalc running on an Apple II computer, by Gortu, CC0, File:Visicalc.png

Software on computers have come a long way over the decades. Desktop software, mobile apps, and websites handle so much more data and have much more functionality than what was around 20 or more years ago. As a code base expands in scope, software architects try to structure the software they work on to be scalable, efficient, and maintainable (though businesses often don't prioritize code maintenance so often times corporate code bases are big and messy and terrible. :)

If you get a job as a software developer, chances are the code base you join will be designed around the object oriented programming paradigm. An interconnected network of objects that have attributes (variables) and functionality, and which interact with other objects across the code base. Many objects represent data that will get stored in a database, some objects' purpose may be to facilitate interactions between other objects. With languages like Java and C#, everything must be built into a class, though C++ makes this optional, allowing for different styles of coding as well.

In this section we will look at some example designs for apps and websites you may commonly use.


  • Example: Spotify

    We can get some kind of idea of the underlying structure of a system by looking at the API (Application Programming Interface) that it exposes to the outside world. Many modern websites offer APIs in order to allow third parties to create helper programs and services that work with the data stored by the original service.

    Looking at the Spotify API documentation (https://developer.spotify.com/documentation/web-api), we can see several main "objects" it provides:

    1. Albums
    2. Artists
    3. Audiobooks
    4. Categories
    5. Chapters
    6. Episodes
    7. Genres
    8. Markets
    9. Player
    10. Playlists
    11. Search
    12. Shows
    13. Tracks
    14. Users

    If you click on one of the objects, it will show an example of requests that an external program can call, such as Get Album, Get Album Tracks, Save Albums for Current User, and so on.

    Clicking on a request type will also show a Response Sample, which shows what kind of information may be returned with a call, such as Get Album:

    {
      "album_type": "compilation",
      "total_tracks": 9,
      "available_markets": [
        "CA",
        "BR",
        "IT"
      ],
      "id": "2up3OPMp9Tb4dAKM2erWXQ",
      "images": [
        {
          "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228",
          "height": 300,
          "width": 300
        }
      ],
      "release_date": "1981-12",
      "type": "album",
      "artists": [
        {
          "id": "string",
          "name": "string",
        }
      ],
      "tracks": {
        "total": 4,
        "href": "https://api.spotify.com/v1/me/shows?offset=0&limit=20",
        "next": "https://api.spotify.com/v1/me/shows?offset=1&limit=1",
        "previous": "https://api.spotify.com/v1/me/shows?offset=1&limit=1"
      },
      "copyrights": [
        {
          "text": "string",
          "type": "string"
        }
      ],
      "genres": [
        "Egg punk",
        "Noise rock"
      ],
      "popularity": 0
    }
    

    The data and the form it's returned in via API response isn't necessarily how the data is stored in the database itself, or how the data is stored in objects when worked on by the Spotify servers themselves. Usually, API systems pulls data from multiple sources and bundles it together to return as a response. But, this can at least give us some idea of how the actual system may be organized.

    reading_u01_ExploringDesign_Spotify.png

    This is just a small example, containing a "Music" section (Artist, Album, Track) and a "User" section (User, Playlist). The arrows show relationships between the objects. But this diagram is also lacking any of the back-end functionality that helps everything actually do something. On their own, it's just data stored somewhere. A program has to be written around it.


  • Software layers

    Software is built in layers. The architecture itself may change over time, so we're going to keep this pretty general. Basically, you'll have a front-end, which is the interface the user sees when working with the program. The user isn't exposed to all of the complexity of the program itself - we give them some kind of nice interface to work with, and they don't care how anything actually works.

    reading_u01_ExploringDesign_interface3.png

    The front-end is hooked into the program back-end, which handles logic. This may also read from or write to storage, or a database.

    reading_u01_ExploringDesign_interface1.png

    A lot of utilities we use these days are websites, not desktop software, so the server we're contacting has the back-end and all its logic, as well as access to its databases. It then sends webpages or other forms of information to our computer (the client), which will be displayed - such as rendered by a web browser.

    reading_u01_ExploringDesign_interface2.png

    Again, architecture can change quickly. At the moment, microservices are a popular way to architect websites. We aren't going to go into depth into this, however, since we're going to just be working with console-based desktop software in this course.


  • Basic desktop program design for this class

    Here's an example of how we can may design programs in this course for larger projects. I have a "database" labeled but data saving and loading for us will just be via text documents or CSV files. (We're not going to set up database stuff because everyone's platforms are different and we don't have time for that. ;)

    reading_u01_ExploringDesign_Program.png

    In the yellow, BankTellerProgram and CustomerATMProgram are two different, separate programs. One would theoretically be used by a bank employee, and they have access to more sensitive information as part of their job. The customer's ATM might only have some basic features like checking balance, depositing, and withdrawing. For us, these act as the "interface layer".

    We have basic objects like BankAccount and Customer, which are more obvious instances of turning real-world concepts into objects (classes) in C++.

    Then we have the Manager objects, which contain all of the data for whatever they manage (BankAccountManager has a vector<BankAccount>, CustomerManager has a vector<Customer>) and interfaces to allow other parts of programs to access the data. This way, we're not cluttering up the Programs themselves with the logic needed to manage Bank Accounts or Customers.

    How does this design "flow"? Here's an example diagram of the process for a bank teller to add a new account:

    reading_u01_ExploringDesign_ProgramFlow.png

    • When the program begins, we enter main(), but we don't put much program logic in there - we just create and run a BankTellerProgram.
    • It starts at the main menu, and then the bank teller would select an option from the list.
    • We're adding a new account, so it calls Menu_AddNewAccount() in the BankTellerProgram.
    • Within this function, it will display a list of all customers by calling CustomerManager 's GetAllCustomers() function (or perhaps we could add a Display function).
    • Next, it asks the bank teller to enter the ID of the customer we're creating an account for.
    • Then, we create a new BankAccount object and set up its initial information.
    • After that's done, we call the BankAccountManager 's AddAccount function, passing in the new account object we created.
    • At the end of the Menu_AddNewAccount() function, it returns the teller back to the main menu so they can select the next operation that they'll perform.

    Utilize this example for the "🧠 Unit 01 Tech Literacy - Exploring software design (U01.TEC.202401CS235)" assignment, where you will come up with some example objects.

    \newpage

🧠 Tech Literacy - Exploring software design (U01.TEC)

Locate the Discussion Board assignment "🧠 Unit 01 Tech Literacy - Exploring software design (U01.TEC.202401CS235)" on Canvas. Refer back to the reading on software design ("πŸ“–οΈ Unit 01 Reading - Exploring software design (U01.READ.202401CS235)") for ideas for your post.

Example software:

  1. A Door Dash style food delivery app where a Customer can place an Order for some Food items from a Restaraunt, a Driver might pick up the items.
  2. A Social Media style app where Users can make friends, create Posts, and mark Posts as favorites.
  3. A Class Learning Management System where Teachers and Students can access a course, Assignments can be created/accessed, Grades can be saved.

You can come up with your own example program to post about as well.

UNIT 02: CS 200 Review - Basics

πŸ“–οΈ Reading - CS 200 review: Basics (U02.READ)

Make sure to also view the "C++ Quick Reference" part of this textbook for a reference guide on basic C++ syntax.

  • Includes
    Include Features
    #include <iostream> Use of cout (console output), cin (console input), getline
    #include <string> Use of the string data type and its functions
    #include <fstream> Use of ofstream (output file stream), ifstream (input file stream), and related functions
    #include <array> Use of array from the Standard Template Library
    #include <vector> Use of vector from the Standard Template Library
    #include <cstdlib> C standard libraries, usually used for rand().
    #include <ctime> C time libraries, usually used for srand( time( NULL ) ); to seed random # generator
    #include <cmath> C math libraries, such as sqrt, trig functions, etc.
    #include "file.h Use "" to include .h files within your own project. DON'T USE INCLUDE ON .cpp FILES!!
    About the using command

    The using namespace std; command states that we're using the std namespace. If this is left off, then we need to prefix any C++ types and functions with std::, such as std::cout << "Hello!" << std::endl;

    Best practices:

    • If your program ONLY uses C++ standard library includes, use using namespace std; for brevity.
    • If your program uses MULTIPLE LIBRARIES, avoid using namespace std; and prefix each type/function with the library it belongs to (e.g., std::string from the STD library, sf::vector2f from the SFML library)

  • Bare minimum C++ programs

    Without arguments:

    #include <iostream>
    using namespace std;
    
    int main()
    {
      cout << "Hello, world!" << endl;
      return 0;
    }
    

    With arguments:

    #include <string>
    #include <iostream>
    using namespace std;
    
    int main( int argCount, char* args[] )
    {
      if ( argCount < 4 ) { cout << "Not enough arguments!" << endl; return 1; }
    
      int my_int = stoi( args[1] );
      float my_float = stof( args[2] );
      string my_string = string( args[3] );
    
      return 0;
    }
    

  • Building and running from the command line

    Building a single source file:

    g++ SOURCEFILE.cpp -o PROGRAMNAME.out
    

    Building multiple source files:

    g++ *.cpp *.h -o PROGRAMNAME.out
    

    (There's a different command to use the Visual Studio compiler to build from command line.)

    Running a program without arguments:

    ./PROGRAMNAME.out
    

    Running a program with arguments:

    ./PROGRAMNAME.out arg1 arg2 arg3
    

  • Variable declaration
    1. Declaring a variable: DATATYPE VARIABLENAME;
    2. Declaring a variable and assigning a value: DATATYPE VARIABLENAME = VALUE;
    3. Declaring and assigning a named constant: const DATATYPE NAME = VALUE;
    4. Assigning a new value to an existing variable: VARIABLENAME = VALUE;
    5. Copying a value from one variable to another: UPDATEDVAR = COPYME;
    Data type Value examples
    int -5, 0, 100
    float 0.99, -3.25, 1.0
    string "Hello world!"
    char 'a', '$'
    bool true, false

    Increment/Decrement statements:

    • a++, a--
    • ++a, --a
    • a+=5;, a-=5;
    • a = a + 5;, a = a - 5;

    Notes:

    • LHS = RHS; copies FROM RHS TO LHS; make sure you have the right order!
    • A hard-coded value, like "Hello", is known as a literal.

  • Console input and output
    1. Output a variable's value: cout << VARIABLENAME;
    2. Output a string literal: cout << "Hello";
    3. Chain together multiple items: cout << "Label: " << VARIABLENAME << endl;
    4. Input to a variable: cin >> VARIABLENAME;
    5. Input a whole line to a string variable: getline( cin, STRINGVARIABLE );

    Notes:

    • The << operator is called the output stream operator and is used on cout statements.
    • The >> operator is called the input stream operator and is used on cin statements.
    • endl is only to be used with cout statements, not cin!
    • getline can only be used with strings!! Use cin >> for other data types.
    • You need a cin.ignore() ONLY in between cin >> ... and getline( cin, ... ).
      • If your program is skipping an input then you're missing a cin.ignore();.
      • If your program is getting input but not storing the first letter then you have too many cin.ignore(); statements / they're in the wrong place.

  • Boolean expressions
    • AND (&&)
      • Expression is TRUE if all sub-expressions are TRUE
      • Expression is FALSE if at least one sub-expression is FALSE
    • OR (||)
      • Expression is TRUE if at least one sub-expression is TRUE
      • Expression is FALSE if all sub-expressions are FALSE
    • NOT (!)
      • Expression is TRUE if sub-expression is FALSE
      • Expression is FALSE if sub-expression is TRUE
    a b a AND b a OR b
    T T T T
    T F F T
    F T F T
    F F F F
    a NOT a
    T F
    F T

  • If statements
    • if statement:
    if ( CONDITION_A )
    {
      // Action A
    }
    
    • if/else statement:
    if ( CONDITION_A )
    {
      // Action A
    }
    else
    {
      // Action Z
    }
    
    • if/else if statement:
    if ( CONDITION_A )
    {
      // Action A
    }
    else if ( CONDITION_B )
    {
      // Action B
    }
    else if ( CONDITION_C )
    {
      // Action C
    }
    
    • if/else if/else statement:
    if ( CONDITION_A )
    {
      // Action A
    }
    else if ( CONDITION_B )
    {
      // Action B
    }
    else if ( CONDITION_C )
    {
      // Action C
    }
    else
    {
      // Action Z
    }
    

    Condition types can be a boolean variable:

    bool done = true;
    if ( done )
    {
      cout << "Goodbye!" << endl;
    }
    

    Or a boolean expression:

    int a = 10, b = 5;
    if ( a > b )
    {
      cout << "a is bigger!" << endl;
    }
    

    Notes:

    • else statements NEVER TAKE A CONDITION
    • Whichever condition evaluates to true, all future else if and else statements are skipped.

  • Switch statements
    switch( myNumber )
    {
      case 1:
        cout << "It's one!" << endl;
        break;
    
      case 2:
        cout << "It's two!" << endl;
        break;
    
      default:
        cout << "I don't know what it is!" << endl;
    }
    

    Notes:

    • You can leave off break; from a case statement. In this case, fallthrough occurs, where the following case's code will be executed, up until it hits a break; statement.
    • Switch statements like the one above can be replaced with if (myNumber = 1)=, else if (myNumber = 2)=.
    • In C++, switch doesn't work with string, only primitive data types like int, float, char.

    Fallthrough example:

    char choice;
    cout << "Enter a choice: (y/n): ";
    cin >> choice;
    
    switch( choice )
    {
      case 'y':
      case 'Y':
        cout << "YES" << endl;
        break;
    
      case 'n':
      case 'N':
        cout << "NO" << endl;
        break;
    
      default:
        cout << "INVALID INPUT!" << endl;
    }
    

  • While loops

    while ( CONDITION ) { }

    while ( a < b )
    {
      cout << a << endl;
      a++;
    }
    

    Notes:

    • While loops use CONDITIONS like if statements do.
    • While loops are susceptible to infinite loop errors if nothing within the loop causes the CONDITION to evaluate to false eventually. Be careful!

  • For loops

    Normal for loop:

    for ( INIT; CONDITION; UPDATE ) { }

    for ( int i = 0; i < 10; i++ )
    {
      cout << i << endl;
    }
    

    Notes:

    • When using STL arrays or vectors, size_t or unsigned int should be used instead of int for i. This will remove the warning relating to testing .size() (which returns size_t) against a signed integer.

    Range-based for loop:

    In versions of C++ past C++98 (from 1998) you can use range-based for loops to iterate over a range of items:

    for ( INIT : RANGE ) { }

    vector<int> myVec = { 1, 2, 3, 4 };
    for ( int element : myVec )
    {
      cout << element << endl;
    }
    

  • Arrays and vectors
    1. Declare a C-style array: DATATYPE ARRAYNAME[ SIZE ];
    2. Declare a C++ STL array: array<DATATYPE, SIZE> ARRAYNAME;
    3. Declare a C++ STL vector: vector<DATATYPE> VECTORNAME;
    4. Declare a dynamic array via pointer: DATATYPE * PTR = NEW DATATYPE[ SIZE ];
    5. Destroy a dynamic array: delete [] PTR;
    6. Initialize an array with an initializer list: DATATYPE ARRAY[] = { VAL1, VAL2, VAL3 } (also works for array and vector).

    Notes:

    • The position of an item within an array is called its index.
    • The variable within the array at some position is called its element.
    • A vector is a dynamic array, but you don't have to worry about the memory management. :) This means that it can be resized.
    • A array is a fixed size, just like the C-style array.

    Iterate over an array/vector

    // C-style array:
    for ( int i = 0; i < TOTAL_STUDENTS; i++ )
    {
      cout << "index: " << i << ", value: " << students[i] << endl;
    }
    
    // STL Array and STL Vector:
    for ( size_t i = 0; i < bankBalances.size(); i++ )
    {
      cout << "index: " << i << ", value: " << students[i] << endl;
    }
    

    size_t is another name for an unsigned int, which allows values of 0 and above - no negatives.


  • File I/O

    Create an output file and write

    ofstream output;
    output.open( "file.txt" );
    
    // Write to text file
    output << "Hello, world!" << endl;
    

    Create an input file and read

    ifstream input;
    input.open( "file.txt" );
    if ( input.fail() )
    {
      cout << "ERROR: could not load file.txt!" << endl;
    }
    string buffer;
    
    // read a word
    input >> buffer;
    
    // read a line
    getline( input, buffer );
    

    Notes:

    • An ifstream open will fail if the file requested does not exist or cannot be found. Use if ( input.fail() ) to check for a failure scenario.
    • An ofstream open will create a file if it doesn't already exist.
    • The default path where files are written to or read from is wherever your project file is (.vcxproj for Visual Studio, .cbp for Code::Blocks). You can also set a default working directory in your project settings.
      • Mac users may need to specify an absolute path, like "~/myfile.txt", which will put the file in your home directory. I don't know how to use XCode but we can work through it together if you need help.

  • Review questions:

    Answer these questions in your notes, then check your work by completing the related Concept Intro assignment on Canvas.

    1. The starting point of all C++ programs is…
    2. Given two variables, vip and player3, how do you copy the name from the player3 variable into the vip variable?
    3. << is known as …, >> is known as…
    4. getline( cin, … ) can only be used with…
    5. An if or else if statement's contents are executed when…
    6. The three parts of a for loop's header is…
    7. … can be resized, but … cannot.

    After the reading, complete the assignment:

    πŸ”Ž Unit 02 Concept Intro - CS 200 review: Basics (U02.CIN)

    \newpage

πŸ§‘β€πŸ”¬ Lab - CS 200 review: Basics (U02.LAB)

About: This assignment will cover some basic C++ concepts. This should give you a chance to refresh your skills and recognize what you may need to review.

pixel-goals.png Goals:

  • Practice with variables
  • Practice with branching
  • Practice with loops
  • Practice with vectors
  • Practice with file output
  • Practice with file input

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Set up: Creating a new branch in your repository

    git-harddrive-opengitbash.png

    First, Open up Git Bash (Windows) or your Terminal (Linux/Mac) from within your repository directory. Make sure you're inside the repository folder, which should be named something like YOURSSO-CLASS.

    Next, create a new branch in the repository with the checkout command: git checkout -b u02_review

    $ git checkout -b u02_review
    Switched to a new branch 'u02_review'
    

    git_newbranch.png

    Now your folder is on the new branch and you can start working on the assignment.


  • Download the starter code

    Download the following starter code and unzip it to your repository folder.

    lab_u02_CPPBasicsReview_extractedfiles.png

    All starter files:

    .
    ├── INFO.h
    ├── main.cpp
    ├── Project_CodeBlocks
    │   ├── CPPReviewBasics.cbp
    │   ├── CPPReviewBasics.depend
    │   └── CPPReviewBasics.layout
    ├── Project_Makefile
    │   └── Makefile
    ├── Project_VisualStudio
    │   ├── CPPBasicsReview.sln
    │   ├── CPPBasicsReview.vcxproj
    │   ├── CPPBasicsReview.vcxproj.filters
    │   ├── results.txt
    │   ├── test1.txt
    │   └── test2.txt
    └── u02_Review_CPPBasics
        ├── P1_variables
        │   └── U02_P1_PizzaSlices.cpp
        ├── P2_branching
        │   └── U02_P2_LetterGrade.cpp
        ├── P3_looping
        │   └── U02_P3_SalaryRaise.cpp
        ├── P4_vector
        │   └── U02_P4_GPA.cpp
        ├── P5_ofstream
        │   └── U02_P5_SalaryRaise.cpp
        ├── P6_ifstream
        │   ├── lyricsB.txt
        │   └── U02_P6_Lyrics.cpp
        ├── U02_Headers.cpp
        └── U02_Headers.h
    

    Visual Studio users:

    Open the Project_VisualStudio folder and open the CPPBasicsReview.sln file to get started.

    Code::Blocks users:

    Open the Project_CodeBlocks folder and open the CPPReviewBasics.cbp file to get started.

    Build from Terminal/VSCode:

    In the Project_Makefile directory you'll find a makefile you can use to build the project. Use the command make in the directory, and it will generate an executable file that you can run like this: ./CPPReviewBasics_out

    DO NOT CREATE YOUR OWN VISUAL STUDIO OR CODE::BLOCKS PROJECT.

    DO NOT REORGANIZE THE CODE.

    KEEP YOUR REPOSITORY DIRECTORY AND FILES CLEAN!!


  • How the program and tests work

    (These are not steps to do yet, just background information on the program. Instructions continue below.)

    Once you run the program, you will see two options: Run the AUTOMATED TESTS or run the PROGRAMS manually.

    1. Run AUTOMATED TESTS
    2. Run PROGRAMS
    >>
    

    You can use the AUTOMATED TESTS to verify your work. Tests will either be marked as PASS or FAIL, and show the input passed in and the expected result:

    [FAIL]  TEST 1, StudentCode(AABC)
       EXPECTED OUTPUT: [3.25]
       ACTUAL OUTPUT:   [3.5]
    
    [PASS]  TEST 2, StudentCode(ABCCCDF) = 2.07143
    

    You can also MANUALLY RUN THE PROGRAMS to test them yourself:

    1. Run AUTOMATED TESTS
    2. Run PROGRAMS
    >> 2
    UNIT 02: C++ Basics Review -----------------------------------------------------
    
    Program 1: Pizza slices
    Program 2: Letter grade
    Program 3: Salary raise v1
    Program 4: GPA
    Program 5: Salary raise v2
    Program 6: Lyrics
    
    Run program #: 4
    Enter another letter grade (A, B, C, D, F) or X to stop: A
    Enter another letter grade (A, B, C, D, F) or X to stop: B
    Enter another letter grade (A, B, C, D, F) or X to stop: C
    Enter another letter grade (A, B, C, D, F) or X to stop: X
    Your GPA is: 3
    

    To begin with, the functionality won't work properly - you will need to implement the functions themselves. We will step through that in a moment.


  • File: INFO.h

    This file contains a string constant. Replace "Your Name" with your actual name. This is to help me with grading.

    const std::string STUDENT_NAME = "Your Name";
    

    As you work through the coding assignments, if you have any questions or get stuck, feel free to ask for hints from the instructor.


  • File: U02_P1_PizzaSlices.cpp

    This code file is under u02_Review_CPPBasics/P1_variables/U02_P1_PizzaSlices.cpp.

    PIZZA PARTY PLANNER
    How many guests will you have at your party? 20
    How many slices of pizza are available? 100
    Total guests: 20
    Total slices of pizza: 100
    Each guest gets 5 slices of pizza
    Each guest can have 5 slices of pizza
    

    You will be editing the function int U02_Program1_Function( int guest_count, int pizza_slices ) for this program.

    • Input parameters:
      1. guest_count, the amount of guests attending the party
      2. pizza_slices, the total amount of pizza slices at the party
    • Return output: The amount of slices of pizza each guest gets
    • Requirements: In this function, calculate the slices per person. You can add cout statements to display each piece of information, or you can also just do the calculation and return it at the end.
    • Math: Given \(s\) total slices and \(g\) total guests, slices per person \(p\) is \[p = \frac{s}{g}\].

  • File: U02_P2_LetterGrade.cpp

    This code file is under u02_Review_CPPBasics/P2_branching/U02_P2_LetterGrade.cpp.

    LETTER GRADE PROGRAM
    How many points was the assignment worth? 75
    How many points did you score? 65
    
    GRADE INFORMATION
    My score ......... 75
    Points possible ...65
    Grade % ...........115.385
    Letter grade ......A
    
    Letter grade result: A
    

    You will be editing the function char U02_Program2_Function( float points_possible, float my_score ) for this program.

    • Input parameters:
      1. points_possible, the amount of total points an assignment is worth
      2. my_score, the amount of points the student earned on the assignment
    • Return output: The letter grade they received on the assignment
    • Requirements: Calculate the percent result the student earned on the assignment
    • Math: Given \(p\) points possible and \(s\) score earned, the percentage grade the student earned \(g\) is: \[ g = \frac{s}{p} \cdot 100 \]

    Letter grade: Use the following criteria to decide which grade the student receives.

    Letter Range
    'A' 89.5% and above
    'B' 79.5% - 89.5%
    'C' 69.5% - 79.5%
    'D' 59.5% - 69.5%
    'F' Below 59.5%

  • File: U02_P3_SalaryRaise.cpp

    This code file is under u02_Review_CPPBasics/P3_looping/U02_P3_SalaryRaise.cpp.

    SALARY RAISE PROGRAM v2
    What is your starting salary? $60000
    What is the raise you get per year? %2
    How many years to calculate? 10
    Starting salary: $60000.00
    Year: 1, Salary: $61200.00
    Year: 2, Salary: $62424.00
    Year: 3, Salary: $63672.48
    Year: 4, Salary: $64945.93
    Year: 5, Salary: $66244.85
    Year: 6, Salary: $67569.75
    Year: 7, Salary: $68921.15
    Year: 8, Salary: $70299.57
    Year: 9, Salary: $71705.56
    Year: 10, Salary: $73139.67
    Salary after 10 years: $73139.67
    

    You will be editing the function float U02_Program3_Function( float starting_salary, float raise_per_year, int years ) for this program.

    • Input parameters:
      1. starting_salary, the salary the user starts out with
      2. raise_per_year the decimal value of the raise per year (e.g., 0.05 instead of 5%)
      3. years, how many years of raises they want to calculate
    • Return output: The salary after years amount of years.
    • Requirements: Use a loop to calculate and display the updated salary each year.
    • Math: Given the last year's salary \(s\) and the decimal raise per year \(d\), the updated salary for the year \(u\) is: \[ u = s + sd \]

  • File: U02_P4_GPA.cpp

    This code file is under u02_Review_CPPBasics/P4_vector/U02_P4_GPA.cpp.

    Enter another letter grade (A, B, C, D, F) or X to stop: A
    Enter another letter grade (A, B, C, D, F) or X to stop: A
    Enter another letter grade (A, B, C, D, F) or X to stop: B
    Enter another letter grade (A, B, C, D, F) or X to stop: C
    Enter another letter grade (A, B, C, D, F) or X to stop: X
    Your GPA is: 3.25
    

    You will be editing the function float U02_Program4_Function( std::vector<char> course_grades ) for this program.

    • Input parameters:
      1. course_grades, a vector of letter grades
    • Return output: The GPA the student received
    • Requirements: Calculate the decimal equivalent of each letter grade, then use that to calculate the GPA.
    • Math: Given \(n\) total decimal scores \(s_1\), \(s_2\), …, \(s_n\), the GPA is: \[ \frac{ s_1 + s_2 + ... + s_n }{ n } \]
    Letter Decimal equivalent
    'A' 4.0
    'B' 3.0
    'C' 2.0
    'D' 1.0
    'F' 0.0

  • File: U02_P5_SalaryRaise.cpp

    This code file is under u02_Review_CPPBasics/P5_ofstream/U02_P5_SalaryRaise.cpp.

    SALARY RAISE PROGRAM v2
    What is your starting salary? $40000
    What is the raise you get per year? %5
    How many years to calculate? 5
    Salary after 5 years: $51051.26
    Result saved to results.txt
    

    Saved file example:

    Starting salary: $40000
    Year: 1, Salary: $42000
    Year: 2, Salary: $44100
    Year: 3, Salary: $46305
    Year: 4, Salary: $48620.2
    Year: 5, Salary: $51051.3
    

    You will be editing the function float U02_Program5_Function( float starting_salary, float raise_per_year, int years ) for this program.

    • Input parameters:
      1. starting_salary, the salary the user starts out with
      2. raise_per_year the decimal value of the raise per year (e.g., 0.05 instead of 5%)
      3. years, how many years of raises they want to calculate
    • Return output: The salary after years amount of years.
    • Requirements: Besides calculating the updated salary, create an ofstream output file and store the following data to the text file:
      • The starting salary
      • In the loop, each year and the updated salary.

  • File: U02_P6_Lyrics.cpp

    This code file is under u02_Review_CPPBasics/P6_ifstream/U02_P6_Lyrics.cpp.

    Get which line # from test2.txt? 1
    Text at that line: we can leave your friends behind
    

    Example input file:

    we can dance if we want to
    we can leave your friends behind
    cuz your friends don't dance
    and if they don't dance
    well then they're no friends of mine.
    

    You will be editing the function std::string U02_Program6_Function( std::string filename, int line_number ) for this program.

    • Input parameters:
      1. filename, the file of lyrics to open
      2. line_number, which line # of the song to display
    • Return output: The lyric at the given line_number
    • Requirements: Use an ifstream object to open the filename given. Read each line of the input file until you hit the correct line number, return the string read in at that line number.
      • Error check: If opening the file fails, then display an error message and return an empty string, "".
      • Default: If the loop completes and we haven't hit the line_number requested (i.e., the file is fewer than that many lines), then return an empty string. "".

    File input hints:

    You can read one line of the file at a time with the getline function:

    // input is our ifstream object
    // buffer is a string, each line of the file is stored in it
    while ( getline( input, buffer ) )
    {
      // Do something with buffer
    }
    

  • Turning in the assignment

    Once you're done with the assignment, you need to add, commit, and push your changes to the server.

    lab_u02_CPPBasicsReview_gitserver.png

    Save your changes to the server:

    1. Open git bash from within your repository folder on your harddrive. git-harddrive-opengitbash.png
    2. Use git status to review what files have changed since your last commit.
    3. Use git add . to add all changed files to a changeset. git_add.png
    4. Use git commit -m "MESSAGE" to create a snapshot of your current changes. (Replace "MESSAGE" with something more descriptive.) git_commit.png
    5. Use git push -u origin u02_review to push all your commits to the GitLab server, on the u02_review branch. git_push.png

    You can continue making changes and do additional add, commit, and push commands as well.

    Get everything ready for next assignment:

    1. Use git checkout main to change back to the main branch to prepare for the next assignment. (You can always use git checkout u02_review to return to this branch and code.)
    2. Use git pull to pull latest from the server. (This might happen if the instructor adds new starter code or makes modifications.)

    git_newbranch.png

    Create a Merge Request on the GitLab website:

    After pushing your changes to your branch, the GitLab webpage will show an update:

    gitlab_web_mergerequest1.png

    Click the blue "Create merge request" button.

    gitlab_web_mergerequest2.png

    Give your Merge Request a descriptive name, such as the unit the assignment belongs to.

    At the bottom of the page, click on the blue "Create merge request" button.

    gitlab_web_mergerequest3.png

    Your merge request will now be created. You don't need to do anything further on this page.

    • Approve button: The instructor may mark the merge request as approved.
    • Merge button: You won't be able to press the Merge button; the instructor will merge the code once they've reviewed your work and approved it.

    Turn in the Merge Request URL on Canvas:

    Once you've created a merge request, copy the URL of the merge request on the GitLab.com webpage:

    merge_request_url.png

    Back on the Canvas page, locate the assignment and click the link:

    canvas_assignment.png

    Then click on "Start Assignment":

    canvas_start_assignment.png

    Paste in the URL to your Merge Request in the "Website URL:" textbox, then click "Submit Assignment" to finish the turn in.

    canvas_submit_mergerequest.png

    \newpage

🧠 Tech Literacy - Command Line programs (U02.TEC)

  • A brief history of interfacing with computers

    Early computers were a shared resource, with one mainframe computer shared by many people at a university, business, or elseware. People often wrote programs on paper punchcards using dedicated hardware separate from the computer itself. (See: 1964 IBM 029 Keypunch Card Punching Demonstration (CuriousMarc))

    Used_Punchcard_(5151286161).jpg

    Figure 3: By Pete Birkinshaw from Manchester, UK - Used Punchcard, CC BY 2.0, index.php?curid=49758093Used_Punchcard_(5151286161).jpg

    Similarly, early computers didn't necessarily utilize video screens or monitors for output. Since the computer was shared, you might drop off your batch of cards for your program and come back later for the result output. If you think about it, a paper-based printout was probably the most cost-effective way for this. (See: YouTube: The IBM 1401 Mainframe Computer Wishes you a Merry Christmas (CuriousMarc) - Feeding a mainframe punchcards and getting printed output)

    Even when video monitors were available for these mainframe computers, the computers were still a shared resource being used by multiple people sharing computer time. In cases like these, users would work at a terminal. These look like what we think of as computers, with a video display and a keyboard, but they don't do any processing on their own. These terminals were connected to the mainframe computer, allowing users in other locations to interact with it via a simple text interface.

    Even as Personal Computers began taking root in households in the 1980s, only rudimentary graphics were available (if any), such as drawing large blocks with some basic colors, or drawing pixels at specific positions on a low resolution screen. For daily use, typed commands were still largely how people interacted with the operating system of their computer, and for many of their programs as well.

    vic20.jpg

    Figure 5: A photo of a Vic-20 BASIC program displayed on a CRT television.

    If a program did offer some kind of graphical user interface, at the time there were no standards and programs by different developers would often have wildly different interfaces, keyboard shortcuts, and styles.

  • Who uses command line programs these days?

    In a way, there are still challenges around different types of interfaces. We have computers running different operating systems like Windows, Mac OS, Linux, and Unix, we have smartphones running their own operating systems as well. Beyond that, people might install different types of software or configure their personal computers differently. Under the surface, however, there is still some form of command line, though the commands may differ between a Windows PowerShell and a Linux/UNIX Bash system. So, what do these non-GUIs offer in utility?

    Consistency
    Sometimes I try to help out a person on a Mac computer, though I have almost no experience with that operating system. Often, I find it more comfortable to navigate through the Mac Terminal to utilize the UNIX-like commands that are in common with Linux - such as ls to list files and folders in a directory. Rather than have to learn a GUI for a system I'll never use on my own, I can interact with it with a "common tongue" - the command line. :)
    Scriptability
    Think of the effort it takes to automate doing anything with a GUI - you'll need some kind of program or library that allows moving the mouse to some (x,y) position, hit mouse clicks, type keys, etc. And even then, windows in a GUI might move around. It's much easier to write a program script to automate running commands from a command line instead - it's just text commands!
    Speed
    Command lines generally can be more responsive than a GUI. I find that even on computers that aren't too old, Windows slogs along. Command lines don't necessarily use your computer's graphics card to make the windows semi-transparent and animate when they perform some operation. And in some cases it can be faster to type something like mkdir cs200 cs235 cs250 to create three folders than to go through the steps to move mouse to "File", move mouse to "New Folder", click, type "cs200", repeat twice more.
    Portability
    Being able to navigate a command line also makes it much easier to access remote computers. While you can run a computer remotely and work with the graphical operating system, it's still usually pretty sluggish. Instead, you could SSH into a computer and work with it from the command line and quickly perform some operations that you need, which could be remotely launching a build, restarting a server, or even writing some notes.

    Most average computer users aren't using the command line in their personal lives, but it is still a hugely useful tool for people who work in IT and software. With IT, imagine you have to configure 20 computers per classroom, for all the classrooms in a building - would it be easier to manually go configure those computers from their GUIs, or to write a script and run that script on each computer?

  • Why Computer Science courses don't (usually) teach GUI creation
    GUI libraries change over time
    Java and C# have both gone through various different systems for creating GUIs, such as WPF for old Windows programs, Swing for Java-based programs. But eventually they go out of date and stop being used.
    C++ isn't "owned" by a company like Microsoft or Oracle
    so there's no company to package some kind of UI library as the de-facto standard for C++ GUI development. There are open source tools (wxWidgets, Qt) and proprietary tools, but who knows what any given company uses for their software - if they even write desktop software at all anymore, anyway. Most things are HTML/CSS/JS based now. :)
    The study of Computer Science is more about how computers work rather than the design of front-ends.
    Adding a GUI layer on top of learning core C++ or about data structures can make the course content less focused and make it hard to understand the important parts on its own.
  • Command line programs on your own computer

    If you're on Windows open up the PowerShell. If you're on Mac open up the Terminal. If you're on Linux open up your favorite Terminal emulator or whatever. :) Let's look at some basic programs that might be useful.

    • ping URL - The ping program allows us to send multiple packets to some URL. I usually just use this to see if my internet has gone down or if a site is just down. I still, by habit, use ping yahoo.com even though I haven't used Yahoo in years. (FYI: Use CTRL+C to end a command line program if it continues looping.)
    rachelwil@rachelwil-GF63-Thin-9SC:~$ ping yahoo.com
    PING yahoo.com (98.137.11.163) 56(84) bytes of data.
    64 bytes from media-router-fp74.prod.media.vip.gq1.yahoo.com (98.137.11.163): icmp_seq=1 ttl=49 time=60.6 ms
    64 bytes from media-router-fp74.prod.media.vip.gq1.yahoo.com (98.137.11.163): icmp_seq=2 ttl=49 time=63.1 ms
    64 bytes from media-router-fp74.prod.media.vip.gq1.yahoo.com (98.137.11.163): icmp_seq=3 ttl=49 time=85.4 ms
    64 bytes from media-router-fp74.prod.media.vip.gq1.yahoo.com (98.137.11.163): icmp_seq=4 ttl=49 time=69.7 ms
    ^C
    --- yahoo.com ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3004ms
    
    • mkdir FOLDER1 FOLDER2 FOLDER3 - The mkdir (make-directory) program allows you to create 1 or more folders within the directory that you're currently in.
    • ls - The ls (list) program shows you all the files and folders in the directory that you're currently in.
    • pwd - The pwd (present working directory) program shows you the path of the directory that you're currently in.
    rachelwil@rachelwil-GF63-Thin-9SC:~/test-folder$ mkdir folderA folderB folderC
    
    rachelwil@rachelwil-GF63-Thin-9SC:~/test-folder$ ls
    folderA  folderB  folderC
    
    rachelwil@rachelwil-GF63-Thin-9SC:~/test-folder$ pwd
    /home/rachelwil/test-folder
    

    Commands that you can use from the terminal can also be called from C++ by using the system() function.

  • See also

WEEK 2 - JAN 22

UNIT 03: CS 200 Review - Functions and classes

πŸ“–οΈ Reading - CS 200 Review - Functions and classes (U03.READ)

Make sure to also view the "C++ Quick Reference" part of this textbook for a reference guide on basic C++ syntax.


  • Functions
    • Function headers

      A function header contains the following information:

      RETURNTYPE FUNCTIONNAME( PARAMETERLIST )

      • The return type is the type of data returned (like an int), or void for functions that don't need to return any data.
      • The function name follows the same naming rules as a variable - letters, numbers, and underscores allowed, no spaces or other special characters.
      • The parameter list is a series of variable declarations to be used within the function. These parameters are assigned values during the function call, when arguments are provided.

    • Function declarations

      Function declarations should go in .h files. A function declaration is the function header, with a semicolon at the end:

      int Sum( int a, int b );
      void DisplayMenu();
      

    • Function definitions

      Function definitions should go in .cpp files. A function definition is the function header, plus a code block, starting and ending with curly braces {}:

      int Sum( int a, int b )
      {
        int result = a + b;
        return result;
      }
      
      void DisplayMenu()
      {
        cout << "1. Add items" << endl;
        cout << "2. Subtract items" << endl;
        cout << "3. Quit" << endl;
      }
      

    • Function calls

      Function calls will happen within other functions. A function call includes the function's name, input arguments to be passed in, and the return data will need to be stored in a variable, if applicable.

      int main()
      {
        DisplayMenu(); // Function call
        int choice;
        cin >> choice;
      
        if ( choice == 1 )
        {
          int num1, num2, result;
          cout << "Enter num1: ";
          cin >> num1;
          cout << "Enter num2: ";
          cin >> num2;
      
          result = Sum( num1, num2 ); // Function call
          cout << "Result: " << result << endl;
        }
        // ... etc ...
      
        return 0;
      }
      

    • Header files

      Function declarations should go in header files (.h files).

      Header files must have file guards, this prevents the .h file from being "copied" into multiple files, which will cause a "duplicate code" build error.

      #ifndef _FILENAME_H
      #define _FILENAME_H
      
      // Code goes here
      
      #endif
      

      For the file guard, a label like _FILENAME_H is used. This must be unique for each file!

      Visual Studio supports using #pragma once for .h files, but this is not cross platform, so you should use these preprocessor file guards!


    • Source files

      Function definitions should go in source files (.cpp files).

      File guards are not needed in .cpp files.


    • Using your functions in other files

      Any file that utilizes your function must include the .h file:

      #include "MyFunctions.h"
      

      Note that something like #include <iostream> is used for including libraries that are not inside the project. Using "" is for including files that are in your project.

      NEVER INCLUDE .cpp FILES, THIS WILL GENERATE ERRORS!


    • Function overloading

      Multiple functions can have the same name as long as their function signatures are different. This means that two functions with the same name either need a different number of parameters, or different parameter data types, so that they can be unambiguously identified.

      AMBIGUOUS:

      void MyFunction( int a, int b );
      
      void MyFunction( int num1, int num2 );
      

      UNAMBIGUOUS:

      void MyFunction( int num1, int num2 );
      
      void MyFunction( string name1, string name2 );
      
      void MyFunction( int oneNum );
      

  • Structs and classes
    • Accessibility levels

      We can specify accessibility levels for struct and class members. This information dictates where a struct/class' internal contents can be accessed from:

      Accessibility level Class' functions? Child's functions? External functions?
      private βœ… ❌ ❌
      protected βœ… βœ… ❌
      public βœ… βœ… βœ…
      • private members can only be accessed from the class' own functions.
      • protected members can only be accessed from the class' own functions, and also the functions of any other classes that INHERIT from this class.
      • public members can be accessed from anywhere in the program, including functions that are not a part of the class.
    • Structs

      Structs are usually used to store very basic structures, often with just variable data and no functions.

      struct Coordinate
      {
        float x, y;
      };
      

      Struct declarations should go in their own .h file, usually the filename will match the name of the class, such as "Coordinate.h".

      All .h files need file guards!

      Also note that at the closing curly brace } of the struct declaration there is a semicolon - this is required!

      Members of a struct are public level accessibility by default.


    • Classes

      Classes are meant for more complex structures.

      class CLASSNAME
      {
          public:
          // Public members
      
          protected:
          // Protected members
      
          private:
          // Private members
      };
      

      Class declarations should go in their own .h file, and class function definitions should go in a corresponding .cpp file!

      It is best practice to make member variables of a class private, and only provide indirect access to this data via functions.

      Example: Product.h

      #ifndef _PRODUCT_H  // File guards
      #define _PRODUCT_H  // File guards
      
      #include <string>
      using namespace std;
      
      class Product
      {
          public:
          // Constructors:
          Product();
          Product( string newName, float newPrice );
      
          // Destructor:
          ~Product();
      
          // Setters:
          void SetName( string newName );
          void SetPrice( float newPrice );
      
          // Getters:
          string GetName() const;
          float GetPrice() const;
      
          private:
          string m_name;
          float m_price;
      }
      
      #endif
      

      Example: Product.cpp

      #include "Product.h"
      
      // Constructors:
      Product::Product()
      {
        m_name = "unset";
        m_price = 0;
      }
      
      Product::Product( string newName, float newPrice )
      {
        SetName( newName );
        SetPrice( newPrice );
      }
      
      // Destructor:
      Product::~Product()
      {
        cout << "Bye." << endl;
      }
      
      // Setters:
      void Product::SetName( string newName )
      {
        m_name = newName;
      }
      
      void Product::SetPrice( float newPrice )
      {
        if ( newPrice >= 0 )
        {
          m_price = newPrice;
        }
      }
      
      // Getters:
      string Product::GetName() const
      {
        return m_name;
      }
      
      float Product::GetPrice() const
      {
        return m_price;
      }
      

      Accessor/Getter functions:

      • Don't take in any input data (no parameters).
      • Return the value of a private member variable (has a return).
      • Should be marked const to prevent data from changing within the function. (This is "read only")

      Mutator/Setter functions:

      • Take in an input value of the new data to be stored (has a parameter).
      • Generally doesn't return any data (no return, void return type).

      Constructor functions:

      • Called automatically when a new object of that class type is created.
      • Can overload.

      Destructor functions:

      • Called automatically when an object of that class type is destroyed/loses scope.
      • Cannot overload.

    • Class objects / instantiating a class object

      Once we've declared a class we can then declare variables whose data types are that class:

      PlayerCharacter bario;
      NonPlayerCharacter boomba;
      

      In this example, bario is a PlayerCharacter object, aka an "instantiation of the PlayerCharacter object".


    • Class inheritance

      A class (called a subclass or a child class) can inherit from another class (called a superclass or a parent class). Doing this means that any protected and public members are inherited by the child class. This can be useful for creating more "specialized" versions of something, storing the shared attributes of a set of items in a common "parent" class.

      Example:

      class Character
      {
        void SetPosition( float x, float y );
        void Move( float xAmount, float yAmount );
      
        protected:
        float x, y;
      };
      
      class PlayerCharacter : Public Character
      {
        public:
        void GetKeyboardInput();
      
        private:
        int totalLives;
      };
      
      class NonPlayerCharacter : public Character
      {
        public:
        void ComputerDecideMove();
      
        private:
        bool attackPlayer;
      };
      

    • Class composition

      Class composition is where a class contains class objects as member variables. This is another form of object oriented design where a class might "contain" traits that could be inherited, but instead encapsulates them into a sub-object.

      Example:

      class MovableObject
      {
        void SetPosition( float x, float y );
        void Move( float xAmount, float yAmount );
      
        private:
        float x, y;
      };
      
      class PlayerCharacter
      {
        public:
        void GetKeyboardInput();
      
        private:
        int totalLives;
        MovableObject mover;
      };
      

  • Review questions:
    1. What is included in a function signature?
    2. Build the function signature given the following specs:
      • The function is naemd "GetValidInput".
      • The function returns an integer.
      • The function takes an int for the minimum valid value, and an int for the maximum valid value.
    3. #include <THING> is used for … ? and #include "THING" is used for … ?
    4. Best practice is to put file guards in every .h file you create. (true/false)
    5. Arguments are … ? and Parameters are … ?
    6. What type of code goes in each type of file? (Function declaration/function definition) vs. (.cpp file/.h file)
    7. Class members are (private/protected/public) by default.
    8. What kind of member function is the following?

      void Pet::XYZ( int age )
      {
        m_age = age;
      }
      
    9. What kind of member function is the following?

      int Pet::XYZ()
      {
        return m_age;
      }
      
    10. This kind of class function will be called automatically when an object of that class type is instantiated…
    11. This kind of class function will be called automatically when an object of that class type is destroyed…

    After reading, do the quiz to check your notes: πŸ”Ž Unit 03 Concept Intro - CS 200 review: Functions and classes (U03.CIN)

πŸ§‘β€πŸ”¬ Lab - CS 200 review: Functions and classes (U03.LAB)

About: This assignment has you working with functions, structs, and classes.

pixel-goals.png Goals:

  • Practice with functions
  • Practice with structs
  • Practice with classes

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Set up: Creating a new branch in your repository

    git-harddrive-opengitbash.png

    First, Open up Git Bash (Windows) or your Terminal (Linux/Mac) from within your repository directory. Make sure you're inside the repository folder, which should be named something like YOURSSO-CLASS.

    Next, create a new branch in the repository with the checkout command: git checkout -b u03_review

    $ git checkout -b u03_review
    Switched to a new branch 'u03_review'
    

    git_newbranch.png

    Now your folder is on the new branch and you can start working on the assignment.


  • Download the starter code

    Download the following starter code and unzip it to your repository folder.

    lab_u02_CPPBasicsReview_extractedfiles.png

    All starter files:

    .
    ├── INFO.h
    ├── main.cpp
    ├── Project_CodeBlocks
    │   ├── FunctionsClasses.cbp
    │   ├── FunctionsClasses.depend
    │   └── FunctionsClasses.layout
    ├── Project_Makefile
    │   └── Makefile
    ├── Project_VisualStudio
    │   ├── FunctionsAndClasses.sln
    │   ├── FunctionsAndClasses.vcxproj
    │   └── FunctionsAndClasses.vcxproj.filters
    ├── u03_Review_FuncClass
    │   ├── P1_functions
    │   │   └── U03_P1_Sale.cpp
    │   ├── P2_struct
    │   │   └── U03_P2_Room.cpp
    │   ├── P3_class
    │   │   └── U03_P3_Employee.cpp
    │   ├── U03_Headers.cpp
    │   └── U03_Headers.h
    └── Utilities
        ├── Colors.hpp
        ├── Helper.cpp
        └── Helper.hpp
    

    Visual Studio users:

    Open the Project_VisualStudio folder and open the FunctionsAndClasses.sln file to get started.

    Code::Blocks users:

    Open the Project_CodeBlocks folder and open the FunctionsClasses.cbp file to get started.

    Build from Terminal/VSCode:

    In the Project_Makefile directory you'll find a makefile you can use to build the project. Use the command make in the directory, and it will generate an executable file that you can run like this: ./CPPReviewClasses_out

    DO NOT CREATE YOUR OWN VISUAL STUDIO OR CODE::BLOCKS PROJECT.

    DO NOT REORGANIZE THE CODE.

    KEEP YOUR REPOSITORY DIRECTORY AND FILES CLEAN!!


  • How the program and tests work

    Once you run the program, you will see two options: Run the AUTOMATED TESTS or run the PROGRAMS manually.

    1. Run AUTOMATED TESTS
    2. Run PROGRAMS
    >>
    

    You can use the AUTOMATED TESTS to verify your work. Tests will either be marked as PASS or FAIL.

    Note that the first "automated" test in the set requires user input once the program is implemented. The test text will describe what inputs it is expecting, the user can set those prices, and then use checkout to finish the test.

    You can also MANUALLY RUN THE PROGRAMS to test them yourself:

    EMPLOYEE LOOKUP
    INDEX     ID        NAME                          WAGE
    --------------------------------------------------------------------------------
    0         123       Ochoa                         16.65
    1         234       Bakalar                       16.65
    2         353       Grubb                         16.65
    
    Enter Employee ID to find index of: 234
    RESULT: Index = 1
    

    To begin with, the functionality won't work properly - you will need to implement the functions themselves. We will step through that in a moment.


  • File: INFO.h

    This file contains a string constant. Replace "Your Name" with your actual name. This is to help me with grading.

    const std::string STUDENT_NAME = "Your Name";
    

    As you work through the coding assignments, if you have any questions or get stuck, feel free to ask for hints from the instructor.


  • File: U03_P1_Sale.cpp

    This code file is under u03_Review_FuncClass/P1_functions/PizzaSlices.cpp.

    ########################################
    MAIN MENU
    
    Current transaction amount: $8.52
    1. Add item
    2. Checkout
    Enter a number between 1 and 2: 2
    
    
    ########################################
    RECEIPT
    
    Transaction total: $8.52
    Tax rate: %9.61
    After tax: $9.34
    RESULT: 9.34
    

    The U03_P1_Sale.cpp file contains several functions that you'll implement for this program.

    void DisplayMenu()

    • This function is responsible for showing the numbered menu, with 1 for "Add item" and 2 for "Checkout".
    • This function does not get any input! Just use cout statements here.

    void FormatUSD( float price )

    • This function takes in the price and formats it as USD. You can set up the precision like this first: std::cout << std::fixed << std::setprecision( 2 );
    • Afterwards, use cout to display "$" and then the price .

    float GetTaxPercent()

    • This function only returns the tax percent. Return a value of 9.61.

    float GetPricePlusTax( float original_price, float tax_percent )

    • Calculate the price plus tax given the inputs.
    • Math: Given original price \(o\) and tax percent \(t\), the new price \(p\) is: \(p = o + ( o \cdot t )\)

    float GetNewPrice()

    This function needs to ask theuser to enter the price of a new item. Use cout and cin statements here, and return the new price they entered afterwards.


    int GetChoice( int min, int max )

    • This function should ask the user to enter a number between min and max.
    • Get the user's input, then use a while loop to check for invalid input: While input is invalid, display an error and have the user enter their choice again.
      • Input is invalid if user_choice < min OR user_choice > max.
    • After the while loop we know the user's input is valid, so we return that value.

    float SaleProgram()

    1. Create a transaction_total float variable and initialize it to 0.
    2. Create a is_running boolean variable and initialize it to true.
    3. Create a program loop: while( is_running ). Within the loop, do the following:
      1. Display the current transaction_total, formatted as USD.
      2. Display the main menu (call the DisplayMenu function).
      3. Get the user's choice (call the GetChoice function, store result in an integer).
      4. Use if/else if statements or a switch statement to check the user's choice:
        • If their choice is 1, then call the GetNewPrice function. Store its result in a float variable, then add the result onto the transaction_total.
        • Otherwise if their choice is 2, then set is_running to false.
    4. After the program while loop, display the RECEIPT. This should include:
      • The transaction_total formatted as USD,
      • the tax rate,
      • the price after tax formatted as USD.
    5. Before the end of the function, return the final_price that was calculated.

    Note that your program output does not need to perfectly match mine. The important thing is the correct information, and readability.

    ########################################
    MAIN MENU
    
    Current transaction amount: $8.52
    1. Add item
    2. Checkout
    Enter a number between 1 and 2: 2
    
    ########################################
    RECEIPT
    
    Transaction total: $8.52
    Tax rate: %9.61
    After tax: $9.34
    RESULT: 9.34
    

    The "automated tests" for this one will require that you manually input information. It will tell you what inputs it is expecting, and then tell you if it passes or not.

    MANUAL TEST, check functionality:
     * DisplayMenu() - main menu shown in loop
     * FormatUSD( p ) - make sure dollar amounts are formatted
     * GetTaxPercent() - Make sure on checkout the tax % shows up
     * GetPricePlusTax( p, t ) - Make sure After Tax price is correct
     * GetChoice( min, max ) - Make sure it disallows numbers less than min / greater than max
     * GetNewPrice() - Make sure function is called to get float values
    --------------------------------------------------------------------------------
    
    Enter prices:     2.25, 4.00, 5.30    <<<<<<<<< This tells you what to enter
    Expected output: 12.66
    

  • File: U03_P2_Room.cpp

    This code file is under u03_Review_FuncClass/P2_struct/U03_P2_Room.cpp.

    ROOM BUILDER
    Enter room length: 15
    Enter room width: 20
    RESULT:
     * width:     20
     * length:    15
     * area:      300
     * perimeter: 70
    

    Within this file, a Room struct has been declared:

    struct Room
    {
      float width;
      float length;
      float area;
      float perimeter;
    };
    

    You will need to implement two functions here:

    Room BuildRoom( float width, float length )

    float width Input parameter The width of a room
    float length Input parameter The length of a room
    Room Return output The built Room struct with all the data set up

    In this function, set the newRoom's member variables, utilizing the input parameters.

    • newRoom.width: Copy the input parameter width.
    • newRoom.length: Copy the input parameter length.
    • newRoom.area: Calculate the area (\(w \cdot l\))
    • newRoom.perimeter: Calculate the perimeter (\(2w + 2l\))

    Make sure the newRoom is being returned at the end of the function.


    void U03_Program2_Program()

    In this function ask the user to enter a room length and a room width. Store their inputs in float variables. Call the BuildRoom function, passing in the data, and storing the returned result in a new Room variable. Finally, use cout to display all of the Room's information.


    Afterwards, the automated tests will pass:

    [PASS]  TEST 1, StudentCode(5.00, 7.00) = [width=5.00, length=7.00, area=35.00, perimeter=24.00]
    [PASS]  TEST 2, StudentCode(3.00, 11.00) = [width=3.00, length=11.00, area=33.00, perimeter=28.00]
    

  • File: U03_P3_Employee.cpp

    This code file is under u03_Review_FuncClass/P3_class/U03_P3_Employee.cpp.

    EMPLOYEE LOOKUP
    INDEX     ID        NAME                          WAGE
    --------------------------------------------------------------------------------
    0         123       Ochoa                         16.65
    1         234       Bakalar                       16.65
    2         353       Grubb                         16.65
    
    Enter Employee ID to find index of: 353
    RESULT: Index = 2
    

    This program has a list of employees, each with an employee_id, name, and hourly_wage. The user can enter in an employee_id in this program, and the associated array index will be returned.

    Within the file is a class Employee declaration and empty member functions. You will implement these and the basic GetEmployeeIndex search function.

    Note: I have all the class functions declared here inside the class declaration, for program simplicity. When implementing class functions, generally the class declaration goes in a .h file and the function definitions go in a .cpp file, and look like int Employee::GetId() const.

    Employee( int new_id, std::string new_name, float new_wage )

    This is the constructor function for the Employee class. Within this function, initialize each of the private member variables (employee_id, name, hourly_wage) to the values passed in as input parameters (new_id, etc.)


    int GetId() const

    Within this function return the value of the private member variable, employee_id.

    Note that this function is const, which means that changes to member variables are not permitted within.


    std::string GetName() const

    Within this function return the value of the private member variable, name.


    float GetWage() const

    Within this function return the value of the private member variable, hourly_wage.


    int GetEmployeeIndex( std::vector<Employee> employee_list, int lookup_id )

    std::vector<Employee> employee_list Input parameter The list of all employees to search through
    int lookup_id Input parameter The employee ID of the person we're looking for
    int Return output The associated index of the employee with the matching ID.
    • Within this function use a for loop to iterate over all the elements of the employee_list, using the index as the looping variable. Within the loop:
      • If the employee at this index's ID matches the lookup_id, then return the current index.
    • At the end of the function, after the for loop, if nothing has been returned yet that means there's no matches - return a -1 here.

    For the for loop:

    • Initialization: size_t index = 0
    • Condition: index < employee_list.size()
    • Update: index++

    To access a certain element of the array: employee_list[index] , then you can also call its functions: employee_list[index].GetId()


    Afterwards, the automated tests should pass:

    [PASS]  TEST 1, StudentCode([11/100/AAA],22/200/BBB],33/300/CCC],44/400/DDD]], 44) = 3
    [PASS]  TEST 2, StudentCode([100/11/aardvark],200/12/bear]], 200) = 1
    [PASS]  TEST 3, StudentCode([100/11/aardvark],200/12/bear]], 444) = -1
    

  • Turning in the assignment

    Once you're done with the assignment, you need to add, commit, and push your changes to the server.

    lab_u02_CPPBasicsReview_gitserver.png

    Save your changes to the server:

    1. Open git bash from within your repository folder on your harddrive. git-harddrive-opengitbash.png
    2. Use git status to review what files have changed since your last commit. git_status.png
    3. Use git add . to add all changed files to a changeset. git_add.png
    4. Use git commit -m "MESSAGE" to create a snapshot of your current changes. (Replace "MESSAGE" with something more descriptive.) git_commit.png
    5. Use git push -u origin u03_review to push all your commits to the GitLab server, on the u03_review branch. git_push.png

    You can continue making changes and do additional add, commit, and push commands as well.

    Get everything ready for next assignment:

    1. Use git checkout main to change back to the main branch to prepare for the next assignment. (You can always use git checkout u03_review to return to this branch and code.)
    2. Use git pull to pull latest from the server. (This might happen if the instructor adds new starter code or makes modifications.)

    git_newbranch.png

    Create a Merge Request on the GitLab website:

    After pushing your changes to your branch, the GitLab webpage will show an update:

    gitlab_web_mergerequest1.png

    Click the blue "Create merge request" button.

    gitlab_web_mergerequest2.png

    Give your Merge Request a descriptive name, such as the unit the assignment belongs to.

    At the bottom of the page, click on the blue "Create merge request" button.

    gitlab_web_mergerequest3.png

    Your merge request will now be created. You don't need to do anything further on this page.

    • Approve button: The instructor may mark the merge request as approved.
    • Merge button: You won't be able to press the Merge button; the instructor will merge the code once they've reviewed your work and approved it.

    Turn in the Merge Request URL on Canvas:

    Once you've created a merge request, copy the URL of the merge request on the GitLab.com webpage:

    merge_request_url.png

    Back on the Canvas page, locate the assignment and click the link:

    canvas_assignment.png

    Then click on "Start Assignment":

    canvas_start_assignment.png

    Paste in the URL to your Merge Request in the "Website URL:" textbox, then click "Submit Assignment" to finish the turn in.

    canvas_submit_mergerequest.png

    "What happens if I make mistakes?"

    lab_u03_CPPClassReview_Mistakes.png

    If you make mistakes, I will be able to mark certain lines of code in the merge request and add a comment as to what isn't correct, similar to how your coworkers would also review your code on the job.

    After receiving feedback, you can go back and make updates to your code and resubmit it. You will need to do the following:

    1. Check out the branch you made the changes on: git checkout BRANCHNAME
    2. Make the fixes to the code.
    3. Make a new add/commit/push:
      1. git add .
      2. git commit -m "MESSAGE"
      3. git push -u origin BRANCHNAME
    4. The merge request automatically gets updated; re-submit the merge request link in Canvas to mark it as ready to be re-graded.

πŸ‹οΈ Exercise - Debugging functions and classes (U03.EXE)

Answer these questions on the related Canvas assignment to check your work.

  1. It's important to know what I'm talking about when I use certain terms. Please identify what each of these pieces of code are called. Identify whether each of the following is a function declaration, function definition, or a function call.

    • (Declaration / Definition / Call?)

      void DisplayMenu()
      {
        cout << "1. Save" << endl;
        cout << "2. Load" << endl;
      }
      
    • (Declaration / Definition / Call?)
    DisplayMenu();
    
    • (Declaration / Definition / Call?)
    void DisplayMenu();
    
  2. Given the following function definition:

    int Sum( int a, int b )
    {
      return a + b;
    }
    

    And elseware in the program the function is called:

    int num1, num2;
    
    cout << "Enter num1: " << num1 << endl;
    cin >> num1;
    cout << "Enter num2: " << num2 << endl;
    cin >> num2;
    
    int result = Sum( int num1, int num2 );
    cout << "Result: " << result << endl;
    

    When trying to build the program, we get compile errors: expected primary-expression before 'int'. It points to the "int result = Sum( int num1, int num2 );" line. The error isn't very descriptive, but it's complaining about an "int" keyword. What is the error with the highlighted line of code?

  3. The following functions are defined:

    int Sum( int a, int b )
    {
      return a + b;
    }
    
    float Sum( float a, float b )
    {
      return a + b;
    }
    

    And is used elseware in the program. But when building, we get an error: error: call of overloaded β€˜Sum(int&, float&)’ is ambiguous| What is causing this error to occur?

  4. The following program has been started:

    #include <iostream>
    using namespace std;
    
    struct Vector
    {
      float x, y;
    }
    
    int main()
    {
      Vector my_vec;
    
      return 0;
    }
    

    But even at this early point, there is a build error: error: expected β€˜;’ after struct definition| What is the cause of this build error?

  5. In "Student.h", this class is declared:

    class Student
    {
      public:
      void Setup( string newName );
    
      private:
      string m_name;
    };
    

    And in "Student.cpp", this function is defined:

    void Setup( string newName )
    {
      m_name = newName;
    }
    

    However, a build error occurs: In function β€˜void Setup(std::string)’:| error: β€˜m_name’ was not declared in this scope; did you mean β€˜tzname’?| Ignore the "tzname" part of the error message, it doesn't help us. There is something missing from the code which is causing this build error. What is it?

UNIT 04: Debugging and testing

πŸ“Š Presentation - Debugging and testing (U04.PRES.202401CS250)

πŸ“–οΈ Reading - Debugging and testing (U04.READ.202401CS250)

reading_u04_DebugTest_tests.png

  • Testing: Validating your work

    How do you know that your program actually works?

    Sure, you may have manually tested your code, running the program and typing in test data and checking the output.

    After a while, manual testing becomes a chore. Maybe you start entering "asdjhfklq" as the test data and just make sure nothing breaks while running the program.

    But are you sure your program runs, doesn't crash, and gives the correct output for all reasonable cases?

    reading_u04_DebugTest_hopeitworks.png

    A skill that is good to develop as a software developer is how to test and validate your work. You can break down parts of your code into little transactions of "inputs" and "outputs", and then develop test cases.

    Test case: A test case is a single test. You specify some input(s) that you will give your program, and the expected output(s) it should return.

    When you run the actual program with your inputs, it will return actual output(s). Compare the actual output with the expected output to validate whether the program worked as expected.

    Test cases can be built without any of the program built so far. In fact, it can be handy to write your tests ahead of time so you have a better understanding of how things are supposed to work.

    Example: Bank withdrawals In this example, the program keeps track of a bank balance and the amount the user wants to withdraw, but it shouldn't let the balance fall below 0.

    Test case Input(s) Expected output Actual output
    1 balance = 100, withdraw = 10 balance is now 90 (enter after testing)
    2 balance = 0, withdraw = 100 can't withdraw!  
    3 balance = 100, withdraw = 100 balance is now 0  

    You would make a list of inputs and what should be the result for each case, and then run your program and check each scenario. If something doesn't match, it could mean that there's an error in a calculation somewhere, or other logic in the program. (And yes, sometimes tests can be wrong, too.)


    • The role of testing in software development

      Companies that write big software products will often have multiple types of testing that they do to ensure the validity of their software.

      QA (Quality Assurance) people generally will be responsible for writing test cases and running through *manual tests to validate the software works as intended. They might also write test scripts that automate going through the UI of the software, or automate other parts of testing.

      Software Engineers will regularly need to be able to write unit tests along with whatever features they're working on in order to validate that their new feature works as intended and they will run older unit tests to ensure that their new feature doesn't break anything already there.

      Testing is an important part of software development and, from a student perspective, it is also a handy tool in ensuring that your programming projects work as intended before turning them in.


    • Designing tests
    • Test cases

      When writing out test cases, it's important to try to think of valid inputs and invalid inputs that could be entered in, and account for how the program will respond to that input.

      There are three main pieces to defining a test case:

      1. Inputs entered in.
      2. What the expected output of the program is.
      3. What the program actually did.

      By comparing the expected result with the actual result, you can validate whether your program is working as intended. If they match - the test passes. If they don't match - the test failed.

      If a test fails, you can investigate the expected output and the actual output to help you get an idea of perhaps where it went wrong - perhaps a formula is wrong somewhere.

      reading_u04_DebugTest_validation.png

      Example: Calculate price plus tax When ringing up a product at a store, there will be a price for that product and a sales tax that gets added onto it. Using a test case can help us make sure that the program's calculations are correct.

      Let's say that the programmer implemented the formula like this: total = price + tax

      We could validate the program's actual output vs. the expected output, and see that there's an error.

      Test case Input(s) Expected output Actual output
      1 price = $10.00, tax = 0.09 total = $10.90 total = $10.09 (FAIL)
      2 price = $5.45, tax = 0.06 total = $5.78 total = $5.51 (FAIL)
      3 price = $2.25, tax = 0.095 total = $2.46 total = $2.35 (FAIL)

      What is the problem with the formula? Well, we don't just add tax as a flat number; "0.09" is supposed to represent 9%, not 9 cents!

      The correct formula would be: total = price + (price * tax)


    • Manual testing

      reading_u04_DebugTest_dontexplode.png

      You can manually test your program effectively by documenting your test cases and running through each of them each time you decide to test your program. Of course it can take a while, but by using your test cases instead of entering in gibberish or only testing one scenario, you make sure your program works, even as you keep adding onto it.


    • Automated unit tests

      reading_u04_DebugTest_inputoutput.png

      Our test cases can be a handy roadmap to follow when running through your program's various features and validating it all works manually, but you can speed up the process by letting the computer do these checks for you.

      After all, when you manually validate outputs yourself, you're basically asking yourself,

      "If the actual output DOES NOT match the expected output, then the test failed."

      A test that only validates one function is known as a unit test, testing the smallest possible unit of a program. You could also write automated tests that checks several functions, but unit tests are good to validate each function on its own, independently of the rest.

      • Unit tests in the real world

        "Tests just mean more code to maintain. We don't keep tests."

        I was told this at a job interview once. I didn't take the job.

        At companies that maintain tests, the software engineers will usually write unit tests to validate their work as they're adding in new functionality or making modifications to the existing code.

        This means that ideally each feature of the program has one or more test to validate that it works.

        This also means that, when adding or modifying features, you can run the entire test suite to make sure that all features still work.

        Many software companies have something called Continuous Integration (CI), which is an automated system that kicks off a build of the software and runs all the tests each time a developer commits code to the repository.

        Example: Area function Let's say we have a function with the header

        int Area( int width, int length )

        and we don't necessarily know what's in it (we don't need that to test). We can write a series of test cases with inputs we specify and outputs we know to be correct in order to check the logic of the Area function…

        Test case Input(s) Expected output Actual output
        1 width = 10, length = 20 120  
        2 width = 2, length = 5 10  

        …and so on.

        We could use this to manually test by calling Area and checking the output it gives you, but you could also write a function to validate it for us

        reading_u04_DebugTest_tedious.png

        Here's some example code of a function that tests Area() for us.

        void Test_Area()
        {
          // Variables used for each test
          int width, length;
          int expectedOutput;
          int actualOutput;
        
          // Test 1
          width = 10;
          length = 20;
          expectedOutput = 200;
          actualOutput = Area( width, length );
        
          if ( actualOutput != expectedOutput ) {
            cout << "Test failed!"
                 << "\n width: " << width
                 << "\n length: " << length
                 << "\n expected output: " << expectedOutput
                 << "\n actual output: " << actualOutput 
                 << endl;
          }
          else {
            cout << "Test 1 passed" << endl;
          }
        }
        

        You can add as many tests as you'd like inside one function, testing different permutations of inputs against outputs to make sure that any reasonable scenarios are covered. To test, just call the function in your program, and check the output:

        Test failed!
        width: 10
        length: 20
        expected output: 200
        actual output: 30
        

        If the test fails, you can cout your variables' values to help you figure out what might be wrong with the logic.

      • Other automated tests

        Unit tests test the smallest unit of code - a single function.

        (It should be the smallest "unit", if your function is really long you should probably split it up!)

        There are other types of automated tests as well, and software tools to help developers create and run those tests.

        An integration test is when you test multiple units together. A unit might work fine on its own, but when multiple units are put together, the resulting system may not work as intended.

        (Look up "integration test unit test" online for some good gif examples. :))

        reading_u04_DebugTest_integrationtest.png

        Tests can also be written to test user interfaces of a program, running a script to drive the cursor around the screen, or defining text that should be typed into fields, running through features of the program from the user's perspective, and validating that data updates and is displayed when some action is performed from the UI.


  • Using debugging tools

    errorinmain.png

    Utilizing debugging tools are an important part of software development. Especially as you begin working in large codebases on the job, being able to step through the execution of a program to find variable values, how the program flows, and where exceptions are thrown is something you will have to do frequently.

    The lab contains more of the steps for learning these tools with your IDE, but here is some general information for now.


    Breakpoints

    reading_u04_DebugTest_setbreakpoint.png

    A breakpoint can be toggled in your code by clicking to the left of the code screen. The exact placement differs between IDEs, but usually a red circle pops up once clicked to show that the program execution will stop here once we reach it.

    You can set breakpoints in your program at various places to begin stepping through to see where the program goes, if it's "flowing" the way you expect it to, and to navigate into other function calls as they're hit.

    reading_u04_DebugTest_stepover.png

    The "next line" or "step over" button will let you move to the next line of code execution. If your pause line is at a function call, it will not enter the function, and just continue on the next line after the function call is done.

    reading_u04_DebugTest_stepinto.png

    The "step into" button will take you to inside a function if you're currently paused on a line that is a function call.


    Watch/Local/Auto window

    reading_u04_DebugTest_watchwindow.png

    Watch, Local, and Auto windows do similar things - they all display a variable's name and its current value (while paused in the program execution).

    Visual Studio has (and other IDEs may have) an auto view, which shows variables that are around the current statement we're paused on. The locals view shows variables that are currently in scope. (Information from https://learn.microsoft.com/en-us/visualstudio/debugger/autos-and-locals-windows?view=vs-2022)


    Call stack

    reading_u04_DebugTest_callstack.png

    The call stack will show you all the functions that have been called, leading up until the point where you are currently paused at. The current function is listed on the top, with the function immediately below it being the one that made the call to the function you're in. The function listed at the bottom is the "oldest" function, or the first one called, usually main().


  • Review questions:
    1. Given some input and expected output defined, a test would pass if…
    2. Let's say I've written a program that takes in two numbers as inputs and returns the sum as the output. For our first test case, let's say the inputs are 2 and 3. What would the expected output be?
    3. Let's say you need to write a program that will update a person's account balance after they deposit some money. What might be the input(s) / output(s)?
    4. Let's say your program is crashing somewhere. How might setting breakpoints be useful?
    5. We are paused in a program at this point:

      int a = 10;
      int b = 20;
      int result = Sum( a, b ); << Break here
      cout << result << endl;
      

      If we hit the step over button, what will the next line the debugger is paused at be?

    6. We are paused in a program at this point:

      int a = 10;
      int b = 20;
      int result = Sum( a, b ); << Break here
      cout << result << endl;
      

      If we hit the step into button, what will the next line the debugger is paused at be?

    7. Given the following call stack:

      Product::GetPrice()
      Store::PurchaseProduct()
      Store::MainMenu()
      main()

      What function are we currently paused in?

    8. Given the following call stack:

      Product::GetPrice()
      Store::PurchaseProduct()
      Store::MainMenu()
      main()

      Once the current function returns, which function will then be at the top of the call stack?

    After reading, do the quiz to check your notes: πŸ”Ž Unit 04 Concept Intro - Debugging and testing (U04.CIN)

πŸ§‘β€πŸ”¬ Lab - Debugging and testing (U04.LAB)

pixel-goals.png Goals:

  • Practice with debugging tools:
    • Watch window
    • Call stack
    • Breakpoints
    • Step
  • Practice writing unit tests

pixel-turnin.png Turn-in:

  • 1. For the DEBUG assignment you'll be creating a text document to log your findings. (You can save it in your repository to submit via the merge request as well.)
  • 2. You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Set up: Creating a new branch in your repository
    1. Navigate to your class repository folder. Right-click the empty space and select GitBash Here (Windows). (Other platforms: Open this directory in your Terminal.)
    2. Create a new branch: git checkout -b u04_debugtest

  • Download the starter code

    Download the following starter code and unzip it to your repository folder.

    All starter files:

    .
    ├── Part1_Debug
    │   ├── engine
    │   │   ├── Helper.cpp
    │   │   ├── Helper.h
    │   │   ├── Log.cpp
    │   │   ├── Log.h
    │   │   ├── PpmImage.cpp
    │   │   ├── PpmImage.h
    │   │   ├── ScreenDrawer.cpp
    │   │   └── ScreenDrawer.h
    │   ├── Game.cpp
    │   ├── Game.h
    │   ├── images
    │   │   ├── food.ppm
    │   │   ├── kansascity.ppm
    │   │   ├── medical.ppm
    │   │   ├── olathe.ppm
    │   │   ├── overlandpark.ppm
    │   │   ├── raytown.ppm
    │   │   ├── safehouse.ppm
    │   │   ├── scavenger.ppm
    │   │   ├── zombie1.ppm
    │   │   └── zombie2.ppm
    │   ├── main.cpp
    │   ├── Player.cpp
    │   ├── Player.h
    │   ├── Project_CodeBlocks
    │   │   ├── log.txt
    │   │   ├── ZombieApoc.cbp
    │   │   ├── ZombieApoc.depend
    │   │   └── ZombieApoc.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   └── Project_VisualStudio
    │       ├── log.txt
    │       ├── ZombieApoc.sln
    │       ├── ZombieApoc.vcxproj
    │       └── ZombieApoc.vcxproj.filters
    └── Part2_Test
        ├── Functions.cpp
        ├── Functions.h
        ├── Project_CodeBlocks
        │   ├── WritingTests.cbp
        │   ├── WritingTests.depend
        │   └── WritingTests.layout
        ├── Project_Makefile
        │   └── Makefile
        └── Project_VisualStudio
            ├── WritingTests.sln
            ├── WritingTests.vcxproj
            └── WritingTests.vcxproj.filters
    
    • Please use the provided Visual Studio or Code::Blocks project files, don't make your own!
    • If you want to build the code from the terminal, go to the Project_Makefile directory and use the command make to generate an out file.

    DO NOT CREATE YOUR OWN VISUAL STUDIO OR CODE::BLOCKS PROJECT.

    DO NOT REORGANIZE THE CODE.

    KEEP YOUR REPOSITORY DIRECTORY AND FILES CLEAN!!


  • Part 1: Debug

    For this assignment you'll run an existing program. You don't need to do any programming - you will be utilizing the debugging tools of your IDE to answer the questions. You will need to create a text document (a MS Word / LibreOffice Write / Google Docs document).


    • Running the game as-is - Learning the controls

      lab_u04_DebugAndTest_gamerunning.png

      Run the game as-is and play a few rounds of it. You can choose to scavenge in the area you're currently at, or travel to another location. For this step, you're just getting used to how the game controls.


    • Using breakpoints - Pause program execution at some line

      Task:

      1. Set breakpoints at the start of Player::Player() in Player.cpp, and at the start of Game::Game() in Game.cpp.
      2. In the watch window, add the following (for the Player class):
        1. food
        2. maxHealth
        3. health
        4. location
      3. In the watch windwo, add the following (for the Game class):
        1. m_done
        2. m_day

      Observe the current values of those variables. Refer to the following documentation on the steps to do this.

      Video: https://youtu.be/BNaTN9KWmu4

      • Setting the breakpoint:

        Step 1: Set a breakpoint at the start of Player::Player():

        Player::Player()
        {
            food = 5;
        

        Set a breakpoint at the start of Game::Game():

        Game::Game()
        {
            m_day = 1;
        

        To set a breakpoint, you can locate a line of code and click on a region in your editor next to the line number that will enable a breakpoint. A breakpoint is a debugging tool that will cause the program execution to pause when that line of code is reached. While the program is paused, you can investigate values and step through the program flow one line at a time.

        lab_u04_DebugAndTest_00setbreakpoint.png

        Depending on your IDE you might need to click before or after the line number to set the breakpoint. You can also toggle breakpoints through the menu or keyboard shortcuts:

        IDE Menu Shortcut Image
        Visual Studio Debug, Toggle breakpoint F9 visualstudio_breakpoint.png
        Code::Blocks Debug, Toggle breakpoint F5 codeblocks_breakpoint.png
        XCode     xcode_breakpoint.png

        Next you'll need to run your program in debug mode:

        IDE Toolbar Menu Shortcut
        Visual Studio visualstudio_debug_toolbar.png visualstudio_debug_dropdownmenu.png F5
        Code::Blocks codeblocks_debug_toolbar.png codeblocks_debug_dropdownmenu.png F8
        XCode xcode_debug_toolbar.png    

        The program will build (if it hasn't already) and start running the program. When the program starts, it will hit the breakpoint and pause execution. Go back to your IDE to view where your program has paused.

      • Opening the variable investigation panes:

        At the bottom of your screen should be some debug panels. If they don't show up, you can usually open these under the Debug drop down menu.

        IDE Steps
        Visual Studio Debug, Windows, Watch window
        Code::Blocks Debug, Debugging wnidows, Watches
        XCode View, Debug area, Activate console

        Now you should have the watch windows open, which is a table that displays a Name (variable name) column and a Value column.

        lab_u04_DebugAndTest_01locals.png

      • Enter the names of the variables we want to track:

        In these watch windows, type in the names of the following variables:

        • Player member variables:
          • food
          • maxHealth
          • health
          • location
        • Game member variables:
          • m_day
          • m_done

        While the program is still paused, we will use the step features to step through each line of code one at a time and watch the variables as they're assigned values.

        Use the Step Over or Next Line to step down one line at a time until you get to the end of the function. As you go down each line, notice how the variable value changes in the watch window once its declaration line is finished.

        lab_u04_DebugAndTest_02stepover.png


    • Step into, step out - Investigating program flow

      Task: Set a breakpoint at the start of Game::Menu_Scavenge() and then use the Continue button to resume program execution. (Or if you closed the program, run it again and enter a name.)

      Select the "1. Scavenge here" option in the game and make sure the program execution pauses within this function.

      void Game::Menu_Scavenge()
      {
          bool actionSuccessful = true;
          int random = rand() % 5;
          RandomEvent_Scavenging( random );
      }
      

      Use the Stepover/Next line button to move the cursor to the line with RandomEvent_Scavenging( random ); Take note of the value of the random variable.

      We are now sitting at a function call. We can go into the function call using the Step into option.

      lab_u04_DebugAndTest_02stepinto.png

      This will take us into the function.

      void Game::RandomEvent_Scavenging( int event )
      {
          ScreenDrawer::Fill( '~', "black", "red" );
          ScreenDrawer::DrawWindow( "", 1, 1, 78, 13 );
          ScreenDrawer::Set( 3, 2, "DAY: " + Helper::ToString( m_day ), "red", "black" );
      
          int mod;
          if ( event == 0 )
          {
              mod = rand() % 3 + 2;
              ScreenDrawer::Set( 3, 4, "You find a stash of food.", "green", "black" );
      // ... etc ...
      

      This function takes the event variable and figures out what kind of event to make happen in the game.

      Use the Step Over / Next Line button to see which if/else if statement is executed: event = 0=? event = 1=? And so on…

      Take note of which kind of event got executed:

      • You find a stash of food.
      • A zombie surprises you!
      • You find some medical supplies.
      • Another scavenger ambushes you!
      • You don't find anything.

      Don't stop the program execution, we are going to keep working with where we've paused.


    • Call Stack - Looking at the function call history

      Another important debugging tool is the Call Stack, which tells you the order of functions called to get to where the program is currently at.

      Task: Use the Call Stack to take note of what functions were called leading up to your Game::RandomEvent_Scavenging breakpoint.

      The call stack should look like this:

      lab_u04_DebugAndTest_03callstack.png

      If the call stack pane isn't showing up, you can open them through the menus:

      IDE Menu
      Visual Studio Debug, Windows, Call Stack
      Code::Blocks Debug, Debugging windows, Call Stack

      The top-most function displayed is the one currently running, and all functions below it are functions that were active previously. If you step all the way down, you can trace all the functions that were called to get to where we're at.

      Task: Set a breakpoint in the Game::EndOfDayStats() function and use the Call Stack to take note of what functions were called leading up to it.

      Task: Set a breakpoint in the Player::Player() function (in Player.cpp) and use the Call Stack to take note of what functions were called leading up to it. You will probably have to close and restart the game to hit this breakpoint.


    • When to use debugging tools…?

      Debugging tools are extremely helpful and the better you get at utilizing them to diagnose your programs' issues, the more well prepared you will be for software development in the "real world".


  • Part 2: IDE navigation techniques

    Don’t close the zombie game yet – now we’re going to use various IDE features in this project.

    Take a screenshot once you've completed each one and paste it into your document.

    • Viewing code split-screen

      In most IDEs you can click-and-drag a code window (via its tab) to another part of the screen, allowing to have a side-by-side view of two (or more) different code files at once.

      Visual Studio:

      Click and drag the tab to the center of the screen. Then, a placement box will show up. Drag the tab to one of these 5 options for different views.

      lab_u04_DebugAndTest_04draggingA.png

      Code::Blocks:

      Click and drag the tab to an edge of the code window. A slight highlight will appear in the place where it will be placed. Let go of the mouse button to place the tab to the side.

      lab_u04_DebugAndTest_04draggingB.png

    • Go to definition

      Go to the .h file and right-click on one of the functions, then select the Go to definition (VS) or Go to implementation (CB) to go directly to the definition of that function.

      lab_u04_DebugAndTest_05definition.png

    • Go to declaration

      Right-click on a function call somewhere, and select Go to declaration to go to where the function is declared.

      lab_u04_DebugAndTest_06declarations.png

    • Find references of

      Right-click on any function and select Find references of and you will get a search result with every place where the function is used throughout the program, including function calls.

      lab_u04_DebugAndTest_07references.png

    • Indenting/unindenting chunks of code

      Highlight a chunk of code and use "tab" to indent it all forward, or "shift+tab" to un-indent it all backward.

      lab_u04_DebugAndTest_08tab.png

    • Autoformatting

      When your indentation gets messed up you can have the IDE automatically format it for you.

      lab_u04_DebugAndTest_09format.png

      IDE Steps
      Visual Studio Edit, Advanced, Format Document
      Code::Blocks Right-click in the code window, select Format use AStyle
    • Comment/uncomment code blocks

      Highlight a section of code. You can comment out a whole block at once, or uncomment out a block, in the following ways:

      lab_u04_DebugAndTest_10commentout.png

      IDE Comment-out Uncomment-out
      Visual Studio Hold CTRL, then press K and then C Hold CTRL, then press K and then U
      Code::Blocks Press CTRL+SHIFT+C Press CTRL+SHIFT+X

  • Part 3: Test

    This program contains four simple functions and four test functions:

    1. int GetArea( int width, int length );
    2. void Test_GetArea();
    3. int GetStringLength( string text );
    4. void Test_GetStringLength();
    5. int GetPerimeter( int width, int length );
    6. void Test_GetPerimeter();
    7. float Withdraw( float balance, float amount );
    8. void Test_Withdraw();

    We are going to approach this from a Test Driven Development standpoint where we will write our TESTS before implementing the functionality. This way, we ensure our tests "work" because they first fail - then, when we implement the functionaltiy, the tests passing should be how we verify our logic is correct.

    • Set 1: Stub functions

      These functions contain placeholder information being returned. They return data because some C++ compilers will complain if a non-void function doesn't have a return statement. Before you implement the function itself, we will be implementing the unit tests for each function first.

      Task: Implement void Test_GetArea() and void Test_GetStringLength() first, then implement int GetArea( int width, int length ) and int GetStringLength( string text );

      Read the following instructions for a guide.

      • GetArea

        First off we need to come up with test cases - at least two for these simple functions that just do computations. Why two? Because if you only have one test that checks "The area of a 2x5 yard is 10", then implementing the function as return 10 would cause it to pass… we need at least two tests to make sure the function has proper logic.

        Here are some test cases we can use for GetArea:

        # Inputs Expected outputs
        1 width=5, height=3 returned area is 15
        2 width=7, height=11 returned area is 77

        So… how do we implement this in code?

        We have two inputs and an expected output, and in the program itself we will have an actual output. We can create variables to represent each of these pieces of information:

        • int input_width = 5;
        • int input_height = 3;
        • int expected_result = 15;
        • int actual_result;

        To get a value for the actual_result, we just call the function itself, passing in our inputs:

        actual_result = GetArea( input_width, input_height );

        We can tell if the test passed if the actual_result matches the expected_result. Or, the test failed if they do not match.

        It would be useful to output the results to the console as well, so we can write out something like this:

        if ( actualResult != expectedResult ) { result = "[FAIL]\t"; }
        else                                  { result = "[PASS]\t"; }
        
        cout << result << testName << endl;
        cout << "* input_width:     " << input_width << endl;
        cout << "* input_height:    " << input_height << endl;
        cout << "* expectedResult:  " << expectedResult << endl;
        cout << "* actualResult:    " << actualResult << endl;
        cout << endl;
        

        If you run the program now, we should get a nice print out of the test result:

        [FAIL]  GetArea - pass in 5 and 3 and should get 15 back
        * input_width:     5
        * input_height:    3
        * expectedResult:  15
        * actualResult:    -1
        

        Implement the second test case in the code before continuing.

        Once they're both implemented, you can now implement the int GetArea( int width, int height ) function. To calculate the area of a rectangle, we use the formula:

        area = width x height

        (But write it in C++ code :)

        Implement the function and once run the tests should now pass. Here's how I've formatted my tests:

        --------------------------------------------------------------------------------
        Test_GetArea
        --------------------------------------------------------------------------------
        [PASS]  GetArea - pass in 5 and 3 and should get 15 back
        * input_width:     5
        * input_height:    3
        * expectedResult:  15
        * actualResult:    15
        
        [PASS]  GetArea - pass in 7 and 11 and should get 77 back
        * input_width:     7
        * input_height:    11
        * expectedResult:  77
        * actualResult:    77
        

      • GetStringLength

        Implement the following test cases as unit tests:

        # Inputs Expected outputs
        1 text="Hello" returned length is 5
        2 text="abc def" returned length is 7

        Once the tests are implemented and they fail when you run the program, now implement the GetStringLength function. (Hint: You can get the length of the text string with text.size().)


    • Set 2: Broken functions

      These functions have already been implemented, but with incorrect logic. You're going to write unit tests before doing any fixes to help ensure that your fixes are valid once implemented.

      Task: Implement void Test_GetPerimeter() and void Test_Withdraw() first, then fix the functions -int GetPerimeter( int width, int length ) and float Withdraw( float balance, float amount )

      Read the following instructions for a guide.

      • GetPerimeter

        For this function, come up with your own TWO test cases and implement them as unit tests. After implementing the unit tests, fix the function logic.


      • Withdraw

        For this function, come up with your own TWO test cases and implement them as unit tests. After implementing the unit tests, fix the function logic.


  • Turning in the assignment

    Once you're done with the assignment, you need to add, commit, and push your changes to the server.

    lab_u02_CPPBasicsReview_gitserver.png

    Save your changes to the server:

    1. Open git bash from within your repository folder on your harddrive.
    2. Use git status to review what files have changed since your last commit.
    3. Use git add . to add all changed files to a changeset.
    4. Use git commit -m "MESSAGE" to create a snapshot of your current changes. (Replace "MESSAGE" with something more descriptive.)
    5. Use git push -u origin u04_debugtest to push all your commits to the GitLab server, on the u04_debugtest branch.

    You can continue making changes and do additional add, commit, and push commands as well.

    Get everything ready for next assignment:

    1. Use git checkout main to change back to the main branch to prepare for the next assignment. (You can always use git checkout u04_debugtest to return to this branch and code.)
    2. Use git pull to pull latest from the server. (This might happen if the instructor adds new starter code or makes modifications.)

    Create a Merge Request / turn in on Canvas:

    1. Navigate to your GitLab repository webpage.
    2. Click on the blue "Create merge request" button.
    3. Add a descriptive Title, then click "Create merge request" at the bottom of the page.
    4. Copy the URL of this merge request.
    5. Find the assignment on Canvas. Click "Start Assignment".
    6. Paste in the Merge Request URL here. Click "Submit Assignment".

🧠 Tech Literacy - Git (U04.TEC)

  • Without Source Control…

    Let's imagine a workplace that doesn't use any source control software. Multiple people have shared files on a network drive and can edit them at any time. Two different users are working on a file at the same time, but what happens when they save?

    tec_Git_SharedFile.png

    If User A saves their file first and then User B saves their file second, the network file will only be the last saved version - User B's version. It won't automatically merge the files together, meaning User A's work gets lost. Imagine working on a lot of code and having this happen - making sure work doesn't get overwritten would be a nightmare to manage manually.


  • Types of Source Control software…

    Various source control systems have been developed over the years to help manage versioning, storing, and merging source code. There are pros and cons to each one, but the most common ones these days are probably Git and TFS (Team Foundation Server). Others you might hear about are SVN (Subversion) and Mercurial. We will be specifically talking about Git here.


  • Basics of Git

    tec_Git_SourceControl.png

    There are two main parts of source control: The Server, where the code is all brought together and stored, and the Clients - or, the user PCs. A person, company, or other organization can set up a Server on their own computers or they can utilize a service like GitLab, GitHub, SourceForge, Bitbucket, or other services to host their code for them.

    Either way, for a project, a repository is created on the server. This is basically a folder, except the Git program is told that it is a special Git folder, and the Git software knows how to process different commands to help users add, update, and manage the code stored within.


    • The clone command

      tec_Git_Clone.png

      When a new developer joins the team, or a new project is created to work on, the repository will be created and then each teammate on that project can use the clone command, which looks like this:

      git clone URL.git
      

      This command tells the Git program to access the git URL given and download the repository folder. Git handles the download and setting up the folder and files within, and on the user's local computer they'll see a folder with the repository name. The clone command only needs to be done once per computer.


    • Branches

      tec_Git_Branch.png

      In our project repository, by default there will be a main branch (Note: sometimes the default is a master branch). The main branch should represent the current, stable version of the program. The goal is to make sure the code on main can build.

      When somebody wants to add a new feature or is working on a bug, they will create a new branch to work on. This starts at the main branch, but gives the user their own workspace. They can make many backups of their code on their own branch without affecting the main branch, and without breaking code on other peoples' machines.

      The checkout command is used with the -b flag to create a new branch:

      git checkout -b BRANCHNAME
      

      Even if you're on a different branch, you can switch back to main, or other branches, any time with git checkout BRANCHNAME. On your local computer you may see files disappear and changes be undone, but those changes will come back once you're on the branch where you created those files/changes.


    • The status command

      While working on a branch you can use the git status command to see a list of files that you've added, removed, and changed since the last commit. Items marked in red haven't yet been marked as "to back up", and items marked in green are ready to be backed up by using the commit command. To mark a file as "ready to backup", we use the add command.

      git status
      

    • The add command

      tec_Git_Add.png

      When you're ready to make a backup of your work, you can use a git add command to add your files. Sometimes you only want to add one or two files, sometimes you want to add only a certain type of files, and sometimes you want to add everything that has been updated.

      Add one specific file:

      git add FILENAME
      

      Add all files ending with ".cpp":

      git add *.cpp
      

      Add all changed files:

      git add .
      

    • The commit command

      tec_Git_Commit.png

      After using the add command, we "bundle" all the changes together in one commit. We use the commit command, and a message is required - this is to help us keep track of what we changed where. Try to add a descriptive message so you can track your changes later if you need to.

      Commits are initially only stored on your own computer, they won't be shown on the repository server until you push everything.

      git commit -m "Fixed bug"
      

    • The push command

      tec_Git_Push.png

      After you have one or more commits ready to go, you can use the push command to send your changes to the server under your branch. This will allow others to view the changes you've made, while still keeping your code separate from the main branch… for now. The overall goal is to eventually get it merged into the main branch, but we aren't doing that quite yet.

      git push -u origin BRANCHNAME
      

    • The pull command

      tec_Git_Pull.png

      Besides just pushing your own changes to the server, you will periodically want to use the pull command to get any changes from the server - such as completed features from other developers, or merged in code.

      The pull will grab the latest version of files, so you want to make sure to first add and commit your changes before using pull.

      git checkout main
      git pull
      

    • Merge requests

      gitlab_web_mergerequest3.png

      The Merge Request is the final part of a work-in-progress feature's life. You create a merge request, and it will show a list of all changes you've made under the changes tab. Usually, you would send this Merge Request to other developers to review your work, but for this class you'll submit your Merge Request as your assignment turn-ins.

      tec_Git_MergeRequest.png

      Other developers can leave comments on specific lines of code in the merge request. Those reviewing your merge request should look out for obvious potential errors, typos, logical fallacies, and so on. If comments are left asking you to fix something, you can go back to your computer, make changes, then add/commit/push again and ask others to re-review your updates.

      Finally, once it has been approved, the code can be merged to the main branch. In this class, the instructor will merge the code. In the work world, someone like the system architect or your team lead might handle this step.


      There's no assignment to turn in for this item.

UNIT 05: Algorithm efficiency

πŸ“–οΈ Reading - Algorithm efficiency (U05.READ.202401CS250)

  • Introduction: Algorithm Efficiency (and why we care)

    Processing power continues increasing as time moves forward. Many things process so quickly that we barely notice it, if at all. That doesn't mean we can just write code as inefficiently as possible and just let it run because "computers are fast enough, right?" - as technology evolves, we crunch more and more data, and as "big data" becomes a bigger field, we need to work with this data efficiently - because it doesn't matter how powerful a machine is, processing large quantities of data with an inefficient algorithm can still take a long time.

    And some computers today are slow. Not usually the computers that the average person is using (Even though it might feel that way if they're running Windows). Some computers that handle systems are pretty basic, or they're small, and don't have the luxury of having great specs. Fitness trackers, dedicated GPS devices, a thermostat, etc. For systems like these, processing efficiency is important.

    Let's say our algorithm's time to execute increases quadratically. That means, as we increase the amount of items to process (\(n\)), the time to process that data goes up quadratically.

    reading_u05_AlgorithmEfficiency_quadratic.png

    For processing 1 piece of data, it takes 1 unit of time (we aren't focused so much on the actual unit of time, since each machine runs at different speeds) - that's fine. Processing 2 pieces of data? 4 units of time. 8 pieces of data? 64 units of time. We don't just double the amount of time it takes when we double the data - we square it.

    How much data do you think is generated every day on a social media website that has millions of users? Now imagine the processing time for an algorithm with a quadratic increase…

    Data \(n\) Time units
    1 1
    2 4
    3 9
    4 16
    5 25
    100 10,000
    1,000 1,000,000
    10,000 100,000,000
    1,000,000 1,000,000,000,000

    From a design standpoint we also need to know what the efficiency of different algorithms are (such as the algorithm to find data in a Linked List or the algorithm to resize a dynamic array) in order to make design decisions on what is the best option for our software.


  • Example: Fibonacci sequence, iterative and recursive

    As an example, you shouldn't use a recursive algorithm to generate a Fibonacci sequence (Each number \(F_n\) in the sequence is equal to the sum of the two previous items: \(F_n = F_{n-1} + F_{n-2}\)). It's just not as efficient! Let's look at generating the string of numbers:

    1, 1, 2, 3, 5, 8, 13, 21, 34, 55

    The most efficient way to do this would be with an iterative approach using a loop. However, we could also figure it out with a recursive approach, though the recursive approach is much less efficient.

    Fibonacci sequence, iterative:

    int GetFib_Iterative(int n)
    {
      if (n == 0 || n == 1) { return 1; }
    
      int n_prev = 1;
      int n_prevprev = 1;
      int result = 0;
    
      for (int i = 2; i <= n; ++i)
        {
          result = n_prev + n_prevprev;
          n_prevprev = n_prev;
          n_prev = result;
        }
    
      return result;
    }
    

    This algorithm has a simple loop that iterates \(n\) times to find a given number of the Fibonacci sequence. The amount of time the loop iterates based on \(n\) is:

    n 3 4 5 6 7 8 9 10 20
    iterations 2 3 4 5 6 7 8 9 19

    Fibonacci sequence, recursive:

    int GetFib_Recursive(int n)
    {
      if (n == 0 || n == 1) { return 1; }
    
      return GetFib_Recursive(n - 1) + GetFib_Recursive(n - 2);
    }
    

    The way this version is written, any time we call this function with any value \(n\), it has to compute the Fibonacci number for \(Fib(n-1)\), \(Fib(n-2)\), \(Fib(n-3)\), … \(Fib(1)\), \(Fib(0)\) … twice. This produces duplicate work b ecause it effectively doesn't "remember" what \(Fib(3)\) was after it computes it, so for \(Fib(n)\) it has to recompute \(Fib(n-1)\) and \(Fib(n-2)\) each time…

    n 3 4 5 6 7 8 9 10 20
    iterations 2 4 7 12 20 33 54 88 10,945

    The growth rate of the iterative version ends up being linear: As \(n\) rises linearly, the amount of iterations also goes up linearly.

    The growth rate of the recursive function is exponential: As \(n\) rises linearly, the amount of iterations goes up exponentially.

    reading_u05_AlgorithmEfficiency_fib.png

    • Red/solid: Recursive growth rate
    • Blue/dashed: Iterative growth rate
    • Y scale from 0 to 25 (the 0th, to 25th Fibonacci number to generate).

    For small amounts this might not be too bad. However, if we were generating a Fibonacci number further in the list, it would continue getting even slower…

    reading_u05_AlgorithmEfficiency_fib2.png

    • Red/solid: Recursive growth rate
    • Blue/dashed: Iterative growth rate
    • Y scale from 0 to 11,000 (the 11,000th Fibonacci number to generate).

    If we are trying to generate the 11,000th item in the sequence, the iterative approach requires 11,000 iterations in a loop. That linear increase is still much faster than each \(Fib(n)\) calling \(Fib(n-1)\) and \(Fib(n-2)\) recursively.


  • Big-O Notation and Growth Rates

    For the most part, we don't care much about the exact amount of times a loop runs. After all, while the program is running, \(n\) could be changing depending on user input, or how much data is stored, or other scenarios. Instead, we are more interested in looking at the big picture: the generalized growth rate of the algorithm.

    We use something called "Big-O notation" to indicate the growth rate of an algorithm. Some of the rates we care about are:

    \(O(1)\) \(O(log(n))\) \(O(n)\) \(O(n^2)\) \(O(n^3)\) \(O(2^n)\)
    Constant Logarithmic Linear Quadratic Cubic Exponential
    Constant time, \(O(1)\)

    We think of any single command as being constant time. The operation \[ a = b + c \] will take the same amount of computing time no matter what the values of \(a\), \(b\), and \(c\) are.

    int F(int x)
    {
      return 3 * x + 2;
    }
    

    reading_u05_AlgorithmEfficiency_constant.png

    Logarithmic time, \(O(log(n))\)

    Having an algorithm that halves its range each iteration of the loop will result in a logarithmic growth rate.

    int Search(int l, int r,
               int search, int arr[])
    {
      while ( l <= r )
        {
          int m = l + (r-l) / 2;
          if ( arr[m] == search )
            { return m; }
          else if ( arr[m] < search )
            { l = m+1; }
          else if ( arr[m] > search )
            { r = m-1; }
        }
      return -1;
    }
    

    reading_u05_AlgorithmEfficiency_log.png

    Linear time, \(O(n)\)

    Having a single loop that iterates over a range will be a linear time increase.

    int Sum( int n )
    {
      int sum = 0;
      for (int i=1; i<=n; i++)
        {
          sum += n;
        }
      return sum;
    }
    

    reading_u05_AlgorithmEfficiency_linear.png

    If there is some scenario that causes the loop to halve its range each time, then its growth rate would be less than linear: as \(O(log(n))\).

    Quadratic time, \(O(n^2)\)

    Quadratic time comes into play when you have one loop iterating \(n\) times nested within an other loop that also iterates \(n\) times. For example, if we were writing out times tables from 1 to 10 (\(n = 10\)), then we would need 10 rows and 10 columns, giving us \(10^2 = 100\) cells.

    void TimesTables(int n)
    {
      for (int y=1; y<=n; y++)
        {
          for (int x=1; x<=n; x++)
            {
              cout << x << "*"
                   << y << "="
                   << x*y << "\t";
            }
          cout << endl;
        }
    }
    

    reading_u05_AlgorithmEfficiency_quadraticB.png

    Cubic time, \(O(n^3)\)

    Just like nesting two loops iterating \(n\) times each gives us a \(n^2\) result, having three nested loops iterating \(n\) times each gives us a \(O(n^3)\) growth rate.

    void CubicThing(int n)
    {
      for (int z=1; z<=n; z++)
        {
          for (int y=1; y<=n; y++)
            {
              for (int x=1; x<=n; x++)
                {
                  cout << x << " "
                       << y << " "
                       << z << endl;
                }
            }
        }
    }
    

    reading_u05_AlgorithmEfficiency_cubic.png

    Exponential time, \(O(2^n)\)

    With an exponential function, each step increases the complexity of the operation. The Fibonacci example is a good illustration: Figuring out Fib(0) and Fib(1) are constant, but Fib(2) requires calling Fib(0) and Fib(1), and Fib(3) calls Fib(1) and Fib(2), with Fib(2) calling Fib(0) and Fib(1), and each iteration adds that many more operations.

    int Fib(int n)
    {
      if (n == 0 || n == 1)
        return 1;
    
      return
        Exponential_Fib(n-2)
        + Exponential_Fib(n-1);
    }
    

    reading_u05_AlgorithmEfficiency_exponential.png

    Growth rate comparisons:

    reading_u05_AlgorithmEfficiency_comparisons.png


  • Review questions:
    1. Let's say an algorithm's time complexity is constant. For this example algorithm we can model the efficiency like this: \[ T(n) = 5 \] where \(n\) is the amount of items to process and \(T\) is the time it takes.
      1. Processing 1 item would cost how many time units?
      2. Processing 10 items would cost how many time units?
      3. Processing 100 items would cost how many time units?
    2. Let's say an algorithm's time complexity is linear. For this example algorithm we can model the efficiency like this: \[ T(n) = 2n \]
      1. Processing 1 item would cost how many time units?
      2. Processing 10 items would cost how many time units?
      3. Processing 100 items would cost how many time units?
    3. Let's say an algorithm's time complexity is quadratic. For this example algorithm we can model the efficiency like this: \[ T(n) = n^2 \]
      1. Processing 1 item would cost how many time units?
      2. Processing 10 items would cost how many time units?
      3. Processing 100 items would cost how many time units?
    4. Given this function:

      int Func( int n )
      {
        return 3 * n; // +1 command
      }
      

      How many commands are executed when…

      1. \(n = 1\)?
      2. \(n = 10\)?
      3. \(n = 100\)?
    5. Given this function:

      int Func( int n )
      {
        n += 1; // +1 command
      
        if ( n > 100 )
          return n+100; // +1 command, or
        else if ( n > 10 )
          return n+10; // +1 command, or
        else
          return n; // +1 command
      }
      

      How many commands are executed when…

      1. \(n = 1\)?
      2. \(n = 10\)?
      3. \(n = 100\)?
    6. Given this function:

      void Func( int n )
      {
        for ( int i = 0; i < n; i++ )
        {
          cout << i << " "; // +1 command each iteration
        }
      }
      

      How many commands are executed when…

      1. \(n = 1\)?
      2. \(n = 10\)?
      3. \(n = 100\)?
      4. The increase is… (constant/linear/quadratic?)
    7. Given this function:

      void Func( int n )
      {
        int iterations = 0; // Don't count me
        for ( int i = 0; i < n; i++ )
        {
          for ( int j = 0; j < n; j++ )
          {
            cout << i << "," << j << endl; // +1 command each iteration
            iterations++; // Don't count me
          }
        }
        cout << "Iterations: " << iterations << endl; // Don't count me
      }
      

      How many commands are executed when…

      1. \(n = 1\)?
      2. \(n = 10\)?
      3. \(n = 100\)?
      4. The increase is… (constant/linear/quadratic?)

    After reading, do the quiz to check your notes: πŸ”Ž Unit 05 Concept Intro - Algorithm efficiency (U05.CIN)

🧠 Tech Literacy - Algorithm efficiency (U05.TEC)

Watch from 26:46 - 32:19 from the "The Secret Rules of Modern Living Algorithms" - Documentary: https://youtu.be/kiFfp-HAu64?si=0274JcEj7vIogCtk&t=1606

Locate the 🧠 Unit 05 Tech Literacy - Algorithm efficiency (U05.TEC.202401CS235) or 🧠 Unit 05 Tech Literacy - Algorithm efficiency (U05.TEC.202401CS250) assignment and add to the discussion!

\newpage

WEEK 3 - JAN 29

UNIT 06: CS 200 Review - Pointers

πŸ“–οΈ Reading - Pointers and memory (U06.READ)

reading_u06_Pointers_pointer.png

  • Memory addresses

    When we declare a variable, what we're actually doing is telling the computer to set aside some memory (in RAM) to hold some information. Depending on what data type we declare, a different amount of memory will need to be reserved for that variable.

    Data type Size
    boolean 1 byte
    character 1 byte
    integer 4 bytes
    float 4 bytes
    double 8 bytes

    A bit is the smallest unit, storing just 0 or 1. A byte is a set of 8 bits. With a byte, we can store numbers from 0 to 255, for an unsigned number (only 0 and positive numbers, no negatives).

    Minimum value, 0

    (Decimal value = \(128 \cdot 0 + 64 \cdot 0 + 32 \cdot 0 + 8 \cdot 0 + 4 \cdot 0 + 2 \cdot 0 + 1 \cdot 0\))

    place 128 64 32 16 8 4 2 1
    value 0 0 0 0 0 0 0 0

    Maximum value, 255

    (Decimal value = \(128 \cdot 1 + 64 \cdot 1 + 32 \cdot 1 + 8 \cdot 1 + 4 \cdot 1 + 2 \cdot 1 + 1 \cdot 1\))

    place 128 64 32 16 8 4 2 1
    value 1 1 1 1 1 1 1 1

    A char only needs one byte to store a single letter because we represent letters with the ASCII or UTF8 codes 65 - 90 for upper-case, and 97 - 122 for lower-case.

    A B C D E F G H I J K L M
    65 66 67 68 69 70 71 72 73 74 75 76 77
    N O P Q R S T U V W X Y Z
    78 79 80 81 82 83 84 85 86 87 88 89 90
    a b c d e f g h i j k l m
    97 98 99 100 101 102 103 104 105 106 107 108 109
    n o p q r s t u v w x y z
    110 111 112 113 114 115 116 117 118 119 120 121 122

    Any of these numbers can be stored with 8 bits:

    A = 65…

    (Decimal value = \(128 \cdot 0 + 64 \cdot 1 + 32 \cdot 0 + 8 \cdot 0 + 4 \cdot 0 + 2 \cdot 0 + 1 \cdot 1\))

    place 128 64 32 16 8 4 2 1
    value 0 1 0 0 0 0 0 1

    z = 122…

    (Decimal value = \(128 \cdot 0 + 64 \cdot 1 + 32 \cdot 1 + 8 \cdot 1 + 4 \cdot 0 + 2 \cdot 1 + 1 \cdot 0\))

    place 128 64 32 16 8 4 2 1
    value 0 1 1 1 1 0 1 0

    So when we declare a variable, the computer finds an available space in memory and reserves the appropriate amount of bytes in memory. For example, our char could be assigned the memory address 0xabc008 in memory, and its value being stored would look like this:

    …007 …008 …009 …00a …00b …00c …00d …00e …00f …010
    0 1 1 1 1 0 1 0

    (…007 and …010 are spaces in memory taken by something else)

    We can view the addresses of variables in our program by using the address-of operator &. Note that we have used the ampersand symbol before to declare pass-by-reference parameters, but this is a different symbol used in a different context.

    #include <iostream>
    using namespace std;
    
    int main()
    {
      int number1 = 10;
      int number2 = 20;
    
      cout << &number1 << "\t"
           << &number2 << endl;
    
      return 0;
    }
    

    Running the program, it would display the memory addresses for these two variables. Notice that they're 4 bytes apart in memory (one is at …70 and one is at …74):

    0x7ffd3a24cc70	0x7ffd3a24cc74
    

    Each time we run the program, we will get different memory addresses, since the operating system reclaims that memory when the program ends, and gives us new memory spaces to allocate next time we run the program.

    0x7ffe0e708a80	0x7ffe0e708a84
    

    When we declare an array of integers of size \(n\), the program asks for \(n \times 4\) bytes of memory to work with. The two variables above didn't have to be side-by-side in memory; they just happened to be because they were declared close together. With an array, however, all elements of the array will be contiguous (side-by-side) in memory.

    Here we have an array of integers:

    int arr[3];
    
    for ( int i = 0; i < 3; i++ )
      {
        cout << &arr[i] << "\t";
      }
    

    And the output:

    0x7ffd09a130c0	0x7ffd09a130c4	0x7ffd09a130c8
    

    Because the elements of an array must be contiguous in memory, we cannot resize a normal array. After our array is declared, chances are the memory addresses right after it will be put to use elseware on the computer and will be unavailable to our program and our array.

    But, after we learn about pointers, we will learn how we can dynamically allocate as much memory as we need at any time during our program's execution - giving us the ability to "resize" arrays by allocating space for a new array, copying the data over to the larger chunk of memory, and deallocating the old chunk of memory from the smaller array.


  • Pointers
    • Creating pointer variables

      We can declare special variables that store memory addresses rather than storing an int or float or char value. These variables are called pointers.

      We can declare a pointer like this: int* ptrNumber; Or like this: int * ptrNumber; Or like this: int *ptrNumber;

      But note that doing this declares one pointer and several integers: int * ptrNumber, notAPointer1, notAPointer2; To avoid confusion, \underline{declare multiple pointers on separate lines}.

      If we declare a pointer as an int* type, then it will only be able to point to the addresses of integer variables, and likewise for any other data type.

      Context: Safety with pointers!

      Remember how variables in C++ store garbage in them initially? The same is true with pointers - it will store a garbage memory address. This can cause problems if we try to work with a pointer while it's storing garbage.

      To play it safe, any pointer that is not currently in use should be initialized to NULL or nullptr.

      Declaring a pointer and initializing it to nullptr:

      #include <iostream>
      using namespace std;
      
      int main()
      {
        int * ptr = nullptr;
      
        return 0;
      }
      
    • Assigning pointers to addresses

      Once we have a pointer, we can point it to the address of any variable with a matching data type. To do this, we have to use the address-of operator to access the variable's address - this is what gets stored as the pointer's value.

      • Assigning an address during declaration: int * ptr = &somevariable;
      • Assigning an address after declaration: ptr = \&somevariable;

      After assigning an address to a pointer, if we cout the pointer it will display the memory address of the pointed-to variable - just like if we had used cout to display the address-of that variable.

      // Shows the same address
      cout << ptr << endl;
      cout << &somevariable;
      

      Here's a simple program that has an integer var with a value of 10, and a pointer ptr that points to var's address.

      #include <iostream>
      using namespace std;
      
      int main()
      {
        int * ptr;
        int var = 10;
      
        ptr = &var;
      
        cout << "var address: " << &var << endl;
        cout << "ptr address: " << &ptr << endl;
        cout << "var value:   " << var << endl;
        cout << "ptr value:   " << ptr << endl;
      
        return 0;
      }
      

      The output would look like:

      var address: 0x7ffc3d6028b0
      ptr address: 0x7ffc3d6028b4
      var value:   10
      ptr value:   0x7ffc3d6028b0
      

      Some things to note:

      • var stores its data at its address 0x7ffc3d6028b0.
      • ptr stores its data at its address 0x7ffc3d6028b4.
      • var's value is 10, since it's an integer.
      • ptr's value is the address of var, since it's a pointer-to-an-integer.

      c2_u08_ptrmemory.png

    • Dereferencing pointers to get values

      Once the pointer is pointing to the address of a variable, we can access that pointed-to variable's value by dereferencing our pointer. This gives us the ability to read the value stored at that memory address, or overwrite the value stored at that memory address.

      We dereference the pointer by prefixing the pointer's name with a * - again, another symbol being reused but in a different context.

      In this code, we point ptr to the address of var. Outputting ptr will give us var's address, and outputting *ptr (ptr dereferenced) will give us the value stored at var's address.

      int * ptr;
      int var = 10;
      
      ptr = &var;
      
      cout << "ptr value:        " << ptr << endl;
      cout << "ptr dereferenced: " << *ptr << endl;
      

      Output:

      ptr value:        0x7ffd21de775c
      ptr dereferenced: 10
      

      Then, we could also overwrite the value stored at var's address by again dereferencing the pointer and writing an assignment statement:

      *ptr = 20;
      

      When we output the value that var is storing, either directly through var or through the ptr, we can see that the value of var has been overwritten:

      cout << "var value:        " << var << endl;
      cout << "*ptr value:       " << *ptr << endl;
      
      var value:        20
      *ptr value:       20
      

  • Pointer cheat sheet
    Declare a pointer int* ptrInt;
      float *ptrFloat;
    Assign pointer to address ptrInt = &intVar;
      ptrFloat = &floatVar;
    Dereference a pointer cout << *ptrChar;
      *ptrInt = 100;
    Assign to nullptr float *ptrFloat = nullptr;
      ptrChar = nullptr;

  • Invalid memory access with pointers

    Remember that when you declare a variable in C++, it will initially store garbage. This is true of pointers as well. When you declare a pointer without initializing it to nullptr, it will be storing random garbage that it will try to interpret as a memory address. If you dereference a pointer that is pointing to garbage, your program is going to run into a problem - a segmentation fault.

    A pointer pointing to garbage:

    int main()
    {
      int * bob;
      cout << *bob << endl;
    
      return 0;
    }
    

    Program output:

    Segmentation fault (core dumped)
    
    How do we check whether memory address is valid?

    In short, we can't check if an address stored in a pointer is valid. This is why we initialize our pointers to nullptr when they're not in use - we know nullptr means that the pointer is not pointing to anything.


  • Dynamically allocating memory

    One handy use of pointers is to point at the addresses of other variables that were already declared. However, there is a second main use of pointers: To dynamically allocate memory any time we need new variables that we don't have to pre-write in the program.


    • Dynamic variables

      At the moment, dynamic variables might seem pointless, but they are really handy for linked data structures - a type of structure you can use to store a series of data in that is an alternative to an array.

      • new and delete

        We use the new keyword to allocate memory for the size of one variable data type (as opposed to an array of the variables), and delete to free up that memory.

        Once we've allocated memory through the pointer, we can use the pointer as normal. The only difference is that we're now accessing an address that was allocated differently from the address of a "normal" variable.

        • Allocating memory for a single item: int * ptr = new int;
        • Deallocating memory for a single item: delete ptr;

        Small program using a dynamic variable:

        int main()
        {
          int * myNumber;             // Declare pointer
        
          myNumber = new int;         // Allocate memory
        
          *myNumber = 10;             // Assign value
        
          cout << *myNumber << endl;  // Print value
        
          delete myNumber;            // Free memory
        
          return 0;
        }
        

    • Dynamic arrays

      For our normal arrays, we are unable to resize them during the program's run time because an array requires that all elements of the array are contiguous (side-by-side) in memory.

      However, if we can allocate and deallocate memory any time we want with pointers, we can create a dynamic array. If we ever need to resize it, we allocate a new array of a bigger size and copy the data from the old array to the new one.

      • new[ ] and delete[ ]

        When we're allocating space for an array of items, we use a slightly different set of keywords:

        • Allocate memory for an array: int * arr = new int[100];
        • Deallocate memory for an array: delete [] arr;

        Example program that lets the user specify how big the array will be.

        int main()
        {
          int arraySize;
          float * pricesArray;
        
          cout << "How many items? ";
          cin >> arraySize;
        
          pricesArray = new float[arraySize]; // Allocate mem
        
          for ( int i = 0; i < arraySize; i++ )
            {
              cout << "Enter price for item " << i << ": ";
              cin >> pricesArray[i];
            }
        
          delete [] pricesArray;      // Deallocate memory
        
          return 0;
        }
        

        When you've used a pointer to allocate space for an array, you will access items in the dynamic array the same way you would do for a normal array. In other words, you would use the subscript operator [ ] to access elements at particular indices, and you don't need to prefix the pointer with the dereference operator *.


      • "Resizing" a dynamic array

        Let's say we're writing a program that stores the user's library of movies they own. It saves a file so they can run the program any time, add or delete movies, close the program and open it again sometime later.

        We can't realistically predict how many movies a person would own - some people might be collectors and have tons of movies. At the same time, if we were to over-estimate and make an array with, say, 10,000 elements, it would be a huge waste of memory if we had such a big array for someone who only had a few dozen movies.

        A dynamic array would work perfectly here because we can initially create an array that's fairly small - maybe 10 items - and if the array fills up, we can then allocate more memory and copy the movies to the new array and deallocating the old array. If it fills up again, we just repeat the process, allocating more memory, copying the data over, and freeing the old array's memory.

        Resizing steps

        The resizing process works like this:

        1. Create a pointer and use it to allocate a new, bigger array.
        2. Use a for loop to copy all the elements from the old-small-array to the new-big-array.
        3. Free the memory for the old-small-array.
        4. Update the array's pointer, which was pointing to old-small-array, to now point to the address of the new-big-array.

        Normally, you would store a dynamic array inside of a class that acts as the interface for adding and removing data, where it will check if there's enough space before adding new information and resizing the array if not.

        Here's an example of resizing a movie array within a MovieLibrary class:

        void MovieLibrary::Resize()
        {
          // Allocate more memory
          int newSize = m_arraySize + 10;
          string * newArray = new string[newSize];
        
          // Copy data from old array to new array
          for ( int i = 0; i < m_arraySize; i++ )
            {
              newArray[i] = m_movieArray[i];
            }
        
          // Free memory of old array
          delete [] m_movieArray;
        
          // Update the pointer to point at new array
          m_movieArray = newArray;
        
          // Update the array size
          m_arraySize = newSize;
        }
        

        Dynamic array example: Movie Library

        Here's a small example of a class that contains a dynamic array, that allows users to add additional information, and handles checking for a full array and resizing.

        MovieLibrary.h:
        #ifndef _MOVIE_LIBRARY_HPP
        #define _MOVIE_LIBRARY_HPP
        
        #include <string>
        using namespace std;
        
        class MovieLibrary
        {
        public:
          MovieLibrary();
          ~MovieLibrary();
        
          void ViewAllMovies() const;
          void ClearAllMovies();
          void AddMovie( string newTitle );
        
        private:
          bool IsFull();
          void Resize();
        
          string * m_movieArray;
          int m_arraySize;
          int m_itemsStored;
        };
        
        #endif
        

        The methods IsFull() and Resize() are set as private because outside users don't need to know anything about how the data is stored. The outside users shouldn't be able to recall Resize whenever they want.

        MovieLibrary.cpp:

        Constructor:

        MovieLibrary::MovieLibrary()
        {
          m_arraySize = 10;
          m_movieArray = new string[m_arraySize];
          m_itemsStored = 0;
        }
        

        In the constructor, we initialize our m_movieArray member pointer variable by allocating some memory to start with.

        If we weren't going to allocate memory in the constructor, then we should initialize m_movieArray by setting it to nullptr.

        We also have two separate size variables - m_arraySize keeps track of how many spaces are available in the array, and m_itemsStored keeps track of how many items the user has added to the array.


        Destructor:

        MovieLibrary::~MovieLibrary()
        {
          if ( m_movieArray != nullptr )
            {
              delete [] m_movieArray;
            }
        }
        

        Before the MovieLibrary item is destroyed (e.g., when the program ends and it goes out of scope), we need to make sure to *free the memory that we allocated}. First, we need to check if the pointer m_movieArray is pointing to nullptr - if it is, we can assume it's not in use and there's nothing to do. But, if m_movieArray is pointing to some address, we assume that memory has been allocated here. In this case, we free that memory.


        ViewAllMovies:

        void MovieLibrary::ViewAllMovies() const
        {
          for ( int i = 0; i < m_itemsStored; i++ )
            {
              cout << i << ". " << m_movieArray[i] << endl;
            }
        }
        

        This is just a simple for loop that goes from index 0 to m_itemsStored - 1, displaying each element to the screen.

        We aren't iterating until i < m_arraySize because if our array size is 10 and the user has only stored 5 movies, we don't need to display 5 empty slots.


        ClearAllMovies:

        void MovieLibrary::ClearAllMovies()
        {
          delete [] m_movieArray;
          m_movieArray = nullptr;
          m_arraySize = 0;
          m_itemsStored = 0;
        }
        

        If the user decides to clear out their movie array, we could just deallocate all memory reserved for the movie list and reset our size variables. We just need to make sure we're checking to make sure the array is allocated before we try to access elements from it or add to it.


        IsFull:

        bool MovieLibrary::IsFull()
        {
          return m_itemsStored == m_arraySize;
        }
        

        The array is full if the amount of items the user has stored, m_itemsStored is equal to the amount of spaces we have allocated for the array, m_arraySize.


        AddMovie:

        void MovieLibrary::AddMovie( string newTitle )
        {
          if ( m_movieArray == nullptr )
            {
              m_arraySize = 10;
              m_movieArray = new string[m_arraySize];
            }
          if ( IsFull() )
            {
              Resize();
            }
        
          m_movieArray[ m_itemsStored ] = newTitle;
          m_itemsStored++;
        }
        

        Before we add a movie, we need to make sure m_movieArray is set up and ready.

        First, we check to see if it is pointing to nullptr. If it is, we allocate memory.

        Next, we check to see if the array is full. If it is, we call Resize().

        Finally, once those two checks have been done and the array has been prepared, we can add a new item to the array and increase hte m_itemsStored count.


        Resize:

        void MovieLibrary::Resize()
        {
          int newSize = m_arraySize + 10;
          string * newArray = new string[newSize];
        
          for ( int i = 0; i < m_arraySize; i++ )
            {
              newArray[i] = m_movieArray[i];
            }
        
          delete [] m_movieArray;
        
          m_movieArray = newArray;
          m_arraySize = newSize;
        }
        

        This is the same Resize() method as was shown before as an example of resizing a dynamic array.


  • Memory Management
    • Types of memory errors

      Working with pointers and dealing with memory can lead to writing code that can mess up our program or even slow down our computer (until the next reboot).

      Invalid memory access:

      Invalid memory access happens when a pointer isn't set to nullptr when it's no longer in use. If this doesn't happen, the memory address it's pointing to could be invalid, such as if delete was used on the pointer, or if the pointer were declared and not initialized.

      An invalid memory access would cause a problem once you try to dereference the pointer pointing to an invalid address - causing a segfault and your program to crash.

      Memory leak:

      A memory leak occurs when you've allocated memory (via the new command) but you never deallocate it. C++ won't automatically deallocate this memory for you, and what happens is that chunk of memory ends up being "called for" even after the program has finished running. This memory block will be unavailable to all programs until the user restarts their computer.

      If you had many items allocating memory but never freeing them, the resulting memory leaks could cause your computer to slow down over time (until the next reboot).

      Missing allocation:

      Missing allocation occurs if you try to delete memory that has already previously been freed. If this happens, the progrma will crash.


    • The Stack and the Heap

      What is the difference between a normal variable…

      int myNum;

      … and a dynamically allocated variable?…

      int * myNum = new int;

      Well, there are different types of memory, and each is stored in a different space.

      The Stack:

      All of our non-dynamic variables we have been declaring, including function parameters and class member variables, get allocated to the Stack. The Stack part of memory has a fixed size, and it contains a sequence of memory addresses. The Stack automatically handles the memory management for these variables - when we declare a int count; variable, it will be pushed onto the Stack, and when it's not in use anymore (when it goes out of scope), it will be removed from (popped off) the Stack.

      A "stack overflow" error occurs when so many variables have been declared that the Stack runs out of room. This usually occurs while writing recursive functions that have a logic error, causing them to repeat until we run out of Stack space.

      The Heap:

      The heap is where dynamically allocated data gets stored. The Heap does not automatically take care of memory management for us, so that is why we have to deal with new and delete ourselves (unless we're using a smart pointer). Any data in the Heap must be accessed via a pointer. There is no size restriction for the Heap.


  • Smart Pointers

    In modern versions of C++ we have access to smart pointers, which allow us to utilize pointer functionality but without having to worry about the memory management side of things. It's important to see both the original style of pointers and the smart pointers so that you're acquainted with both in case you experience them in other classes or on the job.


    • shared_ptr

      reading_u06_Pointers_SharedPointers.png

      Documentation: https://cplusplus.com/reference/memory/shared_ptr/

      Shared pointers are meant for spaces in memory that will be pointed to by multiple pointers. The memory allocated at that address stays active up until the last pointer is destroyed - that triggers the deallocation of the memory once nobody is pointing to it anymore.

      To illustrate this, let's create a City struct:

      struct City
      {
        City( string value )
        {
          name = value;
        }
      
        string name;
        shared_ptr<City> west_neighbor;
        shared_ptr<City> east_neighbor;
        shared_ptr<City> south_neighbor;
        shared_ptr<City> north_neighbor;
      };
      

      Each City can have a north, south, east, and/or west neighbor City. Instead of using a City* traditional pointer, we're using the shared_ptr object that's part of the <memory> C++ library.

      Within our main program, we can create some cities. Instead of our dynamic variable allocation like City lenexa = new City( "Lenexa" );, it will look like this:

      shared_ptr<City> overland_park  = shared_ptr<City>( new City( "Overland Park" ) );
      shared_ptr<City> lenexa         = shared_ptr<City>( new City( "Lenexa" ) );
      shared_ptr<City> olathe         = shared_ptr<City>( new City( "Olathe" ) );
      shared_ptr<City> shawnee        = shared_ptr<City>( new City( "Shawnee" ) );
      

      For each of these shared pointers, this is the first reference to its memory address. When we set up neighbors for each city, we create additional references:

      // Set up neighbors
      lenexa->east_neighbor         = overland_park;
      overland_park->west_neighbor  = lenexa;
      lenexa->south_neighbor        = olathe;
      olathe->north_neighbor        = lenexa;
      lenexa->north_neighbor        = shawnee;
      shawnee->north_neighbor       = lenexa;
      

      If these were traditional pointers, the syntax would look the same.

      The shared_ptr class gives us access to a function called use_count(), which we can use to see how many shared pointers are currently pointing to a given memory address. (https://cplusplus.com/reference/memory/shared_ptr/) Using this, we can build out a table that displays each city, its address, and how many references are going to that address:

      cout << setw( 20 ) << "CITY"              << setw( 20 ) << "ADDRESS"      << setw( 20 ) << "USE COUNT"               << endl << string( 80, '-' ) << endl;
      cout << setw( 20 ) << overland_park->name << setw( 20 ) << overland_park  << setw( 20 ) << overland_park.use_count() << endl;
      cout << setw( 20 ) << lenexa->name        << setw( 20 ) << lenexa         << setw( 20 ) << lenexa.use_count()        << endl;
      cout << setw( 20 ) << olathe->name        << setw( 20 ) << olathe         << setw( 20 ) << olathe.use_count()        << endl;
      cout << setw( 20 ) << shawnee->name       << setw( 20 ) << shawnee        << setw( 20 ) << shawnee.use_count()       << endl;
      
      CITY                ADDRESS             USE COUNT
      --------------------------------------------------------------------------------
      Overland Park       0x55f775311eb0      2
      Lenexa              0x55f775311f40      4
      Olathe              0x55f775311fd0      2
      Shawnee             0x55f775312060      2
      

      At the end of the program we don't have to include code like delete lenexa; to free the allocated memory. Since the shared_ptr is a class, the actual pointer is stored within that class, and utilizing the destructor, it will take care of cleaning up the memory for us.


    • unique_ptr

      Documentation: https://cplusplus.com/reference/memory/unique_ptr/

      With a unique_ptr, we create a pointer that is the only "owner" of the memory block it points to. We can move around "ownership" of that address, but only one pointer may point to it. When the owning unique_ptr goes out of scope and is destroyed, it will automatically free the space allocated at the memory address it owns.

      This can be useful for creating a dynamic array but without having to deal with the memory management. Instead of allocating memory in the traditional way like: string* arr = new string[ array_size ];, we can use the unique_ptr:

      unique_ptr<string[]> arr = unique_ptr<string[]>( new string[ array_size ] );
      

      or you could use the auto keyword to make this a little nicer looking:

      auto arr = unique_ptr<string[]>( new string[ array_size ] );
      

      Otherwise, we use this dynamic array similar to with traditional pointers, but we don't need the delete [] arr; at the end of the program:

      for ( size_t i = 0; i < array_size; i++ )
      {
        cout << "Enter value for #" << i << ": ";
        getline( cin, arr[i] );
      }
      

      If you need to change who owns the pointed-to address, the move command can be used:

      // Previously declared:
      // auto arr = unique_ptr<string[]>( new string[3] );
      // auto biggerArray = unique_ptr<string[]>( new string[ newSize ] );
      arr = move( biggerArray );
      

  • Review questions:
    1. Given the variable declaration: int num = 10; What kind of memory is the num variable stored in?
    2. Given the variable declaration: int* num = new int; What kind of memory is the num variable stored in?
    3. How do you declare a pointer?
    4. How do you access the address of a variable?
    5. How do you assign the address of a variable to a pointer?
    6. How do you dereference a pointer to display the value of the address it's pointing to?
    7. How do you dereference a pointer to store a new value in the memory block pointed to?
    8. When a pointer is not currently in use, it should be initialized to…

    After reading, do the quiz to check your notes: πŸ”Ž Unit 06 Concept Intro - CS 200 review: Pointers (U06.CIN)

πŸ§‘β€πŸ”¬ Lab - CS 200 Review - Pointers (U06.LAB)

pixel-goals.png Goals:

  • Practice with getting addresses
  • Practice with pointers
  • Practice with allocating memory for variables via pointer
  • Practice with allocating memory for array via pointer
  • Practice using unique pointers
  • Practice using shared pointers

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Setup: Starter code and new branch

    For this assignment, I've already added the code to your repository.

    1. Pull the starter code:
      1. Check out the main branch: git checkout main
      2. Pull latest: git pull
    2. Create a new branch to start working from: git checkout -b BRANCHNAME

    The u06_ReviewPointers folder contains the following items:

    .
    ├── graded_program
    │   ├── Array.cpp
    │   ├── Array.h
    │   ├── INFO.h
    │   ├── main.cpp
    │   ├── Project_CodeBlocks
    │   │   ├── DynamicArray.cbp
    │   │   ├── DynamicArray.depend
    │   │   └── DynamicArray.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   └── Project_VisualStudio2022
    │       ├── DynamicArray
    │       │   ├── DynamicArray.vcxproj
    │       │   └── DynamicArray.vcxproj.filters
    │       └── DynamicArray.sln
    ├── practice1_addresses
    │   └── u06_p1_addresses.cpp
    ├── practice2_pointers
    │   └── u06_p2_pointers.cpp
    ├── practice3_dynamic_variables
    │   └── u06_p3_dynvars.cpp
    ├── practice4_dynamic_arrays
    │   └── u06_p4_dynarr.cpp
    ├── practice5_unique_pointers
    │   └── u06_p5_uniqueptr.cpp
    └── practice6_shared_pointers
        └── u06_p6_sharedptr.cpp
    
    • Practice program projects:

      I have only provided a Visual Studio and Code::Blocks project for the graded assignment. For the practice assignments you will need to create a project and add the file to that project. You can follow the steps in this video:


  • practice1_addresses
    EXPLORING ADDRESSES
    
    Original Variables!
    VARIABLE            ADDRESS             VALUE
    --------------------------------------------------------------------------------
    num1                0x7ffd286acffc      10
    num2                0x7ffd286ad000      15
    num3                0x7ffd286ad004      -5
    
    Pointer!
    Address: 0
    Don't dereference the nullptr!!
    
    ptr is pointing to address: 0x7ffd286acffc
    The value at that address is: 10
    Enter a new value: 500
    
    ptr is pointing to address: 0x7ffd286ad000
    The value at that address is: 15
    Enter a new value: 200
    
    ptr is pointing to address: 0x7ffd286ad004
    The value at that address is: -5
    Enter a new value: 300
    
    Changed Variables!
    VARIABLE            ADDRESS             VALUE
    --------------------------------------------------------------------------------
    num1                0x7ffd286acffc      500
    num2                0x7ffd286ad000      200
    num3                0x7ffd286ad004      300
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    You will need to create an integer pointer and assign it to various addresses throughout the program. Once finished, the output should look similar to the above one.


  • practice2_pointers
    POINTERS AND OBJECTS
    
    PRODUCTS:
    0    Pencil              1.59
    1    Markers             2.39
    2    Eraser              0.59
    Enter # of product to modify: 0
    Enter new price: $9.99
    Enter new name: Super Pencil
    
    UPDATED PRODUCTS:
    0    Super Pencil        9.99
    1    Markers             2.39
    2    Eraser              0.59
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    This program will use pointers to objects, so you will need to also access member variables of the given class. Use the -> operator to access members of an object, when it's being pointed to via a pointer.


  • practice3_dynamic_variables
    LINKED NODES
    
    DYNAMICALLY ALLOCATING MEMORY FOR VARIABLES
    --------------------------------------------------------------------------------
     CREATE NODES AND LINK THEM:
     * Create firstNode (A)
     * Create firstNode->ptrNext (B)
     * Create firstNode->ptrNext->ptrNext (C)
    
     ITERATE THROUGH NODES:
    
     FREE MEMORY:
     * Delete firstNode->ptrNext->ptrNext
     * Delete firstNode->ptrNext
     * Delete firstNode
    

    Look out for the comment lines in the code that give instructions on what to do.

    Follow the steps of the program to create a basic "linked" structure.


  • practice4_dynamic_arrays
    DYNAMICALLY ALLOCATING MEMORY FOR ARRAYS
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 0
    ARRAY CONTENTS:
    
    Enter a new item to add, or QUIT to quit: cat
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 1
    ARRAY CONTENTS:
    0. cat
    
    Enter a new item to add, or QUIT to quit: dog
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 2
    ARRAY CONTENTS:
    0. cat
    1. dog
    
    Enter a new item to add, or QUIT to quit: rat
     * NEED TO RESIZE
     * COPY DATA FROM OLD ARRAY TO NEW ARRAY
     * FREE THE OLD MEMORY
     * UPDATE POINTER TO NEW MEMORY
     * UPDATE ARRAYSIZE
    
    ----------------------------------------
    ARRAY SIZE: 6, ITEM COUNT: 3
    ARRAY CONTENTS:
    0. cat
    1. dog
    2. rat
    
    Enter a new item to add, or QUIT to quit: panda
    
    ----------------------------------------
    ARRAY SIZE: 6, ITEM COUNT: 4
    ARRAY CONTENTS:
    0. cat
    1. dog
    2. rat
    3. panda
    
    Enter a new item to add, or QUIT to quit: QUIT
    
    FREE REMAINING MEMORY BEFORE LEAVING!!
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Follow along with the instructions to implement the core functionality of a dynamic array, which will also be part of the graded program.


  • practice5_unique_pointers
    UNIQUE POINTERS
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 0
    ARRAY CONTENTS:
    
    Enter a new item to add, or QUIT to quit: eggs
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 1
    ARRAY CONTENTS:
    0: eggs
    
    Enter a new item to add, or QUIT to quit: bagels
    
    ----------------------------------------
    ARRAY SIZE: 3, ITEM COUNT: 2
    ARRAY CONTENTS:
    0: eggs
    1: bagels
    
    Enter a new item to add, or QUIT to quit: cereal
     * NEED TO RESIZE
     * COPY DATA FROM OLD ARRAY TO NEW ARRAY
     * MOVE BIG ARRAY OWNERSHIP TO ORIGINAL ARRAY
     * UPDATE ARRAYSIZE
    
    ----------------------------------------
    ARRAY SIZE: 6, ITEM COUNT: 3
    ARRAY CONTENTS:
    0: eggs
    1: bagels
    2: cereal
    
    Enter a new item to add, or QUIT to quit: QUIT
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Follow along to practice with unique pointers. This program is similar to the dynamic array program except without the memory management.


  • practice6_shared_pointers
    SHARED POINTERS
    1. Lemon	 2. Lime
    
    Person 1 enter your vote: 1
    Person 2 enter your vote: 2
    Person 3 enter your vote: 2
    
    RESULTS:
    OPTION 1: Lemon, USE COUNT: 2
    OPTION 2: Lime, USE COUNT: 3
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    The shared pointers vote1, vote2, and vote3 will point to either option1 or option2. At the end of the program, we check their use_count() s to see the total amount of references to each address.


  • graded_program
    DYNAMIC ARRAY
    1. Create array
    2. Allocate space
       AllocateSpace
    3. Add items to array, trigger resize
       AddItem
       AddItem
       AddItem
       AddItem
       ResizeArray
       AddItem
    4. Display array
    0: BASIC
    1: COBOL
    2: Java
    3: C#
    4: C++
    5. Deallocate space
       DeallocateSpace
       DeallocateSpace
    

    For this program you will be implementing a dynamic array inside the Array.cpp file.

    The class has already been declared as:

    class Array
    {
    public:
        Array();
        Array( int arraySize );
        ~Array();
    
        void AllocateSpace( int arraySize );
        void DeallocateSpace();
    
        void ResizeArray();
        void AddItem( string value );
        void Display();
    
    private:
        int m_arraySize = 3;
        int m_itemCount = 0;
        string * m_ptr = nullptr;
    
    
        friend void Test_Array_Constructor();
        friend void Test_Array_AllocateSpace();
        friend void Test_Array_DeallocateSpace();
        friend void Test_Array_ResizeArray();
        friend void Test_Array_AddItem();
    };
    

    You can ignore the friend functions - these are used with the unit tests. Basically, marking a function as a friend gives that function access to the private member variables of a class, which helps me with implementing the unit tests.

    The functions of the Array will need to be implemented. You will only need to update Array.cpp.


    • Array::Array()

      This is the default constructor function.

      Initialize the m_ptr pointer to nullptr for safety.


    • Array::Array( int arraySize )

      This is the parameterized constructor function.

      Initialize the m_ptr pointer to nullptr for safety and call the AllocateSpace function, passing in the arraySize.


    • Array::~Array()

      This is the destructor function.

      Call the DeallocateSpace function.


    • void Array::AllocateSpace( int arraySize )

      If m_ptr is NOT pointing to nullptr this means that it is currently in use. Display an error message and return early (you can leave void functions with return;.)

      Otherwise, allocate space for a new array via the m_ptr pointer - we are creating a dynamic array. Use the arraySize given.

      Make sure to also initialize m_arraySize to the arraySize and initialize m_itemCount to 0.

      QUESTION: How do I create a new dynamic array with m_ptr?

      m_ptr = new string[ arraySize ];


    • void Array::DeallocateSpace()

      If m_ptr is NOT pointing to nullptr then we need to free the memory. delete the array at the m_ptr address, then assign nullptr to the m_ptr for safety.

      QUESTION: How do I free the memory that m_ptr is pointing to?

      delete [] m_ptr;

      QUESTION: How do I point m_ptr to nullptr?

      m_ptr = nullptr;


    • void Array::ResizeArray()
      1. Create a new dynamic array with a larger array size. Let's call this newArray.
      2. Copy the data from the old array (m_ptr) to the new array (newArray). You'll use a for loop, and iterate from i=0 to the m_itemCount (not-inclusive).
      3. After copying the data over, free the memory at the m_ptr location using the delete command.
      4. Update the m_ptr pointer to point to the same address as newArray.
      5. Update the value of the m_arraySize to the new array size.

      QUESTION: How do I create a new dynamic array with a larger size?

      string * newArray = new string[ m_arraySize * 2 ]; or some other larger size.

      QUESTION: How do I copy the data from one array to another?

      for ( int i = 0; i < m_itemCount; i++ ) { newArray[i] = m_ptr[i]; }

      QUESTION: How do I free the memory that m_ptr is pointing to?

      delete [] m_ptr;

      QUESTION: How do I point m_ptr to the same address as newArray?

      m_ptr = newArray;


    • void Array::AddItem( string value )

      If the array is full (m_itemCount = marraySize=) then call the ResizeArray function and then continue on.

      Assign the element of m_ptr at the index m_itemCount to the value given.

      Increment m_itemCount afterward.

      QUESTION: How do I assign an item to the m_ptr array?

      m_ptr[INDEX] = value;

      QUESTION: What is the index going to be?

      m_ptr[m_itemCount] = value;


    • void Array::Display()

      Note: This function does not have a unit test.

      Use a for loop to iterate over all the elements of the m_ptr array. Display each item's index and element value.

      QUESTION: What is an index?

      The index is the position of an element in the array. The lowest valid index is 0, and for an array of size size, the highest valid index is size-1.

      QUESTION: How do I iterate over all the elements of the array?

      for ( int i = 0; i < m_itemCount; i++ ) { cout << i << ". " << m_ptr[i] << endl; }


      Once everything is implemented, all the tests should pass. You can also run the program itself to see your dynamic array in use.


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

UNIT 07: The Standard Template Library

πŸ“Š Presentation - The Standard Template Library (U07.PRES)

πŸ“–οΈ Reading - The Standard Template Library (U07.READ)

C++ comes with a bunch of data structures we can use to store sets of data. Each of these have different uses and work better for different designs. They all have documentation available online so you can learn more about how these structures work there as well.

  • Without the STL: Traditional fixed-length array

    One of the first ways you may have learned to store a sequence of data is with a traditional array in C++. These aren't "smart" at all, cannot be resized, and you either have to manually keep track of the size of the array or do a calculation whenever you want to find the size.

    Declaration

    They come in this form:

    const int ARRAY_SIZE = 100;
    int itemCount = 0;
    int myArray[ ARRAY_SIZE ];
    

    The ARRAY_SIZE named constant and the itemCount variable are optional, however, we have to manually keep track of the size of the array and how many items are stored within it - it won't take care of that for us.

    Reading and writing values to this array is simple:

    cout << "Enter a number: ";
    cin >> myArray[ 0 ];
    cout << "Item #0 is " << myArray[0] << endl;
    

    And iterating through it requires, again, keeping track of the amount of items we've stored in the array with some kind of variable:

    Iterating over elements
    for ( int i = 0; i < itemCount; i++ )
    {
      cout << i << " = " << myArray[i] << endl;
    }
    

  • Without the STL: Dynamic array

    We can create dynamic arrays by using pointers. This allows us to define the size of the array at runtime, instead of defining the size at compile-time like with our traditional fixed-length array. The downside is manually managing the memory - making sure to free whatever we have allocated.

    Declaration
    int itemCount = 0;
    int arraySize;
    
    cout << "Enter an array size: ";
    cin >> arraySize;
    
    int* myArray = new int[ arraySize ];
    

    Reading and writing values to the array is the same as with the traditional array. We just have to make sure to free the memory before the program ends:

    Iterating over elements
    for ( int i = 0; i < itemCount; i++ )
    {
      cout << i << " = " << myArray[i] << endl;
    }
    
    Freeing memory

    You need to free the memory you allocate before its pointer loses scope - if this happens, you lose the address and that memory is now taken up and cannot be freed.

    delete [] myArray;
    

  • STL std::array

    Documentation: https://cplusplus.com/reference/array/array/

    he array from the STL is basically a class wrapping our traditional array. It allows us to use the array as an object with basic functions, so that we don't have to keep track of as much.

    Declaration
    array<int, 100> myArray;
    
    Accessing the size
    cout << "Size: " << myArray.size();
    
    Accessing elements
    cout << "Enter item 0: ";
    cin >> myArray[0];
    
    Iterating over elements
    for ( int i = 0; i < myArray.size(); i++ )
    {
      cout << i << " = " << myArray[i] << endl;
    }
    

  • STL std::vector

    Documentation: https://cplusplus.com/reference/vector/vector/

    Vectors are dynamic arrays so you can add items to it and it will resize - no worries on your part regarding resizing and managing memory.

    Declaration
    vector<int> myVector;
    
    Accessing the size
    cout << "Size: " << myVector.size();
    
    Accessing elements

    Because you're generally not pre-allocating space for a vector, you need to use the push_back function to add additional items to the end of the vector's internal array.

    cout << "Enter item 0: ";
    int item;
    cin >> item;
    myVector.push_back( item );
    
    Iterating over elements
    for ( int i = 0; i < myVector.size(); i++ )
    {
      cout << i << " = " << myVector[i] << endl;
    }
    

  • STL std::list

    Documentation: https://cplusplus.com/reference/list/list/

    The list is similar to a vector in that you can store any amount of items in it, however you can only ever access the front and the back of the list - not random items in the middle. You can still iterate over all the items, though.

    Declaration
    list<int> myList;
    
    Accessing the size
    cout << "Size: " << myList.size();
    
    Adding data
    myList.push_front( 1 ); // Stored at the start of the list
    myList.push_back( 10 ); // Stored at the end of the list
    
    Accessing data
    cout << "Front item: " << myList.get_front() << endl;
    cout << "Back item:  " << myList.get_back() << endl;
    
    Removing data
    myList.pop_front();
    myList.pop_back();
    
    Iterating over elements
    for ( auto& item : myList )
    {
      cout << item << endl;
    }
    

  • STL std::stack

    stack.png

    Documentation: https://cplusplus.com/reference/stack/stack/

    A stack is a type of restricted-access data type. It can store a series of items, but you can only add and remove items from the top.

    Declaration
    stack<char> myStack;
    
    Accessing the size
    cout << "Size: " << myStack.size();
    
    Adding data
    myStack.push( 'A' );
    myStack.push( 'B' );
    myStack.push( 'C' );
    
    Accessing data
    cout << "Top item is: " << myStack.top() << endl;
    
    Removing data
    myStack.pop(); // Remove top item
    

  • STL std::queue

    queue.png

    Documentation: https://cplusplus.com/reference/queue/queue/

    A queue is another type of restricted-access data type. It stores a series of items, with items being added at the back of the queue, and being removed from the front of the queue, similar to waiting in line at the store.

    Declaration
    queue<char> myQueue;
    
    Accessing the size
    cout << "Size: " << myQueue.size() << endl;
    
    Adding data
    myQueue.push( 'A' );
    myQueue.push( 'B' );
    myQueue.push( 'C' );
    
    Accessing data
    cout << "The front item is: " << myQueue.front() << endl;
    
    Removing data
    myQueue.pop(); // Remove front item
    

  • STL std::map

    hashtable.png

    Documentation: https://cplusplus.com/reference/map/map/

    Our traditional arrays have index numbers (0, 1, 2, …) and elements stored at that position in the array.

    With a map, we can have any data type as a unique identifier for an element. This could be an integer, but it doesn't have to be 0, 1, 2, and so on - it could be an employee ID, a phone number, etc., but the data type of the identifier (called a key) can be any data type.

    Declaration
    map<int, string> area_codes;
    
    Adding data
    area_codes[913] = "Northeastern Kansas";
    area_codes[816] = "Northwestern Missouri";
    
    Accessing the size
    cout << "Size: " << area_codes.size();
    
    Accessing data
    int key;
    cout << "Enter an area code: ";
    cin >> key;
    
    cout << "The region for that is: " << area_codes[ key ];
    
    Iterating over elements
    for ( auto& item : area_codes )
    {
      cout << "Key: " << item.first << ", Value: " << item.second << endl;
    }
    

  • Review questions:
    1. Identify what "type" each of the following is… (traditional array / traditional dynamic array / array object / vector object)
      1. vector<int> arr;
      2. int * arr = new int[100];
      3. array<int, 100> arr;
      4. int arr[100];
    2. Which of these std data types can be resized (store any amount of data after declaration)? (array / vector / list)
    3. You can only add items to the … of a stack, and you can only remove items from the … of a stack.
    4. You can only add items to the … of a queue, and you can only remove items from the … of a queue.
    5. True or false: A map can use any data type for its unique key item.

    After reading, do the quiz to check your notes: πŸ”Ž Unit 07 Concept Intro - The Standard Template Library (U07.CIN)

πŸ§‘β€πŸ”¬ Lab - The Standard Template Library (U07.LAB)

pixel-goals.png Goals:

  • Practice with STL array
  • Practice with STL vector
  • Practice with STL list
  • Practice with STL map
  • Practice with STL queue
  • Practice with STL stack

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Setup: Starter code and new branch

    For this assignment, I've already added the code to your repository.

    1. Pull the starter code:
      1. Check out the main branch: git checkout main
      2. Pull latest: git pull
    2. Create a new branch to start working from: git checkout -b BRANCHNAME

    The u07_StandardTemplateLibrary folder contains the following items:

    .
    ├── examples
    │   ├── array_example.cpp
    │   ├── list_example.cpp
    │   ├── map_example.cpp
    │   ├── queue_example.cpp
    │   ├── stack_example.cpp
    │   └── vector_example.cpp
    ├── graded_program
    │   ├── Customer.h
    │   ├── events.txt
    │   ├── GroceryStoreProgram.cpp
    │   ├── GroceryStoreProgram.h
    │   ├── INFO.h
    │   ├── main.cpp
    │   ├── Product.h
    │   ├── Project_CodeBlocks
    │   │   ├── STL.cbp
    │   │   ├── STL.depend
    │   │   └── STL.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   ├── Project_VisualStudio2022
    │   │   ├── StandardTemplateLibraryProject.sln
    │   │   ├── StandardTemplateLibraryProject.vcxproj
    │   │   └── StandardTemplateLibraryProject.vcxproj.filters
    │   └── Tester.cpp
    ├── practice1_array
    │   └── u07_p1_array.cpp
    ├── practice2_vector
    │   └── u07_p2_vector.cpp
    ├── practice3_list
    │   └── u07_p3_list.cpp
    ├── practice4_map
    │   └── u07_p4_map.cpp
    ├── practice5_stack
    │   └── u07_p5_stack.cpp
    └── practice6_queue
        └── u07_p6_queue.cpp
    

    The examples folder contains examples of each of the Standard Template Library items we're using in this assignment.


  • practice1_array
    ARRAY PROGRAM
    
    COURSES:
    0: CS 134
    1: CS 200
    2: CS 235
    3: CS 250
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL array class (https://cplusplus.com/reference/array/array/) for this program. Set the array's size and fill it up with items, then iterate over the array, displaying each index and element value.

    QUESTION: How do I iterate over an array?

    for ( size_t i = 0; i < ARRAYNAME.size(); i++ ) for the loop.

    QUESTION: How do I get the index?

    Within the loop example above, i is the index.

    QUESTION: How do I get the element?

    Within the loop example above, ARRAYNAME[i] is the element.


  • practice2_vector
    VECTOR PROGRAM
    Enter a new course, or STOP to finish: CS134
    Enter a new course, or STOP to finish: CS200
    Enter a new course, or STOP to finish: CS235
    Enter a new course, or STOP to finish: STOP
    
    COURSES:
    0: CS134
    1: CS200
    2: CS235
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL vector class (https://cplusplus.com/reference/vector/vector/) for this program. Use the push_back function to add new items to the vector, and use a normal for loop to display each index and element value.

    QUESTION: How do I iterate over a vector?

    for ( size_t i = 0; i < VECTORNAME.size(); i++ ) for the loop.

    QUESTION: How do I get the index?

    Within the loop example above, i is the index.

    QUESTION: How do I get the element?

    Within the loop example above, VECTORNAME[i] is the element.


  • practice3_list
    LIST PROGRAM
    
    Enter a new course, or STOP to finish: ASL120
    Insert at (F) FRONT or (B) BACK? F
    Enter a new course, or STOP to finish: ASL121
    Insert at (F) FRONT or (B) BACK? B
    Enter a new course, or STOP to finish: CS134
    Insert at (F) FRONT or (B) BACK? F
    Enter a new course, or STOP to finish: CS250
    Insert at (F) FRONT or (B) BACK? B
    Enter a new course, or STOP to finish: STOP
    
    COURSES:
    F
    F
    B
    B
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL list class (https://cplusplus.com/reference/list/list/) for this program. As you add items to the list, the user can choose to put the new item at the FRONT or the BACK of the list. Afterwards, use a range-based for loop to iterate over all the items in the list to display each one.

    QUESTION: How do I iterate over a list to display each value?

    for ( auto& item : LISTNAME ) for the loop.


  • practice4_map
    MAP PROGRAM
    
    Item: burrito, Price: $1.29
    Item: quesadilla, Price: $2.36
    Item: taco, Price: $1.29
    
    Enter a food: burrito
    The price is: $1.29
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL map class (https://cplusplus.com/reference/map/map/) for this program. You'll add items to the map, access items via their keys, and utilize a range-based for loop to iterate over all the items in the map.

    QUESTION: How do I iterate over a map and display all keys/values?

    for ( auto& item : product_prices ) for the loop, then you can use item.first to get the key, and item.second to get the value.


  • practice5_stack
    STACK PROGRAM
    --------------------------------------------------
    STACK IS EMPTY
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on stack: A
    --------------------------------------------------
    TOP ITEM IN STACK: A
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on stack: B
    --------------------------------------------------
    TOP ITEM IN STACK: B
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on stack: C
    --------------------------------------------------
    TOP ITEM IN STACK: C
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 2
    Popped top item off stack
    --------------------------------------------------
    TOP ITEM IN STACK: B
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 0
    
    --------------------------------------------------
    TOP OF STACK: B
    TOP OF STACK: A
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL stack class (https://cplusplus.com/reference/stack/stack/) for this program. You'll create a stack, use push, pop, and top functions to interface with it.


  • practice6_queue
    QUEUE PROGRAM
    --------------------------------------------------
    QUEUE IS EMPTY
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on queue: A
    --------------------------------------------------
    FRONT ITEM IN QUEUE: A
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on queue: B
    --------------------------------------------------
    FRONT ITEM IN QUEUE: A
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 1
    Enter new text to push on queue: C
    --------------------------------------------------
    FRONT ITEM IN QUEUE: A
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 2
    Popped top item off queue
    --------------------------------------------------
    FRONT ITEM IN QUEUE: B
    --------------------------------------------------
    0. Quit
    1. PUSH item
    2. POP item
    >> 0
    
    --------------------------------------------------
    FRONT OF QUEUE: B
    FRONT OF QUEUE: C
    
    THE END
    

    Look out for the comment lines in the code that give instructions on what to do.

    Utilize the STL queue class (https://cplusplus.com/reference/queue/queue/) for this program. You'll create a queue, use push, pop, and front functions to interface with it.


  • graded_program
    (8:00) Added apple @ $0.76 to shelves.
    (8:05) Added avocado @ $2.14 to shelves.
    (8:10) Added banana @ $0.24 to shelves.
    (8:15) Added watermelon @ $12.59 to shelves.
    (8:20) Customer Maribel entered store.
    (8:25) Customer Maribel adds apple to their cart.
    (8:30) Customer Maribel adds apple to their cart.
    (8:35) Customer Maribel decides to put apple back.
    (8:40) Customer Maribel adds watermelon to their cart.
    (8:45) Customer Sara entered store.
    (8:50) Customer Sara adds apple to their cart.
    (8:55) Customer Sara queues up in the checkout line.
    (9:00) Customer Cody entered store.
    (9:05) Customer Cody adds watermelon to their cart.
    (9:10) Customer Cody adds watermelon to their cart.
    (9:15) Customer Cody adds watermelon to their cart.
    (9:20) Customer Cody decides to put watermelon back.
    (9:25) Customer Cody queues up in the checkout line.
    (9:30) Customer Rai entered store.
    (9:35) Customer Rai adds avocado to their cart.
    (9:40) Customer Rai adds apple to their cart.
    (9:45) Customer Rai adds watermelon to their cart.
    (9:50) Customer Maribel queues up in the checkout line.
    (9:55) Customer Rai queues up in the checkout line.
    (10:00) Customer Sara is at the register.
    (10:05)   >> apple ($0.76) is their next item.
    (10:10)   >> Checkout done. Total: $0.76... Customer Sara leaves.
    (10:15) Customer Cody is at the register.
    (10:20)   >> watermelon ($12.59) is their next item.
    (10:25)   >> watermelon ($12.59) is their next item.
    (10:30)   >> Checkout done. Total: $25.18... Customer Cody leaves.
    (10:35) Customer Maribel is at the register.
    (10:40)   >> watermelon ($12.59) is their next item.
    (10:45)   >> apple ($0.76) is their next item.
    (10:50)   >> Checkout done. Total: $13.35... Customer Maribel leaves.
    (10:55) Customer Rai is at the register.
    (11:00)   >> watermelon ($12.59) is their next item.
    (11:05)   >> apple ($0.76) is their next item.
    (11:10)   >> avocado ($2.14) is their next item.
    (11:15)   >> Checkout done. Total: $15.49... Customer Rai leaves.
    (11:20) Done processing line!
    

    This program simulates events in a grocery store. The events.txt contains a series of commands, though you don't have to worry about parsing the file. You only need to implement functionality in the GroceryStoreProgram.cpp file.

    You will need to keep in mind these structures as you work on your class:

    Product.h:

    struct Product
    {
        string name;
        float price;
    };
    

    Customer.h: Each Customer has a name and a stack of products_in_cart. Sometimes, customers will put something in their cart and then change their mind. When this happens, the item at the top of the stack will be popped to be removed.

    struct Customer
    {
        string name;
        stack<string> products_in_cart;
    };
    

    GroceryStoreProgram.h:

    class GroceryStoreProgram
    {
    public:
        GroceryStoreProgram();
    
        void Stock( string product, float price );
        void CustomerEnterStore( string customer );
        void CustomerCartAdd( string customer, string product );
        void CustomerOops( string customer );
        void CustomerLineup( string customer );
        void Process();
        void PrintTimestamp();
    
    private:
        queue<Customer> checkout_queue;
        map<string, float> product_prices;
        map<string, Customer> customers_in_store;
        int minutes, hours;
    
        friend void Tester_GroceryStoreProgram();
    };
    

    The grocery store program has several important member variables:

    • queue<Customer> checkout_queue is the queue of customers in line waiting to check out. The customer at the front of the line gets helped first.
    • map<string, float> product_prices is a map of product names -> product prices. This can be used to look up the price of a product.
    • map<string, Customer> customers_in_store is a map of all customers in the store. The customer's name is the key, and then a Customer object is the value.

    • void GroceryStoreProgram::Stock( string product, float price )

      Use the product as the key and the price as the value, adding the data to the product_prices map.


    • void GroceryStoreProgram::CustomerEnterStore( string customer )

      Use the customer as the key for a new item in the customers_in_store map.


    • void GroceryStoreProgram::CustomerCartAdd( string customer, string product )

      Access the customer at the customer key. The Customer object contains a products_in_cart stack, push the new product onto that stack.


    • void GroceryStoreProgram::CustomerOops( string customer )

      Access the customer at the customer key. The Customer object contains a products_in_cart stack, use the pop function to remove the last-added item from their cart.


    • void GroceryStoreProgram::CustomerLineup( string customer )

      Access the customer at the customer key in the customers_in_store map. Push this customer into the checkout_queue.


    • void GroceryStoreProgram::Process()

      This function will process everybody who is currently in the checkout_queue.

      1. While the checkout queue is not empty:
        1. Display the name of the customer at the front of the checkout queue.
        2. Create a float variable to store the total cost of the transaction, initialize to 0.
        3. For the front customer, while their products_in_cart stack is not empty:
          1. Get the price of the next item - use the product_prices and the key of the product name.
          2. Add the product price to your total variable.
          3. Display the front item in the customer's cart and the price.
          4. Pop the top item from the products_in_cart.
        4. After the while loop, display that checkout is done and the total amount of money.
        5. Pop the customer from the checkout queue.

      Hints:

      • You can get the next customer in line with: checkout_queue.front().
      • You can access Customer member variables further: checkout_queue.front().name
      • You can access the next customer's next item in cart with: checkout_queue.front().products_in_cart.top()
      • You can get the price of an item with product_prices[ KEY ]. The key will be the product name, which corresponds to checkout_queue.front().products_in_cart.top().

      Once everything is implemented, all the tests should pass. You can also run the program itself to see the "simulation" run.


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

    \newpage

WEEK 4 - FEB 5

UNIT 08: Recursion

πŸ“–οΈ Reading - Recursion (U08.READ)

reading_u08_Recursion_recursionB.png

  • Examples of recursion in math

    Recursion is a manner of solving a problem by breaking it down into smaller pieces - and those smaller pieces are solved in the same way as the big-picture version.

    • Summation:

      A summation can be broken down into smaller chunks but using the same structure as the original.

      Let's say we have \[ \sum_{i=1}^{6} { i } = 1 + 2 + 3 + 4 + 5 + 6 \]

      The summation can be redefined recursively:

      \[ \sum_{i=1}^{6} { i } = 6 + \sum_{i=1}^{5} { i } \]

      The summation from \(i = 1\) to \(6\) is equivalent to whatever the sum is at \(i=6\), plus the sum from \(i = 1\) to \(5\). We can continue this way until we get to a case that we know (e.g., \(\sum_{i=1}^{1} { i }\)).

      • \(\sum_{i=1}^{6} { i } = 6 + \sum_{i=1}^{5} { i }\)
      • \(\sum_{i=1}^{5} { i } = 5 + \sum_{i=1}^{4} { i }\)
      • \(\sum_{i=1}^{4} { i } = 4 + \sum_{i=1}^{3} { i }\)
      • \(\sum_{i=1}^{3} { i } = 3 + \sum_{i=1}^{2} { i }\)
      • \(\sum_{i=1}^{2} { i } = 2 + \sum_{i=1}^{1} { i }\)
      • We know that \(\sum_{i=1}^{1} { i } = 1\), then we move back up to sub out this value.
      Recursive problem Finding the solution
      A. \[\sum^{6}_{i=1} { i } = 6 + \sum_{i=1}^{5} { i }\] But what is \(\sum_{i=1}^{5} { i }\)? \(\downarrow\) K. DONE! \[\sum_{i=1}^{6} { i } = 6 + \sum_{i=1}^{5} { i } = 6 + 15 = 21\]
      B. \[\sum_{i=1}^{5} { i } = 5 + \sum_{i=1}^{4} { i }\] But what is \(\sum_{i=1}^{4} { i }\)? \(\downarrow\) J. \(\uparrow\) \[\sum_{i=1}^{5} { i } = 5 + \sum_{i=1}^{4} { i } = 5 + 10 = 15\]
      C. \[\sum_{i=1}^{4} { i } = 4 + \sum_{i=1}^{3} { i }\] But what is \(\sum_{i=1}^{3} { i }\)? \(\downarrow\) I. \(\uparrow\) \[\sum_{i=1}^{4} { i } = 4 + \sum_{i=1}^{3} { i } = 4 + 6 = 10\]
      D. \[\sum_{i=1}^{3} { i } = 3 + \sum_{i=1}^{2} { i }\] But what is \(\sum_{i=1}^{2} { i }\)? \(\downarrow\) H. \(\uparrow\) \[\sum_{i=1}^{3} { i } = 3 + \sum_{i=1}^{2} { i } = 3 + 3 = 6\]
      E. \[\sum_{i=1}^{2} { i } = 2 + \sum_{i=1}^{1} { i }\] But what is \(\sum_{i=1}^{1} { i }\)? \(\downarrow\) G. \(\uparrow\) \[\sum_{i=1}^{2} { i } = 2 + \sum_{i=1}^{1} { i } = 2 + 1 = 3\]
      F. \[\sum_{i=1}^{1} { i } = 1\] \(\uparrow\)
      Now we start substituting this value, from bottom-up… \(\rightarrow\) \(\uparrow\)

    • Factorials:

      With a factorial of \(n\), written \(n!\) the formula to solve this is:

      \[ n! = n \cdot (n-1) \cdot ... \cdot 3 \cdot 2 \cdot 1 \]

      So,

      • \(2!\) is \(2 \cdot 1\),
      • \(3!\) is \(3 \cdot 2 \cdot 1\),
      • \(4!\) is \(4 \cdot 3 \cdot 2 \cdot 1\), and so on.

      Additionally, we can break down each of these equations: \(3!\) is equivalent to \(3 \cdot 2!\) \(4!\) is equivalent to \(4 \cdot 3!\) … \(n!\) is equivalent to \(n \cdot (n-1)!\)

      Thinking of breaking down the problem here in this way is looking at it recursively.

      reading_u08_Recursion_recursivefactorial.png


  • Recursion in programming

    In programming, we usually approach problems iteratively, using a for-loop or a while-loop:

    // Iterative solution
    int Sum( int n )
    {
        int result = 0;
        for ( int i = 1; i <= n; i++ )
        {
            result += i;
        }
        return result;
    }
    

    Which solves this summation:

    \[ \sum_{i=1}^n {i} \]

    But some types of problems lend themselves better to a recursive solution. To be fair, though, many problems are better solved iteratively. So how do we know which method is better?


    • Recursion basics

      When defining a problem recursively in programming, we need two things:

      1. A terminating case: A case that ends our recursing. Often, this is some known data, something hard-coded. For example with our summation, the terminating case would be that \[ \sum_{i=1}^1 {i} = 1 \] or for a factorial, \(1! = 1\) and \(0! = 1\).
      2. A recursive case: A recursive case is what happens otherwise - if we're not to a solution yet (via the terminating case), we call the same function again, but with updated arguments. For example:
        • Factorial( 4 ) = 4 * Factorial( 3 )
        • Factorial( 3 ) = 3 * Factorial( 2 )
        • Factorial( 2 ) = 2 * Factorial( 1 )
        • Factorial( 1 ) = 1

      We can solve these basic math operations both iteratively and recursively:

      // Iterative solution
      int FactorialI( int n )
      {
          int result = 1;
          for ( int i = 1; i <= n; i++ )
          {
              result *= i;
          }
          return result;
      }
      
      // Recursive solution
      int FactorialR( int n )
      {
          if ( n == 1 || n == 0 ) { return 1; }
          return n * FactorialR( n-1 );
      }
      

    • Breaking down problems into recursive solutions

      reading_u08_Recursion_recursionstress.png

      One of the most challenging parts of recursion, at least for me, is trying to break away from thinking of something in terms of "looping" and figuring out how to think of it "recursively". It's not as natural-feeling, so don't worry if it's confusing at first.

      Let's tackle some basic design problems to practice.

      Summation:
      Try to convert the Summation function to be recursive. Think about what the terminating case would be and the recursive case. Use the Factorial function for reference.
      int SumI( int n )
      {
          int result = 0;
          for ( int i = 1; i <= n; i++ )
          {
              result += i;
          }
          return result;
      }
      
      int SumR( int n )
      {
          // Terminating case?
          // Recursive case?
      }
      
      Solution for recursive summation:
      int SumR( int n )
      {
          if ( n == 1 ) { return 1; }  // Terminating case
          return n + SumR( n-1 );      // Recursive case
      }
      

      Draw a line:
      Now let's make a function that will draw a line of symbols, with a parameter being the length. Iteratively, it could look like this:
      void DrawLineI( int amount )
      {
          for ( int i = 0; i < amount; i++ )
          {
              cout << "-";
          }
      }
      

      How would we repeat this behavior recursively? How do we have a "count up" sort of functionality? What would be the terminating case?

      We're going to think of it a little differently: The recursive function will only output one "-" before it recurses. Each time it recurses, it draws one more dash…

      void DrawLine_Recursive( int amount )
      {
          cout << "-";
          // Recursive case
          DrawLineR( amount );
      }
      

      However, we don't have a terminating case… it will continue looping, but it won't go forever like a bad while loop. We will eventually run out of stack space and the program will encounter a stack overflow and end.

      So what would the terminating case be? How do we adjust the amount each time? Since amount is the one parameter we have, let's have the recursion stop once it is 0. Each time we recurse, we can pass in amount-1 to the next call…

      void DrawLine_Recursive( int amount )
      {
          cout << "-";
      
          // Terminating case
          if ( amount == 0 ) { return; }
      
          // Recursive case
          DrawLineR( amount - 1 );
      }
      

      Counting Up:
      How can we write a function that takes a start and end integer, and outputs each number between them (including the start and end)?

      Iteratively, it could look like this:

      void CountUpI( int start, int end )
      {
          for ( int i = start; i <= end; i++ )
          {
              cout << i << "\t";
          }
      }
      

      Try to fill in this function to build a recursive solution:

      void CountUpR( int start, int end )
      {
      }
      
      
      Solution for recursive count up:
      void CountUpR( int start, int end )
      {
          cout << start << "\t";
      
          // Terminating case
          if ( start == end ) { return; }
      
          // recursive case
          CountUp_Recursive( start+1, end );
      }
      

    • A case for recursion

      Although there are a lot of problems we could convert from an iterative solution to a recursive solution, there are some types of problems that really are better suited to recursion.

      • Searching a File System

        reading_u08_Recursion_filesystem.png

        On a harddrive, we generally have files and folders. Folders can contain files, but they will also contain subfolders as well. And subfolders can each contain their own subfolders.

        When you don't know the exact layout of the filesystem, how would you even begin to iteratively search for a specific file?

        Instead, it is good to think of it recursively. For example, say we're searching for a folder where you store your Recursion homework. We will begin searching at the top-most folder of the computer. The algorithm would then run like…

        1. folder = "C:", 2. folder = "work", 3. folder = "C:"
        reading_u08_Recursion_traverse1.png reading_u08_Recursion_traverse2.png reading_u08_Recursion_traverse1.png
        Is this "Recursion"? No. Is this "Recursion"? No. Are there subfolders? Yes.
        Are there subfolders? Yes. Are there subfolders? No. Then, search next subfolder.
        Then, search first subfolder. Return.  
        4. folder = "school" 5. folder = "cs210" 6. folder = "school"
        reading_u08_Recursion_traverse3.png reading_u08_Recursion_traverse4.png reading_u08_Recursion_traverse5.png
        Is this "Recursion"? No. Is this "Recursion"? No. Are there subfolders? Yes.
        Are there subfolders? Yes. Are there subfolders? No. Then, search next subfolder.
        Then, search next subfolder. Return.  
        7. folder = "cs235" 8. 9.
        reading_u08_Recursion_traverse6.png reading_u08_Recursion_traverse8.png reading_u08_Recursion_traverse9.png
        Is this "Recursion"? No. folder = "Recursion" folder = "cs235"
        Are there subfolders? Yes. Is this "Recursion"? Yes. Return Recursion.
        Then, search the first subfolder. Return Recursion.  
        10. folder = "school" 11. folder = "C:"  
        reading_u08_Recursion_traverse10.png reading_u08_Recursion_traverse11.png  
        Return Recursion. Return Recursion.  

        For find functionality, terminating cases would be:

        1. Have we found the item? Return it.
        2. Are we out of places to search? Return nothing.

        And the recursive case would be:

        1. Has a subfolder? Call Find() on that folder.

  • Review questions:
    1. Recursion of a means of problem solving that…
    2. Each recursive function needs at least one … case and at least one … case.
    3. We are designing a recursive function CountUp( start, end )

      • The function displays start and CountUp( start+1, end )
      • The function returns when start is greater than end.

      What is the terminating case?

    4. We are designing a recursive function to figure out the result of \(n!\)…

      • The result of \(n!\) is equal to \(n\) times \((n-1)!\)
      • The result of \(0!\) is 1.

      What is the terminating case?

    After reading, do the quiz to check your notes:

πŸ§‘β€πŸ”¬ Lab - Recursion (U08.LAB.202401CS250)

pixel-goals.png Goals:

  • Practice writing recursive functions for several different problems.

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Setup: Starter code and new branch

    For this assignment, I've already added the code to your repository.

    1. Pull the starter code:
      1. Check out the main branch: git checkout main
      2. Pull latest: git pull
    2. Create a new branch to start working from: git checkout -b BRANCHNAME

    The u08_Recursion folder contains the following items:

    .
    ├── graded_program
    │   ├── FilesystemNode.h
    │   ├── Functions.cpp
    │   ├── Functions.h
    │   ├── INFO.h
    │   ├── main.cpp
    │   ├── Project_CodeBlocks
    │   │   ├── FilesystemProject.cbp
    │   │   ├── FilesystemProject.depend
    │   │   └── FilesystemProject.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   └── Project_VisualStudio2022
    │       ├── FilesystemProject.sln
    │       ├── FilesystemProject.vcxproj
    │       └── FilesystemProject.vcxproj.filters
    ├── practice1_countup
    │   └── countup.cpp
    ├── practice2_abc
    │   └── abc.cpp
    ├── practice3_countletter
    │   └── countletter.cpp
    └── practice4_factorial
        └── factorial.cpp
    

  • practice1_countup

    CountUp: Displays each number from start to end (inclusive).

    I've already implemented the iterative solution to this using a for loop. You'll be implementing the recursive solution.

    This program doesn't have any unit tests, but the program itself is a manual test - you can visually check if the Recursive output matches the Iterative output.

    CountUp_Iterative( 2, 5 ): 2 3 4 5
    CountUp_Recursive( 2, 5 ): 2 3 4 5
    
    CountUp_Iterative( 10, 20 ): 10 11 12 13 14 15 16 17 18 19 20
    CountUp_Recursive( 10, 20 ): 10 11 12 13 14 15 16 17 18 19 20
    

    Iterative version:

    void CountUp_Iterative( int start, int end )
    {
      for ( int i = start; i <= end; i++ )
      {
        cout << i << " ";
      }
    }
    

    We use a for loop to start a counter i at the start position, loop while i is less than or equal to the end position, and add 1 to i each time. Within the loop, we display the current value of i to the screen.

    Recursive version:

    With a recursive solution we solve a problem by breaking a big problem into a sequence of identical smaller problems. We approach the question

    "What is CountUp( start , end )?"

    with the answer:

    "Well, it's going to be "start" then… CountUp( start+1 , end )!"…

    "Well, what is CountUp( start+1, end )?"

    "It's start+1 then CountUp( start+2 to end )!"…

    and so on until finally the start value is past the end value, then we're done - we terminate.

    We don't need a counter variable i here, instead we move start closer to the end each time we call the CountUp_Recursive function again.

    • Terminating case: If start is greater than end, return - we're done.
    • Recursive case: Display the current value of start, then call CountUp again, passing in start+1 as the beginning and end as the end.

    lab_u08_Recursion_countup.png


  • practice2_abc
    MANUAL TESTS:
    Abc_Iterative( 'A', 'G' ): ABCDEFG
    Abc_Recursive( 'A', 'G' ): ABCDEFG
    
    Abc_Iterative( 'm', 'p' ): mnop
    Abc_Recursive( 'm', 'p' ): mnop
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, Abc_Recursive(a, f, "") = [abcdef]
    [PASS]  TEST 2, Abc_Recursive(C, G, "") = [CDEFG]
    

    string Abc_Recursive( char start, char end, string built_string )

    This function takes in a start, end, and a built_string. The function is responsible for filling the built_string with the letters from start to end (inclusive). A char can be treated like an integer, where adding 1 to a letter (like 'a') will give you the next letter.

    • Terminating case: If the start is greater than the end, then we're done - return the built_string.
    • Recursive case: Add the start character to the built_string string, then call the function again with start+1 to move forward, the same end, and the built_string. (Make sure to put return before the function call, since a value must be returned!)

    lab_u08_Recursion_abc.png


  • practice3_countletter
    MANUAL TESTS:
    CountLetter_Iterative( "Hello dello!", 'e' ): 2
    CountLetter_Recursive( "Hello dello!", 'e' ): 2
    
    CountLetter_Iterative( "abcd", 'z' ): 0
    CountLetter_Recursive( "abcd", 'z' ): 0
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, CountLetter_Recursive("aabbcc", 'a', 0) = [2]
    [PASS]  TEST 2, CountLetter_Recursive("onethree", 'e', 0) = [3]
    [PASS]  TEST 3, CountLetter_Recursive("xyz", 'X', 0) = [0]
    

    lab_u08_Recursion_countletter.png

    int CountLetter_Recursive( string text, char letter_to_count, size_t i )

    This function takes in a line of text and has a letter_to_count. When it finds that letter in the string, it will add one to the result. The i variable is used as a counter, the current letter we're looking at in the text string.

    • Terminating case: If i is greater than or equal to text.size(), then we're done with the string. Return 0 in this case.
    • Recursive case:
      • a. If text[i] is equal to the letter_to_count, then return 1, plus another call to the function, passing in text, letter, and i+1.
      • b. Otherwise, (text[i] is not equal to the letter_to_count) return the function call, passing in text, letter, and i+1. (I like to put a 0 + at the start to mirror the other case and to be explicit that no additional letter is being counted, but the 0 + is not required. I just think it helps readability.)

  • practice4_factorial
    MANUAL TESTS:
    Factorial_Iterative( 0 ): 1
    Factorial_Recursive( 0 ): 1
    
    Factorial_Iterative( 1 ): 1
    Factorial_Recursive( 1 ): 1
    
    Factorial_Iterative( 3 ): 6
    Factorial_Recursive( 3 ): 6
    
    Factorial_Iterative( 5 ): 120
    Factorial_Recursive( 5 ): 120
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, Factorial_Recursive(0) = [1]
    [PASS]  TEST 2, Factorial_Recursive(1) = [1]
    [PASS]  TEST 3, Factorial_Recursive(3) = [6]
    [PASS]  TEST 4, Factorial_Recursive(5) = [120]
    

    int Factorial_Recursive( int n )

    This function takes in n, and returns the result of \(n!\), where \[n! = n \cdot (n-1) \cdot (n-2) \cdot ... \cdot 3 \cdot 2 \cdot 1\].

    Note that \(n! = 0\) and \(n! = 1\). These make up our terminating case.

    • Terminating case: If n is 0 or 1, then return 1.
    • Recursive case: Return n times the function call for Factorial(n-1).

    lab_u08_Recursion_factorial.png


  • Graded program
    /
     home/
      school/
       english/
        essay1.txt
        essay2.txt
       compsci/
        homeworkA.cpp
        homeworkB.cpp
      projects/
       my_game/
        game.cpp
    
    Enter name of FILE or FOLDER to find, or QUIT to stop: game.cpp
    Path: /home/projects/my_game/game.cpp
    

    This program creates a representation of a filesystem on your computer. I've created a FilesystemNode object, which contains the following:

    enum class NodeType {
      FOLDER,
      FILE
    };
    
    struct FilesystemNode
    {
      FilesystemNode() { }
      FilesystemNode( string new_name, NodeType new_node_type )
      {
        name = new_name;
        node_type = new_node_type;
      }
    
      //! Name of this file/folder
      string name;
      //! Is this a folder or file?
      NodeType node_type;
      //! If this is a folder it may contain files and folders
      vector<shared_ptr<FilesystemNode>> contents;
    };
    

    Within Functions.cpp, a basic filesystem is created like what is shown in the example output above.

    The program displays the filesystem using a recursive function, void DisplayContents( shared_ptr<FilesystemNode> current_node, int tab ), which I've already implemented.

    The program will also allow the user to search the filesystem for a folder or file that matches the search text they enter. This function is also recursive, and you will be implementing it.

    string FindFile( shared_ptr<FilesystemNode> current_node, string find_me )

    Input parameter Description
    current_node Pointer to the current file/folder node we're looking at
    find_me Partial name of file/folder we're looking for

    Returns the path to the file/folder we're searching for, uses recursion.

    1. If the current node's name has a partial match with the find_me parameter, then RETURN this current node's name as the result. To do a partial string match:

      if ( current_node->name.find( find_me ) != string::npos )
      
    2. Afterwards, use a loop to iterate over all of the current node's contents. Within the loop: RECURSE to the FindFile function, passing in this CHILD node and the find_me data. STORE the result in a string variable. IF the result is NOT an empty string "", then we've found the file: Return current_node->name + result
    3. After the for loop, this means nothing was found in this recursive branch. In this case, just return an empty string "".

    Once finished, run the Automated tests to verify that your function works properly. You can also run the Program itself to test the functionality manually.


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

πŸ‹οΈ Exercise - Debugging arrays and pointers (U08.EXE)

UNIT 09: Searching and sorting

πŸ“–οΈ Reading - Searching and sorting (U09.READ)

  • Introduction to searching and sorting

    reading_u09_SearchSort_algorithms.png

    Searching and sorting algorithms are things that we'll study in college, and perhaps see during job interviews, but for the most part in the daily life of an average developer we may not be implementing these algorithms from scratch.

    Like data structures, these algorithms have already been implemented and are available in various forms, usually already tested and optimized by someone else, and you can use them in your programs by adding in third party libraries.

    I'm just saying this because a some of this functionality of sorting algorithms may not make "intuitive" sense, and I find the code pretty ugly for a lot of these, but you won't have to worry about these in the real world.


  • Searching algorithms

    reading_u09_SearchSort_goose.png

    When we're working with structures of data - such as an array - we often need to find information stored within. Perhaps we know the element or a piece of data we want to find, but we need to get the positional index of where it's located in the array. This is where our search algorithms come in handy.

    • Linear search

      Pseudocode:

      LinearSearch (
          Inputs: array, findme
          Outputs: index of found item, or -1 if not found
        )
      
      begin function:
      
        for i begins at 0, loop while i < array size, update i++ each loop
          if array[i] matches findme, return i
        end for
      
        return -1
      
      end function
      

      When we're searching for items in an unsorted linear structure there's not much we can do to speed up the process. We can basically either start at the beginning and move forward checking each item in the structure for what you're looking for.

      We begin at the first index 0 and iterate until we hit the last index. Within the loop, if the element at index i matches what we're looking for, we return this index.

      If the loop completes and we haven't returned an index yet that means we've searched the entire structure and have not found the item. In this case, it is not in the structure and we can throw an exception to be dealt with elseware or return something like -1 to symbolize "no valid index".

      This search algorithm's growth rate is \(O(n)\) – the more items in the structure, the time linearly increases to search through it. Not much we can do about that, which is why we have different types of data structures that sort data as it is inserted - more on those later on.

      Additional documentation: https://en.wikipedia.org/wiki/Linear_search


    • Binary search

      Pseudocode:

      BinarySearch (
          Inputs: array, findme
          Outputs: index of found item, or -1 if not found
          Note: array MUST BE SORTED
        )
      
      begin function:
      
        left = 0
        right = array size - 1
      
        while left <= right:
          mid = left + ( right - left ) / 2
      
          if ( array[mid] == findme ): return mid
          else if ( array[mid] < findme): left = mid+1;
          else if ( array[mid] > findme): right = mid-1;
        end while
      
        return -1
      
      end function
      

      A binary search allows for a faster search - if the array passed in is sorted (otherwise it won't work!). The binary search cuts its search range in half each iteration through the loop, thus giving us a \(log(n)\) growth rate.

      Additional documentation: https://en.wikipedia.org/wiki/Binary_search_algorithm


  • Sorting algorithms
    • Selection sort

      Pseudocode:

      SelectionSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        for i begins at 0, loop while i < array size - 1, update i++ each loop:
          minindex = i
      
          for j begins at i+1, loop while j < array size, update j++ each loop:
            if ( array[j] < array[minindex] ): minindex = j
          end for
      
          if ( minindex != i ): swap( array[i], array[minindex] )
      
        end for
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Selection_sort


    • Bubble sort

      Pseudocode:

      BubbleSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        for i begins at 0, loop while i < array size - 1, update i++ each loop:
      
          for j begins at 0, loop while j < array size - i - 1, update j++ each loop:
            if ( array[j] > array[j+1] ): swap( array[j], array[j+1] )
          end for
      
        end for
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Bubble_sort


    • Insertion sort

      Pseudocode:

      InsertionSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        i = 1
        while ( i < array size ):
          j = i
      
          while ( j > 0 and array[j-1] > array[j] ):
            swap( array[j], array[j-1] )
            j--;
          end while
      
          i++;
        end while
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Insertion_sort


    • Quick sort

      Pseudocode:

      Partition (
          Inputs: array, lowindex, highindex
          Outputs: an index value
        )
      
      begin function:
      
        pivotvalue = array[high]
        i = low - 1
      
        for j begins at low, loop while j <= high - 1, update j++ each loop:
          if ( array[j] <= pivotvalue ) then:
            i++
            swap( array[i], array[j] )
          end if
        end for
      
        swap( array[i+1], array[high] )
        return i+1
      
      end function
      
      ------------------------------------------------------
      
      QuickSort_Recursive (
          Inputs: array, lowindex, highindex
          Outputs: void
        )
      
      begin function:
      
        if ( low < high ) then:
          partitionindex = Partition( array, low, high )
          QuickSort_Recursive( array, low, partitionindex - 1 )
          QuickSort_Recursive( array, partitionindex + 1, high )
        end if
      
      end function
      
      ------------------------------------------------------
      
      QuickSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        QuickSort_Recursive( array, 0, array size - 1 )
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Quicksort


    • Merge sort

      Pseudocode: (Adapted from https://www.programiz.com/dsa/merge-sort)

      MergeParts (
          Inputs: array, left, mid, right
          Outputs: void
        )
      
      begin function:
        n1 = mid - left + 1
        n2 = right - mid
      
        leftarr is a new array
        rightarr is a new array
      
        for i begins at 0, loops while i < n1, updates i++ each loop:
          leftarr push array[ left + i ]
        end for
      
        for j begins at 0, loops while j < n2, updates j++ each loop:
          rightarr push array[ mid + 1 + j ]
        end for
      
        i = 0
        j = 0
        k = left
      
        while ( i < n1 and j < n2 ):
          if ( leftarr[i] <= rightarr[j] ) then:
            array[k] = leftarr[i]
            i++
          else:
            array[k] = rightarr[j]
            j++
          end if
      
          k++
        end while
      
        while ( i < n1 ):
          array[k] = leftarr[i]
          i++
          k++
        end while
      
        while ( j < n2 ):
          array[k] = rightarr[j]
          j++
          k++
        end while
      
      
      end function
      
      ------------------------------------------------------
      
      MergeSort_Recursive (
          Inputs: array, left, right
          Outputs: void
        )
      
      begin function:
      
        if ( left < right ) then:
          mid = floor( left + right ) / 2
      
          MergeSort_Recursive( array, left, mid )
          MergeSort_Recursive( array, mid+1, right )
          Merge( array, left, mid, right )
        end if
      
      end function
      
      ------------------------------------------------------
      
      MergeSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        MergeSort_Recursive( array, 0, array size - 1 )
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Merge_sort


    • Heap sort

      Pseudocode: (Adapted from https://en.wikipedia.org/wiki/Heapsort)

      LeftChildIndex (
          Inputs: index
          Outputs: where this index's left child is
        )
      
      begin function:
        return 2 * index + 1
      end function
      
      ------------------------------------------------------
      
      RightChildIndex (
          Inputs: index
          Outputs: where this index's right child is
        )
      
      begin function:
        return 2 * index + 2
      end function
      
      ------------------------------------------------------
      
      ParentIndex (
          Inputs: index
          Outputs: where this index's parent is
        )
      
      begin function:
        return floor( ( index - 1 ) / 2 )
      end function
      
      ------------------------------------------------------
      
      SiftDown (
          Inputs: array, root, end
          Outputs: void
        )
      
      begin function:
      
        while ( LeftChildIndex( root ) < end ):
      
          child = LeftChildIndex( root )
          if ( child + 1 < end and array[child] < array[child+1] ): child++
      
          if ( array[root] < array[child] ) then:
            swap( array[root], array[child] )
            root = child
          else:
            return
          end if
      
        end while
      
      end function
      
      ------------------------------------------------------
      
      Heapify (
          Inputs: array, end
          Outputs: void
        )
      
      begin function:
      
        start = ParentIndexx( end-1 ) + 1
      
        while ( start > 0 ):
          start--
          SiftDown( array, start, end )
        end while
      
      end function
      
      ------------------------------------------------------
      
      HeapSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        Heapify( array, array size )
        end = array size
      
        while ( end > 1 ):
          end--
          swap( array[end], array[0] )
          SiftDown( array, 0, end )
        end while
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Heapsort


    • Radix sort

      Pseudocode: (Adapted from https://www.geeksforgeeks.org/radix-sort/)

      CountSort (
          Inputs: array, n, exp
          Outputs: void
        )
      
      begin function:
        output is an int array of size n (create as dynamic array)
        count is an int array of size 10
        count[0] = 0
      
        for i starts at 0, loops while i < n, updates i++ each loop:
          count[ ( array[i] / exp ) % 10 ]++
        end for
      
        for i starts at 1, loops while i < 10, updates i++ each loop:
          count[i] += count[i-1]
        end for
      
        for i starts at n-1, loops while i >= 0, updates i-- each loop:
          output[ count[ ( array[i] / exp ) % 10 ] - 1 ] = array[i]
          count[ ( array[i] / exp ) % 10 ]--
        end for
      
        for i starts at 0, loops while i < n, updates i++ each loop:
          array[i] = output[i]
        end for
      end function
      
      ------------------------------------------------------
      
      GetMax (
          Inputs: array
          Outputs: the value of the largest item in the array
        )
      
      begin function:
      
        maxvalue = array[0]
      
        for i starts at 1, loops while i < array size, updates i++ each loop:
          if ( array[i] > maxvalue ): maxvalue = array[i]
        end for
      
        return maxvalue
      
      end function
      
      ------------------------------------------------------
      
      RadixSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        maxvalue = GetMax( array )
      
        for exp starts at 1, loop while maxvalue / exp > 0, update exp *= 10 each loop:
          CountSort( array, array size, exp )
        end for
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Radix_sort

      After reading, do the quiz to check your notes: https://canvas.jccc.edu/courses/68318/modules/items/3910963

πŸ§‘β€πŸ”¬ Lab - Searching and sorting (U09.LAB.202401CS250)

pixel-goals.png Goals:

  • Practice with the searching algorithm: Linear Search
  • Practice with the sorting algorithms: Selection Sort, Bubble Sort, Insertion Sort
  • Implement the searching algorithm: Binary Search
  • Implement the sorting algorithms: Heap Sort, Merge Sort, Quick Sort, Radix Sort
  • Compare speeds of each algorithm

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Setup: Starter code and new branch

    For this assignment, I've already added the code to your repository.

    1. Pull the starter code:
      1. Check out the main branch: git checkout main
      2. Pull latest: git pull
    2. Create a new branch to start working from: git checkout -b BRANCHNAME

    The u09_SearchingSorting folder contains the following items:

    .
    ├── graded_program
    │   ├── Functions.cpp
    │   ├── Functions.h
    │   ├── INFO.h
    │   ├── main.cpp
    │   ├── Project_CodeBlocks
    │   │   ├── SearchSortProject.cbp
    │   │   ├── SearchSortProject.depend
    │   │   ├── SearchSortProject_exe
    │   │   └── SearchSortProject.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   ├── Searching
    │   │   ├── BinarySearch.cpp
    │   │   └── LinearSearch.cpp
    │   ├── SearchSortProject
    │   │   ├── SearchSortProject.sln
    │   │   ├── SearchSortProject.vcxproj
    │   │   └── SearchSortProject.vcxproj.filters
    │   └── Sorting
    │       ├── HeapSort.cpp
    │       ├── MergeSort.cpp
    │       ├── QuickSort.cpp
    │       ├── RadixSort.cpp
    │       └── SelectionSort.cpp
    ├── practice1_linearsearch
    │   └── linearsearch.cpp
    ├── practice2_selectionsort
    │   └── selectionsort.cpp
    ├── practice3_bubblesort
    │   └── bubblesort.cpp
    └── practice4_insertionsort
        └── insertionsort.cpp
    

  • practice1_linearsearch
    MANUAL TESTS:
     Array:	{ 0="ice cream", 1="cake", 2="cookies", 3="pie" }
     Search for "cookies":  2
     Search for "rasgulla": -1
    
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, LinearSearch( { 0="dog", 1="rat", 2="cat" }, "cat" ) = 2
    [PASS]  TEST 2, LinearSearch( { 0="Anuj", 1="Rai", 2="Asha" }, "Rai" ) = 1
    [PASS]  TEST 3, LinearSearch( { 0="One", 1="Two", 2="Three" }, "Four" ) = -1
    

    For this practice you'll implement the LinearSearch function.

    • Inputs: This function takes in arr, a vector of strings to search through,

    and find_me, the item we're looking for.

    • Output: Returns the index of the found item, or -1 if not found.

    A linear search is a simple search. Use a loop to iterate over all the letters of the arr vector. For each element, check to see if arr[i] is equal to find_me. If it matches, return i, the index it was found at. (No ELSE case is needed INSIDE the for loop!)

    If the for loop finishes, that means we searched the entire vector and no match was found. In this case, before the function ends, return -1 to represent "not found".


  • practice2_selectionsort
    
    
    MANUAL TESTS:
     Original:	{ 0="ice cream", 1="cake", 2="cookies", 3="pie" }
    
     After sort:	{ 0="cake", 1="cookies", 2="ice cream", 3="pie" }
    
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, SelectionSort( { 0="c", 1="a", 2="t" } ) =
      { 0="a", 1="c", 2="t" }
    [PASS]  TEST 2, SelectionSort( { 0="h", 1="e", 2="l", 3="l", 4="o" } ) =
      { 0="e", 1="h", 2="l", 3="l", 4="o" }
    

    Additional information: https://en.wikipedia.org/wiki/Selection_sort#Implementations

    Pseudocode:

    SelectionSort (
        Inputs: array
        Outputs: void
      )
    
    begin function:
    
      for i begins at 0, loop while i < array size - 1, update i++ each loop:
        minindex = i
    
        for j begins at i+1, loop while j < array size, update j++ each loop:
          if ( array[j] < array[minindex] ): minindex = j
        end for
    
        if ( minindex != i ): swap( array[i], array[minindex] )
    
      end for
    
    end function
    

    Translate the pseudocode into C++. You can also view other implementations of each sorting algorithm for reference.


  • practice3_bubblesort
    MANUAL TESTS:
     Original:	{ 0="ice cream", 1="cake", 2="cookies", 3="pie" }
    
     After sort:	{ 0="cake", 1="cookies", 2="ice cream", 3="pie" }
    
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, BubbleSort( { 0="c", 1="a", 2="t" } ) =
      { 0="a", 1="c", 2="t" }
    [PASS]  TEST 2, BubbleSort( { 0="h", 1="e", 2="l", 3="l", 4="o" } ) =
      { 0="e", 1="h", 2="l", 3="l", 4="o" }
    

    Additional information: https://en.wikipedia.org/wiki/Bubble_sort

    Pseudocode:

    BubbleSort (
        Inputs: array
        Outputs: void
      )
    
    begin function:
    
      for i begins at 0, loop while i < array size - 1, update i++ each loop:
    
        for j begins at 0, loop while j < array size - i - 1, update j++ each loop:
          if ( array[j] > array[j+1] ): swap( array[j], array[j+1] )
        end for
    
      end for
    
    end function
    

  • practice4_insertionsort
    MANUAL TESTS:
     Original:	{ 0="ice cream", 1="cake", 2="cookies", 3="pie" }
    
     After sort:	{ 0="cake", 1="cookies", 2="ice cream", 3="pie" }
    
    
    AUTOMATED TESTS:
    [PASS]  TEST 1, InsertionSort( { 0="c", 1="a", 2="t" } ) =
      { 0="a", 1="c", 2="t" }
    [PASS]  TEST 2, InsertionSort( { 0="h", 1="e", 2="l", 3="l", 4="o" } ) =
      { 0="e", 1="h", 2="l", 3="l", 4="o" }
    

    Additional information: https://en.wikipedia.org/wiki/Insertion_sort

    Pseudocode:

    InsertionSort (
        Inputs: array
        Outputs: void
      )
    
    begin function:
    
      i = 1
      while ( i < array size ):
        j = i
    
        while ( j > 0 and array[j-1] > array[j] ):
          swap( array[j], array[j-1] )
          j--;
        end while
    
        i++;
      end while
    
    end function
    

  • Graded program
    2. Run PROGRAMS
    >> 2
    Generating 100000 pieces of data... 4 milliseconds
    
    Running Merge sort, please wait...
    Completed in 114 milliseconds
    
    Running Quick sort, please wait...
    Completed in 22 milliseconds
    
    Running Radix sort, please wait...
    Completed in 13 milliseconds
    
    Running Heap sort, please wait...
    Completed in 43 milliseconds
    
    Running Selection sort, please wait...
    Completed in 25686 milliseconds
    
    Running Linear search, please wait...
    Completed in 0 milliseconds
    
    Running Binary search, please wait...
    Completed in 0 milliseconds
    

    This program creates a bunch of data to sort and running it will show you how long each algorithm takes to complete. Every computer will have different results. You can also use the automated tests to verify your function implementations as well.

    You'll be editing the following files:

    • Sorting/
      • HeapSort.cpp
      • MergeSort.cpp
      • QuickSort.cpp
      • RadixSort.cpp
    • Searching/
      • BinarySearch.cpp

    Some sorting algorithms contain multiple functions for different steps of the process. Pseudocode will be provided for each.

    (You do NOT need to have these algorithms memorized or really to understand them. You might encounter them later on in other bachelors level CS classes so it's good to be familiar with them. You can look these algorithms up anywhere, it's not like they're secret knowledge.)


    • QuickSort

      Pseudocode:

      Partition (
          Inputs: array, lowindex, highindex
          Outputs: an index value
        )
      
      begin function:
      
        pivotvalue = array[high]
        i = low - 1
      
        for j begins at low, loop while j <= high-1, update j++ each loop:
          if ( array[j] <= pivotvalue ) then:
            i++
            swap( array[i], array[j] )
          end if
        end for
      
        swap( array[i+1], array[high] )
        return i+1
      
      end function
      
      ------------------------------------------------------
      
      QuickSort_Recursive (
          Inputs: array, lowindex, highindex
          Outputs: void
        )
      
      begin function:
      
        if ( low < high ) then:
          partitionindex = Partition( array, low, high )
          QuickSort_Recursive( array, low, partitionindex - 1 )
          QuickSort_Recursive( array, partitionindex + 1, high )
        end if
      
      end function
      
      ------------------------------------------------------
      
      QuickSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        QuickSort_Recursive( array, 0, array size - 1 )
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Quicksort


    • MergeSort

      Pseudocode: (Adapted from https://www.programiz.com/dsa/merge-sort)

      MergeParts (
          Inputs: array, left, mid, right
          Outputs: void
        )
      
      begin function:
        n1 = mid - left + 1
        n2 = right - mid
      
        leftarr is a new array
        rightarr is a new array
      
        for i begins at 0, loops while i < n1, updates i++ each loop:
          leftarr push array[ left + i ]
        end for
      
        for j begins at 0, loops while j < n2, updates j++ each loop:
          rightarr push array[ mid + 1 + j ]
        end for
      
        i = 0
        j = 0
        k = left
      
        while ( i < n1 and j < n2 ):
          if ( leftarr[i] <= rightarr[j] ) then:
            array[k] = leftarr[i]
            i++
          else:
            array[k] = rightarr[j]
            j++
          end if
      
          k++
        end while
      
        while ( i < n1 ):
          array[k] = leftarr[i]
          i++
          k++
        end while
      
        while ( j < n2 ):
          array[k] = rightarr[j]
          j++
          k++
        end while
      
      
      end function
      
      ------------------------------------------------------
      
      MergeSort_Recursive (
          Inputs: array, left, right
          Outputs: void
        )
      
      begin function:
      
        if ( left < right ) then:
          mid = floor( left + right ) / 2
      
          MergeSort_Recursive( array, left, mid )
          MergeSort_Recursive( array, mid+1, right )
          Merge( array, left, mid, right )
        end if
      
      end function
      
      ------------------------------------------------------
      
      MergeSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        MergeSort_Recursive( array, 0, array size - 1 )
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Merge_sort


    • HeapSort

      Pseudocode: (Adapted from https://en.wikipedia.org/wiki/Heapsort)

      LeftChildIndex (
          Inputs: index
          Outputs: where this index's left child is
        )
      
      begin function:
        return 2 * index + 1
      end function
      
      ------------------------------------------------------
      
      RightChildIndex (
          Inputs: index
          Outputs: where this index's right child is
        )
      
      begin function:
        return 2 * index + 2
      end function
      
      ------------------------------------------------------
      
      ParentIndex (
          Inputs: index
          Outputs: where this index's parent is
        )
      
      begin function:
        return floor( ( index - 1 ) / 2 )
      end function
      
      ------------------------------------------------------
      
      SiftDown (
          Inputs: array, root, end
          Outputs: void
        )
      
      begin function:
      
        while ( LeftChildIndex( root ) < end ):
      
          child = LeftChildIndex( root )
          if ( child + 1 < end and array[child] < array[child+1] ): child++
      
          if ( array[root] < array[child] ) then:
            swap( array[root], array[child] )
            root = child
          else:
            return
          end if
      
        end while
      
      end function
      
      ------------------------------------------------------
      
      Heapify (
          Inputs: array, end
          Outputs: void
        )
      
      begin function:
      
        start = ParentIndexx( end-1 ) + 1
      
        while ( start > 0 ):
          start--
          SiftDown( array, start, end )
        end while
      
      end function
      
      ------------------------------------------------------
      
      HeapSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        Heapify( array, array size )
        end = array size
      
        while ( end > 1 ):
          end--
          swap( array[end], array[0] )
          SiftDown( array, 0, end )
        end while
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Heapsort


    • RadixSort

      Radix sort

      Pseudocode: (Adapted from https://www.geeksforgeeks.org/radix-sort/)

      CountSort (
          Inputs: array, n, exp
          Outputs: void
        )
      
      begin function:
        output is an int array of size n (might need to be a dynamic array)
        count is an int array of size 10
        count[0] = 0
      
        for i starts at 0, loops while i < n, updates i++ each loop:
          count[ ( array[i] / exp ) % 10 ]++
        end for
      
        for i starts at 1, loops while i < 10, updates i++ each loop:
          count[i] += count[i-1]
        end for
      
        for i starts at n-1, loops while i >= 0, updates i-- each loop:
          output[ count[ ( array[i] / exp ) % 10 ] - 1 ] = array[i]
          count[ ( array[i] / exp ) % 10 ]--
        end for
      
        for i starts at 0, loops while i < n, updates i++ each loop:
          array[i] = output[i]
        end for
      end function
      
      ------------------------------------------------------
      
      GetMax (
          Inputs: array
          Outputs: the value of the largest item in the array
        )
      
      begin function:
      
        maxvalue = array[0]
      
        for i starts at 1, loops while i < array size, updates i++ each loop:
          if ( array[i] > maxvalue ): maxvalue = array[i]
        end for
      
        return maxvalue
      
      end function
      
      ------------------------------------------------------
      
      RadixSort (
          Inputs: array
          Outputs: void
        )
      
      begin function:
      
        maxvalue = GetMax( array )
      
        for exp starts at 1, loop while maxvalue / exp > 0, update exp *= 10 each loop:
          CountSort( array, array size, exp )
        end for
      
      end function
      

      Additional documentation: https://en.wikipedia.org/wiki/Radix_sort


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

🧠 Tech Literacy - Problem solving (U09.TEC)

Programming can be confusing. We have to worry about design, implementation, debugging, and maintaining code over an extended period of time. No matter what skill level you are at programming, building up your problem solving skills will help you immensely.

Here are a few ways that I approach challenges to help me figure out a solution:

  1. Sketch it on paper - Diagramming parts of a program, or writing down data, or otherwise organizing my thoughts on paper help me visualize the problem. It can also be helpful to step through the code and writing down the values of each variable on paper as you go, to help you figure out the program flow and variable values.
  2. Ask myself "What is the program doing that it shouldn't be doing… or… what is the program not doing that I want it to do?" - Being able to define what exactly is wrong with your program will help you in solving it, or at least talking to someone else to get ideas for how to solve it.
  3. Rubber duck debugging - This is a technique where you talk through the issue with an object, a pet, or another person. Sometimes, just talking through the problem can help you figure out a solution.
  4. Step away - When you feel like you're hitting your head against a wall and just can't think through anything it is time to step away. Your brain is done and you won't come up with any solutions, but even a half hour break can help you come back with a new perspective. Don't try to force yourself through this kind of roadblock - give yourself a break.
  5. Write out the steps in pseudocode - When trying to design something, I will usually write down the items it needs to accomplish as pseudocode, or as a series of function calls or actions that need to occur. Implementation details can come later once I know where I need to go.

Locate the 🧠 Tech Literacy - Problem solving assignment and add to the discussion!

Assignment link: https://canvas.jccc.edu/courses/68318/modules/items/3910996

\newpage

WEEK 5 - FEB 12

UNIT 10: Templates

πŸ“–οΈ Reading - Templates (U10.READ)

  • Before Templates

    Templates don't exist in C++'s precursor, C. Because of this, if you had a function like - for example - SumTwoNumbers

    • that you wanted to work with different data types, you would have to define different functions for each version. C also doesn't have function overloading, so they would have to have different names as well.

    As a real-world example, OpenGL is a cross-platform graphics library that can be used to create 3D graphics. OpenGL was written in C, and you could tell it a set of vertices to draw in order to create one polygon or quad or other shape.

    There were different functions you could use to define points (vertices) in a shape, like:

    • glVertex2f( 0, 0 );
    • glVertex3f( 0, 0, 0 );

    And, in particular, there are a bunch of "glVertex" functions: glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, and so on… (Don't you wish you were programming in C?)

    reading_u10_Templates_ogl.png


  • What are Templates?

    With C++ and other languages like C# and Java, we can now use Templates with our functions and classes. A Template allows us to specify a placeholder for a data type which will be filled in later.

    In the C++ Standard Template Library, there are objects like the vector that is essentially a dynamic array, but it can store any data type - we just have to tell it what it's storing when we declare a vector object:

    vector<int> listOfQuantities;
    vector<float> listOfPrices;
    vector<string> listOfNames;
    

    We can also define our own functions and even classes with templated functions and member variables ourselves, leading to much more reusable code.


    • Templated functions

      We can write a standalone function with templated parameters or a templated return type or both. For example, here's a simple function to add two items together:

      template <typename T>
      T Sum( T numA, T numB )
      {
          return numA + numB;
      }
      

      This function can be called with any data type, so long as the data type has the + operator defined for it - so, if it were a custom class you wrote, you would have to overload the operator+ function.

      What this means is that we can call Sum with integers and floats, but also with someting like a string, since strings use the + operator to combine two strings together.

      Calling the templated function:
      int main()
      {
          int intA = 4, intB = 6;
          float floatA = 3.9, floatB = 2.5;
          string strA = "alpha", strB = "bet";
      
          cout << intA << " + " << intB
              << " = " << Sum( intA, intB ) << endl;
      
          cout << floatA << " + " << floatB
              << " = " << Sum( floatA, floatB ) << endl;
      
          cout << strA << " + " << strB
              << " = " << Sum( strA, strB ) << endl;
      }
      
      Program output:
      4 + 6 = 10
      3.9 + 2.5 = 6.4
      alpha + bet = alphabet
      

    • Templated classes

      More frequently, you will be using templates to create classes for data structures that can store any kind of data. The C++ Standard Template Library has data structures like vector, list, and map, but we can also write our own.

      When creating our templated class, there are a few things to keep in mind:

      1. We need to use template <typename T> at the beginning of the class declaration.
      2. Method definitions must be in the header file - in this case, we won't be putting the method definitions in a separate .cpp file. You can either define the functions inside the class declaration, or immediately after it.
      3. Method definitions also need to be prefixed with template <typename T>.

      If you try to create a "TemplatedArray.h" file and a "TemplatedArray.cpp" file and put your method definitions in the .cpp file, then you're going to get compile errors:

      reading_u10_Templates_undefined-reference.png

      You might think, "Well, that's weird." - and yes, it is. C++ is a strange language with weird behaviors. In this case in particular, you can read about why this is for templates here: https://isocpp.org/wiki/faq/templates%5C#templates-defn-vs-decl

      In short, the template command is used to generate classes, and while our class declaration looks normal, this is actually special code that is just telling the compiler how it's going to generate a family of classes. Because of this, the compiler needs to see the function definitions as well.


  • Example TemplatedArray (Full):

    This is all in one file - TemplatedArray.hpp. I have the class declaration on top, with all the definitions below.

    #ifndef _TEMPLATED_ARRAY
    #define _TEMPLATED_ARRAY
    
    #include <stdexcept>
    using namespace std;
    
    template <typename T>
    class TemplatedArray
    {
    public:
      TemplatedArray();
      TemplatedArray( int size );
      ~TemplatedArray();
    
      void PushToBack( T item );
      void RemoveFromBack();
    
      bool IsFull();
      bool IsEmpty();
    
      void Display();
      int Size();
    
    private:
      void AllocateMemory( int size );
      void DeallocateMemory();
    
      int m_arraySize;
      int m_storedItems;
      T* m_array;
    };
    
    // Constructors/Destructor
    template <typename T>
    TemplatedArray<T>::TemplatedArray()
    {
      m_arraySize = 0;
      m_storedItems = 0;
      // Be safe: Initialize pointers to nullptr.
      m_array = nullptr;
    }
    
    template <typename T>
    TemplatedArray<T>::TemplatedArray( int size )
    {
      m_array = nullptr;
      AllocateMemory( size );
    }
    
    template <typename T>
    TemplatedArray<T>::~TemplatedArray()
    {
      DeallocateMemory();
    }
    
    // Other functionality
    template <typename T>
    void TemplatedArray<T>::PushToBack( T item )
    {
      if ( IsFull() )
        {
          throw runtime_error( "Array is full!" );
        }
      if ( m_array == nullptr )
        {
          AllocateMemory( 10 );
        }
    
      m_array[ m_storedItems ] = item;
      m_storedItems++;
    }
    
    template <typename T>
    void TemplatedArray<T>::RemoveFromBack()
    {
      if ( IsEmpty() )
        {
          throw runtime_error( "Array is empty!" );
        }
    
      // Lazy deletion
      m_storedItems--;
    }
    
    template <typename T>
    bool TemplatedArray<T>::IsFull()
    {
      return ( m_arraySize == m_storedItems );
    }
    
    template <typename T>
    bool TemplatedArray<T>::IsEmpty()
    {
      return ( m_storedItems == 0 );
    }
    
    template <typename T>
    void TemplatedArray<T>::Display()
    {
      for ( int i = 0; i < m_storedItems; i++ )
        {
          cout << i << ". " << m_array[i] << endl;
        }
    }
    
    template <typename T>
    int TemplatedArray<T>::Size()
    {
      return m_storedItems;
    }
    
    // Private methods
    template <typename T>
    void TemplatedArray<T>::AllocateMemory( int size )
    {
      // Clear out any memory currently stored
      DeallocateMemory();
    
      m_array = new T[ size ];
      m_arraySize = size;
      m_storedItems = 0;
    }
    
    template <typename T>
    void TemplatedArray<T>::DeallocateMemory()
    {
      // Free the memory allocated
      if ( m_array != nullptr )
        {
          delete [] m_array;
          m_array = nullptr;
          m_arraySize = 0;
          m_storedItems = 0;
        }
    }
    #endif
    

    This basic templated data structure is now ready to store any kind of data. In this case, the only requirement is that the object being stored has the ostream<< operator function overloaded, since it is used in the Display() function.

    Using the TemplatedArray
    Within main(), we can then use this templated array to store any kind of data:
    #include "TemplatedArray.hpp"
    
    #include <iostream>
    using namespace std;
    
    int main()
    {
        TemplatedArray<string> myList( 10 );
    
        myList.PushToBack( "cat" );
        myList.PushToBack( "rat" );
        myList.PushToBack( "bat" );
    
        myList.Display();
    
        myList.RemoveFromBack();
        myList.Display();
    
        return 0;
    }
    

  • Review questions:
    1. A templated standalone function that uses the template placeholder type T as a parameter looks like…
    2. The declaration of a templated class looks like…
    3. The definition of a templated class' member function looks like…
    4. Declaring an object whose data type is a templated class will look like…

    After reading, do the quiz to check your notes:

UNIT 11: Exceptions

πŸ“–οΈ Reading - Exceptions (U11.READ)

reading_u11_Exceptions_exceptions.png

  • Writing robust software

    As a software developer, you will often be writing software that other developers will be using. This could be other developers at the company working on the same software product, or perhaps you might write a library that gets licensed out to other companies, or anything else. Your code will need to have checks in place for errors and be able to manage those errors gracefully, allowing the software to continue running instead of letting the program crash and restart.

    A long time ago, lots of developers used numeric error codes to track errors. If you've ever seen something like "Error 12943" with no other useful information, that is an example of these error codes - useless for the end-user, but meant so that the programmer can search the code for that number and find where it broke. This is also why we use return 0 at the end of our C++ programs - technically, you could return anything else, but returning 0 is meant to show that there were no errors. If you ran into an error, you /could return 1 or 2 or 3 instead to mark errors.

    Modern languages have exception handling built in, usually working with a try/catch style. You write in logic to check for errors, and when you find a problem you throw an exception, and that exception can be caught elseware in the code.

    What kind of errors can we listen for?

    • Trying to open a file that doesn't exist
    • Memory access violations
    • Invalid math (dividing by 0)
    • Not enough memory
    • Receiving unexpected inputs
    • Trying to delete from an empty data structure
    • Problem converting one data type to another

  • The C++ Exception object

    C++ has an exception family of objects that we can use when trying to classify what kind of exception has happened. If none of the existing exception objects is appropriate, you can also create your own exception type.

    You can find documentation for the exception object here: http://www.cplusplus.com/reference/exception/exception/

    Exceptions: (From http://www.cplusplus.com/reference/exception/exception/)

    Exception class Description
    bad_alloc Exception thrown on failure allocating memory
    bad_cast Exception thrown on failure to dynamic cast
    bad_exception Exception thrown by unexpected handler
    bad_function_call Exception thrown on bad call
    bad_typeid Exception thrown on typeid of null pointer
    bad_weak_ptr Bad weak pointer
    ios_base::failure Base class for stream exceptions
    logic_error Logic error exception
    runtime_error Runtime error exception
    domain_error Domain error exception
    future_error Future error exception
    invalid_argument Invalid argument exception
    length_error Length error exception
    out_of_range Out-of-range exception
    overflow_error Overflow error exception
    range_error Range error exception
    system_error System error exception\
    underflow_error Underflow error exception
    bad_array_new_length Exception on bad array length

    • Detecting errors and throwing exceptions

      The first step of dealing with exceptions is identifying a place where an error may occur - such as a place where we might end up dividing by zero. We write an if statement to check for the error case, and then throw an exception. We choose an exception type and we can also pass an error message as a string.

      int ShareCookies( int cookies, int kids )
      {
        if ( kids == 0 )
        {
          throw runtime_error( "Cannot divide by zero!" );
        }
      
        return cookies / kids;
      }
      
      • Listening for exceptions with try

        Now we know that the function ShareCookies could possibly throw an exception. Any time we call that function, we need to listen for any thrown exceptions by using try.

        try
        {
          // Calling the function
          cookiesPerKid = ShareCookies( c, k );
        }
        
      • Dealing with exceptions with catch

        Immediately following the try, we can write one or more catch blocks for different types of exceptions and then decide how we want to handle it.

        try
        {
          // Calling the function
          cookiesPerKid = ShareCookies( c, k );
        }
        catch( runtime_error ex )
        {
          // Display the error message
          cout << "Error: " << ex.what() << endl;
        
          // Handling it by setting a default value
          cookiesPerKid = 0;
        }
        
        cout << "The kids get " << cookiesPerKid << " each" << endl;
        

        A function could possibly throw different types of exceptions for different errors, and you can have multiple catch blocks for each type.

        Handling the error: Once you catch an error, it's up to you to decide what to do with it. For example, you could…

        • Ignore the error and just keep going
        • Write some different logic for a "plan B" backup
        • End the program because now the data is invalid

        Coding with others' code: While writing software and utilizing others' code, you will want to pay attention to which functions you're calling that could throw exceptions. Often code libraries will contain documentation that specify possible exceptions thrown.


    • Common error in student implementations

      reading_u11_Exceptions_trycatch.png

      A common error I see students make is to put the try, catch, and throw statements all in one function. This defeats the point of even using exceptions.

      Don't do this:

      float Divide( float num, float denom )
      {
        try
        {
          if ( denom == 0 )
          {
            throw Exception;
          }
      
          return num / denom;
        }
        catch( Exception& ex )
        {
          // ...
        }
      }
      

      Remember that the throw belongs within a function that could cause an error, and the try/catch belongs with the location that calls the potentially exception-causing function.

      // FUNCTION THAT COULD CAUSE ERROR - Responsible for THROW
      float Divide( float num, float denom )
      {
        if ( denom == 0 )
        {
          throw Exception;
        }
      
        return num / denom;
      }
      
      
      int main()
      {
        float n, d;
        cout << "Enter num: ";
        cin >> n;
      
        cout << "Enter denom: ";
        cin >> d;
      
        float result = 0;
        try
        {
          // CALLING "IFFY" FUNCTION - Wrap the CALL in try/catch
          result = Divide( n, d );
        }
        catch( Exception& ex )
        {
          cout << "ERROR OCCURRED!" << endl;
          return 1234;
        }
      
        cout << "Result: " << result << endl;
      
        return 0;
      }
      

  • Review questions:
    1. When is the throw keyword used?
    2. When is the catch keyword used?
    3. When is the try keyword used?

    After reading, do the quiz to check your notes:

πŸ§‘β€πŸ”¬ Lab - Templates and exceptions (U10.LABh)

pixel-goals.png Goals:

  • Practice with the searching algorithm: Linear Search
  • Practice with the sorting algorithms: Selection Sort, Bubble Sort, Insertion Sort
  • Implement the searching algorithm: Binary Search
  • Implement the sorting algorithms: Heap Sort, Merge Sort, Quick Sort, Radix Sort
  • Compare speeds of each algorithm

pixel-turnin.png Turn-in:

  • You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

pixel-dual.png Dual enrollment: If you are in both my CS 235 and CS 250 this semester, these instructions are the same. You only need to implement it once and upload it to one repository, then turn in the link for the one merge request. Please turn in on both Canvas assignments so they're marked done.


  • Setup: Starter code and new branch

    For this assignment, I've already added the code to your repository.

    1. Pull the starter code:
      1. Check out the main branch: git checkout main
      2. Pull latest: git pull
    2. Create a new branch to start working from: git checkout -b BRANCHNAME

    The u11_TemplatesExceptions folder contains the following items:

    .
    ├── graded_program
    │   ├── DataStructure
    │   │   └── SmartFixedArray.h
    │   ├── Exception
    │   │   ├── InvalidIndexException.h
    │   │   ├── ItemNotFoundException.h
    │   │   ├── NotImplementedException.h
    │   │   ├── StructureEmptyException.h
    │   │   └── StructureFullException.h
    │   ├── main.cpp
    │   ├── Project_CodeBlocks
    │   │   ├── TemplateProgram.cbp
    │   │   ├── TemplateProgram.depend
    │   │   └── TemplateProgram.layout
    │   ├── Project_Makefile
    │   │   └── Makefile
    │   ├── Project_VisualStudio2022
    │   │   ├── TemplateProgram.sln
    │   │   ├── TemplateProgram.vcxproj
    │   │   └── TemplateProgram.vcxproj.filters
    │   └── Tester
    │       ├── SmartFixedArrayTester.cpp
    │       └── SmartFixedArrayTester.h
    ├── practice1_functions
    │   └── templfunctions.cpp
    ├── practice2_structs
    │   └── templstruct.cpp
    ├── practice3_stdexcept
    │   └── stdexcept.cpp
    └── practice4_customexcept
        └── customexcept.cpp
    

  • practice1_functions
    FIELD               DATA
    --------------------------------------------------------------------------------
    product             USB-C cable pack
    price               9.99
    quantity            5
    sold_out            0
    

    For this practice program we are looking at templated functions. Starting off, we have three functions for three different data types, even though they do basically the same thing:

    void DisplayValue( string variable_name, string variable_value )
    {
      cout << setw( 20 ) << variable_name << setw( 20 ) << variable_value << endl;
    }
    
    void DisplayValue( string variable_name, int variable_value )
    {
      cout << setw( 20 ) << variable_name << setw( 20 ) << variable_value << endl;
    }
    
    void DisplayValue( string variable_name, float variable_value )
    {
      cout << setw( 20 ) << variable_name << setw( 20 ) << variable_value << endl;
    }
    

    Convert one of these functions into a templated function so that the variable_value can be any data type T. Remove the other two duplicates.

    The code in main() should continue working as usual, and you can add more variables with different data types and display their values now, too.


  • practice2_structs
    NODE      DATA      ADDR                PREV                NEXT
    --------------------------------------------------------------------------------
    Ptr 1     a         0x7fffe480ad18      0                   0x561efe87eef0
    Ptr 2     b         0x7fffe480ad10      0x561efe87eeb0      0x561efe87ef30
    Ptr 3     c         0x7fffe480ad08      0x561efe87eef0      0
    

    This program starts with a Node class that can only contain string data:

    struct Node
    {
      Node()
      {
        ptr_next = nullptr;
        ptr_prev = nullptr;
      }
    
      string data;
      Node* ptr_next;
      Node* ptr_prev;
    };
    

    As well as a PrintTable function that only works with this sort of Node:

    void PrintTable( Node* ptr1, Node* ptr2, Node* ptr3 )
    {
      cout << setw( 10 ) << "NODE" << setw( 10 ) << "DATA" << setw( 20 ) << "ADDR" << setw( 20 ) << "PREV" << setw( 20 ) << "NEXT" << endl;
      cout << string( 80, '-' ) << endl;
      cout << setw( 10 ) << "Ptr 1"  << setw( 10 ) <<  ptr1->data << setw( 20 ) << &ptr1 << setw( 20 ) <<  ptr1->ptr_prev  << setw( 20 ) <<  ptr1->ptr_next << endl;
      cout << setw( 10 ) << "Ptr 2" << setw( 10 ) <<  ptr2->data << setw( 20 ) << &ptr2 << setw( 20 ) <<  ptr2->ptr_prev  << setw( 20 ) <<  ptr2->ptr_next << endl;
      cout << setw( 10 ) << "Ptr 3" << setw( 10 ) <<  ptr3->data << setw( 20 ) << &ptr3 << setw( 20 ) <<  ptr3->ptr_prev  << setw( 20 ) <<  ptr3->ptr_next << endl;
    }
    

    Update the Node struct to be a templated struct. Change the string data to a T data type.

    You'll also have to update the PrintTable to support templated Nodes as its parameters.

    Further below in main(), update the set of Nodes already created to be Node<string> types. For additional practice, create another set of Nodes with a different data type and see that everything still works for the other data type as well.


  • practice3_stdexcept
    --------------------------------------------------------------------------------
    DIVISION EXAMPLE
    Enter a numerator and denominator, separated by a space: 5 0
    invalid_argument Exception: Division by 0 not allowed!
    
    --------------------------------------------------------------------------------
    DISPLAY EXAMPLE
    Enter an index between 0 and 4: 600
    out_of_range Exception: Invalid index!
    
    --------------------------------------------------------------------------------
    POINTER EXAMPLE
    Display good_ptr...
    Item being pointed to is: 600
    
    Display bad_pointer...
    invalid_argument Exception: ptr is null!
    

    For this program we have a few "risky functions" that could encounter errors. Currently, they have no error checking, and main() is not listening for any errors, either.

    For the Divide, Display, and PtrDisplay functions, add if statements to check for certain error scenarios and throw the appropriate exception if found:

    Function Error check Type of exception to throw
    Divide if denom is 0 invalid_argument
    Display if index is less than 0 or greater than/equal to arr size out_of_range
    PtrDisplay if arr is pointing to nullptr invalid_argument

    When throwing an exception, add a message string in the constructor so that the exception has a description to help programmers figure out waht went wrong…

    throw out_of_range( "Invalid index!" );

    Down in main(), I've marked certain functions as "!! Risky function call !!". These functions need to be surrounded in a try {} block (one try for each function), and then have a catch statement following, listening for the type of exception that the risky function might throw - so when calling Divide, use catch( const invalid_argument& ex ), and so on.

    When an exception is caught, use cout to display the exception information (ex.what()).


  • practice4_customexcept
    --------------------------------------------------------------------------------
    PIZZA PARTY
    How many pizza slices at pizza party? 50
    How many friends at party? 0
    
    You should really make more friends.
    Exception: Zero friends at party!
    

    For this program, we're going to inherit from an exception class and create our own exception.

    Custom exceptions:

    Use this as a template for the custom exceptions:

    class EXCEPTIONTYPE : public std::runtime_error
    {
    public:
        EXCEPTIONTYPE(std::string message)
            : std::runtime_error(message) {
            // Additional custom info for this type of error:
            std::cout << "You should really make more friends." << std::endl;
        }
    };
    

    Replace "EXCEPTIONTYPE" with the name of the exception you're creating. This is just creating a new class, inheriting from runtime_error, and setting up its constructor. The : std::runtime_error(message) line is calling the parent class' constructor, passing in the message. The parent class will handle dealing with the message, but we could add additional functionality in our constructor if needed. In this case, I just have an additional message for this exception type.

    SlicesPerPerson function:

    In the SlicesPerPerson function, create two if statements:

    • If friends is 0, then throw the NotEnoughFriendsException with an error message.
    • If pizzaSlices is 0, then throw the NotEnoughPizzaException with an error message.

    main():

    In main, wrap the "risky function call" (SlicesPerPerson( friendCount, pizzaSliceCount )) with a try {} block. Create two catch blocks afterwards, one listening for the NotEnoughFriendsException exception, and one listening for the NotEnoughPizzaException exception. Display each exception's error message (ex.what()) within each catch statement.


  • Graded program

    For the graded program there are only automated tests and no program right now. We will be editing the SmartFixedArray class, located within the DataStructure folder.

    The SmartFixedArray class is a templated class that stores a fixed-length array within it. We are sort of implementing part of the STL array class, just fewer features.

    Keep in mind the member variables that belong to this class, which will be important as you implement the SmartFixedArray's functionality:

    Member variable Description
    T data[100] An array of 100 items, of T data type
    ARRAY_SIZE The hard-coded array size, fixed at 100
    item_count How many items are currently stored in the array

    Custom exceptions:

    These exceptions are provided already, and you will use them in various functions:

    Exception Description
    InvalidIndexException  
    ItemNotFoundException Thrown by Search when an item requested is not found
    NotImplementedException All the functions have this by default to mark them as incomplete
    StructureEmptyException Thrown when trying to Pop or Get data from an empty array
    StructureFullException Thrown when trying to Push data to an array that is full

    Automated testing: Make sure to run the automated tests to verify that your data structure fully works!


    • SmartFixedArray<T>::SmartFixedArray()

      lab_u11_TemplatesExceptions_array_empty.png

      When we start off, we mark the array as empty. To do this, we just set this->item_count to 0.

      Even though the amount of memory allocated doesn't get changed throughout the lifetime of our array, we marked "filled in" spaces by adjusting the item count.


    • SmartFixedArray<T>::Clear()

      lab_u11_TemplatesExceptions_array_empty.png

      This function "deletes" all the items in the array… however, we're going to do lazy deletion. Basically, by setting this->item_count to 0, we effectively say "there's nothing in this array!". All the old data will be overwritten sooner or later when new data is added.


    • SmartFixedArray<T>::Size() const

      lab_u11_TemplatesExceptions_array_3items.png

      Return
      The current amount of items stored in the array

      This function is responsible for telling us how many pieces of data are currently stored in the array. This is reflected by the this->item_count variable (NOT the total this->ARRAY_SIZE, which marks the maximum capacity of the array!)


    • SmartFixedArray<T>::IsFull() const

      lab_u11_TemplatesExceptions_array_full.png

      Return
      true if the array is full, or false otherwise.

      This function will tell us if the array is currently full. The maximum capacity of the array is stored in this->ARRAY_SIZE, so if this->item_count matches that, then it means we've filled up all possible spaces.


    • SmartFixedArray<T>::IsEmpty() const

      lab_u11_TemplatesExceptions_array_empty.png

      Return
      true if the array is empty, or false otherwise.

      The this->item_count represents how many items are currently stored in the array, so if it is 0, then that means the array is empty.


    • SmartFixedArray<T>::Search( T item ) const

      lab_u11_TemplatesExceptions_array_search.png

      Parameters
      • item, the data we're searching for.
      Return
      • Returns the index of the item found
      Exception
      • If the item is not found in the array, then throw a ItemNotFoundException.

      Use a basic Linear Search to iterate over all the used elements of the array (0 to this->item_count), looking at each item. If the item asked for is found within your loop, then return the index of this item (usually i in a for loop).

      After the for loop is over, this means the item wasn't found, so in this case throw the ItemNotFoundException. (Don't throw this INSIDE THE FOR LOOP! Logic error…)


    • SmartFixedArray<T>::ShiftLeft( sizet index )

      lab_u11_TemplatesExceptions_array_shiftleft.png

      Parameters
      • index, all items to the right of this index value will be shifted to the left.
      Exception
      • If the index is invalid then throw an InvalidIndexException. The index is invalid if index is less than 0 or greater than or equal to this->item_count, as we want to keep our items contiguous in the array.

      Use a for loop to shift everything to the right of the index position to the left.

      (Note: DON'T adjust the item count here! This is just a helper function and the function that calls it will handle adjusting item count as needed.)

      QUESTION: How do I shift an item over to the left?

      With an assignment statement, you can move things over from its neighbor: this->data[i] = this->data[i+1]


    • SmartFixedArray<T>::ShiftRight( sizet index )

      lab_u11_TemplatesExceptions_array_shiftright.png

      Parameters
      • index, all items to the right of this index value will be shifted to the left.
      Exception
      • If the index is invalid then throw an InvalidIndexException. The index is invalid if index is less than 0 or greater than or equal to this->item_count, as we want to keep our items contiguous in the array.
      • If the array is full (call IsFull() to check) then throw a StructureFullException, as there is no more space in the array.

      Use a for loop to take everything at index position and to its right and shift these items further to the right.

      (Note: DON'T adjust the item count here! This is just a helper function and the function that calls it will handle adjusting item count as needed.)

      QUESTION: How do I shift an item over to the right?

      With an assignment statement, you can move things over from its neighbor: this->data[i] = this->data[i-1]. You'll want to make sure to start your loop at the end of the range and go towards the index.


    • SmartFixedArray<T>::PushBack( T newItem )

      lab_u11_TemplatesExceptions_array_pushback.png

      Parameters
      • newItem is a new piece of data to eb added to the back (end) of the array.
      Exception
      • If the array is full (call IsFull() to check) then throw a StructureFullException.

      The this->item_count contains the amount of items that are currently stored in the array, but this number ALSO corresponds to the next location where you need to add your data.

      Insert the newItem into your this->data array at the this->item_count position, then make sure to increment this->item_count.


    • SmartFixedArray<T>::PopBack()

      lab_u11_TemplatesExceptions_array_popback.png

      Exception
      • If the array is empty (call IsEmpty() to check) then throw StructureEmptyException.

      This function "deletes" the item at the current end of hte items in the array. However, we just do a "lazy deletion" here by decrementing this->item_count. We don't need to do anything with the data itself, because it will be overwritten by the next Push call.


    • SmartFixedArray<T>::GetBack()

      lab_u11_TemplatesExceptions_array_getback.png

      Exception
      • If the array is empty (call IsEmpty() to check) then throw StructureEmptyException.
      Return
      • Return the element of this->data[] at the this->item_count-1 position.

      This function returns the last item in the contiguous list of elements in the array… Basically, whatever is at size-1, or this->item_count-1.


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

    \newpage

WEEK 6 - FEB 19

UNIT 12: Overloading functions and constructors

πŸ“–οΈ Reading - Overloading functions (U12.READ)

  • Function overloading

    In C++, you can also write multiple functions that have the same name, but a different parameter list. This is known as function overloading.

    Let's say you want to be able to sum numbers, and you make a version for floats and a version for integers:

    int Sum( int a, int b )
    {
        return a + b;
    }
    
    float Sum( float a, float b )
    {
        return a + b;
    }
    

    You can write as many versions of the function as you like, so long as the function headers are uniquely identifiable to the compiler, which means:

    • The functions have a different amount of parameters, or
    • The data types of the parameters are different, or
    • The parameters are in a different order (when mixing data types).

    The return type doesn't factor into the function's uniqueness, so make sure that you're changing up the parameter list.


    Example usage - Config manager

    Here we have a program that uses a ConfigManager class to deal with saving and loading program settings to a file. While the program runs, we probably want to be able to update settings, and those settings will be stored as a key, value pair of some kind.

    Some settings, like "screen_width", might be an integer, while other settings, like "last_save_game" might be a string. Within the ConfigManager, we provide two different functions to be able to update either type of option:

    class ConfigManager
    {
    public:
      void Load( std::ifstream& input );
      void Save();
    
      std::string Get( const std::string& key );
      int GetInt( const std::string& key );
      void Set( const std::string& key, const std::string& value );
      void Set( const std::string& key, int value );
    
      // ... etc ...
    };
    

    To call the function, let's say we have a ConfigManager config; variable declared and set up. We could call the Set function in either of these ways:

    config.Set( "screen_width", 800 );
    config.Set( "last_save_game", "autosave.gam" );
    

    Example usage - MenuManager

    Here is a MenuManager that stores things like UILabel objects and other elements. These elements go on separate layers and each element has a unit name.

    The GetLabel function can be overloaded so that when we call it, we don't necessarily need the layer name - that version of the function does a search. Or, we can call the version and provide the layer name, and the element can be accessed directly and much more quickly.

    class MenuManager
    {
    
    public:
      UILabel&             GetLabel( const std::string& layer, const std::string& name );
      UILabel&             GetLabel( const std::string& name );
      // ... etc ...
    
    private:
      std::map< std::string, UILayer > m_layers;
      // ... etc ...
    };
    

    To call the function, let's say we have a MenuManager menuManager; variable declared and set up. We could call the GetLabel function in either of these ways:

    // Slow version
    UILabel& currentLabel = menuManager.GetLabel( "filename" );
    
    // Fast version
    UILabel& currentLabel = menuManager.GetLabel( "file_info", "filename" );
    

  • Constructor overloading

    Class constructors are special functions that are called automatically when a new object (of that class type) is instantiated. (In other words, when you declare a new variable and the data type is that class.)

    There are three main types of constructors we can write, each of them overloaded.

    • Default constructor

      The default constructor has no parameters and is called automatically when we declare a class object variable like this:

      MyClass variablename;
      

      Default constructors are usually used to initialize an object to get it ready for usage. This can include:

      • Setting any internal pointers to nullptr for safety.
      • Opening files for any member ifstream/ofstream variables to get ready to write to.
      • Setting member variables to some default values.

      MyClass.h

      class MyClass
      {
        public:
        MyClass(); // default constructor
      
        private:
        int m_memberVariable;
      };
      

      MyClass.cpp

      MyClass::MyClass()
      {
        m_memberVariable = 100;
      }
      

      Example usage - Logger class

      Many programs have some kind of Logger that writes out events while the program is running to a external text file (or other format). This can help diagnose issues if errors occur.

      If we were creating a Logger class, it would be useful to open the log file automatically when the Logger object is created, and close the log file automatically when the object is destroyed.

      Logger.h

      class Logger
      {
        public:
        Logger();  // default constructor
        ~Logger(); // destructor
        // ... etc ...
      
        private:
        ofstream m_outfile;
        // ... etc ...
      };
      

      Logger.cpp

      Logger::Logger()
      {
        m_outfile.open( "log.txt" );
      }
      
      Loger::~Logger()
      {
        m_outfile.close();
      }
      

      Elseware in the program, the Logger will be created, and that log file will be opened automatically:

      Logger logger;
      

    • Parameterized constructor

      A parameterized constructor is when we have one or more parameter in the constructor's parameter list. These are values that will be passed into the object as it's being instantiated, and these values can be used to set values to the object's member variables immediately.

      MyClass.h

      class MyClass
      {
        public:
        MyClass();                 // default ctor
        MyClass( int new_value );  // parameterized ctor
      
        private:
        int m_memberVariable;
      };
      

      MyClass.cpp

      MyClass::MyClass( int new_value )
      {
        m_memberVariable = new_value;
      }
      

      Then the object will be instantiated like this:

      MyClass variablename( 100 );
      

      Example usage - Logger

      Similarly to the default constructor example, but maybe there is a time where we want to specify what that logger file is. Perhaps the parameterized constructor will be called most of the time, but if we don't know what file to use we lean back on the default constructor.

      Logger.h

      class Logger
      {
        public:
        Logger();                       // default
        Logger( std::string filename ); // parameterized
        ~Logger();                      // destructor
        // ... etc ...
      
        private:
        ofstream m_outfile;
        // ... etc ...
      };
      

      Logger.cpp

      Logger::Logger() // default
      {
        m_outfile.open( "log.txt" );
      }
      
      Logger::Logger( std::string filename ) // parameterized
      {
        m_outfile.open( filename );
      }
      
      Loger::~Logger()
      {
        m_outfile.close();
      }
      

      Elseware in the program, the Logger will be created, and that log file will be opened automatically:

      Logger logger( "log-sept-28.txt" );
      

    • Copy constructor

      With a copy constructor the idea is that we want to copy information from one object to another object - to make a copy of that original object.

      In some cases, maybe we want to copy all of the member variable values over. In other cases, perhaps we don't want to copy over all the data - such as if the class has a pointer to a memory address.

      MyClass.h

      class MyClass
      {
        public:
        MyClass();                       // default
        MyClass( int new_value );        // parameterized
        MyClass( const MyClass& other ); // copy ctor
      
        private:
        int m_memberVariable;
        int* m_pointer;
      };
      

      MyClass.cpp

      MyClass::MyClass( const MyClass& other )
      {
        // Copy over the member variable
        m_memberVariable = other.memberVariable;
        // Don't copy over the pointer
      }
      

      For this constructor, we need another object of the same type declared already, and we pass in that other object to make a copy of it.

      MyClass original;
      MyClass copied( original );
      

      Shallow copy vs. deep copy
      • A Shallow Copy is where values of variables are copied over. This is generally fine for any sort of non-pointer-based variables. If the class contains a pointer that is pointing to some address, the shallow-copy of the pointer will point to the same address.
      • A Deep Copy is where values are copied over like with a shallow copy, but also will allocate new memory for a dynamic array (if the class has one) in order to copy over values of the element of the array to the new class copy.

      reading_u12_OverloadFunctions_shallow-copy.png

      Example of a shallow copy:

      With the implicit copy constructor, any pointers in the copied version will be pointing to the same address as in the original. If the class contains a dynamic array, both the copy and the original will end up pointing to the same address of the array in memory.

      reading_u12_OverloadFunctions_deep-copy.png

      Example of a deep copy:

      A new block of memory has been allocated for the int * numArr. The values from InstanceA’s numArr would be copied to InstanceB’s numArr via a for loop during construction.


      Example usage - Data

      A program might have some sort of data that you might want to make a copy of to do work on but not potentially mess up the original data set. This can be good if you want to sort the data or perform operations on the data.


    • Additional notes
      • If no constructors are declared, a default parameter is generated automatically at compile-time.
      • If a parameterized or copy constructor is declared but not a default parameter, then no default constructor will be generated at compile-time.

UNIT 13: Default parameters

πŸ“–οΈ Reading - Default parameters (U13.READ)

  • Default parameters

    When declaring a function, you can also set default parameters. These are the default values assigned to the parameters if the user doesn't pass anything in. The default parameters are only specified in a function declaration - NOT the definition!

    In this example, it could be a function that displays ingredients for a recipe, and by default the batch is set to 1.0 (one batch).

    void OutputIngredients( float eggs, float sugar, float flour, float batch = 1.0 );
    

    The function could be called without passing in a batch:

    cout << "Ingredients:" << endl;
    OutputIngredients( 1, 2.0, 3.5 );
    

    Or they could pass a batch amount explicitly:

    cout << "Ingredients:" << endl;
    OutputIngredients( 1, 2.0, 3.5, 0.5 );  // half batch
    

    You can have multiple default parameters specified in your function declaration - but all variables with default values must go after any variables without default values.


    Example usage - Logger

    In the Overloaded Functions part we showed having a Logger class with multiple constructors. With this particular example, it would actually be better to use a default parameter instead of overloading!

    Here's an example using the default paramter:

    Logger.h

    class Logger
    {
      public:
      Logger( std::string filename = "log.txt"  );
      ~Logger();                      // destructor
      // ... etc ...
    
      private:
      ofstream m_outfile;
      // ... etc ...
    };
    

    Logger.cpp

    Logger::Logger( std::string filename /*="log.txt"*/ )
    {
      m_outfile.open( filename );
    }
    
    Loger::~Logger()
    {
      m_outfile.close();
    }
    

    Elseware in the program, the Logger will be created, and that log file will be opened automatically. We can specify a file or not, and both will use the same constructor:

    Logger logger();
    Logger logger( "log-sept-28.txt" );
    

πŸ§‘β€πŸ”¬ Lab - Overloading functions, default parameters (U13.LAB)

pixel-goals.png Goals:

  • Practice writing and using overloaded functions
  • Practice writing and using functions with default parameters

pixel-turnin.png Turn-in:

  • 1. You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

  • Setup: Starter code and new branch

    Start this assignment by checking out main, pulling latest to get the files, and creating a new branch…

    git checkout main
    git pull
    git checkout -b u12u13
    

    TWO DIFFERENT FOLDERS WILL BE PULLED!

    PRACTICE PROGRAMS: Practice programs for this lab are located in the u12u13_Overloading_DefaultParams folder in your repository.

    .
    ├── practice1_overload_functions
    │   └── overloadfx.cpp
    ├── practice2_default_params
    │   └── defaultparams.cpp
    └── practice3_in_classes
        ├── File.cpp
        ├── File.h
        ├── fileout
        └── main.cpp
    

    GRADED PROGRAM: For this and future assignents we will be using the cs235_application for graded assignments.

    .
    ├── Filesystem
    │   ├── File.cpp
    │   ├── File.h
    │   ├── FileTester.cpp
    │   └── FileTester.h
    ├── main.cpp
    ├── Namespace_Utilities
    │   ├── ScreenDrawer.cpp
    │   └── ScreenDrawer.hpp
    ├── Program
    │   ├── Program.cpp
    │   └── Program.h
    ├── Project_CodeBlocks
    │   ├── cs235_program.cbp
    │   ├── cs235_program.depend
    │   └── cs235_program.layout
    ├── Project_Makefile
    │   └── Makefile
    └── Project_VisualStudio2022
        ├── CS235_Application.sln
        ├── CS235_Application.vcxproj
        └── CS235_Application.vcxproj.filters
    

  • Practice programs

    SPRING 2024 NOTE: I accidentally added the "solution" version of all the practice programs to everybody's repositories and I don't feel like fixing this so you can look over these to learn from if you want, or you can erase the code and implement them for practice.

    • practice1_overload_functions
      NAME                                    PRICE               IN STOCK
      --------------------------------------------------------------------------------
      Playstation 1 game
      Playstation 2 game                      39.99
      Playstation 3 game                      49.99
      Playstation 4 game                      59.99               10
      Playstation 5 game                      79.99               30
      

      For this program, we're going to create 3 overloaded standalone functions that will format data in a table:

      1. void DisplayProduct( string name )
      2. void DisplayProduct( string name, float price )
      3. void DisplayProduct( string name, float price, int quantity )

      Within main(), a table header is set up:

      cout << left << setprecision( 2 ) << fixed;
      cout << setw( 40 ) << "NAME" << setw( 20 ) << "PRICE" << setw( 20 ) << "IN STOCK" << endl;
      cout << string( 80, '-' ) << endl;
      

      The "Name" column gets 40 spaces, the "Price" column gets 20 spaces, and the "In stock" column gets 20 spaces.

      Within each of the overloaded DisplayProduct functions, follow this example to write out whatever data is available (e.g., just name, or name and price, or name and price and quantity).

      The resulting output should look like above.


    • practice2_default_params

      This is the same program as practice1, except we create ONE DisplayProduct function, and set default parameters for the price and quantity parameters.

      Note that usually you'd be putting your function declaration in a separate .h file:

      // Default parameters specified for price and quantity
      void DisplayProduct( string name, float price = 0, int quantity = 0 );
      

      And the function definition goes in a .cpp file, and does not have the default parameter values in the parameter list:

      void DisplayProduct( string name, float price, int quantity )
      {
          // ...
      }
      

    • practice3_in_classes

      This program has a File class that will use overloaded functions and default parameters. It is similar to the graded program, but it is not exactly the same, so don't get confused by the differences!

      UML Diagram:

      File  
      - name : string
      - ext : string
      + File()  
      + File( name: string )  
      + File( name: string, ex: string )  
      + File( other: const File& )  
      + CreateFile( name: string, ext: string, text: string ) : void

      The CreateFile function should use default parameters, setting ext to "txt" and text to "hello" if nothing is given.


      Function implementation:

      • File::File() Call the CreateFile function, passing in "Default" as the name.
      • File::File( string name ) Call the CreateFile function, passing in name.
      • File::File( string name, string ext ) Call the CreateFile function, passing in name and ext.
      • File::File( const File& other ) Call the CreateFile function, passing in the other.name, other.ext, and some text like "Copied file".
      • void File::CreateFile( string name, string ext, string text )
        • Set this->name to the name passed in.
        • Set this->ext to the ext passed in.
        • Create an ofstream object, open the file at "fileout/" + name + "." + ext.
        • Write the text to the ofstream object.
        • Close the ofstream object.

      Context: Using the this-> pointer refers back to the class object that you're currently within. this-> can be used to access its own member variables and functions. It is optional, but can be used for clarity.


      Creating a basic program:

      Within main(), test the various ways to create a file like this:

      #include <iostream>
      using namespace std;
      
      #include "File.h"
      
      int main()
      {
          File fileA;
      
          File fileB( "fileb" );
      
          File fileC( "filec", "html" );
      
          File fileD( fileC );
      
          return 0;
      }
      

      Testing the program:

      After building and running the program, your text files will be created in the fileout folder:

      lab_u12u13_OverloadFunctionsDefaultParams_outputs.png

      Open them up and make sure they have the proper contents within.


  • Graded program
    • FYI: cs235 application

      For this set of units and future units the graded part of the labs will be in the cs235_application.

      At the moment, the menus are here but the program won't work until you implement the File class. These screenshots show a finished version.

      Starting off, the application will show a main menu:

      lab_u12u13_OverloadFunctionsDefaultParams_program1.png

      From the main menu, you can select Create document.

      lab_u12u13_OverloadFunctionsDefaultParams_program2.png

      Here, you select what type of document to create.

      lab_u12u13_OverloadFunctionsDefaultParams_program3.png

      Then you enter in lines of text to the file, then type :q and hit ENTER to finish.

      Back at the main menu, if you select View documents, the documents you've created will show up. You can select a document and it will show the contents of the file:

      lab_u12u13_OverloadFunctionsDefaultParams_program4.png

      At the moment, no actual files are created on your computer, these are just abstractions within the File class.

      Note: You can also run the automated tests from the main menu with the "11" option.


    • The File class

      The File class declaration has already been declared, and you will be implementing its functions. Please take note of the File class and its member variables to help you with your implementation.

      UML Diagram:

      File   Notes
      - path : string  
      - name : string  
      - ext : string  
      - contents : vector<string>  
      + File()    
      + File( path: string, name: string )    
      + File( path: string, name: string, ex: string )    
      + File( other: const File& )    
      + OpenFile( path: string, name: string, ext: string ) : void ext defaults to "txt"
      + Write( line: string ) : void  
      + Display() : void  
      + GetFullname() : string  
      + GetContents() : vector<string>  

    • File::File()

      Call the OpenFile function, passing in default values (like "NOTSET") for all the arguments.


    • File::File( string path, string name )

      Call the OpenFile function, passing the path, and name to that function.


    • File::File( string path, string name, string ext )

      Call the OpenFile function, passing the path, name, and ext to that function.


    • File::File( const File& other )

      Call the OpenFile function, passing in the other object's path, name plus " (copy)", and extension. Afterwards, set this->contents to the other object's contents.


    • void File::OpenFile( string path, string name, string ext )

      Set the member variables this->path, this->name, and this->ext to the values passed in as the parameters.


    • void File::Write( string line )

      Push the line given into the this->contents vector.


    • void File::Display() const

      Iterate over all the lines in the file, displaying each one on its own line. Also display the file's path, name, and extention.


    • std::string File::GetFullname() const

      Return the full path of the file, in this form: <PATH><NAME>.<EXT>


    • std::vector<std::string> File::GetContents() const

      Return the this->contents member variable.


    • Testing the program

      Run the automated tester by selecting option 11 from the main menu. All tests should pass.

      You can also manually test the program by choosing option 1 to create a document or two, and selecting option 2 to view the documents and view their contents.


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

    \newpage

WEEK 7 - FEB 26

UNIT 14: Static members

πŸ“–οΈ Reading - Static members (U14.READ)

  • Static Variables and Functions

    Static variables are a special type of variable in a class where all instances of the class share the same member. Another term you might hear is a Class Variable, whereas a normal member variable of a class would be an Instance Variable.

    Let's say we are going to declare a Cat class, and each cat has its own name, but we also want a counter to keep track of how many Cats there are. The Cat counter could be a static variable and we could write a static method to return that variable's value.

    class Cat
    {
    public:
      Cat()
      {
        catCount++;
      }
    
      Cat( string name )
      {
        catCount++;
        m_name = name;
      }
    
      static int GetCount()
      {
        return catCount;
      }
    
    private:
      string m_name;
      static int catCount;
    };
    

    Within a source file, we will need to initialize this static member. This may go in the class' .cpp file outside of any of the function definitions.

    // Initialize static variable
    int Cat::catCount = 0;
    

    And then any time we create a new Cat object, that variable will automatically add up, and every instance of the Cat will share that variable and its value.

    int main()
    {
      Cat catA, catB, catC;
    
      ;; These all display 3
      cout << catA.GetCount() << endl;
      cout << catB.GetCount() << endl;
      cout << catC.GetCount() << endl;
      cout << Cat::GetCount() << endl;
    
      return 0;
    }
    

    Beyond accessing a static method or member directly through an instantiated object, we can also access it through the class name itself, like this:

    cout << Cat::GetCount() << endl;
    

    Example usage: Manager class

    In my game engine I use static member variables and functions for my Manager classes. These Managers are meant to manage parts of the game, such as the Texture library, Audio library, Inputs, Menus, and so on. Throughout the entire game, I don't create multiple instances of the TextureManager. Because the functions and data are static, I can use this class across the entire project directly.

    Declaring the TextureManager:

    class TextureManager
    {
    public:
      static std::string CLASSNAME;
    
      static void Add( const std::string& key, const std::string& path );
      static const sf::Texture& AddAndGet( const std::string& key, const std::string& path );
      static void Clear();
      static const sf::Texture& Get( const std::string& key );
    
    private:
      static std::map<std::string, sf::Texture> m_assets;
    };
    

    Defining the member variables (top of TextureManager.cpp):

    std::string TextureManager::CLASSNAME = "TextureManager";
    std::map<std::string, sf::Texture> TextureManager::m_assets;
    

    Function definitions look the same:

    void TextureManager::Add( const std::string& key, const std::string& path )
    {
      sf::Texture texture;
      if ( !texture.loadFromFile( path ) )
        {
          // Error
          Logger::Error( "Unable to load texture at path \"" + path + "\"", "TextureManager::Add" );
          return;
        }
    
      m_assets[ Helper::ToLower( key ) ] = texture;
    }
    
    const sf::Texture& TextureManager::Get( const std::string& key )
    {
      if ( m_assets.find( Helper::ToLower( key ) ) == m_assets.end() )
        {
          // Not found
          Logger::Error( "Could not find texture with key " + key, "TextureManager::Get" );
          throw std::runtime_error( "Could not find texture with key " + key + " - TextureManager::Get" );
        }
    
      return m_assets[ Helper::ToLower( key ) ];
    }
    

    Calling the TextureManager functions:

    chalo::TextureManager::Add( "moose",   "Content/Graphics/Demos/moose.png" );
    
    // ...etc...
    sf::Sprite m_player;
    m_player.setTexture( chalo::TextureManager::Get( "moose" ) );
    

    Example usage: Singleton pattern

    Further, we can use the Singleton pattern to create a class that can only have one instance.

    Context: A "Design Pattern" is kind of like a blueprint for a way to implement a structure. These are structures that people have figured out how to build that end up being useful in a lot of scenarios.

    You can learn more about the Singleton pattern here: https://en.wikipedia.org/wiki/Singleton_pattern .

    And more about Design Patterns here: https://en.wikipedia.org/wiki/Design_pattern .

UNIT 15: Friends

πŸ“–οΈ Reading - Friends (U15.READ)

  • Friends

    Remember that when member variables and functions of a class are set to public they can be accessed by anything and when they are set to private these members can only be accessed from within the class itself.

    We can make an exception to this rule by declaring some external function or other class as a friend of the class we're creating. A friend function or class has access to any private or protected members of the class.

    Friend function The friend function will be declared within the class' declaration:

    class MyClass
    {
    public:
      void Hi();
    
    private:
      int name;
    
      friend void PrintMyClass( const MyClass& item );
    };
    

    And then it can be defined in a source (.cpp) file elsewhere:

    void PrintMyClass( const MyClass& item )
    {
      ;; name is private, but this function can access it.
      cout << item.name << endl;
    }
    

    Friend class A friend class is the same sort of thing except that any member function of our friend class has access to any private members of the other class.

    class ClassWithAFriend
    {
    private:
      int name;
    
      friend class FriendlyClass;
    };
    

    That other class would be declared elsewhere, and any functions it has can access our ClassWithAFriend's members.

    class FriendlyClass
    {
    public:
      void Display( const ClassWithAFriend& myFriend )
      {
        cout << myFriend.name << endl;
      }
    };
    

    However - it doesn't go both ways. Keep in mind that if classA declares that classB is its friend, this means that classB has access to classA's members. However, this does not mean that classA has access to classB's members - we would have to explicitly state "classA is a friend" within the classB class.


    Example usage: Unit tests

    Using friend is usually considered poor design as you're exposing variables to be modified outside of the class itself. I tend to only use friend for creating a set of unit tests for a given class:

    template <typename T>
    //! A data structure that wraps a fixed array
    class SmartFixedArray : public ILinearDataStructure<T>
    {
    public:
      // ... etc ...
    
    private:
      /* Private member variables */
      T m_array[100];
      const size_t ARRAY_SIZE;
      size_t m_itemCount;
    
      // ... etc ...
    
      friend class SmartFixedArrayTester; // << To make testing easier
    };
    

    Then, my unit tests can directly access the private member variables in order to be able to test one function at a time. For example, this test checks the PushAt function, then directly accesses the array to check that the right values are at the right positions, instead of relying on a function like GetAt to see those values. Unit Tests are meant to test one "unit" at a time.

    SmartFixedArray<std::string> arr;
    arr.m_array[0] = "a";
    arr.m_array[1] = "b";
    arr.m_array[2] = "c";
    arr.m_itemCount = 3;
    
    arr.PushAt( 1, "z" );
    
    if      ( arr.m_array == nullptr )                                              { TestFail(); }
     else if ( !Set_Outputs( "m_itemCount", 4, arr.m_itemCount ) )                  { TestFail(); }
     else if ( !Set_Outputs( "m_array[0]", std::string( "a" ), arr.m_array[0] ) )   { TestFail(); }
     else if ( !Set_Outputs( "m_array[1]", std::string( "z" ), arr.m_array[1] ) )   { TestFail(); }
     else if ( !Set_Outputs( "m_array[2]", std::string( "b" ), arr.m_array[2] ) )   { TestFail(); }
     else if ( !Set_Outputs( "m_array[3]", std::string( "c" ), arr.m_array[3] ) )   { TestFail(); }
     else                                                                           { TestPass(); }
    

    After reading, do the quiz to check your notes:

πŸ§‘β€πŸ”¬ Lab - Static members and friends (U15.LAB)

pixel-goals.png Goals:

  • Implement the functionality of a Static manager class, which also has "friend" access to the Account class.

pixel-turnin.png Turn-in:

  • 1. You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png Practice programs and graded program:

  • This assignment contains several "practice" program folders, and a "graded" program folder. Only the "graded" program is required.
  • The practice programs are there to help you practice with the topics before tackling the larger graded program. If you feel confident in the topic already, you may go ahead and work on the graded program. If you feel like you need additional practice first, that's what the practice assignments are for.
  • The graded program assignment contains unit tests to help verify your code.
  • You can score up to 100% turning in just the graded program assignment.
  • You can turn in the practice assignments for extra credit points.

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

  • Setup: Starter code and new branch

    Start this assignment by checking out main, pulling latest to get the files, and creating a new branch…

    git checkout main
    git pull
    git checkout -b u12u13
    
    • Practice programs are in the u14u15_Static_Friends folder.
    • Graded program will be in cs235_application again; I've made updates to the program.

  • Practice programs
    • practice1_static
      Total students: 0
      Total students: 1
      Total students: 2
      Total students: 3
      Total students: 4
      Total students: 4
      

      Student.h: Create a student class based on the following UML diagram:

      Student  
      - name : string
      - total_students : static int
      + Student( name:string )  
      + GetTotalStudents() : static int

      Note that total_students is a static variable and GetTotalStudents is a static function.


      Student.cpp: Implement as follows:

      First, with static variables, we need to initialize them at the top of the .cpp file, OUTSIDE OF ALL FUNCTIONS. Here is the shell .cpp file:

      #include "Student.h"
      
      // Initialize static members
      int Student::total_students = 0;
      
      // Member function
      Student::Student( string name )
      {
      }
      
      // Class function
      int Student::GetTotalStudents()
      {
      }
      

      Student constructor:

      1. Initialize the this->name member variable to the name passed in as a parameter.
      2. Increment the total_students class variable.

      GetTotalStudents function:

      This is a static function but you don't need any static markers in the definition. For this function, just return the total_students.


      main:

      Once implemented, uncomment out the code in main():

      int main()
      {
          cout << "Total students: " << Student::GetTotalStudents() << endl;
      
          vector<Student> student_list;
          student_list.push_back( Student( "A" ) );
          cout << "Total students: " << student_list[0].GetTotalStudents() << endl;
      
          student_list.push_back( Student( "B" ) );
          cout << "Total students: " << student_list[1].GetTotalStudents() << endl;
      
          student_list.push_back( Student( "C" ) );
          cout << "Total students: " << student_list[2].GetTotalStudents() << endl;
      
          student_list.push_back( Student( "D2" ) );
          cout << "Total students: " << student_list[3].GetTotalStudents() << endl;
      
          cout << "Total students: " << Student::GetTotalStudents() << endl;
      
      
          return 0;
      }
      

      Note that we can access the static function GetTotalStudents either through an instanced object, like student_list[0].GetTotalStudents(), or through the class name itself, like Student::GetTotalStudents().


    • practice2_friend
      LAUNCH PRICES
      
      ID   NAME                PRICE     YEAR
      --------------------------------------------------------------------------------
      0    PlayStation         299.00    1995
      1    PlayStation 2       299.00    2000
      2    PlayStation 3       499.00    2006
      3    PlayStation 4       399.00    2013
      4    PlayStation 5       499.00    2020
      

      Product.h: Declare the function void Display( const vector<Product>& products ); as a friend of the Product class.

      Afterwards, the program will build and the Display function (within main.cpp) will be able to access the Product's member variables.


    • practice3_staticmanager
      LAUNCH PRICES
      
      ID   NAME                PRICE     YEAR
      --------------------------------------------------------------------------------
      0    NES                 199.00    1985
      1    SNES                199.00    1991
      2    Nintendo 64         199.00    1996
      3    GameCube            199.00    2001
      4    Wii                 249.00    2006
      5    Wii U               299.00    2012
      6    Nintendo Switch     299.00    2017
      

      Product.h here is the same as before.

      ProductManager is already implemented - it will handle adding and displaying products.

      Make sure to take a peek at both classes. Notice that ProductManager has static variables and functions.

      main(): Do the following…

      1. Add several products via the ProductManager…

        ProductManager::AddProduct( "SNES", 199, 1991 );
        
      2. Display the products via the ProductManager…

        ProductManager::Display();
        

  • Graded program

    Remember that the program is located under the cs235_application folder. Open the existing project provided in either Project_CodeBlocks, Project_VisualStudio2022, or use the Project_Makefile to build from a console.

    • Updates to the program

      lab_u14u15_StaticFriends_program.png

      I've updated the program to have an Account and AccountManager class. Account is done but you will implement some of the functionality for the AccountManager.

      Additional automated tests are provided to ensure that things are working as intended.


    • Account class

      Keep note of the members of the Account class, which will be needed while implementing the AccountManager class.

      Account  
      - id : size_t
      - username : string
      - password_hash : size_t
      + Account( id : size_t, username : string, password_hash : size_t )  
      + SetUsername( username : string ) : void
      + SetPasswordHash( password_hash : size_t ) : void
      + GetID() : size_t
      + GetUsername() : string
      + CorrectPassword( attempt_hash : size_t ) : bool

    • AccountManager class
      AccountManager   Notes
      - save_path : string  
      - accounts : vector<Account>  
      + Setup( data_path : string) : void Already implemented
      + Teardown() : void Already implemented
      + Clear() : void Already implemented
      + SaveData() : void Already implemented
      + LoadData() : void Already implemented
      + DisplayAccountsTable() : void Already implemented
      + CreateAccount( username: string, password_hash : size_t ) : size_t  
      + GetAccount( id : size_t ) : Account  
      + GetIndexOfAccount( id : size_t ) : size_t  
      + DeleteAccount( id : size_t ) : void  

      size_t AccountManager::CreateAccount( std::string username, size_t password_hash )

      • Parameter: username The new account's username
      • Parameter: password_hash The new account's hashed password
      • Return: The id of the new user (NOT index)

      Functionality:

      1. Create a new Account, set its ID to 1000 plus the current size of the `accounts` vector.
      2. Set the new account's username to the corresponding parameter.
      3. Set the new account's password hash to the corresponding parameter.
      4. Push the new account into the `accounts` vector.
      5. Return the ID of the account you just created (NOT the index).

      size_t AccountManager::GetIndexOfAccount( size_t id )

      • Parameter: id The account ID of a certain user.
      • Return: The index of the Account in the `accounts` vector.

      Functionality:

      1. Iterate over all the elements of the `accounts` vector using a for loop.
        • Inspect each element's ID. If its ID matches the `id` passed in, then return this index (`i`).
      2. After the for loop, at this point nothing has been found. In this case, throw an `outofrange` exception because no matching ID was found.

      Account AccountManager::GetAccount( size_t id )

      • Parameter: id The account ID of a certain user.
      • Return: A copy of the Account element from `accounts` with that matching ID.
      • Use the `GetIndexOfAccount` function to get the index of the account that has the `id` given. Return the matching Account.

      void AccountManager::DeleteAccount( size_t id )

      • Parameter : id The account ID of a certain user.

      Functionality:

      1. Use the `GetIndexOfAccount` function to get the index of the account that has the `id` given.
      2. Use the `accounts` vector's `erase` function to remove this item.

      Refer to the vector documentation for how to use the erase function: https://cplusplus.com/reference/vector/vector/erase/


  • Turning in the assignment

    Screenshot: Before finishing up, run the automated tests and take a screenshot of all of your tests passing. Save the file somewhere as a .png.

    Back up your code: Open Git Bash in the base directory of your repository folder.

    1. Use git status to view all the changed items (in red).
    2. Use git add . to add all current changed items.
    3. Use git commit -m "INFO" to commit your changes, change "INFO" to a more descriptive message.
    4. Use git push -u origin BRANCHNAME to push your changes to the server.
    5. On GitLab.com, go to your repository webpage. There should be a button to Create a new Merge Request. You can leave the default settings and create the merge request.
    6. Copy the URL of your merge request.

    Turn in on Canvas:

    1. Find the assignment on Canvas and click "Start assignment".
    2. Select Upload file and upload your screenshot.
    3. Paste your GitLab URL into the Comments box.
    4. Click "Submit assignment" to finish.

😺 Unit 14/15 Status Update - Static members & friends (U15.SUP)

WEEK 8 - MAR 4

UNIT 16: Anonymous functions

πŸ“–οΈ Reading - Anonymous functions (U16.READ)

  • What are anonymous functions?

    Anonymous functions are functions we can define in-line in our code, without giving that function a name. In C#, the LINQ framework allows us to use Lambda functions, and in JavaScript and with JQuery we see anonymous functions used a lot as the result of other functions completing, or a function passed around as a parameter to some other function.

    This can be confusing to read without seeing it. In software development, I've usually used these in C# to sort through or search through data to get returned data based on some criteria - without having to write a totally new function. In JQuery, it's common to make a function as a "do this once this other function is done".

  • Anonymous functions in C++

    The form of an Anonymous Function in C++ looks like this:

    [CAPTURE_CLAUSE] (PARAMETER_LIST) -> RETURN_TYPE {
        FUNCTION_BODY
    };
    

    So, for example, if you need to sum two numbers but only inside a specific scope:

    int result = []( int a, int b ) {
        return a + b;
    }( 1, 2 );
    

    For these next examples, let's say we have a vector<Restaurant> all_data to look through, and a string find_me as the search term. The anonymous functions we define would be inside another function, so let's use a shell function like this:

    Empty capture
    By doing an empty capture, denoted by empty square brackets [], this means the internal (anonymous) function does not take any variables in the current scope. In this case, it doesn't automatically gain access to all_data or find_me. Instead, we have to specify input parameters as the parameter list and At the end of the anonymous function specification is the function call, where we have to pass in the arguments.
    // vector<Restaurant> all_data and
    // string find_me are in scope
    
    vector<Restaurant> matchedData = []                   // Capture clause
      ( vector<Restaurant> restaurants, string city )     // Parameter list
      -> vector<Restaurant>                               // Return type
      {                                                   // Function body
        vector<Restaurant> matches;
        for ( auto& r : restaurants )
          {
            if ( r.m_name == name )
              {
                matches.push_back( r );
              }
          }
        return matches;
      }
      ( all_data, find_me );                             // Function call
    
    return matchedData;                   // The matched results will be returned here
    
    Capture variables within scope
    In this case, we're capturing the variables within scope using the equal sign within the square brackets, [=]. This means that we don't need to specify a parameter list for any items that are in this scope. Likewise, at the function call step, there is nothing in the parentheses - no arguments being passed in this way.
    // vector<Restaurant> all_data and
    // string find_me are in scope
    
    vector<Restaurant> matchedData = [=]                  // Capture clause
      ()                                                  // Parameter list
      -> vector<Restaurant>                               // Return type
      {                                                   // Function body
        vector<Restaurant> matches;
        for ( auto& r : restaurants )
          {
            if ( r.m_name == name )
              {
                matches.push_back( r );
              }
          }
        return matches;
      }
      ();                                                 // Function call
    
    return matchedData;                   // The matched results will be returned here
    
    Condensing the anonymous functions

    Usually these would be written out much more concisely to take up less space in the code. An example of how this might look in an actual codebase are:

    // Print out matches
    []( vector<Restaurant>& data, string search ) -> void {
      for ( auto& d : data )
        {
          if ( d.name.find( search ) != string::npos )
            {
              cout << d.name << endl;
            }
        }
    }( all_restaurants, search_term );
    

  • Review questions:
    1. What form does an anonymous function take?
    2. What is captured with a [] capture clause?
    3. What is captured with a [=] capture clause?

πŸ”Ž Unit 16 Concept Intro - Anonymous functions (U16.CIN)

UNIT 17: Polymorphism

πŸ“–οΈ Reading - Polymorphism (U17.READ)

  • Design and polymorphism

    So much of the design tricks and features we utilize in C++ and other object-oriented programming languages all stem from the concept of "do not repeat yourself". If you're writing the same set of code in multiple places, there is a chance that we could design the program so that we only need to write that code once.

    reading_u17_Polymorphism_family.png

    Polymorphism is a way that we can utilize pointers and something called vtables to have a family of classes (related by inheritance) and be able to write one set of code to handle interfacing with all of those family members. We have a family tree of classes, and we can write our program to treat all the objects as the parent class, but the program will decide which set of functions to call at run time.

    Parent* myPtr = nullptr;
    if      ( type == 1 ) { myPtr = new ChildA; }
    else if ( type == 2 ) { myPtr = new ChildB; }
    
    myPtr->Display();
    delete myPtr;
    

    Example: Quizzer and multiple question types

    Let's say we are writing a quiz program and there are different types of questions: True/false questions, multiple choice, and fill-in-the-blank. They all have a common question string, but how they store their answers is different…

    Question TrueFalseQuestion MultipleChoiceQuestion FillInQuestion
    # m_question : string # m_question : string # m_question : string # m_question : string
      # m_answer : bool # m_options : string[4] # m_answer : string
        # m_correct : int  
    + bool AskQuestion() + bool AskQuestion() + bool AskQuestion() + bool AskQuestion()
    + void DisplayQuestion()   + void ListAllAnswers()  

    How would you store a series of inter-mixed quiz questions in a program? Without polymorphism, you might think to just have separate vectors or arrays for all the questions:

    vector<TrueFalseQuestion>       tfQuestions;
    vector<MultipleChoiceQuestion>  mcQuestions;
    vector<FillInQuestion>          fiQuestions;
    

    Utilizing polymorphism in C++, we could simply store an array of pointers of the parent type:

    vector<Question*> questions;
    

    And then initialize the question as the type we want during creation:

    questions.push_back(new TrueFalseQuestion);
    questions.push_back(new MultipleChoiceQuestion);
    questions.push_back(new FillInQuestion);
    

    Since we are using the `new` keyword here, we would also need to make sure to `delete` these items at the end of the program:

    for (auto& question : questions)
    {
        delete question;
    }
    

    Other design considerations

    When we're working with polymorphism in this way, we need to be able to treat each child as its parent, from a "calling functions" perspective. Each child can have its own unique member functions and variables, but when we're making calls to functions via a pointer to the parent type, the parent only knows about functions that it, itself, has.

    Let's say that the `Question` class has a `DisplayQuestion()` function. Since all its children use `mquestion` in the same way and inherit this function, it will be fine to call it via the pointer.

    ptrQuestion->DisplayQuestion(); // ok
    

    But with a function that belongs to a child - not the parent's interface - we wouldn't be able to call that function via the pointer without casting.

    ptrQuestion->ListAllAnswers();  // not ok
    
    (static_cast<MultipleChoiceQuestion*>(ptrQuestion))->ListAllAnswers(); ; ok
    

    You could, however, still call that `ListAllAnswers` function from within `MultipleChoiceQuestion`'s `DisplayQuestion` function, and that would still work fine…

    bool MultipleChoiceQuestion::AskQuestion()
    {
        DisplayQuestion();
        ListAllAnswers();
        // etc.
    }
    

    Still fuzzy? That's OK, this is just an overview; we're going to step into how all this works more in-depth next.


  • Reviewing classes and pointers
    • Review: Class inheritance and function overriding

      Some things to remember about inheritance with classes:

      • Any public or protected members (functions and variables) are inherited by the child class. (e.g., m_question, DisplayQuestion(), and AskQuestion()).
      • A child class can override the a parent's function by declaring and defining a function with the same signature. (e.g., AskQuestion()).
      • If the child class doesn't override a parent's function, then when that function is called via the child object it will call the parent's version of that function. (e.g., DisplayQuestion()).

      reading_u17_Polymorphism_questioninherit.png


    • Review: Pointers to class objects

      You can declare a pointer to point to the address of an existing object, or use the pointer to allocate memory for one or more new instances of that class…

      • Pointer to existing address: myPtr = &existingQuestion;
      • Pointer to allocate memory: myPtr = new Question;

      Then, to access a member of that object via the pointer, we use the -> operator, which is equivalent to dereferencing the pointer and then accessing a member:

      • Arrow operator: myPtr->DisplayQuestion();
      • Dereference and access: (*myPtr).DisplayQuestion();

  • Which version of the method is called?

    Let's say we have several objects already declared:

    Question q1, q2;
    MultipleChoiceQuestion mc1;
    

    We could create a Question* ptr that points to q1 or q2 or even mc1

    Question* ptr;
    ptr = &q1;  ; ok
    ptr = &q2;  ; ok
    ptr = &mc1; ; ok?
    

    reading_u17_Polymorphism_questioninherit.png

    And, any functions that the Question class and the MultipleChoiceQuestion class could be called from this pointer…

    ptr->DisplayQuestion();
    

    This is fine for any member methods not overridden by the child class. But, which version of the function is called if we used an overridden method?

    ptr->AskQuestion();
    

    reading_u17_Polymorphism_whichone.png

    • No virtual methods - Which AskQuestion() is called?

      Let's say our class declarations look like this:

      Question:

      class Question
      {
          public:
          bool AskQuestion();
          // etc.
      };
      

      MultipleChoiceQuestion:

      class MultipleChoiceQuestion : public Question
      {
          public:
          bool AskQuestion();
          // etc.
      };
      

      Here are the outputs we could have from using pointers in different ways:

      A. Question* pointer, Question's AskQuestion() is called:

      Question* ptr = new Question;
      bool result = ptr->AskQuestion();
      

      B. MultipleChoiceQuestion* pointer, MultipleChoiceQuestion's AskQuestion() is called:

      MultipleChoiceQuestion* ptr = new MultipleChoiceQuestion;
      bool result = ptr->AskQuestion();
      

      C. Question* pointer, Question's AskQuestion() is called:

      Question* ptr = new MultipleChoiceQuestion;
      bool result = ptr->AskQuestion();
      

      "Well, how is that useful at all? The function called matches the pointer data type!" - true, but we're missing one piece that allows us to call any child's version of the method from a pointer of the parent type…


    • Virtual methods - Which AskQuestion() is called?

      Instead, let's mark our method with the virtual keyword:

      Question:

      class Question
      {
          public:
          virtual bool AskQuestion();
          // etc.
      };
      

      MultipleChoiceQuestion:

      class MultipleChoiceQuestion : public Question
      {
          public:
          virtual bool AskQuestion();
          // etc.
      };
      

      Here are the outputs we could have from using pointers in different ways:

      A. Question* pointer, Question's AskQuestion() is called:

      Question* ptr = new Question;
      bool result = ptr->AskQuestion();
      

      B. MultipleChoiceQuestion* pointer, MultipleChoiceQuestion's AskQuestion() is called:

      MultipleChoiceQuestion* ptr = new MultipleChoiceQuestion;
      bool result = ptr->AskQuestion();
      

      C. Question* pointer, MultipleChoiceQuestion's AskQuestion() is called:

      Question* ptr = new MultipleChoiceQuestion;
      bool result = ptr->AskQuestion();
      

      With this, we can now store a list of Question* objects, and each question can be a different child class, but we can write one set of code to interact with each one of them.


  • Virtual methods, late binding, and the Virtual Table

    By using the virtual keyword, something happens with our functions - it allows the pointer-to-the-parent class to figure out which version of the method to actually call, instead of just defaulting to the parent class' version. But how does this work?

    The virtual keyword tells the compiler that the function called will be figured out later. By marking a function as virtual, it then is added to something called a virtual table - or vtable.

    The vtable stores special pointers to functions. If a class contains at least one virtual function, then it will have its own vtable.

    reading_u17_Polymorphism_vtables1.png

    With the Question class, it isn't inheriting any methods from anywhere else so the vtable reflects the same methods it has. But, we also have the child class that inherits DisplayQuestion() and overrides AskQuestion().

    reading_u17_Polymorphism_vtables2.png

    Because of these vtables, we can then have our pointers reference this vtable when figuring out which version of a method to call. Doing this is called late binding or dynamic binding.


    • When should we use virtual?
      Destructors should always be virtual.

      If you're working with inheritance. By making your destructor virtual for each class in the family, you are ensuring that the correct destructor will be called when the object is destroyed or goes out of scope. If you don't make it virtual and utilize polymorphism, the correct destructor may not be called (i.e., Question's instead of MultipleChoiceQuestion's).

      Constructors cannot be marked virtual

      When the object is instantiated (e.g., ptr = new MultipleChoiceQuestion;) that class' constructor will be called already.

      Not every function needs to be virtual.

      It's all about design. Though generally, if you always want the parent's version of a method to be called, you wouldn't override that method in the child class anyway.


    • Designing interfaces with pure virtual functions and abstract classes

      Polymorphism works best if you're designing a family of classes around some sort of interface that they will all share. In the C# language, there is an interface type that is available to you, but that's not here in C++, so we implement it via classes.

      What is an Interface?

      When we're designing a class to be an interface, the idea is that the user (or other programmers) will just see a set of functions it will interface with - none of the behind-the-scenes, how-it-works stuff.

      Most of the devices we use have some sort of interface, hiding the more complicated specifics of how it actually works within a case. For example, a calculator has a simple interface of buttons, but if you opened it up you would be able to see its hardware and how everything is hooked up.

      We use the same idea with writing software, where we expose some interface (in the form of the class' public methods) as how the "user" interacts with our class.

      reading_u17_Polymorphism_questionmanager.png

      A common design practice is to write the first base (parent) class to be a specification of this sort of interface that all its children will adhere to, and to ensure that each child class must follow the interface by using something that the compiler will enforce itself: pure virtual functions.

      When working with our Quiz program idea, our base class is Question, which would define the interface for all other types of Questions. Generally, our base interface class would never be instantiated - it is not complete in and of itself (i.e., a Question with no types of Answers) - but is merely used to outline a common interface for its family members.

      Here is a blank diagram with just the member variables defined, but not yet any functionality, so that we can begin to step through thinking about an interface:

      reading_u17_Polymorphism_questionfamily.png

      Thinking in terms of implementing a program that could edit questions (such as the teacher's view of the quiz), as well as that could ask questions (such as the student's view), we can try to think of what kind of functionality we would need from a question…

      • Setup the question, answer(s)
      • Display the question to the user
      • Get the user's answer
      • Check if the user's answer was correct

      But, the specifics of how each of these question types stores the correct answer (and what data type it is) and validates it differ between each of them…

        User answer Stored answer Validate
      True/false bool bool answer Userinput = answer?=
      MultiChoice int string options[4] Userinput = answer?=
      FillIn int string answer Userinput = answer?=

      We could design our Questions so that they have functionality that interacts with the user directly (e.g., a bool function that asks the user to enter their response and returns true if they got it right and false if not) rather than writing functions around returning the actual answer (which would be more difficult because they have different data types).

      • Set up question
      • Run question
      Declarations:

      We can set up a simple interface for our Questions with these functions. They've been marked as virtual, which allows us to use polymorphism, and they've also been marked with = 0 at the end, marking them as pure virtual - this tells the compiler that child classes must implement their own version of these methods. A function that contains pure virtual methods is called an abstract class.

      class Question
      {
        public:
        virtual void Setup() = 0;
        virtual bool Run() = 0;
      
        protected:
        string m_question;
      };
      

      Now our child classes can inherit from Question. They will be required to override Setup() and Run(), and we can also have additional functions as needed for that implementation:

      class MultipleChoiceQuestion : public Question
      {
        public:
        virtual void Setup();
        virtual bool Run();
        void ListAllAnswers();
      
        protected:
        string m_options[4];
        int m_answer;
      };
      
      Definitions:

      Each class will have its own implementation of these interface functions, but since they're part of an interface, when we build a program around these classes later we can call all of them the same way.

      Question:

      void Question::Setup() {
          cout << "Enter question: ";
          getline( cin, m_question );
      }
      

      TrueFalseQuestion:

      void TrueFalseQuestion::Setup() {
          Question::Setup();
          cout << "Enter answer (0 = false, 1 = true): ";
          cin >> m_answer;
      }
      

      MultipleChoiceQuestion:

      void MultipleChoiceQuestion::Setup() {
        Question::Setup();
      
        for ( int i = 0; i < 4; i++ )
          {
            cout << "Enter option " << i << ": ";
            getline( cin, m_options[i] );
          }
      
        cout << "Which index is correct? ";
        cin >> m_answer;
      }
      

      FillInQuestion:

      void FillInQuestion::Setup() {
          Question::Setup();
          cout << "Enter answer text: ";
          getline( cin, m_answer );
      }
      
      Function calls:

      Now, no matter what kind of question subclass we're using, we can utilize the same interface - and the same code.

      // Create the pointer
      Question* ptr = nullptr;
      
      // Allocate memory
      if ( choice == "true-false" )
      {
          ptr = new TrueFalseQuestion();
      }
      else if ( choice == "multiple-choice" )
      {
          ptr = new MultipleChoiceQuestion();
      }
      else if ( choice == "fill-in" )
      {
          ptr = new FillInQuestion();
      }
      
      // Set up the question
      ptr->Setup();
      
      // Run the question
      ptr->Run();
      
      // Free the memory
      delete ptr;
      

      And, utilizing this interface, we could then store a vector<Question*> and set up each question as any question subclass without any duplicate code.


  • Example usage: Game objects

    Let's say we have created a family tree of game objects, starting at the most basic object that has an \((x, y)\) coordinate and dimensions:

    class GameObject
    {
    public:
      GameObject();
    
      void Setup( int initialX = 0, int initialY = 0, const std::string& name = "unnamed" );
      void SetTexture( const sf::Texture& texture, sf::IntRect& textureCoordinates );
      std::string GetName() const;
      void Update();
      void Draw();
      // ... etc ...
    
    protected:
      sf::Vector2f m_position;
      std::string m_name;
      sf::Sprite m_sprite;
      sf::IntRect m_textureCoordinates;
      // ... etc ...
    };
    

    Then we might have something like an unanimated item in the world, but maybe it has physics so it needs the ability to update:

    class Item : public GameObject
    {
    public:
      Item();
      void SetObjectType( ObjectType type );
      std::string GetObjectTypeName() const;
      void SetImageCode( int code );
      int GetImageCode() const;
      const sf::Sprite& GetSprite() const;
      void Update();
      void Draw();
      // ... etc ...
    
    protected:
      ObjectType m_objectType;
      int m_imageCode;
      // ... etc ...
    };
    

    But then our player and NPC characters also have animated sprites and the ability to move with keyboard input or rudimentary AI:

    class Character : public GameObject
    {
    public:
        Character();
    
        void Update();
        void Draw();
        const sf::Sprite& GetSprite() const;
    
        void SetSpeed( int speed );
        void SetDirection( Direction direction );
        Direction GetDirection() const;
        CharacterType GetCharacterType() const;
    
        void SetAnimationInformation( int maxFrames, float animationSpeed );
    
        void Move( Direction direction, const Map::ReadableMap& gameMap );
        void Move( Direction direction );
        void Move( Direction direction, int minX, int minY, int maxX, int maxY );
        sf::IntRect GetDesiredPosition( Direction direction );
    
        GravityHandler gravity;
    
        void ForceSpriteUpdate();
        void Animate();
    
        void RestrictMovement();
        void SetRestrictMovement( bool value );
        sf::IntRect GetValidPositionRegion() const;
        void SetValidPositionRegion( sf::IntRect rect );
    
    protected:
        void MoveClipping( Direction direction );
        void BeginAttack();
        void EndAttack();
    
        Direction m_direction;
        int m_speed;
        float m_animationFrame;
        float m_maxFrames;
        float m_animationSpeed;
    
        SheetAction m_sheetAction;
        float m_sheetActionTimer;
    
        Action m_stateAction;
    
        CharacterType m_characterType;
    
        sf::IntRect m_validPositionRegion;
        bool m_restrictMovement;
    
    };
    

    If we didn't use polymorphism, we would have to store all objects in their own vector:

    vector<Character> m_npcList;
    vector<Item> m_pickups;
    vector<GameObject> m_decor;
    

    But utilizing polymorphism, we can store one vector of GameObject* objects initialized on the heap, and any common functionality they have (Update, Draw, etc.) could be accessed via that pointer.

    // Our storage
    vector<GameObject*> m_entities;
    
    // Creating a new item (elseware in program)
    GameObject* newItem = new Item;
    
    // Adding it to the list
    m_entities.push_back( newItem );
    
    // Accessing it later
    for ( auto& entity : entities )
    {
      entity->Update();
    }
    

  • Review questions:
    1. Polymorphism is a technique that…
    2. In order for polymorphism to work properly the member functions to be used need to be marked as…
    3. A class Pet has been declared, with child classes Cat and Dog. Utilizing Polymorphism, how would we declare a new Dog object that is generalized as a Pet?

πŸ”Ž Unit 17 Concept Intro - Polymorphism (U17.CIN)

WEEK 9 - MAR 11 - SPRING BREAK

Take a break!

\newpage

WEEK 10 - MAR 18

UNIT 18: Operator overloading

πŸ“–οΈ Reading - Operator overloading (U18.READ)

  • Overloading operators

    When creating our own classes, we may want to make certain operators mean certain things when used with our class. By default, we can't use math operators (+, -, /, *), stream operators (<<, >>), relational operators (<, <=, >, >=, ==, !=), the subscript operator [], and the assignment operator (=) with the classes we create. In order to do so, we would have to use operator overloading to define our own functions and how these operators work on our class.

    For example, we could define our own Fraction class, without overloaded arithmetic operators we would have to do math like this:

    Frac frac1( 1, 2 );       // 1/2
    Frac frac2( 2, 3 );       // 2/3
    Frac sum = frac1.Add( frac2 );  // Add 1/2 + 2/3
    

    When we set up the arithmetic operators so you could simply do:

    Frac frac1( 1, 2 );     // 1/2
    Frac frac2( 2, 3 );     // 2/3
    Frac sum = frac1 + frac2; // Add 1/2 + 2/3
    

    …Which is much more intuitive.


    • Arithmetic operators

      Arithmetic operators will be friend functions of our class. This is because it will take two objects and return a third, so it doesn't "neatly" fit inside one class as a member function.

      Friend arithmetic function declarations:

      class MyClass
      {
      public:
        friend MyClass operator+( const MyClass& item1,
                                  const MyClass& item2 );
        friend MyClass operator-( const MyClass& item1,
                                  const MyClass& item2 );
        friend MyClass operator/( const MyClass& item1,
                                  const MyClass& item2 );
        friend MyClass operator*( const MyClass& item1,
                                  const MyClass& item2 );
      };
      

      Then the function definition would go in a source file (it could go in MyClass.cpp as well, but it's not a member function so don't prefix MyClass:: on these functions).

      MyClass operator+( const MyClass& item1,
                         const MyClass& item2 )
      {
        MyClass sum;
        sum.memberA = item1.memberA + item2.memberA;
        return sum;
      }
      

    • Relational operators

      Relational operators also operate on two objects so these are also friend functions.

      Friend relational function declarations:

      class MyClass
      {
      public:
        friend bool operator==( const MyClass& item1,
                                const MyClass& item2 );
        friend bool operator!=( const MyClass& item1,
                                const MyClass& item2 );
        friend bool operator<( const MyClass& item1,
                               const MyClass& item2 );
        friend bool operator<=( const MyClass& item1,
                                const MyClass& item2 );
        friend bool operator>( const MyClass& item1,
                               const MyClass& item2 );
        friend bool operator>=( const MyClass& item1,
                                const MyClass& item2 );
      };
      

      Function definition:

      bool operator==( const MyClass& item1,
                       const MyClass& item2 )
      {
        return ( item1.memberA == item2.memberA );
      }
      

    • Stream operators

      Stream operators will take in a stream reference and the object to input or output and return the stream reference of the modified stream. These are also friend functions. We use the ostream and istream classes as the return type and parameter since this is the parent class of cout/cin and ofstream/ifstream, as well as other stream types.

      Friend stream function declarations:

      class MyClass
      {
      public:
        friend ostream& operator<<( ostream& out,
                                    MyClass& item );
      
        friend istream& operator>>( istream& in,
                                    MyClass& item );
      };
      

      Function definition:

      ostream& operator<<( ostream& out, MyClass& item );
      {
        out << item.memberA;
        return out;
      }
      
      istream& operator>>( istream& in, MyClass& item );
      {
        in >> item.memberA;
        return in;
      }
      

      You can output or input multiple items in these functions so that you can get input for all class members, or output all class members, in one statement.


    • Subscript operator

      The subscript operator is usually used when creating a type of list structure so that we can access an element at some position. This function is a member function of the class.

      Member function declaration:

      class MyClass
      {
      public:
        string& operator[] ( const int index );
      
      private:
        string m_data[100];
      };
      

      Function definition:

      string& MyClass::operator[]( const int index )
      {
        return m_data[ index ];
      }
      

    • Assignment operator

      The assignment operator is similar to the copy constructor in that we are telling our class how it will copy the data from another object of the same class. It's up to us to decide which members to copy over as part of this process.

      Member function declaration:

      class MyClass
      {
      public:
        MyClass& operator=( const MyClass& other );
      
      private:
        int a;
        float b;
      };
      

      Function definition:

      MyClass& MyClass::operator=( const MyClass& other )
      {
        if ( this == &other ) { return *this; }
      
        a = other.a;
        b = other.b;
      
        return *this;
      }
      

      Note the if statement in our assignment operator. This is checking to see if the memory address of "this" class is the same as the "other" class being passed in. We don't want to make a copy if these are actually the same object, so in that case we return "this" (but it must be de-referenced).

      Otherwise, we can copy whichever member variables we would like, and make sure to return "this" at the end (also de-referenced).

πŸ§‘β€πŸ”¬ Lab - Operator overloading (U18.LAB)

pixel-goals.png Goals:

  • Practice working with overloading the following operator types:
    • Arithmetic
    • Relational
    • Assignment
    • Streams

pixel-turnin.png Turn-in:

  • 1. You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-practice.png This assignment only has the graded program; no additional practice programs

pixel-fight.png Stuck on the assignment? Not sure what to do next?

  • Continue scrolling down in the documentation and see if you're missing anything.
  • Try skimming through the entire assignment before getting started, to get a high-level overview of what we're going to be doing.
  • If you're stuck on a practice program, try working on a different one and come back to it later.
  • If you're stuck on the graded program, try the practice programs first.

  • Setup: Starter code and new branch

    Start this assignment by checking out main, pulling latest to get the files, and creating a new branch…

    git checkout main
    git pull
    git checkout -b u18
    
    • Graded program is located in the u18_OperatorOverloading folder.

  • The Fraction program
    --------------------------------------------------------------------------------
     | MAIN MENU |
     -------------
    
    Fraction 0: 1/1 (1.000)
    Fraction 1: 1/1 (1.000)
    
    OPTIONS
    
     0.	Quit program
     1.	Run unit tests
     2.	Initialize fraction
     3.	Arithmetic
     4.	Relational
    

    This program allows the user to set two fractions and then utilize the arithmetic and relational operators on those fractions. From the main menu, you can also run option #1 to run the unit tests. These will be used to help you verify that your code works properly.

    Once the Fraction is implemented, you’ll be able to run various functionality to test manually:

    --------------------------------------------------------------------------------
     | INITIALIZATION |
     ------------------
    
    Fraction 0: 1/1 (1.000)
    Fraction 1: 1/1 (1.000)
    
    Update which fraction? #
     >> 0
    
    Enter new fraction with NUMERATOR DENOMINATOR
    Make sure to separate the numerator/denominator with a SPACE
    >> 1 2
    
    Fraction is now: 1/2
    

    ----------------------------------------
     | ARITHMETIC |
     --------------
    
    Fraction 0: 1/2 (0.500)
    Fraction 1: 4/5 (0.800)
    
    0. Go back
    1. Add      1/2 + 4/5
    2. Subtract 1/2 - 4/5
    3. Multiply 1/2 * 4/5
    4. Divide   1/2 / 4/5
    Perform which operation?
    
     >> 1
    
    1/2 + 4/5 = 13/10
    

    ----------------------------------------
     | RELATIONAL |
     --------------
    
    Fraction 0: 1/2 (0.500)
    Fraction 1: 4/5 (0.800)
    
    0. Go back
    1. Is 1/2 == 4/5?
    2. Is 1/2 != 4/5?
    3. Is 1/2 <= 4/5?
    4. Is 1/2 >= 4/5?
    3. Is 1/2 <  4/5?
    4. Is 1/2 >  4/5?
    
    Perform which operation?
    
     >> 1
    
    1/2 == 4/5 is FALSE
    

  • Running the unit tests

    lab_u18_OperatorOverloading_Tester.png

    After running the unit tests view the testresult.html file that is located wherever your project file is – such as vcxproj for Visual Studio or cbp for Code::Blocks.

    After implementing each function in the Fraction class, you should be running the unit tests to verify your work for that item.

    Test driven development is a style of software development where tests are created and implemented before the thing it's testing is implemented. There are some benefits to writing tests first:

    • Ever write an essay and try to proof read it and miss mistakes because your brain fills it in? Well if you write the Fraction class first, your brain will do this while you're writing the tests - you'll forget certain things to look for.
    • Writing test cases first helps you think of what are the requirements for the functionality and helps you document that before doing any coding.

    (That being said, none of the companies I've worked at have used TDD, though most places I've worked have required programmers to write unit tests to test their code after the fact.)

    Having tests in general helps with…

    • Making sure that your logic works as intended.
    • Checking for edge cases / error cases.
    • As you keep working on new features, make sure you aren't breaking old features (old tests don't fail? Yay!)

    In the Tester class, there is a function to test each of the functions available in the Fraction class. We will implement tests for all functionality to ensure we have complete coverage to validate the Fraction class.


  • The Fraction class
    class Fraction
    {
    public:
      Fraction();
    
      float GetDecimal() const;
    
      Fraction CommonDenominatorize( const Fraction& other ) const;
    
      // Operator overloading
      // Assignment operator
      Fraction& operator=( const Fraction& other );
    
      // Arithmetic operators
      friend Fraction operator+( const Fraction& left, const Fraction& right );
      friend Fraction operator-( const Fraction& left, const Fraction& right );
      friend Fraction operator*( const Fraction& left, const Fraction& right );
      friend Fraction operator/( const Fraction& left, const Fraction& right );
    
      // Relational operators
      friend bool operator==( const Fraction& left, const Fraction& right );
      friend bool operator!=( const Fraction& left, const Fraction& right );
      friend bool operator<=( const Fraction& left, const Fraction& right );
      friend bool operator>=( const Fraction& left, const Fraction& right );
      friend bool operator<( const Fraction& left, const Fraction& right );
      friend bool operator>( const Fraction& left, const Fraction& right );
    
      // Stream operators
      friend ostream& operator<<( ostream& out, const Fraction& fraction );
      friend istream& operator>>( istream& in, Fraction& fraction );
    
      friend class Tester;
    
    private:
      int m_num;
      int m_denom;
    };
    

    The Fraction class contains a m_num and m_denom as member variables. Beyond that it’s all functionality. The assignment operator = is used to assign a value from one Fraction to the local ("this") Fraction. It is the only operator that we will be implementing that belongs to the class itself.

    Arithmetic operators allow us to add, subtract, multiply, and divide two fractions together, the relational operators allow us to compare two fractions, and the stream operators let us input and output data from our fraction. These functions are classified as friends as they are not member functions! They are independent functions, but as a friend the function will have access to a Fraction’s m_num and m_denom, which are normally private.


    • float Fraction::GetDecimal() const

      This function is responsible for returning a decimal version of a fraction, such as 0.5 for \(\frac{1}{2}\). To do this, you need to divide the numerator and denominator.

      The result of two integers being divided will be an integer result, which gets rid of our decimal point.

      Make sure to CAST one of the values to a float in order to get the correct result: float( m_num )


    • Fraction Fraction::CommonDenominatorize( const Fraction& other ) const

      We're going to lazily get the common denominator by multiplying FractionA's numerator by FractionB's denominator, and multiply FractionA's denominator by FractionB's denominator. (That's not a typo, remember that d/d = 1).

      1. Check to see if m_denom is equivalent to other.m_denom – if this is the case, then just return *this instead.
      2. Otherwise:
        1. Create a new Fraction to store the result.
        2. Assign the Fraction to the result of *this fraction x other.denom/other.denom.
        3. Return the result Fraction.

    • Fraction& Fraction::operator=( const Fraction& other )
      1. Error check: If THIS item's address is the same as the rhs address then just return *this (don't try to change the data): = if ( this = &other ) { return *this; }
      2. Otherwise, set the numerator to the =other=’s numerator.
      3. Set the denominator to the =other=’s denominator.

    • Fraction operator*( const Fraction& left, const Fraction& right )

      The result of the left fraction times the right fraction, in a Fraction form.

      \[\frac{n_L}{d_L} \cdot \frac{n_R}{d_R} = \frac{n_L \cdot n_R}{d_L \cdot d_R}\]

      • \(n_L\) is "left numerator"
      • \(d_L\) is "left denominator"
      • \(n_R\) is "right numerator"
      • \(n_L\) is "left numerator"

      lab_u18_OperatorOverloading_Multiply.png


    • Fraction operator/( const Fraction& left, const Fraction& right )

      The result of the left fraction divided by the right fraction, in a Fraction form.

      \[\frac{n_L}{d_L} \div \frac{n_R}{d_R} = \frac{n_L \cdot d_R}{d_L \cdot n_R}\]

      • \(n_L\) is "left numerator"
      • \(d_L\) is "left denominator"
      • \(n_R\) is "right numerator"
      • \(n_L\) is "left numerator"

      lab_u18_OperatorOverloading_Division.png


    • Fraction operator+( const Fraction& left, const Fraction& right )

      When adding two fractions together we need to get a common denominator. After we have a common denominator, the solution will share this same denominator, and the numerator will be the left numerator plus the right numerator.

      Steps:

      A. Original fractions: \[\frac{n_L}{d_L} + \frac{n_R}{d_R}\]

      B. Create a new version of the left fraction with the common denominator. \[\frac{n_1}{d_1} = \frac{n_L \cdot d_R}{d_L \cdot d_R}\] Fraction commonLeft = left.CommonDenominatorize( right )

      C. Create a new version of the right fraction with the common denominator. \[\frac{n_2}{d_2} = \frac{n_R \cdot d_L}{d_R \cdot d_L}\] Fraction commonRight = right.CommonDenominatorize( left )

      D. The result is then: \[\frac{n_1+n_2}{d_L \cdot d_R}\]


    • Fraction operator-( const Fraction& left, const Fraction& right )

      Steps A, B, and C are the same as with addition.

      D. The result is then: \[\frac{n_1-n_2}{d_L \cdot d_R}\]


    • bool operator==( const Fraction& left, const Fraction& right )

      Returned output: true if the two fractions are the same (\(\frac{1}{2}\) and \(\frac{2}{4}\) are the same), false otherwise.

      Steps:

      A. We have two fractions, left and right: \(\frac{n_L}{d_L} = \frac{n_R}{d_R}\) ??

      B. Create a new version of the left fraction with the common denominator. \[\frac{n_1}{d_1} = \frac{n_L \cdot d_R}{d_L \cdot d_R}\] Fraction commonLeft = left.CommonDenominatorize( right )

      C. Create a new version of the right fraction with the common denominator. \[\frac{n_2}{d_2} = \frac{n_R \cdot d_L}{d_R \cdot d_L}\] Fraction commonRight = right.CommonDenominatorize( left )

      D. If the numerators are the same and the denominators are the same, then return true, or false otherwise.


    • bool operator!=( const Fraction& left, const Fraction& right )

      Returned output: true if the two fractions are NOT the same (\(\frac{1}{2}\) and \(\frac{2}{4}\) are the same), false otherwise.

      Steps:

      • If == returns true, return false instead.
      • If == returns false, return true instead.

      You can just return the result of !( a == b).


    • bool operator<=( const Fraction& left, const Fraction& right )

      Returned output: true if the left fraction is less than the right fraction (1/2 is less than 3/4) or equal to (1/2 is equal to 2/4), false otherwise.

      Create two common denominatorized Fractions again (like with ==) and then compare the numerators: Return the result of commonLeft.mnum <= commonRight.mnum.


    • bool operator>=( const Fraction& left, const Fraction& right )

      Returned output: true if the left fraction is greater than the right fraction (3/4 is greater than 1/2) or equal to (1/2 is equal to 2/4), false otherwise.

      Create two common denominatorized Fractions again (like with ==) and then compare the numerators: Return the result of commonLeft.mnum <= commonRight.mnum.


    • bool operator<( const Fraction& left, const Fraction& right )

      Returned output: true if the left fraction is less than the right fraction (1/2 is less than 3/4), false otherwise.

      Common denominatorize both fractions, compare the numerators.


    • *bool operator>( const Fraction& left, const Fraction& right )8

      Returned output: true if the left fraction is greater than the right fraction (3/4 is greater than 1/2), false otherwise.

      Common denominatorize both fractions, compare the numerators.


    • ostream& operator<<( ostream& out, const Fraction& fraction )

      Output the numerator and the denominator to the out stream, formatted in a way that is clearly a fraction, like "1/2". (Then return the out stream back out.)

      ostream& operator<<( ostream& out, const Fraction& fraction )
      {
        out << fraction.m_num << "/" << fraction.m_denom;
        return out;
      }
      

    • 8istream& operator>>( istream& in, Fraction& fraction )*

      Input the numerator and then the denominator via the in input stream. (Then return the in stream back out.)

      istream& operator>>( istream& in, Fraction& fraction )
      {
        in >> fraction.m_num >> fraction.m_denom;
        return in;
      }
      

      Once all the unit tests run and pass, you can reasonably assume that your Fraction logic is correct. (I like to think that I’m not a terrible test writer…)


UNIT 19: Third party libraries

πŸ“–οΈ Reading - Third party libraries (U19.READ)

  • Setting up third party libraries
    • Quick checklists
      • Setting up a library in Visual Studio
        1. Download library from website.
        2. Extract library to your hard-drive.
        3. Create a new Empty project in Visual Studio.
        4. Create a new source file (main.cpp) in Visual Studio.
        5. Right-click on your project and go to Properties.
        6. Make sure your Configuration is set to Release and your Platform is set to x64.
        7. Under C/C++ > General, add SFML's include path to the Additional Include Directories property.
        8. Under Linker > General, add SFML's lib path to the Additional Library Directories property.
        9. Under Linker > Input, click the "v" dropdown on Additional Dependencies and click <Edit…>. Add your related .lib files (e.g., sfml-graphics.lib, sfml-window.lib, sfml-system.lib).
        10. Click OK.
        11. Move the .dll files (sfml-graphics-2.dll, sfml-window-2.dll, sfml-system-2.dll) from the SFML/bin folder to your project directory.
        12. Done with configuration.
      • Setting up a library in Code::Blocks (Windows)
        1. Download library from website.
        2. Extract library to your hard-drive.
        3. Create a new Empty project in Code::Blocks.
        4. Create a new empty file (main.cpp) in Code::Blocks.
        5. Right-click on your project and go to Build options…
        6. Make sure you've selected Release instead of Debug on the left-hand side.
        7. Under Search directories > Compiler, add SFML's include path.
        8. Under Search directories > Linker, add SFML's lib path.
        9. Under Linker settings and Link libraries:, add the libraries: sfml-system, sfml-graphics, sfml-window.
        10. Click OK.
        11. Move the .dll files (sfml-graphics-2.dll, sfml-window-2.dll, sfml-system-2.dll) from the SFML/bin folder to your project directory.
        12. Done with configuration.

    • Setting up third-party libraries in your IDE
      • Downloading and extracting the library

        For these instructions we will be working with the SFML (Simple Fast Media Library) library, but setting up most libraries will work about the same.

        • Getting a library in Windows

          Downloading: Go to the library webpage (https://www.sfml-dev.org/download.php) and select "SFML", latest stable version (as of writing this is 2.6.1).

          From the downloads page, there are various options for a Windows download.

          third_party_libraries_sfmlpage.png

          Figure 6: An image of the SFML downloads page, showing different versions that can be downloaded for Windows.

          If you're working with Visual Studio, then download the latest version, even if the year doesn't match. You'll probably want the 64-bit verison, since most computers these days are 64-bit.

          If you're working with Code::Blocks, you will probably want the 64-bit SEH option ("GCC 13.1.0 MinGW (SEH) - 64-bit"), but you can double-check which version you need by going to your CodeBlocks install directory (such as C:\Program Files\CodeBlocks\MinGW\bin\) and looking for a file called "libgcc"… based on this version it will tell you which version of SFML you need to download:

          reading_u19_ThirdPartyLibraries_sfmlpage_libgcc.png

          In Windows I like to have a "LIBRARIES" folder on my root directory, such as "C:\LIBRARIES\". So, all extracted, my default path for SFML would be:

          C:\LIBRARIES\SFML-2.6.1

        • Getting a library in Linux

          You can download many libraries from your distro's package manager, such as synaptic for Debian-based systems. Do a search for "libsfml" and download the libsfml-dev package. (Most libraries are "libXYZ-dev" as well.)

          reading_u19_ThirdPartyLibraries_synaptic.png

          The library will be automatically installed on your machine, with the relevant paths for configuration being.

        • Keeping track of important paths

          You'll need to know where your library files are being kept while we're configuring the project, so make sure to take note of the paths. Note that if you're in Windows, you might have unzipped your library to a different directory than me, and will need to replace "C:\LIBRARIES\" with wherever you put your SFML folder.

          Type Windows Path Linux Path Description
          .dll location C:\LIBRARIES\SFML-2.6.1\bin\ - When distributing your .exe for Windows, you'll want to include the relevant .dll files, held here.
          Library files C:\LIBRARIES\SFML-2.6.1\lib\ /usr/lib/x86_64-linux-gnu/ SFML's C++ code is compiled into libraries, available here.
          .hpp files C:\LIBRARIES\SFML-2.6.1\include\ /usr/include/ This shows the IDE what function and class declarations are available.

    • Setting up the project

      You can also refer to the SFML Tutorials page on how to set up Visual Studio, Code::Blocks, and other platforms.

      The following write-ups are step-by-step guides with screenshots to show you how to configure Visual Studio and Code::Blocks for SFML…


      • Visual Studio

        1. Create a project: Create an Empty C++ Project in Visual Studio.

        reading_u19_ThirdPartyLibraries_vs1_2023.png

        2. Set your Project name and Location, then click Create.

        reading_u19_ThirdPartyLibraries_vs2_2023.png

        3. Create a source file: Create a new .cpp file for your project by going to your Solution Explorer, right-clicking on the project, going to Add, then selecting New Item….

        reading_u19_ThirdPartyLibraries_vs3_2023.png

        4. Name your new file "main.cpp" and click Add.

        reading_u19_ThirdPartyLibraries_vs4_2023.png

        In your main.cpp, it can be useful to add some starter code to make sure your project will build properly with the library linked within. Here is a very stripped-down SFML program:

        #include <SFML/Graphics.hpp>
        
        int main()
        {
          sf::RenderWindow window(sf::VideoMode(200, 200), "Window");
          return 0;
        }
        

        At first this will generate build errors, but once we have the library set up properly, it should compile.

        5. Go to the properties: Right-click on your project in the Solution Explorer and select Properties.

        reading_u19_ThirdPartyLibraries_vs5_2023.png

        Make sure at the top of this screen, to set the Configuration to Release and Platform to x64.

        reading_u19_ThirdPartyLibraries_vs6_2023.png

        6. Set up the include directory: On the left-hand side, open the C/C++ category and the General sub-category. In the Additional Include Directories property, paste the path to your SFML directory's include folder, such as:

        C:\LIBRARIES\SFML-2.6.1\include

        reading_u19_ThirdPartyLibraries_vs7_2023.png

        7. Set up the lib directory: Now under the Linker category and the General sub-category, add the path for the SFML directory's lib folder to the Additional Library Directories property, such as:

        C:\LIBRARIES\SFML-2.6.1\lib

        reading_u19_ThirdPartyLibraries_vs8_2023.png

        8. Set up dependencies: Under the Linker category and the Input sub-category, click the "v" button next to Additional Dependencies. Click the <Edit…> button.

        reading_u19_ThirdPartyLibraries_vs9_2023.png

        In the pop-up box, add the following in the top text box:

        sfml-graphics.lib
        sfml-window.lib
        sfml-system.lib
        

        reading_u19_ThirdPartyLibraries_vs10_2023.png

        Click OK when done with setting up these properties.

        9. Build the program: In the dropdown near the play button, select Release as our build type, and make sure x64 is selected as our platform.

        reading_u19_ThirdPartyLibraries_vs11_2023.png

        Now when you build there shouldn't be any errors:

        reading_u19_ThirdPartyLibraries_vs12_2023.png

        10. Adding the .dll files to the project:

        If we try to run the program right now, it will complain about missing dll files:

        reading_u19_ThirdPartyLibraries_vs13_2023.png

        We need to copy the dll files from the SFML library to our project now.

        Right-click on the project and select Open Folder in File Explorer.

        reading_u19_ThirdPartyLibraries_vs14_2023.png

        In the SFML directory's bin folder (e.g., C:\LIBRARIES\SFML-2.6.1\bin\), copy the relevant library files (sfml-graphics-2.dll, sfml-system-2.dll, sfml-window-2.dll).

        reading_u19_ThirdPartyLibraries_vs15_2023.png

        Paste these files into your project directory:

        reading_u19_ThirdPartyLibraries_vs16_2023.png

        Your program should now start, open, and close without any error messages. Currently the program doesn't really do anything because there's no game loop within. Reference the SFML setup page (https://www.sfml-dev.org/tutorials/2.6/start-vc.php) for some test code you can use to verify that everything works properly.

        My Visual Studio setup video: https://www.youtube.com/watch?v=4BoB5H2Kvzo


      • Code::Blocks (Windows/Linux)

        1. Create a project: Create a new empty project in Code::Blocks.

        reading_u19_ThirdPartyLibraries_cb1_2023.png

        2. Set the Project title and Folder to create project in, then click Next >. Use the default options, then click Finish to create the project.

        reading_u19_ThirdPartyLibraries_cb2_2023.png

        3. Create a source file: Go to File > New > Empty file and create a new file named "main.cpp".

        reading_u19_ThirdPartyLibraries_cb3_2023.png

        In your main.cpp, it can be useful to add some starter code to make sure your project will build properly with the library linked within. Here is a very stripped-down SFML program:

        #include <SFML/Graphics.hpp>
        
        int main()
        {
          sf::RenderWindow window(sf::VideoMode(200, 200), "Window");
          return 0;
        }
        

        4. Go to the project options: Right-click on your project in the Projects view and select Build options….

        reading_u19_ThirdPartyLibraries_cb4_2023.png

        On the left-hand side, make sure to select the Release configuration, not the Debug.

        5. Link to the include directory: Under the Search directories tab, select the Compiler sub-tab. From here, click the Add button and paste in the path to your SFML directory's include folder, such as:

        C:\LIBRARIES\SFML-2.6.1\include

        reading_u19_ThirdPartyLibraries_cb6_2023.png

        6. Link to the lib directory: Under the Search directories tab, select the Linker sub-tab. From here, click the Add button and paste in the path to your SFML directory's lib folder, such as:

        C:\LIBRARIES\SFML-2.6.1\lib

        reading_u19_ThirdPartyLibraries_cb7_2023.png

        7. Set up linked libraries: Under the Linker settings tab…

        For WINDOWS, under the Link libraries: section, click the Add button. You'll do this three times, adding the three libraries: sfml-graphics, sfml-window, and sfml-system.

        reading_u19_ThirdPartyLibraries_cb8_2023.png

        For LINUX, under the Other linker options: section add these flags: -lsfml-graphics, -lsfml-window, -lsfml-system):

        reading_u19_ThirdPartyLibraries_cb8linux_2023.png

        Click OK once finished.

        8. Build the program: In the dropdown near the build buttons, make sure to change it from building the "Debug" version to building the "Release" version.

        reading_u19_ThirdPartyLibraries_cb9_2023.png

        Now when you build there shouldn't be any errors:

        reading_u19_ThirdPartyLibraries_cb10_2023.png

        9. (WINDOWS) Adding the .dll files to the project:

        If we try to run the program right now, it will complain about missing dll files:

        reading_u19_ThirdPartyLibraries_cb11_2023.png

        We need to copy the dll files from the SFML library to our project now.

        In your file explorer, navigate one window to your SFML directory and one to your project directory.

        In SFML's bin directory, highlight the dll files and copy them: sfml-graphics-2.dll, sfml-system-2.dll, and sfml-window-2.dll.

        reading_u19_ThirdPartyLibraries_cb12_2023.png

        Paste these files into your project directory:

        reading_u19_ThirdPartyLibraries_cb13_2023.png

        Your program should now start, open, and close without any error messages. Currently the program doesn't really do anything because there's no game loop within. Reference the SFML setup page (https://www.sfml-dev.org/tutorials/2.6/start-cb.php) for some test code you can use to verify that everything works properly.


        If you use the example code from the SFML webpage guide, after you build and run you should see a green circle rendered to our window:

        reading_u19_ThirdPartyLibraries_running_2023.png

πŸ§‘β€πŸ”¬ Lab - Third party libraries (U19.LAB)

pixel-goals.png Goals:

  • See how to link a third party library.

pixel-turnin.png Turn-in:

  • 1. You'll commit your code to your repository, create a merge request, and submit the merge request URL in Canvas. (Instructions in document.)

pixel-fight.png This assignment is optional!

  • I can't always help out with every person's different computer configurations, so while I can often help with setup in Code::Blocks and Visual Studio, sometimes I can't get it working on different machines. Because of this, this assignment is optional. Try what you can and turn in what you get, you'll get credit either way.

  • Setup: Starter code and new branch

    Start this assignment by checking out main, pulling latest to get the files, and creating a new branch…

    git checkout main
    git pull
    git checkout -b u19
    

    You'll be creating a new project in your IDE of choice and following the setup guide to create a project.


  • Setting up the library

    Windows users:

    First, go to the SFML downloads page here: https://www.sfml-dev.org/download.php. Then click on the "SFML", whatever the latest version is. You will need to download a version based on whether you're using Visual Studio or Code::Blocks.

    Visual Studio:

    Download the latest version even if the year doesn't match. At time of writing, "Visual C++ 15 (2017) - xx-bit" is the version you should download. You can download the 32-bit or 64-bit version.

    Code::Blocks:

    1. Navigate to where you have Codeblocks installed on your computer, such as "C:\Program Files\CodeBlocks\".
    2. Then, go to the "MinGW" folder, then the "bin" folder.
    3. Look for a "libgcc" dll file. We will know which SFML version to download based on this file.

    lab_u19_ThirdPartyLibraries_libgcc.png

    libgcc file Which SFML version to download
    libgcc_s_seh-1.dll GCC 7.3.0 MinGW (SEH) - 64-bit
    libgcc_s_sjlj-1.dll GCC 5.1.0 TDM (SJLJ) - Code::Blocks - 32-bit
    libgcc_s_dw2-1.dll GCC 7.3.0 MinGW (DW2) - 32-bit

    Once the library is downloaded, extract to an easy directory to find on your computer. For example, I usually have a "C:\Libraries\" folder on my computer, and so I'd extract SFML here - "C:\Libraries\SFML-2.5.1\".

    Linux users:

    Go to your package manager and search for "libsfml". Install the libsfml-dev package.

    The files will be installed to "/usr/include/SFML/" and "/usr/lib/x86_64-linux-gnu/" directories (at least that's where they're at on my computer.)

    Mac users:

    You will have to try to follow the Xcode guide on the SFML page (https://www.sfml-dev.org/tutorials/2.6/); I do not have a Mac to try to be able to setup SFML and write documentation with.

    All users:

    Within the SFML folder there are two important directories we care about: The lib folder and the include folder. You might keep these folders open or keep a notepad document open and paste your paths into that file for reference while we're setting up our project.


  • Part 1: Getting a test program running

    Create a new project in Visual Studio or Code::Blocks. Right now we just need one file - main.cpp. The following code is the test program from the SFML setup documentation, you can copy-paste it into the main.cpp file:

    #include <SFML/Graphics.hpp>
    
    int main()
    {
      sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
      sf::CircleShape shape(100.f);
      shape.setFillColor(sf::Color::Green);
    
      while (window.isOpen())
        {
          sf::Event event;
          while (window.pollEvent(evet))
            {
              if (event.type == sf::Event::Closed)
                window.close();
            }
    
          window.clear();
          window.draw(shape);
          window.display();
        }
    
      return 0;
    }
    

    It won't build at the moment, we still need to configure the project to use SFML.

    For instructions on how to configure SFML with your IDE, look at the reference page: "Setting up and using third party libraries"

    You can also follow along with archived videos: https://www.youtube.com/playlist?list=PLCHjtgI2IPe5j5rFDe4liWJiab63r44qk


  • Part 2: Implementing a basic game

    Make sure to download the GameAssets folder: https://gitlab.com/moosadee/courses/-/tree/main/202401_Spring/Course3_ObjectOrientedProgramming/labs/starter_code/u19_ThirdPartyLibraries/GameAssets?ref_type=heads

    Download the assets in that directory, bunny.png, diamond.png, grass.png, and PressStart2P.ttf. Copy these files into your SFML project's directory (where your .vcxproj is for Visual Studio, or your .cbp is for Code::Blocks).

    We're going to keep everything in main.cpp for now so we can just focus on some basic SFML.


    • Additional #includes

      At the top of the program, make sure you're including the following:

      • cmath
      • map
      • string

    • Handy functions

      Add the following function to your program. You can copy/paste this above the int main() part in main.cpp. As long as they show up above main() you'll be able to use these functions within main().

      sf::Vector2f GetRandomPosition( int screenWidth, int screenHeight )
      {
        sf::Vector2f pos;
        pos.x = rand() % screenWidth - 64;
        pos.y = rand() % screenHeight - 64;
        return pos;
      }
      float GetDistance( sf::Vector2f obj1, sf::Vector2f obj2 )
      {
        return sqrt( pow( obj1.x - obj2.x, 2 ) + pow( obj1.y - obj2.y, 2 ) );
      }
      

    • Setting up the game

      1. Create named constants for the window resolution: First, we’re going to define the screen width and height as named constants so we can refer to them in the program without having the numbers hard-coded all over the place (this is handy if you want to change the resolution later.)

      const int SCREEN_WIDTH = 800;
      const int SCREEN_HEIGHT = 600;
      

      2. Set up the game window: Then we need to create a RenderWindow and set it up. We will set the resolution via the VideoMode class and give the title bar the text β€œExample game”, as well as set our framerate.

      sf::RenderWindow window( sf::VideoMode( SCREEN_WIDTH, SCREEN_HEIGHT ), "Example game" );
      window.setFramerateLimit( 60 );
      

      3. Load in images: We will create a map of textures so we can grab them by a string key, instead of having to deal with index numbers. We’re going to load each of the images.

      map<string,sf::Texture> textures;
      textures["bunny"].loadFromFile("bunny.png");
      textures["diamond"].loadFromFile("diamond.png");
      textures["grass"].loadFromFile("grass.png");
      

      4. Load in a font: We’ll also create a Font variable and load in the font.

      sf::Font font;
      font.loadFromFile("PressStart2P.ttf");
      

      5. Create a player sprite: Let’s create one Sprite for the player, set its texture and position, and create a couple other helper variables. (In a larger program, these should be in a class.)

      sf::Sprite player;
      player.setTexture( textures["bunny"] );
      player.setPosition( SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 );
      float playerSpeed = 5;
      int playerScore = 0;
      

      6. Create a diamond sprite: Let’s also create a Sprite for the diamond, the collectable item.

      sf::Sprite item;
      item.setTexture( textures["diamond"] );
      item.setPosition( GetRandomPosition( SCREEN_WIDTH, SCREEN_HEIGHT ) );
      

      7. Add score text: And a Text object to be able to render the player’s score to the screen.

      sf::Text scoreText;
      scoreText.setFont( font );
      scoreText.setCharacterSize( 30 );
      scoreText.setFillColor( sf::Color::White );
      scoreText.setString( "Score: " + to_string( playerScore ) );
      

      8. Add background tiles: Add a vector of sf::Sprites named groundTiles to make the background:

      vector<sf::Sprite> groundTiles;
      sf::Sprite ground;
      ground.setTexture( textures["grass"] );
      for ( int y = 0; y < SCREEN_HEIGHT; y += 32 )
        {
          for ( int x = 0; x < SCREEN_WIDTH; x += 32 )
            {
              ground.setPosition( x, y );
              groundTiles.push_back( ground );
            }
        }
      

      These are all the setup parts, and we will add in the grass background later. Next, we need to create the game loop – the game will continue running until it gets a β€œclose window” event.


    • Creating the game loop

      Create the game loop. This will go after all the setup code.

      while ( window.isOpen() )
        {
        }
      

      And we'll add the following items within…

      9. Listen for window close event: This code checks for any events happening, and if the event code is a β€œClosed” event, then close the window. This will cause the game to exit.

      sf::Event event;
      while (window.pollEvent(event))
        {
          if (event.type == sf::Event::Closed)
            window.close();
        }
      

      10. Listen for keyboard input: Next we’re going to listen for any keyboard presses. If an arrow key is pressed we are going to move the bunny by changing its x, y coordinates.

      sf::Vector2f playerPos = player.getPosition();
      if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Left ) )
        {
          playerPos.x -= playerSpeed;
        }
       else if
         ( sf::Keyboard::isKeyPressed( sf::Keyboard::Right ) )
         {
           playerPos.x += playerSpeed;
         }
      if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Up ) )
        {
          playerPos.y -= playerSpeed;
        }
       else if
         ( sf::Keyboard::isKeyPressed( sf::Keyboard::Down ) )
         {
           playerPos.y += playerSpeed;
         }
      player.setPosition( playerPos );
      

      11. Check for collision between player and diamond: Now that we’ve handled events and inputs, the only game logic we need at the moment is to check if the Diamond and the Player are close to each other. We can use the GetDistance function here. You can adjust the value of 32, it is the amount of pixels of β€œdistance”, and if the two objects are within the threshold, we will count it as a collision

      if ( GetDistance( player.getPosition(), item.getPosition() ) < 32 )
        {
          // Count this as a collect
          item.setPosition( GetRandomPosition( SCREEN_WIDTH, SCREEN_HEIGHT ) );
          playerScore++;
          scoreText.setString( "Score: " + to_string( playerScore ) );
        }
      

      lab_u19_ThirdPartyLibraries_collisionregion.png

      12. Draw items to the screen: Finally within the game loop we are going to clear the screen and draw all our items.

      window.clear();
      // Draw all the backgrounds
      for ( auto& tile : groundTiles )
        {
          window.draw( tile );
        }
      // Draw item
      window.draw( item );
      // Draw player
      window.draw( player );
      // Draw text
      window.draw( scoreText );
      window.display();
      

      13. Testing the program:

      lab_u19_ThirdPartyLibraries_example1.png

      When we run the program, the core bunny game should work. You can move the bunny around with the arrow keys and each time you collect a diamond the score should go up.

      There are lots of ways you could customize or add onto this game, and feel free to experiment, but this is just an example of what you could do with additional libraries linked up in your IDE.


      \newpage

WEEK 11 - MAR 25 - SEMESTER PROJECT PART 1

Project v1

Important links


About

shopazon-program.png

For the semester project I've built out a codebase based on Amazon. There are objects created for different parts of the underlying shopping platform system. You will be selecting one area to work in.

We will also be making use of more of the tools available on the GitLab webpage, such as Issues (tickets) for pulling tasks, merge request reviews, and an automated build system.

We will also have documentation for the codebase as a whole, and you will be familiarizing yourself with this codebase as you work with it.

This is meant to imitate a real-world development scenario and teach you more about software development.

  • Project v1: Due March 31st EOD
  • Project v2: Due April 14th EOD
  • Project v3: Due April 28th EOD

Overview

  • You will CLONE the shared group repository to your computer to work on the item.
    • Use git clone and then the HTTPS link from the GitLab repository page.
    • Clone this new repository somewhere on your hard drive - NOT IN YOUR OTHER CS235 REPOSITORY FOLDER!
  • You will select an Issue to work on. (GitLab page \(\to\) Issues page \(\to\) Select an issue and assign it to yourself).
    • The issue will include the acceptance criteria that you will use to help implement the required tickets.
    • v1, v2, and v3 of the project will have different sets of tickets to work on.
  • You will submit a merge request once you've implemented the feature.
  • You will review someone else's merge request changes and give productive feedback.
  • After each "version" (v1, v2, v3), the instructor will review your changes, give feedback, and if the changes are satisfactory, they will merge your work into the main branch and mark the ticket as complete.

Where do I find information?

  • Information about using GitLab is located on the Repository front page README (Follow Repository link and scroll down).
    • The README here covers how to pull an issue, create a merge request, and review someone else's code.
  • Information about project requirements is located in the Issue ticket description (view on GitLab).
    • The ticket description explains what is required for the feature you're implementing.
  • Information about the codebase is located on the documentation webpage.
    • The documentation has information on all of the structures in the program and their relationships.
    • The documentation also covers details about how the program itself works.

What do I turn in on Canvas?

  • You will turn in the URL to your merge request. Also include the URL to the request that YOU REIVEWED in the comment field of the submission.
  • You don't have to have YOUR merge request reviewed to submit, but you should have reivewed someone else's work.
  • Feedback will be given and a follow up ticket for v2 will be given after grading.

WEEK 12 - APR 1 - R.W. COURSE BREAK

(This class only) - A break week where I can grade the turned in projects so far.

I WILL STILL BE IN CLASS AND YOU CAN STILL COME TO CLASS TO ASK QUESTIONS AND WORK ON THINGS.

WEEK 13 - APR 8 - SEMESTER PROJECT PART 2

Project v2

For v2 there are tickets to "Link" two modals together. Choose a ticket to work on (in addition to any UI/Cleanup/Bug tickets I've assigned to you). Refer to the documentation for additional context on the codebase.

Make sure to review at least one other Merge Request.

Let the instructor know if you have any questions.


If you used one merge request for your main feature and the cleanup/UI/bug tickets, make sure to reference the ticket #s in your merge request title or description!

WEEK 14 - APR 15 - R.W. COURSE BREAK

(This class only) - A break week where I can grade the turned in projects so far.

I WILL STILL BE IN CLASS AND YOU CAN STILL COME TO CLASS TO ASK QUESTIONS AND WORK ON THINGS.

WEEK 15 - APR 22 - SEMESTER PROJECT PART 3

Project v3

  • Late assignments

    If you haven't done v1 or v2 of the project, please start with those first. You can work on both and submit them in one branch/merge request as well.


  • About

    For this part of the project you will test somebody else's work and create a ticket for any fixes and cleanup that need to be done, especially from the user interface side of things. Follow along with the "expected behavior" part of this document for an idea of how things should be designed.

    Who to test?

    There will be tickets set up for "test XYZ feature". Assign one to yourself. Do not test something that you worked on, make sure you're testing someone else's features.


  • Your assignment
  • Testing checklist

    This is written to be generic, but you can follow these steps for any of the models.

    • Style guide

      Each section should have a consistent style:

      • Submenu header should be rendered with the void TextHeader( std::string text ); function, giving this appearance:

        - REVIEW - UPDATE --------------------------------------------------------------
        
      • Integer input within a range should be handled with the int Program::GetValidChoice( int min, int max ) function.
      • Prompts before input should clearly describe what input is expected from the user.
      • Submenu numbered menus should be handled with the void DisplaySubmenu( std::vector<std::string> options, bool zeroIsGoBack ) function for a consistent appearance.
        • This code:

          int options = DisplaySubmenu( { "wallet_id", "title", "cc_last4" }, true );
          int choice = GetValidChoice( 0, options );
          
        • Shows this output:

          1. wallet_id
          2. title
          3. cc_last4
          --------------------
          0. Back to main menu
          
      • Option 0 should always be "go back" or "quit". (Except when displaying a table of models and selecting one of those model IDs.)
      • Update menu should stay editing a specific model until the user decides to cancel/go back.
      • Program dialog should not be misleading; it shouldn't say "model updated successfully" if nothing was updated OR if the update wasn't successful.
      • Table information should adhere to column sizes; use string Helper::Truncate( string original, int length ) as needed.

    • Add MODEL
      • During add the program should step the user through the process of setting up information related to the Model.
      • If there are any linking IDs (e.g., Store -> Account owner id):
        • Make sure that the table of data is displayed showing the linked item's entries (e.g., Account in this example).
        • Make sure that the given linking ID points to a valid entry. (e.g., account_manager.IsValidId( entered_id ))
      • At end of Add functionality, a confirmation message should be displayed that the item was added successfully.
        • If there were any errors that meant the model wasn't added, display an error message, NOT a successful message.
      - WISHLIST - ADD ---------------------------------------------------------------
      
      Enter title of wishlist: Gift ideas
      
      SELECT OWNER ACCOUNT
      
      id                                      email
      --------------------------------------------------------------------------------
      100                                     funny-monitor-lizard-2307@garNET.net
      101                                     heartfelt-magpie-876@telekiNETic.info
      102                                     mindless-butterfly-1264@plaNET.edu
      
      Enter ID of owner: 300
      That's not a valid ID! Try again: 100
      
      Wishlist added.
      
      PRESS ENTER TO CONTINUE...
      

      Notes

      • User should not be entering the unique ID for the Model itself; this should be assigned in the program.

    • Update MODEL
      • List of all models of this type should be displayed, then the user asked for an ID to edit.
        • There should be an error check to make sure the ID provided is valid.
      • All fields (except the Model's Unique ID) should be editable from this menu.
        • User should not be able to edit a model's unique ID for itself.
        • Submenu for editing the chosen model should continue looping until the user decides to back out back to the main menu.
      - REVIEW - UPDATE --------------------------------------------------------------
      
      id              product_id      account_id      review_text     star_rating
      --------------------------------------------------------------------------------
      100             100             100             Good product... 4
      101             101             100             This set is ... 4
      102             102             101             I bought the... 5
      
      Enter a review ID.
      >> 100
      
      - REVIEW - UPDATE - id 100 -----------------------------------------------------
      Editing review:
      id:          100
      product_id:  100
      account_id:  100
      review_text: Good product for the price. I love that it came with a TON of tabs in a variety of different colors.
      star_rating: 5
      
      Editable fields:
      1. product_id
      2. account_id
      3. review_text
      4. star_rating
      --------------
      0. Back to main menu
      
      Your selection?
      >> 2
      
      Available accounts:
      id                                      email
      --------------------------------------------------------------------------------
      100                                     funny-monitor-lizard-2307@garNET.net
      101                                     heartfelt-magpie-876@telekiNETic.info
      102                                     mindless-butterfly-1264@plaNET.edu
      
      Assign review's account_id to which?
      >> 200
      That's not a valid ID! Try again: 102
      
      Updated account_id.
      
      
      - REVIEW - UPDATE - id 100 -----------------------------------------------------
      Editing review:
      id:          100
      product_id:  100
      account_id:  102
      review_text: Good product for the price. I love that it came with a TON of tabs in a variety of different colors.
      star_rating: 5
      
      Editable fields:
      1. product_id
      2. account_id
      3. review_text
      4. star_rating
      --------------
      0. Back to main menu
      
      Your selection?
      >> 0
      
      Returning to main menu.
      

    • Remove MODEL
      • Stay on remove menu until user decides to return to main menu.
      • Give confirmation when a model is successfully removed.
      - PRODUCT - REMOVE -------------------------------------------------------------
      
      id           inventory_id store_id     title        description  price
      --------------------------------------------------------------------------------
      100          100          100          Sticky No... 100 PackS... 6.99
      101          101          100          10 Pack P... Bulk Yell... 8.99
      
      Enter a Product's id to remove, or 0 to return to main menu.
      >> 200
      That's not a valid ID! Try again: 101
      
      Removed product.
      
      - PRODUCT - REMOVE -------------------------------------------------------------
      
      id           inventory_id store_id     title        description  price
      --------------------------------------------------------------------------------
      100          100          100          Sticky No... 100 PackS... 6.99
      
      Enter a Product's id to remove, or 0 to return to main menu.
      >> 0
      
      Returning to main menu.
      

WEEK 16 - APR 29 - LAST WEEK OF CLASS / R.W. COURSE BREAK

Catch-up week, last chance to get your assignments in.

I WILL STILL BE IN CLASS AND YOU CAN STILL COME TO CLASS TO ASK QUESTIONS AND WORK ON THINGS.

I USE FINALS WEEK TO GRADE ALL REMAINING WORK AND I DO NOT ACCEPT LATE WORK DURING FINALS WEEK.

WEEK 17 - MAY 7 - MAY 13 - FINALS WEEK

No final exam for this course; make sure to do the exams that are posted on Canvas.

Professors submit grades by Tuesday, May 14th.

Grades available online to students by noon, Thursday, May 16th.

\newpage

Additional

l1-about.png

About

About this book

book-cute-center.png

This "course book" contains the concept notes, assignment documentation, reference material, and other resources, all of which make up the author's Core C++ course. The goal is to be a one-stop location for all the resources you need for this course, and to give you something to keep for reference later on - in other classes, for review before a job interview, or just as a reference manual. I encourage you to write in this book, keep your notes in it, highlight it, and use it as your guide as you work through the course content.

About this course

about-course-center.png

My course design ideals

  • I am interested in training you how to be a good software developer, which includes how the language works, designing software and good interfaces, diagnosing and fixing issues and bugs, and so on. I am not interested in making sure you're a cookie-cutter "best-student", which usually just translates to "a traditional student with the opportunity to give school 150% of their attention". I've been a non-traditional student, I've had to work full time my entire adult life, I understand that many of my students are also adults with jobs, families, and other responsibilities, and I am not interested in penalizing "non-traditional" students for not being able to make perfect attendance or get everything in by the due date 100% of the time.
  • I try to build my courses in a way like a video game - start by tutorializing and easy challenges, and as you get more experience you are faced with harder challenges, but everything's designed to help you build the skills you need to tackle everything.
  • I think that education should be free and available to everybody. (But also I need to pay bills so I am fortunate that I get to work as a teacher, which I enjoy doing.)
  • All the (good) course content I create is available online for free for anyone to access. (GitLab, my webpage, YouTube). Anything that I think you need to know to be successful will be available in some form online and outside of class; I'm not going to penalize you for not making it to class.
  • I want to help you build up your problem solving skills so I may not always answer your assignment questions directly (more in a guided, "here's a hint" way), but I am always here to answer questions and help guide you to where you need to go.
  • I miss teaching from home where I could wear sweat pants all day. We should all be able to do what we do while being comfy in sweat pants or whatever.

About the author

rachel1.png

I've been in the KC Metro area since I was around 11 years old. I started programming because I was in love with video games, and I spent my free time in my teen years (badly) programming video games. :)

\vspace{0.2cm}

Education: Homeschooled, GED, Associates in Computer Science from MCCKC, Bachelors in Computer Science from UMKC, dropped out of gradschool multiple times (UMKC, MS&T, KU) cuz money/time.

\vspace{0.2cm}

Development Work: Mostly C# (web development) and C++ (software development) professionally, also with PHP, HTML, CSS, JavaScript. Independent mobile app development for Android, independent game development with Lua or C++.

\vspace{0.2cm}

Teaching Work: Tutoring math @ MCCKC (2004), creating programming tutorials on YouTube (since 2008), adjunct teacher @ UMKC (2013, various other years), adjunct teacher @ JCCC (2016-2018), full time teacher @ JCCC (2020 - current).

\vspace{0.2cm}

Free time things: Playing video games, making video games, studying human languages, gardening, arts and crafts, journaling and organization, animating, video editing, playing piano, writing music, TTRPGs, drinking coffee, making zines, volunteering.

\vspace{0.2cm}

Other names: In the work system I'm R.W., but my full name is Rachel Wil Sha Singh. Around many friends I go by "Moose". I am nonbinary and go by various names in various contexts. I relate more to computers than I do to genders.

\vspace{0.2cm}

Links 'n' stuff:

\newpage

Reference

Basic Computer Skills

  • Basic computer skills

    How to take a screenshot

    computer_skills_printscreen.jpg

    In Windows
    Press PRINTSCREEN on your keyboard, then go to an image editing application (Paint, if you have nothing else) and use EDIT > PASTE to paste the image in.
    In Mac
    CTRL+Z, COMMAND+SHIFT+3 to take a screenshot in Mac
  • Navigating the filesystem

    Identifying file extensions

    You should be aware of the extensions of our files. You will need to be able to identify source code files, such as .cpp and .h files for C++, or .py files for Python. Additionally, most executable (runnable) files end with the .exe extension in Windows.

    Turning on viewing file extensions in Windows

    computer_skills_extensions.png

    By default, Windows hides file extensions from you. However, it is important to know what kind of files you're working with in a programming class. To view file extensions, search for "File Explorer Options". Select the "View" tab, and UNCHECK the option "Hide extensions for known file types". Hit OK when done.

    Project working directory

    Visual Studio: computer_skills_workingdir_visualstudio.png

    Code::Blocks: computer_skills_workingdir_codeblocks.png

    It is important to keep in mind that the default working directory of our programs is the location where the project file is. For Visual Studio, that's a .vcxproj file. For Code::Blocks, that's a .cbp file.

    Navigating quickly

    Selecting multiple files with CTRL or SHIFT

  • Editing text

    Cut (CTRL+X), copy (CTRL+C), paste (CTRL+V)

    Double-click to highlight word

    CTRL+arrow keys to scroll a word at a time

    CTRL+HOME/CTRL+END to go to start/end of a document

    SHIFT to select regions of text

  • IDE tips and tricks

    Splitting code views

    Go to declaration

    Go to definition

    Find all references

    Refactor - Rename


Code and UI Style Guide

  • Repository management
    Your merge request should only contain files related to the current feature or project you're working on
    It shouldn't contain code from multiple assignments.

  • User Interface design
    • Prompts before inputs

      Before using an input statement (e.g., cin), use an output statement (e.g., cout) to display a message to the user. Make sure you're clear about what kind of data you're expecting them to enter.

      No:

      _
      

      If you use an input statement it doesn't show anything on the screen, so it looks like your program has crashed.

      Yes:

      Please enter pet name: _
      

      Use an output statement to display a message before getting the user's input.


  • C++ Style Guide
    • Naming conventions
      Variables

      lowerCamelCase - Variable names should begin with a lower-case letter and each subsequent letter use an Upper Case, in a camel-case format.

      Additionally:

      • Variable names should be descriptive.
      • Avoid using single-letter variable names except with for loops.
      Member variables
      m_lowerCamelCase - Variables that belong to a class should be prefixed with m_ to signify "member".
      Functions
      CamelCase - Function names should begin with an upper-case letter and be in CamelCase form. In some cases, using an underscore might be okay to group like-functions together, if it makes things more clear.
      Classes
      CamelCase - Class names should begin with an upper-case letter and be in CamelCase form.

    • Best practices
      • Basics
        • You must have int main, not void main.
        • Don't put an entire program in main() - Split out the program into separate menu functions; ideally use a Program class that contains functions within it.
      • Naming conventions
        • Variable, function, and class names should be descriptive.
      • Arrays
        • Do not use a variable for an array's size; some compilers might support this but not all do. Traditional C arrays and STL arrays must have hard-coded integer literals or named constant integers as its size.
      • Functions
        • Functions should perform the minimal amount of operations - a function should have a specific purpose.
      • Structs and Classes
        • Structs should be used for structures that only contain a few amount of variables and no functions. Classes should be used for more sophisticated structures that contain functions and variables.
        • Prefer making member variables private with member functions as an interface for those items.
      • try/catch
        • The try{} statement should wrap the most minimal amount of code (NOT THE ENTIRE FUNCTION BODY!) - generally, try should wrap the one function that could throw an exception.
      • NEVER USE GLOBAL VARIABLES.
      • NEVER USE goto STATEMENTS.

  • Cross platform compatibility
    Treat everything (including files) as case-sensitive
    When using an #include statement for a file in your project, make sure your casing matches the file itself. E.g., if the file is Product.h, use #include "Product.h", not #include "product.h".
    Use #ifndef fileguards, not #pragma once
    Use the #ifndef _LABEL, #define _LABEL, #endif style of file guards as they are supported by more compilers.
    • Fileguards ONLY go in .h files.
    system("cls"); only works on Windows

    Prefer creating a cross-platform function like this instead:

    void ClearScreen()
    {
    #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
      system( "cls" );
    #else
      system( "clear" );
    #endif
    }
    
    system("pause"); only works on Windows.

    Prefer creating a cross-platform function like this instead:

    // It's klugey but it's the simplest.
    void PressEnterToContinue()
    {
      cout << endl << "PRESS ENTER TO CONTINUE" << endl;
      string temp;
      getline( cin, temp );
      getline( cin, temp );
    }
    
    File guards
    Always use ifndef instead of pragma once for your file guards.

    Yes:

    #ifndef _MYFILE_H
    #define _MYFILE_H
    
    // My code
    
    #endif
    

    No:

    #pragma once
    
    // My code
    

  • File organization
    Function/class declarations
    Function and class declarations should go in a header (.h) file.
    Function definitions
    Function definitions should go in a source (.cpp) file, EXCEPT TEMPLATED FUNCTIONS (those all go in .h).
    Each class/struct should have its own set of .h and .cpp (as needed) files
    • Don't put multiple class declarations in one file.
    Function definitions must go in .cpp file
    Don't define class functions within the class or in the .h file.
    • Except for templated classes - Everything goes in the .h file for templated classes.
    Don't define structs/classes inside other classes
    Each struct/class should have its own .h file/set of .h and .cpp files.]
    Never include .cpp files!

    Includes should only include .h or .hpp header files.

    If a project has many files, separate them into folders
    Logically separate related files into subfolders, such as Exceptions, DataStructures, Menus, etc.

  • Clean code
    Erase unused codeblocks
    If you want to make sure to be able to reference old code later, back it up to GitLab, then delete the comment. Don't leave commented out code blocks in turn-in assignments.
    Be consistent with naming conventions
    Choose either UpperCaseFunctionNames or lowerCaseFunctionNames but don't interchange them. Same with variable and class naming. Be consistent.
    Whitespace
    Adequate whitespace should be used to differentiate logical sections of code. At the same time, don't use too much whitespace. The goal is to have readable code.
    If ( true ) return true; else return false;
    If you're writing a condition to check if something is true in order to return true, just return the result of the condition instead.

    No:

    if ( a == b )
      return true;
    else
      return false;
    

    Yes:

    return ( a == b );
    

    • Curly braces
      Follow the existing code base
      • If you're writing code from scratch you can follow whatever style you'd like as long as it is consistent.
      • When working with existing code-bases, use the same style as the existing code.
      • See https://en.wikipedia.org/wiki/Indentation_style for indentation styles

      Preferred style:

      if ( a == b )
      {
        DoThing();
      }
      

      GNU styling:

      if ( a == b )
        {
          DoThing();
        }
      

      K&R Styling ("JavaScript" style):

      if ( a == b ) {
        DoThing();
      }
      
      One-line contents
      Always surround your if statement and while loop contents within curly braces { } even though it's not required for one-line internals.

      No:

      if ( CONDITION )
        DoThingA();
      else if ( CONDITION2 )
        DoThingB();
      else
        DoThingC();
      

      Yes:

      if ( CONDITION )
      {
        DoThingA();
      }
      else if ( CONDITION2 )
      {
        DoThingB();
      }
      else
      {
        DoThingC();
      }
      

      Or:

      if      ( CONDITION )  { DoThingA(); }
      else if ( CONDITION2 ) { DoThingB(); }
      else                   { DoThingC(); }
      

C++ Quick Reference

  • Starter C++ programs

    Without arguments:

    int main()
    {
      return 0;
    }
    

    With arguments:

    int main( int argCount, char* args[] )
    {
      return 0;
    }
    
  • Variables and named constants

    Declare a variable

    DATATYPE VARIABLENAME;

    int myNumber;
    string myString;
    float myFloat;
    

    Declare a variable and initialize it

    DATATYPE VARIABLENAME = VALUE;

    int myNumber = 10;
    string myString = "Hello!";
    float myFloat = 9.99;
    char myChar = 'a';
    bool myBool = true;
    

    Declare a named constant

    const DATATYPE NAME = VALUE;

    const int NUMBER = 10;
    const string STATE = "Missouri";
    

    Assign a value to a variable that has already been declared

    VARIABLENAME = VALUE;

    myNumber = 100;
    myString = "Goodbye";
    

    Copy a value from one variable to another

    UPDATEDVAR = COPYME;

    vipStudent = student3;
    
  • Console input and output

    Output text to the console

    cout << ITEM;

    You can mix variables and string literals, as long as the stream operator << is between each item.

    cout << "Text";
    cout << myVariable;
    cout << "Label: " << myVariable << endl;
    

    Get input from the keyboard and store it in a variable

    cin >> VARIABLENAME;

    cin >> myInteger;
    cin >> myString;
    

    Get a full line of text (with spaces) and store in a variable (string only)

    getline( cin, VARIABLENAME );

    getline( cin, myString );
    

    Note that if you have a cin >> statement immediately before the getline statement, your input will get skipped. In this case you need to add a cin.ignore() statement:

    cin >> myNumber;
    cin.ignore();
    getline( cin, myString );
    
  • Branching

    If statement

    If the condition evaluates to true, then execute the code within the if statement. Otherwise, skip that code completely.

    if ( a == 1 )
    {
    }
    

    If/else statement

    If the condition from if statement results to false, then the code in the else case is executed instead. No condition is written with the else case.

    if ( a == 1 )
    {
    }
    else
    {
    }
    

    If/else if statement

    Checks each condition in order. If one is true, then subsequent else if statements are ignored.

    if ( a == 1 )
    {
    }
    else if ( a == 2 )
    {
    }
    

    If/else if/else statement

    Similar to above, but if all if and else if statements are false, then else is executed.

    if ( a == 1 )
    {
    }
    else if ( a == 2 )
    {
    }
    else
    {
    }
    
  • Switch

    switch( VARIABLE ) { case VALUE: break; }

    switch( myNumber )
    {
      case 1:
        cout << "It's one!" << endl;
        break;
    
      case 2:
        cout << "It's two!" << endl;
        break;
    
      default:
        cout << "I don't know what it is!" << endl;
    }
    
  • Loops

    Looping with While Loops

    Runs 0 or more times, depending on whether the CONDITION is true.

    while ( CONDITION ) { }

    while ( a < b )
    {
      cout << a << endl;
      a++;
    }
    

    Looping with Do…While Loops

    Runs at least once and then continues looping if the condition is true.

    do { } while ( CONDITION );

    do {
      cout << "Enter a number: ";
      cin >> input;
     } while ( input < 0 || input > max );
    

    Looping with For Loops

    Iterate some amount of times through the loop based on your counter variable and range.

    for ( INIT; CONDITION; UPDATE ) { }

    for ( int i = 0; i < 10; i++ )
    {
      cout << i << endl;
    }
    

    Looping with Range-Based For Loops

    Iterates over all elements in a collection. Only moves forward, doesn't give access to index

    for ( INIT : RANGE ) { }

    vector<int> myVec = { 1, 2, 3, 4 };
    for ( int element : myVec )
    {
      cout << element << endl;
    }
    
  • Arrays and Vectors

    Declare a traditional C-style array

    When declaring a vanilla array, you need to set its size. You can either hard-code the size with a number:

    string arr[10];
    

    Or use a named constant:

    const int TOTAL_STUDENTS;
    string students[TOTAL_STUDENTS];
    

    Declare an STL array object Remember that you will need #include <array> at the top of your file to use the array object.

    Declare an array object, passing in what the data type of it values are, and the size of the array:

    array<float, 3> bankBalances;
    

    Use the array documentation (https://cplusplus.com/reference/array/array/) for info on its functions.

    Declare an STL vector object

    vector<string> studentList;
    

    Use the vector documentation (https://cplusplus.com/reference/vector/) for info on its functions.

    Initialize array/vector with an initializer list

    string cats1[] = { "Kabe", "Luna", "Pixel", "Korra" };
    array<string,4> cats2 = { "Kabe", "Luna", "Pixel", "Korra" };
    vector<string> cats3 = { "Kabe", "Luna", "Pixel", "Korra" };
    

    Iterate over an array/vector

    // C-style array:
    for ( int i = 0; i < TOTAL_STUDENTS; i++ )
    {
      cout << "index: " << i << ", value: " << students[i] << endl;
    }
    
    // STL Array and STL Vector:
    for ( size_t i = 0; i < bankBalances.size(); i++ )
    {
      cout << "index: " << i << ", value: " << students[i] << endl;
    }
    

    size_t is another name for an unsigned int, which allows values of 0 and above - no negatives.

  • File I/O

    Make sure to have #include <fstream> in any files using ifstream or ofstream!

    Create an output file and write

    ofstream output;
    output.open( "file.txt" );
    
    // Write to text file
    output << "Hello, world!" << endl;
    

    Create an input file and read

    ifstream input;
    input.open( "file.txt" );
    if ( input.fail() )
      {
        cout << "ERROR: could not load file.txt!" << endl;
      }
    string buffer;
    
    // read a word
    input >> buffer;
    
    // read a line
    getline( input, buffer );
    
  • Functions

    Prevent file duplication

    When creating .h/.hpp files, it is important to include these lines to prevent file duplication, which happens if you #include this file in more than one place.

    #ifndef _FILENAME_H // LABEL SHOULD BE UNIQUE FOR EACH .h FILE!
    #define _FILENAME_H
    
    // Code goes here
    
    #endif
    

    Function declaration

    A function declaration contains the function header and no body. It declares that the function exists and will be defined elseware.

    void DisplayMenu();
    int Sum(int a, int b);
    

    Function definition

    A function definition defines how the function operates. It includes the function header and a function body, which has commands in it.

    void DisplayMenu()
    {
      cout << "1. Deposit" << endl;
      cout << "2. Withdraw" << endl;
      cout << "3. Quit" << endl;
    }
    
    int Sum(int a, int b)
    {
      return a + b;
    }
    

    Calling a function

    Calling a function requires invoking the function's name and passing in any arguments.

    DisplayMenu();
    
    int result = Sum( 1, 2 ); // Passing in literal values
    
    int num1 = 2;
    int num2 = 5;
    int result = Sum( num1, num2 ); // Passing in variable values
    
  • Classes

    Declaring a class

    Remember that classes must end with ; on their closing curly brace!

    class CLASSNAME
    {
        public:
        // Public members
    
        protected:
        // Protected members
    
        private:
        // Private members
    };
    
    class Student
    {
    public:
      // Constructor functions
      Student();
      Student( string name, float gpa );
    
      // Member functions
      void Setup( string name, float gpa );
      void Display();
    
    private:
      // Member variables
      string m_name;
      float m_gpa;
    };
    

    Defining a class member function (method)

    A class' member functions must be declared within the class declaration. Then, you define the member function within a .cpp file.

    RETURNTYPE CLASSNAME::FUNCTIONNAME( PARAMETERS )

    // Function definition
    void Student::Setup( string name, float gpa )
    {
      m_name = name;
      m_gpa = gpa;
    }
    

    Getter and Setter patterns

    It is best practice to make member variables private to protect the integrity of the data. Private members cannot be accessed outside of the function, so instead you'll have to create interfacing functions to access (get) and mutate (set) data.

    Getter function, aka Accessor
    • Doesn't take in any inputs (parameters)
    • Returns the value of the member variable (return type should match the member variable)
    • Getter functions can be good for formatting data before returning it
    // Basic getter function
    float Student::GetGPA()
    {
      return m_gpa;
    }
    
    // Example of formatting before returning data
    string Person::GetFullName()
    {
      return m_lastname + ", " + m_firstname;
    }
    
    Setter function, aka Mutator
    • Takes in an input value for a new value for the member variable - parameter should have the same data type as the member variable
    • Doesn't return any data - void return type
    • Setter functions should include any needed error checking and reject invalid data
    // Basic setter function
    void Student::SetGPA( float newGpa )
    {
      m_gpa = newGpa;
    }
    
    // Example of data validity check
    void Student::SetGPA( float newGpa )
    {
      if ( newGpa < 0 || newGpa > 4 )
      {
        cout << "Error! Invalid GPA!" << endl;
        return; // Return without saving data
      }
      else
      {
        m_gpa = newGpa;
      }
    }
    
  • Pointers

    Declare a pointer

    DATATYPE* PTRNAME = nullptr;

    
    

    Assign an address to a pointer

    PTRNAME = &VARIABLE;

    int * ptrInt = nullptr;
    string * ptrString = nullptr;
    

    Pointer variables store addresses as their value. To get the add