译文 · 原文: Friday Q&A 2011-02-18: Compound Literals · 作者 Mike Ash
原文:https://www.mikeash.com/pyblog/friday-qa-2011-02-18-compound-literals.html 发布:2011-02-18 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样
我们终于回归了常规更新节奏。在今天的主题中,我打算暂时跳出以往读者提问驱动的形式,来探讨一个自选话题:C99 中的复合字面量(compound literals)。
复合字面量是 C 语言中相对鲜为人知的一项特性。它相当新,作为 C99 标准的一部分于 2000 年引入。对于一门可追溯至 1960 年代的语言而言,这算是个新近的补充,尽管至今已有相当一段时间。
C99 为这门语言增添了许多实用特性,而现代 Mac 和 iOS 开发者往往视其为理所当然。其中不少特性此前已作为编译器扩展存在。像 // 注释、long long 类型、以及变量声明与代码混合书写这样的简单功能,都是 C99 新引入的。复合字面量远不如这些特性广为人知,但它同样是标准的一部分,且相当实用,这便是我今天想要讨论它的原因。
复合字面量基础
复合字面量提供了在代码中编写任意数据类型值的方法。像 “hello” 这样的表达式是一个类型为 char * 的字符串字面量(string literal)。复合字面量只是一种不同类型的表达式,可以产生你需要的任意类型。例如,可以创建一个表达式来产生与字符串字面量相同的 C 字符串,但采用显式的逐字符构造方式:
(char []){ 'h', 'e', 'l', 'l', 'o', '\0' } (NSSize){ 1, 2 }复合字面量(compound literal)的语法与变量初始化语法非常相似。例如:
NSSize s = { 1, 2 }; (NSSize){ 1, 2 }; // same value
int x[] = { 3, 4, 5 }; (int []){ 3, 4, 5 }; // same Type name = { val }; (Type){ val };基本技巧
创建自定义结构体值的能力可能是复合字面量(compound literals)最实用且显而易见的应用。虽然 Cocoa 通过 NSMakeRect 等函数处理了其最常见的类型,但仍有一些场景适合运用复合字面量。
例如,CGRect 本质上只是 CGPoint 和 CGSize 的组合。CGRectMake 需要四个独立的数值参数,但有时直接操作这两个结构体会更便捷。复合字面量允许你以内联方式实现这一点:
[layer setFrame: (CGRect){ origin, size }]; [NSString stringWithCharacters: (unichar []){ 0x00a9 } length: 1] int *ptr; ptr = (int []){ 42 }; NSLog(@"%d", *ptr); int *ptr; do { ptr = (int []){ 42 }; } while(0); NSLog(@"%d", *ptr);可变性 关于复合字面量(compound literals)的一个真正违反直觉的特性是,除非将其类型声明为 const,否则它们会产生可变的值。以下代码完全合法,尽管实际上毫无意义:
(int){ 0 } = 42;例如,套接字(socket)上一个常见操作是设置 SO_REUSEADDR 选项(地址复用选项)。该选项告知操作系统:当套接字关闭后,立即释放其占用的端口以供重新使用,而不是默认行为(通常需要等待几分钟)。此选项通过 setsockopt 函数(设置套接字选项函数)进行配置。该函数可用于设置多种参数,且这些参数需要不同的类型,因此它仅接收一个 void * 参数(无类型指针)及一个长度值。以下是常规情况下使用它来设置此选项的方式:
int yes = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));另一个实用场景是编写接受 NSError ** 参数的方法,用于向调用方传递错误信息。按照约定,传入 NULL 指针表示调用方不关心错误是合法的。这意味着在可能发生错误的每个位置都必须检查该指针。这会有些乏味:
- (BOOL)doWithError: (NSError **)error { if(fail1) { if(error) *error = [NSError ...]; return NO; } if(fail2) { if(error) *error = [NSError ...]; return NO; } if(fail3) { if(error) *error = [NSError ...]; return NO; }
return YES; } - (BOOL)doWithError: (NSError **)error { error = error ? error : &(NSError *){ nil };
if(fail1) { *error = [NSError ...]; return NO; } if(fail2) { *error = [NSError ...]; return NO; } if(fail3) { *error = [NSError ...]; return NO; }
return YES; } - (BOOL)doWithError: (NSError **)error { error = error ? error : &(NSError *){ nil };
BOOL success = [obj doWithError: error]; if(!success) { // don't bail out if we can work around it if(![[*error domain] isEqual: CanWorkAroundDomain]) return NO; }
if(fail1) { *error = [NSError ...]; return NO; }
return YES; } #define ARRAY(...) [NSArray \ arrayWithObjects: (id []){ __VA_ARGS__ } \ count: sizeof((id []){ __VA_ARGS__ }) / sizeof(id)]结论
复合字面量(Compound literals)是一种能够简化并清晰化代码的实用技巧。它们并非普遍适用,必须注意避免在弊大于利的场景中使用。然而,这仍是一个值得收入你技巧库的实用工具,它有助于让 C 语言变得更加实用和通用。
本次的内容就到这里。希望我现在能恢复定期更新,两周后将会有新的文章。在那之前,请继续发送你的想法。Friday Q & A 通常由读者建议驱动,因此如果有你想在这里看到的主题,请发送给我。
Original (English)
Source: https://www.mikeash.com/pyblog/friday-qa-2011-02-18-compound-literals.html
We’re back to our regular schedule at last. For today’s edition, I’m taking a break from the usual reader-driven format to discuss a topic of my own choosing: compound literals in C99.
Compound literals are a relatively unknown feature in C. They are fairly new. Introduced as part of the C99 standard in 2000, they’ve been around for a while, but for a language that dates to the 1960s, it’s a recent addition.
C99 added a lot of useful features to the language that modern Mac and iOS programmers tend to take for granted. Many of these existed as compiler extensions beforehand. Simple features like // comments, the long long type, and the ability to mix variable declarations and code are all new in C99. Compound literals are much less well known than these features, but are equally standard and can be handy, and this is why I want to talk about them today.
Compound Literal Basics Compound literals provide a way to write values of arbitrary data types in code. An expression like “hello” is a string literal that has type char *. A compound literal is simply a different kind of expression that has whatever type you’re after. For example, it’s possible to create an expression which produces the same C string as the string literal, but with explicit character-by-character construction:
(char []){ 'h', 'e', 'l', 'l', 'o', '\0' } (NSSize){ 1, 2 }Compound literal syntax closely matches variable initialization syntax. For example:
NSSize s = { 1, 2 }; (NSSize){ 1, 2 }; // same value
int x[] = { 3, 4, 5 }; (int []){ 3, 4, 5 }; // same Type name = { val }; (Type){ val };Basic Tricks The ability to create custom struct values is probably the most useful obvious application of compound literals. Although Cocoa takes care of its most common types with NSMakeRect and friends, there are still places to put compound literals to good use.
For example, a CGRect is really just an CGPoint and an CGSize. CGRectMake takes four discrete numbers, but sometimes it’s more convenient to just deal with those two elements. Compound literals let you do that inline:
[layer setFrame: (CGRect){ origin, size }]; [NSString stringWithCharacters: (unichar []){ 0x00a9 } length: 1] int *ptr; ptr = (int []){ 42 }; NSLog(@"%d", *ptr); int *ptr; do { ptr = (int []){ 42 }; } while(0); NSLog(@"%d", *ptr);Mutability One really unintuitive thing about compound literals is that, unless you declare their type as const, they produce mutable values. The following is perfectly legal, albeit completely pointless, code:
(int){ 0 } = 42;For example, a common operation on sockets is to set the SO_REUSEADDR option. This tells the OS to free up the socket’s port for use as soon as the socket is closed, instead of the default behavior of waiting a few minutes first. This option is set using setsockopt. It can be used to set various parameters which need different argument types, so it simply takes a void * and a length. This is how it’s normally used to set this option:
int yes = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));Another place where this comes in handy is writing methods which take an NSError ** parameter to pass error information to the caller. By convention, it’s legal to pass NULL as the pointer to indicate that the caller doesn’t care about the error. This means that at every place where an error can occur, the pointer must be checked. This gets a bit tedious:
- (BOOL)doWithError: (NSError **)error { if(fail1) { if(error) *error = [NSError ...]; return NO; } if(fail2) { if(error) *error = [NSError ...]; return NO; } if(fail3) { if(error) *error = [NSError ...]; return NO; }
return YES; } - (BOOL)doWithError: (NSError **)error { error = error ? error : &(NSError *){ nil };
if(fail1) { *error = [NSError ...]; return NO; } if(fail2) { *error = [NSError ...]; return NO; } if(fail3) { *error = [NSError ...]; return NO; }
return YES; } - (BOOL)doWithError: (NSError **)error { error = error ? error : &(NSError *){ nil };
BOOL success = [obj doWithError: error]; if(!success) { // don't bail out if we can work around it if(![[*error domain] isEqual: CanWorkAroundDomain]) return NO; }
if(fail1) { *error = [NSError ...]; return NO; }
return YES; } #define ARRAY(...) [NSArray \ arrayWithObjects: (id []){ __VA_ARGS__ } \ count: sizeof((id []){ __VA_ARGS__ }) / sizeof(id)]Conclusion Compound literals are a nice trick to simplify and clarify code. They are not universally applicable, and you must take care not to use them in situations where they hurt more than they help. However, they are a nice tool to have in your bag of tricks, and they help make C a little more useful and generic.
That’s it for this time. I hope to be back to my regular schedule now, so look for another post in two weeks. Until then keep sending your ideas. Friday Q&A is (usually!) driven by reader suggestions, so if you have a topic that you would like to see covered here, send it to me.