Bagaimana cara memeriksa apakah string C ++ std :: dimulai dengan string tertentu, dan mengonversi substring ke int?


242

Bagaimana cara menerapkan yang berikut (Python pseudocode) di C ++?

if argv[1].startswith('--foo='):
    foo_value = int(argv[1][len('--foo='):])

(Misalnya, jika argv[1]ada --foo=98, maka foo_valueadalah 98.)

Pembaruan: Saya ragu-ragu untuk melihat Boost, karena saya hanya ingin membuat perubahan kecil ke alat command-line kecil yang sederhana (saya lebih suka tidak harus belajar cara menautkan dan menggunakan Boost untuk anak di bawah umur perubahan).


Ini juga menarik.
manlio

Jawaban:


449

Gunakan kelebihan rfindyang memiliki posparameter:

std::string s = "tititoto";
if (s.rfind("titi", 0) == 0) {
  // s starts with prefix
}

Siapa yang butuh yang lain? STL murni!

Banyak yang salah membaca ini berarti "mencari mundur seluruh string mencari awalan". Itu akan memberikan hasil yang salah (misalnya string("tititito").rfind("titi")mengembalikan 2 sehingga bila dibandingkan terhadap == 0akan kembali salah) dan itu akan menjadi tidak efisien (melihat seluruh string bukan hanya awal). Tapi itu tidak melakukan itu karena melewati posparameter sebagai 0, yang membatasi pencarian hanya cocok pada posisi itu atau sebelumnya . Sebagai contoh:

std::string test = "0123123";
size_t match1 = test.rfind("123");    // returns 4 (rightmost match)
size_t match2 = test.rfind("123", 2); // returns 1 (skipped over later match)
size_t match3 = test.rfind("123", 0); // returns std::string::npos (i.e. not found)

32
jawaban ini harus yang paling banyak dipilih bukan yang meningkatkan: D mengapa menggunakan perpustakaan lain ketika Anda sudah memiliki STL.
Iuliu Atudosiei

@ sweisgerber.dev, saya bingung pada pertengkaran pertama Anda. Nilai kembali dari findhanya akan nol jika titiberada di awal string. Jika ditemukan di tempat lain, Anda akan mendapatkan nilai balik tidak nol dan, jika tidak ditemukan, Anda akan mendapatkan nposyang juga bukan nol. Dengan asumsi saya benar, saya lebih suka jawaban ini karena saya tidak harus membawa barang-barang non-standar (ya, saya tahu Boost ada di mana-mana, saya hanya lebih suka core C ++ libs untuk hal-hal sederhana seperti ini).
paxdiablo

@paxdiablo: Anda benar, memang memeriksa apakah itu dimulai dengan titi, tetapi bagian konversi tidak ada.
sweisgerber.dev

2
Apakah kami memiliki bukti bahwa ini dioptimalkan di sebagian besar kompiler? Saya tidak menemukan di tempat lain menyebutkan optimasi "find" atau "rfind" adalah praktik umum berdasarkan nilai pengembalian yang diperiksa.
Superziyi

2
@alcoforado "rfind akan mulai dari belakang string ..." Tidak, itu hanya berlaku untuk kelebihan rfind()yang tidak mengambil posparameter. Jika Anda menggunakan overload yang mengambil posparameter maka itu tidak akan mencari seluruh string, hanya posisi itu dan sebelumnya. (Sama seperti biasa find()dengan posparameter hanya terlihat di posisi itu atau yang lebih baru.) Jadi jika Anda lulus pos == 0, seperti yang ditunjukkan dalam jawaban ini, maka secara harfiah hanya akan mempertimbangkan untuk pertandingan di satu posisi itu. Itu sudah menjelaskan baik dalam jawaban maupun komentar.
Arthur Tacca

188

Anda akan melakukannya seperti ini:

std::string prefix("--foo=");
if (!arg.compare(0, prefix.size(), prefix))
    foo_value = atoi(arg.substr(prefix.size()).c_str());

Mencari lib seperti Boost.ProgramOptions yang melakukan ini untuk Anda juga merupakan ide yang bagus.


7
Masalah terbesar dengan ini adalah yang atoi("123xyz")kembali 123, sedangkan Python int("123xyz")melemparkan pengecualian.
Tom

Solusinya, yang bisa kita lakukan, adalah ke sscanf () dan membandingkan hasil dan aslinya, untuk memutuskan apakah akan melanjutkan atau melempar pengecualian.
Roopesh Majeti

1
Atau cukup ganti atoidengan strtolatau strtoll, yang memungkinkan kami mendeteksi kondisi kesalahan dalam nilai input.
Tom

1
Ini adalah solusi yang lebih baik daripada rfindyang tergantung pada optimasi untuk bekerja.
Calmarius

143

Hanya untuk kelengkapan, saya akan menyebutkan cara C untuk melakukannya:

Jika strstring asli Anda, substrapakah substring yang ingin Anda periksa, maka

strncmp(str, substr, strlen(substr))

akan kembali 0jika str dimulai dengan substr. Fungsi strncmpdan strlenberada di file header C.<string.h>

(awalnya diposting oleh Yaseen Rauf di sini , markup ditambahkan)

Untuk perbandingan case-insensitive, gunakan strnicmpsebagai ganti strncmp.

Ini adalah cara C untuk melakukannya, untuk string C ++ Anda dapat menggunakan fungsi yang sama seperti ini:

strncmp(str.c_str(), substr.c_str(), substr.size())

9
memang, semua orang tampaknya hanya pergi "menggunakan dorongan" dan saya untuk seseorang bersyukur untuk stl atau versi perpustakaan OS
Force Gaia

Iya. Namun, ia menganggap string tidak memiliki karakter nol di dalamnya. Jika bukan itu masalahnya - orang harus menggunakanmemcmp()
Avishai Y

mengapa ada orang yang menggunakan selain solusi sederhana yang indah ini?
Adam Zahran

88

Jika Anda sudah menggunakan Boost, Anda bisa melakukannya dengan meningkatkan algoritma string + boost cast lexical:

#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>

try {    
    if (boost::starts_with(argv[1], "--foo="))
        foo_value = boost::lexical_cast<int>(argv[1]+6);
} catch (boost::bad_lexical_cast) {
    // bad parameter
}

Pendekatan semacam ini, seperti banyak jawaban lain yang disediakan di sini adalah ok untuk tugas yang sangat sederhana, tetapi dalam jangka panjang Anda biasanya lebih baik menggunakan parsing library baris perintah. Boost memiliki satu ( Boost.Program_options ), yang mungkin masuk akal jika Anda sudah menggunakan Boost.

Kalau tidak, pencarian untuk "c ++ command line parser" akan menghasilkan sejumlah opsi.


107
Menarik dependensi besar untuk pemeriksaan awalan string seperti menembak burung dengan kanon.
Tobi

150
"Gunakan Peningkatan" selalu merupakan jawaban yang salah ketika seseorang bertanya bagaimana melakukan operasi string sederhana di C ++.
Glenn Maynard

90
minus 1 untuk menyarankan Peningkatan
uglycoyote

37
Menggunakan boost di sini benar, jika Anda sudah menggunakan boost dalam proyek Anda.
Alex Che

17
Jawabannya diawali dengan "Jika Anda menggunakan Boost ...". Jelas ini adalah jawaban yang tepat "... jika Anda menggunakan Boost". Jika tidak, lihat saran dari
@Thomas

82

Kode yang saya gunakan sendiri:

std::string prefix = "-param=";
std::string argument = argv[1];
if(argument.substr(0, prefix.size()) == prefix) {
    std::string argumentValue = argument.substr(prefix.size());
}

2
yang paling ringkas dan hanya tergantung pada std :: string, kecuali hapus argumen opsional.size (dan opsional) di akhir substr akhir.
Ben Bryant

@ ben-bryant: Terima kasih atas kepala. Tidak tahu itu opsional.
Hüseyin Yağlı

16
Menggunakan substrlead untuk menyalin yang tidak perlu. The str.compare(start, count, substr)Metode yang digunakan dalam jawaban Thomas' lebih efisien. Jawaban razvanco13 memiliki metode lain yang menghindari penyalinan dengan menggunakan std::equal.
Felix Dombek

4
@ HüseyinYağlı Thomas uses atoi which is only for windowsHuh? atoitelah menjadi fungsi perpustakaan standar C sejak ... sebelumnya. Pada kenyataannya, atoiitu buruk - bukan karena itu khusus Windows - tetapi karena itu (1) C, bukan C ++, dan (2) ditinggalkan bahkan di C (Anda harus menggunakan strtolatau salah satu dari yang lain, fungsi terkait. Karena atoimemiliki tidak ada penanganan kesalahan. Tapi, sekali lagi, itu hanya di C, sih).
Parthian Shot

50

Belum ada yang menggunakan fungsi algoritma / ketidakcocokan STL . Jika ini mengembalikan true, awalan adalah awalan dari 'toCheck':

std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()

Contoh lengkap prog:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char** argv) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "Will print true if 'prefix' is a prefix of string" << std::endl;
        return -1;
    }
    std::string prefix(argv[1]);
    std::string toCheck(argv[2]);
    if (prefix.length() > toCheck.length()) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "'prefix' is longer than 'string'" <<  std::endl;
        return 2;
    }
    if (std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()) {
        std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck << '"' << std::endl;
        return 0;
    } else {
        std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"' << toCheck << '"' << std::endl;
        return 1;
    }
}

Edit:

Seperti yang disarankan @James T. Huggett, std :: equal lebih cocok untuk pertanyaan: Apakah A awalan B? dan kode sedikit lebih pendek:

std::equal(prefix.begin(), prefix.end(), toCheck.begin())

Contoh lengkap prog:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char **argv) {
  if (argc != 3) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "Will print true if 'prefix' is a prefix of string"
              << std::endl;
    return -1;
  }
  std::string prefix(argv[1]);
  std::string toCheck(argv[2]);
  if (prefix.length() > toCheck.length()) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "'prefix' is longer than 'string'" << std::endl;
    return 2;
  }
  if (std::equal(prefix.begin(), prefix.end(), toCheck.begin())) {
    std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck
              << '"' << std::endl;
    return 0;
  } else {
    std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"'
              << toCheck << '"' << std::endl;
    return 1;
  }
}

2
Mengapa tidak menggunakan std :: sama?
Brice M. Dempsey

Terdengar bagus untukku. Itu akan menjadi kode yang lebih pendek juga. Saya kira, saya harus mengedit jawabannya sekarang: p
matiu

2
Menggunakan std::equaluntuk string memiliki kelemahan yang tidak mendeteksi akhir string, jadi Anda perlu memeriksa secara manual apakah awalan lebih pendek dari seluruh string. (Seperti yang dilakukan dengan benar dalam contoh prog, tetapi dihilangkan dalam satu-liner di atas.)
Felix Dombek

Jadi, tidak ada untungnya dibanding kulit?
Андрей Вахрушев

26

Mengingat bahwa kedua string - argv[1]dan "--foo"- adalah string C, jawaban @ FelixDombek adalah solusi terbaik.

Namun, melihat jawaban lain, saya pikir perlu dicatat bahwa, jika teks Anda sudah tersedia sebagai std::string, maka ada solusi sederhana, tanpa salinan, dan efisien maksimal yang belum disebutkan sejauh ini:

const char * foo = "--foo";
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(strlen(foo));

Dan jika foo sudah menjadi string:

std::string foo("--foo");
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(foo.length());

6
rfind(x, 0) == 0harus benar-benar didefinisikan dalam standar sebagaistarts_with
porges

1
Tidak, karena rfind()(sebagai pengganti startswith()) sangat tidak efisien - ia terus mencari hingga akhir string.
ankostis

4
@ankostis rfind (x) mencari dari akhir sampai awal sampai menemukan x, memang. Tapi rfind (x, 0) mulai mencari dari awal (posisi = 0) sampai awal; jadi itu hanya mencari di mana ia perlu mencari; tidak mencari dari / sampai akhir.
Anonim Coward

18

Dengan C ++ 17 Anda dapat menggunakan std::basic_string_view& dengan C ++ 20 std::basic_string::starts_withatau std::basic_string_view::starts_with.

Manfaat std::string_viewdibandingkan dengan std::string- mengenai manajemen memori - adalah bahwa ia hanya memegang pointer ke "string" (urutan berdekatan objek seperti char) dan tahu ukurannya. Contoh tanpa memindahkan / menyalin string sumber hanya untuk mendapatkan nilai integer:

#include <exception>
#include <iostream>
#include <string>
#include <string_view>

int main()
{
    constexpr auto argument = "--foo=42"; // Emulating command argument.
    constexpr auto prefix = "--foo=";
    auto inputValue = 0;

    constexpr auto argumentView = std::string_view(argument);
    if (argumentView.starts_with(prefix))
    {
        constexpr auto prefixSize = std::string_view(prefix).size();
        try
        {
            // The underlying data of argumentView is nul-terminated, therefore we can use data().
            inputValue = std::stoi(argumentView.substr(prefixSize).data());
        }
        catch (std::exception & e)
        {
            std::cerr << e.what();
        }
    }
    std::cout << inputValue; // 42
}

1
@RolandIllig Tidak, std::atoitidak apa-apa. Itu melempar pengecualian pada input buruk (yang ditangani dalam kode ini). Apakah Anda memikirkan hal lain?
Roi Danton

Apakah Anda berbicara tentang atoidari <cstdlib>? The dokumentasi mengatakan "tidak pernah melempar pengecualian".
Roland Illig

@RolandIllig saya mengacu pada komentar pertama Anda. Sepertinya, Anda salah bicara, atoialih - alih std::atoi. Yang pertama tidak aman untuk digunakan, sementara yang kedua baik-baik saja. Saya menggunakan yang terakhir dalam kode di sini.
Roi Danton

Tolong buktikan kepada saya bahwa std::atoimemang melempar pengecualian, dengan mengutip referensi yang sesuai. Sampai Anda melakukannya, saya tidak percaya Anda karena akan sangat membingungkan untuk memiliki keduanya ::atoidan std::atoibertindak dengan cara yang sama sekali berbeda.
Roland Illig

4
@RolandIllig Terima kasih telah gigih! Anda benar, itu adalah pengawasan yang std::atoidigunakan alih-alih std::stoi. Saya sudah memperbaikinya.
Roi Danton

12
text.substr(0, start.length()) == start

3
@ GrregorDoroschenko itu menjawab bagian "periksa apakah string dimulai dengan yang lain".
etarion

1
Efisien dan elegan menggunakan std :: string. Saya belajar paling banyak dari ini.
Michael B

1
poin tambahan untuk menjadi one-liner yang cocok untuk digunakan denganif (one-liner)
Adam.at.Epsilon

@Roland Illig Mengapa Anda percaya bahwa perilaku dalam kasus itu tidak terdefinisi? Ekspresi akan kembali salah karena substr mengembalikan string dengan panjang yang sama dengan teks sesuai dengan en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus

11

Menggunakan STL ini bisa terlihat seperti:

std::string prefix = "--foo=";
std::string arg = argv[1];
if (prefix.size()<=arg.size() && std::equal(prefix.begin(), prefix.end(), arg.begin())) {
  std::istringstream iss(arg.substr(prefix.size()));
  iss >> foo_value;
}

2
Itu seharusnya if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb

10

Dengan risiko dinyalakan karena menggunakan konstruksi C, saya pikir sscanfcontoh ini lebih elegan daripada kebanyakan solusi Peningkatan. Dan Anda tidak perlu khawatir tentang hubungan jika Anda menjalankan di mana saja yang memiliki juru bahasa Python!

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i) {
        int number = 0;
        int size = 0;
        sscanf(argv[i], "--foo=%d%n", &number, &size);
        if (size == strlen(argv[i])) {
            printf("number: %d\n", number);
        }
        else {
            printf("not-a-number\n");
        }
    }
    return 0;
}

Berikut adalah beberapa contoh output yang menunjukkan solusi menangani terkemuka / trailing sampah dengan benar sebagai kode Python yang setara, dan lebih tepat daripada apa pun yang menggunakan atoi(yang keliru mengabaikan akhiran non-numerik).

$ ./scan --foo=2 --foo=2d --foo='2 ' ' --foo=2'
number: 2
not-a-number
not-a-number
not-a-number

7
Jika argv[i]adalah "--foo=9999999999999999999999999", perilaku tidak terdefinisi (meskipun sebagian besar atau semua implementasi harus bersikap secara masuk akal). Saya berasumsi 9999999999999999999999999 > INT_MAX.
Keith Thompson

10

Saya menggunakan std::string::comparemetode utilitas terbungkus seperti di bawah ini:

static bool startsWith(const string& s, const string& prefix) {
    return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}

5

Mengapa tidak menggunakan getah gnu? Inilah contoh dasar (tanpa pemeriksaan keamanan):

#include <getopt.h>
#include <stdio.h>

int main(int argc, char** argv)
{
  option long_options[] = {
    {"foo", required_argument, 0, 0},
    {0,0,0,0}
  };

  getopt_long(argc, argv, "f:", long_options, 0);

  printf("%s\n", optarg);
}

Untuk perintah berikut:

$ ./a.out --foo=33

Kamu akan mendapatkan

33

5

Jika Anda membutuhkan kompatibilitas C ++ 11 dan tidak dapat menggunakan boost, berikut adalah drop-in yang kompatibel dengan boost dengan contoh penggunaan:

#include <iostream>
#include <string>

static bool starts_with(const std::string str, const std::string prefix)
{
    return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin()));
}

int main(int argc, char* argv[])
{
    bool usage = false;
    unsigned int foos = 0; // default number of foos if no parameter was supplied

    if (argc > 1)
    {
        const std::string fParamPrefix = "-f="; // shorthand for foo
        const std::string fooParamPrefix = "--foo=";

        for (unsigned int i = 1; i < argc; ++i)
        {
            const std::string arg = argv[i];

            try
            {
                if ((arg == "-h") || (arg == "--help"))
                {
                    usage = true;
                } else if (starts_with(arg, fParamPrefix)) {
                    foos = std::stoul(arg.substr(fParamPrefix.size()));
                } else if (starts_with(arg, fooParamPrefix)) {
                    foos = std::stoul(arg.substr(fooParamPrefix.size()));
                }
            } catch (std::exception& e) {
                std::cerr << "Invalid parameter: " << argv[i] << std::endl << std::endl;
                usage = true;
            }
        }
    }

    if (usage)
    {
        std::cerr << "Usage: " << argv[0] << " [OPTION]..." << std::endl;
        std::cerr << "Example program for parameter parsing." << std::endl << std::endl;
        std::cerr << "  -f, --foo=N   use N foos (optional)" << std::endl;
        return 1;
    }

    std::cerr << "number of foos given: " << foos << std::endl;
}

2

Anda juga dapat menggunakan strstr:

if (strstr(str, substr) == substr) {
    // 'str' starts with 'substr'
}

tapi saya pikir itu bagus hanya untuk string pendek karena harus mengulang seluruh string ketika string tidak benar-benar dimulai dengan 'substr'.


2

Ok mengapa rumit menggunakan perpustakaan dan barang-barang? C ++ String objek membebani operator [], jadi Anda bisa membandingkan karakter .. Seperti yang baru saja saya lakukan, karena saya ingin membuat daftar semua file dalam direktori dan mengabaikan file yang tidak terlihat dan .. dan. pseudofil.

while ((ep = readdir(dp)))
{
    string s(ep->d_name);
    if (!(s[0] == '.')) // Omit invisible files and .. or .
        files.push_back(s);
}

Sesederhana itu ..



2
@robertwb Google+ tidak lagi tersedia
_Static_assert

0
std::string text = "--foo=98";
std::string start = "--foo=";

if (text.find(start) == 0)
{
    int n = stoi(text.substr(start.length()));
    std::cout << n << std::endl;
}

3
Akan lebih bagus, jika Anda menghindari menempelkan kode tanpa penjelasan kode. Terima kasih.
Reborn

1
Kode yang tidak efisien, akan terus mencari melewati awal string.
ankostis

0

Dengan C ++ 11 atau lebih tinggi, Anda dapat menggunakan find()danfind_first_of()

Contoh menggunakan find untuk menemukan char tunggal:

#include <string>
std::string name = "Aaah";
size_t found_index = name.find('a');
if (found_index != std::string::npos) {
    // Found string containing 'a'
}

Contoh menggunakan find untuk menemukan string penuh & mulai dari posisi 5:

std::string name = "Aaah";
size_t found_index = name.find('h', 3);
if (found_index != std::string::npos) {
    // Found string containing 'h'
}

Contoh menggunakan find_first_of()dan hanya karakter pertama, untuk mencari di awal saja:

std::string name = ".hidden._di.r";
size_t found_index = name.find_first_of('.');
if (found_index == 0) {
    // Found '.' at first position in string
}

Semoga berhasil!


Mengapa tidak menemukan? rfind (str, 0) tidak akan perlu memindai seluruh string untuk membuat pilihan karena tidak dapat maju. Lihat yang lain.
user2864740

0

Karena C ++ 11 std::regex_searchjuga dapat digunakan untuk memberikan pencocokan ekspresi yang lebih kompleks. Contoh berikut ini menangani juga angka mengambang thorugh std::stofdan pemain berikutnyaint .

Namun parseIntmetode yang ditunjukkan di bawah ini bisa melempar std::invalid_argumentpengecualian jika awalan tidak cocok; ini dapat dengan mudah disesuaikan tergantung pada aplikasi yang diberikan:

#include <iostream>
#include <regex>

int parseInt(const std::string &str, const std::string &prefix) {
  std::smatch match;
  std::regex_search(str, match, std::regex("^" + prefix + "([+-]?(?=\\.?\\d)\\d*(?:\\.\\d*)?(?:[Ee][+-]?\\d+)?)$"));
  return std::stof(match[1]);
}

int main() {
    std::cout << parseInt("foo=13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-.9", "foo=") << std::endl;
    std::cout << parseInt("foo=+13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-0.133", "foo=") << std::endl;
    std::cout << parseInt("foo=+00123456", "foo=") << std::endl;
    std::cout << parseInt("foo=-06.12e+3", "foo=") << std::endl;

//    throw std::invalid_argument
//    std::cout << parseInt("foo=1", "bar=") << std::endl;

    return 0;
}

Jenis sulap dari pola regex terinci dengan baik dalam jawaban berikut .

EDIT: jawaban sebelumnya tidak melakukan konversi ke bilangan bulat.


0

Dimulai dengan C ++ 20, Anda dapat menggunakan starts_withmetode ini.

std::string s = "abcd";
if (s.starts_with("abc")) {
    ...
}

-3
if(boost::starts_with(string_to_search, string_to_look_for))
    intval = boost::lexical_cast<int>(string_to_search.substr(string_to_look_for.length()));

Ini sama sekali belum diuji. Prinsipnya sama dengan yang Python. Membutuhkan Boost.StringAlgo dan Boost.LexicalCast.

Periksa apakah string dimulai dengan string lain, lalu dapatkan substring ('slice') dari string pertama dan konversikan menggunakan lexical cast.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.