为什么C++支持在结构中成员分配数组,但一般不支持?

ozm*_*zmo 82 c c++ arrays struct variable-assignment

我理解不支持成员分配数组,因此以下方法不起作用:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"
Run Code Online (Sandbox Code Playgroud)

我只是接受了这个事实,认为该语言的目的是提供一个开放式框架,并让用户决定如何实现诸如复制数组之类的东西.

但是,以下工作正常:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;
Run Code Online (Sandbox Code Playgroud)

该数组num[3]是从其实例中成员分配struct1到其实例中的struct2.

为什么结构支持成员方式的数组,但一般情况下不支持?

编辑:Roger Pate 在结构中的线程std :: string中的注释- 复制/赋值问题?似乎指向答案的大致方向,但我不知道自己确认.

编辑2:许多出色的回应.我之所以选择Luther Blissett,是因为我对这种行为背后的哲学或历史原理很感兴趣,但James McNellis对相关规范文档的引用也很有用.

Nor*_*ame 43

这是我的看法:

C语言的开发提供了对C中数组类型演变的一些见解:

我将尝试概述数组的事情:

C的先行者B和BCPL没有明显的数组类型,声明如下:

auto V[10] (B)
or 
let V = vec 10 (BCPL)
Run Code Online (Sandbox Code Playgroud)

将V声明为(无类型)指针,该指针被初始化为指向10"字"内存的未使用区域.乙已经使用*了指针引用,并有[] 速记符号,*(V+i)意味着V[i],/ C++今天刚刚在C.但是,V它不是一个数组,它仍然是一个必须指向某个内存的指针.当Dennis Ritchie试图用结构类型扩展B时,这会带来麻烦.他希望数组成为结构的一部分,就像今天的C一样:

struct {
    int inumber;
    char name[14];
};
Run Code Online (Sandbox Code Playgroud)

但是,使用B,BCPL数​​组作为指针的概念,这将要求name字段包含一个指针,该指针必须在运行时初始化为结构中14字节的内存区域.初始化/布局问题最终通过为数组提供特殊处理来解决:编译器将跟踪数组在结构中,堆栈上的位置等,而实际上不需要指向数据的指针来实现,除了涉及数组的表达式.这种处理几乎允许所有B代码仍然运行,并且是"如果你看它们就转换为指针的数组"规则的来源.这是一个兼容性的黑客,结果非常方便,因为它允许开放大小的阵列等.

这里是我的猜测为什么不能分配数组:由于数组是B中的指针,你可以简单地写:

auto V[10];
V=V+5;
Run Code Online (Sandbox Code Playgroud)

改变一个"数组".这现在毫无意义,因为数组变量的基数不再是左值.所以这个分配是不允许的,这有助于捕获在声明的数组上进行这种变基的少数程序.然后这个概念卡住了:因为数组从来没有被设计为C类型系统的第一类城市化,所以它们大多被视为特殊的野兽,如果你使用它们就会成为指针.从某个角度来看(忽略了C阵列是一个拙劣的黑客攻击),禁止数组赋值仍然有一定道理:开放数组或数组函数参数被视为没有大小信息的指针.编译器没有为它们生成数组赋值的信息,并且出于兼容性原因需要指针赋值.

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}
Run Code Online (Sandbox Code Playgroud)

当1978年的C修订版添加了结构分配(http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf)时,这没有改变.即使记录 C 中的不同类型,也不可能在早期的K&R C中分配它们.您必须使用memcpy将它们成员复制,并且您只能将指针作为函数参数传递给它们.Assigment(和参数传递)现在简单地定义为struct的原始内存的memcpy,因为这不能破坏现有的代码,所以它很容易被引用.作为一种意想不到的副作用,这隐含地引入了某种类型的数组赋值,但是它在结构中的某处发生了,所以这不能真正引入数组使用方式的问题.

  • 听起来很合理! (2认同)

Jam*_*lis 28

关于赋值运算符,C++标准如下(C++03§5.17/ 1):

有几个赋值运算符... 都需要一个可修改的左值作为它们的左操作数

数组不是可修改的左值.

但是,特别定义了对类类型对象的赋值(§5.17/ 4):

对类的对象的赋值由复制赋值运算符定义.

因此,我们期待看一个类的隐式声明的复制赋值运算符(§12.8/ 13):

类X的隐式定义的复制赋值运算符执行其子对象的成员分配....每个子对象都以适合其类型的方式分配:
...
- 如果子对象是一个数组,则以适合于元素类型的方式分配每个元素
...

因此,对于类类型对象,可以正确复制数组.请注意,如果您提供用户声明的复制赋值运算符,则无法利用此操作,并且您必须逐个元素地复制数组.


推理在C(C99§6.5.16/ 2)中类似:

赋值运算符应具有可修改的左值作为其左操作数.

并且§6.3.2.1/ 1:

一个可修改的左值是一个没有数组类型的左值... [其他约束如下]

在C中,赋值比C++(§6.5.16.1/ 2)简单得多:

在简单赋值(=)中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值.

对于struct-type对象的赋值,左右操作数必须具有相同的类型,因此右操作数的值只是复制到左操作数中.

  • 有谁知道 C++03 标准中“可修改左值”的定义在哪里?它_应该_在 §3.10 中。索引说它是在该页面上定义的,但事实并非如此。§8.3.4/5 中的(非规范性)注释说“不能修改数组类型的对象,请参阅 3.10”,但 §3.10 一次没有使用“数组”这个词。 (2认同)
  • @GMan, James:最近有一个关于 comp.lang.c++ 的讨论 http://groups.google.com/group/comp.lang.c++/browse_frm/thread/feeed9c057be0d41# 如果你错过了它并且仍然感兴趣。显然这不是因为数组不是可修改的左值(数组肯定是左值,所有非常量左值都是可修改的),而是因为 `=` 需要 *RHS* 上的 *rvalue* 而数组不能成为*右值*!数组禁止 lvalue-to-rvalue 转换,用 lvalue-to-pointer 代替。`static_cast` 在创建右值方面并没有更好,因为它是用相同的术语定义的。 (2认同)