Kirjoittaja: SiperianSiika
Kirjoitettu: 31.12.2010 – 22.11.2011
Tagit: teksti, koodi näytille, vinkki
Koodi tutustuttaa lukijan erilaisiin tapoihin käyttää C++:n standardikirjaston algoritmeja merkkijonon ympäri kääntämiseen. Erityisesti tutustutaan kopiointialgoritmeihin ja erikoisempiin iterattoreihin, kuten reverse_iteratoriin ja insertoivaan iteraattoriin. Lisäksi tavataan templateilla toteutettu myös unicode-merkkijonoa std::wstring tukeva stringinkääntöfunktio ja tulostetaan konsoliin unicodea.
// Includataan käytettävät headerit kauniisti järjestyksessä #include <algorithm> #include <iostream> #include <string> #include <cassert> #include <cstdio> // Käännä char-stringi. Tapa 1. // Luodaan insert-iteraattori, joka lisää merkkejä output-stringin loppuun. // Kopioidaan input-stringi käännettynä insert-iteraattoriin reverse_copyllä. std::string reverseString_1(const std::string &input) { std::string output; std::insert_iterator<std::string> out_iter(output, output.end()); std::reverse_copy(input.begin(), input.end(), out_iter); return output; } // Käännä char-stringi. Tapa 2. // Luodaan insert-iteraattori, joka lisää merkkejä output-stringin loppuun. // Kopioidaan input-stringi insert-iteraattoriin, mutta annetaan copy- // funktiolle iteraattori joka käy input-stringin läpi lopusta alkuun. std::string reverseString_2(const std::string &input) { std::string output; std::insert_iterator<std::string> out_iter(output, output.end()); std::copy(input.rbegin(), input.rend(), out_iter); return output; } // Käännä char-stringi. Tapa 3. // Käydään input-stringi läpi lopusta alkuun ja lisätään merkit // yksitellen output-stringin loppuun. Periaatteessa sama kuin tapa 2. std::string reverseString_3(const std::string &input) { std::string output; std::string::const_reverse_iterator r_it = input.rbegin(); for(; r_it != input.rend(); ++r_it) { output.push_back(*r_it); } return output; } // Käännä char-stringi. Tapa 4. // Alustetaan output-stringi oikean pituisella määrällä merkkejä. // Iteroidaan stringin merkkien yli kopioiden input-stringin // merkit käänteisessä järjestyksessä output-stringiin. std::string reverseString_4(const std::string &input) { std::string output(input.length(), '\0'); for(size_t i = 0; i < input.length(); ++i) { output[i] = input[input.length() - i - 1]; } return output; } // Käännä char-stringi. Tapa 5. // Kopioidaan input-stringi output-stringiin. // Käännetään output-stringi. std::string reverseString_5(const std::string &input) { std::string output = input; std::reverse(output.begin(), output.end()); return output; } // std::string on toteutukseltaan std::basic_string<char>, kun taas // unicode-merkkejä tukeva std::wstring on std::basic_string<wchar_t>. // Muunkinlaisia stringejä voisi periaatteessa määritellä. // Tehdään funktiotemplaatti, joka toimii näille kaikille. Kutsutaan // käytettävää merkkityyppiä nimellä char_t. Tällöin stringityyppi on // std::basic_string<char_t>. template<typename char_t> std::basic_string<char_t> reverseString(const std::basic_string<char_t> &input) { // Käyttömukavuuden vuoksi annetaan käytettävälle stringityypille // lempinimi string_t. typedef std::basic_string<char_t> string_t; // Käännetään merkkijono edellä esitellyllä tavalla 2. string_t output; std::insert_iterator<string_t> out_iter(output, output.end()); std::copy(input.rbegin(), input.rend(), out_iter); return output; } int main(int argc, char *argv[]) { // Testataan edellisten funktioiden toimivuus. // Makro assert keskeyttää ohjelman suorituksen, mikäli parametrin // ehto on epätosi. Tässä on kyseessä helppo ns. yksikkötesti, joiden // käyttäminen helpottaa ohjelmointia, sillä virheet havaitaan etukäteen // ja paikannetaan nopeasti. // Huomaa, että käännettäessä ohjelmasta julkaisuversiota, // assert-käskyt usein eliminoidaan hidastamasta ohjelmaa (esim. -DNDEBUG). std::string input = "Saippuakauppias"; std::string result = "saippuakauppiaS"; assert(reverseString_1(input) == result); assert(reverseString_2(input) == result); assert(reverseString_3(input) == result); assert(reverseString_4(input) == result); assert(reverseString_5(input) == result); // Testaa yleisemmän funktion toimivuus erityyppisillä parametreilla. // L"merkkijono" tarkoittaa wchar_t-stringiä, kun taas // "merkkijono" tarkoittaa char-stringiä. std::wstring winput = L"Säippyäkäyppiäs"; std::wstring wresult = L"säippyäkäyppiäS"; assert(reverseString(winput) == wresult); assert(reverseString(input) == result); // Aseta locale ja tulosta testitulos. Skandien tulisi näkyä. setlocale(LC_ALL, ""); std::wcout << L"Säippyäkäyppiäs ok!" << std::endl; }
Listalta puuttuu muuten pari helpointa.
Kopiointi appendilla toimii aavistuksen helpommin kuin std::copy-funktiolla:
std::string reverseString_6(const std::string &input) { std::string output; output.append(input.rbegin(), input.rend()); return output; }
Myös vastaava muodostin on olemassa, eli yhden rivin funktiokin onnistuu (jolloin täytyy miettiä, onko funktion tekemisessä järkeä ensinkään):
std::string reverseString_7(const std::string &input) { return std::string(input.rbegin(), input.rend()); } // Tai muualla koodissa: std::string input = "abcde"; std::string output(input.rbegin(), input.rend());
Kas vain! Ovela kettu olet!
Parametrin ottaminen const-referenssinä ja kopioiminen lokaaliin muuttujaan on itse asiassa huono idea, varsinkin modernissa C++:ssa. Jos funktio tarvitsee kopion, kannattaa ottaa parametri arvona ja käyttää sitä suoraan.
std::string reverseString_8(std::string input) { std::reverse(input.begin(), input.end()); return input; }
Tällä tavalla kutsujalla on mahdollisuus hyödyntää move-operaatioita ja välttää turha kopio.
void foo() { // väliaikaisarvo auto a = reverseString_8(std::string("...")); // std::move castaa a:n rvalue referenssiksi std::string b("..."); auto c = reverseString_8(std::move(b)); }