使用类在 c++ 中输入字符串无法正常工作

Inputing strings in c++ using class is not working properly

本文关键字:常工作 工作 字符串 c++ 输入      更新时间:2023-10-16
#include<fstream>
#include<iostream>
using namespace std;
class employee
{
        char *name;
        int age;
        string designation; // string data type is used
        float salary;
    public:
        void getdata();
        void display();
};
void employee::getdata() // for taking the input 
{
    cout<<"nENTER THE NAME OF THE EMPLOYEE: ";
    gets(name); /*name is a char pointer */
    cout<<"nENTER THE AGE OF THE EMPLOYEE: ";
    cin>>age;
    cout<<"nENTER THE DESIGNATION OF THE EMPLOYEE: ";
    getline(cin,designation); /*designation is string data type*/
    cout<<"nENTER THE SALARY OF THE EMPLOYEE: ";
    cin>>salary;
}
void employee::display()//for displaying the inputed data
{
    cout<<"nTHE NAME OF THE EMPLOYEE: ";
    puts(name);
    cout<<"nENTER THE AGE OF THE EMPLOYEE: ";
    cout<<age;
    cout<<"nENTER THE DESIGNATION OF THE EMPLOYEE: ";
    cout<<designation;
    cout<<"ENTER THE SALARY OF THE EMPLOYEE: ";
    cout<<salary;
}
int main()
{
    ofstream fout;
    char ch;
    fout.open("employee.txt",ios::out|ios::binary);/*use of open function in file handing*/
    employee e;
     for(int i=0;i<3;i++)
     {
        e.getdata();
        fout.write((char*)&e,sizeof(e));//write() is used
     }
     fout.close();
     ifstream fin;
     fin.open("employee.txt",ios::in|ios::binary);
     int j;
     cout<<"n Enter the employee no. to be read ";
     cin>>j;
     fin.seekg((j-1)*sizeof(e));
     fin.read((char*)&e,sizeof(e));
     cout<<"n Details of employee no. of object is = ";
     e.display();
     return 0;
}

我无法识别代码中的错误...我已经交叉检查了代码...没有语法错误和编译器错误,但输出不正确...它没有从用户那里获取正确的输入。

  • 正如Bo Persson所说,你的程序没有为名称和名称提供任何内存。您可以在类中声明像name这样的指针,但它们不指向任何内容。例如,您可以将名称声明为 char name[100];,并希望 100 就足够了(并且在生产代码中,添加检查以确保不超过它)。

    但是在C++有字符串类可以免除您的许多后顾之忧。特别是,它使任意长字符串的输入变得容易。如果字符串中不能包含空格,则一个简单的string s; cin >> s;从输入中读取字符串中的"单词"。如果它可以有空格,你需要一些方法来告诉它在哪里开始和结束。数据库,Excel等经常使用引号将字符串括起来。如果它不能包含换行符,则确保它位于自己的行上就足够了,并且无需解析。这就是我们在这里要做的。

  • 您面临的第二个问题是技术上称为"序列化"。您希望将类存储("持久化")在磁盘上,然后(也许是很久以后)将其重新读取到新实例中。内森·奥利弗(Nathan Oliver)向您指出了一个讨论序列化的资源。对于像employee这样具有有限数量的简单数据成员的简单类,我们可以简单地滚动我们自己的临时序列化:我们使用 operator<<() 将所有内容写入磁盘,并使用 operator>>() 读回所有内容。

  • 要考虑的主要事情是字符串可能有空格,因此我们将它们放在自己的行上。

  • 另外,为了在从文件中读取时更加健壮,我们用开始标记开始每个员工(header下面的代码)。这样,读取员工将从文件中的任何位置工作。此外,如果后来的员工应该有更多的字段,我们仍然可以读取我们的基本员工数据,并在我们读取磁盘上员工序列中的下一个员工之前跳过其他字段。

  • 当流在其作用域结束时被销毁时,它们会自动关闭;我们为此使用块作用域(检查代码中的附加{})。

  • 对于更高的薪水来说,普通float不够精确(它只有大约 7 个十进制数字,因此对于工资> 167772.16(如果我能相信维基百科的话),无论以何种货币计算,便士开始从悬崖上掉下来)。我使用long double并确保在将其转换为文本时不会失去精度。

  • 你没有比较真实,但我这样做是为了检查我是否正确地阅读了员工。那里必须小心。我通过比较半便士来摆脱血腥的细节,这应该适合金钱。

这是整个程序。(与我以前的版本相比,我简化了(反)序列化,特别是我删除了无用的标签。

明智的做法是在每次读/写后执行错误检查,以确保它成功;记住,流隐蔽到布尔值,所以一个简单的if(!os) { cerr << "oops" << endl; /* exit? */}就足够了;但我不想过多地分散对实际程序的注意力。

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cfloat>   // for LDBL_DIG in ostream precision.
#include <cstdlib>  // for exit()
using namespace std;

/** A simple class holding employee information */
class employee
{   
    public:
 
        /** This header starts each 
            "serialization" of an employee on a line
            of its own. */
        static constexpr const char *header = "--- Employee ---";
        
        // use C++ std::string
        string name;
        int age;
        // use C++ std::string
        string designation;
        // Be as precise as possible,
        // for later uses like salary increases
        // by 4.5% or so :-)
        // The salary is in units like USD or EUR.
        // The fraction part is pennies, and fractions of them.
        long double salary;
    public:
        void readdata(istream &is);
        void writedata(ostream &os);
        void display();
        bool operator==(employee &rhs)
        {   return 
                name == rhs.name 
            &&  age == rhs.age 
            &&  designation == rhs.designation 
            
            // Do not compare floats directly.
            // We compare pannies, leaving slack for rounding.
            // If two salaries round to the same penny value,  
            // i.e. 0.01, they are equal for us.
            // (This may not be correct, for an accounting app,
            // but will do here.)
            &&  salary - rhs.salary < 0.005 
            &&  rhs.salary - salary < 0.005;
        }
};
/** Write a header and then 
    each data member in declaration order, converted to text,
    to the given stream. The header is used to find the start
    of the next employee; that way we can have comments or other
    information in the file between employees.
    The conversion is left to operator<<.
    Each member is written to a line of its own, so that we
    can store whitespace in them if applicable.
    The result is intended to be readable by @readdata().
*/
void employee::writedata(ostream &os)
{           
    os.precision(LDBL_DIG); // do not round the long double when printing
    
    // make sure to start on a new line....
    os                  << endl
        // ... write the header on a single line ...
        << header       << endl
        // ... and then the data members.
        << name         << endl
        << age          << endl
        << designation  << endl
        << salary       << endl;
}
/** 
    Read an amployee back which was written with @writedata().
    We first skip lines until we hit a header line,
    because that's how an employee record starts.
    Then we read normal data mambers with operator>>. 
    (Strictly spoken, they do not have to be on lines
    of thier own.)
    Strings are always on a line of their own,
    so we remove a newline first.
*/
void employee::readdata(istream &is)
{
    string buf;
    
    while(getline(is, buf)) // stream converts to bool; true is "ok"
    {
        if( buf == header) break; // wait for start of employee
    }
    if( buf != header ) 
    { 
        cerr << "Error: Didn't find employee" << endl;
        return;
    }
    
    getline(is, name);  // eats newline, too
    is >> age;          // does not eat newline:
    
    // therefore skip all up to and including the next newline
    is.ignore(1000, 'n');
    
    // line on its own, skips newline
    getline(is, designation);
    
    is >> salary;
}
int main()
{
    const char *const fname = "emp.txt";
    
    employee empa;
    empa.name = "Peter A. Schneider";
    empa.age = 42;
    empa.designation = "Bicycle Repair Man";
    empa.salary = 12345.67;
    employee empb;
    empb.name = "Peter B. Schneider";
    empb.age = 43;
    empb.designation = "Bicycle Repair Woman";
    empb.salary = 123456.78;
    {
        ofstream os(fname);
        if(!os) 
        { 
            cerr << "Couldn't open " 
            << fname << " for writing, aborting" << endl;
            exit(1);
        }
        empa.writedata(os);
        cout << "Employee dump:" << endl;
        empa.writedata(cout);
        // insert a few funny non-employee lines
        os << endl << "djasdlköjsdj" << endl << endl;
        
        empb.writedata(os);
        cout << "Employee dump:" << endl;
        empb.writedata(cout);
    }
    // show the file contents
    {
        ifstream is(fname);
        if(!is) 
        { 
            cerr << "Couldn't open " 
                << fname << " for reading, aborting" << endl;
            exit(1);
        }
        string line;
        cout << "-------------- File: -------------" << endl;
        while(getline(is, line)) cout << line << endl;
        cout << "---------------End file ----------" << endl;
    }
    /////////////////////////////////////////////////////////
    {
        ifstream is(fname); // read from the file "emp.txt" just written
        if(!is) 
        { 
            cerr << "Couldn't open " 
                << fname << " for reading, aborting" << endl;
            exit(1);
        }
        
        {
            employee emp2;  // new employee, sure to be empty
            cout << endl << "Re-reading an employee..." << endl;
            emp2.readdata(is);
            cout << endl << "Re-read employee dump:" << endl;
            emp2.writedata(cout);
            cout << "Original and written/read employee are " 
                << (empa == emp2 ? "" : "NOT ") << "equal" << endl;
        }
    
        {   
            employee emp2;  // new employee, sure to be empty
            
            // now read next employee from same stream.
            // readdata() should skip garbage until the header is found.
            cout << endl << "Re-reading an employee..." << endl;
            emp2.readdata(is);
            cout << endl << "Re-read employee dump:" << endl;
            emp2.writedata(cout);
            cout << "Original and written/read employee are " 
                << (empb == emp2 ? "" : "NOT ") << "equal" << endl;
        }
    }
}

示例会话:

Employee dump:
--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001
Employee dump:
--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
-------------- File: -------------
--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001
djasdlköjsdj

--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
---------------End file ----------
Re-reading an employee...
Re-read employee dump:
--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001
Original and written/read employee are equal
Re-reading an employee...
Re-read employee dump:
--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
Original and written/read employee are equal

除了评论中所说的gets危险之外,您还会立即遇到缓冲区溢出,因为没有为name分配任何空间。仅仅有一个指针是不够的。

除此之外,混合cin >>getline(cin,...)已知会跳过输入,因为这两个函数处理行尾的方式不同。

然后我们遇到了为 employee 类型执行二进制 I/O 的问题。通常,不能对非平凡的类类型执行此操作。具体而言,std::string designation成员在内部保存指向某些数据的指针。该指针将无法在传输到磁盘和返回后继续存在。