C Language
deklarationer
Sök…
Anmärkningar
Förklaring om identifierare som hänvisar till objekt eller funktion hänvisas ofta till korta som en förklaring av objekt eller funktion.
Ringa en funktion från en annan C-fil
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;
}
Kompilera och länka
Först kompilerar vi både foo.c
och main.c
till objektfiler . Här använder vi gcc
kompilatorn, din kompilator kan ha ett annat namn och behöver andra alternativ.
$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c
Nu kopplar vi dem samman för att producera vår slutliga körbara:
$ gcc -o testprogram foo.o main.o
Använda en global variabel
Användning av globala variabler är i allmänhet avskräckta. Det gör ditt program svårare att förstå och svårare att felsöka. Men ibland är det acceptabelt att använda en global variabel.
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;
}
Se även Hur använder jag extern
att dela variabler mellan källfiler?
Med hjälp av globala konstanter
Rubriker kan användas för att deklarera globalt använda skrivskyddade resurser, till exempel strängtabeller.
Förklara de i en separat rubrik som ingår i alla filer (" Översättningsenhet ") som vill använda dem. Det är praktiskt att använda samma rubrik för att förklara en relaterad uppräkning för att identifiera alla strängresurser:
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
För att faktiskt definiera resurserna skapade en relaterad .c-fil, det är en annan översättningsenhet som innehar de faktiska förekomsten av det som hade deklarerats i den relaterade rubrikfilen (.h):
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"
};
Ett program som använder detta kan se ut så här:
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;
}
Kompilera de tre filerna ovan med GCC och länka dem för att bli programfilens main
till exempel med hjälp av denna:
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(använd dessa -Wall -Wextra -pedantic -Wconversion
att göra kompilatorn verkligen picky, så att du inte missar något innan du lägger koden till SO, säger världen, eller till och med värt att distribuera den i produktion)
Kör programmet skapat:
$ ./main
Och få:
resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'
Introduktion
Exempel på deklarationer är:
int a; /* declaring single identifier of type int */
Ovanstående deklaration deklarerar en enda identifierare med namnet a
som hänvisar till något objekt med int
typ.
int a1, b1; /* declaring 2 identifiers of type int */
Den andra deklarationen deklarerar 2 identifierare med namnet a1
och b1
som avser vissa andra objekt men med samma int
typ.
I grund och botten är hur detta fungerar så här - först lägger du till någon typ , sedan skriver du ett enda eller flera uttryck separerade via komma ( ,
) ( som inte kommer att utvärderas vid denna tidpunkt - och som annars borde kallas deklaratorer i detta sammanhang ). När du skriver sådana uttryck får du endast använda operatörerna indirection ( *
), funktionssamtal ( ( )
) eller subscript (eller array indexering - [ ]
) på någon identifierare (du kan inte heller använda några operatörer alls). Den identifierare som används behöver inte vara synlig i det aktuella omfånget. Några exempel:
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | Beskrivning |
---|---|
1 | Namnet på heltalstyp. |
2 | Ej utvärderat uttryck som använder indirekt till någon identifierare z . |
3 | Vi har ett komma som indikerar att ytterligare ett uttryck kommer att följa i samma förklaring. |
4 | Ovärderat uttryck som använder indirekt till någon annan identifierare x . |
5 | Ovärderat uttryck som använder indirekt till värdet på uttrycket (*c) . |
6 | Slut på deklarationen. |
Observera att ingen av ovanstående identifierare var synliga före denna deklaration och därför skulle uttryck som använts inte vara giltiga innan den.
Efter varje sådant uttryck introduceras identifieraren som används i det nuvarande omfånget. (Om identifieraren har tilldelat länk till den kan den också deklareras med samma typ av länkning så att båda identifierarna hänvisar till samma objekt eller funktion)
Dessutom kan lika operatörstecken ( =
) användas för initialisering. Om ett ovärderat uttryck (deklarator) följs av =
inne i deklarationen - säger vi att identifieraren som införs också initialiseras. Efter =
-tecknet kan vi åter sätta något uttryck, men den här gången kommer det att utvärderas och dess värde kommer att användas som initial för det deklarerade objektet.
Exempel:
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];
Senare i din kod får du skriva exakt samma uttryck från deklarationsdelen av den nyinförda identifieraren, vilket ger dig ett objekt av den typ som anges i början av deklarationen, förutsatt att du har tilldelat giltiga värden till alla åtkomna föremål i vägen. Exempel:
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 */
}
Deklarationen av b3
anger att du potentiellt kan använda b3
värde som ett medel för att få åtkomst till ett heltalobjekt.
För att tillämpa indirection ( *
) på b3
bör du naturligtvis också ha ett korrekt värde lagrat i det (se pekare för mer info). Du bör också lagra något värde i ett objekt innan du försöker hämta det (du kan se mer om det här problemet här ). Vi har gjort allt detta i ovanstående exempel.
int a3(); /* you should be able to call a3 */
Den här säger kompilatorn att du försöker ringa a3
. I detta fall avser a3
funktion i stället för ett objekt. En skillnad mellan objekt och funktion är att funktioner alltid har någon slags länk. Exempel:
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
I exemplet ovan hänvisar de 2 deklarationerna till samma funktion f2
, medan om de deklarerar objekt då i detta sammanhang (med 2 olika blockomfång) skulle de ha två olika distinkta objekt.
int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */
Nu kan det tyckas bli komplicerat, men om du vet att operatörens företräde har du 0 problem med att läsa deklarationen ovan. Parenteserna behövs eftersom *
operatören har mindre företräde än den ( )
.
Vid användning av abonnentoperatören skulle det resulterande uttrycket faktiskt inte vara giltigt efter deklarationen eftersom indexet som används i det (värdet inuti [
och ]
) alltid är 1 över det högsta tillåtna värdet för detta objekt / funktion.
int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */
Men det bör vara tillgängligt av alla andra index lägre än 5. Exempel:
a4[0], a4[1]; a4[4];
a4[5]
kommer att bli UB. Mer information om matriser finns här .
int (*a5)[5](); /* here a4 could be applied indirection
indexed up to (but not including) 5
and called */
Tyvärr för oss, även om det är syntaktiskt möjligt, är deklarationen om a5
förbjuden enligt den nuvarande standarden.
typedef
Typedefs är deklarationer som har nyckelordet typedef
framför och före typen. T.ex:
typedef int (*(*t0)())[5];
( du kan tekniskt sätta typedef efter typen också - som den här int typedef (*(*t0)())[5];
men detta är avskräckt )
Ovanstående deklarationer deklarerar en identifierare för ett typedef-namn. Du kan använda det så här efteråt:
t0 pf;
Vilket kommer att ha samma effekt som att skriva:
int (*(*pf)())[5];
Som du kan se typedef-namnet "sparar" deklarationen som en typ som ska användas senare för andra deklarationer. På så sätt kan du spara några tangenttryckningar. Eftersom deklaration med typedef
fortfarande är en deklaration begränsas du inte bara av exemplet ovan:
t0 (*pf1);
Är det samma som:
int (*(**pf1)())[5];
Använd höger-vänster- eller spiralregeln för att dechiffrera C-deklarationen
Regeln "höger-vänster" är en helt regelbunden regel för att dechiffrera C-deklarationer. Det kan också vara användbart när du skapar dem.
Läs symbolerna när du möter dem i förklaringen ...
* as "pointer to" - always on the left side
[] as "array of" - always on the right side
() as "function returning" - always on the right side
Hur man tillämpar regeln
STEG 1
Hitta identifieraren. Det här är din utgångspunkt. Säg sedan till dig själv, "identifierare är." Du har börjat din förklaring.
STEG 2
Titta på symbolerna till höger om identifieraren. Om du säger att du hittar ()
där, så vet du att det här är deklarationen för en funktion. Så du skulle då ha "identifierare är funktionsåterföring" . Eller om du hittade ett []
där, skulle du säga "identifierare är matris med" . Fortsätt åt höger tills symbolerna ELLER träffar en högra parentes )
. (Om du träffar en vänster parentes (
, det är början på en ()
symbol, även om det finns saker i mellan parenteserna. Mer om detta nedan.)
STEG 3
Titta på symbolerna till vänster om identifieraren. Om det inte är en av våra symboler ovan (säg något som "int"), säg det bara. Översätt annars det till engelska med tabellen ovan. Fortsätt gå till vänster tills du får slut på symboler ELLER träffar en vänster parentes (
.
Upprepa nu steg 2 och 3 tills du har formulerat din förklaring.
Här är några exempel:
int *p[];
Först hitta identifierare:
int *p[];
^
"p är"
Flytta nu till höger tills symboler eller högra parenteser träffas.
int *p[];
^^
"p är matris av"
Kan inte flytta åt höger längre (utan symboler), så flytta till vänster och hitta:
int *p[];
^
"p är en rad pekare till"
Fortsätt gå till vänster och hitta:
int *p[];
^^^
"p är en rad pekare till int".
(eller "p är en matris där varje element är av typen pekare till int" )
Ett annat exempel:
int *(*func())();
Hitta identifieraren.
int *(*func())();
^^^^
"func är"
Flytta höger.
int *(*func())();
^^
"func är funktionsåtergång"
Kan inte flytta åt höger längre på grund av den högra parentesen, så flytta till vänster.
int *(*func())();
^
"func är funktion som returnerar pekaren till"
Kan inte flytta åt vänster längre på grund av den vänstra parentesen, så fortsätt åt höger.
int *(*func())();
^^
"func är funktion som returnerar pekaren till funktionsåterföring"
Kan inte flytta åt höger längre eftersom vi inte har några symboler, så gå till vänster.
int *(*func())();
^
"func är funktion som returnerar pekaren till funktionen som returnerar pekaren till"
Och slutligen, fortsätt vänster, för det finns inget kvar till höger.
int *(*func())();
^^^
"func är funktionen som returnerar pekaren till funktionen som returnerar pekaren till int".
Som ni ser kan denna regel vara ganska användbar. Du kan också använda den för att sanitetskontrollera dig själv när du skapar förklaringar, och för att ge dig ett tips om var du ska sätta nästa symbol och om parenteser krävs.
Vissa förklaringar ser mycket mer komplicerade ut än de beror på matrisstorlekar och argumentlistor i prototypform. Om du ser [3]
, läses det som "array (storlek 3) av ..." . Om du ser (char *,int)
som läses som * "funktion förväntar sig (char , int) och returnerar ..." .
Här är en rolig:
int (*(*fun_one)(char *,double))[9][20];
Jag kommer inte gå igenom var och en av stegen för att dechiffrera den här.
* "fun_one är pekaren för att förvänta sig (char , dubbel) och returnera pekaren till array (storlek 9) av array (storlek 20) av int."
Som ni ser är det inte så komplicerat om du blir av med matrisstorlekar och argumentlistor:
int (*(*fun_one)())[][];
Du kan dechiffrera det på det sättet och sedan lägga till matrisstorlekar och argumentlistor senare.
Några sista ord:
Det är fullt möjligt att avge olagliga deklarationer med denna regel, så viss kunskap om vad som är lagligt i C är nödvändigt. Om ovanstående exempelvis hade varit:
int *((*fun_one)())[][];
den skulle ha läst "fun_one is pekaren för att fungera tillbaka array of array of pekaren till int" . Eftersom en funktion inte kan returnera en matris, utan bara en pekare till en matris, är denna deklaration olaglig.
Olagliga kombinationer inkluderar:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array
I alla ovanstående fall skulle du behöva en uppsättning parenteser för att binda en *
-symbol till vänster mellan dessa ()
och []
högra sidosymboler för att deklarationen ska vara laglig.
Här är några fler exempel:
Rättslig
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
Olaglig
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
Källa: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html