Аргументы переменной в C, как получить значения с универсальным типом?

Я использую CAShapeLayer для маскировки UIView, устанавливая self.layer.mask для этого слоя формы.

Чтобы анимировать маску всякий раз, когда изменяется размер представления, я переписал -setBounds:, чтобы анимировать путь слоя маски, если границы изменяются во время анимации.

Вот как я это реализовал:

- (void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];
    CAPropertyAnimation *boundsAnimation = (CABasicAnimation *)[self.layer animationForKey:@"bounds"];

    // update the mask
    self.maskLayer.frame = self.layer.bounds;

    // if the bounds change happens within an animation, also animate the mask path
    if (!boundsAnimation) {
        self.maskLayer.path = [self createMaskPath];
    } else {
        // copying the original animation allows us to keep all animation settings
        CABasicAnimation *animation = [boundsAnimation copy];
        animation.keyPath = @"path";

        CGPathRef newPath = [self createMaskPath];
        animation.fromValue = (id)self.maskLayer.path;
        animation.toValue = (__bridge id)newPath;

        self.maskLayer.path = newPath;

        [self.maskLayer addAnimation:animation forKey:@"path"];
    }
}

(Для примера self.maskLayer установлено значение `self.layer.mask)

My -createMaskPath вычисляет CGPathRef, который я используйте, чтобы замаскировать вид. Я также обновляю путь маски в -layoutSubviews.

12
задан Tim Post 7 November 2009 в 12:53
поделиться

5 ответов

Вы не можете сделать это так, как вы это описываете.

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

  • Нет проблем для функций с прототипами, каждый тип известен.

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

Следовательно, вы должны передать тип.

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

Но в любом случае вы должны передать его.

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

Давайте рассмотрим пример, в котором вы передаете тип за которым следует аргумент (таким образом, ни трюк с объединением, ни строка формата, такая как printf). Он преобразует все переданные значения в двойные и добавляет их, бесполезно, не так ли:

#include <stdio.h>
#include <stdarg.h>

enum mytypes {LONG, INT, FLOAT, DOUBLE };

double myfunc(int count, ...){
    long tmp_l;
    int tmp_i;
    double tmp_d;
    double res = 0;
    int i;

    va_list ap;
    va_start(ap, count);
    for(i=0 ; i < count; i++){
        int type = va_arg(ap, enum mytypes);
        switch (type){
            case LONG:
            tmp_l = va_arg(ap, long);
            res += tmp_l;
            break;
            case INT:
            tmp_i = va_arg(ap, int);
            res += tmp_i;
            break;
            case FLOAT:
            /* float is automatically promoted to double when passed to va_arg */
            case DOUBLE:
            tmp_d = va_arg(ap, double);
            res += tmp_d;
            break;
            default: /* unknown type */
            break;
        }
    }
    va_end(ap);
    return res;
}

int main(){
    double res;
    res = myfunc(5,
        LONG, (long)1,
        INT, (int)10,
        DOUBLE, (double)2.5,
        DOUBLE, (double)0.1,
        FLOAT, (float)0.3);
    printf("res = %f\n", res);
}

В этом примере используется переменный заголовок new stdarg, определенный в C99. Чтобы использовать его, вам необходимо иметь хотя бы один фиксированный параметр для вашей функции (в этом примере это count ). Хорошо, что таким образом у вас может быть несколько списков с переменными числами в вашей функции (например, что-то вроде myfunc (int count1, ..., int count2, ...) ). Плохо то, что у вас не может быть чисто вариативной функции (то есть чего-то вроде myfunc (...), например, в старом формате. Вы все еще можете использовать старый формат, используя заголовки совместимости varargs. Но он является более сложным и редко необходимым, потому что вам нужны типы, но также и какой-то способ узнать, что список закончен, и что-то вроде count удобно (но не единственный способ сделать это, например, можно использовать терминатор).

Хорошо, что таким образом у вас может быть несколько списков с переменными числами в вашей функции (например, что-то вроде myfunc (int count1, ..., int count2, ...) ). Плохо то, что у вас не может быть чисто вариативной функции (то есть чего-то вроде myfunc (...), например, в старом формате. Вы все еще можете использовать старый формат, используя заголовки совместимости varargs. Но он является более сложным и редко необходимым, потому что вам нужны типы, а также какой-то способ узнать, что список закончен, и что-то вроде count удобно (но не единственный способ сделать это, например, можно использовать терминатор).

Хорошо, что таким образом у вас может быть несколько списков с переменными числами в вашей функции (например, что-то вроде myfunc (int count1, ..., int count2, ...) ). Плохо то, что у вас не может быть чисто вариативной функции (то есть чего-то вроде myfunc (...), например, в старом формате. Вы все еще можете использовать старый формат, используя заголовки совместимости varargs. Но он является более сложным и редко необходимым, потому что вам нужны типы, а также какой-то способ узнать, что список закончен, и что-то вроде count удобно (но не единственный способ сделать это, например, можно использовать терминатор).

) как и в старом формате. Вы все еще можете использовать старый формат, используя заголовки совместимости varargs. Но это более сложно и редко необходимо, потому что вам нужны типы, а также какой-то способ узнать, что список закончен, и что-то вроде count удобно (но это не единственный способ сделать это, например, можно использовать терминатор).

) как и в старом формате. Вы все еще можете использовать старый формат, используя заголовки совместимости varargs. Но это более сложно и редко необходимо, потому что вам нужны типы, а также какой-то способ узнать, что список закончен, и что-то вроде count удобно (но не единственный способ сделать это, например, можно использовать терминатор).

11
ответ дан 2 December 2019 в 05:15
поделиться

Используйте пустоту * (или типизированную структуру) для каждого параметра и используйте структуру с аргументом «тип» (целое число). Указатель / объединение, содержащее фактическое значение.

Другими словами, каждый параметр передается с указателем на типизированную структуру. Каждый экземпляр этой типизированной структуры содержит значение. Тип этого «значения» содержится в этой типизированной структуре.

Лучший пример:

typedef struct  {
  int type;
  union {
    int int_value;
    double double_value;
    ...
  };
} Param;

void function (Param * p1, Param * p2, ...)

Последний пример такого трюка, с которым я столкнулся был DBus.

14
ответ дан 2 December 2019 в 05:15
поделиться

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

Чтобы varargs работал с типами переменных, вы можете хранить информацию о типе в самих аргументах (например, как описано jldupont в его ответе), или вы можете хранить информацию в неизменяемом аргументе (например, в строке формата как printf ).

3
ответ дан 2 December 2019 в 05:15
поделиться

Копирование из http://en.wikipedia.org/wiki/Stdarg.h :

Не определен механизм для определения [...] типов переданных безымянных аргументов к функции. Функция просто обязана знать или определять это каким-то образом, средства этого различаются.

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

1
ответ дан 2 December 2019 в 05:15
поделиться

Попробуйте u? Intptr_t, который определен в stdint.h. Это целое число, которое гарантированно будет достаточно большим, чтобы содержать указатель.

Вы также можете подумать о том, что происходит, когда вы передаете число с плавающей запятой; он преобразуется и передается как double. Если ваша программа ожидает int, это уничтожит ее представление стека. Ой. И компилятор не может это уловить.

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

Что именно вы пытаетесь сделать?

0
ответ дан 2 December 2019 в 05:15
поделиться
Другие вопросы по тегам:

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