Подтвердить что ты не робот

Битмаскирование в Objective C

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

Если это предположение верно, я решил, что могу сделать что-то вроде этого:

typedef NSUInteger Traits;

enum
{
    TraitsCharacterHonest       = 0,
    TraitsCharacterOptimistic   = 1,
    TraitsCharacterPolite       = 4,
    TraitsCharacterDevious      = 8,
    TraitsPhysicalTall          = 16,
    TraitsPhysicalBeautiful     = 32,
    TraitsPhysicalFat           = 64,
    TraitsPhysicalBigEyes       = 128,
    TraitsPhysicalRedHair       = 256, 
};

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (strong, nonatomic) NSString  *name;
@property (assign, nonatomic) Traits    *traits;

@end

Вопрос 1: как назначить больше признаков одному человеку?

Вопрос 2 заключается в том, нужно ли постоянно добавлять числа для перечисления элементов или есть способ указать это?

В конечном итоге я хочу добиться чего-то вроде этого:

Person *john = [[Person alloc] init];

//here code that assigns john three traits: TraitsCharacterHonest,      
//TraitsCharacterOptimistic and TraitsPhysicalBeautiful.

Если я правильно понимаю, значение

john.traits должен быть 100011., читающий справа и каждое место, представляющее это конкретное значение/черту enum..and 0, означающее отсутствие его, и 1 означает его наличие.

Можете ли вы посоветовать синтаксис и объяснить конкретный аспект, если это необходимо?

4b9b3361

Ответ 1

Я бы рекомендовал изменить несколько вещей:

  • Значения перечисления могут быть изменены как сдвинутые слева. Делает это немного легче писать, на мой взгляд.

  • Вам не нужно вводить typedef в NSUInteger, вы можете объявить тип перечисления напрямую с помощью typedef enum.

  • И, как говорили другие люди, ваша собственность не должна быть указателем на тип объектов.

Мой код будет выглядеть так:

typedef enum
{
    TraitsCharacterHonest       = 1 << 0,
    TraitsCharacterOptimistic   = 1 << 1,
    TraitsCharacterPolite       = 1 << 2,
    TraitsCharacterDevious      = 1 << 3,
    TraitsPhysicalTall          = 1 << 4,
    TraitsPhysicalBeautiful     = 1 << 5,
    TraitsPhysicalFat           = 1 << 6,
    TraitsPhysicalBigEyes       = 1 << 7,
    TraitsPhysicalRedHair       = 1 << 8
} Traits;

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (strong, nonatomic) NSString  *name;
@property (assign, nonatomic) Traits     traits;

@end

Настройка свойств Джона будет выглядеть так:

Person *john = [[Person alloc] init];

john.traits = TraitsCharacterHonest | TraitsCharacterOptimistic | TraitsPhysicalBeautiful;

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

NSMutableString *result = [NSMutableString string];

if (self.traits & TraitsCharacterHonest)
{
    [result appendString: @"Honest, "];
}
if (self.traits & TraitsCharacterOptimistic)
{
    [result appendString: @"Optimistic, "];
}
if (self.traits & TraitsCharacterPolite)
{
    [result appendString: @"Polite, "];
}
// etc...

Кроме того, синтаксис операций, таких как удаление признака, запутан. Вам нужно будет использовать & и константу NOT-ed,

// remove 'Tall' trait
john.traits = john.traits & ~TraitsPhysicalTall

Если вы можете (и производительность не слишком большая проблема), я бы предпочел использовать функцию более высокого уровня. Возможно, NSSet со строковыми константами? например.

__unused static NSString *TraitsCharacterHonest = @"TraitsCharacterHonest";
__unused static NSString *TraitsCharacterOptimistic = @"TraitsCharacterOptimistic";
__unused static NSString *TraitsCharacterPolite = @"TraitsCharacterPolite";
// etc...

@interface Person : NSObject

@property (strong, nonatomic) NSString     *name;
@property (assign, nonatomic) NSMutableSet *traits;

@end

Затем вы можете сделать:

// adding
[john.traits addObject: TraitsCharacterHonest];
// checking
[john.traits containsObject: TraitsCharacterHonest];
// removing 
[john.traits removeObject: TraitsCharacterHonest];

Имеет для меня больше смысла. Что еще, вы можете распечатать описание признаков напрямую с помощью

NSLog(@"John traits: %@", john.traits);

и вы получите разумный результат.

Ответ 2

Одной из проблем, с которой вы можете столкнуться, является то, что использование бит-масок для указания членства внутри наборов может быть ограничено количеством бит в базовом типе данных. Например, unsigned long 32 бит имеет место только для 32 непересекающихся или разных членов. Если вам нужно добавить 33-й, вам не повезло, если вы не перейдете к 64-разрядному целу без знака.

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

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

Чтобы использовать массив байтов для хранения набора битов, вы можете подумать об использовании unsigned long для представления членов набора, в котором младшим значащим байтом является бит-маска, а остальные байты используются как беззнаковое 3 байта смещения в массив байтов. Затем вы сделали бы что-то вроде следующего:

int getBitSet (unsigned char *bArray, unsigned long ulItem)
{
    unsigned long ulByteOffset = ((ulItem >> 8) & 0x00ffffff);
    unsigned char ucByteMask = (ulItem & 0x000000ff);

    return (*(bArray + ulByteOffset) & ucByteMask);
}

int setBitSet (unsigned char *bArray, unsigned long ulItem, unsigned long ulNewValue)
{
    unsigned char oldValue;
    unsigned long ulByteOffset = ((ulItem >> 8) & 0x00ffffff);
    unsigned char ucByteMask = (ulItem & 0x000000ff);

    oldValue = *(bArray + ulByteOffset) & ucByteMask;

    if (ulNewValue) {
        *(bArray + ulByteOffset) |= ucByteMask;  // set bit
    } else {
        *(bArray + ulByteOffset) &= ~ucByteMask;  // clear bit
    }

    return oldValue;
}

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

Ответ 3

В iOS 6 или выше Mac OS X 10.8 и выше

Вы можете сделать:

typedef NS_OPTIONS(NSUInteger, Traits) {
    TraitsCharacterHonest,
    TraitsCharacterOptimistic,
    TraitsCharacterPolite,
    TraitsCharacterDevious,
    TraitsPhysicalTall,
    TraitsPhysicalBeautiful,
    TraitsPhysicalFat,
    TraitsPhysicalBigEyes,
    TraitsPhysicalRedHair
};

Для получения дополнительной информации см. http://nshipster.com/ns_enum-ns_options/

Ответ 4

Ваша основная проблема заключается в том, чтобы сделать traits указатель. Отбросьте указатель и сделайте так, как в C:

john.traits |= TraitsCharacterOptimistic | TraitsCharacterOptimistic | TraitsCharacterOptimistic;

Помните, что вам нужны только указатели в нескольких ситуациях в Objective-C:

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

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

Ответ 5

Прежде всего измените:

...
typedef NSUInteger Traits;

enum
{
    TraitsCharacterHonest = 0, //cann't be a 0
    ...
};
...
@property (assign, nonatomic) Traits *traits; //you no need create a pointer to a primitive type

чтобы: ...

typedef NSUInteger Traits;

enum
{
    TraitsCharacterHonest = 1, 
    ...
};
...
@property (assign, nonatomic) Traits traits;

Для назначения вам следует выполнить следующие действия:

john.traits |= TraitsCharacterHonest | TraitsCharacterDevious;

Побитовые операции в ObjC такие же, как в языке C. Проверьте этот учебник Побитовые операторы в C и С++: Учебник

Ответ 6

Предполагая, что:

1 << 8 то же самое, что 100000000:

john.traits = TraitsCharacterHonest | TraitsCharacterOptimistic | TraitsPhysicalBeautiful;

точно такая же, как:

john.traits = 000000001 | 000000010 | 000100000;

и результат:

john.traits = 000100011

Теперь, когда вы хотите проверить условное:

if (self.traits & TraitsCharacterHonest) { ... }

он эквивалентен:

if (000100011 & 000000001) { ... } 

и результат:

if (000000001) { ... }

И это на самом деле 1, not zero значение true, поэтому все условное значение true. Наслаждайтесь: -)