44
Tags:
c++
Skrevet af
Bruger #5688
@ 10.01.2005
Lad Os Komme I Gang
Teori er godt, men man lærer nu også ved at læse kode. Her er et simpelt program der benytter sig af en speciel container fra STL -- en
vector:
// Fig. 2.0.
#include <iostream>
#include <vector>
using namespace std;
// Ellers skulle vi skrive std::vector -- alle STL's containers er i std-namespacet.
int main(int argc, char* argv[]) {
vector<string> Vec;
for (int p = 1; p < argc; p++)
{
Vec.push_back(argv[p]);
// Placér en kopi af argv[p] i slutningen af Vec.
}
cout << "Programmet blev kørt med følgende parametre: " << endl;
vector<string>::iterator it = Vec.begin();
// Initialisering af iteratoren til begyndelsen af containeren.
while ( it != Vec.end() )
{
std::cout << *it << " ";
it++;
}
std::cout << endl;
return 0;
}
(Dette program løser ikke noget problem der ikke kunne klares uden brug af containere, men bær over med mig for eksemplets skyld).
Vi bruger containeren
std::vector, en container der er optimeret til hurtig lineær tilføjelse af elementer. En
vectors ydelse er meget ringe hvis man forsøger at benytte den til andet end at tilføje elementer i slutningen af den -- på denne måde virker
vector meget som den container der minder mest om et traditionelt array, og hvis man bare har brug for en simpel container, er det også denne jeg vil anbefale.
Det første vi gør i vor
main()-funktion er at oprette et
vector-objekt, og specificere hvilken type objekt det vil indeholde. Dette
skal gøres ved oprettelsen, og typen specificeres i vinkel-parenteser efter containerens typenavn, som det ses i koden. Dette er altså templateinstantieringen.
Vi gennemgår alle de parametre programmet er kaldt med, og gemmer dem i vores
vector. Dette sker gennem medlemsfunktionen
push_back(), der tager et argument af den type som den relevante
vector indeholder (i dette tilfælde konverteres
char* til
string), og tilføjer (en kopi af) argumentet til slutningen af
vectoren. Det skal retfærdigvis siges at en
vector har en maksimal størrelse, men denne afhænger af den relevante C++-implementation, og er generelt så høj at den ikke er værd at bekymre sig om. Du vil sandsynligvis løbe tør for virtuel hukommelse før du når at fylde en
vector op.
Når vi skal printe indholdet af vor
vector benytter vi os af iteratorer, som man passende kan tænke på som en pointer til et givent element i
vectoren. Læg især mærke til hvordan vi definerer iteratoren -- vi bruger ikke vores instans af en
vector, men derimod selve
vector-klassen. Vi tildeler iteratoren værdien der bliver returneret fra
Vec.begin(), en funktion der returnerer en iterator til det første element i
vectoren. Vi derefererer iteratoren ganske som med en pointer og printer det returnerede element på skærmen, hvorefter vi bruger postincrement-operatoren (++) til at flytte iteratoren til det næste element i
vectoren.
Vec.end() returnerer en iterator til et punkt efter det sidste element i
vectoren, derfor kan vi benytte funktionen til at checke om vores loop skal stoppes, uden at vi mangler at printe det sidste element. Denne itereringsteknik er en af de hyppigst benyttede container-teknikker, idet den både er relativt hurtig, sikker og simpel.
Vær dog opmærksom på at ikke alle containere understøtter denne fremgangsmåde. De har hver deres styrker, hvilket vil blive beskrevet senere.
Iteratorer er grundstenen i STL containers, og dem der gør de forskellige containers til mere end arrays med bounds checking. Det er også især ved iteratorerne at de forskellige containers skiller sig ud.
Iteratorer er den abstraktion der gør generiske algoritmer mulige:
Algoritmer og Containere
Ud over selve containerne, definerer STL også en række praktiske algoritmer til brug i forbindelse med behandling af iteratorer (eller rettere, den data iteratorerne refererer til). Algoritmer opererer ikke, som man skulle tro, på en container -- du kalder altså ikke f.eks. sort(Vec), men derimod sort(Vec.begin(), Vec.end()). Derved kan man nøjes med kun at sortere en mindre del af en container, eller endda sortere almindelige arrays -- pointers kan i næsten alle tilfælde bruges som iteratorer. Jeg vil præsentere brugen af udvalgte algoritmer, og vise en mulig måde de kan implementeres på.
Jeg vil nu demonstrere funktionen
sort(). For at forstå kravene en sådan funktion stiller, er det nødvendigt først at forstå at der findes forskellige kategorier af iteratorer, alt efter hvilke funktioner de understøtter (basalt set, hvilken adgang de giver til det underliggende objekt). Kategorierne er:
Input iteratorDenne form for iterator kan flytte til det næste element i containeren (men ikke det forrige), kan få adgang til det underliggende objekt (gennem
*-operatoren), follow-pointer-operatoren (
->) og er-ikke-lig-med-operatoren (
!=). Denne iterator giver også kun read-only adgang til det element der peges på. En iterator der udelukkende har disse operatorer defineret vil skabe compile-errors hvis vi forsøger at bruge andre operatorer. Alle containere i C++'s STL understøtter
mindst disse operatorer, hvilket vil sige at alle iteratorer er input-iteratorer. De kan også tilhøre andre iteratorkategorier, forudsat at de opfylder de relevante kategoriers krav, men de er i hvert fald input-iteratorer, idet de opfylder input-iterator-gruppens krav. Hvis man tilknytter en iterator til
cin er der tale om en input iterator.
Output iteratorOutput-iteratorer er som input-iteratorer, bortset fra at de kun tillader at der skrives til det underliggende objekt, ikke at det læses (f.eks.
*it = foo). Derudover er de magen til input-iteratorer. Alle iteratorer i C++'s STL er, ganske som med input-iteratorer, output-iteratorer. En output-iterator ses ofte i forbindelse med fil-output. Du kan skrive til en fil, og du kan rykke fremad i filen, men hvis du kun har åbnet filen til output, kan du naturligvis ikke læse fra den.
Forward IteratorDisse iteratorer fungerer som input-iteratorer, i den forstand at de kun kan flytte til næste objekt, men til gengæld understøtter de både skrivning og læsning af og til det underliggende element. Som med de to foregående typer, understøtter alle iteratorer i C++'s STL disse krav, og er derfor forward iterators.
Bidirectional IteratorBirectional iterators skal understøtte alle forward-iterator operatorer, men skal derudover også understøtte at gå til forrige element. Alle containere i STL opfylder kravene til bidirectional iterators.
Random Access IteratorDenne iterator er den mest kraftfulde i C++'s STL, og kun få containere understøtter den. Basalt set kræver den at man kan udførearitmetik med iteratoren. F.eks. trække to iteratorer fra hinanden -- hvis man f.eks. trækker en iterator der peger på det 3. element fra en iterator der peger på det 8. element, skal man få en iterator der peger på det 5. element. I mange containere ville denne iterator slet ikke give mening, idet der ikke er tale om en lineær container, men snarere en "pøl" af data. Containers der har random access iterators, understøtter også
[]-operatoren, kendt fra arrays.
std::string og
std::vector opfylder begge random access iteratorens krav (idet begge containere er lineære repræsentationer af data).
Det virker måske absurd at der findes så primitive iteratorer som input-iterators, idet det jo er svært at se hvorfor nogen iterator skulle være så begrænset, og hvad man overhovedet kan bruge den til. En sådan iterator ville være praktisk, hvis man f.eks. havde en kompleks database, hvor man ikke ville være det i stand til at definere at et objekt kommer "efter" et andet, hvilket vil sige at der bliver byttet om på rækkefølgen af data hver gang man opretter en iterator. I et sådant tilfælde ville en input iterator være praktisk (eller nødvendig), idet man ved at inkrementere den nok gange eventuelt ville læse hele databasen (hvis klassen da havde et system der forhindrer iteratoren i at pege på samme data to gange).
sort() kræver random access adgang via random access iteratorer, og compileren vil melde fejl hvis man forsøger at kalde funktionen med iteratorer der ikke er i denne klasse.
Med denne teori i baghovedet burde det være til at forstå hvordan
sort() skal bruges:
// Fig. 3.0.
#include <iostream>
#include <algorithm> // Indeholder de fleste generiske algoritmer.
#include <vector>
using namespace std;
int main()
{
vector<int> intVec;
// Opretter en list på samme måde som med vector.
int temp;
cout << endl << "Indtast et eller flere heltal, adskilt af mellemrum. EOF for at udregne: ";
while (cin >> temp) // Mens der er input i cin...
{
intVec.push_back(temp);
}
cout << endl;
sort(intVec.begin(), intVec.end()); // Sorterer alle elementer fra begin() til end().
for ( vector<int>::const_iterator it = intVec.begin();
it != intVec.end();
it++ )
cout << *it << " ";
cout << endl;
return 0;
}
Udover
sort() selv, er der intet nyt i dette program. Programmet tager EOF (Ctrl-D på de fleste operativsystemer) for at afbryde inputsekveksen. for-loopet er også mere kompakt end i det forrige eksempel, men princippet er det samme (læg dog mærke til at der benyttes en read-only
const_iterator -- denne bør altid benyttes, når man ikke vil ændre på iteratorens underliggende objekt, ganske som med traditionel
const).
sort() tager to iteratorer som parametre, der definerer hvilken sekvens af objekter der skal sorteres (praktisk, hvis man ikke ønsker at sortere hele containeren). Eftersom vores
vector består af
ints, har compileren ikke svært ved at sammenligne de respektive elementer, men hvis vi havde en
vector der indeholdt klasser der ikke kunne sammenlignes med '<'-operatoren , eller vi bare ønskede at sortere på en anden måde, kunne vi bruge en funktion som det tredje argument. En sådan funktion kaldes et
prædikat(predicate), og returnerer
bool. F.eks:
// Fig. 3.1.
sort(Vec.begin(), Vec.end(), compare);
Hvor
compare() er en funktion der tager to argumenter af samme type som Vec indeholder, og som returnerer
true, hvis det andet argument er "større" (hvad dette betyder i denne sammenhæng, afhænger af programmøren) end det første.
F.eks:
// Fig. 3.2.
bool compare(int x, int y)
{
return x % 2 == 0;
}
/* Kode... */
sort(Vec.begin(), Vec.end(), compare);
Dette vil resultere i at alle de tal der kan deles med 2 (alle runde tal), bliver placeret i starten af
vectoren.
Man bør altid bruge denne
sort() funktion, frem for
qsort(), som C++ har arvet fra C.
En anden generisk algoritme er
fill(). Den bruges således:
// Fig. 3.3.
fill(begin, end, value)
Denne algoritme vil tildele alle elementer i intervallet [
begin;
end[ værdien
value.
Man kunne forestille sig
fill() være implementeret således:
// Fig. 3.4.
template<class B, class E, class V>
void fill(B beg, E end, V value)
{
while (beg != end) *beg++ = value;
}
Bemærk at denne algoritme kan benyttes både med pointers og rigtige iteratorer. Betragt:
// Fig. 3.5.
#include <vector>
#include <string>
#include <iostream>
using namespace std;
template<class B, class E, class V>
void fill(B beg, E end, V value)
{
while (beg != end) *beg++ = value;
}
template<class B, class E, class P>
void print_container(B beg, E end, P padding)
{
while (beg != end) cout << *beg++ << padding;
}
int main(int argc, char** argv)
{
int array[] = { 1, 2, 3, 4, 5, 6, 7 };
vector<string> lang_vec;
lang_vec.push_back("Java");
lang_vec.push_back("C");
lang_vec.push_back("Python");
lang_vec.push_back("ECMAScript");
lang_vec.push_back("Pascal");
lang_vec.push_back("C#");
print_container(array, array + 7, "\\n");
print_container(lang_vec.begin(), lang_vec.end(), "\\n");
cout << endl;
fill(array, array + 7, 1337);
fill(lang_vec.begin(), lang_vec.end(), "C++");
cout << endl;
print_container(array, array + 7, "\\n");
print_container(lang_vec.begin(), lang_vec.end(), "\\n");
}
Ganske elegant. Selvom dette program er simpelt, demonstrerer det alligevel de grundlæggende ideer bag generiske algoritmer.
Hvilke iteratorer kræver de to funktioner så? Det er tydeligt at
fill() ikke kan klare sig med read-only iteratorer, men eftersom de eneste funktioner der bliver benyttet er
operator*,
operator= og postfix
operator++ burde en output iterator være acceptabel.
print_container kan klare sig med en
input iterator -- så simpel er funktionen.
Prædikater og Funktionsobjekter
Ovenfor nævnte jeg kort hvorledes man kunne specificere sin egen sammenligningsfunktion i
sort(). Jeg brugte der en funktionspointer, men det er langt mere normalt at bruge funktionsobjekter (også kendt som functors). Disse er objekter hvor
operator() er defineret:
// Fig. 4.0.
class numfinder
{
public:
numfinder(int x, int y):
m_x(x), m_y(y)
{}
bool operator()(int i)
{
if ( i == m_x || i == m_y )
return true;
return false;
}
private:
int m_x;
int m_y;
};
For at vise hvordan disse fungerer, ser vi på en logisk implementation af
find() og
find_if(), hvor
find_if() bruger et prædikat:
// Fig. 4.1.
#include <iostream>
using namespace std;
class numfinder
{
public:
numfinder(int x, int y):
m_x(x), m_y(y)
{}
bool operator()(int i)
{
if ( i == m_x || i == m_y )
return true;
return false;
}
private:
int m_x;
int m_y;
};
template <class B, class E, class V>
B find(B begin, E end, V value)
{
while (begin != end)
if (*begin++ == value)
return begin;
return end;
}
template <class B, class E, class P>
B find_if(B begin, E end, P predicate)
{
while (begin != end)
if (predicate(*begin++))
return begin;
return end;
}
int main(int argc, char** argv)
{
int numarray[] = { 1, 2, 3, 4, 5, 6, 7, 888, 666, 1337, 1987, 3, 2, 1 };
cout << "Elementet er på den "
<< find_if(numarray, numarray + 14, numfinder(7, 3)) - numarray + 1
<< ". plads."
<< endl;
return 0;
}
find() og
find_if() returnerer den sidste iterator i sekvensen, hvis den ønskede værdi ikke kan findes, eller iteratoren der refererer til den ønskede værdi i containeren, hvis den kan findes.
find_if() bruger blot et simpelt prædikat til dette. Det trick jeg bruger for at finde hvilken plads i sekvensen resultatet har, er noget jeg kan gøre fordi jeg i dette eksempel bruger pointers og arrays. Pointers er effektivt det samme som random-access iterators. Dette eksempel er interessant, fordi det viser at man, uden at udvide selve algoritmen, kan få en
find() til at lede efter to forskellige værdier. I princippet er der ingen grænser for hvor kompliceret logik der kan indbygges i et prædikat. Se f.eks. dette, der leder efter to på hinanden følgende værdier:
// Fig. 4.2.
template <class C>
class numfinder_seq
{
public:
numfinder_seq(C x, C y):
m_x(x), m_y(y)
{}
bool operator()(C val)
{
if ( m_last == m_x && val == m_y )
return true;
m_last = val;
return false;
}
private:
C m_last;
C m_x;
C m_y;
};
Der er adskillige fordele ved at bruge function objects frem for function pointers. For det første er det trivielt for compileren at inline funktionskaldende, hvorved man sparer den overhead der er associeret med ethvert funktionskald, og ydermere kan funktionen gemme information om sig selv imellem kaldende, uden brug af statisk data.
max_element() og
min_element returnerer hver en iterator der refererer til hhv. det største og det mindste element i sekvensen. Disse to algoritmer kunne implementeres, og bruges, således:
// Fig. 4.3.
#include <iostream>
#include <vector>
using namespace std;
template <class B, class E>
B max_element(B begin, E end)
{
B* cur_max = begin;
while (++begin != end)
if (*cur_max < *begin)
cur_max = &begin;
return *cur_max;
}
template <class B, class E>
B min_element(B begin, E end)
{
B* cur_min = begin;
while (++begin != end)
if (*cur_min < *begin)
cur_min = &begin;
return *cur_min;
}
int main()
{
vector<int> num_vec;
num_vec.push_back(5);
num_vec.push_back(3);
num_vec.push_back(1337);
num_vec.push_back(66*72);
num_vec.push_back(2);
num_vec.push_back(3);
cout << "Max: " << *max_element(num_vec.begin(), num_vec.end()) << endl;
cout << "Min: " << *min_element(num_vec.begin(), num_vec.end()) << endl;
return 0;
}
Jeg bruger en pointer til en iterator, for jeg kan ikke være sikker på at en iterator er et letvægtsobjekt, så jeg vil helst undgå dyr initialisering. Det kan også lade sig gøre at lave en
max_element_if(), hvor et binært prædikat (en funktion der tager to parametre) benyttes:
// Fig. 4.4.
template <class B, class E, class P>
B max_element_if(B begin, E end, P pred = P())
{
B* cur_max = begin;
while (++begin != end)
if (pred(*cur_max, *begin))
cur_max = &begin;
return *cur_max;
}
copy() er en af de mest fleksible algoritmer i STL. Dens mest åbenlyse brug, er kopiering af en sekvens til en ny container:
// Fig. 4.5.
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
int main(int argc, char** argv)
{
vector<string> src_vec(5); // vector med 5 elementer.
src_vec[0] = "Chadk";
src_vec[1] = "Frnz";
src_vec[2] = "HyBreeD";
src_vec[3] = "KasperTSW";
src_vec[4] = "Athas";
vector<string> dest_vec(5); // vector med 5 elementer.
copy(src_vec.begin(), src_vec.end(), dest_vec.begin());
src_vec.clear();
for(vector<string>::const_iterator it = dest_vec.begin();
it != dest_vec.end();
it++)
cout << *it << endl;
return 0;
}
copy() er en funktion man skal passe på med. Her ses det at jeg opretter to
vector-objekter, hver med plads til fem elementer, for derefter at tildele en værdi til elementerne i
src_vec.
copy() fungerer ved at hvert element i den angivne sekvens (her
src_vec.begin(),
src_vec.end()) tildeles til destinationen (her
dest_vec.begin()), som inkrementeres efter hver værditildeling, med det resultat at iteratoren bevæger sig til det næste element i containeren. Det er her man skal passe på, for
copy() kender ikke til destinationens størrelse, og vil uden videre lade destinationsiteratoren vandre ind i hukommelse uden for containeren. Derfor specificerer jeg eksplicit at
dest_vec har fem elementer -- så er jeg sikker på at den ikke overflower. Dette holder jo naturligvis ikke, derfor findes der en wrapper-klasse, der fungerer som en iterator, men som kalder push_back på den relevante container, når der tildeles til iteratoren. Deklarationen af
dest_vec og kaldet af
copy() kan altså erstattes med:
// Fig. 4.6.
vector<string> dest_vec;
copy(src_vec.begin(), src_vec.end(), back_inserter(dest_vec));
Ligeledes findes der
front_inserter, der, som navnet antyder, bruger
push_front(), frem for
push_back().
Jeg bruger i Fig. 4.5 en klassisk
for-konstruktion til at printe indholdet af containeren... det virker, men det er ikke synderligt elegant. Her kan
copy() og opfindsom brug af wrapper-iteratorer også hjælpe til. Den iteratorwrapper vi her skal bruge hedder
ostream_iterator, der er at finde i
<iterator. Den tager to argumenter, den stream den skal skrive til (i vores tilfælde,
cout), og en seperator, der udskrives imellem hvert element.
ostream_iterator er en template, og den instantieres med den type som den modtager. Vi kan erstatte
for-konstruktionen med følgende kald af
copy():
// Fig. 4.7.
copy(dest_vec.begin(), dest_vec.end(), ostream_iterator<string>(cout, "\\n"));
Her vises en revideret udgave af programmet i Fig. 4.5, der har de to forbedringer jeg nævnte her (og som ikke længere behøver at specificere størrelsen på sine
vector-objekter eksplicit):
// Fig. 4.8.
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <iterator>
using namespace std;
int main(int argc, char** argv)
{
vector<string> src_vec;
src_vec.push_back("Chadk");
src_vec.push_back("Frnz");
src_vec.push_back("HyBreeD");
src_vec.push_back("KasperTSW");
src_vec.push_back("Athas");
vector<string> dest_vec;
copy(src_vec.begin(), src_vec.end(), back_inserter(dest_vec));
src_vec.clear();
copy(dest_vec.begin(), dest_vec.end(), ostream_iterator<string>(cout, "\\n"));
return 0;
}
ostream_iterator skal fungere som en iteratoremulator, men i princippet er der jo ikke meget iterator over den. Der er intet element at rykke videre til, og det ville ikke give mening at rykke tilbage. Alligevel er
ostream_iterator en output iterator.
operator= for et
ostream_iterator objekt er som at kalde
operator<< for den relevante stream. Det er ikke specificeret hvad der sker når man kalder postfix
operator++ for et
ostream_iterator-objekt, men en given implementation kan have valgt at det først er når denne kaldes, at der rent faktisk skrives til den relevante
ostream.
back_inserters og
ostream_iterators er abstraktioner der kun er mulige pga. C++'s elegante template, stream og iterator-design. Som det sås ovenfor, ved Fig. 4.5 , skal man dog også passe på, for der er grænser for hvor meget de individuelle algoritmer ved om de objekter de behandler, hvilket er årsagen til at wrappere som
ostream_iterator og
back_inserter eksisterer. Som algoritmeprogrammør kan det dog være nyttigt at kende til visse detaljer omkring iteratorerne, f.eks. hvilke typer de refererer til, og dette klares via
iterator_traits-klassen.
iterator_traits er en template-klasse indeholdende typedef's. Idéen er at man, når man laver en ny iterator, laver en template specialization, så
iterator_traits også fungerer for denne. Via template specialization fungerer klassen også med pointers. Her er definitionen:
// Fig. 4.9.
template <class Iter>
struct iterator_traits
{
typedef typename Iter::iterator_category iterator_category;
typedef typename Iter::value_type value_type;
typedef typename Iter::difference_type difference_type;
typedef typename Iter::pointer pointer;
typedef typename Iter::reference reference;
};
template <class T>
struct iterator_traits<T*>
{
typedef std::random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
Hvis en iterator er en klasse i sig selv, vil den, hvis den har defineret de rigtige typer i sin deklaration, automatisk virke i
iterator_traits. Hvis ikke, kan man lave template specialization, såsom det kan ses at det er gjort med pointers.
random_access_iterator_tag er en "tom" klasse, der udelukkende er beregnet til at blive brugt til template specialization og lignende, og den indikerer at iteratoren er en random access iterator. Ligeså findes der klasserne
input_iterator_tag,
output_iterator_tag,
forward_iterator_tag og
bidirectional_iterator_tag.
ptrdiff_t er den type man får når man trækker to pointere fra hinanden. Som regel er dette en
int.
// Fig. 4.10.
#include <iostream>
#include <iterator>
#include <vector>
#include <list>
#include <string>
using namespace std;
template<class B, class E>
vector<typename iterator_traits<B>::value_type> mkvector(B begin, E end)
{
vector<typename iterator_traits<B>::value_type> ret;
copy(begin, end, back_inserter(ret));
return ret;
}
int main(int argc, char** argv)
{
list<int> src_vec;
typedef list<int>::value_type src_type;
src_vec.push_back(5);
src_vec.push_back(4);
vector<src_type> dest_vec = mkvector(src_vec.begin(), src_vec.end());
copy(dest_vec.begin(), dest_vec.end(), ostream_iterator<int>(cout, "\\n"));
return 0;
}
I Fig. 4.10 er der vist en funktion der kan producere
vector-objekter fra input iteratorer. Den type en given
vector skal indeholde skal defineres ved templateinstantiering, ved compile-time, så her bruger jeg
iterator_traits til at få information om den type iteratorerne refererer til. Jeg bruger eksplicit
typename, for at give compileren et hint om at jeg mener en type, og ikke et udtryk.
typename er nødvendigt når templateparametre benyttes på en så relativt kompleks måde. Der vises hvordan en container af typen
list kan omdannes til en
vector, men funktionen burde fungere med enhver gyldig sekvens.
list er en container jeg ikke har nævnt før, og den vil blive beskrevet i næste sektion.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (10)
meget lang.. eventuelt split den op i 2? 3?
Den kan ikke splittes op uden at forstyrre sammenhængen. Det er et meget komplekst emne.
En rigtig god artikel... den har fortjent en femmer
Flot artikel... du får en femmer herfra.
Jeg håber du senere får tid til at skrive lidt mere om templates.
god artikel
Efterfølgeren bliver ikke nær så stor, men den skal nok komme engang.
God artikel, lærte godt nok ikke noget nyt, men håber at andre gjorde ;-)
Selvom jeg ikke lærte noget, så du får skam stadig 5 :-P
Jeg vil opfordre alle andre til at læse meget mere om dette emne, for der er rent faktisk stor ydelsesmæssig forskel på de forskellige containers. Det blev der ikke nævnt særligt meget om i denne artikel, så jeg synes at jeg burde fremhæve det her.
Dejligt med en artikel som bevæger sig ud over det rene begynder niveau. Eneste minus - som forfatteren ikke kan gøre noget ved - er at man ikke kan hente artiklen i fx. PDF format ;-).
Forsæt endelig det gode arbejde.
Jeg er ny bruger
Udemærket artikel.
Jeg har kun skimmet den, da jeg har nogle ret velskrevne, uddybende refs til STL/templates på engelsk. Jeg vil give artiklen 4. Fordi: Indholdet er solidt, men strukturen er lidt forvirrende, og den er måske mere forvirrende for folk, der intet kender til emnet.
Jeg vil som nogle andre skrev ovenfor også klart mene, at det ville være fedt, hvis forfatteren skrev videre på artiklen. MEN Det BEHØVER vel ikke egentlig ikke gøres af den samme person? Måske kunne NOGEN føre artiklen videre, hvis forfatteren ikke har tid. Rigtig "open source"-stil!
Jeg har surfet lidt rundt her på siden og synes, at der mangler en rigtig "børnehave"-C++ tutorial.
Noget kunne jo "for skæg" sende et "manuskript" ind til censur og se, hvad "redaktøren" siger?
Personligt ville jeg sagtens kunne skrive en "lille" tekst, som indtroducerer sproget C/C++ på en MEGET, MEGET pædagogisk måde, så de sidste 10%, der er bange for programmering kan komme med.
Idé?
Nicolai Lystner Fersner: Hvad er forskellen på ydelse mellem de forskellige containere? Hvilke containere snakker vi om? God kommentar, lærte godt nok ikke noget nyt, og tvivler andre gjorde.
Stefan E. Nielsen: Du kan jo læse lidt af artiklen, holde pause, arbejde med det, du har lært, og så læse videre. På den måde vil artiklen ikke virke så lang.
Christian Dyrnesli: Hvad holder dig tilbage? Det virker som en god idé, synes jeg.
Troels Henriksen: God artikel! Jeg har arbejdet med templates før, men du har vist mig nogle metoder at anvende dem på, jeg end ikke havde overvejet.
Du skal være
logget ind for at skrive en kommentar.