Почему объект типа array не изменяется?

указано здесь это

термин modifiable lvalue используется, чтобы подчеркнуть, что lvalue позволяет изменять назначенный объект, а также исследовать. Следующие типы объектов являются lvalues, но не изменяемые lvalues:

  • тип массива
  • неполного типа
  • const-квалифицированный тип
  • тип структуры или объединения с одним из его членов квалифицированным как const тип

поскольку эти значения не изменяются, они не могут отображаться в левой части инструкции присваивания.

почему объект типа массива не изменяется? Разве не правильно писать

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

?
А также, что такое неполный тип?

5 ответов


принять декларацию

int a[10];

тогда верно все следующее:

  • тип выражение a является " 10-элементный массив int"; кроме тех случаев, когда a является операндом sizeof или унарный & операторы, выражение будет преобразовано в выражение типа "указатель на int" и его стоимость будет адрес первого элемента в массиве;
  • тип выражение a[i] и int; он относится к целочисленному объекту, хранящемуся как i ' - й элемент массива;
  • на выражение a может не быть целью назначения, потому что C не обрабатывает массивы, как другие переменные, поэтому вы не можете написать что-то вроде a = b или a = malloc(n * sizeof *a) или что-то в этом роде.

вы заметите, что я продолжаю подчеркивать слово "выражение". Есть разница между куском память мы выделяем для хранения 10 целых чисел и символов (выражений), которые мы используем для обозначения этого куска памяти. Мы можем обратиться к нему с выражением a. Мы также можем создать указатель на этот массив:

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

выражение *ptr также имеет тип " 10-элементный массив int", и это относится к тому же куску памяти, что a относится.

C не обрабатывает выражения массива (a, *ptr) как и выражения других типов, и одно из различия в том, что выражение массив тип не может быть объектом присвоения. Вы не можете переназначить a для ссылки на другой объект массива (то же самое для выражения *ptr). Вы мая присвоить новое значение a[i] или (*ptr)[i] (измените значение каждого массива элемент), и вы можете назначить ptr чтобы указать на другой массив:

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

что касается второго вопроса...

An неполное type не хватает информации о размере; объявления, такие как

struct foo;
int bar[];
union bletch;

все создают неполные типы, потому что компилятору не хватает информации, чтобы определить, сколько места для хранения объекта этого типа. Вы не можете создавать объекты неполного типа; например, вы не можете объявить

struct foo myFoo;

если вы не выполните определение struct foo. Однако, вы можете создать указатели для неполных типов; для пример, ты мог бы объявить

struct foo *myFooPtr;

без завершения определения для struct foo потому что указатель хранит адрес объекта, и вам не нужно знать размер, тип для этого. Это позволяет определить самореферентные типы, такие как

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

определение типа struct node не полное пока мы не достигли этого закрытия }. Поскольку мы можем объявить указатель на неполный тип, мы в порядке. Однако мы не могли определить структуру как

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

потому что тип не является полным, когда мы объявляем left и right членов, а также потому, что каждый left и right каждый член будет содержать left и right собственные члены, каждый из которых будет содержать left и right члены их собственный, и так далее и так далее.

это отлично подходит для структур и союзов, но как насчет

int bar[];

???

мы объявили символ bar и указал, что это будет типа массива, но размер неизвестен на данный момент. в конце концов мы должны определить его с размером, но таким образом символ может использоваться в контекстах, где размер массива не имеет смысла или необходимости. У меня нет хорошего, не придуманного примера с моей головы, чтобы проиллюстрировать это, хотя.

EDIT

отвечая на комментарии здесь, так как в разделе комментариев не будет места для того, что я хочу написать (я в многословном настроении сегодня вечером). Вы спросили:

значит все переменные выражения?

это означает, что любая переменная может быть выражением или частью выражения. Вот как стандартный язык определение термина выражение:

6.5 выражения
1 элемент выражение - это последовательность операторов и операндов, которая задает вычисление значение, или обозначающее объект или функцию, или создающее побочные эффекты, или выполняет их комбинацию.

например, переменная a все само по себе считается выражением; он обозначает объект массива, который мы определили для хранения 10 целых значений. Он также вычисляет адрес первого элемента массива. Переменная a также может быть частью большего выражения вроде a[i]; оператор-это оператор subscript [] и операнды являются переменными a и i. Это выражение обозначает один элемент массива и вычисляет значение, хранящееся в этом элементе. Это выражение, в свою очередь, может быть частью большего выражения вроде a[i] = 0.

и пусть мне ясно, что в объявлении int a[10] A [] означает тип массива

Да, точно.

в C объявления основаны на типах выражений, а не на типах объектов. Если у вас есть простая переменная с именем y что хранит int значение, и вы хотите получить доступ к этому значению, вы просто использовать y в выражении, как

x = y;

тип выражение y is int, так что объявление y написано

int y;

если, с другой стороны, у вас есть массив of int значения, и вы хотите получить доступ к определенному элементу, вы должны использовать имя массива и индекс вместе с оператором индекса для доступа к этому значению, например

x = a[i];

тип выражение a[i] и int, поэтому объявление массива записывается как

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

в "int-ность" arr задается спецификатором типа int; "массивность"arr задается декларантом arr[N]. Декларатор дает нам имя объявляемого объекта (arr) вместе с некоторой дополнительной информацией о типе, не заданной спецификатором типа ("является массивом N-элементов"). Декларация "читается" как

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

EDIT2

и после всего этого, я еще не сказал вам реальные причина, по которой выражения массива являются немодифицируемыми lvalues. Итак, вот еще одна глава этой книги ответов.

C не полностью сформировался из разума Денниса Ричи; он был получен из более раннего языка, известного как B (который был получен из BCPL).1 B был" безтипным " языком; у него не было разных типов для целых чисел, поплавков, текста, записей и т. д. Вместо этого все было просто словом фиксированной длины или "ячейкой" (по сути, целым числом без знака). Память рассматривалась как линейный массив ячеек. Когда вы выделили массив в B, например

auto V[10];

компилятор выделил 11 ячеек; 10 смежных ячеек для самого массива, плюс ячейка, которая была привязана к V, содержащая местоположение первой ячейки:

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

когда Ричи добавить struct типы В C, он понял, что эта схема вызывает у него некоторые проблемы. Например, он хотел создать тип структуры для представления записи в файле или таблица каталогов:

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

он хотел, чтобы структура не просто описывала запись абстрактно, но и представляла биты в фактической записи таблицы файлов, в которой не было дополнительной ячейки или слова для хранения местоположения первого элемента в массиве. Поэтому он избавился от него-вместо того, чтобы выделить отдельное место для хранения адреса первого элемента, он написал C таким образом, чтобы адрес первого элемента был вычислен, когда выражение массива было оцененный.

этой почему вы не можете сделать что-то вроде

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

так как a и b возвращать указатель значения в этом контексте, это эквивалентно записи 3 = 4. В памяти нет ничего, что на самом деле магазинах адрес первого элемента в массиве; компилятор просто вычислить его на этапе перевода.


1. Это все взято из газеты развитие языка C

термин "lvalue типа массива" буквально относится к объекту массива как lvalue типа массива, т. е. объект массива в целом. Это lvalue не модифицируется в целом, так как нет никакой юридической операции, которая может изменить его в целом. Фактически, единственные операции, которые вы можете выполнить на lvalue типа массива: unary & (адрес), sizeof и неявное преобразование в тип указателя. Ни одна из этих операций не изменяет массив, поэтому объекты массива не являются поддающийся изменению.

a[i] не работает с lvalue типа массива. a[i] обозначает


неполный тип-это тип, который объявлен, но не определен, например struct Foo;.

вы всегда можете назначить индивидуальные массива элементов (предполагая, что они не const). Но вы не можете назначить что-то всему массиву.

C и c++ довольно запутаны в том, что что-то вроде int a[10] = {0, 1, 2, 3}; не является назначением, а инициализацией, хотя это выглядит в значительной степени как назначение.

Это нормально (инициализация):

int a[10] = {0, 1, 2, 3};

Это не работает в C/с++:

int a[10];
a = {0, 1, 2, 3};

предполагая, что a представляет собой массив ints,a[10] Не массив. Это int.

a = {0} будет незаконным.


помните, что значение массива на самом деле является адресом (указателем) его первого элемента. Этот адрес нельзя изменить. Так что

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

является незаконным.

это, конечно, не имеет ничего общего с изменением контент массива как в a[1] = 3