Ostatnio pojawił się u mnie w kodzie C++ wielokropek (..., ang. ellipsis). Rzecz nie była zbyt jasna, więc dzisiaj więcej szczegółów ;)
Zastanawialiście się kiedyś jak działa funkcja printf? Ma ona w sobie pewną magię, bo przyjmuje niemalże dowolną liczbę parametrów - coś, czego nie robią funkcje na co dzień. Spójrzmy na jej definicję:
int printf ( const char * format, ... );
Pojawia się tutaj tajemniczy wielokropek :) Jak go użyć w swojej aplikacji? Najlepiej wcale nie używać ;)
Zastosowanie najlepiej pokazać na kawałku kodu :)
#include <cstdarg>
double average(int n, ...) {
va_list list;
va_start(list, n);
double total = 0.0;
for (int i = 0; i < n; i++) {
total += va_arg(list, double);
}
va_end(list);
return total / n;
}
Ciekawsze rzeczy pogrubiłem:
1. #include <cstdarg> - Aby móc korzystać z wielokropka potrzebny jest nagłówek standardowy cstdarg. Tzn. jest to nam potrzebne do definiowania funkcji, deklarowanie funkcji z wielokropkiem spokojnie się powiedzie bez tego ;) Nagłówek ten udostępnia nam następujące elementy:
2. (int n, ...) - wielokropek może wystąpić tylko jako ostatni "argument" funkcji. Dodatkowo w C nie może on być jednym argumentem funkcji. W C++ nie ma takiego ograniczenia, ale... patrz punkt 4 ;)
3. va_list list - "va" to skrót od "variable arguments". Typ va_list to lista argumentów przekazana z pomocą wielokropka i to na niej będziemy pracować.
4. va_start(list, n) - makro inicjujące listę argumentów. Ważny jest drugi parametr tej funkcji - jest to argument, który jest tuż przed wielokropkiem na liście argumentów. Nie musi to być liczba argumentów - chociaż jest sensowne, aby tak było ;) Jak widać, w C++ można napisać deklarację funkcji przyjmującej wyłącznie wielokropek (niezgodne z C) - ale nie napiszemy do niej już sensownej definicji (tzn. wykorzystującej zmienną listę argumentów)...
5. va_arg(list, double) - to makro przetwarza kolejny argument z listy argumentów (1 parametr), przyjmując że ma on typ taki jak podany jako drugi parametr. Jak widać nie można kontrolować kolejności, w jakiej argumenty będą przetwarzane przez to makro. Również "przetworzenie" większej liczby argumentów niż jest na liście lub podanie błędnego typu nie wzruszy tej funkcji - ona po prostu interpretuje kolejne bity pamięci jak podany typ danych.
6. va_end(list) - to makro musimy wywołać na liście argumentów, aby po sobie elegancko posprzątać ;)
Jak widać nie jest to trudne... Ale dlaczego by z tego nie korzystać? Wyobraźmy sobie taki kod:
#include <iostream>
using namespace std;
int main() {
cout << average(6, 2.0, 3.0, 4.0, 7.0, 5.0, 8.0) << endl;
cout << average(7, 2.0, 3.0, 4.0, 7.0, 5.0, 8.0) << endl;
cout << average(6, 2.0, 3.0, 4.0, 7, 5.0, 8.0) << endl;
return 0;
}
Dostaniemy 3 różne wyniki, przy czym tylko pierwszy jest poprawny :) Niestety kompilator milczy jeżeli popełnimy jakiś prosty błąd:
1. Podamy mniej argumentów niż funkcja się spodziewa (2. wywołanie) - wtedy va_arg> pobierze kolejne bity pamięci znajdujące się za argumentami (jakieś śmieci) i zinterpretuje po swojemu - nieprzewidywalne rezultaty.
2. Podamy inny typ argumentu niż jest spodziewany (3. wywołanie) - wtedy znowu va_arg>, nie patrząc na nic, pobierze bity i zinterpretuje po swojemu - co skończy się kolejnym nieprzewidywalnym rezultatem.
Dobrze, że w C# jest to już lepiej rozwiązane ;)
czwartek, 27 maja 2010
Subskrybuj:
Komentarze do posta (Atom)
Brak komentarzy:
Prześlij komentarz