All topics discussed in detail are referenced by the official study guide for the exam. Refer to this study guide Camille created for a reference to the midterm material.
Vim is a simple screen-based text editor on the surface, but in actuality it is a powerful tool for programmers looking to maximize their workflow. It’s especially popular because it is ubiquitous on pretty much every Unix system out there, which means anyone experienced with Vim can be a productive developer no matter the environment. It stands for Vi IMproved.
There are many different modes in Vim, but the only ones you should know about at this point are normal mode & insert mode. You can enter these modes from within one another by using ESC
and i
respectively.
To open a file with Vim from the command line, you can use vim [file here]
or (only on the lab machines) vi [file here]
. “Vim” and “Vi” are two separate programs, but on Hydra & Tesla, the vi
command is automatically converted to vim
, so it’ll open a file with Vim regardless of the command used. This is not the default behavior though.
To save and exit while inside of Vim, use the command :wq
. Alternatively, you can enter ZZ
as well. To exit without saving, you can use :q
or sometimes :q!
if you’ve made changes to the file and still wish to exit without saving.
Unix
: the broader family of operating systems including any Linux architecture & MacOSX, as well as others.Linux
: a subset of the Unix
family, and the quintissential operating system of choice for programmers and systems around the world.#include <iostream>
- Includes the IO library for stdin/stdout
which allows us to utilize functions like cout
& cin
.
using namespce std
- allows us to remove the std::
prefix from <iostream>
library methods and fields.
cin/cout
- the input/output stream operators, respectively. cin
allows us to receive input from stdin
i.e. input from console, and cout
allows us to “print” output to stdout
i.e. the console screen. cin
reads one word at a time, where a word is any value until (and not including) a whitespace character is encountered.
cin.eof()
- returns a bool
indicating true if the end of stdin
has been reached i.e. there is no more input to be read, stands for end-of-file.
1
2
3
4
5
6
while (cin >> dest) {
if (cin.eof()) {
// we've reached the end of input, so break
break;
}
}
cin.clear()
- clears the error-state of cin
which is “set” i.e. raised when cin
reads in “bad-input” e.g. reading a string
into an int
variable.
1
2
3
4
5
6
7
8
9
int dest;
while (cin >> dest) {
/* if we read in something like "hello", then the error flag is raised for cin
because "hello" is not an int but we're attempting to read it as such */
if (!cin.good()) {
// so clear the state and continue reading
cin.clear();
}
}
cin.ignore(size, delimiter)
- “ignores” (I like to say “erases”) whatever is in the “buffer” that’s between cin
& the variable you’re reading into. Its arguments size
and delimiter
are the amount of characters to be ignored if inside the buffer and the character that signals to stop ignoring once encountered, respectively. So cin.ignore(size, delimiter)
will stop ignoring either when it reaches whatever number size
is, or when it encounteres the char
passed to delimiter
(usually '\n'
).
If you were to read bad input, then that input gets left in the buffer instead of going into the intended variable. If it’s left inside the buffer, then it would cause issues for future reading because cin
will start reading from where it left off from inside the buffer, and in this case would be corrupted because of the bad input left over. Note that the following example uses #include <limits>
because the size
argument passed is std::numeric_limits<streamsize>::max()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int value;
while (cin >> value) {
/* if we read in something like "hello", then the error flag is raised for cin
because "hello" is not an int but we're attempting to read it as such */
if (!cin.good()) {
// so clear the state and continue reading
cin.clear();
// erase the input that was placed in the buffer to restore future reading operations
// which is "hello" in this case
// notice we use numeric_limits which is from the <limits> library
cin.ignore(std::numeric_limits<streamsize>::max(), '\n');
}
cin.get(dest)
- reads a char
from stdin
into whatever variable is specified by dest
, which should always be a char
since that is what cin.get()
reads specifically. There are two big differences between cin.get()
and cin >>
. The obvious one is that cin.get()
only reads a single char
, whereas cin >>
reads by starting at the first non-whitespace character and then ending when it encounters a whitespace character (basically it reads a word, by default). The not so obvious difference is that cin.get()
also reads the \n
character, which is the newline character. Any time you enter something into the console and read it in, the computer interprets it as [input]\n
. There is always an implicit \n
at the end of any input.
1
2
3
4
5
char dest;
while (cin.get(dest)) {
// read & print every char while possible
cout << dest;
}
wrapping cin
in if-statement - cin
is able to return true or false based on whether or not it successfully read a value from input.
1
2
3
4
5
6
7
8
```cpp
int dest;
/* reads dest while it can. If "bad" input was read or EOF was
encountered, it should fail */
while (cin >> dest) {
cout << dest;
}
```
printf(string, formating_args...)
- I’m not sure if you need to know what printf()
is exactly, but it is essentially the C
analog to cout <<
. string
is the string to be printed, and formatting_args
are the format specifiers to be passed to the string. It’s a bit similar to how you would print with Java from 101.
1
2
/* prints "I am 21 years old" using printf. */
printf("I am %d years old", 21);
At the very least, be familiar with the following format specifiers (very similar to the ones in 101)
1
2
3
4
%d - integer specifier
%f - floating point specifier
%s - string specifier
%c - char specifier
iomanip
- You can refer to the iomanip section of the lab 5 writeup for all of this.
Vectors exist in Java as well as Vector
, but they are a bit different behind the scenes compared to std::vector
. The Java-equivalent you’re familiar with is the ArrayList
, which is almost identical in behavior and function. The underyling data structure for std::vector
is an array, which is something you should all be familiar with. The difference between an array and a vector
is that arrays are fixed in size, whereas vectors
can change their size accordingly. That’s more or less it. vectors
provide methods like push_back()
, insert()
, size()
, and more to make your life easier than if you were to use an array. Just know that once you create an array, you cannot modify its size directly. You can modifiy its contents, but not the bounds of those contents. However with a vector
, you can modify both the size and the contents whenever you want.
To declare a vector
, use the following syntax
1
2
3
4
5
6
7
8
9
#include<vector>
using namespace std;
int main() {
vector<int> v; // declare empty vectory
vector<int> v2 = {1, 2, 3, 4}; // declare vector of 4 elements [1,2,3,4]
vector<int> v3(10); // declare vector of 10 empty elements
vector<int> v4(10, 3); // declare vector of 10 elements, all set to 3
}
Like mentioned previously, vector
provides many methods that make your life much easier, including but not limited to push_back()
, size()
, clear()
, and resize()
.
push_back(element)
- appends an element to the end of a vector
. The vector
will automatically resize itself if appending the element would go past its boundaries. This is the most common way of inserting elements into your vector.size()
- returns the size of the vector it was called on. So a vector
of 10 elements would be v.size() == 10
.clear()
- erases all elements in the vector it was called on, reducing its size to 0. (note this does not reduce the capacity to 0)resize(n)
- resizes a vector
’s capacity to fit n
elements. So v.resize(10)
will resize v
to hold 10 total elements. If you resize an array that already contains elements, it will cut off any elements necessary to resize (if shrinking) or it will expand normally if no cutting is necessary. e.g. a vector of {1, 2, 3}
with resize(2)
will be shrunk to {1, 2}
.As shown in the first example, you’ll need to use #include<vector>
if you want to have access to the vector
library.
Conceptually, vectors of vectors (I’m gonna call them 2D vectors
from here on out) are basically just a “table” Like a multiplication table, or even a graph. You can think of it as a 2-dimensional grid that can be indexed the same way you would refer to a coordinate in math. So if you have a 2D vector
, each with 10 elements, then if you wanted the last element in the table, you’d access it like v[9][9]
. The mathemtical equivalent would be (9, 9)
, where the layout is (x, y)
. If you wanted the element on the 2nd row, first column, you’d access it like v[1][0]
. It goes [ROWS][COLS]
. To declare a 2D vector
, you do
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<vector>
using namespace std;
int main() {
// declare 2d vector
vector <vector <int>> v;
// initialize 2d vector
v.resize(10); // create 10 rows
for (int i = 0; i < v.size(); i++) {
v[i].resize(10, 0); // add 10 columns to each row
}
}
I’ve covered file streams pretty extensively in the Lab 5 writeup, so I’ll cover it briefly here since you can just refer to the notes there for more detail if needed
Similar to cout
/cin
, a file stream is a way to interpret a file e.g. ticket.txt
as a stream, which will allow you to process it for input or output.
Again, exactly the same as cin
/cout
, we read from a file using ifstream fin
and we write to a file using ofstream fout
. The only difference is we first have to open a file before we can read/write, AND we must close that file as well
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<fstream>
using namespace std;
int main() {
// you can call these whatever you want, but we usually use "fin" & "fout"
ifstream fin;
ofstream fout;
// have to open
fin.open("ticket.txt");
fout.open("output.txt");
// read from ticket.txt word by word
fin >> word1 >> word2 >> etc...
// write to output.txt
fout << "we just read: " << word1 << word2 << etc...
// MUST close
fin.close();
fout.close();
}
To use file streams, include the file stream library #include<fstream>
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<fstream>
using namespace std;
int main() {
ifstream fin;
// if we can't open the file, exit program
if (!fin.open("foo.txt")) {
cout << "Could not open foo.txt!\n";
return 1;
}
// otherwise do stuff...
}
}
I haven’t gone over getline before, but it’s pretty self explanatory. It takes two arguments: the stream you’ll read from, and the destination you’ll write to. (getline(src, dest)
). It’s different from cin
, but it works the same in that A. it can read from stdin
(standard input) and B. it can be used the same way in if statements, while loops, etc.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<string>
using namespace std;
int main() {
// line buffer
string line;
// read lines until we can't anymore
while (getline(cin, line)) {
// print each line
cout << line << '\n';
}
}
getline()
will read a line until it reaches its “delimiter” (a char
at which it’ll stop) which is \n
by default. It’ll discard that delimiter from the stream once read (so it won’t be left in the buffer), but it will not include it in the string it reads. So when you read a line, it will not be ended with a \n
, hence why I output cout << line << '\n';
.
argv/argc are implicitly defined variables that are arguments to int main()
. They hold the “command-line arguments” and the number of command-line arguments, respectively.
A command-line argument is anything that comes after you do ./program
. So in ./program one two
, there are 2 command-line args. one
& two
.
argc
is an int
that is equal to the number of command-line arguments used, and argv
is an array of c-style strings that contains the actual command-line arguments. in C++, there is always at least one command-line argument which is the name of the program itself.
running ./program one two
, argc == 3
and argv == {"program", "one", "two"}
. Notice how although we technically only used two command-line args, argc
is equal to three. That’s what I mean when I say there is always at least one command-line argument, which is the name of the program being ran. In this case program
. This is also evident in argv
, as argv[0]
is the name of the program. This is always true.
running ./program
, argc == 1
and argv == {"program"}
. Hopefully that makes sense.
Now you’re probably asking why these are useful. It’s pretty niche for you right now, but it’s nice to be able to run your programs with “input” without really inputting it yourself over and over, if you’re trying to debug your code for instance. Often times your program will accept a variety of command-line args which will change the behavior of your program based on what they are. E.g. if you have a program that reads a variable number of lines, it’d be nice to choose how many lines are read from command-line arguments instead of inputting it every time and reading it with cin
or something.
./read-lines 10
would read 10 lines, ./read-lines 1
would read just 1 line. You get the point.
Argc
stands for argument count, and argv
stands for argument values.
argc
is type int
, and argv
is type char**
or char*[]
(both the same thing). char*[]
just means an array (signified by the []
) of char*
, which you can just think of as “C-style” strings for now. So it’s an array of C-style strings. It’s important to note that a C-style string is not the same thing as when you use string
in C++. They’re different. A string
is similar to char*
, but they are very different in practice.
Also note that even if you have a program ./read-lines 100
, the command-line argument 100
will ont be treated as a number, It is read as a C-style string, so it’s stored as "100"
. You need to do the work yourself to convert it to a number, if necessary.
I went over this already, but to reiterate, argc
contains the number of commmand-line args, and argv
contains the actual command-line args themselves. It’s kinda like a scale. When you step on a scale, it shows your weight in lbs (argc
), but YOU’RE what makes up that amount (argv
).
Also note that argv[0]
will always contain the program name. Thus, even if there are no command-line args passed (e.g. ./myprogram
), argc
will be equal to 1, and argv[0]
will be "myprogram"
.
Like I mentioned in the What are the types of each one, argv
holds strings. (C-style, but I’m not gonona keep typing that). So even if you use a number as a command-line arg, it’ll be stored as a string. So if you want to convert it to a number (which you will 99% of the time), then the easy way to do it is with stringstream
’s.
./program one two
- argc == 3
& argv == {"program", "one", "two"}
./foo 1 two 3
- argc == 4
& argv == {"foo", "1", "two", "3"}
./bar
- argc == 1
& argv == {"bar"}
main()
1
2
3
int main(int argc, char*[] argv) {
// you define the arguments EXACTLY as they are up there.
}
argc
Say we have a program that reads how ever many lines we specify via the command-line. But maybe we don’t want the program to run unless someone specifies the line amount. You would implement argc
error checking to achieve this
1
2
3
4
5
6
7
8
9
int main(int argc, char*[] argv) {
// if there aren't two command-line args (the name and line number),
// then exit the program
if (argc != 2) {
cout << "usage: ./read-lines [n]\n";
return 1;
}
}
Again, another stream-type. A lot of students get tripped up with string streams, but they are just a way to interpret strings
as streams. Just like fstream
lets you interpret files as streams. A stream is just a medium for us to fluidly read/write things in C++. The most common use cases for string streams are to either convert from strings to numbers easily, (or numbers to strings), or to “parse” a line from getline
into individual words.
To use the string stream library, you must #include<sstream>
istringstream
/ostringstream
objectJust like fstream
, you have to create an input stream, and output stream. istringstream
is the input string stream type, and ostringstream
is the output string stream type.
You use declare and use an input string stream like so
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<sstream>
using namespace std;
int main() {
// declare istringstream with a string already opened
string my_string = "Parse me!";
istringstream iss(my_string);
string word;
// "extract" & print every word from the string we're parsing
while (iss >> word) {
cout << word << '\n';
}
}
If you recall back to COSC 101, you’ll remember the concept of a constructor. When I declare the istringstream
above, I pass the constructor a string my_string
to initialize the stream it’s going to extract from (istringstream iss(my_string)
). You can also just declare an empty istringstream
and initialize it later using the str()
method.
1
2
3
4
5
6
7
8
9
10
11
12
#include<sstream>
using namespace std;
int main() {
// declare istringstream with a string already opened
string my_string = "Parse me!";
istringstream iss(); // use default constructor which leaves stream empty
// initialize stream using .str()
iss.str(my_string);
}
str()
can also be used to change whatever the stream is for that string stream. So if you wanted to start extracting from a different string, you would also use str()
1
2
3
4
5
6
7
8
9
10
11
12
#include<sstream>
using namespace std;
int main() {
// declare istringstream with a string already opened
string my_string = "Parse me! 25";
istringstream iss(my_string);
// change what the stream is for our istringstream
string pick_me = "Pick me!";
iss.str(pick_me);
}
ostringstream
is defined similarly, except .str()
has different behavior. ostringstream
is like the StringBuilder
class from Java. It allows you to fluidly “build” a string from words.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<sstream>
using namespace std;
int main() {
ostringstream string_builder;
// build a string
string_builder << "Hello, my name is " << name << " and I am " << age << " years old!\n";
/* create that string */
string final_string = string_builder.str();
}
The above code looks very similar to printing something with cout
, but it’s building a string and then copying that string to an actual variable (final_string
) So instead of outputting all of that to stdout
, it writes it to the ostringstream
buffer, and we extract all of it into a string using string_builder.str()
. The use case for ostringstream
is a bit more limited for you guys at the moment, but that’s how you can use it.
1
- Converting string to number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<sstream>
using namespace std;
int main() {
// holds our number
int number;
// our string we're gonna convert
string strNumber = "10";
// initialize istringstream with the string "10"
istringstream iss(strNumber);
// extract "10" and place it in number variable
iss >> number;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<sstream>
#include<string>
using namespace std;
int main() {
// holds our line
string line;
// holds our words we'll extract from each line
string word1, word2, word3;
// read every line from stdin
while (getline(cin, line)) {
istringstream iss(line);
// extract words from line to word1, 2, and 3
iss >> word1 >> word2 >> word3;
}
}
If you have any questions, feel free to ask on the Discord or DM me directly via whichever way you want. I’ll be up all night to answer your midterm-related questions!