2013-01-23 8 views
11

ich irgendwie diesen Bereich für Schleifen basierend angenommen würde C-StringsBereich basiert für Schleifen auf null Strings beendet

void print_C_str(const char* str) 
{ 
    for(char c : str) 
    { 
     cout << c; 
    } 
} 

Dies ist jedoch nicht der Fall ist, die Standard-[stmt.ranged] (6.5.4) sagt, dass bereichsbasierte-für Unterstützung arbeitet in einer von 3 Möglichkeiten:

  1. der Bereich ist ein Array
  2. der Bereich wird eine Klasse mit einer abrufbaren begin und end Verfahren
  3. Es ist ADL erreichbar in einem zugehörigen Namensraum (plus std Namespace)

Wenn ich begin und end Funktionen für const char* im globalen Namespace füge ich noch Fehler auftreten (von beiden VS12 und GCC 4.7).

Gibt es eine Möglichkeit, range-based-for-Schleifen mit C-Stil-Strings zu arbeiten?

Ich habe versucht, eine Überlastung zu namespace std das Hinzufügen und das hat funktioniert, aber nach meinem Verständnis ist es illegal Überlastungen namespace std hinzufügen (ist das richtig?)

+2

können Sie rechtlich Vorlagen in std Namensraum spezialisiert. – inf

+6

@bamboon true aber IIRC nur für benutzerdefinierte Typen und dies ist eine Überladung keine Spezialisierung und für einen eingebauten Typ, kein UDT. – Motti

+1

Warum passierst du C-Saiten? –

Antwort

19

Wenn Sie einen trivialen Iterator für nullterminierte Strings schreiben, können Sie dies tun, indem Sie eine Funktion für den Zeiger aufrufen, der einen speziellen Bereich zurückgibt, anstatt den Zeiger selbst als Bereich zu behandeln.

template <typename Char> 
struct null_terminated_range_iterator { 
public: 
    // make an end iterator 
    null_terminated_range_iterator() : ptr(nullptr) {} 
    // make a non-end iterator (well, unless you pass nullptr ;) 
    null_terminated_range_iterator(Char* ptr) : ptr(ptr) {} 

    // blah blah trivial iterator stuff that delegates to the ptr 

    bool operator==(null_terminated_range_iterator const& that) const { 
     // iterators are equal if they point to the same location 
     return ptr == that.ptr 
      // or if they are both end iterators 
      || is_end() && that.is_end(); 
    } 

private: 
    bool is_end() { 
     // end iterators can be created by the default ctor 
     return !ptr 
      // or by advancing until a null character 
      || !*ptr; 
    } 

    Char* ptr; 
} 

template <typename Char> 
using null_terminated_range = boost::iterator_range<null_terminated_range_iterator<Char>>; 
// ... or any other class that aggregates two iterators 
// to provide them as begin() and end() 

// turn a pointer into a null-terminated range 
template <typename Char> 
null_terminated_range<Char> null_terminated_string(Char* str) { 
    return null_terminated_range<Char>(str, {}); 
} 

Und Nutzung sieht wie folgt aus:

for(char c : null_terminated_string(str)) 
{ 
    cout << c; 
} 

Ich glaube nicht, dass dies irgendeine Aussagekraft verliert. Eigentlich denke ich, dass dieser klarer ist.

+4

+1 sind, es ist * sauberer, weil es korrekt mit 'char *' und 'char []' zusammenarbeitet.Das Problem mit 'char []' ist, dass es nativ funktioniert, aber die falsche Sache tut (es wird wie jedes C-Array behandelt, statt einer Null-terminierten Zeichenkette und folglich ist ein Element zu lang. –

+0

+1 Aus Neugier, Gibt es einen Grund, warum diese Lösung "für (char c: std :: string (str))" vorzuziehen wäre? Dies scheint mir eine offensichtliche Lösung zu sein. Da niemand sie als Antwort gepostet hat, kann ich nur vermuten, dass es da ist etwas, das ich vermisse.In jeder dieser Lösungen, die du gepostet hast oder mit 'std :: string' hast, muss etwas zusätzliches konstruiert werden, um die Iteration auszuführen: – hmjd

+2

@hmjd Ja, 'for (char c: std :: string (str))' ist ein Der Vorteil dieses Ansatzes gegenüber std :: string ist, dass diese Abstraktion einen sehr geringen Laufzeitaufwand hat: Die zusätzlichen Objekte, die konstruiert werden, sind extrem billig, im Gegensatz zu std :: string, das eine dynamische Zuweisung beinhalten kann und die ganze Zeichenfolge hineinkopiert einen eigenen Puffer, im Grunde würde ich zwar nicht stöhnen, aber std :: string sehen Wenn dies verwendet wird (es sei denn, das Profiling hat sich als Performance-Problem erwiesen), bietet dies genau die gewünschte Funktionalität, Iteration, während std :: string unnötige Funktionalität bietet und das kostet. –

2

Ein C-String kein Array ist, ist es nicht eine Klasse, die hat begin/end Mitglieder, und Sie werden nichts von ADL finden, weil das Argument ein Primitiv ist. Argumentiert sollte dies klar unqualifizierte Suche sein, mit ADL, würde eine Funktion im globalen Namespace finden. Aber angesichts der Formulierung denke ich, dass das nicht möglich ist.

2

Eine mögliche Problemumgehung besteht darin, die nullterminierte Zeichenfolge in einem anderen Typ zu umbrechen. Die einfachste Implementierung ist wie folgt (es ist weniger performant als R. Martinho Fernandes Vorschlag, da es ruft strlen, aber es ist auch deutlich weniger Code).

class null_terminated_range { 
    const char* p: 
public: 
    null_terminated_range(const char* p) : p(p) {} 
    const char * begin() const { return p; } 
    const char * end() const { return p + strlen(p); } 
}; 

Verbrauch:

for(char c : null_terminated_range(str)) 
Verwandte Themen