简体   繁体   中英

c++ split string by delimiter into char array

I have a file with lines in the format:

firstword;secondword;4.0

I need to split the lines by ;, store the first two words in char arrays, and store the number as a double .

In Python, I would just use split(";") , then split("") on the first two indexes of the resulting list then float() on the last index. But I don't know the syntax for doing this in C++.

So far, I'm able to read from the file and store the lines as strings in the studentList array. But I don't know where to begin with extracting the words and numbers from the items in the array. I know I would need to declare new variables to store them in, but I'm not there yet.

I don't want to use vectors for this.

#include <iomanip>
#include <fstream>
#include <string>
#include <stdlib.h>
#include <iostream>
using namespace std;
   
int main() {
    string studentList[4];

    ifstream file;
    file.open("input.txt");
    if(file.is_open()) {
        for (int i = 0; i < 4; i++) {
            file >> studentList[i];
        }
        file.close();
    }

    for(int i = 0; i < 4; i++) {        
        cout << studentList[i];
    }

    return 0;
}

you can use std::getline which support delimiter

#include <string>
#include <sstream>
#include <iostream>
   
int main() {
    std::istringstream file("a;b;1.0\nc;d;2.0");
    for (int i = 0; i < 2; i++){
        std::string x,y,v;
        std::getline(file,x,';');
        std::getline(file,y,';');
        std::getline(file,v); // default delim is new line
        std::cout << x << ' ' << y << ' ' << v << '\n';
    }
}

C++ uses the stream class as its string-handling workhorse. Every kind of transformation is typically designed to work through them. For splitting strings, std::getline() is absolutely the right tool. (And possibly a std::istringstream to help out.)

A few other pointers as well.

Use struct for related information

Here we have a “student” with three related pieces of information:

struct Student {
    std::string last_name;
    std::string first_name;
    double      gpa;
};

Notice how one of those items is not a string.

Keep track of the number of items used in an array

Your arrays should have a maximum (allocated) size, plus a separate count of the items used.

constexpr int MAX_STUDENTS = 100;
Student studentList[MAX_STUDENTS];
int num_students = 0;

When adding an item (to the end), remember that in C++ arrays always start with index 0:

if (num_students < MAX_STUDENTS) {
    studentList[num_students].first_name = "James";
    studentList[num_students].last_name  = "Bond";
    studentList[num_students].gpa        = 4.0;
    num_students += 1;
}

You can avoid some of that bookkeeping by using a std::vector :

std::vector <Student> studentList;
studentList.emplace_back( "James", "Bond", 4.0 );

But as you requested we avoid them, we'll stick with arrays.

Use a stream extractor function overload to read a struct from stream

The input stream is expected to have student data formatted as a semicolon-delimited record — that is: last name, semicolon, first name, semicolon, gpa, newline.

std::istream & operator >> ( std::istream & ins, Student & student ) {
    ins >> std::ws;                           // skip any leading whitespace
    getline( ins, student.last_name,  ';' );  // read last_name & eat delimiter
    getline( ins, student.first_name, ';' );  // read first_name & eat delimiter 
    ins >> student.gpa;                       // read gpa. Does not eat delimiters
    ins >> std::ws;                           // skip all trailing whitespace (including newline)
    return ins;
}

Notice how std::getline() was put to use here to read strings terminating with a semicolon. Everything else must be either:

  • read as a string then converted to the desired type, or
  • read using the >> operator and have the delimiter specifically read.

For example, if the GPA were not last in our list, we would have to read and discard (“eat”) a semicolon:

char c;
ins >> student.gpa >> c;
if (c != ';') ins.setstate( std::ios::failbit );

Yes, that is kind of long and obnoxious. But it is how C++ streams work.

Fortunately with our current Student structure, we can eat that trailing newline along with all other whitespace.

Now we can easily read a list of students until the stream indicates EOF (or any error):

while (f >> studentList[num_students]) {
    num_students += 1;
    if (num_students == MAX_STUDENTS) break;  // don’t forget to watch your bounds!
}

Use a stream insertion function overload to write

'Nuff said.

std::ostream & operator << ( std::ostream & outs, const Student & student ) {
    return outs 
        << student.last_name  << ";" 
        << student.first_name << ";"
        << std::fixed << std::setprecision(1) << student.gpa << "\n";
}

I am personally disinclined to modify stream characteristics on argument streams, and would instead use an intermediary std::ostreamstream :

std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << student.gpa;
outs << oss.str() << "\n";

But that is beyond the usual examples, and is often unnecessary. Know your data.

Either way you can now write the list of students with a simple << in a loop:

for (int n = 0;  n < num_students;  n++)
    f << studentList[n];

Use streams with C++ idioms

You are typing too much. Use C++'s object storage model to your advantage. Curly braces (for compound statements) help tremendously.

While you are at it, name your input files as descriptively as you are allowed.

{
    std::ifstream f( "students.txt" );
    while (f >> studentList[num_students])
        if (++num_students == MAX_STUDENTS)
            break;
}

No students will be read if f does not open. Reading will stop once you run out of students (or some error occurs) or you run out of space in the array, whichever comes first. And the file is automatically closed and the f object is destroyed when we hit that final closing brace, which terminates the lexical context containing it.

Include only required headers

Finally, try to include only those headers you actually use. This is something of an acquired skill, alas. It helps when you are beginning to list those things you are including them for right alongside the directive.

Putting it all together into a working example

#include <algorithm>  // std::sort
#include <fstream>    // std::ifstream
#include <iomanip>    // std::setprecision
#include <iostream>   // std::cin, std::cout, etc
#include <string>     // std::string

struct Student {
    std::string last_name;
    std::string first_name;
    double      gpa;
};

std::istream & operator >> ( std::istream & ins, Student & student ) {
    ins >> std::ws;                           // skip any leading whitespace
    getline( ins, student.last_name,  ';' );  // read last_name & eat delimiter
    getline( ins, student.first_name, ';' );  // read first_name & eat delimiter 
    ins >> student.gpa;                       // read gpa. Does not eat delimiters
    ins >> std::ws;                           // skip all trailing whitespace (including newline)
    return ins;
}

std::ostream & operator << ( std::ostream & outs, const Student & student ) {
    return outs 
        << student.last_name  << ";" 
        << student.first_name << ";"
        << std::fixed << std::setprecision(1) << student.gpa << "\n";
}

int main() {
    constexpr int MAX_STUDENTS = 100;
    Student studentList[MAX_STUDENTS];
    int num_students = 0;
    
    // Read students from file
    std::ifstream f( "students.txt" );
    while (f >> studentList[num_students])
        if (++num_students == MAX_STUDENTS)
            break;
    
    // Sort students by GPA from lowest to highest
    std::sort( studentList, studentList+num_students, 
        []( auto a, auto b ) { return a.gpa < b.gpa; } );

    // Print students
    for(int i = 0;  i < num_students;  i++) {        
        std::cout << studentList[i];
    }
}

The “ students.txt ” file contains:

Blackfoot;Lawrence;3.7
Chén;Junfeng;3.8
Gupta;Chaya;4.0
Martin;Anita;3.6

Running the program produces the output:

Martin;Anita;3.6
Blackfoot;Lawrence;3.7
Chén;Junfeng;3.8
Gupta;Chaya;4.0

You can, of course, print the students any way you wish. This example just prints them with the same semicolon-delimited-format as they were input. Here we print them with GPA and surname only:

for (int n = 0;  n < num_students;  n++)
    std::cout << studentList[n].gpa << ": " << studentList[n].last_name << "\n";

Every language has its own idiomatic usage which you should learn to take advantage of.

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