What We Will Cover
SI survey (Carter)
Continuations
Midterm feedback
0, 1, 2, 3, 4, 5
double myarray[6] = { 0, 1, 2, 3, 4 };
Constructors are function calls
Homework Questions?
Last Day to Withdraw Approaching
- Last day to drop a full-term section with a grade of "W": 4/28/2018
- See Admissions and Records: Dates and Deadlines
- Grade calculation "what-if" available in Canvas Grades
^ top
12.1: Reading Data From Files
Learner Outcomes
At the end of the lesson the student will be able to:
- Read data of various types from a file
- Check for end-of-file conditions
- Read data from a file in a loop
|
^ top
12.1.1: Reviewing Files and IO
Procedure For File I/O
- Place the following include directives in your program file:
#include <fstream> // for file I/O
#include <iostream> // for cout
#include <cstdlib> // for exit()
using namespace std;
- Declare names for input and output streams like:
ifstream fin;
ofstream fout;
- Connect each stream to a file using
open() and check for failure:
fin.open("infile.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
fout.open("outfile.txt");
if (fout.fail()) {
cout << "Output file failed to open.\n";
exit(-1);
}
- Read or write the data:
- Read from a file with
fin like using cin :
double first, second, third;
fin >> first;
fin >> second;
fin >> third;
- Write to a file with
fout like using cout :
fout << "first = " << first << endl;
- Close the streams when finished reading and writing:
fin.close();
fout.close();
Streams are Objects
- Notice that streams are objects and have functions associated with them
open() : connect a stream to a file
close() : disconnect a stream from a file
fail() : returns true if the stream has an error
good() : returns true if the stream is error free
- Most of the functions are available using either file streams or
cin and cout
^ top
12.1.2: Using Loops to Read Files
- Sometimes we do not know how many data items are in a file
- To solve this, the typical approach is to use a loop to process the file
- We read from the file until we reach the end of the file or encounter an error
- The basic IO stream library has several functions we may call
Commonly Used IO Stream Functions for Error Detection
Name |
Description |
eof |
Returns true if the stream has reached end-of-file. |
fail |
Returns true if an error has occurred on the stream, including reaching the end of file. |
good |
Returns true if the most recent I/O operation on the stream completed successfully. |
bad |
Returns true if a non-recoverable error has occurred on the stream. |
Testing for End of File with Functions
Testing for End of File with the >> Operator
Example Program Reading a File Using a Loop and the >> Operator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <fstream> // for file I/O
#include <iostream>
#include <cstdlib>
using namespace std;
int main() {
ifstream fin;
fin.open("infile.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
double nextNum, sum = 0;
int count = 0;
while (fin >> nextNum) {
cout << "Read: " << nextNum << endl;
sum = sum + nextNum;
count++;
}
cout << "average = " << (sum / count) << endl;
fin.close();
return 0;
}
|
Try It: Read a File with a Loop (5m)
- Copy the following program into a text editor, save it as
readwrite.cpp , and then compile and run the starter program to make sure you copied it correctly.
#include <iostream>
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
using namespace std;
int main() {
// Enter code here
return 0;
}
- Save the file rawdata.txt to the same directory as your program source code.
We will read from this file after writing our program.
- Add a function with the following prototype to your program.
void readData();
- In
main() , add code to call the new function. Compile your code to verify you added the function and function call correctly.
- In
readData() , add code to declare an input stream named fin and to connect the stream to the input file "rawdata.txt". In addition, make sure you check for failure after calling open() .
For more information see the section Procedure For File I/O.
- First we read from the file using the following loop code:
double nextNum;
while (fin.good()) {
fin >> nextNum;
if (fin.good()) {
cout << "Read: " << nextNum << endl;
}
}
- Add a statement after the above to close the input stream.
- Compile and run your code, then verify you see output like the following:
Read: 12.94
Read: -9.87654
Read: 2.3131
Read: -89.506
Read: 12.3333
Read: 92.8765
Read: -123.457
If you have problems, ask a classmate or the instructor for help as needed.
- Now change the loop code to read using the
>> operator inside the test condition:
while (fin >> nextNum) {
cout << "Read: " << nextNum << endl;
}
If you have problems, ask a classmate or the instructor for help as needed or click here .

- Save your source code file as we will be adding to it in the next Try It.
- When finished, please help those around you. Then be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- When the number of file data items is unknown you can use a ________ statement to read all the data.
- When successfully reading data using
fin >> nextValue , the input stream returns a value interpreted as ________.
- The problem with the following code is ________.
double total = 0;
while (fin >> nextValue) {
total += nextValue;
}
- you cannot read data in a test condition
nextValue should be nextNum
- there is no way to exit the loop
- nothing
^ top
12.1.3: Reading Files using getline()
- We can read text using a loop and an input stream like
fin :
ifstream fin;
// open stream and test for failure code omitted
string word;
while (fin >> word) {
cout << word << endl;
}
- However, just like with
cin , there are complications when you want to read text with spaces
- Operator
>> skips whitespace, reads characters and stops when encountering more whitespace
- Thus, we only get a single word for each input variable
- If we want to read a complete line of text like
"Hello Mom!" , we need to use getline()
- For example:
ifstream fin;
// open stream and test for failure code omitted
string line;
while(fin.good()) {
getline(fin, line);
if (fin.good()) {
cout << line << endl;
}
}
Using Loops with getline()
Example Program Reading a File Using getline() in a Loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <fstream> // for file I/O
#include <iostream>
#include <cstdlib>
using namespace std;
int main() {
ifstream fin;
fin.open("rawdata.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
string line;
int count = 1;
while(getline(fin, line)) {
cout << "Line " << count << ": " << line << endl;
count++;
}
fin.close();
return 0;
}
|
Check Yourself
- True or false: Using the >> operator with string variables only reads one word from a file stream at a time.
- To read strings containing multiple words use the ________ function.
- True or false: the following code reads input one word at a time from the file input stream named
fin .
string str;
while (fin >> str) {
cout << str << endl;
}
- True or false: the following code reads input one line at a time from the file input stream named
fin .
string str;
while (getline(fin, str)) {
cout << str << endl;
}
- True or false: when all data has been read from the file associated with
fin , both of the above loops end.
^ top
12.1.4: Reading Mixed Data Types
Example Program Reading Mixed Data Types in a Loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include <fstream> // for file I/O
#include <iostream> // for cout
#include <cstdlib> // for exit()
using namespace std;
int main() {
string name;
double price;
ifstream fin("products2.txt");
if (fin.fail())
{
cout << "Input file failed to open.\n";
exit(-1);
}
while (fin.good())
{
fin >> ws; // clear whitespace including newlines
getline(fin, name);
fin >> price;
if (fin.good()) {
cout << name << " @ " << price << endl;
}
}
fin.close();
return 0;
}
|
Check Yourself
- True or false: before you switch from using the >> operator to using
getline() , you must clear all newline characters from the input buffer.
- To clear all whitespace, including newlines, from the input buffer use: ________.
^ top
12.1.5: Reading File Data into a Vector
- Sometimes you want to process the data in a file several times
- One way to do this is to load the data into a vector
- Then you can process the data as a list
- Processing a list multiple times is faster than reading from a file multiple times
- The following example shows how to read from a file into a vector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
#include <fstream>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
int main() {
ifstream fin;
fin.open("infile.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
// Load data into a vector
vector<int> data;
int value;
while(fin >> value) {
cout << "Read: " << value << endl;
data.push_back(value);
}
fin.close();
// Process vector data
double sum = 0;
int count = data.size();
for (int i = 0; i < count; i++) {
sum = sum + data[i];
}
cout << "average = " << (sum / count) << endl;
return 0;
}
|
Try It: Load Data into a Vector (6m)
- Open your
readwrite.cpp from the last Try It.
- Include the following library at the top of the program source code file:
#include <vector>
- Inside the
readData() function parenthesis, add the following parameter:
void readData(vector<double>& data);
- In
main() before the function call, declare a vector of type double named data . Then add the data variable as an argument to the readData() function like:
readData(data);
- Inside the
while -loop braces { } , add the following statement to save the values read from the input file into the vector.
data.push_back(nextNum);
- At the end of
readData() , add a statement to close the input file.
For more information see the section Procedure For File I/O step 5.
- In
main() after the function call, add the following cout statement:
cout << "Vector data:\n";
- After the above statement, add a
for -loop to display all the elements of the vector.
- Compile and run your code to verify it works correctly. When run, you should see the data from the file displayed twice like:
Read: 12.94
Read: -9.87654
Read: 2.3131
Read: -89.506
Read: 12.3333
Read: 92.8765
Read: -123.457
Vector data:
12.34
-9.87654
2.3131
-89.506
12.3333
92.8765
-123.457
If you have problems, ask a classmate or the instructor for help as needed or click here .

- Save your source code file as we will be adding to it in the next exercise.
- When finished, please help those around you. Then be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- When you need to process the data in a file several times, it is faster to load the data into a(n) ________.
- To add data to the end of a vector, call the vector function ________.
- To discover how many data items were read from a file into a vector, call the vector function ________.
^ top
12.1.6: Writing File Data from a Vector
Example Program Writing a Vector to a File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#include <iostream> // for cout
#include <iomanip> // for setprecision
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
#include <vector>
using namespace std;
void writeData(const vector<double>& data) {
ofstream fout;
fout.open("outfile.txt");
if (fout.fail()) {
cout << "Output file failed to open.\n";
exit(-1);
}
fout << fixed << setprecision(2);
for (unsigned i = 0; i < data.size(); i++) {
fout << setw(10) << right << data[i] << endl;
}
fout.close();
}
int main() {
// C++0x vector initializer list
vector<double> data = { 12.94, -9.87654, 2.3131, -89.506, 12.9333,
92.8765, -123.457, 42 };
writeData(data);
cout << "Done writing data to outfile.txt...\n";
return 0;
}
|
Check Yourself
- Usually the best loop statement for writing the data from a vector to a file is the ________.
for -loop
while -loop
do-while -loop
repeat -loop
- When accessing every member of a vector named
data , we use a for -loop with the test condition ________ function.
i < data.size()
i > data.size()
i <= data.size()
i >= data.size()
- For the following loop accessing a vector, the missing piece is ________.
for (unsigned i = 0; i < data.size(); i++) {
cout << setw(10) << right << _____ << endl;
}
^ top
Exercise 12.1: Reading Files with Loops (5m)
In this exercise we explore reading all the lines of a file using a loop.
Specifications
- If you have not already created the
readwrite.cpp file, complete the following exercises:
- Try It: Read a File with a Loop
- Try It: Load Data into a Vector
- Add an include for the
iomanip library:
#include <iomanip> // for setprecision
- Add a function with the following prototype to your file.
void writeData(const vector<double>& data);
- Call the
writeData() function from main() just before return 0 .
- In
writeData() , add code to declare an output stream named fout and to connect the stream to the output file "neat.txt". In addition, make sure you check for failure after calling open() .
For more information see the section Procedure For File I/O.
- After opening the output file, add the following code:
fout << fixed << setprecision(2);
for (unsigned i = 0; i < data.size(); i++) {
fout << setw(10) << right << data[i] << endl;
}
- Add a statement after the above to close the output stream.
- Compile and run your code to verify it works correctly. When run, you should see the following data in the file neat.txt:
12.34
-9.88
2.31
-89.51
12.33
92.88
-123.46
If you have problems, ask a classmate or the instructor for help as needed.
- Save your program source code file to submit to Canvas as part of assignment 12.
Listing of readwrite.cpp
When finished developing your code, click here to compare your solution to mine. After you have completed your own solution, reviewing another is often helpful in learning how to improve your programming skills.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#include <iostream>
#include <iomanip> // for setprecision, setw
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
#include <vector>
using namespace std;
void readData(vector<double>& data) {
ifstream fin;
fin.open("rawdata.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
double nextNum;
while (fin >> nextNum) {
cout << "Read: " << nextNum << endl;
data.push_back(nextNum);
}
fin.close();
}
void writeData(const vector<double>& data) {
ofstream fout;
fout.open("neat.txt");
if (fout.fail()) {
cout << "Output file failed to open.\n";
exit(-1);
}
fout << fixed << setprecision(2);
for (unsigned i = 0; i < data.size(); i++) {
fout << setw(10) << right << data[i] << endl;
}
fout.close();
}
int main() {
vector<double> data;
readData(data);
cout << "Vector data:\n";
for (unsigned i = 0; i < data.size(); i++) {
cout << data[i] << endl;
}
writeData(data);
return 0;
}
|
^ top
12.1.7: Summary
- We can use the extraction operator to read data from a file:
ifstream fin;
// ... more code here
double num;
fin >> num;
cout << "Read data: " << num << endl;
- The variable
fin is a stream object and works just like cin
- Like with
cin , we use the getline() function to read complete lines of text with spaces between words:
ifstream fin;
// ... more code here
string line;
getline(fin, line);
cout << "Read data: " << line << endl;
- Sometimes we do not know how many lines are in a file
- To solve this, the typical approach is to use a loop to process the file:
while(fin >> next) {
// process input
}
- We can read entire lines in a loop as well:
while(getline(fin, line)) {
// process input
}
- In addition, we can read file data into vectors
vector<int> data;
int value;
while(fin >> value) {
data.push_back(value);
}
Check Yourself
As time permits, be prepared to answer these questions. You can find more information by following the links after the question.
- After opening a stream named
fin , what code will read the first item in the file into the variable named first ? (12.1.1)
- What functions can be used to detect an end-of-file condition? (12.1.2)
- What control-flow statement do you use to read every number in a file when you do not know how many numbers are in the file? (12.1.2)
- What is a loop condition you can use to read every number in a file? (12.1.2)
- How do you code a statement to read from a file a line of text that includes spaces? (12.1.3)
- What statement will clear all whitespace, including newlines, from a stream buffer? (12.1.4)
- What is the advantage of reading file data into a vector? (12.1.5)
- What is a loop condition you can use to read every line in a file? (12.1.5)
- What is the code to write to a file from a vector? (12.1.6)
^ top
12.2: More I/O Topics
Learner Outcomes
At the end of the lesson the student will be able to:
- Append data to files
- Code user input as file names
|
^ top
12.2.1: Alternative Syntax for Open
- It is possible to construct a stream and open a file in one step
- Rather than:
ofstream fout;
fout.open("outfile.txt");
if (fout.fail()) {
cout << "Output file opening failed.\n";
exit(-1);
}
- We can use:
ofstream fout("outfile.txt");
if (fout.fail()) {
cout << "Output file opening failed.\n";
exit(-1);
}
- We can do the same with input streams:
ifstream fin("infile.txt");
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
Check Yourself
- True or false: the following two lines of code are equivalent.
fout.open("messages.txt");
ofstream fout("messages.txt");
- Enter the code that combines the following two lines of code into one statement.
ofstream outputStream;
outputStream.open("myfile.txt");
^ top
12.2.2: Appending to a File
- The standard open operation for writing begins with an empty file
- Even if the file exists we loose all the contents
- To prevent loosing the information, we must open for appending to a file:
ofstream fout;
fout.open("important.txt", ios::app);
- If the file does not exist then
ofstream creates it
- If the file exists then
ofstream positions itself to append to the end
- The second argument is a constant defined in class
ios
- As without appending, the two lines can be combined into one statement like:
ofstream fout("important.txt", ios::app);
- The following program will add a message to the file every time we run it
Example Appending to a File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <fstream>
#include <iostream>
#include <cstdlib>
using namespace std;
int main() {
string message;
cout << "Enter a message to add to the file: ";
getline(cin, message);
ofstream fout("messages.txt", ios::app);
if (fout.fail()) {
cout << "Output file opening failed.\n";
exit(-1);
}
fout << message << endl;
fout.close();
cout << "See you later!\n";
return 0;
}
|
Check Yourself
- True or false: opening a file for writing deletes the existing contents by default.
- The argument for appending to a file is ________.
^ top
12.2.3: File Names as Strings
- C++ stores its strings in a kind of
char array known as a C-string
- A C-string is a hold over from the C programming language
- However, many library functions still use C-strings and NOT
string variables
- For example, the argument to
open() in C++98 is a C-string:
fin.open("infile.txt");
- Thus if we want to use a
string variable for a file name we need to convert the variable to a C-string
- To convert to a C-string, we call the
c_str() member function
fin.open(filename.c_str());
- Similarly, we can use a string variable with a stream overloaded constructor
ifstream fin(filename.c_str());
- Starting in C++11, this problem is fixed
- By default, g++ (Cygwin) uses C++98
- If we want to use C++11 then we must add the following extra option when compiling
g++ -Wall -Wextra -Wpedantic -std=c++11 -o programName sourceFile.cpp
- The other option is to use the
c_str() function to convert a string to a C-string
- Here is an example program using a
string with c_str() for file names
Example Using a string for File Names
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <iostream>
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
using namespace std;
int main() {
string filename, line;
cout << "Enter a file name: ";
cin >> filename;
ifstream fin(filename.c_str());
if (fin.fail()) {
cout << "Input file " << filename << "failed to open.\n";
exit(-1);
}
while(getline(fin, line)) {
cout << line << endl;
}
fin.close();
return 0;
}
|
Try It: Open with a String File Name (7m)
- Copy the following program into a text editor, save it as
filelist.cpp , and then compile and run the starter program to make sure you copied it correctly.
#include <iostream>
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
using namespace std;
int main() {
// Enter code here
return 0;
}
- Save the file products.txt to the same directory as your program source code.
We will read from this file after writing our program.
- Write a function with the following signature:
void readFile(string filename);
- Inside the function
readFile() , add code to declare an input stream named fin and to connect the stream to the input file using the string fileName parameter. Make sure you connect the stream without calling open() (see lesson 12.2.1).
- Add the following code to function
readFile() that reads all the values from the input file.
while (fin.good()) {
string name;
double price;
fin >> ws; // clear whitespace including newlines
getline(fin, name);
fin >> price;
if (fin.good()) { // verify not end-of-file
cout << name << " @ " << price << endl;
}
}
- Add a statement to close the input stream.
In main() , call the readFile() function with:
readFile("products.txt");
- Compile and run your code, then verify you see output like the following:
Milk@3.95
Bread@2.99
Cheese@3.95
If you have problems, ask a classmate or the instructor for help as needed.
- Save your source code as we will add to it in a future exercise.
- When finished, please help those around you.
Check Yourself
- True or false: the file stream open function requires a C-string parameter and will not work with type
string .
- To convert a
string to a C-string, use the string member function ________.
- In the above Try It!, the reason for the last output shown below is ________.
Read: @3.95
- there were four products in
products.txt
- One of the products did not have a name
- while reading
name , fin reached the end of the file
- the computer does strange things at odd times
- To fix the problem in the previous question, we can ________.
- remove the extra product
- add the missing product name
- test for stream failure before
cout
- buy a new computer
^ top
12.2.4: Stream Parameters
- Stream types can be formal parameters in functions
- Here is an example of an
ifstream parameter:
string readLine(ifstream& aStream) {
string line;
getline(aStream, line);
return line;
}
- Similarly, we can have
ofstream parameters:
void writeLine(ofstream& aStream, string line) {
aStream << line << endl;
}
- Notice that both streams are call-by-reference parameters
- The reason is that the file stream object changes as the file is processed
- The changes must be recorded in the file stream object to keep the file synchronized properly
Example Using Stream Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#include <iostream>
#include <fstream> // for file I/O
#include <cstdlib> // for exit()
using namespace std;
/**
Reads a line from the istream.
@param aStream the output stream.
@return The line of text read.
*/
string readLine(ifstream& aStream) {
string line;
getline(aStream, line);
return line;
}
/**
Writes a line to the ostream.
@param aStream the output stream.
@param line The string to output.
*/
void writeLine(ofstream& aStream, string line) {
aStream << line << endl;
}
int main() {
ifstream fin("infile.txt");
if (fin.fail()) {
cout << "Input file opening failed.\n";
exit(-1);
}
ofstream fout("outfile.txt");
if (fout.fail()) {
cout << "Output file opening failed.\n";
exit(-1);
}
while (fin.good()) {
string line = readLine(fin);
if (fin.good()) {
writeLine(fout, line);
}
}
fin.close();
fout.close();
cout << "Done copying file...\n";
return 0;
}
|
Testing the Stream
Check Yourself
- True or false: file streams can be passed to function parameters.
- True or false: file stream arguments must always to passed by reference.
- If an I/O operation is successful, the stream member function
good() returns ________
true
false
- indeterminate
- nothing,
good() is a void function
^ top
Exercise 12.2: Functions with Stream Parameters (5m)
In this exercise we explore passing an input stream to a function.
- Open
filelist.cpp from the last Try It.
- Write a function with the following signature:
void read(ifstream& fin);
- Move the following code from the inside the
while -loop of readFile() into the read() function.
string name;
double price;
fin >> ws; // clear whitespace including newlines
getline(fin, name);
fin >> price;
if (fin.good()) { // verify not end-of-file
cout << name << " @ " << price << endl;
}
- Inside the
while -loop of readFile() , call the read() function. When finished the while -loop of readFile() should look like this:
while (fin.good()) {
read(fin);
}
- Compile and run your code to verify it still works the same. When run, you should see the data from the file displayed twice like:
Milk @ 3.95
Bread @ 2.99
Cheese @ 3.95
If you have problems, ask a classmate or the instructor for help as needed.
- Save your program source code file to submit to Canvas as part of assignment 12.
- When finished please help those around you.
^ top
12.2.5: Other File Operations
- Some file operations are not supported by C++ streams
- In these cases, we use the C-style functions
- These functions are part of the
cstdio library:
#include <cstdio>
- Note that these functions take C-string arguments
- Thus we must use the
c_str() function when using string variables
Commonly Used C-Functions for File Manipulation
Function |
Description |
remove(fileName) |
Deletes the file specified by the C-string fileName . |
rename(oldName, newName) |
Changes the file or directory name specified by the C-string oldName to the newName . |
perror(message) |
Print the error message specified by the C-string along with the system error message. |
system(command) |
Executes a command specified by the C-string like you were using the command line. |
Example Code to Remove a File
string fileName;
cout << "File to remove: ";
cin >> fileName;
int result = remove(fileName.c_str());
if (result == 0) {
cout << "File successfully removed\n";
} else {
perror("Error removing file\n");
}
Example Code to Rename a File
int result;
string oldName, newName;
cout << "Old file name: ";
cin >> oldName;
cout << "New file name: ";
cin >> newName;
result = rename(oldName.c_str(), newName.c_str());
if (result != 0 ) {
perror( "Error renaming file" );
}
Example Code Using the system() Function
system("ls"); // list files
system("clear"); // clear the screen
system("cmd.exe /c color 1E"); // change colors in Windows terminal
Changing Console Colors
- Since we are running Cygwin on Windows, we can use Windows to change the console colors
- The Windows command function is a program named
cmd
- You can set the console colors using the COLOR command using:
system("cmd.exe /c color attr");
- Where attr is TWO hex digits
- The first specifies the background
- The second specifies the foreground
- Each digit can be any of the following values:
0 = Black 8 = Gray
1 = Blue 9 = Light Blue
2 = Green A = Light Green
3 = Aqua B = Light Aqua
4 = Red C = Light Red
5 = Purple D = Light Purple
6 = Yellow E = Light Yellow
7 = White F = Bright White
Check Yourself
- True or false: we need to use C functions for some file operations.
- The function to delete a file is ________.
- The function to rename a file is ________.
^ top
12.2.6: Summary
- We can construct a stream and open it in one step:
ofstream fout("messages.txt");
- The standard open operation will create an empty file
- We can open a file for appending data by using an extra argument:
ofstream fout("important.txt", ios::app);
- We may use
string variables as the names of files
- However, we must use the
c_str() function to convert to a C-string:
ifstream fin(filename.c_str());
- Streams can be arguments to a function, but we must use call-by-reference
- Type
istream for function parameters works for both cin and ifstream
- Type
ostream for function parameters works for both cout and ofstream
Check Yourself
As time permits, be prepared to answer these questions. You can find more information by following the links after the question.
- How can you construct and open a file stream in one statement? (12.2.1)
- How do you keep a file opened for writing from destroying existing information? (12.2.2)
- What string function do you use to convert a
string to a C-string? (12.2.3)
- How do you code streams as parameters for a function? (12.2.4)
- To delete the file named "error.log", what statement would you write? (12.2.5)
- To change the name of the file named "error.log" to "error.bak", what statement would you write? (12.2.5)
^ top
12.3: Working with Objects and Files
Learner Outcomes
At the end of the lesson the student will be able to:
- Code stream parameters in functions
- Pass objects to functions
- Return objects from functions
|
^ top
12.3.1: Revisiting Objects and Classes
- Previously we discussed how to code classes to create objects
Objects
- Recall that an object in C++ is a location in memory containing a structured collection of variables and functions defined by its class
- As an example, here is a "product" object in memory:
- The example object has two pieces of data, a name and a price, structured one after the other in memory
- In addition, the object has access to associated functions
- To define the data structure of objects, we write a class
Classes
Information Hiding
- Remember that we always code our class member variables as
private
- The keyword
private restricts access to member functions only
- Keeping member variables
private is important so we can make design changes
Object Interface
Constructing Objects
Constructor Functions
- A constructor is a special type of function whose purpose is to initialize member variables
- Whenever an object is created from a class, a constructor is always called automatically
- A default constructor must set the member variables to default values:
Product::Product() {
name = "none";
price = 0.0;
}
- Even though we should always code a default constructor, it is convenient to code other constructors like:
Product::Product(string newName, double newPrice) {
setName(newName);
setPrice(newPrice);
}
- This lets us construct an object and initialize data members at the same time:
Product milk("Low fat milk", 3.95);
- When we are done, we have a modular grouping of variables and functions
Example Program with a Class Declaration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
#include <iostream>
using namespace std;
class Product {
public:
Product();
Product(string newName);
Product(string newName, double newPrice);
double getPrice() const;
void setPrice(double newPrice);
void read();
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "Unknown";
price = 0.0;
}
Product::Product(string newName) {
name = newName;
price = 0.0;
}
Product::Product(string newName, double newPrice) {
name = newName;
price = newPrice;
}
double Product::getPrice() const {
return price;
}
void Product::setPrice(double newPrice) {
price = newPrice;
}
void Product::read() {
cout << "Enter the name of the product: ";
cin >> ws;
getline(cin, name);
cout << "Enter the price for a " << name << ": ";
cin >> price;
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
// For testing
int main() {
Product milk;
Product bread("Rye Bread");
bread.setPrice(2.99);
Product cheese("Cheddar", 6.75);
cout << "Enter the milk product data\n";
milk.read();
milk.print();
bread.print();
cheese.print();
double price = milk.getPrice();
cout << "The current price of milk is $" << price << endl;
cout << "Enter the new price: ";
double newPrice = 0;
cin >> newPrice;
milk.setPrice(newPrice);
milk.print();
return 0;
}
|
Check Yourself
- True or false: a class contains (encapsulates) both variables and functions.
- To allow only member functions and constructors of an object to access a member variable, use the keyword ________.
- True or false: good programming practice is to set the accessibility of all member variables to
private .
- Public functions are the ________ of a class.
- True or false: the purpose of a constructor is to initialize all the member variables.
^ top
12.3.2: Returning Objects from Functions
Returning an Object: productapp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
#include <iostream>
#include <vector>
using namespace std;
class Product {
public:
Product();
Product(string newName, double newPrice);
string getName() const;
double getPrice() const;
void setName(string newName);
void setPrice(double newPrice);
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "none";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
setName(newName);
setPrice(newPrice);
}
string Product::getName() const {
return name;
}
double Product::getPrice() const {
return price;
}
void Product::setName(string newName) {
name = newName;
}
void Product::setPrice(double newPrice) {
price = newPrice;
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
// Function that returns an object
Product makeProduct();
// For testing class Product
int main() {
vector<Product> store;
char repeat = 'Y';
while ('Y' == repeat || 'y' == repeat) {
cout << "Enter product data:\n";
Product temp = makeProduct();
store.push_back(temp);
cout << "You entered:"
<< "\n Name: " << temp.getName()
<< "\n Price: " << temp.getPrice()
<< endl;
cout << "Enter another product? (y/n): ";
cin >> repeat;
}
cout << "\nAll your products:\n";
for (unsigned i = 0; i < store.size(); i++) {
store[i].print();
}
return 0;
}
Product makeProduct() {
string name;
cout << "Product name: ";
cin >> name;
double price;
cout << "Price for a " << name << ": ";
cin >> price;
Product newProd(name, price);
return newProd;
}
|
Check Yourself
- True or false: objects can be returned from functions.
- True or false: objects are always returned from functions by reference.
- True or false: the only way to return an object from a function is by using a
return statement.
^ top
12.3.3: Passing Objects to Functions
- Class types can be function parameters and we may pass objects to functions
- We may pass objects by value or by reference
- However, usually we pass objects by reference because it requires less work for the computer
- As an example, let us write a function to compare the price of two products
- One way we can write the function is as a non-member function
- For example:
bool isHigherPrice(Product& prod1, Product& prod2) {
if (prod1.getPrice() > prod2.getPrice()) {
return true;
}
return false;
}
- To call the function we need two arguments like:
if (isHigherPrice(prod1, prod2)) {
cout << prod1.getName() << " costs more\n";
} else {
cout << prod2.getName() << " costs more\n";
}
- We revise our
productapp.cpp file to add this function as shown below
Revised productapp.cpp with Class Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
#include <iostream>
using namespace std;
class Product {
public:
Product();
Product(string newName, double newPrice);
string getName() const;
double getPrice() const;
void setName(string newName);
void setPrice(double newPrice);
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "none";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
setName(newName);
setPrice(newPrice);
}
string Product::getName() const {
return name;
}
double Product::getPrice() const {
return price;
}
void Product::setName(string newName) {
name = newName;
}
void Product::setPrice(double newPrice) {
price = newPrice;
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
// Function with Product parameters
bool isHigherPrice(Product& prod1, Product& prod2);
// Function that returns an object
Product makeProduct();
// For testing class Product
int main() {
cout << "Enter the first product:\n";
Product prod1 = makeProduct();
cout << "Enter the second product:\n";
Product prod2 = makeProduct();
if (isHigherPrice(prod1, prod2)) {
cout << prod1.getName() << " costs more\n";
} else {
cout << prod2.getName() << " costs more\n";
}
return 0;
}
bool isHigherPrice(Product& prod1, Product& prod2) {
if (prod1.getPrice() > prod2.getPrice()) {
return true;
}
return false;
}
Product makeProduct() {
string name;
cout << "Product name: ";
cin >> name;
double price;
cout << "Price for a "
<< name << ": ";
cin >> price;
Product newProd(name, price);
return newProd;
}
|
Check Yourself
- True or false: objects are usually passed by reference to improve execution speed.
- Of the following functions called from
main() , ________ is made to a non-member function.
Product bread;
bread.read();
bread.print();
list(products);
- True or false: calling a member function, outside of a class, requires an object. (see lesson 8.2.6)
^ top
12.3.4: Comparing Member Functions with Non-member Functions
- We looked at comparing two
Product objects using a non-member function:
bool isHigherPrice(Product& prod1, Product& prod2) {
if (prod1.getPrice() > prod2.getPrice()) {
return true;
}
return false;
}
- Another way is to write the comparison as a member function:
bool Product::isHigherPrice(Product& prod2) const {
if (getPrice() > prod2.getPrice()) {
return true;
}
return false;
}
- Note the difference in the parameter lists
- To call the non-member function we need two objects:
if (isHigherPrice(prod1, prod2)) {
cout << prod1.getName() << " costs more\n";
} else {
cout << prod2.getName() << " costs more\n";
}
- To call the member function we need one object:
if (prod1.isHigherPrice(prod2)) {
cout << prod1.getName() << " costs more\n";
} else {
cout << prod2.getName() << " costs more\n";
}
- By using dot notation, the object name supplies an implicit argument to the function
- The implicit argument identifies which object data to access for comparison against the object specified by the parameter
- Note that we could have written the member function as:
bool Product::isHigherPrice(Product& prod2) const {
if (price > prod2.price) {
return true;
}
return false;
}
- We do not need to use function calls because member functions can access private member variables directly
When to Write Member and NonMember Functions
- Which solution is better: member or nonmember functions?
- It depends on the ownership of the class
- If you own the class, you should implement useful operations as member functions
- If you are using a class supplied by someone else, you should write a nonmember function rather than changing the class
- The author of the class may improve it and give you a new version
- It would be a nuisance to have to add your modifications every time you received a new version of the class
Check Yourself
- True or false: member function tend to have fewer parameters than non-member functions.
- True or false: if a member function compares two objects, only one object parameter is needed.
- True or false: if you are the owner of a class, it is better to write a member function to implement useful behaviors.
^ top
12.3.5: Reading File Data into a Vector of Objects
- Lets say we have a file with information about products organized like products2.txt:
Milk
3.95
Whole-wheat bread
2.99
Cheddar cheese
3.95
- We want to read data from the file into a vector of
Product objects
- Object-oriented design principles say that an object should know how to read and write it's own data
- Thus a good way to approach this problem is to add a
read() function to the class
- The
read() function reads from an input file stream with code like:
void Product::read(ifstream& fin) {
fin >> ws; // clear whitespace including newlines
getline(fin, name);
fin >> price;
}
- We open the input stream in
main() and pass the stream in the function call:
ifstream fin("products.txt");
...
Product temp;
temp.read(fin);
- To read all the product data into a vector of objects, we use a loop as shown in the following example
- To run the example, we read from the file: products2.txt
Example Reading a File into a Vector of Objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#include <fstream>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
class Product {
public:
Product();
Product(string newName, double newPrice);
void read(ifstream& fin);
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "Unknown";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
name = newName;
price = newPrice;
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
void Product::read(ifstream& fin) {
fin >> ws; // clear whitespace including newlines
getline(fin, name);
fin >> price;
}
// Read from filename into the vector
void readFile(vector<Product>& list, string filename);
// Display vector data
void listProducts(const vector<Product>& list);
int main() {
vector<Product> list;
readFile(list, "products2.txt");
cout << "\nProducts in my store:\n";
listProducts(list);
return 0;
}
void readFile(vector<Product>& list, string filename) {
ifstream fin(filename.c_str());
if (fin.fail()) {
cout << "Input file failed to open.\n";
exit(-1);
}
while(fin.good()) {
Product temp;
temp.read(fin);
if (fin.good()) {
list.push_back(temp);
}
}
fin.close();
}
void listProducts(const vector<Product>& list) {
for (unsigned i = 0; i < list.size(); i++) {
Product temp = list[i];
temp.print();
}
}
|
Check Yourself
- True or false: an object should know how to read and write its own data.
- True or false: a convenient way to read data from a file into an object is to write a member function to read the data.
- True or false: the class member function reading from a file should have an
ifstream parameter rather than a file name (string ) parameter.
- When a read operation is successful, the
istream member function good() returns ________.
^ top
12.3.6: Writing File Data from a Vector of Objects
- After reading data into a vector of objects, we may want to write the data to a file
- As with reading, an object should know how to write its own data
- Thus a good way to approach this problem is to add a
write() function to the class
- The
write() function writes to an output file stream with code like:
void Product::write(ofstream& fout) {
fout << name << endl;
fout << price << endl;
}
- We open the input stream in another function and pass the stream in the function call:
void writeFile(vector<Product>& store, string fileName)
{
ofstream fout(fileName);
if (fout.fail())
{
cout << "Output file " << fileName << " failed to open.\n";
exit(-1);
}
fout << fixed << setprecision(2); // two decimal places
for (unsigned i = 0; i < store.size(); i++)
{
store[i].write(fout);
}
fout.close();
}
- To write all the product data into a vector of objects, we use a loop as shown in the above example
- Notice that writing to a file is like output to a terminal window
- We know in advance how many sets of data to write
- Thus the loop for writing is a for-loop
Check Yourself
- True or false: an object should know how to write its own data.
- True or false: a convenient way to write data from an object into a file is to write a member function to write the data.
- True or false: the class member function reading from a file should have an
ofstream parameter rather than a file name (string ) parameter.
^ top
Exercise 12.3: Read and Writing File Data to and from a Vector of Objects (10m)
In this exercise we explore reading a file into a vector of objects. Start with the following Product class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
#include <fstream>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
class Product {
public:
Product();
Product(string newName, double newPrice);
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "none";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
name = newName;
price = newPrice;
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
// Read product data from a file.
void readFile(vector<Product>& list, string filename);
// List the products in the store.
void listProducts(const vector<Product>& store);
// Write vector of objects to the file.
void writeFile(vector<Product>& store, string fileName);
int main() {
vector<Product> list;
int choice;
do { // simple menu
cout << "\nSelect an option:\n";
cout << "0. Exist program.\n";
cout << "1. Load data from file.\n";
cout << "2. Print data in vector.\n";
cout << "3. Write data to a file.\n";
cout << "Choice: ";
cin >> choice;
if (choice == 1) {
// readFile(list, "products2.txt");
} else if (choice == 2) {
listProducts(list);
} else if (choice == 3) {
// writeFile(list, "saved.txt");
} else if (choice != 0) {
cout << "Please enter a number from 0 - 3.\n";
}
} while (choice != 0);
cout << "Goodbye.\n";
return 0;
}
void listProducts(const vector<Product>& list) {
for (unsigned i = 0; i < list.size(); i++) {
Product temp = list[i];
temp.print();
}
}
|
- Start with the starter file listed above and copy it into a text editor and save it as
productfile.cpp .
- Compile your code to make sure you copied it correctly and after each step.
- In the same directory (folder) as your source code, save the data file: products2.txt.
- In the
Product class, add a read function with the following prototype:
void read(ifstream& fin);
- Outside the
Product class, add the implementation of the read function using the prototype just added.
void Product::read(ifstream& fin) {
// read the whitespace before getline
// read the product name
// read the price
}
See lesson 12.3.5 for more information.
- Implement the
readFile() function using the declared prototype from the starter code and the following pseudocode.
void readFile(vector<Product>& list, string filename) {
// open an input file stream
// test if the stream failed to open
// while the file stream is good
// construct a temporary object
// call the read() function on the object
// if no error during read()
// then push onto back of vector
// close the stream after the loop ends
}
See section 12.3.5 for more information.
- Call
readFile() from main() by uncommenting the menu code like:
readFile(list, "products2.txt");
- Compile and run your code to verify it works. When run, you should see the data from the file displayed like the following. Numbers in red show the input and are NOT part of the code to write.
Select an option:
0. Exist program.
1. Load data from file.
2. Print data in vector.
3. Write data to a file.
Choice: 1
Select an option:
0. Exist program.
1. Load data from file.
2. Print data in vector.
3. Write data to a file.
Choice: 2
Milk @ 3.95
Bread @ 2.99
Cheese @ 3.95
Select an option:
0. Exist program.
1. Load data from file.
2. Print data in vector.
3. Write data to a file.
Choice: 4
Please enter a number from 0 - 3.
Select an option:
0. Exist program.
1. Load data from file.
3. Write data to a file.
Choice: 0
Goodbye.
If you have problems, ask a classmate or the instructor for help as needed.
- After reading from a file works correctly, in the
Product class, add a write function with the following prototype:
void write(ofstream& fout);
- Outside the
Product class, add the implementation of the write function using the prototype just added.
void Product::write(ofstream& fout) {
// write the product name
// write the price
}
See lesson 12.3.6 for more information.
- Implement the
writeFile() function using the declared prototype from the starter code and the following pseudocode.
void writeFile(vector<Product>& list, string filename) {
// open an output file stream
// test if the stream failed to open
// for each object in the vector
// call the write function
// close the stream after the loop ends
}
Write an endl after each output command. See section 12.3.6 for more information.
- Call
writeFile() from main() by uncommenting the menu code like:
writeFile(list, "saved.txt");
- Compile and run your code to verify it works as before. Select menu 3 to write to a file. Open the "
saved.txt " output file and verify it is the same as the original "products2.txt " file.
- Submit your program source code (
productfile.cpp ) to Canvas as part of assignment 12.
- When finished please help those around you.
^ top
12.3.7: Converting Between Strings and Numbers (optional)
- We can read from or write to strings, rather than files, using string streams
- The
sstream library contains definitions for both istringstream and ostringstream
#include <sstream>
- These string streams make it easy to convert between numbers and strings
Converting Numbers to Strings
Converting Strings to Numbers
Example Using String Streams to Convert Between Strings and Numbers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#include <sstream>
#include <iostream>
using namespace std;
/**
Convert a string to a double.
*/
double stringToDouble(string s) {
istringstream instr(s);
double number;
instr >> number;
return number;
}
/**
Convert a double to a string.
*/
string doubleToString(double num) {
ostringstream outstr;
outstr << num;
return outstr.str();
}
int main() {
string str = "123.4567";
double x = stringToDouble(str);
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2);
cout << "Numerical value: " << x * 2 << endl;
string strVal = doubleToString(x * 2);
string msg = "String value: " + strVal + "\n";
cout << msg;
return 0;
}
|
Check Yourself
- True or false: you can read and write data to strings instead of files.
- To read numbers or characters from a string using stream operators
>> use the class ________.
- To write numbers or characters to a string using stream operators
<< use the class ________.
^ top
12.3.8: Summary
- You can construct a stream and open it in one step:
ofstream fout("messages.txt");
- The standard open operation will create an empty file
- You can open a file for appending data by using an extra argument:
ofstream fout("important.txt", ios::app);
- You can use
string variables as the names of files
- However, we must use the
c_str() function to convert to a C-string:
ifstream fin(filename.c_str());
- Streams can be arguments to a function, but you must use call-by-reference
- Type
istream for function parameters works for both cin and ifstream
- Type
ostream for function parameters works for both cout and ofstream
- You can read or write to strings, rather than files, using string streams
- This is useful for converting between numbers and strings
- Using an
istringstream , we can read numbers that are stored in a string by using the >> operator
- Similarly, by writing to an
ostringstream , we can convert numbers to strings using the << operator
- We developed some helper functions to support these conversions
- In addition, we discussed file operations for removing and renaming files
Check Yourself
As time permits, be prepared to answer these questions. You can find more information by following the links after the question.
- How can you read data from a file into an object before inserting it into a vector? (12.2.5)
- Using string streams, you can read from and write to strings rather than files. Why is this useful? (12.3.6)
- To delete the file named "error.log", what statement would you write? (12.2.5)
- To change the name of the file named "error.log" to "error.bak", what statement would you write? (12.2.5)
^ top
12.4: Separate Compilation
Objectives
At the end of the lesson the student will be able to:
- Separate classes from applications
- Discuss why programs should be separated into parts
- Describe how to separate the interface from the implementation
- Use separate compilation
|
^ top
12.4.1: Separating Classes from the main() Function
- When we work with classes and objects, we usually specify a class in one file and write code to use the class in another file
- This creates a more modular set of classes and allows you to reuse classes in other programs without copying and pasting code
- Recall the
#include directive:
#include <iostream>
- It turns out we can include our own files into other program files
- Syntax:
#include "myfile.cpp"
- For example:
#include "product.cpp"
Programs and the main() Function
Class Product Without a main() Function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#include <iostream>
using namespace std;
class Product {
public:
Product();
Product(string newName, double newPrice);
string getName() const;
double getPrice() const;
void setPrice(double newPrice);
void print() const;
private:
string name;
double price;
};
Product::Product() {
name = "none";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
name = newName;
setPrice(newPrice);
}
string Product::getName() const {
return name;
}
double Product::getPrice() const {
return price;
}
void Product::setPrice(double newPrice) {
if (newPrice > 0.0) {
price = newPrice;
} else {
cout << "Error: negative price!\n"
<< "Setting price to 0.\n";
price = 0.0;
}
}
void Product::print() const {
cout << name << " @ " << price << endl;
}
|
Check Yourself
- To have the preprocessor insert another file into our source code, use the ________ directive.
- True or false: if we want to include non-library files, use the include directive with double quote marks rather than angle brackets.
- True or false: in C++ you must have exactly one
main() function per executable file.
^ top
12.4.2: Including a Class in an Application
- Now let us look at how to create an application by including the separate
product.cpp file
- The following program is called
productapp.cpp
- It consists of a
main() function that includes the Product class using the #include directive:
#include "product.cpp"
- Notice that standard library includes are placed before our custom includes
Program productapp.cpp Including a File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#include <iostream>
#include <vector>
using namespace std;
#include "product.cpp"
// For testing class Product
int main() {
vector<Product> store;
char repeat = 'Y';
while ('Y' == repeat || 'y' == repeat) {
cout << "Enter product data:\n";
string name;
cout << "Product name: ";
cin >> name;
double price;
cout << "Price for a " << name << ": ";
cin >> price;
Product temp(name, price);
store.push_back(temp);
cout << "You entered:"
<< "\n Name: " << temp.getName()
<< "\n Price: " << temp.getPrice()
<< endl;
cout << "Enter another product? (y/n): ";
cin >> repeat;
}
cout << "\nAll your products:\n";
for (unsigned i = 0; i < store.size(); i++) {
store[i].print();
}
return 0;
}
|
Try It: Separating main() (5m)
- Copy the following program into a text editor, save it as
rectangleclass.cpp , and then compile and run the starter program to make sure you copied it correctly.
#include <iostream>
using namespace std;
class Rectangle {
public:
Rectangle();
Rectangle(double newLength, double newWidth);
void print();
private:
double length;
double width;
};
Rectangle::Rectangle() {
length = 0;
width = 0;
}
Rectangle::Rectangle(double newLength, double newWidth) {
length = newLength;
width = newWidth;
}
void Rectangle::print() {
cout << length << " long x " << width << " wide\n";
}
// For testing
int main() {
Rectangle rec;
Rectangle rec3x5(3.0, 5.0);
cout << "Printing rec: ";
rec.print();
cout << "Printing rec3x5: ";
rec3x5.print();
return 0;
}
- Start a new file named
rectanglemain.cpp and move the main() function to this file, deleting it from the rectangleclass.cpp file.
Try compiling rectanglemain.cpp and notice that it will NOT compile at this time. We will make it compile in the next step.
- Now we include the
Rectangle class in the main application by adding the following code at the top of the file:
#include "rectangleclass.cpp"
- Compile
rectanglemain.cpp and run the program to verify it works the same.
- Be prepared to answer the following Check Yourself questions when called upon.
Listing of rectanglemain.cpp

Check Yourself
- True or false: we can create an application using two separate source code files.
- We can include the following number of files in our code: ________.
- 1
- 2
- 4
- as many as we need
- True or false: standard library includes should be placed before custom includes.
- To include a custom library named "mylib.cpp" we add the code: ________.
#include (mylib.cpp)
#include [mylib.cpp]
#include "mylib.cpp"
#include <mylib.cpp>
^ top
12.4.3: Separate Compilation Process
- We can take the separation of files even further
- C++ lets us divide a program into separate parts and compile each part separately
- After all the parts are compiled, the parts are linked together into an executable program
- We can see the compilation steps in the following diagram
Compilation Process
Benefits of Separating the Parts
- We can place the definition for a class and its functions in files separate from the programs using the class
- This allows us to build libraries of classes
- These libraries can be used by many different programs
- In addition, we can compile a class just one time and use it in many different programs
- We use a similar process with standard libraries like
iostream
- Moreover, we can define the class itself in two files with the definition of the class in one file and the implementation in another
- If we only change the implementation of the class, then we only need to recompile the implementation
- Other files in the program, including files that use the class, need not be changed or recompiled
Encapsulation Reviewed
- The principles of encapsulation and data hiding tells us to separate the interface from the implementation
- The separation should be complete enough that we can change the implementation without changing the program that uses the class
- We ensure this separation with the following three rules
Rules of Separation
- Make all member variables private
- Make each basic operation of the class a public member function. All the public functions become the interface
- Make the member function implementations unavailable to the programmer who uses the class
More Information
^ top
12.4.4: Example of Separate Compilation
- Separate compilation starts by placing class interfaces and implementations in separate files
- The interface is placed in a header file with a .h extension
- The implementation code is placed in an implementation file with a .cpp extension
- For example, we can save our
Product class interface in a header file
- We name the file:
product.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#ifndef PRODUCT_H
#define PRODUCT_H
#include <string>
using namespace std;
class Product {
public:
// Constructors
Product();
Product(string newName, double newPrice);
// Member functions
string getName() const { return name; }
double getPrice() const { return price; }
void setName(string newName);
void setPrice(double newPrice);
private:
string name;
double price;
};
#endif
|
#include Guards
Implementation (Function Definition) Files
- The remainder of the class definition stays in the
.cpp file, such as product.cpp
- However, the
.cpp function definitions still need the declarations to compile
- Thus, we add them back by including the
product.h file in the .cpp
- The following is an example of
product.cpp where the function headers (declarations) have been placed in a separate .h file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include "product.h"
// no-parameter constructor
Product::Product() {
name = "Unknown";
price = 0.0;
}
Product::Product(string newName, double newPrice) {
setName(newName);
setPrice(newPrice);
}
void Product::setName(string newName) {
if (newName.length() == 0) {
name = "Unknown";
} else {
name = newName;
}
}
void Product::setPrice(double newPrice) {
if (newPrice > 0.0) {
price = newPrice;
} else {
price = 0.0;
}
}
|
Application File
- The
main() function is the starting point of any application
- We usually place
main() in a separate file and include the header files we need for compiling
- Notice that the includes for our header files are placed after the standard library includes
- For our example, we call our main file
productapp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <iostream>
using namespace std;
#include "product.h"
// For testing class Product
int main() {
char choice = 'Y';
string name;
double price;
while ('Y' == choice || 'y' == choice) {
cout << "Enter a product name: ";
cin >> name;
cout << "Enter the price for a "
<< name << ": ";
cin >> price;
Product prod(name, price);
cout << "You entered:"
<< "\n Name: " << prod.getName()
<< "\n Price: " << prod.getPrice()
<< endl;
cout << "Enter another product? ";
cin >> choice;
}
return 0;
}
|
Compiling
^ top
12.4.5: Instructions for Separate Compilation
- Separate the interface from the implementation
- Place the class declaration into a
classname.h file
- Place #include guards around the declaration in the
classname.h file
#ifndef PRODUCT_H
#define PRODUCT_H
// code goes here
#endif
- Place the class implementation (function definitions) into a file named
classname.cpp
- Code a
#include "classname.h " directive in the classname.cpp file and place it after the standard library includes
- Place the
main() function in a separate file
- Place the application main function into a file like
appname.cpp
- Code a
#include "classname.h " directive in the appname.cpp file
#include "product.h"
- Compile the class and the driver into object files:
g++ -c classname.cpp
g++ -c appname.cpp
for example:
g++ -c product.cpp
g++ -c productapp.cpp
- Link both files together into the application
g++ -o appname appname.o classname.o
like:
g++ -o productapp productapp.o product.o
- Then run the application in the usual way:
./appname
for instance:
./productapp
Try It: Separate Compilation (10m)
- If you are using a Windows computer, open the Cygwin terminal window.
- Copy the following program into a text editor, save it as
myrectangle.cpp , and then compile and run the starter program to make sure you copied it correctly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include <iostream>
using namespace std;
class MyRectangle {
public:
MyRectangle(double length, double width);
void print() const;
private:
double length;
double width;
};
MyRectangle::MyRectangle(double newLength, double newWidth) {
length = newLength;
width = newWidth;
}
void MyRectangle::print() const {
cout << "length: " << length
<< "\nwidth: " << width
<< endl;
}
// For testing
int main() {
MyRectangle rec(3.0, 5.0);
rec.print();
return 0;
}
|
- Apply the separate compilation process to the
MyRectangle class shown above. You should end up with three files:
myrectangle.h
myrectangle.cpp
myrectangleapp.cpp
- Make sure you have
#include guards in the myrectangle.h file.
#ifndef MYRECTANGLE_H
#define MYRECTANGLE_H
// code goes here
#endif
- Make sure you place the class declaration into the
myrectangle.h and then add a #include "myrectangle.h " directive at the top of the myrectangle.cpp file.
#include "myrectangle.h"
- Make sure you place the
main() function in a separate file and add a #include "myrectangle.h " directive at the top of the file but after using namespace std;
#include "myrectangle.h"
- At the command line, compile the files separately, like:
g++ -c myrectangle.cpp
g++ -c myrectangleapp.cpp
- Link both files together into an application like:
g++ -o myrectangleapp myrectangleapp.o myrectangle.o
- Then run the application in the usual way to test the file:
./myrectangleapp
- Check to see if the student on either side of you needs help. If so, offer to help them.
^ top
12.4.6: Makefiles
- It quickly becomes tedious to recompile code with multiple source files
- We can use a program named
make to automatically recompile our files
- However, we must create a file named
Makefile that has the instructions for the make program
- Note that the large blank areas before a command is a tab character
- To invoke a
Makefile , we type make at the command line
- Most people write a
Makefile by modifying an existing one
Makefile Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# simple makefile
# define target dependencies and files
productapp: productapp.o product.o
g++ -o productapp productapp.o product.o
# define how each object file is to be built
productapp.o: productapp.cpp product.h
g++ -c productapp.cpp -W -Wall --pedantic
product.o: product.cpp product.h
g++ -c product.cpp -W -Wall --pedantic
# clean up
clean:
rm -f productapp.exe *.o
|
Important Information
Further Information
^ top
Exercise 12.4: Separate Compilation and Makefiles (5m)
In this exercise we create a Makefile to make separate compilation easier.
Specifications
- If you have not already applied the separate compilation process to the
MyRectangle class, complete the exercise: Try It: Separate Compilation. Make sure you have three separate files:
myrectangle.h
myrectangle.cpp
myrectangleapp.cpp
- We must save the Makefile with tab characters. Thus on Windows, use NotePad or NotePad++ for editing in the classroom rather than TextPad.
- Prepare a
Makefile that automatically compiles the files when you type make at the command line. Use the Makefile Example as a template and make sure your Makefile does NOT have a .txt extension.
- Save the Makefile Example without a
.txt extension by saving as type "All Files (*.*)" and then place a dot (.) after the file name.

- Once finished, check to see if the student on either side of you needs help. If so, offer to help them.
- Zip and submit your three code files and one
Makefile as the solution to this exercise.
Makefile
myrectangle.h
myrectangle.cpp
myrectangleapp.cpp
Note: please zip (archive) files to be able to submit the Makefile.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# simple makefile
# define target dependencies and files
productapp: productapp.o product.o
g++ -o productapp productapp.o product.o
# define how each object file is to be built
productapp.o: productapp.cpp product.h
g++ -c productapp.cpp -W -Wall --pedantic
product.o: product.cpp product.h
g++ -c product.cpp -W -Wall --pedantic
# clean up
clean:
rm -f productapp.exe *.o
|
^ top
12.4.7: Summary
- C++ allows us to place the interface and the implementation in separate files
- We can write a
Makefile for easy recompiling
Check Yourself
- What are the steps for separate compilation?
^ top
Wrap Up
Due Today:
A11-Keeping Lists (11/15/18)
Due Next:
A12-Storing Information (11/25/18)
- When class is over, please shut down your computer
- You may complete unfinished exercises at the end of the class or at any time before the next class.
^ top
Last Updated:
Mon Nov 12 13:19:14 PST 2018
|