Questions from Week 4
Q: What do step-over, step-into, step-out, etc. mean in the debugging environments of my IDE?
A: They represent scope / functional scope traversals!
You can find a complete write-up on a nice stack overflow article located here.
For summary, however:
Continue executes code until it hits the next breakpoint.
Step over steps over any sub-scopes or function calls (i.e., follows breakpoints only in the present scope / function)
Step into steps into any sub-scopes or function calls
Step out steps back from the current scope or function call.
These last 3 options are largely advanced debugging tactics, and will find little use in this class.
But hey, at least now you know!
Introduction to Arrays
We make lists of things every day, for example:
Shopping lists are lists of storebought items.
The even integers between 0 and 100 (inclusive) is a list of 51 integers.
The list of things you can make a list out of is a list of list items... woah...
Here's a typical to-do list for me:
What kind of properties do these lists have?
These lists have a number of separate items that are usually of the same type.
Each item has a relative position in the list (e.g. a first item, second item, and last item on a to-do list).
If I'm writing my list on paper, I only have so much room to write, so our lists might have a maximum size.
But wait! It was an elaborate ruse! I was talking about C++ arrays this whole time! (kinda)
An array in C++ is a list of a particular type of variable. It has a specific amount of space assigned to it, and indexes its items starting at 0.
We declare arrays using the following syntax:
<type> <name>[size];
Notice that I define a value size within my array declaration that decides how big I want it to be.
Note: The value we declare for size MUST be a CONSTANT at compile time.
What do I mean by "how big?"
#include <iostream>
#include <string>
using namespace std;
int main() {
// An array called i
// that can hold 3 ints
int i[3];
// An array of called
// yarn that can hold 2 strings
string yarn[2];
}
So, when I declare an array with a given size, I'm saying:
"Dearest computer, please make sure there's space somewhere for a list of <size> <type> variables. -Love, Andrew"
So, here's what the above statement, int i[3]; looks like:
Cool boxes, Andrew! Thanks, I drew them by hand.
But how do we access those boxes?
We can access the elements of an array by the index at which each is defined.
For example, to access the element in the second box of i, I would say i[1]; (remember indexes start at 0).
Let's look at a bunch of examples with declarations.
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[3];
cout << i[1] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
string yarn[2];
cout << yarn[1] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int flimsySize = 3;
int i[flimsySize];
cout << i[1] << endl;
}
How could I make the above compile while still using flimsySize?
Make flimsySize a constant; e.g., const int FLIMSY_SIZE = 3;
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
// Apple of my pi
int apple[3.14];
cout << apple[1] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[];
// Please compiler, I promise I'll
// deal with i later ;_;
}
Some notes from the above examples:
Array sizes must be defined at compile time and must be constant.
Arrays of built-in types, if not initialized, will have indeterminate values in their storage.
Arrays of strings, if not initialized, will have empty strings in their storage.
Initializing Arrays
So far we've talked about just declaring arrays, but that's dull; what if we want to give them values?
We can initialize arrays using the following syntax:
<type> <name>[<size (optional)>] = {<value0>, <value1>, ...};
For example, I can initialize i with the values {1, 2, 3}:
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[] = {1, 2, 3};
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
But wait a second... I didn't tell my compiler how much space I wanted to reserve for i...
Is my compiler going to break my legs for this offense?
As long as you initialize an array at declaration, you do not need to specify the size; it will match the dimension of the initialization.
Some examples:
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[3] = {1, 2, 3};
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[4] = {5, 5, 5};
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
// What's in here? :O
cout << i[3] << endl;
}
A depiction of the above:
So, as you see, it's still fine to save more room for more stuff in an array that I initialize to a smaller array of values.
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[2] = {1, 2, 3};
cout << i[0] << endl;
cout << i[1] << endl;
}
The same thing would happen if I tried to add another list item to my todo-list paper of limited size:
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[3];
i = {104, 101, 121};
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
Weird right? It looks like it should work, but the initialization syntax is special at declaration.
That said, we can individually assign values to array elements.
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[3];
i[0] = 1;
i[2] = 2;
i[1] = 0;
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
int i[3];
i[0] = 1;
i[2] = 2;
i[1] = 0;
// >_> <_<
i[4] = 3;
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
cout << i[4] << endl;
}
Note, I can declare constant arrays just like I would a variable:
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
const int IT[3] = {1, 2, 3};
// Am... am I allowed to touch
// IT compiler?
IT[1] = 5000;
cout << IT[1] << endl;
}
Type coercion works the same way with arrays as it does with variables (pretty much):
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
char i[3] = {104, 101, 121};
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int main () {
string s = "...awkward...";
if (s == "...awkward...") {
// Weird spacing for
// presentation
string greeting[]
= {"hi...", ":/"};
} else {
string greeting[]
= {"SUP", ">:)"};
}
cout << greeting[0] << endl;
cout << greeting[1] << endl;
}
You're all experts now, yes? How about a nice big exercise.
What will the following code output?
#include <iostream>
#include <string>
using namespace std;
int main () {
double andrewsCandy = 3.5;
// I ate half of a snickers :(
// Weird spacing for presentation
const int TYPE_COUNT = 3;
string candyTypes[]
= {"Snickers", "Starburst", "Banana!"};
for (int i = 0; i < andrewsCandy; i++) {
cout << candyTypes[i % TYPE_COUNT] << endl;
candyTypes[i % TYPE_COUNT]
= candyTypes[(i+1) % TYPE_COUNT];
}
}
Arrays & Functions
Of course we can use functions with arrays! Glad you asked.
There's one BIG difference between how C++ functions handle array arguments versus single variables.
How do C++ functions pass arguments by default for basic types like int, double, and string?
Pass by Value: meaning the function parameters are copies of the arguments.
What will the following code output?
#include <iostream>
#include <string>
using namespace std;
int arbitraryIntFunctionThatAddsStuff (int a);
int main () {
int i = 1;
arbitraryIntFunctionThatAddsStuff(i);
cout << i << endl;
}
int arbitraryIntFunctionThatAddsStuff (int a) {
return a++;
}
Now, if we wanted out function arbitraryIntFunctionThatAddsStuff to modify its argument, then we'd write:
#include <iostream>
#include <string>
using namespace std;
int arbitraryIntFunctionThatAddsStuff (int& a);
int main () {
int i = 1;
arbitraryIntFunctionThatAddsStuff(i);
cout << i << endl;
}
int arbitraryIntFunctionThatAddsStuff (int& a) {
return a++;
}
How do C++ functions pass arguments for arrays like int[], double[], and string[]?
Think of it as passing by reference (though it's not): meaning the function parameters are pointers to the argument; they refer to the same array in memory. (more on pointers later)
#include <iostream>
#include <string>
using namespace std;
int arbitraryArrayFunctionThatAddsStuff (int a[]);
int main () {
int i[] = {1, 2};
arbitraryArrayFunctionThatAddsStuff(i);
cout << i[0] << endl;
}
int arbitraryArrayFunctionThatAddsStuff (int a[]) {
return a[0]++;
}
Notice that I don't have to put the & in my parameter a.
So, what does this look like pictorially? We can look at it in terms of scope:
So knowing this, how can I make functions that don't touch the original array?
Well, if I want to be extra safe, then I can declare parameters constant, even though what I pass in is not a constant:
What would the following code segment output?
#include <iostream>
#include <string>
using namespace std;
int noTouchySum (const int a[], int n);
int main () {
int i[] = {1, 2, 3};
int j = noTouchySum(i, 3);
cout << j << endl;
}
int noTouchySum (const int a[], int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += a[i];
}
return result;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
int noTouchySum (const int a[], int n);
int main () {
// Passing in the {...}
// notated "array"...
int j = noTouchySum ({1, 2, 3}, 3);
cout << j << endl;
}
int noTouchySum (const int a[], int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += a[i];
}
return result;
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
void yallPlusOne (int yall[], int n);
int main () {
const int i[] = {1, 2, 3};
// argument i is constant...
yallPlusOne(i, 3);
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
// ...but the parameter a isn't
void yallPlusOne (int a[], int n) {
for (int i = 0; i < n; i++) {
a[i]++;
}
}
Will the following code compile? If so, what will it output?
#include <iostream>
#include <string>
using namespace std;
void yallPlusOne (int yall[], int n);
int main () {
int i[] = {1, 2, 3};
// argument i has 3 elements...
yallPlusOne(i, 3);
cout << i[0] << endl;
cout << i[1] << endl;
cout << i[2] << endl;
}
// ...but the parameter lists size 2 0_o
void yallPlusOne (int a[2], int n) {
for (int i = 0; i < n; i++) {
a[i]++;
}
}
This curious example illustrates that when arrays are declared as parameters, the array type is said to decay to a pointer pointing to the first element of that array, meaning that any size definitions of the array in the parameter are ignored.
Now, when dealing with arrays, it's helpful to have some... well, helper functions. Let's write a couple.
Design a function intArraysEqual that compares n items starting at an index (called start) of two int arrays and determines (via bool return) if they are equivalent.
#include <iostream>
#include <string>
using namespace std;
bool intArraysEqual(int arr1[], int arr2[], int start, int n) {
// [!] Iterate through all n items starting
// at the start index
for (int i = ???; ???; i++) {
// [!] We can return something whenever we find
// an inconsistency
if (???) {
???
}
}
// [!] If we get here, we know there was no
// inconsistency so...
???
}
int main () {
int i[] = {1, 2, 3};
int j[] = {0, 2, 3, 4, 5};
cout << intArraysEqual(i, j, 1, 2) << endl;
// Above will print out: 1
// because {2, 3} == {2, 3}
cout << intArraysEqual(i, j, 0, 2) << endl;
// Above will print out: 0
// because {1, 2} != {0, 2}
}
Design a function stringArrayPrint that does nothing but print the comma and space-separated values of the first n values of an array of strings.
#include <iostream>
#include <string>
using namespace std;
/*
* Takes in an array of strings and some max index
* n, and prints all n strings in the array, comma
* and space separated and enclosed in brackets
*/
void stringArrayPrint (string s[], int n) {
// [!] What should be prepended?
string result = ???;
// Iterate through all n specified indexes
for (int i = 0; i < n; i++) {
// [!] Add the string, its quotes, and any
// comma-space separation
result += ???;
}
// [!] We need to fix something at the end of all this
// (hint, consider n as zero and non-zero)
if (???) {
result = ???;
}
// Tack on the closing bracket
result += "}";
cout << result << endl;
}
int main () {
string s[] = {"print", "me", "NOW"};
stringArrayPrint(s, 3);
// Above will print out:
// {"print", "me", "NOW"}
stringArrayPrint(s, 0);
// Above will print out:
// {}
}
Arrays of Strings
Note: For the present section, we'll ignore any reference to strings being null terminated (i.e., we'll speak only of C++ strings). Wait for a later lecture on that distinction...
What is special about arrays of strings?
Well, think about what strings actually are...
In terms of arrays, what are strings?
Strings are, for the purposes of the current discussion, an array of characters.
Take the following for example:
#include <iostream>
#include <string>
using namespace std;
int main () {
string hola = "hi!";
string greets[] = {"howdy", "bonjour", "hallo"};
// [!] How to print out the '!' in string hola?
// [!] How to print out "bonjour" in array of string greets?
cout << ??? << endl;
}
Just as we consider hola[2] to give us the character of hola at index 2, how can we grab a character from a string array item?
Thinking about strings as arrays of characters means that an array of strings is an array of an array of characters!
As such, we can access the j-th character of an i-th string in an array via the syntax:
stringArray[i][j];
Here's a small example:
What does the following code segment output?
#include <iostream>
#include <string>
using namespace std;
int main () {
string greets[] = {"howdy", "bonjour", "hallo"};
greets[1][0] = 'B';
cout << greets[1] << endl;
cout << greets[2][4] << endl;
}
Finally, let's try one giant array, string, function exercise to drive it home.
Design a function called echoyCapitalz that takes in an array of strings (and its length), and capitalizes every i-th
letter in each array string, where i is the (index + 1) of the string in the array. Make this function a void mutator.
For example, the string at index 0 of the input will have every letter capitalized, the string at index 1 will have every other letter capitalized, etc.
Some test cases for the function:
string s[] = {"this", "example", "is lame"};
echoyCapitalz(s, 3);
// Use our stringArrayPrint from before
stringArrayPrint(s);
// Above will print out:
// {"THIS", "ExAmPlE", "Is LamE"}
Practice Problems
Here are a few practice problems involving arrays, functions, and stuff...
What will the output of this function be?
#include <iostream>
#include <string>
using namespace std;
int i[] = {1, 2, 3};
int awesomeFunc (int a[], int n) {
int result = 1;
for (int j = 0; j < n; j++) {
result *= i[j];
}
return result;
}
int main () {
int i[] = {4, 5, 6};
cout << awesomeFunc(i, 3) << endl;
}
Design a function called interleaveStrings that takes as input 1 array of strings (called s), a number of elements n, and returns a string composed of all of the elements interleaved in sequence.
Some examples of interleaveStrings:
#include <iostream>
#include <string>
using namespace std;
/*
* interleaveStrings takes in an array of strings s,
* and a number of strings in that array n, and interleaves
* all characters of each string, producing a string
* as a result.
*/
string interleaveStrings (string s[], int n) {
string result;
// We'll set a flag that determines whether or not
// we should keep iterating through strings: it will
// be true whenever we still have more characters to
// add in AT LEAST ONE string in the array s
bool charsLeft = true;
// We'll iterate as long as at least one string in s
// has characters left to add; i will represent the
// index of each string we're currently examining
for (int i = 0; charsLeft; i++) {
// We'll assume that charsLeft starts as
// false, and will be set to true if we ever
// have a string with at least 1 char left
charsLeft = false;
// [!] We want to iterate through all n strings
// in s
for (int j = 0; ???; j++) {
// [!] We'll only add a character if the
// current index i is in bounds for the
// current string at j in array s
if ( ??? ) {
// [!] If it IS in bound, we add it to
// result
result += ???;
// ...and also set charsLeft to true
// since we found a string with chars
// left
charsLeft = true;
}
}
}
return result;
}
int main () {
string s[] = {"AAA", "BB", "CCCC"};
cout << interleaveStrings(s, 3) << endl;
// Above prints out:
// ABCABCACC
string w[] = {"BB", "AAA", "CCCC"};
cout << interleaveStrings(w, 3) << endl;
// Above prints out:
// BACBACACC
string t[] = {"AAA", ""};
cout << interleaveStrings(t, 2) << endl;
// Above prints out:
// AAA
}