简体   繁体   中英

Translating C++ File reading/writing to C# File reading/writing

All,

I'm doing some programming exercises in C++, then attempting to translate them to C# and other languages to brush up.

This example comes from Ch. 11.2 from Nell Dale and Chip Weems' "Programming and Problem Solving with C++" (fifth edition), pages 521-522. It's a program that takes in this file (student name, GPA, four grades, and the course grade)- let's call it rec.in :

Mary Abbott 3.4 80 90 60 99 B

Bob Baker 4.0 90 90 90 95 A

Sally Carter 2.0 70 70 70 65 C

Bill Dansk 3.0 65 50 60 70 D

JoEllen Zureck 1.9 90 95 80 99 A

And (more or less) just copies it to another file.

Here's the code to do this:

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

const int MAX_STUDENTS = 150;

enum GradeType{A,B,C,D,F};

struct StudentRec
{
    string stuName;
    float gpa;
    int examScore[4];
    GradeType courseGrade;
};
StudentRec gradeBook[MAX_STUDENTS];

int main()
{
    StudentRec record[MAX_STUDENTS];
    ofstream outFile;
    outFile.open("rec.out");
    ifstream inFile;
    inFile.open("rec.in");
    for (int count = 0; count < MAX_STUDENTS; count++)
    ReadValues(inFile, record[count]);
    for (int count = 0; count <= MAX_STUDENTS; count++)
    PrintValues(outFile, record[count]);
    inFile.close();
    outFile.close();
    return 0;
}

void ReadValues(ifstream& inFile, StudentRec& record)
{
    char letter;
    char throwAway;

    getline(inFile, record.stuName);
    inFile >> record.gpa>>record.examScore[0]
    >> record.examScore[1] >> record.examScore[2]
    >> record.examScore[3] >> letter;

    inFile.get(throwAway);

    switch(letter)
    {
        case 'A' : record.courseGrade = A;
           break;
        case 'B' : record.courseGrade = B;
           break;
        case 'C' : record.courseGrade = C;
           break;
            case 'D' : record.courseGrade = D;
           break;
        case 'F' : record.courseGrade = F;
               break;
   }
}

void PrintValues(ofstream& outFile, StudentRec& record)
{
    outFile << record.stuName << endl;
    outFile << record.gpa << ' ' << record.examScore[0] << ' '
        << record.examScore[1] << ' '
        << record.examScore[2] << ' '
        << record.examScore[3] << ' ';
    switch (record.courseGrade)
    {
        case A : outFile << 'A';
        break;
        case B : outFile << 'B';
        break;
        case C : outFile << 'C';
        break;
        case D : outFile << 'D';
        break;
        case F : outFile << 'F';
        break;
    }

outFile << endl;
}

I'm starting out with a translation to C#. I've done C# work before, but not console work. As a result, it's a bit... messy... working to translate stuff. I'm a tad confused with matching the getline function of C++ in C#. There is, of course, System.IO.StreamReader , but that only reads a file a line at a time. getline does it in a stream format, as seen above. This breaks it up using the spaces/newlines between words/characters. How would I achieve the same result with C#'s library choices? I'm not exactly married to the System.IO.StreamReader solution.

Here's the beginning of what I have:

using System;
using System.IO;

namespace _112a
{
    class Program
    {
        const int MAX_STUDENTS = 150;
        enum GradeType{A,B,C,D,F};
        struct StudentRec{
            string stuName;
            float gpa;
            GradeType courseGrade;
        };

        StudentRec[] gradeBook = new StudentRec[MAX_STUDENTS];

        static void Main(string[] args)
        {
            StudentRec[] record = new StudentRec[MAX_STUDENTS];
            System.IO.StreamWriter outFile = new System.IO.StreamWriter("rec.out"); 
            System.IO.StreamReader inFile = new System.IO.StreamReader("rec.in");

            for (int count = 0; count < MAX_STUDENTS; count++)
            {
                ReadValues(inFile, record[count]);
            }

            for (int count = 0; count <= MAX_STUDENTS; count++)
            {
                PrintValues(outFile, record[count]);
            }

            inFile.Close();
            outFile.Close();
        }

        static void ReadValues(StreamReader inFile, StudentRec record)
        {
            char letter;
            char throwAway;

            /*can't use getline, what do?*/

            inFile.ReadLine();

            switch (letter)
            {
                case 'A':
                    record.courseGrade = (GradeType)Enum.Parse(typeof(GradeType), "A");
                    break;
                case 'B':
                    record.courseGrade = (GradeType)Enum.Parse(typeof(GradeType), "B");
                    break;
                case 'C':
                    record.courseGrade = (GradeType)Enum.Parse(typeof(GradeType), "C");
                    break;
                case 'D':
                    record.courseGrade = (GradeType)Enum.Parse(typeof(GradeType), "D");
                    break;
                case 'F':
                    record.courseGrade = (GradeType)Enum.Parse(typeof(GradeType), "F");
                    break;
            }


        }

        static void PrintValues(StreamWriter outFile, StudentRec record)
        {
            outFile.Write("{0}\n{1} {2} {3} ", record.stuName, record.examScore[0], record.examScore[1], record.examScore[2], record.examScore[3]);

            switch(record.courseGrade)
            {
                case GradeType.A: 
                    outFile.Write("A\n");
                    break;
                case GradeType.B: 
                    outFile.Write("B\n");
                    break;
                case GradeType.C: 
                    outFile.Write("C\n");
                    break;
                case GradeType.D:
                    outFile.Write("D\n");
                    break;
                case GradeType.F:
                    outFile.Write("F\n");
            }
        }
    }
}

I have an additional question regarding the switch structure you see in the method PrintValues : in each of the cases, it's specified in the C++ file that it just takes the record.courseGrade field and compares it to the argument specified behind case . C#, being a type-strong language, won't let this happen... if you compare char 'A' to record.courseGrade , you get a type mismatch error. Alternately, leaving it without the single quotes (and thus leaving it as a record.courseGrade field) gives the error of The name 'A' does not exist in the current context. How do I best deal with this?- RESOLVED

My confusion using ReadLine() stems from how it would work. You have multiple members of the StudentRec struct being filled in this case, but they're separate entities in the struct. My fear would be doing something like:

StudentRec.stuName = inFile.ReadLine();

and having StudentRec.stuName filled with the entire line, with nothing in the other struct fields.

Furthermore, we have the issue of letter being unassigned and used as a switch; this is due to it never receiving a value from the ReadLine . However, that's an academic solution, if ReadLine does indeed split things up.

You want to use StreamReader and Writer respectively. The methods you're likely looking for are ReadLine and WriteLine , the StreamReader docs have a working example at the bottom that will suite your needs. You'll just need to change the file path and logic inside the while loop. The docs can be found here;

http://msdn.microsoft.com/en-us/library/system.io.streamreader.aspx

StreamWriter docs are here and have an example the the bottom that uses both the reader and writer;

http://msdn.microsoft.com/en-us/library/system.io.streamwriter.aspx

All of the methods have logical names and are simpler to implement than their c++ counterparts.

I believe your switch causing a compilation error because the cases need to be case "A": instead of case A: . If you put a literal there it's going to do a string comparison. It doesn't understand what an A is unless that's the name of a local variable.

EDIT: sample code to read the file;

// we need to alloc/init the streams inside for using blocks so they're properly disposed of
List<string> grades = new List<string>();
using (StreamReader inFile = new StreamReader("rec.in"))
{
     string line;
     while ((line = inFile.ReadLine()) != null)
     {
         string[] tokens = line.Split(' '); // split line on whitespace
         if (tokens.Length > 7)
             grades.Add(tokens[7]);
     }
}

using (StreamWriter outFile = new StreamWriter("rec.out"))
{
      foreach (string s in grades)
          outFile.WriteLine(s);
}

The techniques I'm using above aren't completely necessary, for example you could nest the StreamWriter using inside the StreamReaders and do the writing within that same scope. In the C# version you're looping over the contents of the file line by line. Each time we get a line we want to split it on spaces to get it's sub components. Then I add the letter grade to a list. We don't want newlines in our output strings because WriteLine does that automatically. After we're doing reading the file we loop over each string in the list and write it to the output file.

For the problem with switch try this:

case GradeType.A: 
    outFile.Write("A\n");
    break;

You work with enumeration and must give it to the switch case.

For other problem if you want to work with the each row in file, you must assign it to a string and then process it to extract the information you need through methods such as split and so on.

string line = inFile.ReadLine();
string[] data = line.Split(' ');
record.stuName = string.Format("{0} {1}", data[0], data[1]);
record.gpa = float.Parse(data[2]);
....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM