“struct (*)[]”和“struct *[]”有什么区别?

3
我正在尝试使用指针、指针数组和数组。
我创建了一个名为“Fraction”的结构体,并尝试将一个Fraction数组传递到一个接受Fraction指针数组的函数中。但是我收到了以下错误信息:
Error   1   error C2664: 'void display(Fraction *[],int)' : cannot convert 
                             argument 1 from 'Fraction (*)[10]' to 'Fraction *[]'

我知道这个不起作用,但是你如何称呼它们中的每一个?例如:Fraction(*)[]Fraction*[]。int[] 是整数数组,int* 是整数指针。上面的代码对我来说看起来完全相同,除了 * 周围的括号。我不需要修复此错误,只是想简单了解这两个看似相似的结构之间的区别。

1
farry 是如何声明的? - Christophe
此外,在 main 函数之前,最大大小被声明为一个常量整数。 - Brett Reinhard
你已经回答了自己的问题,你的 farry 应该声明为 Fraction* farry = new Fraction[MAX_SIZE],因为你的函数期望一个指向指针的指针,而你传递的只是数组的第一个元素的指针。如果你使用 *farry 对 farry 进行解引用,你将得到数组的第一个元素,如果你按照我指出的方式声明它,你将得到一个指向数组的指针,然后指向一个数组的第一个元素,简单明了 :-) - XAMlMAX
@XAMlMAX 如果这样做,情况会比现在更糟。原本的函数希望接收一个指向对象指针的序列,而不是对象序列。使用 new Fraction[N] 进行分配也无法与该函数配合使用,无论您是否通过地址引用那些对象的指针。更糟糕的是,按照您描述的方式传递 &farry 将完全隐藏问题,直到运行时调用未定义的行为 - WhozCraig
@WhozCraig 我从来没有说过要把&farry传到任何地方,我只是想表明他传递的类型与函数期望接收的参数不匹配。而且,如果你声明一个变量 Fraction** farry 并正确初始化它,该函数期望的是一个指向指针的指针,它仍然可以正常工作。 - XAMlMAX
显示剩余3条评论
3个回答

5
错误原因 参数Fraction *fracArr[]需要一个指向分数的指针数组。
您定义了Fraction farry[max_size],这意味着farry是一个分数数组。
当您调用函数并提供&farry作为第一个参数时,您正在尝试获取一个数组的指针(Fraction (*)[10]),而不是一个指针数组(Fraction *[])。因此产生了不匹配的错误。 解决方案 如果您想使用分数数组,请按以下方式更改函数:
void display(Fraction fracArr[], int fracCounter){
    for (int i = 0; i < fracCounter; i++){
        cout << fracArr[i].num << "/" << fracArr[i].den << endl;
    }
}

并使用display(farry, fracCounter);进行调用。

附加说明:

更一般地,类型为未知大小的数组参数T arg[]将作为指向第一个元素的指针T *arg传递。

定义您的参数Fraction *arg[]Fraction **arg将导致相同的代码。 []只是隐藏了这个技术细节,并使意图更清晰(即使用指针数组还是使用指向指针的指针)。


@BrettReinhard 如果你添加 fracArray[0] = Fraction();,那么它仍然是指针数组吗? - XAMlMAX
我认为我混淆了分数数组的地址与分数指针数组的地址相同。这是一个公平的假设吗?它们在编译器中的视图并不相同吗?@christophe,我理解数组根据定义就是一个地址。 - Brett Reinhard
无论是值还是指向值的指针,您都不能使用相同的函数处理它们。对于fracArray,如果您在函数调用中省略&,它将按原样工作(实际上,如果您定义一个数组,则没有索引的该数组名称就是该数组的地址)。如果您想使用相同的函数名称根据参数类型执行不同的处理,则必须考虑进行重载。 - Christophe
参数 Fraction *fracArr[] 是一个指向分数的指针数组。实际上它是一个指向指向 Fraction 的指针的指针。 - juanchopanza
显示剩余6条评论

4
Fraction*[]是一个Fraction*数组(指针数组)。Fraction(*)[]是一个指向Fraction[](数组指针)的指针。区别在于括号将“指针”与Fraction隔离开来,否则两者会结合起来并给出不同于预期的类型。
机械上说,*&更喜欢绑定到类型名称上,而不是被孤立并代表整个内容,因此必须使用括号将其与元素类型隔离开来。这也适用于声明函数指针:int*(int, int)是一个接受两个int并返回int*的函数,而int(*)(int, int)是一个指向接受两个int并返回int的函数的指针。
考虑this这个简单的程序:
#include <iostream>
#include <typeinfo>

struct Type {};

// 1: Array of Type*.
void func(Type *arr [3]) {
    std::cout << "Type* array.\n"
              << typeid(arr).name() << "\n\n";
}

// 2: Array of Type&.
// Illegal.
// void func(Type &arr [3]) {
//     std::cout << "Type& array.\n"
//               << typeid(arr).name() << "\n\n";
// }

// 3: Pointer to array of Type.
void func(Type (*arr) [3]) {
    std::cout << "Pointer to Type array.\n"
              << typeid(arr).name() << "\n\n";
}

// 4: Reference to array of Type.
void func(Type (&arr) [3]) {
    std::cout << "Reference to Type array.\n"
              << typeid(arr).name() << "\n\n";
}

int main() {
    // Array of Type.
    Type   t_arr[3] = {};

    // Array of Type*.
    Type* tp_arr[3] = { &t_arr[0], &t_arr[1], &t_arr[2] };

    // Array of Type&.
    // Illegal.
    // Type& tr_arr[3] = { t_arr[0], t_arr[1], t_arr[2] };

    std::cout << "Type[3]: " << typeid(t_arr).name() << "\n\n";

    func(t_arr);  // Calls #4.
    func(&t_arr); // Calls #3.
    func(tp_arr); // Calls #1.
}

根据所使用的编译器,它将输出arr的压缩或未压缩类型,输出显示所有三种类型都是不同的:

// MSVC:
Type[3]: struct Type [3]

Reference to Type array.
struct Type [3]

Pointer to Type array.
struct Type (*)[3]

Type* array.
struct Type * *

// GCC:
Type[3]: A3_4Type

Reference to Type array.
A3_4Type

Pointer to Type array.
PA3_4Type

Type* array.
PP4Type

如果你不习惯这种语法,它可能会有些奇怪,并且很容易出现打错字的情况,因此如果需要使用它,建议创建一个类型别名。

// Array.
typedef Type Type_arr_t[3];

// Pointer.
typedef Type (*Type_arr_ptr_t)[3];

// Reference.
typedef Type (&Type_arr_ref_t)[3];

// ...

// Without typedefs.
Type   arr   [3];
Type (*arr_p)[3] = &arr;
Type (&arr_r)[3] =  arr;

// With typedefs.
Type_arr_t     arr2;
Type_arr_ptr_t arr2_p = &arr2;
Type_arr_ref_t arr2_r =  arr2;

这在声明返回指向数组的指针或引用的函数时非常有用,因为它们没有typedef看起来很傻,而且很容易出错和/或忘记语法。
typedef Type (*Type_arr_ptr_t)[3];
typedef Type (&Type_arr_ref_t)[3];

// Without typedefs.
Type (*return_ptr())[3];
Type (&return_ref())[3];

// With typedefs.
Type_arr_ptr_t return_ptr_2();
Type_arr_ref_t return_ref_2();

如需了解如何解析类似这样的内容,请参见顺时针螺旋法则


注意: 当一个数组被按值传递作为函数参数时,在许多其他情况下(特别是在任何不期望数组但需要指针的情况下),类型和维度信息会丢失,并且它会被隐式转换为指向数组的第一个元素的指针; 这被称为数组衰减成指针。这在上面的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]);

此外,请注意,有些情况下数组不会衰减为指针
// Example array.
Type arr[3];

// Function parameter.
void func(Type arr[3]);
void func(Type (*arr)[3]);
void func(Type (&arr)[3]);

// Function template parameter.
template<typename T>
void temp(T t);

// Class template parameter.
template<typename T>
struct S { typedef T type; };

// Specialised class template parameter.
template<typename T> struct S2;
template<typename T, size_t Sz>
struct S2<T[Sz]> { typedef T type[Sz]; };

func(arr);           // C: arr decays into Type*.
                     // C++: arr either binds to a Type(&)[3], or decays into Type*.
                     //  If both are available, causes error due to ambiguous function call.
func(&arr);          // C/C++: No decay, &arr is Type(*)[3].
sizeof(arr);         // C/C++: No decay, evaluates to (sizeof(Type) * 3).
alignof(arr);        // C/C++: No decay, evaluates to alignof(Type).
decltype(arr);       // C++: No decay, evaluates to Type[3].
typeid(arr);         // C++: No decay, evaluates to a std::type_info for Type[3].
for (Type& t : arr); // C++: No decay, ranged-based for accepts arrays.
temp(arr);           // C++: arr decays to Type* during function template deduction.
temp<Type[3]>(arr);  // C++: No decay, deduction isn't required.

// For class templates, deduction isn't performed, so array types used as template parameters
//  don't decay.
S<Type[3]>::type;    // C++: No decay, type is Type[3].
S2<Type[3]>::type;   // C++: No decay, type is Type[3].

// String literals are arrays, too.
decltype("Hello.");             // C++: No decay, evaluates to const char[7].
char  c_arr[] = "Hello.";       // C/C++: No decay, c_arr is a local array, of type char[7],
                                //  containing copy of "Hello."
const char* c_ptr   = "Hello."; // C/C++: const char[7] "Hello." is stored in read-only
                                //  memory, and ptr points to it.

// There may be other cases in which arrays don't decay, which I'm currently not aware of.

因此,简而言之,虽然说 Type[3] 是一个数组类型,Fraction*[5] 也是一个数组类型,但是会有一些情况下,编译器会将这两种声明无声地替换为 Type*Fraction**,并且由于这个替换,类型和维度信息将会丢失;这个损失被称为数组衰减或指针衰减。
感谢 juanchopanza 提醒我提到了指针衰减。

谢谢,这正是我在寻找的。 - Brett Reinhard
不,它不是。它是一个函数参数。当函数被调用时,它是会衰减的参数。该参数是指向指针的指针。 - juanchopanza
话虽如此,我已经添加了一条关于数组指针衰减的注释,以及它何时发生和不发生。 - Justin Time - Reinstate Monica
说了这么多,但你的引导句仍然是错误的。赶紧修正它吧。这个东西根本不是一个数组。 - juanchopanza
“decays to”可能比“is adjusted to”短,但它实际上是完全错误的,因为实际上有一个叫做数组衰减的东西。 - juanchopanza
显示剩余14条评论

2

这是编译器输出原始类型与参数声明之间引起一些混淆的地方之一。如果重新插入变量名,则比较现在是:

Fraction (*farray)[10]

并且:

Fraction *farray[]

在这一点上,如果您愿意接受声明与正则表达式一样具有优先级的事实,那么错误就变得显而易见了。
根据C/C++的优先级表,数组索引运算符“[]”比指针解引用运算符unary "*"的优先级更高。
如果将此规则应用于声明,则第二个声明成为指针数组,而第一个由于括号中的“指针”绑定更紧密,因此它是指向数组的指针。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接