Fraction*[]
是一个
Fraction*
数组(指针数组)。
Fraction(*)[]
是一个指向
Fraction[]
(数组指针)的指针。区别在于括号将“指针”与
Fraction
隔离开来,否则两者会结合起来并给出不同于预期的类型。
机械上说,
*
或
&
更喜欢绑定到类型名称上,而不是被孤立并代表整个内容,因此必须使用括号将其与元素类型隔离开来。这也适用于声明函数指针:
int*(int, int)
是一个接受两个
int
并返回
int*
的函数,而
int(*)(int, int)
是一个指向接受两个
int
并返回
int
的函数的指针。
考虑
this这个简单的程序:
#include <iostream>
#include <typeinfo>
struct Type {};
void func(Type *arr [3]) {
std::cout << "Type* array.\n"
<< typeid(arr).name() << "\n\n";
}
void func(Type (*arr) [3]) {
std::cout << "Pointer to Type array.\n"
<< typeid(arr).name() << "\n\n";
}
void func(Type (&arr) [3]) {
std::cout << "Reference to Type array.\n"
<< typeid(arr).name() << "\n\n";
}
int main() {
Type t_arr[3] = {};
Type* tp_arr[3] = { &t_arr[0], &t_arr[1], &t_arr[2] };
std::cout << "Type[3]: " << typeid(t_arr).name() << "\n\n";
func(t_arr);
func(&t_arr);
func(tp_arr);
}
根据所使用的编译器,它将输出arr
的压缩或未压缩类型,输出显示所有三种类型都是不同的:
Type[3]: struct Type [3]
Reference to Type array.
struct Type [3]
Pointer to Type array.
struct Type (*)[3]
Type* array.
struct Type * *
Type[3]: A3_4Type
Reference to Type array.
A3_4Type
Pointer to Type array.
PA3_4Type
Type* array.
PP4Type
如果你不习惯这种语法,它可能会有些奇怪,并且很容易出现打错字的情况,因此如果需要使用它,建议创建一个类型别名。
typedef Type Type_arr_t[3];
typedef Type (*Type_arr_ptr_t)[3];
typedef Type (&Type_arr_ref_t)[3];
Type arr [3];
Type (*arr_p)[3] = &arr;
Type (&arr_r)[3] = arr;
Type_arr_t arr2;
Type_arr_ptr_t arr2_p = &arr2;
Type_arr_ref_t arr2_r = arr2;
这在声明返回指向数组的指针或引用的函数时非常有用,因为它们没有typedef看起来很傻,而且很容易出错和/或忘记语法。
typedef Type
如需了解如何解析类似这样的内容,请参见顺时针螺旋法则。
注意: 当一个数组被按值传递作为函数参数时,在许多其他情况下(特别是在任何不期望数组但需要指针的情况下),类型和维度信息会丢失,并且它会被隐式转换为指向数组的第一个元素的指针; 这被称为数组
衰减成指针。这在上面的
func(Type*[3])
中得到了证明,编译器采用
Type*[3]
参数类型,即
Type*
数组,并将其替换为
Type**
,即
Type*
指针;
[3]
被丢弃并被简单的
*
取代,因为函数可以接受指针而不是数组。当调用
func()
时,由于这个原因,数组将会衰变。由于此,以下签名被视为相同,参数在所有三个中都是
Type**
。
void func(Type*[3]);
void func(Type*[] ); // Dimension isn't needed, since it'll be replaced anyways.
void func(Type** );
这样做是因为它比尝试通过值传递整个数组更有效率(它只需要传递一个指针,可以轻松地放入单个寄存器中,而不是尝试将整个数组加载到内存中),并且将数组类型编码到函数的参数列表中会从函数中删除任何关于它所能接受的数组大小的灵活性(如果一个函数接受
Type[3]
,那么你不能将其传递给
Type[4]
或者
Type[2]
)。由于这个原因,编译器会自动用
Type*
替换
Type[N]
或
Type[]
,导致数组在传递时衰减。这可以通过明确地使用指针或引用来避免;虽然这与让数组衰减一样高效(前者因为它仍然只传递一个指针,后者因为大多数编译器使用指针实现引用),但它失去了灵活性(这就是为什么它通常与模板配对使用,模板恢复了灵活性,同时不移除任何严格性)。
// Will take any pointer to a Type array, and replace N with the number of elements.
// Compiler will generate a distinct version of `func()` for each unique N.
template<size_t N>
void func(Type (*)[N]);
// Will take any reference to a Type array, and replace N with the number of elements.
// Compiler will generate a distinct version of `func()` for each unique N.
template<size_t N>
void func(Type (&)[N]);
请注意,C语言没有模板的便利,因此任何旨在与两种语言一起使用的代码都应该使用传递“大小”参数的C习惯用法,或者专门为特定大小的数组编写;前者更加灵活,而后者则在您永远不需要使用其他大小的数组时非常有用。
void func1(Type *arr, size_t sz);
void func2(Type (*arr)[3]);
此外,请注意,有些情况下数组
不会衰减为指针。
Type arr[3];
void func(Type arr[3]);
void func(Type [3].
sizeof(arr);
alignof(arr);
decltype(arr);
typeid(arr);
for (Type& t : arr);
temp(arr);
temp<Type[3]>(arr);
S<Type[3]>::type;
S2<Type[3]>::type;
decltype("Hello.");
char c_arr[] = "Hello.";
const char* c_ptr = "Hello.";
因此,简而言之,虽然说
Type[3]
是一个数组类型,
Fraction*[5]
也是一个数组类型,但是会有一些情况下,编译器会将这两种声明无声地替换为
Type*
或
Fraction**
,并且由于这个替换,类型和维度信息将会丢失;这个损失被称为数组衰减或指针衰减。
感谢
juanchopanza 提醒我提到了指针衰减。
farry
应该声明为Fraction* farry = new Fraction[MAX_SIZE]
,因为你的函数期望一个指向指针的指针,而你传递的只是数组的第一个元素的指针。如果你使用*farry
对 farry 进行解引用,你将得到数组的第一个元素,如果你按照我指出的方式声明它,你将得到一个指向数组的指针,然后指向一个数组的第一个元素,简单明了 :-) - XAMlMAXnew Fraction[N]
进行分配也无法与该函数配合使用,无论您是否通过地址引用那些对象的指针。更糟糕的是,按照您描述的方式传递&farry
将完全隐藏问题,直到运行时调用未定义的行为。 - WhozCraig&farry
传到任何地方,我只是想表明他传递的类型与函数期望接收的参数不匹配。而且,如果你声明一个变量Fraction** farry
并正确初始化它,该函数期望的是一个指向指针的指针,它仍然可以正常工作。 - XAMlMAX