Lesson Pages Summary
If you want to know which lesson covered a given topic, this page may help you find it.
For each lesson of each week, we've reproduced the final “Key Points” page from that lesson, and then provided links back to the original lesson pages for specific topics.
Week 1
Lesson 1 — Welcome, Variables, and Memory Diagrams
Key Points
Class Format
- Each week you'll have lesson material to go through at your own pace.
- In class, you'll work on homework and check in with the professors.
- During each class meeting, fill out a check in form to get credit for the day.
- You may attend class on Zoom if it's best if you don't attend in person (e.g., if you have symptoms of illness).
Homework
- Except for the first one, you'll have a partner for all homework assignments.
- Each partnership will have one 24-hour extension to apply to a homework deadline.
- We want you to support and learn from your classmates: be careful to preserve each other's learning processes.
Proficiency Checks
- Each week there are four proficiency checks, each focused on a single learning objective.
- Take proficiency checks in exam-like conditions.
- There will be opportunities to retry proficiency checks where you did not demonstrate proficiency the first time.
- During finals week there will be a final try at all remaining proficiency checks (with the possibility of partial credit).
Getting the Most Out of CS 70
- We are here to help! Please reach out to the course staff—and to each other—early and often!
CS 70 Memory Diagrams
- We use memory diagrams to help trace the correctness of C++ code.
- In our diagrams, we draw a variable's
- value "inside the box" since it exists at runtime.
- name, type, and location "outside the box" because they are useful for tracing the code, but they are not explicitly represented in memory at runtime.
- Our model abstracts away other details of specific computers (such as registers, the specific size of different numeric types, and the real naming conventions for memory slots) because those details vary from one computer to another, and are not helpful for modeling correctness.
C++ Guiding Principles
- Programming languages are designed by particular people, for particular purposes, in particular contexts.
- We discussed two principles that are important for explaining some of the design choices in C++ and let us reason about how the language behaves.
- The Zero-Overhead Principle: Don't slow programs down “needlessly”.
- Providing a lanuage feature should never slow down a program that doesn't use that feature.
- Generally do things in the quickest possible way (even at the expense of safety or convenience).
- The “Power to the People” Principle: Provide language features that allow expert programmers to do things in unsual ways.
- Sometimes beginners accidentally use featues intended for experts!
- The Zero-Overhead Principle: Don't slow programs down “needlessly”.
- Both of these features mean we should not ignore compiler warnings when it notices we're doing something questionable!
- Welcome!
- Introductions to the instructors
- Course goals and policies
- The CS 70 Animal Friends
- Variables in Python & C++
- Similarities and differences between variables in Python and C++
- A First Look at C++
- First C++ program example
- Running code in Online GDB
- Memory Diagrams in CS 70
- CS 70 memory diagrams for modeling C++ programs
- First draft of rules for memory diagrams
- Modeling Memory
- Comparison of CS 5 and CS 70 memory models
- Von Neumann architecture
Lesson 2 — Object Lifetime, Numeric Types, Promotion and Conversion
Key Points
Object Lifetime
- Every variable goes through the same 5 phases of object lifetime.
- The space for a local variable is allocated at the opening curly brace of the function where it's declared.
- The value of a variable is initialized on the line with its declaration.
- Once a variable's value is initialized, it is in use. For primitive values, that primarily means that its name is in scope, which means the name can be used in other expressions.
- The value of a variable is destroyed at the closing curly brace of the declaring block.
- Once a variable's value is destroyed, it is no longer in use. For primitive values, that primarily means that its name is out of scope, which means that the name cannot be used in other expressions.
- The space for a local variable is deallocated at the closing curly brace of the function where it's declared.
Numeric Types
- Numeric values in C++ can either be floating point (able to hold non-integer parts, like decimals) or integers (only able to hold integer values).
- Integer values in C++ can either be signed (able to hold positive and negative values) or unsigned (only able to hold non-negative values).
- Types like booleans and characters are numeric types in C++. A boolean, for example, is an integer type that is guaranteed to be able to hold two values.
Numeric Promotion and Conversion
- If a floating point is used where an integer type is needed, that is always conversion since the non-integer part of the value may be lost.
- If an integer type is used where a floating point is needed, that is always conversion since there may not be enough bits allocated to storing the integer part of the value.
- If a signed value is used where unsigned type is needed, that is always conversion since negative values cannot be correctly represented.
- If an unsigned value is used as a signed type of the same size is needed, that is always conversion since there may not be enough bits allocated to storing the magnitude of the value.
- If a value from a larger numeric type is used where a smaller numeric type is needed, that is always conversion since there may not be enough bits to store the value.
- If a value from a smaller numeric type is used as a larger numeric type of the same signedness is needed, that is always promotion since there will definitely be enough bits to store the value.
- The Five Stages of an Object's Life
- Object lifetime in C++ - allocation, initialization, use, destruction, deallocation
- Loops and Conditionals with Object Lifetime
- Object lifetime with loops and conditionals in C++
- Numeric Types—Fundamentals
- Fundamentals of numeric types in C++
- integers vs floating point
- signed vs unsigned
- Fundamentals of numeric types in C++
- Promotion and Conversion
- Promotion and conversion between numeric types in C++
- Practical Advice for Numeric Types
- Practical advice on choosing and using numeric types in C++
Week 2
Lesson 1 — Compilation, Version Control, and Pair Programming
Key Points
Compilation
- All of the C++ code we write will go through the following steps:
- First, the compiler changes source code in to assembly code.
- Next, the assembler changes the assembly code into object code.
- Finally, the linker changes the object code into machine code.
- The compiler deals with all of the parts of variables that are not actually stored in memory at run time.
- Specifically, that means that errors related to a variable's type or name will be caught at compile time!
- The (simplest) commands to compile a source file
myprogram.cpp
into an executable namedmyprogram
and then run that program areclang++ -c myprogram.cpp
(compilation and assembly)clang++ myprogram.o -o myprogram
(linking)./myprogram
(running the program)
- (In the homework we will typically have additional command-line options.)
Version Control
- A version control tool lets us keep track of all the different versions of our files, without having them clutter up our workspace.
- The version-control tool you will use this semester is called
git
. - We also use a website called GitHub to have a shared, web-accessible place to put copies of everyone's repositories.
Pair Programming
- Pair programming is a skill. Like any skill, it requires practice and intention to get better.
- Our job this semester, collectively, is to support each other in developing that skill!
- What is Compilation?
- What is compilation
- Stages of compilation (preprocessing, compilation, assembly, linking)
- Compiler translates high-level code to low-level machine instructions
- Brief history of C/C++ and Unix
- Compilation Commands
- Compilation commands to generate object file and executable
- clang++ options for compiling and linking
- Running the executable
- Compiler Responsibilities
- Compiler handles types
- Compiler handles names
- Compiler checks syntax
- Version Control
- Version control systems
git
and GitHub- Tracking file changes
- Thinking About Pair Programming
- Pair programming skills and mindset
- Academic integrity and pair programming
Lesson 2 — Arrays, Style, and Program Correctness
Key Points
Arrays of Primitives
- Arrays are
- Fixed-size — Once they're created, we can't change their size.
- Homogenous — All of the elements in them have to be the same type.
- Contiguous — If we looked at an array in memory, we'd see all of the elements side by side.
- Ordered — We can talk about what the first, third, or forty-second element of the array is.
- Arrays allow constant-time access: We can jump straight to an arbitrary element of the array.
- In terms of object lifetime, arrays act just like individual variables. The compiler needs to know ahead of time how big the array will be, so that it can write the instructions to allocate enough space to store the array.
- Declare and initialize an array:
int arr[3]{1, 2, 3};
- Usage: To access the item at index
i
:arr[i]
arr[i]
is translated to*(arr + i)
*x
means the value in memory at the address given byx
arr + i
means the address at an offset ofi
from the address given byarr
- So
*(arr + i)
means the value in memory at the address of the item at offseti
from the start of the array.
- Destruction and deallocation:
- When the array is destroyed, each item is destroyed.
- When the array is deallocated, all items are deallocated.
- Declare and initialize an array:
- Accessing an array out-of-bounds causes undefined behavior.
- Undefined behavior means your program has gone off the rails, anything could happen, from something catastrophic to everything seeming to work.
- You should never write a program that you know has undefined behavior just because nothing bad happened when you tested it.
- Typical C++ compilers generate code that does things the fastest and easiest way, without any special safety checks.
- That includes finding the place in memory where we're claiming an item should be and interpreting the bits at that location as being of the appropriate type (that you asked for).
- Accessing an array out-of-bounds can cause strange behavior. While, in principle, anything can happen when code causes undefined behavior, the strange behaviors you might see can vary between systems. But you can learn to recognize problematic behaviors, especially on the systems you use for coding and testing.
- Accessing memory that is way out-of-bounds can cause your program to crash.
- The operating system will not allow your program to access memory outside of its allotted region.
- This error is called a segmentation fault (segfault for short).
Code Style
- The goal of our code should always be to explain our thinking to other people who read the code.
- Similar things should look similar and different things should look different.
Testing
- When we write tests for our code, our goal should be to reveal bugs, not just demonstrate that our code "works".
- When you write tests, think about what edge cases you need to consider to make sure that your implementation is correct.
- If you make a mistake in your own code that results in "wrong" behavior, write a test for it! While we encourage test-first development, you can always add to your tests to make them more thorough as you develop a deeper understanding of where the tricky parts are in a particular coding task.
- Arrays, Our First Data Structure!
- Properties of arrays: contiguous, ordered, homogeneous, fixed-size
- Advantages/disadvantages of arrays
- Declaring Arrays in C++
- Declaring arrays in C++
- Specifying size, initialization
constexpr
for non-variable sizes
- Accessing Array Elements
- Array variable holds address of first element
- Indirection/dereference operator (
*
) - Indexing with offset (
[]
) - Changing array elements
- Out-of-Bounds Access
- Undefined behavior from out-of-bounds access
- Typical unsafe array access behavior
- Segmentation faults from accessing invalid memory
- Array Example
- Object lifetime for arrays
- Walkthrough of array example code
- Style and Elegance
- Code style and consistency
- Naming conventions
- Whitespace and indentation
- Idioms and community expectations
- Program Correctness Matters
- Purposes and limitations of testing
- Real-world bugs and consequences
- Test goals: revealing bugs, not proving correctness
Week 3
Lesson 1 — Multiple Files, Forward Declarations and Include Guards
Key Points
- We split our code into multiple files to avoid code duplication.
- A multifile program contains:
- One or more source files (
.cpp
), exactly one of which has amain
function. - One or more header files (
.hpp
), which declare functions that are defined in the source files (.cpp
).
- One or more source files (
-
Make sure that every header file (
.hpp
) has an include guard.#ifndef FILENAME_HPP_INCLUDED #define FILENAME_HPP_INCLUDED ...stuff... #endif
- Include guards prevent multiple definitions when a header file is included more than once in the same source file.
- Each preprocessor macro we use for an include guard must be unique (otherwise multiple files will be trying to use the same guard and only one of them will be seen), which is why we usually use the filename as part of the name.
-
Make sure that each file uses
#include
to include any header files (.hpp
) that declare things it uses.- That often includes a source file's own header file!
- Sometimes header files need to include other header files!
- Use
#include "
filename
"
for your local files from the current directory. - Use
#include <
filename
>
for system include files
- To compile your project:
- Compile each source file (
.cpp
) into an object file (.o
) usingclang++ -c filename.cpp
.- In practice, you'll probably want other flags too, such as
-std=c++17
,-g
and-Wall -Wextra -pedantic
. - (Remember from last week that this phase includes preprocessing, compiling the source file into assembly code, and then assembling the assembly code into machine code.)
- In practice, you'll probably want other flags too, such as
- Link object files (
.o
) together into an executable usingclang++ -o executableName file1.o file2.o ...
- Compile each source file (
- To recompile:
- If a source file (
.cpp
) changes, recompile it into an object file (.o
) and relink as necessary. - If a header file (
.hpp
) changes, recompile all source files that include it or that include a file that includes it, and so on.
- If a source file (
- Rules of thumb:
- Don't compile a header file (
.hpp
). - Don't
#include
a source file (.cpp
).
- Don't compile a header file (
- Why Multiple Files?
- Motivation for splitting code into multiple files
- Compiling and linking multiple files
- A Multifile Program
- Forward declarations
- Compiling vs linking
#include
Directives- The C++ preprocessor
#include
directives- Header files for the standard library
- Header Files
- Header files
- Forward declarations
- Another Problem
- Problem: multiple declarations from multiple includes
- Include Guards
- Include guards
#define
,#ifndef
,#endif
- Preventing multiple definitions
- Should a Source File Include Its Own Header File?
- Whether a source file should include its own header (It should!)
- Error Messages
- Interpreting compiler errors
- Header Files and Recompilation
- Header file changes and recompilation
Lesson 2 — References
Key Points
- A variable with a reference type (e.g.,
int&
) is just another name for an existing variable (in this case anint
). - An important use of references is as function parameters:
- Allows the function to alter data outside of its stack frame.
- Prevents unnecessary copy operations.
- The
const
keyword specifies that changes are prohibited (i.e., we have read-only access to the data).- The
const
keyword affects what we are allowed to do via a particular name, not what others might be able to do via a different name, so the same piece of data can haveconst
names that cannot be used to change it and non-const
names that can. - You cannot initialize a non-
const
reference (e.g., of typeint&
) with aconst
name (e.g., of typeconst int
). That would circumvent theconst
restriction! - A
const
-reference function parameter is a good way to avoid needless copying but still promise not to change the given argument.
- The
- Reference Types
- References in C++
- Rules for visualizing references
- References: Exercise 1
- Visualizing simple code with references
- References Exercise 2: Assigning to a Reference
- Assigning to a reference
- References Exercise 3: References to References?
- No references to references in C++
- Example: References as Function Parameters
- References allow a function to alter data outside its own stack frame.
- Warning: C++ Is Not Java
- Differences between C++ and Java references (which are actually more like C++ pointers!)
- References Exercise 4: Multiple “Return” Values via References
- Using references to return multiple values from a function
- const References
const
references are read-only references
- References Exercise 5: Find the Compiler Errors
- Finding compiler errors with references
- Example: Why const References?
- Motivation for
const
references as parameters
- Motivation for
Week 4
Lesson 1 — Classes and Objects
Key Points
Recap Video
There was a fair amount to digest in this lesson. As a review, and because sometimes it's helpful to see things presented a different way, here's a video that goes over what we've covered in this lesson. If you feel like you've already mastered the material, you can skip it.
MORE videos! I'm excited!!
I'm pretty sure I've understood, but sometimes it's comforting to watch a video that tells me things I think I know.
Key Points
- Classes are conceptually similar to what you've seen before, but have syntactic/terminological differences.
- Instead of “instance variables” and “methods” we say “member variables” or “data members” and “member functions” (“members” to refer to them all together).
- Declarations/definitions
- The class definition goes in the header file (
.hpp
). It declares all of the class members.- In CS 70 convention we name all member variables with a trailing underscore (e.g.,
age_
).
- In CS 70 convention we name all member variables with a trailing underscore (e.g.,
- The member-function definitions go in the source file (
.cpp
). They specify the instructions for each function. - When we define a member function we have to specify which class it belongs to using the scope-resolution operator (
::
):- For example,
void Cow::moo(int numMoos);
.
- For example,
- The class definition goes in the header file (
- A member function can be declared
const
.- A
const
member function promises not to change the values of member variables. - If you have a
const
object, you can only callconst
member functions on it.
- A
- We looked at two different kinds of constructors:
- Parameterized constructors take parameters and must be invoked explicitly (e.g.,
Cow bessie{3, 12}
). - Default constructors take no parameters and are implicitly invoked for default initialization (e.g.,
Sheep fluffy;
).
- Parameterized constructors take parameters and must be invoked explicitly (e.g.,
- You can disable the use of a default constructor using the
delete
keyword (e.g.,Cow() = delete;
). - In a constructor, the member-initialization list specifies how to initialize each member variable.
- For example,
Cow(int numSpots, int age) : numSpots_{numSpots}, age_{age}
. - In CS 70, you must use a member-initialization list whenever possible. (The only exception is initializing a primitive array when you want a copy of another array—for that case, you need to loop over the elements of the array you're copying to populate your new array.)
- For example,
- A class definition ends in a semicolon (
;
).- A class definition ends in a semicolon.
- A class definition ends in a semicolon.
Did we mention that a class definition ends in a semicolon?
- Before You Start
- Difference between header (.hpp) and source (.cpp) files
- Exploring a C++ Class
- Overall structure of a C++ class
- Similarities and differences from classes in other languages like Java and Python
- C++'s Class-Related Terminology
- C++ terminology for classes and objects
- Declarations/Definitions
- Header files contain class interface (declarations)
- Source files contain implementations (definitions)
#include
the header to use the class
- Member Variables (a.k.a. Data Members)
- Member variables (data members)
- Naming convention with trailing underscore
- Accessing member variables
- Member Functions
- Declaring and defining member functions
- Calling member functions
- Scope resolution operator
::
- What is a Namespace?
- Namespaces in C++
- Avoiding name collisions with namespaces
std
namespace andusing namespace std;
- const Member Functions
const
member functions- Promise not to modify member variables
- Allowed on const objects
- Placement of
const
keyword
- Exercise: const Member Functions
- Identifying which member functions should be
const
- Calling
const
and non-const
member functions onconst
objects
- Identifying which member functions should be
- Constructors
- Constructors in C++
- Purpose is to initialize object
- Parameterized vs. default constructors
- Exercise: Constructors
- Valid and invalid ways to invoke constructors
- Constructing Objects
- Object initialization syntax in C++
- Lack of assignment operator
- Curly brace initialization
- Objects in Our Memory Model
- Drawing objects in memory diagrams
- Member Initialization Lists
- Initialization of member variables
- Member initialization list syntax
- Ordering rules
- Access Control
- Public and private access control in C++ classes
- Placement of
public
/private
keywords
- Semicolon at the End!
- Class definitions end with a semicolon
Lesson 2 — Object Lifecycle in Detail (Constructors, etc.)
Key Points
- Object lifetime on the stack: when?
- Allocation: at the opening
{
of the function - Initialization: at the declaring line
- Use: between initialization and destruction
- Destruction: at the closing
}
of the declaring block - Deallocation: at the closing
}
of the function
- Allocation: at the opening
- Default initialization/construction
int x;
Cow mabel;
Cow bessie{};
- For an object, invokes the default (parameterless) constructor
- Copy initialization/construction
- Creates a new object that is a copy of an exiting object
int x = y;
int a{b};
Cow mabel = adeline;
Cow bessie{fennel};
- For an object, invokes the default constructor (which takes a reference to an object of the same type)
- Also used to initialize function parameters and return values
- Assignment operator
- Changes an existing object to be a duplicate of another existing object
s = t;
cadewyn = ethyl;
- For an object, invokes the assignment operator, a special member function called
operator=
.
- Destructor
- A special member function that is automatically invoked when an object is destroyed
- For class
Cow
, the destructor is named~Cow
- Typically used to “clean up” or release any resources the object is holding onto
- The compiler can synthesize or disable these functions:
Cow(); // Means that the programmer will define the default constructor
Cow() = default; // Means that we will use the compiler's synthesized default constructor
Cow() = delete; // Means that this class does not have a default constructor
- Same for default constructor, copy constructor, assignment operator, and destructor (but don't disable the destructor!!)
- For arrays, the same as above, only \( N \) times, where \( N \) is the size of the array.
- For references, initialization and destruction mark the usage period of the name
- We don't model any allocation or deallocation for references
- For data members of a class
- Allocation happens when the object is allocated
- Initialization happens before the opening
{
of the constructor (so use the member-initialization list!) - Destruction happens at the closing
}
of the destructor - Deallocation happens when the object is deallocated
- Before You Start…
- The five phases of object lifetime
- Objects on the Stack
- Timing of each phase for stack variables
- All About Initialization
- Initialization of primitive vs. class types
- Default vs. direct vs. copy initialization
- Copy constructors
- Function parameters and return values
- Synthesized Constructors
- Synthesized default and copy constructors
- Specifying synthesized, programmer-defined, or deleted constructors
- Use Phase
- Assignment operators
- Synthesized assignment operators
- Assignment vs. copy initialization
- Destruction
- Purpose of destruction phase
- Destructors in C++
- Naming convention
- Purpose is to clean up / release resources
- Synthesized destructors
- Arrays
- Object lifetime for arrays
- References
- Object lifetime for references
- Data Members
- Object lifetime for data members
- Putting It All Together
- Comprehensive tracing of object lifetime in an example program
(When logged in, completion status appears here.)