C Language
arrayer
Sök…
Introduktion
Matriser är härledda datatyper som representerar en ordnad samling av värden ("element") av en annan typ. De flesta matriser i C har ett fast antal element av vilken typ som helst, och dess representation lagrar elementen sammanhängande i minnet utan luckor eller stoppning. C tillåter flerdimensionella matriser vars element är andra matriser, och även matriser av pekare.
C stöder dynamiskt allokerade matriser vars storlek bestäms vid körning. C99 och senare stöder matriser eller VLA: er med variabel längd.
Syntax
- typnamn [längd]; / * Definiera matris med 'typ' med namn 'namn' och längd 'längd'. * /
- int arr [10] = {0}; / * Definiera en matris och initiera ALLA element till 0. * /
- int arr [10] = {42}; / * Definiera en matris och initiera 1: a element till 42 och resten till 0. * /
- int arr [] = {4, 2, 3, 1}; / * Definiera och initiera en matris med längd 4. * /
- arr [n] = värde; / * Ställ in värde vid index n. * /
- värde = arr [n]; / * Få värde vid index n. * /
Anmärkningar
Varför behöver vi matriser?
Matriser ger ett sätt att organisera objekt i ett aggregerat med sin egen betydelse. Till exempel är C-strängar matriser av tecken ( char
) och en sträng som "Hej, världen!" har betydelse som ett aggregerat som inte är inneboende i tecknen individuellt. På liknande sätt används matriser ofta för att representera matematiska vektorer och matriser, liksom listor av många slag. Utan något sätt att gruppera elementen skulle man dessutom behöva adressera var och en för sig, till exempel via separata variabler. Inte bara är det svårt, det rymmer inte lätt samlingar i olika längder.
Matriser konverteras implicit till pekare i de flesta sammanhang .
Förutom när den visas som operand för operatören sizeof
operatören _Alignof
(C2011) eller operatören unary &
(adress-of), eller som en strängbokstav som används för att initiera en (annan) grupp, konverteras en matris implicit till ( "sönderfaller till") en pekare till sitt första element. Denna implicita konvertering är tätt kopplad till definitionen av array-subskriptoperatören ( []
): uttrycket arr[idx]
definieras som motsvarande *(arr + idx)
. Eftersom pekarens aritmetik är kommutativ är *(arr + idx)
också ekvivalent med *(idx + arr)
, vilket i sin tur motsvarar idx[arr]
. Alla dessa uttryck är giltiga och utvärderar till samma värde, förutsatt att antingen idx
eller arr
är en pekare (eller en matris, som sönderfaller till en pekare), den andra är ett heltal och heltalet är ett giltigt index i arrayen som pekaren pekar på.
Som ett speciellt fall bör du observera att &(arr[0])
motsvarar &*(arr + 0)
, vilket förenklar att arr
. Alla dessa uttryck är utbytbara varhelst de sista förfaller till en pekare. Detta uttrycker helt enkelt igen att en matris sönderfaller en pekare till dess första element.
I motsats härtill, om operatörens adress tillämpas på en matris av typ T[N]
( dvs &arr
), har resultatet typ T (*)[N]
och pekar på hela matrisen. Detta skiljer sig från en pekare till det första matriselementet åtminstone med avseende på pekarearmetik, vilket definieras i termer av storleken på den pekade typen.
Funktionsparametrar är inte matriser .
void foo(int a[], int n);
void foo(int *a, int n);
Även om den första deklarationen av foo
använder array-liknande syntax för parameter a
, används en sådan syntax för att deklarera en funktionsparameter som förklarar den parametern som en pekare till arrayens elementtyp. Således är den andra signaturen för foo()
semantiskt identisk med den första. Detta motsvarar sönderfallet av gruppvärden till pekare där de visas som argument till ett funktionsanrop, så att om en variabel och en funktionsparameter deklareras med samma typ array då att variabelns värde är lämplig för användning i ett funktionsanrop som argument kopplat till parametern.
Förklara och initiera en matris
Den allmänna syntaxen för att deklarera en endimensionell matris är
type arrName[size];
där type
kan vara vilken inbyggd type
som helst eller användardefinierade typer som strukturer, arrName
är en användardefinierad identifierare och size
är en heltalskonstant.
Att deklarera en matris (en matris med 10 int-variabler i detta fall) görs så här:
int array[10];
det har nu obestämda värden. För att säkerställa att det har nollvärden när du deklarerar, kan du göra detta:
int array[10] = {0};
Arrays kan också ha initialiseringar, detta exempel förklarar en matris med 10 int
, där de första 3 int
kommer att innehålla värdena 1
, 2
, 3
, alla andra värden kommer att vara noll:
int array[10] = {1, 2, 3};
I ovanstående metod för initialisering tilldelas det första värdet i listan till den första medlemmen i matrisen, det andra värdet tilldelas den andra medlemmen i matrisen och så vidare. Om liststorleken är mindre än matrisstorleken, kommer som de i exemplet ovan att initialiseras de återstående medlemmarna i arrayen till nollor. Med utsedd listinitialisering (ISO C99) är det möjligt att uttryckligen initialisera arraymedlemmarna. Till exempel,
int array[5] = {[2] = 5, [1] = 2, [4] = 9}; /* array is {0, 2, 5, 0, 9} */
I de flesta fall kan kompilatorn härleda längden på matrisen för dig, detta kan uppnås genom att lämna de fyrkantiga konsolerna tomma:
int array[] = {1, 2, 3}; /* an array of 3 int's */
int array[] = {[3] = 8, [0] = 9}; /* size is 4 */
Det är inte tillåtet att förklara en matris med noll längd.
Arrays med variabel längd (VLA för kort) tillsattes i C99 och gjordes valfria i C11. De är lika med normala matriser, med en, viktig skillnad: Längden behöver inte vara känd vid sammanställningstiden. VLA: er har automatisk lagringstid. Endast pekare till VLA: er kan ha statisk lagringstid.
size_t m = calc_length(); /* calculate array length at runtime */
int vla[m]; /* create array with calculated length */
Viktig:
VLA: er är potentiellt farliga. Om matrisen vla
i exemplet ovan kräver mer utrymme på stacken än tillgängligt, kommer stacken att flyta över. Användning av VLA är därför ofta avskräckta i stilguider och genom böcker och övningar.
Rensa matrisinnehåll (nollställning)
Ibland är det nödvändigt att ställa in en matris till noll efter initieringen.
#include <stdlib.h> /* for EXIT_SUCCESS */
#define ARRLEN (10)
int main(void)
{
int array[ARRLEN]; /* Allocated but not initialised, as not defined static or global. */
size_t i;
for(i = 0; i < ARRLEN; ++i)
{
array[i] = 0;
}
return EXIT_SUCCESS;
}
En vanlig genväg till slingan ovan är att använda memset()
från <string.h>
. Att passera array
som visas nedan gör att det förfaller till en pekare till det första elementet.
memset(array, 0, ARRLEN * sizeof (int)); /* Use size explicitly provided type (int here). */
eller
memset(array, 0, ARRLEN * sizeof *array); /* Use size of type the pointer is pointing to. */
Som i detta exempel array
är en matris och inte bara en pekare till en matris 1st element (se Array längd på varför detta är viktigt) ett tredje alternativ till 0 ut matrisen är möjligt:
memset(array, 0, sizeof array); /* Use size of the array itself. */
Array längd
Matriser har fasta längder som är kända inom ramen för sina förklaringar. Ändå är det möjligt och ibland bekvämt att beräkna matrislängder. I synnerhet kan detta göra koden mer flexibel när matrisens längd bestäms automatiskt från en initialisering:
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
/* size of `array` in bytes */
size_t size = sizeof(array);
/* number of elements in `array` */
size_t length = sizeof(array) / sizeof(array[0]);
I de flesta sammanhang där en matris visas i ett uttryck konverteras emellertid automatiskt till ("sönderfaller") en pekare till sitt första element. Fallet där en matris är operand för operatörens sizeof
är ett av ett litet antal undantag. Den resulterande pekaren är inte i sig själv en matris och den innehåller inte någon information om längden på den matris som den härrör från. Om den längden behövs i samband med pekaren, till exempel när pekaren överförs till en funktion, måste den transporteras separat.
Anta till exempel att vi vill skriva en funktion för att returnera det sista elementet i en matrix med int
. Genom att fortsätta från ovanstående kanske vi kan kalla det så:
/* array will decay to a pointer, so the length must be passed separately */
int last = get_last(array, length);
Funktionen kan implementeras så här:
int get_last(int input[], size_t length) {
return input[length - 1];
}
Notera särskilt att även om förklaringen av input
liknar det av en matris, i själva verket förklarar det input
som en pekare (till int
). Det är exakt motsvarande att deklarera input
som int *input
. Detsamma skulle vara sant även om en dimension ges. Detta är möjligt eftersom matriser aldrig kan vara faktiska argument för funktioner (de förfaller till pekare när de visas i funktionssamtaluttryck), och det kan ses som mnemoniskt.
Det är ett mycket vanligt fel att försöka bestämma matrisstorlek från en pekare, som inte kan fungera. GÖR INTE DETTA:
int BAD_get_last(int input[]) {
/* INCORRECTLY COMPUTES THE LENGTH OF THE ARRAY INTO WHICH input POINTS: */
size_t length = sizeof(input) / sizeof(input[0]));
return input[length - 1]; /* Oops -- not the droid we are looking for */
}
Faktum är att det speciella felet är så vanligt att vissa kompilatorer känner igen det och varnar om det. clang
, till exempel, kommer att avge följande varning:
warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
int length = sizeof(input) / sizeof(input[0]);
^
note: declared here
int BAD_get_last(int input[])
^
Ställa in värden i matriser
Åtkomst till matrisvärden görs vanligtvis genom fyrkantiga parenteser:
int val;
int array[10];
/* Setting the value of the fifth element to 5: */
array[4] = 5;
/* The above is equal to: */
*(array + 4) = 5;
/* Reading the value of the fifth element: */
val = array[4];
Som en bieffekt av att operandema till +
operatören kan bytas ut (-> kommutativ lag) är följande lika:
*(array + 4) = 5;
*(4 + array) = 5;
så nästa uttalanden är likvärdiga:
array[4] = 5;
4[array] = 5; /* Weird but valid C ... */
och de två också:
val = array[4];
val = 4[array]; /* Weird but valid C ... */
C utför inga gränskontroller, åtkomst till innehåll utanför den deklarerade matrisen är odefinierad ( åtkomst till minne utöver tilldelad del ):
int val;
int array[10];
array[4] = 5; /* ok */
val = array[4]; /* ok */
array[19] = 20; /* undefined behavior */
val = array[15]; /* undefined behavior */
Definiera array och access array-element
#include <stdio.h>
#define ARRLEN (10)
int main (void)
{
int n[ ARRLEN ]; /* n is an array of 10 integers */
size_t i, j; /* Use size_t to address memory, that is to index arrays, as its guaranteed to
be wide enough to address all of the possible available memory.
Using signed integers to do so should be considered a special use case,
and should be restricted to the uncommon case of being in the need of
negative indexes. */
/* Initialize elements of array n. */
for ( i = 0; i < ARRLEN ; i++ )
{
n[ i ] = i + 100; /* Set element at location i to i + 100. */
}
/* Output each array element's value. */
for (j = 0; j < ARRLEN ; j++ )
{
printf("Element[%zu] = %d\n", j, n[j] );
}
return 0;
}
Tilldela och nollinitiera en matris med användardefinierad storlek
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int * pdata;
size_t n;
printf ("Enter the size of the array: ");
fflush(stdout); /* Make sure the prompt gets printed to buffered stdout. */
if (1 != scanf("%zu", &n)) /* If zu is not supported (Windows?) use lu. */
{
fprintf("scanf() did not read a in proper value.\n");
exit(EXIT_FAILURE);
}
pdata = calloc(n, sizeof *pdata);
if (NULL == pdata)
{
perror("calloc() failed"); /* Print error. */
exit(EXIT_FAILURE);
}
free(pdata); /* Clean up. */
return EXIT_SUCCESS;
}
Detta program försöker skanna in ett osignerat heltal från standardinmatningen, allokera ett minnesblock för en grupp n
element av typ int
genom att ringa calloc()
-funktionen. Minnet initialiseras till alla nollor av det senare.
Om det lyckas släpps minnet av samtalet att free()
.
Iterera genom en matris effektivt och rad-stor ordning
Matriser i C kan ses som en sammanhängande bit av minnet. Mer exakt är den sista dimensionen av matrisen den sammanhängande delen. Vi kallar detta rad-major order . Förståelse av detta och det faktum att ett cachefel laddar en komplett cachelinje i cachen när du får åtkomst till ocacherad data för att förhindra efterföljande cachefel, kan vi se varför åtkomst till en matris med dimension 10000x10000 med array[0][0]
skulle potentiellt ladda array[0][1]
i cache, men åtkomst till array[1][0]
direkt efter skulle generera ett andra cache-fel, eftersom det är sizeof(type)*10000
byte från array[0][0]
, och därför inte på samma cachelinje. Därför är det att initera så här ineffektivt:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[j][i] = 0;
}
}
Och iterating som detta är mer effektivt:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[i][j] = 0;
}
}
På samma sätt är det därför när det handlar om en matris med en dimension och flera index (låt oss säga två dimensioner här för enkelhets skull med index i och j) är det viktigt att iterera igenom arrayen så här:
#define DIM_X 10
#define DIM_Y 20
int array[DIM_X*DIM_Y];
size_t i, j;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
array[i*DIM_Y+j] = 0;
}
}
Eller med 3 dimensioner och index i, j och k:
#define DIM_X 10
#define DIM_Y 20
#define DIM_Z 30
int array[DIM_X*DIM_Y*DIM_Z];
size_t i, j, k;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
for (k = 0; k < DIM_Z; ++k)
{
array[i*DIM_Y*DIM_Z+j*DIM_Z+k] = 0;
}
}
}
Eller på ett mer generiskt sätt, när vi har en matris med N1 x N2 x ... x Nd- element, d- dimensioner och index som anges som n1, n2, ..., och beräknas offset så här
Bild / formel från: https://sv.wikipedia.org/wiki/Row-major_order
Multidimensionella matriser
C-programmeringsspråket tillåter flerdimensionella matriser . Här är den allmänna formen av en flerdimensionell matrisdeklaration -
type name[size1][size2]...[sizeN];
Till exempel skapar följande deklaration ett tredimensionellt (5 x 10 x 4) heltalarray:
int arr[5][10][4];
Två-dimensionella matriser
Den enklaste formen för flerdimensionell matris är den tvådimensionella matrisen. En tvådimensionell matris är i huvudsak en lista med endimensionell matris. För att deklarera en tvådimensionell heltal med dimensioner mxn, kan vi skriva på följande sätt:
type arrayName[m][n];
Där type
kan vara vilken som helst giltig C-datatyp ( int
, float
, etc.) och arrayName
kan vara vilken som helst giltig C-identifierare. En tvådimensionell matris kan visualiseras som en tabell med m
rader och n
kolumner. Obs : Ordningen spelar ingen roll i C. Arrayen int a[4][3]
är inte densamma som arrayen int a[3][4]
. Antalet rader kommer först som C är en rad -Major språk.
En tvådimensionell matris a
, som innehåller tre rader och fyra kolumner kan visas enligt följande:
Således identifieras varje element i matrisen a
med ett elementnamn i formen a[i][j]
, där a
är namnet på matrisen, i
representerar vilken rad och j
representerar vilken kolumn. Kom ihåg att rader och kolumner är nollindexerade. Detta liknar mycket matematisk notation för att prenumerera 2-D-matriser.
Initiera tvådimensionella matriser
Flerdimensionella matriser kan initialiseras genom att specificera parentesvärden för varje rad. Följande definierar en matris med 3 rader där varje rad har 4 kolumner.
int a[3][4] = {
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};
De kapslade hängslen, som anger den avsedda raden, är valfria. Följande initialisering motsvarar föregående exempel:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
Medan metoden för att skapa matriser med kapslade hängslen är valfri, uppmuntras den starkt eftersom den är mer läsbar och tydligare.
Åtkomst till tvådimensionella arrayelement
Ett element i en tvådimensionell matris nås med hjälp av underskript, dvs radindex och kolumnindex för matrisen. Till exempel -
int val = a[2][3];
Ovanstående uttalande tar det fjärde elementet från den tredje raden i matrisen. Låt oss kontrollera följande program där vi har använt en kapslad slinga för att hantera en tvådimensionell matris:
#include <stdio.h>
int main () {
/* an array with 5 rows and 2 columns*/
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
int i, j;
/* output each array element's value */
for ( i = 0; i < 5; i++ ) {
for ( j = 0; j < 2; j++ ) {
printf("a[%d][%d] = %d\n", i,j, a[i][j] );
}
}
return 0;
}
När koden ovan kompileras och körs ger den följande resultat:
a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8
Tredimensionell matris:
En 3D-matris är i huvudsak en matris med matriser av matriser: det är en matris eller samling av 2D-matriser, och en 2D-matris är en matris med 1D-matriser.
3D array-minneskarta:
Initiera en 3D-array:
double cprogram[3][2][4]={
{{-0.1, 0.22, 0.3, 4.3}, {2.3, 4.7, -0.9, 2}},
{{0.9, 3.6, 4.5, 4}, {1.2, 2.4, 0.22, -1}},
{{8.2, 3.12, 34.2, 0.1}, {2.1, 3.2, 4.3, -2.0}}
};
Vi kan ha matriser med valfritt antal dimensioner, även om det är troligt att de flesta av matriserna som skapas kommer att ha en eller två dimensioner.
Iterera genom en matris med hjälp av pekare
#include <stdio.h>
#define SIZE (10)
int main()
{
size_t i = 0;
int *p = NULL;
int a[SIZE];
/* Setting up the values to be i*i */
for(i = 0; i < SIZE; ++i)
{
a[i] = i * i;
}
/* Reading the values using pointers */
for(p = a; p < a + SIZE; ++p)
{
printf("%d\n", *p);
}
return 0;
}
Här, i initieringen av p
i det första for
slingtillstånd, arrayen a
sönderfall till en pekare till dess första element, som det skulle i nästan alla platser där en sådan uppsättning variabel används.
Sedan utför ++p
pekaren aritmetik på pekaren p
och går en efter en genom elementen i matrisen, och hänvisar till dem genom att återföra dem med *p
.
Vidarebefordra flerdimensionella matriser till en funktion
Flerdimensionella matriser följer samma regler som endimensionella matriser när de överförs till en funktion. Kombinationen av sönderfall-till-pekare, operatörsföreträde och de två olika sätten att förklara en flerdimensionell matris (matris med matriser mot array av pekare) kan göra förklaringen av sådana funktioner icke-intuitiv. Följande exempel visar de rätta sätten att passera multidimensionella matriser.
#include <assert.h>
#include <stdlib.h>
/* When passing a multidimensional array (i.e. an array of arrays) to a
function, it decays into a pointer to the first element as usual. But only
the top level decays, so what is passed is a pointer to an array of some fixed
size (4 in this case). */
void f(int x[][4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* This prototype is equivalent to f(int x[][4]).
The parentheses around *x are required because [index] has a higher
precedence than *expr, thus int *x[4] would normally be equivalent to int
*(x[4]), i.e. an array of 4 pointers to int. But if it's declared as a
function parameter, it decays into a pointer and becomes int **x,
which is not compatable with x[2][4]. */
void g(int (*x)[4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* An array of pointers may be passed to this, since it'll decay into a pointer
to pointer, but an array of arrays may not. */
void h(int **x) {
assert(sizeof(*x) == sizeof(int*));
}
int main(void) {
int foo[2][4];
f(foo);
g(foo);
/* Here we're dynamically creating an array of pointers. Note that the
size of each dimension is not part of the datatype, and so the type
system just treats it as a pointer to pointer, not a pointer to array
or array of arrays. */
int **bar = malloc(sizeof(*bar) * 2);
assert(bar);
for (size_t i = 0; i < 2; i++) {
bar[i] = malloc(sizeof(*bar[i]) * 4);
assert(bar[i]);
}
h(bar);
for (size_t i = 0; i < 2; i++) {
free(bar[i]);
}
free(bar);
}
Se även
Vidarebefordra i matriser till funktioner