Szukaj…


Wprowadzenie

Mechanizm typedef umożliwia tworzenie aliasów dla innych typów. Nie tworzy nowych typów. Ludzie często używają typedef aby poprawić przenośność kodu, nadać aliasy typom struktury lub unii lub tworzyć aliasy dla typów funkcji (lub wskaźnika funkcji).

W standardzie C typedef jest dla wygody klasyfikowany jako „klasa pamięci”; zachodzi syntaktycznie tam, gdzie mogłyby pojawić się klasy pamięci takie jak static lub extern .

Składnia

  • typedef nazwa_ istniejąca nazwa aliasu;

Uwagi


Wady Typedef

typedef może prowadzić do zanieczyszczenia przestrzeni nazw w dużych programach C.

Wady struktur Typedef

Również struktury typedef 'bez nazwy znacznika są główną przyczyną niepotrzebnego narzucania relacji uporządkowania między plikami nagłówkowymi.

Rozważać:

#ifndef FOO_H
#define FOO_H 1

#define FOO_DEF (0xDEADBABE)

struct bar; /* forward declaration, defined in bar.h*/

struct foo {
    struct bar *bar;
};

#endif

Przy takiej definicji, bez użycia typedefs , możliwe jest, że jednostka kompilująca uwzględni foo.h aby uzyskać definicję FOO_DEF . Jeśli nie spróbuje wyrejestrować elementu bar struktury foo wówczas nie będzie potrzeby bar.h pliku bar.h

Typedef vs #define

#define to dyrektywa dotycząca procesora wstępnego C, która służy również do definiowania aliasów dla różnych typów danych podobnych do typedef ale z następującymi różnicami:

  • typedef ogranicza się do nadawania nazw symbolicznych typom tylko wtedy, gdy #define może być również użyte do zdefiniowania aliasu dla wartości.

  • interpretacja typedef jest wykonywana przez kompilator, podczas gdy instrukcje #define są przetwarzane przez procesor wstępny.

  • Zauważ, że #define cptr char * po której następuje cptr a, b; nie robi tego samego co typedef char *cptr; następnie cptr a, b; . W przypadku #define b jest zwykłą zmienną char , ale jest również wskaźnikiem z typedef .

Typedef dla struktur i związków

Możesz nadać nazwy alias do struct :

typedef struct Person {
    char name[32];
    int age;
} Person;

Person person;

W porównaniu z tradycyjnym sposobem deklarowania struktur, programiści nie musieliby mieć struct każdym razem, gdy deklarują wystąpienie struktury.

Zauważ, że nazwa Person (w przeciwieństwie do struct Person ) nie jest zdefiniowana do ostatniego średnika. Dlatego w przypadku list połączonych i struktur drzewiastych, które muszą zawierać wskaźnik do tego samego typu struktury, należy użyć:

typedef struct Person {
    char name[32];
    int age;
    struct Person *next;
} Person;

lub:

typedef struct Person Person;

struct Person {
    char name[32];
    int age;
    Person *next;
};

Użycie typedef dla typu union jest bardzo podobne.

typedef union Float Float;

union Float
{
    float f;
    char  b[sizeof(float)];
};

Strukturę podobną do tej można wykorzystać do analizy bajtów tworzących wartość float .

Proste zastosowania Typedef

Do nadawania krótkich nazw typowi danych

Zamiast:

long long int foo;
struct mystructure object;

można użyć

/* write once */
typedef long long ll;
typedef struct mystructure mystruct;

/* use whenever needed */
ll foo;
mystruct object;

Zmniejsza to ilość potrzebnego pisania, jeśli typ jest używany wielokrotnie w programie.

Poprawa przenośności

Atrybuty typów danych różnią się w zależności od architektury. Na przykład int może być 2-bajtowym typem w jednej implementacji, a 4-bajtowym typem w innym. Załóżmy, że program musi używać typu 4-bajtowego, aby działać poprawnie.

W jednej realizacji niech wielkość int będzie wynosić 2 bajty, a long być 4 bajtami. W innym, niech rozmiar int będzie int 4 bajty, a long 8 bajtów. Jeśli program został napisany przy użyciu drugiej implementacji,

/* program expecting a 4 byte integer */
int foo; /* need to hold 4 bytes to work */
/* some code involving many more ints */

Aby program mógł działać w pierwszej implementacji, wszystkie deklaracje int będą musiały zostać zmienione na long .

/* program now needs long */
long foo; /*need to hold 4 bytes to work */
/* some code involving many more longs - lot to be changed */

Aby tego uniknąć, można użyć typedef

/* program expecting a 4 byte integer */
typedef int myint; /* need to declare once - only one line to modify if needed */
myint foo; /* need to hold 4 bytes to work */
/* some code involving many more myints */

Wówczas za każdym razem trzeba będzie zmieniać tylko instrukcję typedef , zamiast sprawdzać cały program.

C99

<stdint.h> i powiązany nagłówek <inttypes.h> definiują standardowe nazwy typów (przy użyciu typedef ) dla liczb całkowitych o różnych rozmiarach, a te nazwy są często najlepszym wyborem we współczesnym kodzie, który wymaga liczb całkowitych o stałym rozmiarze. Na przykład uint8_t jest 8-bitową liczbą całkowitą bez znaku; int64_t to 64-bitowa liczba całkowita ze int64_t . Typ uintptr_t jest liczbą całkowitą bez znaku wystarczająco dużą, aby pomieścić dowolny wskaźnik na obiekcie. Te typy są teoretycznie opcjonalne - ale rzadko są niedostępne. Istnieją warianty takie jak uint_least16_t (najmniejszy typ liczby całkowitej bez znaku z co najmniej 16 bitami) i int_fast32_t (typ liczby całkowitej z najszybszym int_fast32_t z co najmniej 32 bitami). Również intmax_t i uintmax_t są największymi typy całkowite wspierane przez wdrożenie. Te typy są obowiązkowe.

Aby określić użycie lub poprawić czytelność

Jeśli zbiór danych ma określony cel, można użyć typedef aby nadać mu sensowną nazwę. Ponadto, jeśli właściwość danych zmieni się w taki sposób, że typ podstawowy musi się zmienić, tylko instrukcja typedef musiałaby zostać zmieniona, zamiast badania całego programu.

Typedef dla wskaźników funkcji

Możemy użyć typedef aby uprościć korzystanie ze wskaźników funkcji. Wyobraźmy sobie, że mamy pewne funkcje, wszystkie o tej samej sygnaturze, które używają swojego argumentu do wydrukowania czegoś na różne sposoby:

#include<stdio.h>

void print_to_n(int n)
{
    for (int i = 1; i <= n; ++i)
        printf("%d\n", i);
}

void print_n(int n)
{
    printf("%d\n, n);
}

Teraz możemy użyć typedef aby utworzyć nazwany typ wskaźnika funkcji o nazwie drukarka:

typedef void (*printer_t)(int);

Tworzy to typ o nazwie printer_t dla wskaźnika do funkcji, która pobiera pojedynczy argument int i nie zwraca niczego, co odpowiada sygnaturze funkcji, które mamy powyżej. Aby go użyć, tworzymy zmienną utworzonego typu i przypisujemy wskaźnik do jednej z omawianych funkcji:

printer_t p = &print_to_n;
void (*p)(int) = &print_to_n; // This would be required without the type

Następnie, aby wywołać funkcję wskazywaną przez zmienną wskaźnika funkcji:

p(5);           // Prints 1 2 3 4 5 on separate lines
(*p)(5);        // So does this

Zatem typedef pozwala na prostszą składnię w przypadku wskaźników funkcji. Staje się to bardziej widoczne, gdy wskaźniki funkcji są używane w bardziej złożonych sytuacjach, takich jak argumenty funkcji.

Jeśli używasz funkcji, która przyjmuje wskaźnik funkcji jako parametr bez zdefiniowanego typu wskaźnika funkcji, definicją byłoby:

void foo (void (*printer)(int), int y){
    //code
    printer(y);
    //code
}

Jednak w typedef jest to:

void foo (printer_t printer, int y){
    //code
    printer(y);
    //code
}

Podobnie funkcje mogą zwracać wskaźniki funkcji i ponownie użycie typedef może uprościć składnię.

Klasycznym przykładem jest funkcja signal z <signal.h> . Deklaracja tego (od standardu C) to:

void (*signal(int sig, void (*func)(int)))(int);

Jest to funkcja, która przyjmuje dwa argumenty - int i wskaźnik do funkcji, która przyjmuje int jako argument i nic nie zwraca - i która zwraca wskaźnik, aby działał jak drugi argument.

Jeśli zdefiniowaliśmy typ SigCatcher jako alias wskaźnika dla typu funkcji:

typedef void (*SigCatcher)(int);

wtedy możemy zadeklarować signal() za pomocą:

SigCatcher signal(int sig, SigCatcher func);

Ogólnie rzecz biorąc, jest to łatwiejsze do zrozumienia (nawet jeśli standard C nie zdecydował się zdefiniować typu do wykonania zadania). Funkcja signal przyjmuje dwa argumenty, int i SigCatcher , i zwraca SigCatcher - gdzie SigCatcher jest wskaźnikiem do funkcji, która przyjmuje argument int i nic nie zwraca.

Chociaż używanie nazw typedef dla wskaźnika do typów funkcji ułatwia życie, może również prowadzić do zamieszania u innych osób, które utrzymają kod w przyszłości, więc używaj go z rozwagą i odpowiednią dokumentacją. Zobacz także Wskaźniki funkcji .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow