C Language
verklaringen
Zoeken…
Opmerkingen
Identificatieverklaring die verwijst naar object of functie wordt vaak kortweg aangeduid als eenvoudig een verklaring van object of functie.
Een functie uit een ander C-bestand oproepen
foo.h
#ifndef FOO_DOT_H /* This is an "include guard" */
#define FOO_DOT_H /* prevents the file from being included twice. */
/* Including a header file twice causes all kinds */
/* of interesting problems.*/
/**
* This is a function declaration.
* It tells the compiler that the function exists somewhere.
*/
void foo(int id, char *name);
#endif /* FOO_DOT_H */
foo.c
#include "foo.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync. Put this
* header first in foo.c to ensure the header is self-contained.
*/
#include <stdio.h>
/**
* This is the function definition.
* It is the actual body of the function which was declared elsewhere.
*/
void foo(int id, char *name)
{
fprintf(stderr, "foo(%d, \"%s\");\n", id, name);
/* This will print how foo was called to stderr - standard error.
* e.g., foo(42, "Hi!") will print `foo(42, "Hi!")`
*/
}
main.c
#include "foo.h"
int main(void)
{
foo(42, "bar");
return 0;
}
Compileren en koppelen
Eerst compileren we zowel foo.c
als main.c
om bestanden te foo.c
. Hier gebruiken we de gcc
compiler, uw compiler heeft mogelijk een andere naam en heeft andere opties nodig.
$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c
Nu koppelen we ze samen om ons definitieve uitvoerbare bestand te produceren:
$ gcc -o testprogram foo.o main.o
Een globale variabele gebruiken
Het gebruik van globale variabelen wordt over het algemeen afgeraden. Het maakt uw programma moeilijker te begrijpen en moeilijker te debuggen. Maar soms is het gebruik van een globale variabele acceptabel.
global.h
#ifndef GLOBAL_DOT_H /* This is an "include guard" */
#define GLOBAL_DOT_H
/**
* This tells the compiler that g_myglobal exists somewhere.
* Without "extern", this would create a new variable named
* g_myglobal in _every file_ that included it. Don't miss this!
*/
extern int g_myglobal; /* _Declare_ g_myglobal, that is promise it will be _defined_ by
* some module. */
#endif /* GLOBAL_DOT_H */
global.c
#include "global.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync.
*/
int g_myglobal; /* _Define_ my_global. As living in global scope it gets initialised to 0
* on program start-up. */
main.c
#include "global.h"
int main(void)
{
g_myglobal = 42;
return 0;
}
Zie ook Hoe gebruik ik extern
om variabelen tussen bronbestanden te delen?
Wereldwijde constanten gebruiken
Headers kunnen worden gebruikt om algemeen gebruikte alleen-lezen bronnen aan te geven, zoals bijvoorbeeld string-tabellen.
Verklaar die in een aparte koptekst die wordt opgenomen in elk bestand (" Translation Unit ") dat er gebruik van wil maken. Het is handig om dezelfde koptekst te gebruiken om een gerelateerde opsomming aan te geven om alle tekenreeksbronnen te identificeren:
resources.h:
#ifndef RESOURCES_H
#define RESOURCES_H
typedef enum { /* Define a type describing the possible valid resource IDs. */
RESOURCE_UNDEFINED = -1, /* To be used to initialise any EnumResourceID typed variable to be
marked as "not in use", "not in list", "undefined", wtf.
Will say un-initialised on application level, not on language level. Initialised uninitialised, so to say ;-)
Its like NULL for pointers ;-)*/
RESOURCE_UNKNOWN = 0, /* To be used if the application uses some resource ID,
for which we do not have a table entry defined, a fall back in
case we _need_ to display something, but do not find anything
appropriate. */
/* The following identify the resources we have defined: */
RESOURCE_OK,
RESOURCE_CANCEL,
RESOURCE_ABORT,
/* Insert more here. */
RESOURCE_MAX /* The maximum number of resources defined. */
} EnumResourceID;
extern const char * const resources[RESOURCE_MAX]; /* Declare, promise to anybody who includes
this, that at linkage-time this symbol will be around.
The 1st const guarantees the strings will not change,
the 2nd const guarantees the string-table entries
will never suddenly point somewhere else as set during
initialisation. */
#endif
Om de bronnen daadwerkelijk te definiëren, heeft u een gerelateerd .c-bestand gemaakt, dat is een andere vertaaleenheid die de werkelijke instanties bevat van wat in het gerelateerde header (.h) -bestand was aangegeven:
resources.c:
#include "resources.h" /* To make sure clashes between declaration and definition are
recognised by the compiler include the declaring header into
the implementing, defining translation unit (.c file).
/* Define the resources. Keep the promise made in resources.h. */
const char * const resources[RESOURCE_MAX] = {
"<unknown>",
"OK",
"Cancel",
"Abort"
};
Een programma dat dit gebruikt, kan er zo uitzien:
main.c:
#include <stdlib.h> /* for EXIT_SUCCESS */
#include <stdio.h>
#include "resources.h"
int main(void)
{
EnumResourceID resource_id = RESOURCE_UNDEFINED;
while ((++resource_id) < RESOURCE_MAX)
{
printf("resource ID: %d, resource: '%s'\n", resource_id, resources[resource_id]);
}
return EXIT_SUCCESS;
}
Compileer de drie bovenstaande bestanden met behulp van GCC en koppel ze om het main
van het programmabestand te worden, bijvoorbeeld door dit te gebruiken:
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(gebruik deze -Wall -Wextra -pedantic -Wconversion
om de compiler erg kieskeurig te maken, zodat je niets mist voordat je de code op SO zet, zal de wereld zeggen, of zelfs de moeite waard om het in productie te gebruiken)
Voer het gemaakte programma uit:
$ ./main
En krijg:
resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'
Invoering
Voorbeelden van verklaringen zijn:
int a; /* declaring single identifier of type int */
In de bovenstaande verklaring wordt één ID genoemd met de naam a
die verwijst naar een object met het type int
.
int a1, b1; /* declaring 2 identifiers of type int */
De tweede aangifte declareert 2 identifiers genaamd a1
en b1
die naar enkele andere objecten verwijzen, hoewel met hetzelfde int
type.
Kortom, de manier waarop dit werkt is als volgt: eerst plaats je een type , dan schrijf je een of meerdere expressies gescheiden via komma ( ,
) ( die op dit punt niet worden geëvalueerd) en die anders als declarators moeten worden aangeduid in deze context ). Bij het schrijven van dergelijke uitdrukkingen is het u toegestaan om alleen de operatoren indirection ( *
), function call ( ( )
) of subscript (of array-indexering - [ ]
) op een bepaalde identificatie toe te passen (u kunt ook helemaal geen operatoren gebruiken). De gebruikte identifier hoeft niet zichtbaar te zijn in het huidige bereik. Een paar voorbeelden:
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | Beschrijving |
---|---|
1 | De naam van het type geheel getal. |
2 | Niet-geëvalueerde expressie die indirection toepast op een of andere identifier z . |
3 | We hebben een komma die aangeeft dat er nog een uitdrukking zal volgen in dezelfde verklaring. |
4 | Niet-geëvalueerde expressie die indirection toepast op een andere identificatie x . |
5 | Niet-geëvalueerde expressie die indirection toepast op de waarde van de expressie (*c) . |
6 | Einde aangifte. |
Merk op dat geen van de bovenstaande identificatiegegevens zichtbaar was vóór deze verklaring en dat de gebruikte uitdrukkingen daarvoor dus niet geldig waren.
Na elke dergelijke uitdrukking wordt de daarin gebruikte identificator geïntroduceerd in het huidige bereik. (Als aan de identifier een koppeling is toegewezen, kan deze ook opnieuw worden aangegeven met hetzelfde type koppeling zodat beide identifiers naar hetzelfde object of dezelfde functie verwijzen)
Bovendien kan het gelijke operator-teken ( =
) worden gebruikt voor initialisatie. Als een niet-beoordeelde uitdrukking (declarator) wordt gevolgd door =
in de aangifte - zeggen we dat de identificator die wordt ingevoerd ook wordt geïnitialiseerd. Na het =
-teken kunnen we weer wat expressie plaatsen, maar deze keer wordt het geëvalueerd en wordt de waarde gebruikt als initiaal voor het aangegeven object.
Voorbeelden:
int l = 90; /* the same as: */
int l; l = 90; /* if it the declaration of l was in block scope */
int c = 2, b[c]; /* ok, equivalent to: */
int c = 2; int b[c];
Later in uw code mag u exact dezelfde uitdrukking schrijven uit het declaratiegedeelte van de nieuw geïntroduceerde id, waardoor u een object krijgt van het type dat aan het begin van de declaratie is opgegeven, ervan uitgaande dat u geldige waarden hebt toegewezen aan alle toegangen objecten in de weg staan. Voorbeelden:
void f()
{
int b2; /* you should be able to write later in your code b2
which will directly refer to the integer object
that b2 identifies */
b2 = 2; /* assign a value to b2 */
printf("%d", b2); /*ok - should print 2*/
int *b3; /* you should be able to write later in your code *b3 */
b3 = &b2; /* assign valid pointer value to b3 */
printf("%d", *b3); /* ok - should print 2 */
int **b4; /* you should be able to write later in your code **b4 */
b4 = &b3;
printf("%d", **b4); /* ok - should print 2 */
void (*p)(); /* you should be able to write later in your code (*p)() */
p = &f; /* assign a valid pointer value */
(*p)(); /* ok - calls function f by retrieving the
pointer value inside p - p
and dereferencing it - *p
resulting in a function
which is then called - (*p)() -
it is not *p() because else first the () operator is
applied to p and then the resulting void object is
dereferenced which is not what we want here */
}
De declaratie van b3
geeft aan dat u mogelijk de waarde b3
kunt gebruiken als middel om toegang te krijgen tot een geheel getal.
Om indirection ( *
) op b3
, moet u natuurlijk ook een juiste waarde hebben opgeslagen (zie verwijzingen voor meer info). U moet ook eerst wat waarde in een object opslaan voordat u het probeert op te halen (u kunt hier meer over dit probleem zien ). We hebben dit allemaal gedaan in de bovenstaande voorbeelden.
int a3(); /* you should be able to call a3 */
Deze vertelt de compiler dat je a3
probeert te bellen. In dit geval verwijst a3
naar functie in plaats van een object. Een verschil tussen object en functie is dat functies altijd een soort koppeling hebben. Voorbeelden:
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
In het bovenstaande voorbeeld verwijzen de 2 verklaringen naar dezelfde functie f2
, terwijl als ze objecten zouden verklaren dan in deze context (met 2 verschillende blokbereiken), dit 2 verschillende afzonderlijke objecten zouden zijn.
int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */
Nu lijkt het misschien ingewikkeld te worden, maar als u weet dat operators prioriteit hebben, heeft u 0 problemen met het lezen van de bovenstaande verklaring. De haakjes zijn nodig omdat de operator *
minder prioriteit heeft dan de ( )
.
In het geval van het gebruik van de subscript-operator, zou de resulterende expressie niet geldig zijn na de aangifte, omdat de index die erin wordt gebruikt (de waarde tussen [
en ]
) altijd 1 boven de maximaal toegestane waarde voor dit object / functie zal zijn.
int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */
Maar het moet toegankelijk zijn voor alle andere indexen lager dan 5. Voorbeelden:
a4[0], a4[1]; a4[4];
a4[5]
zal resulteren in UB. Meer informatie over arrays vindt u hier .
int (*a5)[5](); /* here a4 could be applied indirection
indexed up to (but not including) 5
and called */
Helaas voor ons, hoewel syntactisch mogelijk, is de verklaring van a5
verboden door de huidige standaard.
typedef
Typedefs zijn verklaringen met het trefwoord typedef
vóór en vóór het type. bv:
typedef int (*(*t0)())[5];
( je kunt technisch gezien ook het typedef achter het type plaatsen - zoals dit int typedef (*(*t0)())[5];
maar dit wordt afgeraden )
De bovenstaande verklaringen verklaren een identificatie voor een typedefnaam. Je kunt het achteraf als volgt gebruiken:
t0 pf;
Welke hetzelfde effect heeft als schrijven:
int (*(*pf)())[5];
Zoals u ziet, "bewaart" de typedef-naam de aangifte als een type dat later voor andere aangiften wordt gebruikt. Op deze manier kunt u enkele toetsaanslagen opslaan. typedef
aangifte met typedef
nog steeds een aangifte is, wordt u niet alleen beperkt door het bovenstaande voorbeeld:
t0 (*pf1);
Is hetzelfde als:
int (*(**pf1)())[5];
Gebruik de rechts-links of spiraalregel om C-aangifte te ontcijferen
De regel "rechts-links" is een volledig reguliere regel voor het ontcijferen van C-aangiften. Het kan ook nuttig zijn om ze te maken.
Lees de symbolen zoals u ze tegenkomt in de verklaring ...
* as "pointer to" - always on the left side
[] as "array of" - always on the right side
() as "function returning" - always on the right side
Hoe de regel toe te passen
STAP 1
Zoek de identificatie. Dit is je startpunt. Zeg dan tegen jezelf: "identifier is." U bent begonnen met uw aangifte.
STAP 2
Kijk naar de symbolen rechts van de identificatie. Als u bijvoorbeeld ()
daar vindt, weet u dat dit de verklaring voor een functie is. Dus je zou dan hebben "identifier is function return" . Of als u daar een []
zou vinden, zou u zeggen "identifier is array of" . Ga door naar rechts totdat je geen symbolen meer hebt OF een rechter haakje raakt )
. (Als u een haakje links indrukt (
, dat is het begin van een ()
symbool, zelfs als er dingen tussen de haakjes staan. Meer daarover hieronder.)
STAP 3
Kijk naar de symbolen links van de identificatie. Als het niet een van onze bovenstaande symbolen is (zeg iets als "int"), zeg het dan gewoon. Anders vertaalt u het in het Engels met behulp van die tabel hierboven. Blijf links gaan totdat je geen symbolen meer hebt OF sla een haakje links (
.
Herhaal nu stap 2 en 3 totdat u uw aangifte hebt opgesteld.
Hier zijn enkele voorbeelden:
int *p[];
Zoek eerst de identificatie:
int *p[];
^
"p is"
Ga nu naar rechts totdat de symbolen uit zijn of de rechter haakjes raken.
int *p[];
^^
"p is array van"
Kan niet meer naar rechts bewegen (uit symbolen), dus ga naar links en zoek:
int *p[];
^
"p is een reeks van pointer naar"
Blijf links gaan en vind:
int *p[];
^^^
"p is array van pointer naar int".
(of "p is een array waarbij elk element van het type pointer naar int is" )
Een ander voorbeeld:
int *(*func())();
Zoek de identificatie.
int *(*func())();
^^^^
"func is"
Ga naar rechts.
int *(*func())();
^^
"func is functie retourneren"
Kan niet meer naar rechts bewegen vanwege het rechter haakje, dus ga naar links.
int *(*func())();
^
"func is functie die de aanwijzer terugzet naar"
Kan niet meer naar links bewegen vanwege het linker haakje, dus blijf rechts gaan.
int *(*func())();
^^
"func is functie terugkerende pointer naar functie terugkeert"
Kan niet meer naar rechts gaan omdat we geen symbolen meer hebben, dus ga naar links.
int *(*func())();
^
"func is functie terugkerende wijzer naar functie terugkerende wijzer naar"
En tot slot, blijf links gaan, want er is niets aan de rechterkant.
int *(*func())();
^^^
"func is functie terugkerende pointer naar functie terugkerende pointer naar int".
Zoals u ziet, kan deze regel behoorlijk nuttig zijn. U kunt het ook gebruiken om gezond verstand te controleren terwijl u verklaringen maakt en om u een hint te geven over waar u het volgende symbool moet plaatsen en of haakjes vereist zijn.
Sommige verklaringen zien er veel gecompliceerder uit dan ze te wijten zijn aan matrixgroottes en argumentlijsten in prototypevorm. Als u [3]
, wordt dat gelezen als "array (grootte 3) van ..." . Als u (char *,int)
dat gelezen als * "functie die (char , int) verwacht en terugkeert ..." .
Hier is een leuke:
int (*(*fun_one)(char *,double))[9][20];
Ik zal niet alle stappen doorlopen om deze te ontcijferen.
* "fun_one is een pointer die functioneert (verwacht, char , dubbel) en de pointer teruggeeft aan array (grootte 9) van array (grootte 20) van int."
Zoals je kunt zien, is het niet zo ingewikkeld als je de matrixgroottes en argumentenlijsten verliest:
int (*(*fun_one)())[][];
Je kunt het op die manier ontcijferen en later de matrixgroottes en argumentenlijsten invoeren.
Enkele laatste woorden:
Het is heel goed mogelijk om illegale verklaringen af te leggen met behulp van deze regel, dus enige kennis van wat legaal is in C is noodzakelijk. Als het bovenstaande bijvoorbeeld was geweest:
int *((*fun_one)())[][];
het zou hebben gelezen "fun_one is pointer om te functioneren, array van array van pointer terug naar int" . Aangezien een functie geen array kan retourneren, maar alleen een pointer naar een array, is die verklaring illegaal.
Illegale combinaties zijn onder meer:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array
In alle bovengenoemde gevallen heeft u een set haakjes nodig om een *
-symbool links tussen deze ()
en []
rechterkantsymbolen te binden om de verklaring legaal te maken.
Hier zijn nog enkele voorbeelden:
wettelijk
int i; an int
int *p; an int pointer (ptr to an int)
int a[]; an array of ints
int f(); a function returning an int
int **pp; a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[]; a pointer to an array of ints
int (*pf)(); a pointer to a function returning an int
int *ap[]; an array of int pointers (array of ptrs to ints)
int aa[][]; an array of arrays of ints
int *fp(); a function returning an int pointer
int ***ppp; a pointer to a pointer to an int pointer
int (**ppa)[]; a pointer to a pointer to an array of ints
int (**ppf)(); a pointer to a pointer to a function returning an int
int *(*pap)[]; a pointer to an array of int pointers
int (*paa)[][]; a pointer to an array of arrays of ints
int *(*pfp)(); a pointer to a function returning an int pointer
int **app[]; an array of pointers to int pointers
int (*apa[])[]; an array of pointers to arrays of ints
int (*apf[])(); an array of pointers to functions returning an int
int *aap[][]; an array of arrays of int pointers
int aaa[][][]; an array of arrays of arrays of int
int **fpp(); a function returning a pointer to an int pointer
int (*fpa())[]; a function returning a pointer to an array of ints
int (*fpf())(); a function returning a pointer to a function returning an int
Onwettig
int af[](); an array of functions returning an int
int fa()[]; a function returning an array of ints
int ff()(); a function returning a function returning an int
int (*pfa)()[]; a pointer to a function returning an array of ints
int aaf[][](); an array of arrays of functions returning an int
int (*paf)[](); a pointer to a an array of functions returning an int
int (*pff)()(); a pointer to a function returning a function returning an int
int *afp[](); an array of functions returning int pointers
int afa[]()[]; an array of functions returning an array of ints
int aff[]()(); an array of functions returning functions returning an int
int *fap()[]; a function returning an array of int pointers
int faa()[][]; a function returning an array of arrays of ints
int faf()[](); a function returning an array of functions returning an int
int *ffp()(); a function returning a function returning an int pointer
Bron: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html