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

时间:2010-08-09 03:10:50

标签: c++ c arrays variable-assignment struct

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

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

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

但是,以下方法确实有效:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

数组num[3]是成员分配的,从struct1中的实例分配到struct2中的实例。

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

编辑 Roger Pate 在帖子std::string in struct - Copy/assignment issues?中的评论似乎指向答案的大致方向,但我不够了解我自己确认一下。

编辑2 :许多出色的回复。我选择 Luther Blissett ,因为我主要想知道这种行为背后的哲学或历史原理,但 James McNellis 对相关规范文档的引用也很有用。 。

5 个答案:

答案 0 :(得分:41)

这是我的看法:

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

我将尝试概述数组的内容:

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

auto V[10] (B)
or 
let V = vec 10 (BCPL)

将V声明为(无类型)指针,该指针被初始化为指向10“字”内存的未使用区域。 B已使用*进行指针解除引用,并使用[]简写符号,*(V+i)表示V[i],就像今天的C / C ++一样。但是,V不是数组,它仍然是一个必须指向某个内存的指针。当Dennis Ritchie试图用结构类型扩展B时,这会带来麻烦。他希望数组成为结构的一部分,就像今天的C一样:

struct {
    int inumber;
    char name[14];
};

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

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

auto V[10];
V=V+5;

重新定义“数组”。这现在毫无意义,因为数组变量的基数不再是左值。所以这个分配是不允许的,这有助于捕获在声明的数组上执行此变基的少数程序。然后这个概念卡住了:因为数组从来没有被设计为C类型系统的第一类城市化,所以它们大多被视为特殊的野兽,如果你使用它们就会成为指针。从某个角度来看(忽略了C阵列是一个拙劣的黑客攻击),禁止数组赋值仍然有一定道理:开放数组或数组函数参数被视为没有大小信息的指针。编译器没有为它们生成数组赋值的信息,并且出于兼容性原因需要指针赋值。为声明的数组引入数组赋值会引入错误,虽然是虚假的分配(是一个= ba指针赋值还是元素副本?)和其他麻烦(你如何通过值传递一个数组?)而不实际解决问题 - 只需制作一切用memcpy显式!

/* 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
}

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

答案 1 :(得分: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对象的赋值,左右操作数必须具有相同的类型,因此右操作数的值只是复制到左操作数中。

答案 2 :(得分:2)

在此链接中:http://www2.research.att.com/~bs/bs_faq2.html有一个关于数组赋值的部分:

数组的两个基本问题是

  • 数组不知道自己的大小
  • 数组的名称在最轻微的挑衅时转换为指向其第一个元素的指针

我认为这是数组和结构之间的根本区别。数组变量是具有有限自我知识的低级数据元素。从根本上说,它是一块记忆和一种索引的方式。

因此,编译器无法区分int a [10]和int b [20]。

然而,结构并不具有相同的歧义。

答案 3 :(得分:0)

我知道,每个回答的人都是C / C ++的专家。但我想,这是主要原因。

num2 = num1;

在这里,您尝试更改阵列的基址,这是不允许的。

当然, struct2 = struct1;

这里,对象struct1被分配给另一个对象。

答案 4 :(得分:0)

没有进一步努力增强C中的数组的另一个原因可能是数组分配不会有用。即使可以通过将其包装在struct中轻松实现(并且struct的地址可以简单地转换为数组的地址,甚至可以转换为数组的第一个元素的地址以进行进一步处理),但很少使用此功能。原因之一是不同大小的数组不兼容,这限制了赋值或相关的按值传递给函数的好处。

大多数具有数组参数的函数都使用语言(其中数组是第一类类型)针对任意大小的数组编写。然后,该函数通常在给定数量的元素上进行迭代,即数组提供的信息。 (当然,在C语言中,习惯用法是传递一个指针和一个单独的元素计数。)不需要经常接受仅接收一个特定大小的数组的函数,因此不会遗漏太多。 (当您可以将其留给编译器以针对任何出现的数组大小生成单独的函数时(与C ++模板一样,这会发生变化;这就是#event_tut.py import random, time from threading import Event, Thread event = Event() def waiter(event, nloops): count=0 while(count<10): print("%s. Waiting for the flag to be set." % (i+1)) event.wait() # Blocks until the flag becomes true. print("Wait complete at:", time.ctime()) event.clear() # Resets the flag. print('wait exit') count=count+1 def setter(event, nloops): for i in range(nloops): time.sleep(random.randrange(2, 5)) # Sleeps for some time. event.set() threads = [] nloops = 10 threads.append(Thread(target=waiter, args=(event, nloops))) threads[-1].start() threads.append(Thread(target=setter, args=(event, nloops))) threads[-1].start() for thread in threads: thread.join() print("All done.") 很有用的原因。)