Tam gdzie widoczna jest deklaracja tablicy tab, nazwa tab oznacza zmienną typu tablicowego o określonym rozmiarze. Na przykład po
int tab[100];typem tab jest int[100], a więc stuelementowa tablica liczb całkowitych typu int. Zauważmy, że wymiar jest elementem specyfikacji typu: typy int[6] i int[5] są różnymi typami.
Wartością wyrażenia sizeof(tab) będzie zatem rozmiar w bajtach całej tablicy. Znając tę wartość można łatwo obliczyć ilość elementów; dla np. tablicy liczb całkowitych, będzie to
sizeof(tab) / sizeof(int)lub w formie niezależnej od typu tablicy
sizeof(tab) / sizeof(tab[0])Niestety, nie oznacza to, że tablice mają własność tablic z Javy polegającą na tym, że „tablica zna swój wymiar”. W prawie każdej operacji wykonywanej na zmiennej tab zmienna ta jest niejawnie konwertowana do typu wskaźnikowego. Wartością tab jest wtedy adres pierwszego elementu tablicy, a więc elementu o indeksie zero. A zatem po deklaracji
int tab[20];zmienna tab może być traktowana jako wskaźnik typu int* const wskazujący na pierwszy element tablicy. Modyfikator const znaczy tutaj, że zawartość tablicy może być zmieniana, ale nie można do tab wpisać adresu innej tablicy. Ponieważ tab może być przekonwertowane do wskaźnika wskazującego na pierwszy element, więc wyrażenie *tab jest niczym innym jak nazwą pierwszego elementu tablicy.
Konwersja, o której wspomnieliśmy, zachodzi w szczególności,
gdy tablicę „wysyłamy” do funkcji. Wysyłamy tak naprawdę
(przez wartość) adres początku tablicy i nic więcej.
W szczególności nie ma tam żadnej informacji o
wymiarze tablicy. Więcej: nie ma nawet informacji, że to
w ogóle jest adres tablicy, a nie adres „normalnej” zmiennej
odpowiedniego typu. Zatem jeśli
argumentem funkcji jest tablica, to typem odpowiedniego
parametru funkcji (w jej deklaracji/definicji) jest
typ wskaźnikowy, a nie tablicowy!
Przyjrzyjmy się następującemu programowi:
      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  void fun1(double tab[]) {
      5.     cout << "Wymiar \'tab\' w fun1: " << sizeof(tab) << endl;
      6.     cout << "Wartosc *tab w fun1: "   << tab[0]      << endl;
      7.  }
      8.  
      9.  void fun2(double* tab) {
     10.     cout << "Wymiar \'tab\' w fun2: " << sizeof(tab) << endl;
     11.     cout << "Wartosc *tab w fun2: "   << tab[0]      << endl;
     12.  }
     13.  
     14.  int main() {
     15.     double tab[] = {6,2,3,2,1};
     16.     cout << "Wymiar \'tab\' w main: " << sizeof(tab) << endl;
     17.     cout << "Wartosc *tab w main: "   << *tab        << endl;
     18.     fun1(tab);
     19.     fun2(tab);
     20.  }
którego uruchomienie daje (na maszynie 32-bitowej)
    Wymiar 'tab' w main: 40
    Wartosc *tab w main: 6
    Wymiar 'tab' w fun1: 4
    Wartosc *tab w fun1: 6
    Wymiar 'tab' w fun2: 4
    Wartosc *tab w fun2: 6
W programie głównym (main) tworzymy tu tablicę
tab.
Wypisując teraz wartość
sizeof(tab)
dostajemy 40, co jest prawidłowym wymiarem w bajtach tej tablicy
(5 elementów po 8 bajtów). Wartość
*tab
 wynosi 6,
bo jest to wartość pierwszego elementu tablicy.
Następnie tablicę
tab
 wysyłamy do dwóch funkcji.
Funkcje te są prawie identyczne, różnią się tylko deklaracją typu
parametru: w funkcji
fun1
 mamy
double tab[],
a w funkcji
fun2
double* tab.
Jak widzimy z wydruku wyników, obie funkcje są równoważne.
W obu, również w 
fun1, gdzie typem zadeklarowanym był
double tab[], zmienna
tab
 wewnątrz funkcji
jest dokładnie typu
double*. W związku z tym jej wymiar
(wynik
sizeof(tab)) jest teraz cztery (lub osiem na maszynie
64-bitowej)! Jest to bowiem wymiar wskaźnika, a nie tablicy.
Zwróćmy uwagę na funkcję fun2. Parametr jest typu double* i nigdzie nie ma tu mowy o żadnych tablicach. Mimo to użyliśmy wyrażenia z indeksem, tab[0], tak jak dla tablic! Jest to przykład tzw. arytmetyki wskaźników, którą omówimy w następnym podrozdziale. Jeszcze raz uwypukla to związek typu wskaźnikowego z tablicami.
Tak więc
Wniosek z tego jest taki, że niemal zawsze, kiedy posyłamy do funkcji tablicę, musimy w osobnym argumencie przesłać informację o jej rozmiarze (liczbie elementów).
Jeszcze ważniejszym wnioskiem jest fakt, że choć przekazanie
argumentu zachodzi jak zwykle przez wartość, to tą przekazywaną do
funkcji wartością jest adres początku tablicy, a nie sama tablica.
Dysponując adresem początku, wewnątrz funkcji mamy dostęp do
oryginalnych elementów tablicy, a nie do ich kopii.
Samego wskaźnika do tablicy nie możemy zmienić, bo jej adres
funkcja otrzymuje w postaci kopii poprzez stos.
Modyfikacje elementów tablicy będą jednak widoczne w funkcji
wywołującej, bo adres przekazany w kopii wskaźnika był „prawdziwy”.
Rozpatrzmy na przykład:
      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int* fun(int *tab1, int *tab2, int size) {
      5.      int i,x,y,s1 = 0,s2 = 0;
      6.      for (i = 0; i < size; ++i) {
      7.          x = tab1[i];
      8.          y = tab2[i];
      9.          tab1[i] = y;
     10.          tab2[i] = x;
     11.          s1 += y;
     12.          s2 += x;
     13.      }
     14.      return s1 > s2 ? tab1 : tab2;
     15.  }
     16.  
     17.  void printTable(int *tab, int size) {
     18.      int i;
     19.      for (i = 0; i < size; ++i) cout << tab[i] << " ";
     20.      cout << endl;
     21.  }
     22.  
     23.  int main() {
     24.      int tab1[] = {1,2,3}, tab2[] = {4,5,6}, *tab3;
     25.  
     26.      cout << "tab1 przed: "; printTable(tab1,3);
     27.      cout << "tab2 przed: "; printTable(tab2,3);
     28.      tab3 = fun(tab1,tab2,3);
     29.      cout << "tab1    po: "; printTable(tab1,3);
     30.      cout << "tab2    po: "; printTable(tab2,3);
     31.      cout << "tab3      : "; printTable(tab3,3);
     32.  }
Zauważmy, że w definicjach funkcji fun i printTab parametrami są wskaźniki do zmiennych całkowitych (tab1, tab2, tab) i, osobno, parametr całkowity (size), poprzez który przekazywać będziemy rozmiar — liczbę elementów — tablicy.
Przy wywoływaniu tych funkcji przekazujemy, przez wartość, adresy pierwszych elementów tablic. Funkcja fun zamienia elementy dwóch tablic: elementy z tab1 są kopiowane do odpowiednich elementów tab2 i odwrotnie. Przy okazji obliczana jest suma elementów w obu tablicach. Następnie funkcja zwraca wartość albo tab1, albo tab2 w zależności od tego, w której z tablic suma elementów po zamianie była większa (użyta tu konstrukcja oznacza „jeśli s1>s2 to zwróć tab1, a jeśli nie, to zwróć tab2” — więcej w rozdziale o operatorach ). Zauważmy, że w związku z tym zadeklarowanym typem zwracanym funkcji fun był typ int* — a więc typ wskaźnikowy.
Rezultat tego programu to:
    tab1 przed: 1 2 3
    tab2 przed: 4 5 6
    tab1    po: 4 5 6
    tab2    po: 1 2 3
    tab3      : 4 5 6
W programie głównym, po powrocie z funkcji
fun
wypisujemy — za pomocą funkcji
printTab
 — zawartość
tablic: widzimy, że wartości elementów w tablicach zamieniły się.
Natomiast rezultat zwracany przez funkcję zapamiętujemy w zmiennej
tab3, typu
int*, a nie typu tablicowego.
Jej wartość będzie zatem identyczna z wartością jednej ze zmiennych
tab1
 lub
tab2
 — po wywołaniu
funkcji
printTab
 z argumentem
tab3
 przekonujemy się
(ostatnia linia wydruku), że musiał to być adres tablicy
tab1. Zauważmy, że wysyłamy
tab3
do funkcji
printTab
 chociaż
tab3
 nie jest tablicą.
To jest legalne, bo funkcja spodziewa się adresu zmiennej
typu
int, a wartość
tab3
 właśnie jest tego typu.
T.R. Werner, 21 lutego 2016; 20:17