变量声明的阅读: int* (*(*const& value)[3][4])(double, double)?

变量声明的阅读: int* (*(*const& value)[3][4])(double, double)?#

常见的声明类型#

复杂声明的阅读#

我个人是 非常不建议学习这个的, 能写出复杂的声明, 那说明你的代码本身已经过于复杂了. 实际遇到了建议通过 明确复杂声明的语义 解决.

规则#

警告

这是我自创的规则, 可能与实际存在微妙的差异.

括号分层, 由里及外; 调用括号, 同时延展; 其他元素, 先右后左.

括号分层

对于将名字包括起来的括号, 先分析括号内, 再分析括号外.

例如 int (*value) 的阅读顺序为 value-(*value)-int (*value).

由里及外

对于同一层的内容, 以名字为里面, 向外面读.

例如 int* value 的阅读顺序为 value-* value-int* value.

调用括号, 同时延展

对于没将名字包括起来的括号, 它是作为函数的参数存在的. 既然是函数, 则除了参数还存在返回值, 故应该向右分析参数部分并向左分析返回值部分, 即为 "同时延展".

例如 int (*value)(int, double), 右边读取到参数 (int, double), 左边读取到返回值 int.

其他元素, 先右后左.

对于同一层的其他内容, 先分析所有右边的内容, 再分析所有左边的内容.

例如 int value[3] 的阅读顺序为 value-value[3]-int value[3].

示例#

int value[3]
  • value[3]: value 是一个长度为 3 的数组; 其元素是?

  • int value[3]: 其元素是 int.

  • 所以 value 是一个长度为 3 的数组, 数组的元素是 int.

int (*value)[3]
  • (*value): value 是一个指针; 其指向的对象是?

  • (*value)[3]: 指向一个长度为 3 的数组; 其元素是?

  • int (*value)[3]: 其元素是 int.

  • 所以 value 是一个指针, 指针指向一个长度为 3 的数组, 数组的元素是 int.

int* (*const& value)[3]
  • (& value): value 是一个引用; 其引用的对象是?

  • (const& value): 是一个 const 对象; const 作用于?

  • (*const& value): 作用于一个指针; 其指向的元素是?

  • (*const& value)[3]: 指向一个长度为 3 的数组; 其元素是?

  • * (*const& value)[3]: 其元素是指针; 其指向的对象是?

  • int* (*const& value)[3]: 指向一个 int 对象.

  • 所以 value 是一个引用, 引用一个被 const 作用的指针, 指针指向一个长度为 3 的数组, 数组的元素是指针, 指向一个 int 对象.

int* (*value[3])(int, double)
  • (value[3]): value 是一个长度为 3 的数组; 其元素是?

  • (*value[3]): 其元素是指针; 其指向的对象是?

  • int* (*value[3])(int, double): 其指向一个函数, 参数是 (int, double), 返回值是 int*.

  • 所以 value 是一个长度为 3 的数组, 数组的元素是指针, 指向一个函数, 函数的参数是 (int, double), 返回值是 int*.

习题#

int* (*(*const& value)[3][4])(double, double).

明确复杂声明的语义#

如果真的考这种题那只能说没活了, 在实际的程序设计中, 复杂的类型往往有明确的语义乃至 不变式, 应该通过命名来解决.

类型别名#

C++11 前通过 typedef 原来的类型名 别名, C++11 及以后通过 using 别名 = 原来的类型名 可以创建类型别名, 为类型增加语义.

1using Log_type     = int;
2using Log_function = void(Log_type);  // 函数参数为 Log_type, 返回值为 void

但要注意类型别名只是一个别名, 实际使用的还是原来的类型.

1void function(int);
2
3int main() {
4  using Log_type = int;
5  Log_type value = 0;
6  function(value);  // 通过
7};

用自定义类型包装#

有时候我们需要区别于原来的类型定义一个新的类型名, 但又与原来的类型有同样的功能, 这在目前的 C++ 版本没有很好的解决方案.

折中方法是, 通过自定义类型进行包装. 这方面涉及的内容很多, 此处仅给出一个非常简单的例子.

 1void function(int);
 2
 3struct Widget {
 4 public:
 5  int value;
 6};
 7
 8int main() {
 9  Widget widget;
10  function(widget);  // 预期发生编译错误
11}