C Language
Erklärungen
Suche…
Bemerkungen
Die Deklaration eines Bezeichners, der sich auf ein Objekt oder eine Funktion bezieht, wird häufig kurz als Deklaration eines Objekts oder einer Funktion bezeichnet.
Aufruf einer Funktion aus einer anderen C-Datei
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!")`
*/
}
Haupt c
#include "foo.h"
int main(void)
{
foo(42, "bar");
return 0;
}
Kompilieren und Verknüpfen
Zuerst kompilieren wir foo.c
und main.c
in Objektdateien . Hier verwenden wir den gcc
Compiler. Ihr Compiler hat möglicherweise einen anderen Namen und benötigt andere Optionen.
$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c
Jetzt verknüpfen wir sie miteinander, um unsere endgültige ausführbare Datei zu erstellen:
$ gcc -o testprogram foo.o main.o
Verwendung einer globalen Variablen
Von der Verwendung globaler Variablen wird generell abgeraten. Dies macht Ihr Programm schwieriger zu verstehen und schwieriger zu debuggen. Aber manchmal ist die Verwendung einer globalen Variablen akzeptabel.
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. */
Haupt c
#include "global.h"
int main(void)
{
g_myglobal = 42;
return 0;
}
Siehe auch Wie verwende ich extern
, um Variablen zwischen Quelldateien zu teilen?
Globale Konstanten verwenden
Header können verwendet werden, um global verwendete schreibgeschützte Ressourcen, wie zum Beispiel Stringtabellen, zu deklarieren.
Deklarieren Sie diese in einem separaten Header, der in jeder Datei (" Translation Unit ") enthalten ist, die sie verwenden möchte. Es ist praktisch, den gleichen Header zu verwenden, um eine zugehörige Aufzählung zu definieren, um alle String-Ressourcen zu identifizieren:
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
Um tatsächlich die Ressourcen zu definieren, die eine zugehörige .c-Datei erstellt haben, ist dies eine weitere Übersetzungseinheit, die die tatsächlichen Instanzen der in der zugehörigen Header-Datei (.h) deklarierten Dateien enthält:
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"
};
Ein Programm, das dies verwendet, könnte folgendermaßen aussehen:
Haupt 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;
}
Kompilieren Sie die drei obigen Dateien mithilfe von GCC und verknüpfen Sie sie, um beispielsweise die Programmdatei main
zu werden.
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(Verwenden Sie diese -Wall -Wextra -pedantic -Wconversion
, um den Compiler wirklich wählerisch zu machen, damit Sie nichts verpassen, bevor Sie den Code in SO veröffentlichen, sagen Sie der Welt, oder lohnt es sich, ihn in der Produktion einzusetzen).
Führen Sie das erstellte Programm aus:
$ ./main
Und bekomme:
resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'
Einführung
Beispiele für Deklarationen sind:
int a; /* declaring single identifier of type int */
Die obige Deklaration deklariert einen einzelnen Bezeichner mit dem Namen a
der sich auf ein Objekt mit dem Typ int
bezieht.
int a1, b1; /* declaring 2 identifiers of type int */
Die zweite Deklaration deklariert 2 Bezeichner mit den Namen a1
und b1
die sich auf andere Objekte beziehen, jedoch mit demselben int
Typ.
Im Grunde funktioniert das folgendermaßen: Zuerst geben Sie einen Typ ein , dann schreiben Sie einen einzelnen oder mehrere durch Kommas getrennte Ausdrücke ( ,
die an dieser Stelle nicht ausgewertet werden) und die sonst als Deklaratoren bezeichnet werden diesem Kontext ). Beim Schreiben solcher Ausdrücke dürfen Sie nur die Operatoren Indirection ( *
), Funktionsaufruf ( ( )
) oder Subskription (oder Array-Indizierung - [ ]
) auf einen Bezeichner anwenden (Sie können auch keine Operatoren verwenden). Der verwendete Bezeichner muss im aktuellen Bereich nicht sichtbar sein. Einige Beispiele:
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | Beschreibung |
---|---|
1 | Der Name des ganzzahligen Typs. |
2 | Nicht ausgewerteter Ausdruck, der die Indirektion auf einen Bezeichner anwendet, z . |
3 | Wir haben ein Komma, das darauf hinweist, dass in derselben Deklaration ein weiterer Ausdruck folgt. |
4 | Nicht ausgewerteter Ausdruck, der die Indirektion auf einen anderen Bezeichner x anwendet. |
5 | Nicht ausgewerteter Ausdruck, der Indirektion auf den Wert des Ausdrucks (*c) anwendet. |
6 | Ende der Erklärung |
Beachten Sie, dass vor dieser Deklaration keine der oben genannten Bezeichner sichtbar waren und die verwendeten Ausdrücke daher nicht gültig waren.
Nach jedem solchen Ausdruck wird der darin verwendete Bezeichner in den aktuellen Bereich eingefügt. (Wenn der Bezeichner ihm eine Verknüpfung zugewiesen hat, kann er auch mit derselben Art von Verknüpfung neu deklariert werden, sodass sich beide Bezeichner auf dasselbe Objekt oder dieselbe Funktion beziehen.)
Zusätzlich kann das Gleichheitszeichen ( =
) zur Initialisierung verwendet werden. Wenn auf einen nicht ausgewerteten Ausdruck (Deklarator) innerhalb der Deklaration =
gefolgt wird, sagen wir, dass der eingeführte Bezeichner ebenfalls initialisiert wird. Nach dem =
-Zeichen können wir noch einmal einen Ausdruck setzen, aber dieses Mal wird es ausgewertet und sein Wert wird als Anfang für das deklarierte Objekt verwendet.
Beispiele:
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];
Später in Ihrem Code können Sie genau den gleichen Ausdruck aus dem Deklarationsteil des neu eingeführten Bezeichners schreiben. Dadurch erhalten Sie ein Objekt des Typs, der zu Beginn der Deklaration angegeben wurde. Dabei wird davon ausgegangen, dass Sie allen Zugriff auf gültige Werte zugewiesen haben Objekte im Weg. Beispiele:
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 */
}
Die Deklaration von b3
gibt an, dass Sie den b3
Wert möglicherweise als Mittelwert für den Zugriff auf ein ganzzahliges Objekt verwenden können.
Um Indirection ( *
) auf b3
anzuwenden, müssen Sie natürlich auch einen korrekten Wert speichern (siehe Zeiger für weitere Informationen). Sie sollten auch einen Wert in ein Objekt speichern , zuerst bevor Sie es abrufen (können Sie mehr über dieses Problem finden Sie hier ). Wir haben das alles in den obigen Beispielen gemacht.
int a3(); /* you should be able to call a3 */
Dies sagt dem Compiler, dass Sie versuchen, a3
aufzurufen. In diesem Fall bezieht sich a3
auf die Funktion anstelle eines Objekts. Ein Unterschied zwischen Objekt und Funktion besteht darin, dass Funktionen immer eine Art Verknüpfung aufweisen. Beispiele:
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
In dem obigen Beispiel beziehen sich die 2 Deklarationen auf dieselbe Funktion f2
, während sie, wenn sie Objekte deklarieren würden, in diesem Zusammenhang (mit zwei verschiedenen Blockbereichen) zwei unterschiedliche Objekte haben würden.
int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */
Nun scheint es kompliziert zu werden, aber wenn Sie die Vorrangigkeit der Operatoren kennen, haben Sie 0 Probleme beim Lesen der obigen Deklaration. Die Klammern werden benötigt, da der Operator *
eine geringere Priorität als der ( )
.
Im Falle der Verwendung des Indexoperators ist der resultierende Ausdruck nach der Deklaration nicht wirklich gültig, da der darin verwendete Index (der Wert innerhalb von [
und ]
) immer 1 über dem maximal zulässigen Wert für dieses Objekt / diese Funktion liegt.
int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */
Es sollte jedoch für alle anderen Indizes unter 5 verfügbar sein. Beispiele:
a4[0], a4[1]; a4[4];
a4[5]
führt zu UB. Weitere Informationen zu Arrays finden Sie hier .
int (*a5)[5](); /* here a4 could be applied indirection
indexed up to (but not including) 5
and called */
Leider ist die Deklaration von a5
, obwohl syntaktisch möglich, durch den aktuellen Standard verboten.
Typedef
Typedefs sind Deklarationen, bei denen das Schlüsselwort typedef
vor und vor dem Typ steht. Z.B:
typedef int (*(*t0)())[5];
( Sie können das typedef auch technisch nach dem Typ setzen - wie hier int typedef (*(*t0)())[5];
Die obigen Deklarationen deklarieren einen Bezeichner für einen Typedef-Namen. Sie können es später so verwenden:
t0 pf;
Was hat den gleichen Effekt wie das Schreiben:
int (*(*pf)())[5];
Wie Sie sehen, "speichert" der Typedef-Name die Deklaration als einen Typ, der später für andere Deklarationen verwendet wird. Auf diese Weise können Sie einige Tastatureingaben speichern. Auch als Deklaration mit typedef
sind Sie immer noch eine Deklaration. Sie sind nicht nur auf das obige Beispiel beschränkt:
t0 (*pf1);
Ist das gleiche wie:
int (*(**pf1)())[5];
Verwenden der Rechts-Links- oder Spiralregel zum Entschlüsseln der C-Deklaration
Die "Rechts-Links" -Regel ist eine völlig reguläre Regel zum Entschlüsseln von C-Deklarationen. Es kann auch nützlich sein, um sie zu erstellen.
Lesen Sie die Symbole, wenn Sie auf sie in der Deklaration stoßen ...
* as "pointer to" - always on the left side
[] as "array of" - always on the right side
() as "function returning" - always on the right side
So wenden Sie die Regel an
SCHRITT 1
Identifizierer suchen Dies ist dein Ausgangspunkt. Dann sagen Sie sich: "Identifier is". Sie haben mit Ihrer Erklärung begonnen.
SCHRITT 2
Schauen Sie sich die Symbole auf der rechten Seite des Bezeichners an. Wenn Sie beispielsweise ()
dort finden, wissen Sie, dass dies die Deklaration einer Funktion ist. Sie hätten also "Kennung gibt Funktion zurück" . Wenn Sie dort ein []
haben, würden Sie sagen "Bezeichner ist Array von" . Fahren Sie nach rechts fort, bis Ihnen die Symbole ausgehen ODER drücken Sie eine rechte Klammer )
. (Wenn Sie eine linke Klammer treffen (
, das ist der Beginn eines ()
Symbol, auch wenn es Zeug ist in den Klammern. Mehr dazu weiter unten.)
SCHRITT 3
Schauen Sie sich die Symbole links neben dem Bezeichner an. Wenn es sich nicht um eines unserer Symbole handelt (sagen Sie etwas wie "int"), sagen Sie es einfach. Andernfalls übersetzen Sie es mit Hilfe der obigen Tabelle ins Englische. Fahren Sie weiter nach links, bis Ihnen die Symbole ausgehen ODER Sie eine linke Klammer (
.
Wiederholen Sie die Schritte 2 und 3, bis Sie Ihre Deklaration erstellt haben.
Hier sind einige Beispiele:
int *p[];
Zuerst Identifizierer finden:
int *p[];
^
"p ist"
Bewegen Sie sich jetzt nach rechts, bis keine Symbole oder die rechte Klammer getroffen ist.
int *p[];
^^
"p ist Array von"
Kann sich nicht mehr nach rechts bewegen (außerhalb von Symbolen), also nach links gehen und suchen:
int *p[];
^
"p ist ein Array von Zeiger auf"
Gehe nach links und finde:
int *p[];
^^^
"p ist ein Array von Zeiger auf int".
(oder "p ist ein Array, in dem jedes Element vom Typ Zeiger auf int ist" )
Ein anderes Beispiel:
int *(*func())();
Identifizierer suchen
int *(*func())();
^^^^
"func is"
Nach rechts bewegen.
int *(*func())();
^^
"Funktion ist Rückkehr"
Ich kann mich wegen der rechten Klammer nicht mehr nach rechts bewegen, also nach links gehen.
int *(*func())();
^
"func ist Funktion, die einen Zeiger auf" zurückgibt.
Ich kann mich wegen der linken Klammer nicht mehr nach links bewegen, also weiter nach rechts.
int *(*func())();
^^
"func ist Funktion, die Zeiger auf Funktion zurückgibt"
Ich kann mich nicht mehr nach rechts bewegen, weil wir keine Symbole mehr haben. Gehen Sie also nach links.
int *(*func())();
^
"func ist Funktion, die den Zeiger auf die Funktion zurückgibt.
Und schließlich weiter nach links, denn rechts ist nichts mehr.
int *(*func())();
^^^
msgstr "func ist eine Funktion, die einen Zeiger auf eine Funktion zurückgibt und einen Zeiger auf int."
Wie Sie sehen, kann diese Regel sehr nützlich sein. Sie können es auch verwenden, um sich während der Erstellung von Deklarationen zu überprüfen und einen Hinweis darauf zu geben, wo das nächste Symbol platziert werden soll und ob Klammern erforderlich sind.
Einige Deklarationen sehen viel komplizierter aus, als sie aufgrund von Arraygrößen und Argumentlisten in Prototypform sind. Wenn Sie [3]
, wird dies als "Array (Größe 3) von ..." gelesen. Wenn Sie (char *,int)
, wird dies als * "Funktion gelesen, die erwartet (char , int) und zurückgibt ..." .
Hier ist ein Spaß:
int (*(*fun_one)(char *,double))[9][20];
Ich werde nicht durch die einzelnen Schritte gehen, um diese zu entschlüsseln.
* "fun_one ist ein Zeiger auf eine Funktion, die einen Zeiger auf das Array (Größe 9) des Arrays (Größe 20) von int erwartet .
Wie Sie sehen, ist es nicht so kompliziert, wenn Sie die Arraygrößen und Argumentlisten loswerden:
int (*(*fun_one)())[][];
Sie können es auf diese Weise entschlüsseln und später die Array-Größen und Argumentlisten eingeben.
Einige abschließende Worte:
Es ist durchaus möglich, mit dieser Regel illegale Erklärungen abzugeben, daher ist ein gewisses Wissen darüber erforderlich, was in C legal ist. Wenn zum Beispiel das Obige gewesen wäre:
int *((*fun_one)())[][];
es hätte gelesen "fun_one ist ein Zeiger auf eine Funktion, die ein Array des Zeigerarrays auf int zurückgibt" . Da eine Funktion kein Array, sondern nur einen Zeiger auf ein Array zurückgeben kann, ist diese Deklaration ungültig.
Zu den illegalen Kombinationen gehören:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array
In allen oben genannten Fällen benötigen Sie eine Reihe von Klammern, um ein linkes *
-Symbol zwischen diesen Symbolen ()
und []
rechts zu binden, damit die Deklaration zulässig ist.
Hier einige weitere Beispiele:
Rechtliches
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
Illegal
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
Quelle: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html