Зачем нужен указатель на массив? [Дубликат]

TL; DR

Логический AND (and):

Возвращает первое значение False, если оно есть, else возвращает последнее значение в выражение

Логическое ИЛИ (or):

Возвращает первое значение Truthy, если оно есть, иначе верните последнее значение в выражении.


return len(args) and max(args) - min(args)

Является very питоновым лаконичным способом:

< blockquote>

, если args не пуст, верните результат из max(args) - min(args).

Операторы and и or не ограничены работой или возвращающих логические значения. Здесь можно протестировать любой объект со значением правдоподобия. Это включает в себя int, str, list, dict, tuple, set, NoneType и пользовательские объекты.

Обратите внимание, что это более сжатый способ построения выражения if-else:

return exp1 and exp2

Должен (грубо) перевести на:

r1 = exp1
if not r1:
    return r1

return exp2

Вот несколько примеров того, как и где эти конструкции могут использоваться для ручного ввода недопустимого ввода.

Пример с and (как показано OP)

Возвращает разницу между min и max группы аргументов.

def foo(*args):
     return len(args) and max(args) - min(args)

foo(1, 2, 3, 4, 5)
4

foo()
0

Поскольку and , второе выражение также должно быть оценено, если первое - True. Обратите внимание, что если первое выражение оценивается как Truthy, возвращаемый результат будет всегда результатом второго выражения .

Если первое выражение оценивается как Falsey, тогда возвращаемый результат является результатом первого выражения.

Если foo принимает любые аргументы, len(args) больше 0 (положительное число), поэтому Возвращаемый результат - max(args) - min(args).

Если foo не принимает аргументы, len(args) - 0, который является Falsey, и возвращается 0.

Обратите внимание, что альтернативным способом записи этой функции было бы следующее:

def foo(*args):
    if not len(args):
        return 0

    return max(args) - min(args)

Пример с or

Возвращает все числа над 9000.

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
[9004]

foo(1, 2, 3, 4)
'No number over 9000!'

В этом случае используется or. Если первым выражением является Truthy, то возвращаемый результат является результатом первого выражения. В противном случае оба выражения оцениваются, а результат возвращается в результате второго выражения.

foo выполняет фильтрацию в списке, чтобы сохранить все числа над 9000. Если существуют такие числа, результатом понимания списка является непустой список, который является Truthy, поэтому он возвращается (здесь короткое замыкание в действии).

Если таких чисел нет, то результатом списка comp является [], который является Falsey. Итак, второе выражение теперь оценивается (непустая строка) и возвращается.

И альтернативой для этой функции будет:

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 

    return r
100
задан Trevor Hickey 21 April 2016 в 05:39
поделиться

9 ответов

Я хотел бы добавить к ответу AndreyT (если кто-то наткнулся на эту страницу, ища дополнительную информацию по этой теме):

Когда я начинаю больше играть с этими объявлениями, я понимаю, что есть основной гандикап, связанный с ними в C (по-видимому, не в C ++). Достаточно распространено иметь ситуацию, когда вы хотели бы предоставить вызывающему абоненту const указатель на буфер, в который вы вложили. К сожалению, это невозможно при объявлении такого указателя на C. Другими словами, стандарт C (6.7.3 - параграф 8) не согласуется с чем-то вроде этого:


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

Это ограничение похоже, не присутствует в C ++, что делает эти типы объявлений более полезными. Но в случае C необходимо вернуться к регулярному объявлению указателя всякий раз, когда вам нужен указатель const на буфер фиксированного размера (если только сам буфер не был объявлен как const). Вы можете найти больше информации в этой почтовой цепочке: текст ссылки

Это серьезное ограничение, на мой взгляд, и это может быть одной из основных причин, почему люди обычно не объявляют указатели например, в C. Другой факт заключается в том, что большинство людей даже не знают, что вы можете объявить такой указатель, как указал AndreyT.

7
ответ дан figurassa 25 August 2018 в 14:31
поделиться

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

Когда специфика вашей области приложения вызывает массив определенного фиксированного размера (размер массива - константа времени компиляции), единственный правильный способ передать такой массив функции - использовать параметр указатель-массив

void foo(char (*p)[10]);

( на языке C ++ это также делается со ссылками

void foo(char (&p)[10]);

).

Это позволит проверить тип уровня на уровне языка, который обеспечит доставку массива точно правильного размера как аргумент. Фактически, во многих случаях люди используют этот метод неявно, даже не осознавая этого, скрывая тип массива за именем typedef

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

Обратите внимание, что приведенный выше код является инвариантным по отношению к типу Vector3d являющийся массивом или struct. Вы можете в любой момент переключить определение Vector3d с массива на struct и обратно, и вам не придется изменять объявление функции. В любом случае функции получат совокупный объект «по ссылке» (есть исключения из этого, но в контексте этого обсуждения это верно).

Однако вы не увидите этот метод Передача массива слишком часто используется слишком просто, потому что слишком много людей путают довольно запутанный синтаксис и просто не достаточно удобны с такими функциями языка C, чтобы правильно их использовать. По этой причине, в средней реальной жизни, передача массива в качестве указателя на его первый элемент является более популярным подходом. Это просто выглядит «проще».

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

void foo(char p[], unsigned plen);

Во многих случаях очень полезно иметь возможность обрабатывать массивы времени выполнения, что также способствует популярности метода. Многие разработчики C просто никогда не сталкиваются (или не признают) необходимость обрабатывать массив фиксированного размера, тем самым оставаясь не обратившись к правильной технике фиксированного размера.

Тем не менее, если размер массива фиксирован, передача его как указатель на элемент

void foo(char p[])

, является важной погрешностью на уровне техники, которая, к сожалению, довольно распространена в наши дни. Метод-указатель к массиву - гораздо лучший подход в таких случаях.

Еще одна причина, которая может помешать принятию метода передачи массива фиксированного размера, - это доминирование наивного подхода к набору динамически распределенных массивов , Например, если программа вызывает фиксированные массивы типа char[10] (как в вашем примере), средний разработчик будет malloc такими массивами, как

char *p = malloc(10 * sizeof *p);

Этот массив не может быть передан функции объявленный как

void foo(char (*p)[10]);

, который смущает среднего разработчика и заставляет их отказаться от объявления параметра фиксированного размера, не давая ему дальнейших размышлений. В действительности же корень проблемы кроется в наивном подходе malloc. Формат malloc, показанный выше, должен быть зарезервирован для массивов времени выполнения. Если тип массива имеет размер времени компиляции, лучший способ malloc выглядел бы следующим образом

char (*p)[10] = malloc(sizeof *p);

Это, конечно же, можно легко передать выше объявленному foo

foo(p);

, и компилятор выполнит правильную проверку типа. Но опять же, это чересчур запутывает неподготовленного разработчика C, поэтому вы не увидите его слишком часто в «типичном» среднем ежедневном коде.

144
ответ дан AnT 25 August 2018 в 14:31
поделиться

Ну, просто положите, C не делает так. Массив типа T передается как указатель на первый T в массиве, и это все, что вы получаете.

Это позволяет использовать некоторые классные и элегантные алгоритмы, например, массив с выражениями типа

*dst++ = *src++

Недостатком является то, что управление размером зависит от вас. К сожалению, неспособность сделать это добросовестно также привела к миллионам ошибок в кодировании C и / или возможностях для злонамеренной эксплуатации.

Что близко к тому, что вы просите в C, - это пройти через struct (по значению) или указатель на один (по ссылке). Пока один и тот же тип структуры используется с обеих сторон этой операции, оба кода, которые передают ссылку и код, который ее использует, согласуются с размером обрабатываемых данных.

Ваш struct может содержать любые данные, которые вы хотите; он может содержать ваш массив четко определенного размера.

Тем не менее, ничто не мешает вам или некомпетентному или злонамеренному кодеру использовать приведения, чтобы обмануть компилятор, рассматривая вашу структуру как один из разного размера. Почти нераскрытая способность делать такие вещи является частью дизайна C.

1
ответ дан Carl Smotricz 25 August 2018 в 14:31
поделиться

Очевидная причина заключается в том, что этот код не компилируется:

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

По умолчанию для массива используется неквалифицированный указатель.

Также см. this вопрос , используя foo(&p), должен работать.

4
ответ дан Community 25 August 2018 в 14:31
поделиться

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

Не могли бы вы просто использовать void foo(char p[10], int plen); ?

0
ответ дан fortran 25 August 2018 в 14:31
поделиться

В моем компиляторе (vs2008) он рассматривает char (*p)[10] как массив указателей на символы, как если бы не было круглых скобок, даже если я скомпилирован как файл C. Поддерживает ли компилятор эту «переменную»? Если это так, это основная причина не использовать его.

-1
ответ дан mort 25 August 2018 в 14:31
поделиться

Я также хочу использовать этот синтаксис для большей проверки типов.

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

Вот еще некоторые препятствия, с которыми я столкнулся.

  • Доступ к массиву требует использования (*p)[]:
    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }
    
    Вместо этого возникает соблазн использовать локальный указатель на символ:
    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }
    
    Но это частично превзойдет цель использования правильного типа.
  • Нельзя забывать использовать адрес-оператор при назначении адреса массива указателю на массив:
    char a[10];
    char (*p)[10] = &a;
    
    Адрес- оператора получает адрес всего массива в &a, с правильным типом, чтобы назначить его p. Без оператора a автоматически преобразуется в адрес первого элемента массива, как и в &a[0], который имеет другой тип. Поскольку это автоматическое преобразование уже происходит, я всегда озадачен тем, что & необходим. Это согласуется с использованием & для переменных других типов, но я должен помнить, что массив является особым и мне нужен &, чтобы получить правильный тип адреса, хотя значение адреса одинаково , Одной из причин моей проблемы может быть то, что я узнал K & amp; R C еще в 80-х годах, что еще не позволило использовать оператор & на всех массивах (хотя некоторые компиляторы игнорировали это или допускали синтаксис). Это, кстати, может быть еще одной причиной, по которой переходы с привязками трудно принять: они работают только с ANSI C, а ограничение оператора & может быть еще одной причиной считать их слишком неудобными.
  • Когда typedef является not , используемым для создания типа для указателя на массив (в общем заголовочном файле), тогда глобальный указатель на массив должен более сложная декларация extern для обмена ею между файлами:
    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];
    
1
ответ дан Orafu 25 August 2018 в 14:31
поделиться

Я бы не рекомендовал это решение

typedef int Vector3d[3];

, поскольку он скрывает тот факт, что Vector3D имеет тип, о котором вы должны знать. Программисты обычно не ожидают, что переменные того же типа будут иметь разные размеры. Рассмотрим:

void foo(Vector3d a) {
   Vector3D b;
}

, где sizeof a! = Sizeof b

1
ответ дан Per Knytt 25 August 2018 в 14:31
поделиться

Вы можете объявить массив символов несколькими способами:

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

Прототипом функции, которая принимает массив по значению, является:

void foo(char* p); //cannot modify p

или reference:

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

или синтаксисом массива:

void foo(char p[]); //same as char*
1
ответ дан s1n 25 August 2018 в 14:31
поделиться
Другие вопросы по тегам:

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