Для чего в с

Содержание

Для чего в с

Ещё у начинающих вызывает замешательство такая фигня:
int x=10;
int *a=&x;
*a=10;
И в первом и во-втором случае есть"*а".Но в первом случае присваивается адрес, а во-втором число. Что за нелепость? Что там себе думал Страуструп? И молодой программист попадает в ступор.
Всё очень просто. Первое выражение рассматривайте так
(int *) a=&x;
В этот момент "*" не имеет отношения к получению значения. Просто объявляется переменная "а",в которую записывается адрес переменной "х". "*" здесь говорит о том, что мы создаём указатель, а не простую переменную и всё.Такой синтаксис объявления указателей. Первое время, чтоб привыкнуть, вообще пишите так, в две строки:
int *a; //объявили указатель
a=&x;//записали в него адрес
int d=*a;//теперь d присвоили значение, которое находится в памяти по адресу, записаному в "а"

Зачем нужны указатели?

Oбъяcнитe, зaчeм нyжны yкaзaтeли? Heт, я пoнял пpинцип paбoты, нo нe пoнял oднoгo — ЗAЧEM? Зaчeм paбoтaть c фyнкциями-члeнaми, пepeмeнными пo ccылкe, ecли я мoгy paбoтaть c ними нaпpямyю?

Отслеживать
51.3k 194 194 золотых знака 56 56 серебряных знаков 231 231 бронзовый знак
задан 4 янв 2012 в 13:25
3,213 4 4 золотых знака 34 34 серебряных знака 49 49 бронзовых знаков
Если Вы не поняли "зачем", значит Вы не поняли указатели!
4 янв 2012 в 13:28

Посмотрите, например, man qsort (это очень полезная функция сортировки), подумайте как это можно запрограммировать и многое прояснится.

4 янв 2012 в 13:59

qsort реализуется и без указателей. Например в таких языках как lisp/haskell, где указателей (по крайней мере в явном виде) нет. На плюсах можно без указателей тоже сделать, но только памяти будет больше потребляться.

4 янв 2012 в 14:20
Какой смысл биться за лишние сто байт памяти?
4 янв 2012 в 15:20

На самом деле все намного проще, указатели нужны для того чтобы писать "волшебные" коды, быстрые и непанятные. + для того чтобы те кто не знает С++ думали что С++ это что-то неимоверно сложное 😀 Использовать х надо потому, что в они есть и твой код становится более непонятным. **** PS:Как мне показалось — объяснил на вашем нынешнем уровне **** PPS:не трольте коммент ибо коммент сам по себе троллинг 😀

4 янв 2012 в 15:35

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

"Если кто-то зажигает звезды, значит, это кому-нибудь нужно"

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

void func (int a) < a += 10; >. int i = 2; func(i); 

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

void func (int* a) < *a += 10; >. int i = 2; func(&i); 

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

Уже говорили о динамическом выделении памяти. Если еще про это не читали, то прочитаете. Там без указателей В ПРИНЦИПЕ не обойтись.

Это лишь несколько примеров. Чем глубже будете вникать в С++, тем больше об этом узнаете.

И еще. Не думайте, что программисты до Вас были дураками.

Отслеживать
user207618
ответ дан 4 янв 2012 в 18:56
23.8k 2 2 золотых знака 37 37 серебряных знаков 69 69 бронзовых знаков

  1. Для возврата нескольких значений из функции. В качестве аргумента передаётся указатель на переменную, функция записывает туда значение. Такой подход очень распространён в DirectX, OpenGL, Windows API и других библиотеках в стиле C. Для этого можно использовать и ссылки, но не рекомендуется, так как синтаксис передачи и возврата неотличим.
  2. Для хранения адреса динамически выделенной памяти. Она отличается от обычной тем, что программист сам регулирует время жизни объектов, и её больше (а размер стека всего порядка мегабайта). Если адрес будет потерян, то память нельзя будет ни использовать, ни освободить. Возникнет утечка памяти.
  3. C-строка представляет собой указатель на её первый символ.
  4. Для создания различных структур данных: связанных списков, деревья и т. д.
  5. Для передачи аргумента в функцию без копирования (и вызова конструктора для объектов), которое может оказаться долгим для сложных объектов. Правда, здесь лучше использовать константные ссылки.

Таким образом, примений указателей очень много.

Отслеживать

@Xyanight, вспомните, что массивы в Си представляются указателями (даже не в случае параметров). Например: int a[10], *b = a+5; . printf ("%d\n",b[0]); // распечатает значение a[5] Вообще указатели в Си позволяют писать более обобщенный код.

4 янв 2012 в 16:46
В качестве аргумента передаётся указатель на переменную, функция записывает туда значение.
27 июл 2015 в 14:05

А зачем нужны почтовые адреса? Можно же общаться сразу с людьми. Но иногда приходится поручать доставку письма почтальону, которому надо сначала сказать, куда конкретно он должен доставить письмо, так как один и тот же почтальон должен уметь единообразно для адресанта доставлять письма всем адресатам. Как? Лезть в его днк нельзя, нельзя его даже переучивать. Указатель – это такая величина, в которой можно хранить адрес. А ссылка – это нечто, ведущее себя как указатель, но имеющее при этом синтаксис сразу целевой величины, которую мы по этому адресу ищем. То есть поле на конверте с адресом Пети, притворяющееся самим Петей. На это поле на конверте можно наорать вместо Пети, а обидится Петя. По указателю же можно только нанять хулигана, поручив ему наорать на Петю. Совсем же по-другому выглядит, а результат тот же. В одном случае мы голос посрвали, в другом – явно послали посредника. Но по указателю можно послать: почтальона, шпиона, хулигана и киллера, а по ссылке – только наорать на адрес на конверте, спокойно ему что-то сказать или поглазеть прямо на Петин стол, но киллеры даже не поймут, чего от них хотят, а сами мы убивать не умеем.

Отслеживать
ответ дан 8 ноя 2022 в 13:12
Тарас Атавин Тарас Атавин
204 1 1 серебряный знак 9 9 бронзовых знаков

Чтобы выделять большое количество памяти. Советую прочитать про стек и кучу (heap). На стеке при большом объеме данных будет происходить переполнение.

Отслеживать
ответ дан 4 янв 2012 в 14:03
221 1 1 серебряный знак 10 10 бронзовых знаков

Вот именно! Автору вопроса нужно смотреть в сторону размерных и ссылочных типов данных, как они устроены, какие отличия, где хранятся, зачем их разделили и тд. К чему тут тролинг.

4 янв 2012 в 15:56
Нeт, я пoнял пpинцип paбoты, нo нe пoнял oднoгo — ЗAЧEM Малеха противоречивый бред? Не находите?
4 янв 2012 в 16:00
Согласен — автор не разобрался в данной теме!
4 янв 2012 в 16:13
@AlexWindHope ну значит плохо объясняли
4 янв 2012 в 16:14

public и private предполагает что код может быть использовн кем-то еще, кто может залезть своими руками куда не надо и все поломать.

Для чего в С++ нужна звездочка вот такая *?

Для чего в С++ нужна звездочка вот такая *? Например int *a; ну и про другие применения расскажите.
Объясняйте как можно подробней и на пальцах до меня долго доходит.

Лучший ответ

Это указатель на адрес

int b = 11
int *a = &b;

// *a сейчас 11, a — адрес указывающий на перемпнную b

b = 15;
// *a сейчас так же 15

*a = 100;
// b сейчас 100

// А вот так вы скорее всего получите Exception
a = 12345;
*a = 12;

// Так как первым действием задали адрес, который не принадлежит стеку вашего процесса

Тут нужно читать и пробовать )) По другому никак

Остальные ответы

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

Читай и разбирайся. Это в двух словах не расскажешь.
Если разбирёшся с "этой звёздочкой",можешь считать, что ты в С/С++ перешёл Рубикон.

int x=10;
int *a=&x; //в "а" записали адрес переменной "х"

Для чего в с

*a=10; //(разыменование) равно значению переменной "x" или содержимому ячейки, на которую указывает "а"
int *pa=&a;//(взятие адреса) создаём новый указатель, в который записываем адресс "а"
a=&b; //теперь в "а" хочем записать адрес переменной "в",но отсос, такой переменной ещё нет
int b=20; //создадим переменную "в"
*a=b; //значение, имеющееся по адресу, записанному в "а" изменим на "в",т. е. на 20.Теперь х=20

Ещё у начинающих вызывает замешательство такая фигня:
int x=10;
int *a=&x;
*a=10;
И в первом и во-втором случае есть"*а".Но в первом случае присваивается адрес, а во-втором число. Что за нелепость? Что там себе думал Страуструп? И молодой программист попадает в ступор.
Всё очень просто. Первое выражение рассматривайте так
(int *) a=&x;
В этот момент "*" не имеет отношения к получению значения. Просто объявляется переменная "а",в которую записывается адрес переменной "х". "*" здесь говорит о том, что мы создаём указатель, а не простую переменную и всё.Такой синтаксис объявления указателей. Первое время, чтоб привыкнуть, вообще пишите так, в две строки:
int *a; //объявили указатель
a=&x;//записали в него адрес
int d=*a;//теперь d присвоили значение, которое находится в памяти по адресу, записаному в "а"

х==10;
a==0x33787974(или что-то похожее) ;
d==10;

Ко всем предыдущим остается только добавить, что кроме указателя, * также означает умножение в формулах.

* используется по-разному. зависит от места применения.

* оператор умножения
int a, b,c;
c = a * b; // оператор умножения a и b
с *= а; // оператор умножения с присваиванием, равносильно c = c * a;

* в определении типа переменной
int a, *b, **c;
a — переменная типа int,
b — переменная типа указатель на int
c — переменная типа указатель на указатель на int
указатель- это переменная, содержащая адрес другой переменной

* оператор разыменования указателя.
Если переменная-указатель содержит адрес другой переменной, то чтобы использовать переменную на которую "смотрит" указатель, указатель нужно разыменовать.

int a, *b;
b = &a;// инициализация указателя, теперь b смотрит на a;
*b = 20;// переменной на которую смотрит b (т. е. — a) присвоить значение 20
int c = *b; // c присвоить значение переменной, на которую смотрит b

Указатели, ссылки и массивы в C и C++: точки над i

В этом посте я постараюсь окончательно разобрать такие тонкие понятия в C и C++, как указатели, ссылки и массивы. В частности, я отвечу на вопрос, так являются массивы C указателями или нет.

Обозначения и предположения

  • Я буду предполагать, что читатель понимает, что, например, в C++ есть ссылки, а в C — нет, поэтому я не буду постоянно напоминать, о каком именно языке (C/C++ или именно C++) я сейчас говорю, читатель поймёт это из контекста;
  • Также, я предполагаю, что читатель уже знает C и C++ на базовом уровне и знает, к примеру, синтаксис объявления ссылки. В этом посте я буду заниматься именно дотошным разбором мелочей;
  • Буду обозначать типы так, как выглядело бы объявление переменной TYPE соответствующего типа. Например, тип «массив длины 2 int’ов» я буду обозначать как int TYPE[2] ;
  • Я буду предполагать, что мы в основном имеем дело с обычными типами данных, такими как int TYPE , int *TYPE и т. д., для которых операции =, &, * и другие не переопределены и обозначают обычные вещи;
  • «Объект» всегда будет означать «всё, что не ссылка», а не «экземпляр класса»;
  • Везде, за исключением специально оговоренных случаев, подразумеваются C89 и C++98.

Указатели и ссылки

Указатели. Что такое указатели, я рассказывать не буду. 🙂 Будем считать, что вы это знаете. Напомню лишь следующие вещи (все примеры кода предполагаются находящимися внутри какой-нибудь функции, например, main):

int x; int *y = &x; // От любой переменной можно взять адрес при помощи операции взятия адреса "&". Эта операция возвращает указатель int z = *y; // Указатель можно разыменовать при помощи операции разыменовывания "*". Это операция возвращает тот объект, на который указывает указатель 

Также напомню следующее: char — это всегда ровно один байт и во всех стандартах C и C++ sizeof (char) == 1 (но при этом стандарты не гарантируют, что в байте содержится именно 8 бит :)). Далее, если прибавить к указателю на какой-нибудь тип T число, то реальное численное значение этого указателя увеличится на это число, умноженное на sizeof (T) . Т. е. если p имеет тип T *TYPE , то p + 3 эквивалентно (T *)((char *)p + 3 * sizeof (T)) . Аналогичные соображения относятся и к вычитанию.

Для чего нужен язык C++

Ссылки. Теперь по поводу ссылок. Ссылки — это то же самое, что и указатели, но с другим синтаксисом и некоторыми другими важными отличиями, о которых речь пойдёт дальше. Следующий код ничем не отличается от предыдущего, за исключением того, что в нём фигурируют ссылки вместо указателей:

int x; int &y = x; int z = y; 

Если слева от знака присваивания стоит ссылка, то нет никакого способа понять, хотим мы присвоить самой ссылке или объекту, на который она ссылается. Поэтому такое присваивание всегда присваивает объекту, а не ссылке. Но это не относится к инициализации ссылки: инициализируется, разумеется, сама ссылка. Поэтому после инициализации ссылки нет никакого способа изменить её саму, т. е. ссылка всегда постоянна (но не её объект).

Lvalue. Те выражения, которым можно присваивать, называются lvalue в C, C++ и многих других языках (это сокращение от «left value», т. е. слева от знака равенства). Остальные выражения называются rvalue. Имена переменных очевидным образом являются lvalue, но не только они. Выражения a[i + 2] , some_struct.some_field , *ptr , *(ptr + 3) — тоже lvalue.

Удивительный факт состоит в том, что ссылки и lvalue — это в каком-то смысле одно и то же. Давайте порассуждаем. Что такое lvalue? Это нечто, чему можно присвоить. Т. е. это некое фиксированное место в памяти, куда можно что-то положить. Т. е. адрес. Т. е. указатель или ссылка (как мы уже знаем, указатели и ссылки — это два синтаксически разных способа в C++ выразить понятие адреса). Причём скорее ссылка, чем указатель, т. к. ссылку можно поместить слева от знака равенства и это будет означать присваивание объекту, на который указывает ссылка. Значит, lvalue — это ссылка.

А что такое ссылка? Это один из синтаксисов для адреса, т. е., опять-таки, чего-то, куда можно класть. И ссылку можно ставить слева от знака равенства. Значит, ссылка — это lvalue.

Окей, но ведь (почти любая) переменная тоже может быть слева от знака равенства. Значит, (такая) переменная — ссылка? Почти. Выражение, представляющее собой переменную — ссылка.

Иными словами, допустим, мы объявили int x . Теперь x — это переменная типа int TYPE и никакого другого. Это int и всё тут. Но если я теперь пишу x + 2 или x = 3 , то в этих выражениях подвыражение x имеет тип int &TYPE . Потому что иначе этот x ничем не отличался бы от, скажем, 10, и ему (как и десятке) нельзя было бы ничего присвоить.

Этот принцип («выражение, являющееся переменной — ссылка») — моя выдумка. Т. е. ни в каком учебнике, стандарте и т. д. я этот принцип не видел. Тем не менее, он многое упрощает и его удобно считать верным. Если бы я реализовывал компилятор, я бы просто считал там переменные в выражениях ссылками, и, вполне возможно, именно так и предполагается в реальных компиляторах.

Более того, удобно считать, что особый тип данных для lvalue (т. е. ссылка) существует даже и в C. Именно так мы и будет дальше предполагать. Просто понятие ссылки нельзя выразить синтаксически в C, ссылку нельзя объявить.

Принцип «любое lvalue — ссылка» — тоже моя выдумка. А вот принцип «любая ссылка — lvalue» — вполне законный, общепризнанный принцип (разумеется, ссылка должна быть ссылкой на изменяемый объект, и этот объект должен допускать присваивание).

Теперь, с учётом наших соглашений, сформулируем строго правила работы со ссылками: если объявлено, скажем, int x , то теперь выражение x имеет тип int &TYPE . Если теперь это выражение (или любое другое выражение типа ссылка) стоит слева от знака равенства, то оно используется именно как ссылка, практически во всех остальных случаях (например, в ситуации x + 2 ) x автоматически конвертируется в тип int TYPE (ещё одной операцией, рядом с которой ссылка не конвертируется в свой объект, является &, как мы увидим далее). Слева от знака равенства может стоять только ссылка. Инициализировать (неконстантную) ссылку может только ссылка.

Операции * и &. Наши соглашения позволяют по-новому взглянуть на операции * и &. Теперь становится понятно следующее: операция * может применяться только к указателю (конкретно это было всегда известно) и она возвращает ссылку на тот же тип. & применяется всегда к ссылке и возвращает указатель того же типа. Таким образом, * и & превращают указатели и ссылки друг в друга. Т. е. по сути они вообще ничего не делают и лишь заменяют сущности одного синтаксиса на сущности другого! Таким образом, & вообще-то не совсем правильно называть операцией взятия адреса: она может быть применена лишь к уже существующему адресу, просто она меняет синтаксическое воплощение этого адреса.

Замечу, что указатели и ссылки объявляются как int *x и int &x . Таким образом, принцип «объявление подсказывает использование» лишний раз подтверждается: объявление указателя напоминает, как превратить его в ссылку, а объявление ссылки — наоборот.

Что пишут на языке программирования C#

Также замечу, что &*EXPR (здесь EXPR — это произвольное выражение, не обязательно один идентификатор) эквивалентно EXPR всегда, когда имеет смысл (т. е. всегда, когда EXPR — указатель), а *&EXPR тоже эквивалентно EXPR всегда, когда имеет смысл (т. е. когда EXPR — ссылка).

Массивы

Итак, есть такой тип данных — массив. Определяются массивы, например, так:

int x[5]; 

Выражение в квадратных скобках должно быть непременно константой времени компиляции в C89 и C++98. При этом в квадратных скобках должно стоять число, пустые квадратные скобки не допускаются.

Подобно тому, как все локальные переменные (напомню, мы предполагаем, что все примеры кода находятся внутри функций) находятся на стеке, массивы тоже находятся на стеке. Т. е. приведённый код привёл к выделению прямо на стеке огромного блока памяти размером 5 * sizeof (int) , в котором целиком размещается наш массив. Не нужно думать, что этот код объявил некий указатель, который указывает на память, размещённую где-то там далеко, в куче. Нет, мы объявили массив, самый настоящий. Здесь, на стеке.

Чему будет равно sizeof (x) ? Разумеется, оно будет равно размеру нашего массива, т. е. 5 * sizeof (int) . Если мы пишем

struct foo < int a[5]; int b; >; 

то, опять-таки, место для массива будет целиком выделяться прямо внутри структуры, и sizeof от этой структуры будет это подтверждать.

От массива можно взять адрес ( &x ), и это будет самый настоящий указатель на то место, где этот массив расположен. Тип у выражения &x , как легко понять, будет int (*TYPE)[5] . В начале массива размещён его нулевой элемент, поэтому адрес самого массива и адрес его нулевого элемента численно совпадают. Т. е. &x и &(x[0]) численно равны (тут я лихо написал выражение &(x[0]) , на самом деле в нём не всё так просто, к этому мы ещё вернёмся). Но эти выражения имеют разный тип — int (*TYPE)[5] и int *TYPE , поэтому сравнить их при помощи == не получится. Но можно применить трюк с void * : следующее выражение будет истинным: (void *)&x == (void *)&(x[0]) .

Хорошо, будем считать, я вас убедил, что массив — это именно массив, а не что-нибудь ещё. Откуда тогда берётся вся эта путаница между указателями и массивами? Дело в том, что имя массива почти при любых операциях преобразуется в указатель на его нулевой элемент.

Итак, мы объявили int x[5] . Если мы теперь пишем x + 0 , то это преобразует наш x (который имел тип int TYPE[5] , или, более точно, int (&TYPE)[5] ) в &(x[0]) , т. е. в указатель на нулевой элемент массива x. Теперь наш x имеет тип int *TYPE .

Конвертирование имени массива в void * или применение к нему == тоже приводит к предварительному преобразованию этого имени в указатель на первый элемент, поэтому:

&x == x // ошибка компиляции, разные типы: int (*TYPE)[5] и int *TYPE (void *)&x == (void *)x // истина x == x + 0 // истина x == &(x[0]) // истина 
  • x[2] эквивалентно *(x + 2)
  • x + 2 относится к тем операциям, при которых имя массива преобразуется в указатель на его первый элемент, поэтому это происходит
  • Далее, в соответствии с моими объяснениями выше, x + 2 эквивалентно (int *)((char *)x + 2 * sizeof (int)) , т. е. x + 2 означает «сдвинуть указатель x на два int’а»
  • Наконец, от результата берётся операция разыменования и мы извлекаем тот объект, который размещён по этому сдвинутому указателю

Типы у участвовавших выражений следующие:

x // int (&TYPE)[5], после преобразования типа: int *TYPE x + 2 // int *TYPE *(x + 2) // int &TYPE x[2] // int &TYPE 

Также замечу, что слева от квадратных скобок необязательно должен стоять именно массив, там может быть любой указатель. Например, можно написать (x + 2)[3] , и это будет эквивалентно x[5] . Ещё замечу, что *a и a[0] всегда эквивалентны, как в случае, когда a — массив, так и когда a — указатель.

Теперь, как я и обещал, я возвращаюсь к &(x[0]) . Теперь ясно, что в этом выражении сперва x преобразуется в указатель, затем к этому указателю в соответствии с вышеприведённым алгоритмом применяется [0] и в результате получается значение типа int &TYPE , и наконец, при помощи & оно преобразуется к типу int *TYPE . Поэтому, объяснять при помощи этого сложного выражения (внутри которого уже выполняется преобразование массива к указателю) немного более простое понятие преобразования массива к указателю — это был немного мухлёж.

А теперь вопрос на засыпку: что такое &x + 1 ? Что ж, &x — это указатель на весь массив целиком, + 1 приводит к шагу на весь этот массив. Т. е. &x + 1 — это (int (*)[5])((char *)&x + sizeof (int [5])) , т. е. (int (*)[5])((char *)&x + 5 * sizeof (int)) (здесь int (*)[5] — это int (*TYPE)[5] ). Итак, &x + 1 численно равно x + 5 , а не x + 1 , как можно было бы подумать. Да, в результате мы указываем на память, которая находится за пределами массива (сразу после последнего элемента), но кого это волнует? Ведь в C всё равно не проверяется выход за границы массива. Также, заметим, что выражение *(&x + 1) == x + 5 истинно. Ещё его можно записать вот так: (&x)[1] == x + 5 . Также будет истинным *((&x)[1]) == x[5] , или, что тоже самое, (&x)[1][0] == x[5] (если мы, конечно, не схватим segmentation fault за попытку обращения за пределы нашей памяти :)).

Массив нельзя передать как аргумент в функцию. Если вы напишите int x[2] или int x[] в заголовке функции, то это будет эквивалентно int *x и в функцию всегда будет передаваться указатель (sizeof от переданной переменной будет таким, как у указателя). При этом размер массива, указанный в заголовке будет игнорироваться. Вы запросто можете указать в заголовке int x[2] и передать туда массив длины 3.

Однако, в C++ существует способ передать в функцию ссылку на массив:

void f (int (&x)[5]) < // sizeof (x) здесь равен 5 * sizeof (int) >int main (void) < int x[5]; f (x); // OK f (x + 0); // Нельзя int y[7]; f (y); // Нельзя, не тот размер >

При такой передаче вы всё равно передаёте лишь ссылку, а не массив, т. е. массив не копируется. Но всё же вы получаете несколько отличий по сравнению с обычной передачей указателя. Передаётся ссылка на массив. Вместо неё нельзя передать указатель. Нужно передать именно массив указанного размера. Внутри функции ссылка на массив будет вести себя именно как ссылка на массив, например, у неё будет sizeof как у массива.

И что самое интересное, эту передачу можно использовать так:

// Вычисляет длину массива template size_t len (t (&a)[n])

Похожим образом реализована функция std::end в C++11 для массивов.

«Указатель на массив». Строго говоря, «указатель на массив» — это именно указатель на массив и ничто другое. Иными словами:

int (*a)[2]; // Это указатель на массив. Самый настоящий. Он имеет тип int (*TYPE)[2] int b[2]; int *c = b; // Это не указатель на массив. Это просто указатель. Указатель на первый элемент некоего массива int *d = new int[4]; // И это не указатель на массив. Это указатель 

Однако, иногда под фразой «указатель на массив» неформально понимают указатель на область памяти, в которой размещён массив, даже если тип у этого указателя неподходящий. В соответствии с таким неформальным пониманием c и d (и b + 0 ) — это указатели на массивы.

Многомерные массивы. Если объявлено int x[5][7] , то x — это не массив длины 5 неких указателей, указывающих куда-то далеко. Нет, x теперь — это единый монолитный блок размером 5 x 7, размещённый на стеке. sizeof (x) равен 5 * 7 * sizeof (int) . Элементы располагаются в памяти так: x[0][0] , x[0][1] , x[0][2] , x[0][3] , x[0][4] , x[0][5] , x[0][6] , x[1][0] и так далее. Когда мы пишем x[0][0] , события развиваются так:

x // int (&TYPE)[5][7], после преобразования: int (*TYPE)[7] x[0] // int (&TYPE)[7], после преобразования: int *TYPE x[0][0] // int &TYPE 

То же самое относится к **x . Замечу, что в выражениях, скажем, x[0][0] + 3 и **x + 3 в реальности извлечение из памяти происходит только один раз (несмотря на наличие двух звёздочек), в момент преобразования окончательной ссылки типа int &TYPE просто в int TYPE . Т. е. если бы мы взглянули на ассемблерный код, который генерируется из выражения **x + 3 , мы бы в нём увидели, что операция извлечения данных из памяти выполняется там только один раз. **x + 3 можно ещё по-другому записать как *(int *)x + 3 .

А теперь посмотрим на такую ситуацию:

int **y = new int *[5]; for (int i = 0; i != 5; ++i)

Что теперь есть y? y — это указатель на массив (в неформальном смысле!) указателей на массивы (опять-таки, в неформальном смысле). Нигде здесь не появляется единый блок размера 5 x 7, есть 5 блоков размера 7 * sizeof (int) , которые могут находиться далеко друг от друга. Что есть y[0][0] ?

y // int **&TYPE y[0] // int *&TYPE y[0][0] // int &TYPE 

Теперь, когда мы пишем y[0][0] + 3 , извлечение из памяти происходит два раза: извлечение из массива y и последующее извлечение из массива y[0] , который может находиться далеко от массива y. Причина этого в том, что здесь не происходит преобразования имени массива в указатель на его первый элемент, в отличие от примера с многомерным массивом x. Поэтому **y + 3 здесь не эквивалентен *(int *)y + 3 .

Объясню ещё разок. x[2][3] эквивалентно *(*(x + 2) + 3) . И y[2][3] эквивалентно *(*(y + 2) + 3) . Но в первом случае наша задача найти «третий элемент во втором ряду» в едином блоке размера 5 x 7 (разумеется, элементы нумеруются с нуля, поэтому этот третий элемент будет в некотором смысле четвёртым :)). Компилятор вычисляет, что на самом деле нужный элемент находится на 2 * 7 + 3 -м месте в этом блоке и извлекает его. Т. е. x[2][3] здесь эквивалентно ((int *)x)[2 * 7 + 3] , или, что то же самое, *((int *)x + 2 * 7 + 3) . Во втором случае сперва извлекает 2-й элемент в массиве y, а затем 3-й элемент в полученном массиве.

В первом случае, когда мы делаем x + 2 , мы сдвигаемся сразу на 2 * sizeof (int [7]) , т. е. на 2 * 7 * sizeof (int) . Во втором случае, y + 2 — это сдвиг на 2 * sizeof (int *) .

В первом случае (void *)x и (void *)*x (и (void *)&x !) — это один и тот же указатель, во втором — это не так.

Похожие статьи

  • Для с с что это

    Вы можете сказать, что вы не так сильно заботитесь о скорости. Вам нужно обрабатывать относительно небольшое количество транзакций, и у вас нет серьезных…

  • Для чего нужна программа яндекс

    Если значок не отображается, или вы хотите убрать навязчивую иконку с наушниками, воспользуйтесь следующей инструкцией: Чем отличается Яндекс Браузер от…

  • Для чего обновлять драйвера

    В большинстве случаев, люди думают, когда какое то устройства не работает должным образом, существует проблема с самим устройством. Часто дело в…

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Что будем искать? Например,Человек

Мы в социальных сетях