我是C的新手,并且在使用按位操作时玩得很开心。我在做什么似乎有效,我只是想知道它是否被认为是好风格。
好的,我们假设我的程序有3个命令行选项,-a
,-b
和-c
。以前我会有3个整体充当布尔值,比如aFlag
,bFlag
和cFlag
。然后,当我调用processOpts( int *aFlag, int *bFlag, int *cFlag)
函数时,我会将&aFlag
,&bFlag
和&cFlag
作为参数传递,并将它们设置为*aFlag = 1
。
这是我的新方法:让1 int *options
代表所有选项,并将其视为一组布尔值。所以要在函数中设置它们:
case 'a':
*options |= 1;
break;
case 'b':
*options |= 2;
break;
case 'c':
*options |= 4;
break;
然后,回到main
(或任何地方),当我想测试以查看是否选择了一个选项时:
if ( *options & 1 )
// Option 'a' selected
if ( *options & 2 )
// Option 'b' selected
if ( *options & 4 )
// Option 'c' selected
我的问题是:哪种方法被认为是更好的风格?第一种方式可能更清晰,更不容易出错,而第二种方式可能更容易重构(无需更改函数原型,因为它只是一个int
)。
或者,有没有更好的方法呢? :D
编辑:根据Mat的建议添加休息时间。
感谢所有回复,我对这个社区印象深刻,并且愿意帮助每个人学习 - 你们摇滚!
答案 0 :(得分:3)
使用单个变量来表示一组布尔标志,如果它们是相关的,则效果很好。一般来说,如果标志不相关,我会避免这样做。但是在你的情况下,3个标志与程序及其运行方式有关,我会说这是一个很好用的。
就实现而言,不应使用标记的硬编码常量值,而应定义宏或枚举来表示标志。很明显,您正在设置(或取消设置)哪个标志。虽然你可能没有必要在代码中使用指针。
如,
enum option
{
OPTION_none = 0,
OPTION_a = 0x1,
OPTION_b = 0x2,
OPTION_c = 0x4,
};
enum option processOpts(int argc, char **argv)
{
enum option options = OPTION_none;
if (argc > 1)
{
int i;
for (i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
switch (argv[i][1])
{
case 'a': options |= OPTION_a; break;
case 'b': options |= OPTION_b; break;
case 'c': options |= OPTION_c; break;
}
}
}
}
return options;
}
int main(int argc, char *argv[])
{
enum option options = processOpts(argc, argv);
if (options & OPTION_a)
{
/* do stuff for option a */
}
if (options & OPTION_b)
{
/* do stuff for option b */
}
if (options & OPTION_c)
{
/* do stuff for option c */
}
return 0;
}
答案 1 :(得分:1)
更好的方法是使用 unsigned 整数类型来表示标志集合。否则,你的新方法在许多方面远远优于一堆单独的标志。
当然,对应于每个选项的位掩码应该由命名常量表示,而不是代码中间的“幻数”(1,2,4 ...)。
答案 2 :(得分:1)
这很好 - 你只是将位打包成int - 我要做的唯一建议是1,2,4等应该是符号常量,而不是上面的文字硬编码值,例如
enum {
OPTION_A = 1 << 0,
OPTION_B = 1 << 1,
OPTION_C = 1 << 2,
};
答案 3 :(得分:1)
使用位标志是更好的风格 - 因为如果您需要更多命令行选项,它可以轻松扩展。
但是,我强烈建议你不要使用普通的魔术数字 - 而是做这样的事情:
#define OPTION_A 1
#define OPTION_B 2 ...
if (*options & OPTION_A) {
}
这使得检查选项或多或少地自我解释,而带有原始数字的按位操作总是闻起来有点像隐藏的魔法。
答案 4 :(得分:1)
您的新方法(使用无符号变量)通常(并且是惯用的)。
通常使用枚举(或#define)定义1
,2
和4
等,并为其命名
enum {
OPTION_LEFT_TO_RIGHT = 1,
OPTION_TOP_TO_BOTTOM = 2,
OPTION_BOTTOM_TO_TOP = 4,
OPTION_RIGHT_TO_LEFT = 8,
};
case 'a': *options |= OPTION_BOTTOM_TO_TOP; break;
if (*options & OPTION_RIGHT_TO_LEFT) { /* ... */ }
答案 5 :(得分:1)
正如我在评论中所建议的那样,我的偏好是使用一些字段来做到这一点。对我来说,它有一个更直观的用法,同时允许编译器为我完成大部分工作(至少我的编译器检查汇编输出确认它在检查/设置标志时正在执行预期和/或操作)。因此,以Jeff M的解决方案为出发点,我将其改为:
// Assume TRUE defined as 1
struct option
{
unsigned int OptionA : 1; // defines 1 bit for option A flag
unsigned int OptionB : 1; // next bit is for option B
unsigned int OptionC : 1; // note, you can define more than one bit per flag
// if more complex options are required.
};
struct option processOpts(int argc, char **argv)
{
struct option options = {0,0,0}; // explicit setting of all flags to false
// compiler optimizes to options=0
if (argc > 1)
{
int i;
for (i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
switch (argv[i][1])
{
case 'a': options.OptionA = TRUE; break;
case 'b': options.OptionB = TRUE; break;
case 'c': options.OptionC = TRUE; break;
}
}
}
}
return options;
}
int main(int argc, char *argv[])
{
struct option options = processOpts(argc, argv);
if (options.OptionA)
{
/* do stuff for option a */
}
if (options.OptionB)
{
/* do stuff for option b */
}
if (options.OptionC)
{
/* do stuff for option c */
}
return 0;
}
答案 6 :(得分:0)
你扭动自己的方式是一种出色的方式。它甚至可能是“最好的”方式。恭喜!做得好。
- 皮特
答案 7 :(得分:0)