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));
}