在 C++ 中将整数添加到数组中?
Posted
技术标签:
【中文标题】在 C++ 中将整数添加到数组中?【英文标题】:Adding integers to arrays in C++? 【发布时间】:2016-07-15 21:01:04 【问题描述】:考虑:
int sum(const int numbers[], const int size)
if (size == 0)
return 0;
else
return numbers[0] + sum(numbers+1, size-1);
这是一个来自 MIT 6.096 的简单递归函数,用于添加任意数量的整数,并且有效。
我看不懂的是最后一行:
numbers+1
是如何工作的,因为 numbers[]
是一个 int
数组,并且您不应该将整数添加到 int[]
常量?
【问题讨论】:
这是 C 代码。使用递归函数来计算数组的总和确实是他们可能选择的最糟糕的例子。谁写这样的例子?递归函数最好用在必然递归的算法上。 这里,const int numbers[]
与const int* numbers
相同:指向常量值的非常量指针
@tadman 出于某种原因,在某些编译器上,如果它们发现尾递归,它们会生成比循环更快的代码。但是,我同意这并不容易阅读。
@TomTanner 我真的很想看到一些基准,因为这对我来说听起来像是一个都市传奇。
@TomTanner 这并不重要,因为这个例子不是尾递归的。
【参考方案1】:
“numbers+1”如何工作,给定 numbers[] 是一个 int 数组,您不应该将整数添加到 int[] 常量?
没有int[]
常量。 numbers
被衰减为一个指针,numbers+1
是简单的指针运算,应用于传递给递归调用的参数。
【讨论】:
“numbers
衰减为指针”。不,不是。它被声明为指针。标记序列const int numbers[]
,当它出现在形参列表中时,声明一个指针而不是一个数组。
From 8.3.5p5: "函数的类型由以下规则确定。每个参数的类型(包括函数参数包)由其自己的decl-specifier-seq确定 和 declarator . 确定每个参数的类型后,任何类型为'array of T
'或'function return T
'的参数都被调整为'pointer to @987654328 @' 或 '指向函数的指针分别返回T
'。"
@BenVoigt 我觉得 πάντα ῥεῖ 在说 numbers
衰减为 int*
以传递到 int sum(const int numbers[], const int size)
。如果您不同意这一点,也许您应该单独回答,说明描述。
@BenVoigt 是对的。 numbers
- 函数参数 - 不是数组,因此它不会被隐式转换(“衰减”)为指针。它总是一个指针。
@JonathanMee 当然可以,它在 std (13.1.3) 中: 仅指针 *
与数组 []
不同的参数声明是等效的。也就是将数组声明调整为指针声明。只有第二个和后续的数组维度在参数类型中很重要【参考方案2】:
作为@πάντα ῥεῖ 回答的旁注,以下是对术语的一些澄清:
以下是描述数组表示法的另一种方式:
短语numbers[1]
也可以表示为*(numbers + 1)
其中*
运算符被称为取消引用 指针地址 numbers + 1
。
dereference 在这种情况下可以被认为是读取由 指向的值。
因此,您示例中的代码使用 pointer arithmetic。短语numbers + 1
是指针符号,指向指针numbers
的第二个int 位置。 size - 1
是从 numbers
开始的内存位置到数组末尾的字节数。
关于腐烂的意思:
通常,在C 数组参数 的上下文中,decay 传达了数组参数会丢失类型和维度信息的想法。您的const int numbers[]
被说(可以说)衰减 成int *
,因此不再能够提供数组大小信息。 (例如,使用sizeof()
宏不提供数组的长度,而是提供指针的大小。)这也是提供第二个参数以传达大小信息的原因。
然而,在这个问题的上下文中,decay 的意思是学术性的,正如@Ben Voigt 指出的那样:token sequence const int numbers [],当它出现在形参列表中时,声明的是一个指针而不是一个数组。(它从未衰减为指针,因为它是一个开始的指针。)
【讨论】:
我正要劫持这个问题,但后来发现了一些信息here 所以 C/C++ 足够聪明,可以根据数组的类型找出实际地址?例如,如果numbers
位于C00
位置,那么numbers+1
实际上不是C01
,而是C04
,因为int
的长度为4 个字节?我有点惊讶你不需要做numbers+1*sizeof(int)
什么的。
@Celeritas - 大声笑,劫持?我应该害怕吗? :) 所以 C/C++ 足够聪明,可以根据数组的类型计算出实际地址?。确实,当指针作为参数传递时,函数原型会传达该指针的类型信息。关于:那么 numbers+1 实际上不是 C01 而是 C04。是的,如果在该实现中定义了一个具有 32 位的 int。这通常是正确的,但并非总是如此。地址会随着与其关联的变量类型的递增(或递减)而改变,但该变量类型已定义。
我想我很惊讶 C/C++ 足够友好/足够智能,可以自动执行此操作。【参考方案3】:
随着πάντα ῥεῖ says int[]
衰减为int*
。
但是sum
这个函数是穷人的解决方案,你应该更喜欢accumulate
:
cout << accumulate(numbers, next(numbers, size), decay_t<decltype(numbers[0])>);
Live Example
如果你有 C++17 和一个静态分配的数组,比如int numbers[size]
,你可以利用cbegin
和cend
:
cout << accumulate(cbegin(numbers), cend(numbers), decay_t<decltype(numbers[0])>);
我尝试将递归sum
与accumulate
进行基准测试,但是sum
在我能够达到具有有意义差异的vector
大小之前耗尽了堆栈空间,这使得accumulate
变得清晰赢家。
我将accumulate
的init
参数的类型与numbers
' 元素的类型相关联:decay_t<decltype(numbers[0])>
。这样做的原因是,如果有人回来更改numbers
的类型,而不是更改accumulate
的init
参数的类型,则会将累积分配给错误的类型。
例如,如果我们使用累积线:cout << accumulate(cbegin(numbers), cend(numbers), 0)
,这对于int numbers[]
来说是可以的。如果我们切换到定义:double numbers[] = 1.3, 2.3, 3.3, 4.3;
,则会出现问题,但我们未能将init
参数更改为我们将把double
s 求和为int
。这将导致 10 而不是 11.2:http://ideone.com/A12xin
【讨论】:
是的,因为decay_t<decltype(numbers[0])>
具有超强的表现力且易于理解。 WTG C++!
极度讽刺。 C++ 很糟糕。
@BarryTheHatchet:是的,除非你故意试图混淆你的代码(恕我直言,这是大约 2/3 的 C++“功能”的唯一理由),请使用 for 循环。
@JamesAdkison:这是一个常见的、幽默的、故意的错误。
是不是因为你做了decltype(numbers[0])
,所以extent已经被删除了,所以数组中的一个元素没有extent?【参考方案4】:
numbers 是一个指针;在每次迭代中,函数 sum() 在数组中前进(这是 numbers+1 所做的),同时将大小减小 1(--size 会起作用也一样)。
当 size 达到 0 时,这是退出条件,递归结束。
【讨论】:
考虑将您的答案合并为一个答案。【参考方案5】:int sum(int *num,int size)
int total=0;
/* function to sum integer array */
if (size <= 0) return(ERROR);
while(size--) total+= *num++;
return total;
更快、更紧凑且容错。
【讨论】:
@TomTanner 说“一些编译器,如果他们发现尾递归,他们生成的代码比循环更快。”所以当你说你的代码“更快”时,你有没有计时? 我已经用基准测试更新了my answer,我确信您的“更快”声明是错误的。如果堆栈空间不足,我无法达到这些解决方案之间的统计差异。换句话说,编译器正在将它们全部转换为完全相同的解决方案。 @Jonathan Mee:我们可以说“不慢”而不是“快”吗?我希望一个好的编译器(无论如何在英特尔架构中)能够有效地将该构造与 SSE 指令并行化。还要记住,如果我们谈论的是大型数组,从内存中读取的时间很重要,而且执行线性读取通常更快。而且无论执行速度如何,这比任何替代方案都更容易理解。以上是关于在 C++ 中将整数添加到数组中?的主要内容,如果未能解决你的问题,请参考以下文章