Oto, co odkryłem podczas mojej nauki:
#include<iostream>
using namespace std;
int dis(char a[1])
{
int length = strlen(a);
char c = a[2];
return length;
}
int main()
{
char b[4] = "abc";
int c = dis(b);
cout << c;
return 0;
}
Więc w zmiennej int dis(char a[1])
The [1]
zdaje się nic nie robić i nie działa w
ogóle, bo mogę korzystać a[2]
. Tak jak int a[]
lub char *a
. Wiem, że nazwa tablicy jest wskaźnikiem i jak przekazać tablicę, więc moja zagadka nie dotyczy tej części.
Chcę wiedzieć, dlaczego kompilatory zezwalają na to zachowanie ( int a[1]
). A może ma inne znaczenia, o których nie wiem?
typedef
z typem tablicy. Więc „zaniku do wskaźnika” w rodzaju argument nie jest po prostu cukier syntaktyczny zastępując[]
ze*
to się naprawdę dzieje za pośrednictwem systemu typu. Ma to rzeczywiste konsekwencje dla niektórych standardowych typów, takich jak ten,va_list
który może być zdefiniowany za pomocą typu tablicowego lub nie-tablicowego.int dis(char (*a)[1])
. Następnie należy przekazać wskaźnik do tablicy:dis(&b)
. Jeśli chcesz użyć funkcji C, które nie istnieją w C ++, możesz również powiedzieć takie rzeczy jakvoid foo(int data[static 256])
iint bar(double matrix[*][*])
, ale to zupełnie inna puszka robaków.Odpowiedzi:
Jest to dziwactwo składni przekazywania tablic do funkcji.
W rzeczywistości nie jest możliwe przekazanie tablicy w C. Jeśli piszesz składnię, która wygląda tak, jakby powinna przekazywać tablicę, w rzeczywistości jest przekazywany wskaźnik do pierwszego elementu tablicy.
Ponieważ wskaźnik nie zawiera żadnych informacji o długości, zawartość your
[]
na liście parametrów formalnych funkcji jest w rzeczywistości ignorowana.Decyzja o dopuszczeniu tej składni została podjęta w latach siedemdziesiątych XX wieku i od tego czasu wywołała wiele zamieszania ...
źródło
void foo(int (*somearray)[20])
składni. w tym przypadku 20 jest egzekwowane w witrynach wywołujących.[]
nie są ignorowane w tablicach wielowymiarowych, jak pokazano w odpowiedzi pat. Dlatego dołączenie składni tablicy było konieczne. Ponadto nic nie stoi na przeszkodzie, aby kompilator wydawał ostrzeżenia nawet w przypadku tablic jednowymiarowych.void foo(int (*args)[20]);
Ponadto, ściśle mówiąc, C nie ma wielowymiarowych tablic; ale ma tablice, których elementami mogą być inne tablice. To niczego nie zmienia.Długość pierwszego wymiaru jest ignorowana, ale długość dodatkowych wymiarów jest konieczna, aby umożliwić kompilatorowi prawidłowe obliczenie przesunięć. W poniższym przykładzie do
foo
funkcji przekazywany jest wskaźnik do dwuwymiarowej tablicy.#include <stdio.h> void foo(int args[10][20]) { printf("%zd\n", sizeof(args[0])); } int main(int argc, char **argv) { int a[2][20]; foo(a); return 0; }
Rozmiar pierwszego wymiaru
[10]
jest ignorowany; kompilator nie przeszkodzi w indeksowaniu z końca (zauważ, że formal potrzebuje 10 elementów, ale faktyczny dostarcza tylko 2). Jednak rozmiar drugiego wymiaru[20]
jest używany do określenia kroku każdego wiersza, a tutaj formalny musi być zgodny z rzeczywistym. Ponownie, kompilator również nie uniemożliwi indeksowania końca drugiego wymiaru.Przesunięcie bajtów od podstawy tablicy do elementu
args[row][col]
jest określane przez:sizeof(int)*(col + 20*row)
Zauważ, że jeśli
col >= 20
, to faktycznie indeksujesz do następnego wiersza (lub poza końcem całej tablicy).sizeof(args[0])
, wraca80
na moim komputerze, gdziesizeof(int) == 4
. Jeśli jednak spróbuję to zrobićsizeof(args)
, otrzymuję następujące ostrzeżenie kompilatora:foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument] printf("%zd\n", sizeof(args)); ^ foo.c:3:14: note: declared here void foo(int args[10][20]) ^ 1 warning generated.
W tym przypadku kompilator ostrzega, że zamiast rozmiaru samej tablicy poda tylko rozmiar wskaźnika, na który rozpadła się tablica.
źródło
args
. W tym przypadku pierwszym elementem args jest „tablica 20 int”. Wskaźniki zawierają informacje o typie; przekazywany jest „wskaźnik do tablicy 20 int”.int (*)[20]
typ; „wskaźnik do tablicy zawierającej 20 int”.int (*p)[]
jest wskaźnikiem do 1-wymiarowej tablicy o nieokreślonej długości. Rozmiar*p
jest niezdefiniowany, więc nie możesz indeksowaćp
bezpośrednio (nawet z indeksem0
!). Jedyne, co możesz zrobić,p
to wyodrębnić go jako*p
, a następnie zindeksować jako(*p)[i]
. Nie zachowuje to dwuwymiarowej struktury oryginalnej tablicy.Problem i jak go rozwiązać w C ++
Problem został szczegółowo wyjaśniony przez Pat i Matta . Kompilator zasadniczo ignoruje pierwszy wymiar rozmiaru tablicy, skutecznie ignorując rozmiar przekazanego argumentu.
Z drugiej strony w C ++ można łatwo pokonać to ograniczenie na dwa sposoby:
std::array
(od C ++ 11)Bibliografia
Jeśli twoja funkcja próbuje tylko odczytać lub zmodyfikować istniejącą tablicę (nie kopiować jej), możesz łatwo użyć referencji.
Na przykład załóżmy, że chcesz mieć funkcję, która resetuje tablicę dziesięciu
int
s, ustawiając każdy element na0
. Możesz to łatwo zrobić, używając następującego podpisu funkcji:void reset(int (&array)[10]) { ... }
Nie tylko będzie to działać dobrze , ale także wymusi wymiar tablicy .
Możesz także skorzystać z szablonów, aby powyższy kod był ogólny :
template<class Type, std::size_t N> void reset(Type (&array)[N]) { ... }
I wreszcie możesz skorzystać z
const
poprawności. Rozważmy funkcję, która wyświetla tablicę 10 elementów:void show(const int (&array)[10]) { ... }
Stosując
const
kwalifikator zapobiegamy ewentualnym modyfikacjom .Standardowa klasa biblioteczna dla tablic
Jeśli uznasz powyższą składnię za brzydką i niepotrzebną, tak jak ja, możemy wrzucić ją do puszki i użyć
std::array
zamiast niej (od C ++ 11).Oto refaktoryzowany kod:
void reset(std::array<int, 10>& array) { ... } void show(std::array<int, 10> const& array) { ... }
Czy to nie wspaniałe? Nie wspominając o tym, że ogólna sztuczka z kodem, której nauczyłem Cię wcześniej, nadal działa:
template<class Type, std::size_t N> void reset(std::array<Type, N>& array) { ... } template<class Type, std::size_t N> void show(const std::array<Type, N>& array) { ... }
Nie tylko to, ale otrzymujesz za darmo kopiowanie i przenoszenie semantyczne. :)
void copy(std::array<Type, N> array) { // a copy of the original passed array // is made and can be dealt with indipendently // from the original }
Więc na co czekasz? Idź, użyj
std::array
.źródło
To zabawna funkcja C, która pozwala skutecznie strzelić sobie w stopę, jeśli masz taką skłonność.
Myślę, że powodem jest to, że C jest tylko krokiem powyżej języka asemblera. Sprawdzanie rozmiaru i podobne funkcje bezpieczeństwa zostały usunięte, aby umożliwić najwyższą wydajność, co nie jest złe, jeśli programista jest bardzo sumienny.
Ponadto przypisanie rozmiaru do argumentu funkcji ma tę zaletę, że gdy funkcja jest używana przez innego programistę, istnieje szansa, że zauważy on ograniczenie rozmiaru. Samo użycie wskaźnika nie przekazuje tej informacji następnemu programiście.
źródło
Po pierwsze, C nigdy nie sprawdza granic tablicy. Nie ma znaczenia, czy są lokalne, globalne, statyczne, parametry, cokolwiek. Sprawdzanie granic tablicy oznacza więcej przetwarzania, a C ma być bardzo wydajne, więc sprawdzanie granic tablic jest wykonywane przez programistę w razie potrzeby.
Po drugie, istnieje sztuczka, która umożliwia przekazanie wartości tablicy do funkcji. Możliwe jest również zwrócenie tablicy z funkcji według wartości. Wystarczy utworzyć nowy typ danych za pomocą struct. Na przykład:
typedef struct { int a[10]; } myarray_t; myarray_t my_function(myarray_t foo) { myarray_t bar; ... return bar; }
Musisz uzyskać dostęp do takich elementów: foo.a [1]. Dodatkowe „.a” może wyglądać dziwnie, ale ta sztuczka dodaje wielką funkcjonalność do języka C.
źródło
Aby przekazać kompilatorowi, że myArray wskazuje na tablicę składającą się z co najmniej 10 int:
void bar(int myArray[static 10])
Dobry kompilator powinien dać ci ostrzeżenie, jeśli uzyskasz dostęp do myArray [10]. Bez słowa kluczowego „static” liczba 10 nie miałaby żadnego znaczenia.
źródło
[static]
pozwala kompilatorowi ostrzec, jeśli wywołaszbar
plikint[5]
. To nie dyktuje, co można uzyskać dostęp do wewnątrzbar
. Obowiązek spoczywa całkowicie po stronie dzwoniącego.error: expected primary-expression before 'static'
nigdy nie widziałem tej składni. raczej nie będzie to standardowe C lub C ++.Jest to dobrze znana „funkcja” języka C, przekazana do C ++, ponieważ C ++ ma poprawnie kompilować kod w C.
Problem wynika z kilku aspektów:
Można powiedzieć, że tablice nie są tak naprawdę obsługiwane w C (nie jest to do końca prawdą, jak mówiłem wcześniej, ale jest to dobre przybliżenie); tablica jest tak naprawdę traktowana jako wskaźnik do bloku danych i dostępna za pomocą arytmetyki wskaźników. Ponieważ C NIE ma żadnej formy RTTI, musisz zadeklarować rozmiar elementu tablicy w prototypie funkcji (aby obsługiwać arytmetykę wskaźników). Jest to nawet „bardziej prawdziwe” w przypadku tablic wielowymiarowych.
Zresztą wszystko powyżej nie jest już prawdą: s
Większość nowoczesnych C / C ++ kompilatory zrobić granice wsparcia kontroli, ale standardy wymagają, że jest domyślnie wyłączona (dla wstecznej kompatybilności). Na przykład najnowsze wersje gcc sprawdzają zakres czasu kompilacji za pomocą „-O3 -Wall -Wextra”, a pełne sprawdzanie granic czasu wykonywania za pomocą „-fbounds -Check”.
źródło
struct MyStruct s = { .field1 = 1, .field2 = 2 };
) do inicjowania struktur, ponieważ jest to o wiele jaśniejszy sposób inicjowania struktury. W rezultacie większość aktualnego kodu C zostanie odrzucona przez standardowe kompilatory C ++, ponieważ większość kodu C będzie inicjalizować struktury.C nie tylko przekształci parametr typu
int[5]
w*int
; biorąc pod uwagę deklaracjętypedef int intArray5[5];
, przekształci również parametr typuintArray5
na*int
. Istnieją sytuacje, w których to zachowanie, choć dziwne, jest przydatne (szczególnie w przypadku rzeczy takich jakva_list
zdefiniowane wstdargs.h
, które niektóre implementacje definiują jako tablicę). Byłoby nielogiczne dopuszczenie jako parametru typu zdefiniowanego jakoint[5]
(z pominięciem wymiaru), ale niedopuszczenieint[5]
do bezpośredniego określenia.Uważam, że obsługa parametrów typu tablicowego przez C jest absurdalna, ale jest to konsekwencja wysiłków, aby wziąć język ad-hoc, którego duże części nie były szczególnie dobrze zdefiniowane lub przemyślane, i spróbować wymyślić behawioralny specyfikacje, które są spójne z tym, co zrobiły istniejące implementacje dla istniejących programów. Wiele dziwactw języka C ma sens, gdy patrzy się na nie w tym świetle, zwłaszcza jeśli weźmie się pod uwagę, że kiedy wiele z nich zostało wynalezionych, duże części języka, który znamy dzisiaj, jeszcze nie istniały. Z tego, co rozumiem, w poprzedniku C, zwanym BCPL, kompilatory nie bardzo dobrze śledziły typy zmiennych. Deklaracja
int arr[5];
była równoważnaint anonymousAllocation[5],*arr = anonymousAllocation;
; po zarezerwowaniu przydziału. kompilator nie wiedział ani nie przejmował się tymarr
był wskaźnikiem lub tablicą. Po uzyskaniu dostępu jako alboarr[x]
lub*arr
, będzie traktowany jako wskaźnik, niezależnie od tego, jak został zadeklarowany.źródło
Jedyną rzeczą, na którą jeszcze nie ma odpowiedzi, jest faktyczne pytanie.
Odpowiedzi już podane wyjaśniają, że tablice nie mogą być przekazywane przez wartość do funkcji ani w C, ani w C ++. Wyjaśniają również, że parametr zadeklarowany jako
int[]
jest traktowany tak, jakby miał typint *
i że zmienną typuint[]
można przekazać do takiej funkcji.Ale nie wyjaśniają, dlaczego jawne podanie długości tablicy nigdy nie zostało popełnione.
void f(int *); // makes perfect sense void f(int []); // sort of makes sense void f(int [10]); // makes no sense
Dlaczego ostatnia z nich nie jest błędem?
Powodem tego jest to, że powoduje problemy z typami czcionek.
typedef int myarray[10]; void f(myarray array);
Gdyby określenie długości tablicy w parametrach funkcji było błędem, nie byłoby możliwe użycie
myarray
nazwy w parametrze funkcji. A ponieważ niektóre implementacje używają typów tablic dla standardowych typów bibliotek, takich jakva_list
i wszystkie implementacje są wymagane do utworzeniajmp_buf
typu tablicowego, byłoby bardzo problematyczne, gdyby nie było standardowego sposobu deklarowania parametrów funkcji przy użyciu tych nazw: bez tej możliwości nie może być przenośną implementacją funkcji, takich jakvprintf
.źródło
Kompilatory mogą sprawdzić, czy rozmiar przekazanej tablicy jest taki sam, jak oczekiwano. Kompilatory mogą ostrzegać o problemie, jeśli tak nie jest.
źródło