为什么数组类型对象不可修改? [英] Why array type object is not modifiable?

查看:34
本文介绍了为什么数组类型对象不可修改?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此处声明

<块引用>

术语可修改左值用于强调左值允许更改和检查指定的对象.以下对象类型是左值,但不可修改的左值:

  • 数组类型
  • 不完整的类型
  • const 限定类型
  • 一个结构或联合类型,其成员之一被限定为 const 类型

因为这些左值是不可修改的,所以它们不能出现在赋值语句的左侧.

为什么数组类型的对象不可修改?写的对不对

int i = 5, a[10] = {0};[i] = 1;

?
还有,什么是不完整类型?

解决方案

假设声明

int a[10];

那么以下所有情况都为真:

  • 表达式 a的类型是int的10元素数组";除非 asizeof 或一元 & 运算符的操作数,否则表达式将转换为指向 int" 其值将是数组中第一个元素的地址;
  • 表达式 a[i]的类型是int;它指的是存储为数组的第 i 个元素的整数对象;
  • 表达式 a 可能不是赋值的目标,因为 C 不像其他变量那样对待数组,所以你不能写类似 a = b 的东西a = malloc(n * sizeof *a) 或类似的东西.

你会注意到我一直在强调表达"这个词.我们留出的用于容纳 10 个整数的内存块与我们用来引用该内存块的符号(表达式)之间存在差异.我们可以用表达式 a 来引用它.我们还可以创建一个指向该数组的指针:

int (*ptr)[10] = &a;

表达式*ptr也有类型int的10元素数组",它引用与a相同的内存块> 指的是.

C 不会像对待其他类型的表达式一样对待数组表达式(a, *ptr),区别之一是 array 类型可能不是赋值的目标.您不能重新分配 a 以引用不同的数组对象(表达式 *ptr 也是如此).你可以a[i](*ptr)[i]分配一个新值(改变每个数组的值element),你可以指定 ptr 指向不同的数组:

int b[10], c[10];.....ptr = &b;.....ptr = &c;

至于第二个问题...

不完整类型缺少尺寸信息;声明如

struct foo;内部酒吧[];工会布莱奇;

所有类型都会创建不完整的类型,因为编译器没有足够的信息来确定为该类型的对象留出多少存储空间.您不能创建不完整类型的对象;例如,您不能声明

struct foo myFoo;

除非您完成了struct foo 的定义.但是,您可以创建指向不完整类型的指针;例如,您可以声明

struct foo *myFooPtr;

没有完成 struct foo 的定义,因为指针只存储对象的地址,您不需要知道类型的大小.这使得定义自引用类型成为可能,例如

结构节点{T键;//对于任何类型 TQ值;//对于任何类型的 Q结构节点 *left;结构节点*右;};

struct node 的类型定义不是完整,直到我们点击结束 }.因为我们可以声明一个指向不完整类型的指针,所以我们没问题.但是,我们不能将结构定义为

结构节点{...//同上左结构节点;右结构节点;};

因为当我们声明leftright 成员时类型不完整,也因为每个leftright 成员将每个包含自己的 leftright 成员,每个成员将包含 leftright 他们 自己的成员,等等等等.

这对结构体和联合体来说很棒,但是

int bar[];

???

我们已经声明了符号 bar 并表明它将是一个数组类型,但此时大小未知.最终我们必须用一个大小来定义它,但这样符号就可以用在数组大小没有意义或没有必要的上下文中.不过,我的脑海里没有一个好的、非人为的例子来说明这一点.

编辑

回复这里的评论,因为评论部分没有空间来写我想写的东西(今晚我心情很复杂).你问:

<块引用>

这是否意味着每个变量都是表达式?

这意味着任何变量都可以是表达式或表达式的一部分.这是语言标准 定义术语表达式:

<块引用>6.5 表达式
1 表达式 是一个运算符和操作数的序列,用于指定一个值,或指定对象或函数,或产生副作用,或执行其组合.

例如,变量a本身就算一个表达式;它指定我们定义的数组对象来保存 10 个整数值.它还计算数组的第一个元素的地址.变量 a 也可以是更大表达式的一部分,如 a[i];运算符是下标运算符[],操作数是变量ai.此表达式指定数组的单个成员,并计算当前存储在该成员中的值.该表达式又可以是更大的表达式的一部分,例如 a[i] = 0.

<块引用>

另外让我澄清一下,在声明 int a[10] 中,a[] 是否代表数组类型

是的,正是.

在 C 中,声明基于表达式的类型,而不是对象的类型.如果您有一个名为 y 的简单变量,用于存储 int 值,并且您想访问该值,则只需在表达式中使用 y,喜欢

x = y;

表达式 y的类型是int,所以写了y的声明

int y;

另一方面,如果您有一个 int 值的 array,并且您想访问特定元素,则可以使用数组名称和索引与下标运算符一起访问该值,例如

x = a[i];

表达式 a[i]的类型是int,所以数组的声明写成

int arr[N];//对于某个值 N.

arrint-ness"由类型说明符int 给出;arr 的数组性"由声明符 arr[N] 给出.声明符为我们提供了被声明对象的名称 (arr) 以及类型说明符未给出的一些附加类型信息(是一个 N 元素数组").声明读"为

 a -- aa[N] -- 是一个 N 元素数组int a[N];-- 整数

编辑2

毕竟,我还没有告诉你为什么数组表达式是不可修改的左值的真正原因.所以这是这本书的另一章答案.

C 并没有完全从丹尼斯·里奇 (Dennis Ritchie) 的头脑中形成;它派生自一种称为 B 的早期语言(派生自 BCPL).1 B 是一种无类型"语言;它没有整数、浮点数、文本、记录等的不同类型.相反,一切都只是一个固定长度的单词或单元格"(本质上是一个无符号整数).记忆被视为一个线性的单元阵列.当你在B中分配了一个数组时,比如

auto V[10];

编译器分配了11个单元格;数组本身的 10 个连续单元格,加上一个绑定到 V 的单元格,其中包含第一个单元格的位置:

 +----+V: ||-----++----+ |... |+----+ |||<----++----+||+----+||+----+||+----+...

当 Ritchie 将 struct 类型添加到 C 时,他意识到这种安排给他带来了一些问题.例如,他想创建一个 struct 类型来表示文件或目录表中的条目:

struct {国际号码;字符名称[14];};

他希望结构不仅以抽象的方式描述条目,而且还表示实际文件表条目中的位,没有额外的单元格或单词来存储第一个元素的位置数组.所以他摆脱了它——他没有留出一个单独的位置来存储第一个元素的地址,而是编写了 C 以便在计算数组表达式时计算第一个元素的地址.

就是为什么你不能做这样的事情

int a[N], b[N];a = b;

因为 ab 在那个上下文中评估为指针 values;相当于写3 = 4.内存中没有任何东西实际存储数组中第一个元素的地址;编译器只是在翻译阶段计算它.

<小时><子>1.本文全部摘自The Development of the C Language

It is stated here that

The term modifiable lvalue is used to emphasize that the lvalue allows the designated object to be changed as well as examined. The following object types are lvalues, but not modifiable lvalues:

  • An array type
  • An incomplete type
  • A const-qualified type
  • A structure or union type with one of its members qualified as a const type

Because these lvalues are not modifiable, they cannot appear on the left side of an assignment statement.

Why array type object is not modifiable? Isn't it correct to write

int i = 5, a[10] = {0};    
a[i] = 1;

?
And also, what is an incomplete type?

解决方案

Assume the declaration

int a[10];

then all of the following are true:

  • the type of the expression a is "10-element array of int"; except when a is the operand of the sizeof or unary & operators, the expression will be converted to an expression of type "pointer to int" and its value will be the address of the first element in the array;
  • the type of the expression a[i] is int; it refers to the integer object stored as the i'th element of the array;
  • The expression a may not be the target of an assignment because C does not treat arrays like other variables, so you cannot write something like a = b or a = malloc(n * sizeof *a) or anything like that.

You'll notice I keep emphasizing the word "expression". There's a difference between the chunk of memory we set aside to hold 10 integers and the symbols (expressions) we use to refer to that chunk of memory. We can refer to it with the expression a. We can also create a pointer to that array:

int (*ptr)[10] = &a;

The expression *ptr also has type "10-element array of int", and it refers to the same chunk of memory that a refers to.

C does not treat array expressions (a, *ptr) like expressions of other types, and one of the differences is that an expression of array type may not be the target of an assignment. You cannot reassign a to refer to a different array object (same for the expression *ptr). You may assign a new value to a[i] or (*ptr)[i] (change the value of each array element), and you may assign ptr to point to a different array:

int b[10], c[10];
.....
ptr = &b;
.....
ptr = &c;

As for the second question...

An incomplete type lacks size information; declarations like

struct foo;
int bar[];
union bletch;

all create incomplete types because there isn't enough information for the compiler to determine how much storage to set aside for an object of that type. You cannot create objects of incomplete type; for example, you cannot declare

struct foo myFoo;

unless you complete the definition for struct foo. However, you can create pointers to incomplete types; for example, you could declare

struct foo *myFooPtr;

without completing the definition for struct foo because a pointer just stores the address of the object, and you don't need to know the type's size for that. This makes it possible to define self-referential types like

struct node {
  T key;  // for any type T
  Q val;  // for any type Q
  struct node *left; 
  struct node *right;
};

The type definition for struct node isn't complete until we hit that closing }. Since we can declare a pointer to an incomplete type, we're okay. However, we could not define the struct as

struct node {
  ... // same as above
  struct node left;
  struct node right;
};

because the type isn't complete when we declare the left and right members, and also because each left and right member would each contain left and right members of their own, each of which would contain left and right members of their own, and on and on and on.

That's great for structs and unions, but what about

int bar[];

???

We've declared the symbol bar and indicated that it will be an array type, but the size is unknown at this point. Eventually we'll have to define it with a size, but this way the symbol can be used in contexts where the array size isn't meaningful or necessary. Don't have a good, non-contrived example off the top of my head to illustrate this, though.

EDIT

Responding to the comments here, since there isn't going to be room in the comments section for what I want to write (I'm in a verbose mood this evening). You asked:

Does it mean every variables are expression?

It means that any variable can be an expression, or part of an expression. Here's how the language standard defines the term expression:

6.5 Expressions
1 An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.

For example, the variable a all by itself counts as an expression; it designates the array object we defined to hold 10 integer values. It also evaluates to the address of the first element of the array. The variable a can also be part of a larger expression like a[i]; the operator is the subscript operator [] and the operands are the variables a and i. This expression designates a single member of the array, and it evaluates to the value currectly stored in that member. That expression in turn can be part of a larger expression like a[i] = 0.

And also let me clear that, in the declaration int a[10], does a[] stands for array type

Yes, exactly.

In C, declarations are based on the types of expressions, rather than the types of objects. If you have a simple variable named y that stores an int value, and you want to access that value, you simply use y in an expression, like

x = y;

The type of the expression y is int, so the declaration of y is written

int y;

If, on the other hand, you have an array of int values, and you want to access a specific element, you would use the array name and an index along with the subscript operator to access that value, like

x = a[i];

The type of the expression a[i] is int, so the declaration of the array is written as

int arr[N]; // for some value N.  

The "int-ness" of arr is given by the type specifier int; the "array-ness" of arr is given by the declarator arr[N]. The declarator gives us the name of the object being declared (arr) along with some additional type information not given by the type specifier ("is an N-element array"). The declaration "reads" as

    a       -- a
    a[N]    -- is an N-element array
int a[N];   -- of int

EDIT2

And after all that, I still haven't told you the real reason why array expressions are non-modifiable lvalues. So here's yet another chapter to this book of an answer.

C didn't spring fully formed from the mind of Dennis Ritchie; it was derived from an earlier language known as B (which was derived from BCPL).1 B was a "typeless" language; it didn't have different types for integers, floats, text, records, etc. Instead, everything was simply a fixed length word or "cell" (essentially an unsigned integer). Memory was treated as a linear array of cells. When you allocated an array in B, such as

auto V[10];

the compiler allocated 11 cells; 10 contiguous cells for the array itself, plus a cell that was bound to V containing the location of the first cell:

    +----+
V:  |    | -----+
    +----+      |
     ...        |
    +----+      |
    |    | <----+
    +----+
    |    |
    +----+
    |    |      
    +----+
    |    |
    +----+
     ...

When Ritchie was adding struct types to C, he realized that this arrangement was causing him some problems. For example, he wanted to create a struct type to represent an entry in a file or directory table:

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

He wanted the structure to not just describe the entry in an abstract manner, but also to represent the bits in the actual file table entry, which didn't have an extra cell or word to store the location of the first element in the array. So he got rid of it - instead of setting aside a separate location to store the address of the first element, he wrote C such that the address of the first element would be computed when the array expression was evaluated.

This is why you can't do something like

int a[N], b[N];
a = b;

because both a and b evaluate to pointer values in that context; it's equivalent to writing 3 = 4. There's nothing in memory that actually stores the address of the first element in the array; the compiler simply computes it during the translation phase.


1. This is all taken from the paper The Development of the C Language

这篇关于为什么数组类型对象不可修改?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆