C Language
structs
Sök…
Introduktion
Strukturer ger ett sätt att gruppera en uppsättning relaterade variabler av olika typer i en enda minneenhet. Strukturen som helhet kan refereras med ett enda namn eller pekare; Strukturmedlemmarna kan också nås individuellt. Strukturer kan överföras till funktioner och returneras från funktioner. De definieras med nyckelordet struct
.
Enkla datastrukturer
Strukturdatatyper är användbara sätt att paketrelatera data och låta dem uppträda som en enda variabel.
Förklara en enkel struct
som har två int
medlemmar:
struct point
{
int x;
int y;
};
x
och y
är kallade medlemmarna (eller fält) av point
struct.
Definiera och använda strukturer:
struct point p; // declare p as a point struct
p.x = 5; // assign p member variables
p.y = 3;
Strukturer kan initialiseras vid definition. Ovanstående motsvarar:
struct point p = {5, 3};
Strukturer kan också initialiseras med hjälp av utsedda initialiserare .
Åtkomst till fält görs också med hjälp av .
operatör
printf("point is (x = %d, y = %d)", p.x, p.y);
Typedef Structs
Att kombinera typedef
med struct
kan göra koden tydligare. Till exempel:
typedef struct
{
int x, y;
} Point;
i motsats till:
struct Point
{
int x, y;
};
kan förklaras som:
Point point;
istället för:
struct Point point;
Ännu bättre är att använda följande
typedef struct Point Point;
struct Point
{
int x, y;
};
att ha fördel av båda möjliga definitioner av point
. En sådan deklaration är mest bekväm om du först lär dig C ++, där du kan utelämna struct
nyckelordet om namnet inte är tvetydigt.
typedef
namn för strukturer kan vara i konflikt med andra identifierare av andra delar av programmet. Vissa anser att detta är en nackdel, men för de flesta som har en struct
och en annan identifierare är samma sak ganska oroande. Notorious är t.ex. POSIX ' stat
int stat(const char *pathname, struct stat *buf);
där du ser en funktion stat
som har ett argument som är struct stat
.
typedef
's strukturer utan taggnamn innebär alltid att hela struct
är synlig för koden som använder den. Hela struct
måste sedan placeras i en rubrikfil.
Överväga:
#include "bar.h"
struct foo
{
bar *aBar;
};
Så med en typedef
d struct
som inte har något taggnamn, bar.h
filen alltid inkludera hela definitionen av bar
. Om vi använder
typedef struct bar bar;
i bar.h
, detaljerna i bar
kan strukturen döljas.
Se Typedef
Pekare på strukturer
När du har en variabel som innehåller en struct
kan du komma åt dess fält med punktoperatören ( .
). Men om du har en pekare till en struct
fungerar detta inte. Du måste använda piloperatören ( ->
) för att komma åt dess fält. Här är ett exempel på ett oerhört enkelt (vissa kan säga "fruktansvärt och enkelt") implementering av en stack som använder pekare för att struct
och demonstrerar piloperatören.
#include <stdlib.h>
#include <stdio.h>
/* structs */
struct stack
{
struct node *top;
int size;
};
struct node
{
int data;
struct node *next;
};
/* function declarations */
int push(int, struct stack*);
int pop(struct stack*);
void destroy(struct stack*);
int main(void)
{
int result = EXIT_SUCCESS;
size_t i;
/* allocate memory for a struct stack and record its pointer */
struct stack *stack = malloc(sizeof *stack);
if (NULL == stack)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
/* initialize stack */
stack->top = NULL;
stack->size = 0;
/* push 10 ints */
{
int data = 0;
for(i = 0; i < 10; i++)
{
printf("Pushing: %d\n", data);
if (-1 == push(data, stack))
{
perror("push() failed");
result = EXIT_FAILURE;
break;
}
++data;
}
}
if (EXIT_SUCCESS == result)
{
/* pop 5 ints */
for(i = 0; i < 5; i++)
{
printf("Popped: %i\n", pop(stack));
}
}
/* destroy stack */
destroy(stack);
return result;
}
/* Push a value onto the stack. */
/* Returns 0 on success and -1 on failure. */
int push(int data, struct stack *stack)
{
int result = 0;
/* allocate memory for new node */
struct node *new_node = malloc(sizeof *new_node);
if (NULL == new_node)
{
result = -1;
}
else
{
new_node->data = data;
new_node->next = stack->top;
stack->top = new_node;
stack->size++;
}
return result;
}
/* Pop a value off of the stack. */
/* Returns the value popped off the stack */
int pop(struct stack *stack)
{
struct node *top = stack->top;
int data = top->data;
stack->top = top->next;
stack->size--;
free(top);
return data;
}
/* destroy the stack */
void destroy(struct stack *stack)
{
/* free all pointers */
while(stack->top != NULL)
{
pop(stack);
}
}
Flexibla array-medlemmar
Typdeklaration
En struktur med minst ett element kan dessutom innehålla en enda gruppdel med ospecificerad längd i slutet av strukturen. Detta kallas en flexibel gruppmedlem:
struct ex1
{
size_t foo;
int flex[];
};
struct ex2_header
{
int foo;
char bar;
};
struct ex2
{
struct ex2_header hdr;
int flex[];
};
/* Merged ex2_header and ex2 structures. */
struct ex3
{
int foo;
char bar;
int flex[];
};
Effekter på storlek och stoppning
Ett flexibelt arrayelement behandlas som att det inte har någon storlek vid beräkning av storleken på en struktur, även om stoppning mellan det elementet och det tidigare elementet i strukturen fortfarande kan existera:
/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));
/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));
/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));
Det flexibla arrayelementet anses ha en ofullständig arraytyp, så dess storlek kan inte beräknas med hjälp av sizeof
.
Användande
Du kan förklara och initialisera ett objekt med en strukturtyp som innehåller ett flexibelt array-medlem, men du får inte försöka initiera det flexibla array-medlemmet eftersom det behandlas som om det inte finns. Det är förbjudet att försöka göra detta och kompileringsfel kommer att resultera.
På liknande sätt bör du inte försöka tilldela ett värde till något element i ett flexibelt arrayelement när du deklarerar en struktur på detta sätt eftersom det kanske inte finns tillräckligt med vaddering i slutet av strukturen för att möjliggöra föremål som krävs av det flexibla arrayelementet. Kompilatorn kommer inte nödvändigtvis att hindra dig från att göra detta, så det kan leda till odefinierat beteende.
/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};
e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */
Du kan istället välja att använda malloc
, calloc
eller realloc
att fördela strukturen med extra lagring och senare frigöra den, vilket gör att du kan använda det flexibla array-medlemmet som du vill:
/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));
/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));
/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));
pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */
"Structure hack"
Flexibla matrismedlemmar fanns inte före C99 och behandlas som fel. En vanlig lösning är att förklara en matris med längd 1, en teknik som kallas 'strukt hack':
struct ex1
{
size_t foo;
int flex[1];
};
Detta kommer dock att påverka storleken på strukturen, till skillnad från en verklig flexibel matrisdel:
/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));
För att använda flex
elementet som ett flexibelt array-medlem skulle du tilldela det med malloc
som visas ovan, förutom att sizeof(*pe1)
(eller motsvarande sizeof(struct ex1)
) skulle ersättas med offsetof(struct ex1, flex)
eller den längre typ-agnostiska uttrycksstorleken sizeof(*pe1)-sizeof(pe1->flex)
. Alternativt kan du subtrahera 1 från önskad längd på den "flexibla" matrisen eftersom den redan ingår i strukturstorleken, förutsatt att den önskade längden är större än 0. Samma logik kan tillämpas på de andra användningsexemplen.
Kompatibilitet
Om kompatibilitet med kompilatorer som inte stöder flexibla array-medlemmar önskas, kan du använda ett makro definierat som FLEXMEMB_SIZE
nedan:
#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif
struct ex1
{
size_t foo;
int flex[FLEXMEMB_SIZE];
};
När du tilldelar objekt bör du använda offsetof(struct ex1, flex)
att hänvisa till strukturstorleken (exklusive det flexibla arrayelementet) eftersom det är det enda uttrycket som kommer att förbli konsekvent mellan kompilatorer som stöder flexibla arrayelement och kompilatorer som gör inte:
struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));
Alternativet är att använda förprocessorn för att villkorligt subtrahera 1 från den angivna längden. På grund av den ökade potentialen för inkonsekvens och allmän mänskliga fel i denna form flyttade jag logiken till en separat funktion:
struct ex1 *ex1_alloc(size_t n)
{
struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
if (n != 0)
n--;
#endif
return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...
/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);
Vidarebefordra strukturer till funktioner
I C överförs alla argument till funktioner efter värde, inklusive strukturer. För små strukturer är detta bra eftersom det innebär att det inte finns några omkostnader från åtkomst till data via en pekare. Men det gör det också väldigt lätt att av misstag passera en enorm struktur som resulterar i dålig prestanda, särskilt om programmeraren är van vid andra språk där argument skickas som referens.
struct coordinates
{
int x;
int y;
int z;
};
// Passing and returning a small struct by value, very fast
struct coordinates move(struct coordinates position, struct coordinates movement)
{
position.x += movement.x;
position.y += movement.y;
position.z += movement.z;
return position;
}
// A very big struct
struct lotsOfData
{
int param1;
char param2[80000];
};
// Passing and returning a large struct by value, very slow!
// Given the large size of the struct this could even cause stack overflow
struct lotsOfData doubleParam1(struct lotsOfData value)
{
value.param1 *= 2;
return value;
}
// Passing the large struct by pointer instead, fairly fast
void doubleParam1ByPtr(struct lotsOfData *value)
{
value->param1 *= 2;
}
Objektbaserad programmering med hjälp av strukturer
Strukturer kan användas för att implementera kod på ett objektorienterat sätt. En struktur liknar en klass, men saknas de funktioner som normalt också ingår i en klass, vi kan lägga till dessa som funktionspekarmedlemvariabler. För att följa vårt koordinateksempel:
/* coordinates.h */
typedef struct coordinate_s
{
/* Pointers to method functions */
void (*setx)(coordinate *this, int x);
void (*sety)(coordinate *this, int y);
void (*print)(coordinate *this);
/* Data */
int x;
int y;
} coordinate;
/* Constructor */
coordinate *coordinate_create(void);
/* Destructor */
void coordinate_destroy(coordinate *this);
Och nu den genomförande C-filen:
/* coordinates.c */
#include "coordinates.h"
#include <stdio.h>
#include <stdlib.h>
/* Constructor */
coordinate *coordinate_create(void)
{
coordinate *c = malloc(sizeof(*c));
if (c != 0)
{
c->setx = &coordinate_setx;
c->sety = &coordinate_sety;
c->print = &coordinate_print;
c->x = 0;
c->y = 0;
}
return c;
}
/* Destructor */
void coordinate_destroy(coordinate *this)
{
if (this != NULL)
{
free(this);
}
}
/* Methods */
static void coordinate_setx(coordinate *this, int x)
{
if (this != NULL)
{
this->x = x;
}
}
static void coordinate_sety(coordinate *this, int y)
{
if (this != NULL)
{
this->y = y;
}
}
static void coordinate_print(coordinate *this)
{
if (this != NULL)
{
printf("Coordinate: (%i, %i)\n", this->x, this->y);
}
else
{
printf("NULL pointer exception!\n");
}
}
Ett exempel på vår koordinatklass skulle vara:
/* main.c */
#include "coordinates.h"
#include <stddef.h>
int main(void)
{
/* Create and initialize pointers to coordinate objects */
coordinate *c1 = coordinate_create();
coordinate *c2 = coordinate_create();
/* Now we can use our objects using our methods and passing the object as parameter */
c1->setx(c1, 1);
c1->sety(c1, 2);
c2->setx(c2, 3);
c2->sety(c2, 4);
c1->print(c1);
c2->print(c2);
/* After using our objects we destroy them using our "destructor" function */
coordinate_destroy(c1);
c1 = NULL;
coordinate_destroy(c2);
c2 = NULL;
return 0;
}