We introduced the C++ command line arguments with a simple program that displays them in reverse order.
//File: command.cpp
#include <iostream>
using namespace std;
//argc is the number of command line arguments
//argv is an array of strings representing each argument
int main(int argc, char * argv[])
{
for (--argc; argc >= 0; --argc)
{
//cout << "argc = " <<
argc << endl;
cout << argv[argc] << endl;
}
return 0;
}
c:\>a.exe How Now Brown Cow
Cow
Brown
Now
How
c:/a.exe
Note that the executable file a.exe is counted as one of the arguments. It is displayed along with the actual string arguments.
This is a good time to introduce records, which in C and C++ are called structures. But before getting into the details, let's look at the big picture. Records are designed to allow the harmonious coexistence of different types in a single structure; i.e., they are non-homogeneous. This is in contrast to arrays (and vectors), which are restricted to storing elements of a single data type (homogeneous). Records would be useful in maintaining information about, say employees or students. Employee records would store information like name, address, telephone number, date of hire, wage rate, and position. Student records might include grade point average, year of study, expected date of graduation, etc. Notice that these categories include items which would be appropriately represented as double (wage rate and grade point average), integer (year of study), and string (name, date, telephone number, etc.). It's reasonable and convenient to keep all information (even though of different types) in one place for each employee or student.
Example 1. Construct a C++ structure for a student. Use the input data file: students_in.txt
//File: records.cpp
#include <fstream>
#include <string>
using namespace std;
int main()
{
struct student_record
//defines a new type
{
string
name; //three data fields
int
age;
double
gpa;
}; //NOTE: You need the semicolon here!
student_record student; //defines a
variable of student_record type
ifstream fin ("students_in.txt");
ofstream fout ("students_out.txt");
while (!fin.eof())
{
fin >> student.name >> student.age
>> student.gpa;
fout <<
student.name << "\t"
<< student.age << "\t"
<< student.gpa << endl;
}
return 0;
// NOTE: Output of results reflects the dot
notation used above.
// You can't input or output records or struct's
in the aggregate.
// That is, the following are illegal:
// cin >> student; or
cout << student;
// However, you can make aggregate assignments;
i.e., the following is OK:
// student1 = student2;
// In this case, each field of student2 is
copied to the corresponding
// field of student1.
}
The following illustrates that there are often many ways to accomplish the same task. In particular, the input data file is an input file stream and so has several available methods to detect the end of file as well as other characters.
Alternate Version
//File: records2.cpp
#include <fstream>
#include <string>
using namespace std;
int main()
{
struct student_record
//defines a new type
{
string
name; //three data fields
int
age;
double gpa;
}; //NOTE: You need the semicolon here!
student_record student; //defines a
variable of student_record type
ifstream fin ("students_in.txt");
ofstream fout ("students_out.txt");
//The new part
while (fin.peek() != EOF)
{
while (fin.peek() != '\n')
{
fin >> student.name >> student.age >> student.gpa;
fout << student.name << '\t'
<< student.age << '\t'
<< student.gpa << endl;
}
fin.ignore();
}
return 0;
}
Lab Exercise. With the given data file, students_in.txt, the alternative version leads to an infinite loop. Can you discover why? Can you alter the input file so that the alternative version behaves properly?
Lab Exercise. Can you modify the records2 program to accomodate the original data file, students_in.txt ? (Solution: recordsFix.cpp or recordsFix2.cpp)
Lab Exercise. Write a program that creates an array of student records, reads them into memory and then writes them out to memory. Don't peek until after class, but my version is a click away at array.cpp.
Lab Exercise. Try modifying the previous exercise by using vectors instead of arrays. That is, add the include file <vector> and change the line
student_record student[100];
to read:
vector<student_record> student(100);
If you are using Visual C++, you will get the following error message:
'struct main::student_record' : types with no linkage cannot be used as template
arguments.
The essential difficulty here is that the class student_record is defined locally in main() and the vector class can't see it. So, of course, a vector of student_record's cannot be constructed. If you move the definition of student_record above main() (global), the program runs fine.
But of course you've replaced one static memory scheme with another. How about trying the following declaration:
vector<student_record> student;
You'll need to modify your program slightly to make it work. However, this new version will fully utilize the vector's ability to assign memory dynamically.
For C-style strings, you've got the following options:
cin.getline (s, 80)
<== "This is a test"
cout << s << endl;
<==
"This is a test"
cin.get (s, 80)
<== "This is a test"
cout << s << endl;
<==
"This is a test"
For the new C++ string type, an overloaded version of getline() gives:
string s;
getline(cin, s);
<== "This is a test"
cout << s << endl;
<== "This is a test"
Unfortunately, the I/O functions do not always behave the same way with different compilers under different operating systems. For example, the last getline() example takes the carriage return as the delimiter for the string, but requires the user to enter another carriage return to clear the buffer with Visual C++. Using the UNIX gnu C++ compiler, however, no second carriage return is required.
Finally, note that for either string type (i.e., old C-style or new C++), the following can cause run-time errors:
int i;
cin >> s;
<== "This is a test"
cout << s << endl;
<==
"This"
cin >> i;
<== ERROR ("is a test" remains in input stream)
^
input for i
Next, we extended the struct construct (inherited from C) as we created a new circle data type.
//File: circle_struct.cpp
#include <iostream>
#include <string>
using namespace std;
const double PI=3.14159;
struct circle
{
//Member data fields
double m_rad;
double m_x, m_y;
string m_name;
//Member functions -- DON'T HAVE THESE IN C
circle() //Constructor
{
m_x=0.0;
m_y=0.0;
m_rad=1.0;
m_name="Unit Circle";
}
//Overloaded constructor
circle (double xcoord, double ycoord,
double radius, string name)
{
m_x=xcoord;
m_y=ycoord;
m_rad=radius;
m_name=name;
}
~circle() //Destructor
{
}
//NOTE: Constructors and destructors are called
automatically.
// You're not permitted to call them yourself.
// Also, note that they have no return type.
//Accessor Methods
double circumference() const
//The use of const here prevents
//changing or updating the member data.
{
return (m_rad*2.0*PI);
}
double radius() const
{
return m_rad;
}
string name() const
{
return m_name;
}
void center (double & xcoord, double
& ycoord) const
{
xcoord=m_x;
ycoord=m_y;
}
};
int main()
{
circle c, d(1.0, 3.0, 5.0, "Test Circle");
double x, y;
c.center(x,y);
cout << c.name() << " is located at (" << x
<< "," << y << ")" << endl;
cout << "Its radius is: " << c.radius() <<
endl;
cout << "Its circumference is: " <<
c.circumference() << endl;
cout << endl;
d.center(x,y);
cout << d.name() << " is located at (" << x
<< "," << y << ")" << endl;
cout << "Its radius is: " << d.radius() <<
endl;
cout << "Its circumference is: " <<
d.circumference() << endl;
cout << endl;
return 0;
}