Как я понимаю сложные объявления функции?

Поведение x ++ состоит в том, чтобы увеличить x, но возвратить значение прежде инкремент. Его названные сообщение увеличивают поэтому.

Так x = x ++; просто помещенный будет

1. возвратите значение , тогда

2. увеличьте x , тогда

3. присвойтесь исходное значение (возвратился на шаге 1) x к x .

32
задан Peter Mortensen 1 January 2016 в 18:58
поделиться

10 ответов

Как указывали другие, cdecl - правильный инструмент для работы.

Если вы хотите понять такого рода декларации без помощи cdecl, попробуйте читать изнутри и правильно влево

Взять один случайный пример из вашего списка char (* (* X [3]) ()) [5];
Начать с X, который является объявленным / определяемым идентификатором (и самый внутренний идентификатор):

char (*(*X[3])())[5];
         ^

X -

X[3]
 ^^^

X - массив из 3

(*X[3])
 ^                /* the parenthesis group the sub-expression */

X - массив из 3 указателей на

(*X[3])()
       ^^

X - массив из 3 указателей на функцию, принимающую неуказанный ( но фиксированное) количество аргументов

(*(*X[3])())
 ^                   /* more grouping parenthesis */

X - это массив из 3 указателей на функцию, принимающую неуказанное (но фиксированное) количество аргументов и возвращающую указатель

(*(*X[3])())[5]
            ^^^

X - это массив из 3 указателей на функцию, принимающую неопределенное (но фиксированное) количество аргументов и возврат указателя на массив из 5

char (*(*X[3])())[5];
^^^^                ^

X - это массив из 3 указателей на функцию, принимающую неуказанное (но фиксированное) количество аргументов и возвращающую указатель на массив из 5 char .

73
ответ дан 27 November 2019 в 19:46
поделиться

Похоже, это работа для инструмента cdecl:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

Я поискал официальную домашнюю страницу для этого инструмента, но не нашел ни одной, которая показалась бы подлинной. В Linux вы обычно можете ожидать, что выбранный вами дистрибутив будет включать этот инструмент, поэтому я просто установил его, чтобы сгенерировать приведенный выше образец.

13
ответ дан 27 November 2019 в 19:46
поделиться

Вы должны использовать инструмент cdecl . Он должен быть доступен в большинстве дистрибутивов Linux.

например, для этой функции он вернет вам:

char (* (* f ()) []) (); - объявить f как функцию, возвращающую указатель на массив указателя на функцию, возвращающую char

void (* f) (int, void (*) ()); - прототип указателя на функцию f. f - это функция, которая принимает два параметра: первый - int, а второй - указатель на функцию, которая возвращает void.

char far * far * ptr; - ptr - это дальний указатель на дальний указатель (который указывает на некоторый символ / байт).

char (* (* X [3]) ()) [5]; - X - это массив из 3 указателей на функцию, принимающую неопределенное количество аргументов и возвращающую указатель на массив из 5 символов.

typedef void (* pfun) (int, float); - объявление указателя функции pfun. pfun - это функция, которая принимает два параметра: первый - int, второй - типа float. функция не имеет возвращаемого значения;

например

void f1(int a, float b)
{ //do something with these numbers
};

Между прочим, сложные объявления, такие как последнее, встречаются нечасто. Вот пример, который я только что придумал для этой цели.

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}
5
ответ дан 27 November 2019 в 19:46
поделиться
char far *far *ptr;

Это устаревшая форма Microsoft, восходящая к MS-DOS и очень ранним временам Windows. Версия SHORT состоит в том, что это дальний указатель на дальний указатель на char, где дальний указатель может указывать где угодно в памяти, в отличие от ближнего указателя, который может указывать только на любое место в сегменте данных размером 64 КБ. Вы действительно не хотите знать подробности о моделях памяти Microsoft для работы с совершенно бессмысленной архитектурой сегментированной памяти Intel 80x86.

typedef void (*pfun)(int,float);

Это объявляет pfun как typedef для указателя на процедуру, которая принимает int и float . Обычно вы используете это в объявлении функции или прототипе, а именно.

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}
1
ответ дан 27 November 2019 в 19:46
поделиться

Прочтите его изнутри, аналогично тому, как вы решали бы такие уравнения, как {3 + 5 * [2 + 3 * ( x + 6 * 2)]} = 0 - вы начнете с решения того, что внутри () , затем [] и, наконец, {} :

char (*(*x())[])()
         ^

Th is означает, что x равно что-то .

char (*(*x())[])()
          ^^

x - это функция .

char (*(*x())[])()
        ^

x возвращает указатель на что-то .

char (*(*x())[])()
       ^    ^^^

x возвращает указатель на массив .

char (*(*x())[])()
      ^

x возвращает указатель на массив указателей .

char (*(*x())[])()
     ^         ^^^

x возвращает указатель на массив указателей на функции

char (*(*x())[])()
^^^^

Это означает, что указатель массива, возвращаемый x , указывает на массив указателей функций, которые указывают на функции, возвращающие символ .

Но да, используйте cdecl .Я сам проверял свой ответ :).

Если вас это все еще смущает (а, вероятно, и должно), попробуйте сделать то же самое на листе бумаги или в своем любимом текстовом редакторе. Невозможно узнать, что это значит, просто взглянув на это.

16
ответ дан 27 November 2019 в 19:46
поделиться

Похоже, ваш настоящий вопрос таков:

Каков вариант использования указателя на указатель?

Указатель на указатель обычно появляется, когда у вас есть массив некоторого типа T , а сам T является указателем на что-то еще. Например,

  • Что такое строка в C? Обычно это char * .
  • Хотели бы вы время от времени получать массив строк? Конечно.
  • Как бы вы его объявили? char * x [10] : x - это массив из 10 указателей на char , то есть 10 строк.

На этом этапе вам может быть интересно, откуда берется char ** . Он входит в картину из-за очень тесной взаимосвязи между арифметикой указателей и массивами в C. Имя массива, x (почти) всегда преобразуется в указатель на его первый элемент.

  • Какой первый элемент? A char * .
  • Что означает указатель на первый элемент? A char ** .

В языке C массив E1 [E2] определен как эквивалентный * (E1 + E2) . Обычно E1 - это имя массива, скажем x , которое автоматически преобразуется в char ** , а E2 - некоторый индекс. , скажем 3. (Это правило также объясняет, почему 3 [x] и x [3] - одно и то же.)

Указатели на указатели также появляются, когда вам нужен динамически выделяемый массив некоторого типа T , который сам является указателем . Для начала представим, что мы не знаем, что такое тип T.

  • Если нам нужен динамически распределяемый вектор T, какой тип нам нужен? T * vec .
  • Почему? Поскольку мы можем выполнять арифметику указателей в C, любой T * может служить основой непрерывной последовательности T в памяти.
  • Как нам разместить этот вектор, скажем, из n элементов? vec = malloc (n * sizeof (T)) ;

Эта история верна абсолютно для любого типа T , а значит, и для char * .

  • Что это за тип vec , если T равно char * ? char ** vec .

Указатели на указатели также появляются, когда у вас есть функция, которой необходимо изменить аргумент типа T, который сам является указателем .

  • Посмотрите объявление для strtol : long strtol (char * s, char ** endp, int b) .
  • Что все это значит? strtol преобразует строку из base b в целое число. Он хочет сказать вам, как далеко он зашел в строку. Возможно, он мог бы вернуть структуру, содержащую как long , так и char * , но это не то, как он объявлен.
  • Вместо этого он возвращает свой второй результат, передав адрес строки, которую он модифицирует перед возвратом.
  • Что опять за веревка? Ах да, char * .
  • Так что же адрес строки? символ ** .

Если вы будете блуждать по этому пути достаточно долго, вы также можете столкнуться с типами T *** , хотя вы почти всегда можете реструктурировать код, чтобы избежать их.

Наконец, указатели на указатели появляются в некоторых сложных реализациях связанных списков . Рассмотрим стандартное объявление двусвязного списка в C.

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

Это работает нормально, хотя я не буду воспроизводить здесь функции вставки / удаления, но с ним есть небольшая проблема. Любой узел может быть удален из списка (или перед ним вставлен новый узел) без ссылки на заголовок списка. Ну, не совсем какой-либо узел. Это неверно для первого элемента списка, где prev будет нулевым. Это может быть умеренно раздражающим в некоторых типах кода C, где вы работаете больше с самими узлами, чем со списком как концепцией. Это довольно частое явление в низкоуровневом системном коде.

Что, если мы перепишем узел следующим образом:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

В каждом узле prevp указывает не на предыдущий узел, а на следующий предыдущих узлов указатель. А как насчет первого узла? Это prevp указывает на голову . Если вы нарисуете такой список (и у вас есть , чтобы нарисовать его, чтобы понять, как это работает), вы увидите, что вы можете удалить первый элемент или вставить новый узел перед первым элементом без явного ссылка на голову по имени.

4
ответ дан 27 November 2019 в 19:46
поделиться

x: функция, возвращающая указатель на array [] указателя на функцию , возвращающую символ "- да?

У вас есть функция

Эта функция возвращает указатель.

] Этот указатель указывает на массив.

Этот массив является массивом указателей на функции (или указателей на функции)

Эти функции возвращают char *.

 what's the use case for a pointer to a pointer?

Один из них - облегчить возвращение значений с помощью аргументов.

Давайте скажем, у вас есть

int function(int *p)
  *p = 123;
   return 0; //success !
}

. Вы называете это как

int x;
function(&x);

Как видите, для того, чтобы функция могла изменять наши x , мы должны передать ей указатель на наш x.

Что, если x было не int, а char * ? Что ж, это все то же самое, мы должны передать на него указатель. Указатель на указатель:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

Вы называете это так

char *x;
function(&x); 
2
ответ дан 27 November 2019 в 19:46
поделиться

Забудьте про 1 и 2 - это чисто теоретически.

3: Используется в функции входа программы int main (int argc, char ** argv) . Вы можете получить доступ к списку строк, используя char ** . argv [0] = первая строка, argv [1] = вторая строка, ...

0
ответ дан 27 November 2019 в 19:46
поделиться

Передача указателя в качестве аргумента функции позволяет этой функции изменять содержимое указанной переменной, что может быть полезно для возврата информации средствами, отличными от возвращаемого функцией значения. Например, возвращаемое значение может уже использоваться для обозначения ошибки / успеха, или вы можете захотеть вернуть несколько значений. Синтаксис для этого в вызывающем коде - foo (& var), который принимает адрес var, то есть указатель на var.

Таким образом, если переменная, содержимое которой вы хотите, чтобы функция изменила, сама является указателем (например, строкой), параметр будет объявлен как указатель на указатель .

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated

int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
    *str1 = some_defined_string ;
    *str2 = alloc_string() ;
    *str3 = "!!" ;

    if (str2 != 0) {
        return 0 ; //successful
    } else {
        return -1 ; //error
    }
}

main()
{
    char *s1 ; //uninitialized
    char *s2 ;
    char *s3 ;

    int success = point_me_to_the_strings(&s1, &s2, &s3) ;

    printf("%s%s%s", s1, s2, s3) ;
}

Обратите внимание, что main () не выделяет памяти для строк, поэтому point_me_to_the_strings () не записывает в str1, str2 и str3, как если бы они были переданы как указатели на символы. Скорее point_me_to_the_strings () изменяет сами указатели, заставляя их указывать на разные места, и он может это делать, потому что у него есть указатели на них.

0
ответ дан 27 November 2019 в 19:46
поделиться

Ответ Remo.D для чтения функций - хорошее предложение. Вот некоторые ответы на другие.

Один из вариантов использования указателя на указатель - это когда вы хотите передать его функции, которая изменяет указатель. Например:

void foo(char **str, int len)
{
   *str = malloc(len);
}

Кроме того, это может быть массив строк:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

Обычно один не должен использовать такие сложные объявления, хотя иногда вам нужны типы, которые довольно сложны для таких вещей, как функция указатели.В таких случаях гораздо удобнее использовать typedef для промежуточных типов; например:

typedef void foofun(char**, int);
foofun *foofunptr;

Или для вашего первого примера «функция, возвращающая указатель на массив [] указателя на функцию, возвращающую char», вы можете сделать:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

На практике, если вы пишете что-то разумное, многие из них вещи будут иметь свои собственные значимые имена; например, это могут быть функции, возвращающие имена (какого-то типа), поэтому fun_returning_char может быть name_generator_type , а array_of_ptrs_to_fun может быть name_generator_list . Таким образом, вы можете свернуть его на пару строк и определить только эти два typedef, которые, вероятно, будут полезны в другом месте в любом случае.

2
ответ дан 27 November 2019 в 19:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: