Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Tablica to sekwencja obiektów tego samego typu, które zajmują ciągły obszar pamięci. Tradycyjne tablice w stylu C są źródłem wielu usterek, ale są nadal powszechne, zwłaszcza w starszych bazach kodu. W nowoczesnym języku C++zdecydowanie zalecamy używanie std::vector
tablic w stylu C lub std::array
zamiast tablic w stylu C opisanych w tej sekcji. Oba te standardowe typy bibliotek przechowują swoje elementy jako ciągły blok pamięci. Zapewniają one jednak większe bezpieczeństwo typów i obsługują iteratory, które mają gwarancję wskazywania prawidłowej lokalizacji w sekwencji. Aby uzyskać więcej informacji, zobacz Kontenery.
Deklaracje stosu
W deklaracji tablicy języka C++ rozmiar tablicy jest określany po nazwie zmiennej, a nie po nazwie typu, jak w innych językach. W poniższym przykładzie zadeklarowana jest tablica 1000 podwaja, która ma zostać przydzielona na stosie. Liczba elementów musi być podana jako literał liczby całkowitej lub jako wyrażenie stałe. Wynika to z faktu, że kompilator musi wiedzieć, ile miejsca na stos ma przydzielić; nie może używać wartości obliczonej w czasie wykonywania. Każdy element w tablicy ma przypisaną wartość domyślną 0. Jeśli nie przypiszesz wartości domyślnej, każdy element początkowo zawiera wszystkie losowe wartości, które występują w tej lokalizacji pamięci.
constexpr size_t size = 1000;
// Declare an array of doubles to be allocated on the stack
double numbers[size] {0};
// Assign a new value to the first element
numbers[0] = 1;
// Assign a value to each subsequent element
// (numbers[1] is the second element in the array.)
for (size_t i = 1; i < size; i++)
{
numbers[i] = numbers[i-1] * 1.1;
}
// Access each element
for (size_t i = 0; i < size; i++)
{
std::cout << numbers[i] << " ";
}
Pierwszym elementem w tablicy jest element zeroth. Ostatnim elementem jest element (n-1), gdzie n jest liczbą elementów, które może zawierać tablica. Liczba elementów w deklaracji musi być typu całkowitego i musi być większa niż 0. Twoim zadaniem jest zapewnienie, że program nigdy nie przekazuje wartości do operatora indeksu dolnego, który jest większy niż (size - 1)
.
Tablica o zerowym rozmiarze jest legalna tylko wtedy, gdy tablica jest ostatnim polem struct
w obiekcie lub union
i kiedy rozszerzenia firmy Microsoft są włączone (/Za
lub /permissive-
nie jest ustawiona).
Tablice oparte na stosie są szybsze do przydzielania i uzyskiwania dostępu niż tablice oparte na stercie. Jednak przestrzeń stosu jest ograniczona. Liczba elementów tablicy nie może być tak duża, że używa zbyt dużej ilości pamięci stosu. Ile jest za dużo zależy od programu. Za pomocą narzędzi profilowania można określić, czy tablica jest zbyt duża.
Deklaracje stert
Może być wymagana tablica, która jest zbyt duża, aby przydzielić na stosie lub którego rozmiar nie jest znany w czasie kompilacji. Tę tablicę można przydzielić na stercie przy użyciu new[]
wyrażenia. Operator zwraca wskaźnik do pierwszego elementu. Operator indeksu dolnego działa na zmiennej wskaźnika w taki sam sposób, jak w przypadku tablicy opartej na stosie. Można również użyć arytmetyki wskaźnika, aby przenieść wskaźnik do dowolnych elementów w tablicy. Twoim zadaniem jest zapewnienie, że:
- Zawsze przechowujesz kopię oryginalnego adresu wskaźnika, aby można było usunąć pamięć, gdy tablica nie jest już potrzebna.
- nie zwiększasz ani nie dekrementujesz adresu wskaźnika poza granicami tablicy.
W poniższym przykładzie pokazano, jak zdefiniować tablicę na stercie w czasie wykonywania. Pokazuje on, jak uzyskać dostęp do elementów tablicy przy użyciu operatora indeksu dolnego i przy użyciu arytmetyki wskaźnika:
void do_something(size_t size)
{
// Declare an array of doubles to be allocated on the heap
double* numbers = new double[size]{ 0 };
// Assign a new value to the first element
numbers[0] = 1;
// Assign a value to each subsequent element
// (numbers[1] is the second element in the array.)
for (size_t i = 1; i < size; i++)
{
numbers[i] = numbers[i - 1] * 1.1;
}
// Access each element with subscript operator
for (size_t i = 0; i < size; i++)
{
std::cout << numbers[i] << " ";
}
// Access each element with pointer arithmetic
// Use a copy of the pointer for iterating
double* p = numbers;
for (size_t i = 0; i < size; i++)
{
// Dereference the pointer, then increment it
std::cout << *p++ << " ";
}
// Alternate method:
// Reset p to numbers[0]:
p = numbers;
// Use address of pointer to compute bounds.
// The compiler computes size as the number
// of elements * (bytes per element).
while (p < (numbers + size))
{
// Dereference the pointer, then increment it
std::cout << *p++ << " ";
}
delete[] numbers; // don't forget to do this!
}
int main()
{
do_something(108);
}
Inicjowanie tablic
Tablicę można zainicjować w pętli, jeden element naraz lub w jednej instrukcji. Zawartość następujących dwóch tablic jest identyczna:
int a[10];
for (int i = 0; i < 10; ++i)
{
a[i] = i + 1;
}
int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Przekazywanie tablic do funkcji
Gdy tablica jest przekazywana do funkcji, jest przekazywana jako wskaźnik do pierwszego elementu, niezależnie od tego, czy jest to tablica oparta na stosie, czy sterta. Wskaźnik nie zawiera żadnych innych informacji o rozmiarze ani typie. To zachowanie jest nazywane rozkładem wskaźnika. Po przekazaniu tablicy do funkcji należy zawsze określić liczbę elementów w osobnym parametrze. To zachowanie oznacza również, że elementy tablicy nie są kopiowane, gdy tablica zostanie przekazana do funkcji. Aby zapobiec modyfikowaniu elementów przez funkcję, określ parametr jako wskaźnik do const
elementów.
W poniższym przykładzie przedstawiono funkcję, która akceptuje tablicę i długość. Wskaźnik wskazuje oryginalną tablicę, a nie kopię. Ponieważ parametr nie const
jest , funkcja może modyfikować elementy tablicy.
void process(double *p, const size_t len)
{
std::cout << "process:\n";
for (size_t i = 0; i < len; ++i)
{
// do something with p[i]
}
}
Zadeklaruj i zdefiniuj parametr p
tablicy tak const
, aby był tylko do odczytu w bloku funkcji:
void process(const double *p, const size_t len);
Tę samą funkcję można również zadeklarować w ten sposób, bez zmiany zachowania. Tablica jest nadal przekazywana jako wskaźnik do pierwszego elementu:
// Unsized array
void process(const double p[], const size_t len);
// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);
Tablice wielowymiarowe
Tablice skonstruowane z innych tablic to tablice wielowymiarowe. Te tablice wielowymiarowe są określane przez umieszczenie wielu wyrażeń stałych w nawiasach w sekwencji. Rozważmy na przykład następującą deklarację:
int i2[5][7];
Określa tablicę typu int
, koncepcyjnie rozmieszczoną w dwuwymiarowej macierzy pięciu wierszy i siedmiu kolumn, jak pokazano na poniższej ilustracji:
Obraz jest siatką 7 komórek szerokich i 5 komórek wysokich. Każda komórka zawiera indeks komórki. Pierwszy indeks komórek ma etykietę 0,0. Następna komórka w tym wierszu wynosi 0,1 itd. do ostatniej komórki w tym wierszu, która wynosi 0,6. Następny wiersz rozpoczyna się od indeksu 1,0. Komórka po tej komórce ma indeks 1,1. Ostatnia komórka w tym wierszu to 1,6. Ten wzorzec powtarza się do ostatniego wiersza, który rozpoczyna się od indeksu 4,0. Ostatnia komórka w ostatnim wierszu zawiera indeks 4,6.
Można zadeklarować tablice wielowymiarowe, które mają listę inicjatorów (zgodnie z opisem w temacie Initializers). W tych deklaracjach wyrażenie stałe określające granice pierwszego wymiaru można pominąć. Na przykład:
// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {
{ 32.19, 47.29, 31.99, 19.11 },
{ 11.29, 22.49, 33.47, 17.29 },
{ 41.97, 22.09, 9.76, 22.55 }
};
Poprzednia deklaracja definiuje tablicę, która jest trzema wierszami o cztery kolumny. Wiersze reprezentują fabryki, a kolumny reprezentują rynki, na których są dostarczane fabryki. Wartości to koszty transportu z fabryk do rynków. Pierwszy wymiar tablicy jest pominięty, ale kompilator wypełnia go, sprawdzając inicjator.
Użycie operatora pośredniego (*) w typie tablicy nwymiarowej daje tablicę wymiarową n-1. Jeśli n ma wartość 1, zostanie zwrócony skalarny (lub element tablicy).
Tablice języka C++ są przechowywane w kolejności głównej wierszy. Kolejność główna wiersza oznacza, że ostatni indeks dolny różni się najszybciej.
Przykład
Można również pominąć specyfikację granic dla pierwszego wymiaru tablicy wielowymiarowej w deklaracjach funkcji, jak pokazano poniżej:
// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits> // Includes DBL_MAX
#include <iostream>
const int cMkts = 4, cFacts = 2;
// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {
{ 32.19, 47.29, 31.99, 19.11 },
{ 11.29, 22.49, 33.47, 17.29 },
{ 41.97, 22.09, 9.76, 22.55 }
};
// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /
sizeof( double[cMkts] );
double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);
using namespace std;
int main( int argc, char *argv[] ) {
double MinCost;
if (argv[1] == 0) {
cout << "You must specify the number of markets." << endl;
exit(0);
}
MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
cout << "The minimum cost to Market " << argv[1] << " is: "
<< MinCost << "\n";
}
double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
double MinCost = DBL_MAX;
for( size_t i = 0; i < cFacts; ++i )
MinCost = (MinCost < TransportCosts[i][Mkt]) ?
MinCost : TransportCosts[i][Mkt];
return MinCost;
}
The minimum cost to Market 3 is: 17.29
Funkcja FindMinToMkt
jest napisana w taki sposób, że dodanie nowych fabryk nie wymaga żadnych zmian w kodzie, tylko ponownej kompilacji.
Inicjowanie tablic
Tablice obiektów, które mają konstruktor klasy, są inicjowane przez konstruktora. Jeśli na liście inicjatora znajduje się mniej elementów niż elementy w tablicy, domyślny konstruktor jest używany dla pozostałych elementów. Jeśli dla klasy nie zdefiniowano domyślnego konstruktora, lista inicjatorów musi zostać ukończona, czyli musi istnieć jeden inicjator dla każdego elementu w tablicy.
Rozważ klasę Point
, która definiuje dwa konstruktory:
// initializing_arrays1.cpp
class Point
{
public:
Point() // Default constructor.
{
}
Point( int, int ) // Construct from two ints
{
}
};
// An array of Point objects can be declared as follows:
Point aPoint[3] = {
Point( 3, 3 ) // Use int, int constructor.
};
int main()
{
}
Pierwszy element aPoint
jest konstruowany przy użyciu konstruktora Point( int, int )
; pozostałe dwa elementy są konstruowane przy użyciu konstruktora domyślnego.
Statyczne tablice składowe (bez względu na to, czy const
) można zainicjować w swoich definicjach (poza deklaracją klasy). Na przykład:
// initializing_arrays2.cpp
class WindowColors
{
public:
static const char *rgszWindowPartList[7];
};
const char *WindowColors::rgszWindowPartList[7] = {
"Active Title Bar", "Inactive Title Bar", "Title Bar Text",
"Menu Bar", "Menu Bar Text", "Window Background", "Frame" };
int main()
{
}
Uzyskiwanie dostępu do elementów tablicy
Dostęp do poszczególnych elementów tablicy można uzyskać przy użyciu operatora indeksu dolnego tablicy ([ ]
). Jeśli używasz nazwy tablicy jednowymiarowej bez indeksu dolnego, zostanie ona obliczona jako wskaźnik do pierwszego elementu tablicy.
// using_arrays.cpp
int main() {
char chArray[10];
char *pch = chArray; // Evaluates to a pointer to the first element.
char ch = chArray[0]; // Evaluates to the value of the first element.
ch = chArray[3]; // Evaluates to the value of the fourth element.
}
W przypadku używania tablic wielowymiarowych można użyć różnych kombinacji w wyrażeniach.
// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {
double multi[4][4][3]; // Declare the array.
double (*p2multi)[3];
double (*p1multi);
cout << multi[3][2][2] << "\n"; // C4700 Use three subscripts.
p2multi = multi[3]; // Make p2multi point to
// fourth "plane" of multi.
p1multi = multi[3][2]; // Make p1multi point to
// fourth plane, third row
// of multi.
}
W poprzednim kodzie multi
jest trójwymiarową tablicą typu double
. Wskaźnik p2multi
wskazuje tablicę typu double
trzy. W tym przykładzie tablica jest używana z jednym, dwoma i trzema indeksami dolnymi. Chociaż częściej określa się wszystkie indeksy dolny, jak w cout
instrukcji , czasami warto wybrać określony podzbiór elementów tablicy, jak pokazano w instrukcjach, które są zgodne z cout
instrukcjami .
Przeciążanie operatora indeksu dolnego
Podobnie jak inne operatory, operator indeksu dolnego ([]
) może zostać ponownie zdefiniowany przez użytkownika. Domyślne zachowanie operatora indeksu dolnego, jeśli nie jest przeciążone, polega na połączeniu nazwy tablicy i indeksu dolnego przy użyciu następującej metody:
*((array_name) + (subscript))
Podobnie jak w przypadku wszystkich typów wskaźników, skalowanie odbywa się automatycznie w celu dostosowania rozmiaru typu. Wynikowa wartość nie jest n bajtami ze źródła array_name
; zamiast tego jest to nelement th tablicy. Aby uzyskać więcej informacji na temat tej konwersji, zobacz Operatory addytywne.
Podobnie w przypadku tablic wielowymiarowych adres jest uzyskiwany przy użyciu następującej metody:
((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))
Tablice w wyrażeniach
Gdy identyfikator typu tablicy pojawia się w wyrażeniu innym niż sizeof
, adres-of (&
) lub inicjalizacji odwołania, jest konwertowany na wskaźnik do pierwszego elementu tablicy. Na przykład:
char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;
Wskaźnik psz
wskazuje pierwszy element tablicy szError1
. Tablice, w przeciwieństwie do wskaźników, nie są modyfikowalne wartości l. Dlatego następujące przypisanie jest nielegalne:
szError1 = psz;