Huge update

This commit is contained in:
left_adjoint 2024-02-05 17:02:18 -08:00
parent 8911265ee9
commit e49f59a2d8
10 changed files with 543 additions and 3 deletions

14
cs161/badcin1.cpp Normal file
View File

@ -0,0 +1,14 @@
#include <iostream>
using namespace std;
int main(){
int num1;
int num2;
cout << "Enter two numbers" << endl;
cin >> num1;
cin >> num2;
cout << "The sum of these numbers was: " << num1 + num2 << endl;
return 0;
}

View File

@ -585,7 +585,7 @@ Now we're finally ready to write our number guessing game
#+end_src
Try to read this code through and understand the logic of what it's doing! The only thing we haven't explained to this point is the =%= operator, which is the "modulus" operator. Basically, =a % b= returns the remainder---in the "you're learning division as a kid" sense of remainder---of dividing =a= by =b=. Now, in terms of why we *care* about this operator it gives us a quick way to take a big range of numbers and map it to something smaller. For example, if we do =a % 20= our outputs, no matter what =a= is, will be between 0 and 19. Similarly if we say =a % 100= this will give us an output that's between 0 and 99.
** TODO Averaging numbers and while-loops
** DONE Averaging numbers and while-loops
Here's another idiom that will help us get practice with while-loops: how do you average an *arbitrary* number of numbers?
So this is a fun exercise because it means we need to keep track of how many things have been entered and then divide by that at the end. So I'm going to just assume we're adding up =double= s here because it makes everything fundamentally similar. How does one quit out of this loop? Well there's two ways we could do this. One is to have a special number that means "we're done". Something like "-1". That's the approach we're going to do first. After that, we'll see how to
@ -653,8 +653,10 @@ Now you can see that we've safeguarded against dividing by zero!
** TODO For-loops (and the shape of loops to come)[fn:8]
So far we've seen some examples of using =while= loops and now it's time to see the other kind of loop: the =for=-loop. Unlike =while=, which is "indefinite" iteration because it could theoretically go on forever, the =for=-loop is "definite" iteration because it should (unless there's a bug) only run for a pre-determined number of times.
If =while= can be thought of us
*** Summing a bunch of numbers
If =while= can be thought of us "keep going until something changes", then =for= should be thought of as "do this X times".
The simplest possible for-statement we can write looks
*** TODO Summing a bunch of numbers
*** Arrays: the reasons for =for=
** TODO Data validation, the world's most annoying idiom
@ -688,6 +690,331 @@ So what we mean here is something like you asking for "yes" or "no" from the use
}
#+end_src
*** TODO The wrong kind of data (cin.fail() &c.)
Okay so from here we have some interesting issues that come down to exactly *what* =cin= and =>>= are doing together.
**** TODO What ever happened to baby cin?
First off, consider the following program.
#+begin_src cpp :tangle badcin1.cpp
#include <iostream>
using namespace std;
int main(){
int num1;
int num2;
cout << "Enter two numbers" << endl;
cin >> num1;
cin >> num2;
cout << "The sum of these numbers was: " << num1 + num2 << endl;
return 0;
}
#+end_src
Now if I run this program and enter input like this
#+begin_example
1[hit the enter key]
2[hit the enter key]
#+end_example
You'll see "The sum of these numbers was: 3" printed out.
If I enter
#+begin_example
1 2[hit the enter key]
#+end_example
I'll *also* see "The sum of these numbers was: 3" printed out. Why is that? Okay, so it has to do with the way =>>= works. Think of =cin= as being like a kind of pipeline. We fill it with stuff by typing a bunch of things and then hitting the enter key. Now =>>= doesn't just empty out everything in the pipe at once. It goes until either there's an error or until it hits whitespace (like, well, a space). So in the second example you've put "1 2" in the pipeline and the first call to =>>= will grab the =1= from the pipeline then *stop*. Then the second call to =>>= will grab the =2= from the pipeline then *stop*.
Now with that behavior in mind, we can start talking about what happens if you enter something that is the wrong type.
Let's run the program above one more time but now with the following input
#+begin_example
1 dog[hit the enter key]
#+end_example
What you'll get printed out is "The sum of these numbers was: 1". Why? Because it failed to read a number into =num2= and, so, just gave it a value of 0 rather than stop the program and fail.[fn:11]
We're going to introduce a new function called =cin.fail()= that lets us ask the question "did the last time we tried to read from =cin= go wrong?"
So let's
**** TODO Recovering from an error
When you
* TODO More advanced programs [1/3]
** TODO Functions on strings
*** DONE Simple operations on strings
So there's a few things you can do with strings. The first, is the ability to glue strings together, also called "concatentation". So far we've been implicitly gluing strings together using the syntax of =cout= and =<<=, but you can *actually* stick two strings to each other with =+=, just like adding two numbers.
Like this program:
#+begin_src cpp :tangle stringConcat.cpp
#include <iostream>
using namespace std;
int main(){
string str1;
string str2;
cout << "Enter some things: ";
cin >> str1;
cin >> str2;
cout << "Okay gluing those together you said: " << str1 + str2 << endl;
return 0;
}
#+end_src
This is an example of something kinda neat in C++, called "operator overloading". Basically, you can reuse things like =<<= and =+= in all sorts of different contexts at different *types*. That's the key part. The different uses have to be for different types or else there will be confusion. =+= can mean something for two ints, for an int and a string (check that one out yourself, by the way), for two strings, for two doubles, &c. But it can't mean two different things if you're just writing =1 + 2= where 1 and 2 are both ints.
Okay, also we should talk about how to turn data into strings because this is going to be useful! Basically, there's just a function in the =<string>= library that you can use called =to_string= that will convert any other data to being a string. This works with basically everything, like in this program:
#+begin_src cpp :tangle toString.cpp
#include <iostream>
#include <string>
using namespace std;
int main(){
int num1 = 1;
bool b = true;
char c = 'd';
double d = 1.23456;
cout << to_string(num1) + to_string(b) + to_string(c) + to_string(d) << endl;
return 0;
}
#+end_src
*** TODO =find= and =substr=
Okay, we we
*** TODO Iterating over strings with for-loops
Now remember how I showed you a bit about how arrays work and the fact that for-loops were made to work with them? Okay, so part of why I needed to show you that is while *technically* you're not using arrays in this class it's useful to understand the idea behind them because you can treat strings "like"[fn:13] arrays for operating on them.
The general idiom is that you can get the length of the string like this =s.length()= for a string =s=, then you can put that as the bounds of the for-loop and you can use array syntax (e.g. the square brackets) in order access the characters inside the string one by one.
Okay so here's a wholeLower function that turns an entire string to lower case using the per-character function =tolower()=:
#+begin_src cpp :tangle wholeLower.cpp
#include <iostream>
#include <string>
using namespace std;
string wholeLower(string s){
for(int i = 0; i < s.length(); i++){
s[i] = tolower(s[i]);
}
return s;
}
int main(){
string str = "YELLING";
cout << wholeLower(str) << endl;
}
#+end_src
*** TODO Example: Sarcasm Case
Alright, here's a nice silly example for how we use for-loops with strings:
#+begin_src cpp :tangle sarcasm.cpp
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
string sarcasmCase(string s){
string s2 = s;
for(int i = 0; i < s2.length(); i++){
if(rand() % 2 == 0){
s2[i] = tolower(s[i]);
}
else{
s2[i] = toupper(s[i]);
}
}
return s2;
}
int main(){
srand(time(0));
string str = "you can't do that!";
cout << sarcasmCase(str) << endl;
}
#+end_src
** DONE Switches vs. ifs
Switch statements are actually pretty simple and can, essentially, be thought of as "compiling" down to a more verbose if-elseif-else statement, except that rather than being able to ask *any* question you're just asking the question is /this/ equal to /this/.
Okay, concrete example time:
#+begin_src cpp :tangle switch1.cpp
#include <iostream>
#include <string>
using namespace std;
int main(){
char letter;
cout << "Enter a letter and I'll tell you the name of a dog that starts with that: ";
cin >> letter;
switch(tolower(letter)){
case 'b':
cout << "Bertie" << endl;
break;
case 'c':
cout << "Charles" << endl;
break;
case 'd':
cout << "Dora" << endl;
break;
case 'e':
cout << "Edie" << endl;
break;
case 'f':
cout << "Francine" << endl;
break;
case 't':
cout << "Taffy" << endl;
break;
case 'p':
cout << "Pisces" << endl;
break;
default:
cout << "Sorry, I don't know any dog names that start with that" << endl;
}
return 0;
}
#+end_src
So what this code is doing is equivalent to the following program
#+begin_src cpp :tangle switchIf.cpp
#include <iostream>
#include <string>
using namespace std;
int main(){
char letter;
cout << "Enter a letter and I'll tell you the name of a dog that starts with that: ";
cin >> letter;
if(tolower(letter) == 'b'){
cout << "Bertie" << endl;
}
else if(tolower(letter) == 'c'){
cout << "Charles" << endl;
}
else if(tolower(letter) == 'd'){
cout << "Dora" << endl;
}
else if(tolower(letter) == 'e'){
cout << "Edie" << endl;
}
else if(tolower(letter) == 'f'){
cout << "Francine" << endl;
}
else if(tolower(letter) == 't'){
cout << "Taffy" << endl;
}
else if(tolower(letter) == 'p'){
cout << "Pisces" << endl;
}
else {
cout << "Sorry, I don't know any dog names that start with that" << endl;
}
return 0;
}
#+end_src
So you can see some of the differences between if and switch. First, note that you only have to provide the expression you're comparing *once* to the switch statement. Meanwhile the direct equivalent would involve having to run =tolower= on the letter every time.
Now that just seems inconvenient here but there are times that it's actually a big deal! Let's consider this example with random numbers
#+begin_src cpp :tangle diceRoll.cpp
#include <iostream>
#include <cstdlib>
using namespace std;
int main(){
// here we set the starting seed for the random number generator
srand(time(0));
// here we're playing a game where on a 1 you super win
// on a 2 or 6 you lose
// on a 3,4,5 you win a little bit
// we're going to use a neat property of switch which is that if you don't use break you can have multiple things
// all fall into the same case
switch(rand() % 6 + 1){
case 1 :
cout << "You super duper win" << endl;
break;
case 3:
case 4:
case 5:
cout << "You win a little!" << endl;
break;
default:
cout << "You lose, you so lose" << endl;
}
return 0;
}
#+end_src
If you were to naively[fn:12] convert this to if-statements you might do something like this!
#+begin_src cpp :tangle diceRollIf.cpp
#include <iostream>
#include <cstdlib>
using namespace std;
// this is a translation of the dice roll program from switches to ifs but
int main(){
srand(time(0));
if((rand() % 6 + 1) == 1){
// ...
}
else if(rand() % 6 + 1 == 3 || rand() % 6 + 1 == 4 || rand() % 6 + 1 == 5){
// ...
}
else {
// ...
}
return 0;
}
#+end_src
Given what we've talked about, can you see the problem here? See the problem is that you're going to get a *different* random number every time you call =rand()=. So that middle condition isn't testing whether the number you rolled is 3, 4, or 5 it's doing a separate dice roll for each condition. That's very much not what you want when dealing with random number generation!
No, instead, you need to do something more like
#+begin_src cpp
#include <iostream>
#include <cstdlib>
using namespace std;
// this is a translation of the dice roll program from switches to ifs, problems fixed
int main(){
srand(time(0));
int rolledDie = rand() % 6 + 1;
if(rolledDie == 1){
// ...
}
else if(rolledDie == 3 || rolledDie == 4 || rolledDie == 5){
// ...
}
else {
// ...
}
return 0;
}
#+end_src
** TODO Writing your own functions
* TODO Extended example:
* TODO Extended example: Let's play shop
In this example we'll be writing a small program that prints out a menu, let's you select items and quantities to purchase, and prints out your total at the end.
@ -697,6 +1024,11 @@ We'll do this program "the hard way" at first, which isn't very flexible, but we
* TODO Extended example: An Adventure Game
In this example we're going to look at how to make an old-school text-adventure game in C++.
* Footnotes
[fn:13] yes, "like", and it's not exactly true but it's close enough for the purposes of this class. In future courses you will be using literal arrays of characters that behave a bit differently. For example, you can't just access the length as part of character array and need to use a function to calculate this. If you take the second class from me I will definitely show you how to write functions like that.
[fn:12] This isn't meant in an insulting way it's sort of a use in technical writing to mean "try the way that is sorta obvious, almost automatic, without putting any thought into it"
[fn:11] You could easily argue that this is the wrong behavior, that it should throw an error that has to be handled in order for the program to continue. It is, however, the behavior of the language and we have to live with.
[fn:10] The reader might ask "ah, but what if I run the program over and over again very quickly, less than a second apart" and, yes, you'd be right! You do in fact get the exact same seed if you're very fast. In reality, this doesn't matter much because rarely are you both (a) caring about randomness (b) running a program that takes less than a second (c) running said program repeatedly
[fn:9] This isn't really random, it's technically what's called "pseudo-random". True radomness is when the next output is completely unrelated to the previous outputs. Like a coin flip. The coin doesn't "remember" its past flips. From the perspective of the coin flip, the universe could have come into existence a split second before it was tossed into the air. Pseudo-random is very different. Pseudo-random means "even if you have have the history of outputs, you cannot reliably predict the next output". This isn't the same as being random because pseudo-random processes do, in fact, depend on their past behavior it's just in a way that an external observer will always be surprised by what happens. This means that it's *like* being random in that if I'm looking at the output of a good pseudo-random generator and an actual random process I won't be able to tell which is which.

29
cs161/diceRoll.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <iostream>
#include <cstdlib>
using namespace std;
int main(){
// here we set the starting seed for the random number generator
srand(time(0));
// here we're playing a game where on a 1 you super win
// on a 2 or 6 you lose
// on a 3,4,5 you win a little bit
// we're going to use a neat property of switch which is that if you don't use break you can have multiple things
// all fall into the same case
switch(rand() % 6 + 1){
case 1 :
cout << "You super duper win" << endl;
break;
case 3:
case 4:
case 5:
cout << "You win a little!" << endl;
break;
default:
cout << "You lose, you so lose" << endl;
}
return 0;
}

20
cs161/diceRollIf.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <iostream>
#include <cstdlib>
using namespace std;
// this is a translation of the dice roll program from switches to ifs but
int main(){
srand(time(0));
if((rand() % 6 + 1) == 1){
// ...
}
else if(rand() % 6 + 1 == 3 || rand() % 6 + 1 == 4 || rand() % 6 + 1 == 5){
// ...
}
else {
// ...
}
return 0;
}

24
cs161/sarcasm.cpp Normal file
View File

@ -0,0 +1,24 @@
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
string sarcasmCase(string s){
string s2 = s;
for(int i = 0; i < s2.length(); i++){
if(rand() % 2 == 0){
s2[i] = tolower(s[i]);
}
else{
s2[i] = toupper(s[i]);
}
}
return s2;
}
int main(){
srand(time(0));
string str = "you can't do that!";
cout << sarcasmCase(str) << endl;
}

16
cs161/stringConcat.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <iostream>
using namespace std;
int main(){
string str1;
string str2;
cout << "Enter some things: ";
cin >> str1;
cin >> str2;
cout << "Okay gluing those together you said: " << str1 + str2 << endl;
return 0;
}

38
cs161/switch1.cpp Normal file
View File

@ -0,0 +1,38 @@
#include <iostream>
#include <string>
using namespace std;
int main(){
char letter;
cout << "Enter a letter and I'll tell you the name of a dog that starts with that: ";
cin >> letter;
switch(tolower(letter)){
case 'b':
cout << "Bertie" << endl;
break;
case 'c':
cout << "Charles" << endl;
break;
case 'd':
cout << "Dora" << endl;
break;
case 'e':
cout << "Edie" << endl;
break;
case 'f':
cout << "Francine" << endl;
break;
case 't':
cout << "Taffy" << endl;
break;
case 'p':
cout << "Pisces" << endl;
break;
default:
cout << "Sorry, I don't know any dog names that start with that" << endl;
}
return 0;
}

36
cs161/switchIf.cpp Normal file
View File

@ -0,0 +1,36 @@
#include <iostream>
#include <string>
using namespace std;
int main(){
char letter;
cout << "Enter a letter and I'll tell you the name of a dog that starts with that: ";
cin >> letter;
if(tolower(letter) == 'b'){
cout << "Bertie" << endl;
}
else if(tolower(letter) == 'c'){
cout << "Charles" << endl;
}
else if(tolower(letter) == 'd'){
cout << "Dora" << endl;
}
else if(tolower(letter) == 'e'){
cout << "Edie" << endl;
}
else if(tolower(letter) == 'f'){
cout << "Francine" << endl;
}
else if(tolower(letter) == 't'){
cout << "Taffy" << endl;
}
else if(tolower(letter) == 'p'){
cout << "Pisces" << endl;
}
else {
cout << "Sorry, I don't know any dog names that start with that" << endl;
}
return 0;
}

13
cs161/toString.cpp Normal file
View File

@ -0,0 +1,13 @@
#include <iostream>
#include <string>
using namespace std;
int main(){
int num1 = 1;
bool b = true;
char c = 'd';
double d = 1.23456;
cout << to_string(num1) + to_string(b) + to_string(c) + to_string(d) << endl;
return 0;
}

18
cs161/wholeLower.cpp Normal file
View File

@ -0,0 +1,18 @@
#include <iostream>
#include <string>
using namespace std;
string wholeLower(string s){
for(int i = 0; i < s.length(); i++){
s[i] = tolower(s[i]);
}
return s;
}
int main(){
string str = "YELLING";
cout << wholeLower(str) << endl;
}