25

我想学习位掩码。据我了解,这意味着将某种类型的二进制值存储到一个变量中。

如果上面的假设是真的,我想我可以做这样的事情:

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.,从右边读取,每个地方代表那个特定的枚举值/特征..并且 0 表示没有它,1 表示有它。

如果需要,您能否就语法提出建议并解释特定方面?

4

6 回答 6

60

我建议更改一些内容:

  • 枚举值可以更改为左移一个。在我看来,让写作更容易一些。

  • 你不需要 typedef 到 NSUInteger,你可以直接使用typedef enum.

  • 而且,正如其他人所提到的,您的属性不应该是指向 Traits 类型的指针。

我的代码如下所示:

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

设置 John 的特征将如下所示:

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...

此外,删除特征等操作的语法令人困惑。你必须使用&和一个未编辑的常量,

// 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's traits: %@", john.traits);

你会得到合理的输出。

于 2012-09-09T15:43:56.500 回答
4

您可能遇到的一个问题是,使用位掩码来指示集合内的成员资格可能会受到基础数据类型中的位数的限制。例如,一个 32 位的无符号长整数只能容纳 32 个不相交或不同的成员。如果您需要添加第 33 位,那么除非您使用 64 位无符号整数,否则您将不走运。

一种解决方法是使用字节数组。使用这种方法,您必须将您的位成员资格指定为两条数据,即字节的偏移量和用于特定位的位掩码。

我还看到人们使用字节数组作为单个成员,这样就使用了整个字节,而不是使用一个位。这可能会浪费内存,但它可能更灵活、更有用,而且浪费的内存量不是问题。

为了使用字节数组来保存位集,您可以考虑使用无符号长整数来表示集合的成员,其中最低有效字节是位掩码,其余字节用作无符号 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;
}

然后,您可以使用一组函数来获取和设置字节,或者您可以使用宏。使用 C++,您可以为此功能创建自己的类并提供各种类型的逻辑操作,以便您可以创建各种类型的集合,然后对集合执行逻辑操作。

于 2012-09-09T14:59:25.683 回答
2

你在这里的主要问题是做traits一个指针。放下指针,像在 C 中那样做:

john.traits |= TraitsCharacterOptimistic | TraitsCharacterOptimistic | TraitsCharacterOptimistic;

请记住,您只需要在 Objective-C 中的几种情况下使用指针:

  • 当您处理实际对象时(派生自NSObject
  • 当您需要通过引用传递原语(int *函数的参数以返回计数)时,在这种情况下,您获取局部变量的地址,并且该指针不会被函数保存。
  • 当您需要一个原始类型数组时,在堆上动态分配(例如使用malloc&friends)。

否则,只需使用堆栈分配的原始类型,因为您可以用它做很多事情。

于 2012-09-09T14:24:17.053 回答
2

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/

于 2014-01-21T14:52:42.580 回答
1

首先改变:

...
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 和 C++ 中的位运算符:教程

于 2012-09-09T14:31:48.190 回答
1

假设:

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) { ... }

而且,这实际上是1not zero值是true,所以整个条件是true。享受:-)

于 2015-03-13T18:59:05.317 回答