Educational Objectives: After completing this assignment the student should have the following knowledge, ability, and skills:
Operational Objectives: Create customized output manipulators and a client program that formats numerical input.
Deliverables: Three files xiomanip.h, xiomanip.cpp, and b2b.cpp.
Rubric to be used for grading: range : value build fmanip.x 0..5 : build b2b.x 0..5 : assess 1: fmanip.x < num1 > fmanip.num1 0..5 : assess 2: b2b.x d1.in d1.out 0..5 : assess 3: b2b.x d2.in d2.out 0..5 : assess 4: b2b.x d3.in d3.out 0..5 : code quality -20..20 : late fees 0..25% : ------ : -- total 0..50 : Note: input files may vary over time and from those distributed. Code quality includes: - conformance to assignment requirements and specifications [see assignment document] - conformance to coding standards [see course organizer] - engineering and design, including appropriateness of name choices - readability
One of the goals for this assignment is learn how to create and use I/O manipulators.
An I/O manipulator may seem mysterious at first, but it is really a simple function. (The manipulators that take parameters are somewhat more complex, requiring function objects.) Here's how it works:
Various properties of I/O are controlled by "flags", which are public member variables of the base class std::ios and hence inherited by other classes such as std::istream, std::ostream, std::ifstream, and std::ofstream. These flag names are:
boolalpha , dec , fixed , hex , internal , left , oct , right , scientific , showbase , showpoint , showpos , skipws , unitbuf , uppercase , adjustfield , basefield , floatfield
These names can be manipulated as if they were single bits (and in most implementations they are single bits, although this is not specified by the language). There are also member functions of class ios
void setf (std::ios::flag flags); void unsetf (std::ios::flag flags);
that take flags as argument and either set or unset the flags. If, for example, you want to unset the flags dec and oct and set the hex flag for the stream object s, you would make these two calls:
s.unsetf (std::ios::dec | std::ios::oct); s.setf (std::ios::hex);
There is also a 2-argument version of setf designed to perform of this kind of change in one call:
void setf (std::ios::flag flags, std::ios::flag mask);
This member function unsets the bits defined by mask and then sets the bits defined by flags. This is typically used when there are flags that are related, as in hex,dec,oct. For example, we should not have both the hex and the dec flags set in the same object. Using the defined std::ios::basefield = std::ios::hex | std::ios::dec | std::ios::oct the correct way to set the hex flag would be:
setf (std::ios::hex, std::ios::basefield);
This has the effect of unsetting whatever base flag is set, and then setting the hex flag, thus ensuring that only one of the basefield flags is set.
Note that the scope of the flags must also be resolved as in (std::ios::). Note also the use of bitwise OR to combine the dec and oct flags into a single argument.
An I/O manipulator has the following prototype pattern:
std::ios& M(std::ios& s);
where M is the name of the manipulator. Note that it takes a stream base object by reference and then returns the same object (modified), so that it makes sense to call the manipulator using the syntax
os << ... << M << ... // manipulator call syntax for an ostream os is >> ... >> M >> ... // manipulator call syntax for an istream is
This syntax is facilitated by overloads of the input and output operators something like this:
ios& operator << (ios& s, std::ios& f(std::ios&)) { return f(s); } ios& operator >> (ios& s, std::ios& f(std::ios&)) { return f(s); }
(In case you are wondering - the second parameter is a function f with signature std::ios& f(std::ios&).) These overloads are already defined in the iostream library. So, the only thing left to do is actually write the manipulator function f. Here is how to write the std::hex manipulator function:
std::ios& hex (std::ios& s) { s.setf(std::ios::hex, std::ios::basefield); return s; }
Note the return of the incoming stream object (after its state is modified by changing the flag settings). The effect of this code:
is >> std::hex; os << std::hex;
is to set the hex flag (by calling hex(os)), unset the other flags in basefield, and return the modified stream object for further processing. For input streams, this will mean that numerical input is assumed to be in hexadecimal notation. For output streams, this will mean that numbers are output in hexadecimal notation.
Create and work within a separate subdirectory cop3330/hw5. Review the COP 3330 rules found in Introduction/Work Rules.
Begin by copying the following files from the course directory: into your hw5 directory:
[LIB]/hw5/fmanip.cpp [LIB]/hw5/makefile [LIB]/hw5/data?.in [LIB]/hw5/hw5submit.sh [LIB]/area51/b2b_i.x [LIB]/area51/fmanip_i.x
The naming of these files uses the convention that _s are compiled for Sun/Solaris and _i are compiled for Intel/Linux. Use one of the sample client executables to experiment to get an understanding of how your program should behave.
Create the files xiomanip.h, xiomanip.cpp, and b2b.cpp that meet the specifications and requirements below.
Turn in the files xiomanip.h, xiomanip.cpp, and b2b.cpp using the hw5submit.sh submit script.
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.
The manipulators are in the fsu namespace.
The manipulators should be defined (prototyped) in the file xiomanip.h and implemented in the file xiomanip.cpp.
Provide manipulators that accomplish the following:
fsu I/O Manipulators name functionality hex hexadecimal, lower case, no fill, no show base Hex hexadecimal, upper case, no fill, no show base hexFill hexadecimal, lower case, fill, no show base HexFill hexadecimal, upper case, fill, no show base hexShowBase hexadecimal, lower case, no fill, show base HexShowBase hexadecimal, upper case, no fill, show base oct octal, no fill, no show base octFill octal, fill, no show base octShowBase octal, no fill, show base dec decimal, no fill, no show base [default settings]
Implement each manipulator using std::ios member functions only - do not use std:: manipulators.
Thoroughly test your manipulators using fmanip.cpp as follows: First compile with the command make fmanip.x. Then run the program and compare the results with the results using the same input on fmanip_i.x. File redirect and the unix diff command are handy for this comparison.
The program b2b.cpp must use the manipulators in namespace fsu prototyped in the file xiomanip.h. The only standard manipulator allowed is std::setw(cw) that takes a column width as argument.
The I/O header files included by b2b.cpp should be exactly these:
#include <iostream> #include <fstream> #include <iomanip> // std::setw #include <xiomanip.h> // fsu::hex, fsu::hexShowBase, fsu::hexFill, ...
Do not use any "using" directives in your code.
The following constants should be declared and used in b2b.cpp:
static const unsigned short cwh = 16, // max digits of unsigned long hexadecimal cwd = 20, // max digits of unsigned long decimal cwo = 26, // max digits of unsigned long octal cs1 = 4, cs2 = 2// col spacers
Do not hard code literal values for column widths and column spacers. These constants are set up to produce tables when the integers are 64-bit (eight byte) words (the size of type unsigned long). The various cw values are the max size of a 64-bit number in the various notations. The cs values are the spacing preceeding the column.
The application b2b should take two file names at the command line. The first is an input file, which contains documentation, instructions, and data. The second is an output file in which a table should be created according to the input file instructions and data.
Note that there are options specifying the numerical notation for input and for the notation in the second column of output. (The first column of output should be formated the same as the read format for the input file.) The output file should be a table with header (giving the names of the output file and the input file) and column headers indicating the notation used in that column. The data in the table consists of one line for each number in the input file, in the notation specified by the formatting characters. See b2b.x and the example input data files for clarification of this behavior.
The input file is formatted as follows:
input file format: ================= 1: file documentation lines at top of file begin with '#' 2: next string of data: (three format control characters) format for input file first character - how to interpret input numbers: 'o' or 'O': read in octal notation 'd' or 'D': read in decimal notation 'h' or 'H': read in hexadecimal notation next two characters format second column of output first character of pair: numerical notation 'd' = decimal 'D' = decimal 'h' = hex/lower case 'H' = hex/upper case 'o' = octal 'O' = octal second character of pair: fill (affects hex and oct only) 'f' or 'F' = fill hex and octal with leading zero, 64-bit 'n' or 'N' = no fill 'b' or 'B' = no fill and show base 3: remainder of input file: unsigned long integers in notation designated by first format control character
The program should skip the documentation, then read the three formatting characters, and then proceed to read the file data, constructing the table in the output file as it goes. (There is no need to store more than one integer at a time, just read an integer and then write the line of the output file table corresponding to that input.)
The output file should begin with two lines of information giving the names of the file and the input data file. The third line should be blank, and the fourth line should have the headers for the table columns. The actual table entries should commence below, and allign correctly with, the column headers.
Be sure that hex and octal numbers are output with the correct number of leading zeros in case 'f'. The defined constants cwx above give the correct size needed for 64-bit numbers.
Output from your b2b.x should duplicate exactly the output produced by the distributed executables. When in doubt, use the distributed executables for guidance in constructing the output file.
Don't forget to protect against multiple reads of the fsu manipulator prototypes, using the usual #ifndef ... #define ... #endif mechanism.
The test harness fmanip.cpp and a makefile are supplied.
Execute the provided fmanip_i.x to get understanding of the desired effects of the various manipulators.
Keep in mind: numbers inside the machine are all binary. Decimal, octal, and hexadecimal are human lexical representations that exist only outside the machine, on screen or in files.
Keep in mind: numbers inside the machine are all binary. Decimal, octal, and hexadecimal are human lexical representations that exist only outside the machine, on screen or in files.
To get a number to print in 64-bit (16 digit) hex notation with leading zero fill, you need a column exactly 16 characters wide. If you need more visual space in the table, you will need an extra "column" of spaces for that purpose.
The best way to skip documentation is to peek() and then get() to end of line. It's a mistake to try to use "getline()" in this assignment; this function is usually too crude for intricate work, and it often erroneously discards data from the input stream. You really don't need any string variables for this assignment, although they are allowed.
Note: There might be zero lines of documentation.
See the program examples/commandLineArguments.cpp to see how to handle command line arguments in C++. Compile and run this program with some command line arguments (strings), then take a look at the source code. For example:
lacher@diablo:~/3330/examples>commandLineArguments.x aa bbbbbb cccc 4 command line arguments were entered. The data passed to function main were: 4 = argc argv[0] = commandLineArguments.x argv[1] = aa argv[2] = bbbbbb argv[3] = cccc lacher@diablo:~/3330/examples>
Note that the program and the OS cooperate via the "argument counter" argc and the "argument vector" argv. argc gives the number of input strings (starting with the name of the executable itself as "zero"), and argv is an array of strings containing all of these arguments. Note that the name of the executable will always be argv[0], for example.
Here is one sample session:
lacher@linprog:~/3330/hw5>make b2b.x lacher@linprog:~/3330/hw5>b2b.x data2.in data2.out lacher@linprog:~/3330/hw5>
Here is the input file data2.in:
# data2.in # read data as decimal numbers # convert these numbers to a table of dec, hex values # DHF 0 1 10 100 256 1000 10000 65535 100000 1000000 10000000 100000000 1000000000 4294967295 10000000000 100000000000 1000000000000 10000000000000 100000000000000 1000000000000000 10000000000000000 100000000000000000 1000000000000000000 10000000000000000000 18446744073709551615
Here is the outputfile data2.out:
Name of this file: data2.out Name of input data file: data2.in dec hex 0 0000000000000000 1 0000000000000001 10 000000000000000A 100 0000000000000064 256 0000000000000100 1000 00000000000003E8 10000 0000000000002710 65535 000000000000FFFF 100000 00000000000186A0 1000000 00000000000F4240 10000000 0000000000989680 100000000 0000000005F5E100 1000000000 000000003B9ACA00 4294967295 00000000FFFFFFFF 10000000000 00000002540BE400 100000000000 000000174876E800 1000000000000 000000E8D4A51000 10000000000000 000009184E72A000 100000000000000 00005AF3107A4000 1000000000000000 00038D7EA4C68000 10000000000000000 002386F26FC10000 100000000000000000 016345785D8A0000 1000000000000000000 0DE0B6B3A7640000 10000000000000000000 8AC7230489E80000 18446744073709551615 FFFFFFFFFFFFFFFF