В С++ указательная переменная объявляется с помощью звёздочки:
C++
1
int*x;//переменная x есть указатель на int;
Указательня переменная хранит адрес памяти, поэтому любое ваше обращение к указательной переменной предполагает, что вы нуждаетесь в адресе памяти.
C++
1
2
int*x;//указательная переменная "x" хранит адрес целого числа
cout<<x;//На экран выводится какой-то адрес памяти
Чтобы использовать прописанное в адресе (хранимом в указательной переменной) значение, нужно использовать операцию разыменования указательной переменной.
C++
1
2
int*x;//указательная переменная "x" хранит адрес целого числа
cout<<*x;//На экран выводится значение, прописанное в адресе, хранимом в указательной переменной "x"
Мне мало нравится термин разыменование, для объяснений я подменю его своим авторским термином инспекция. Таким образом использование значения, хранимого внутри сохранённого в указателе адреса, я буду называть инспекцией указателя, но технически верно использовать именно термин разыменование. Фразу "значение, прописанное внутри адреса, хранимого в указателе" писать долго, и большое число слов мешает ясности вашей мысли, поэтому я использую собственное слово прописант; Имейте это в виду.
Для того, чтобы что-то могло прописаться на адресе, сам адрес должен быть обязательно узаконен. Если адрес вне закона, то ничего хорошего не жди:
C++
1
2
3
4
5
6
7
8
9
10
11
//clang Указатели Листинг #1
#include <iostream>
usingnamespacestd;
intmain(){
int*p;//адрес есть, но не узаконен
*p=200;//на неузаконенный адрес приходит жить прописант 200
cout<<*p;//прописант вне закона
}
Вы, как власть имущий над программой, должны для прописанта использовать адрес узаконенный или программой, или непосредственно вами. Когда создаётся переменная любого типа, в тот момент программа самостоятельно выделяет участок памяти под хранение. Любой участок памяти, выделенный самой программой, следует считать законным участком. Таким образом под указательную переменную выделяется отдельный участок памяти. Адрес памяти, используемый переменной, можно узнавать с помощью амперсанда, применяемого к имени переменной, поэтому вы легко можете подсмотреть адрес указательной переменной:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//clang Указатели Листинг #2
#include <iostream>
usingnamespacestd;
intmain(){
int*p;//для переменной "p" адрес узаконен
//для прописанта "p" адрес неузаконен
cout<<&p << '\n';//Вывод узаконенного адреса под указательну переменную "p"
cout<<p<<'\n';//Вывод адреса "p"-прописанта, адрес вне закона
cout<<*p<<'\n';//Инспекция "p"-прописанта
cout<<&*p << '\n';//то же, что cout << p, т. е. Вывод адреса прописанта "p", адрес вне закона
cin.get();
}
Поскольку указательная переменная является переменной как таковой, у неё можно узнавать её непосредственный адрес. Важно не запутаться в адресе указательной переменной и адресе прописанта. Чтобы понять, как определить законность и незаконность используемого прописантом адреса, вам нужно уловить суть того, что исходный код является обычным текстом, и какие адреса будут использоваться полученной из текста кода программой, компилятор никак предвидеть не может. Под использование внутренних для создаваемой программы переменных компилятор умеет выделять память самостоятельно. В листинге #2 прописант никак не фигурирует и компилятор о нём просто не знает, компилятор знает, что есть указательная переменная p, которая куда-то указывает, и всё. Куда она указывает, зачем она туда указывает.., компилятор мало интересует, в задачи компилятора входит только выделение памяти под саму указательную переменную, всё остальное находится не в юрисдикции компилятора. Под любую собственную программе переменную компилятор выделяет память, поэтому адреса всех переменных, объявленных в программе, считаются законными. "p"-прописант, который согласно листингу #2, является обычным целым числом (тип указательной переменной), адрес этому прописанту никак не выделялся ни программой, ни компилятором. Тут такое дело, что просто используется какой-то адрес из вообще всего адресного пространства: на этом адресе может находится любой компонент извне доступности программы, поэтому программа легко падает.
Запомните! При работе с адресам памяти, программой каждый используемый адрес должен подхватываться и закрепляться за программой. Такой процесс образно можно назвать отжатием программой у операционной системы памяти. Не отжал — не твоё.
Если используются уже отжатые программой у операционной системы участки памяти, то прописанты, использующие те адреса, никаких проблем программе не доставляют. Но если у операционной системы адреса не отжимались, т. е. в коде программы не происходило объявления переменных, то прописанта на такие адреса не пропишешь, необходимо, опять же, отжимать адреса у операционной системы под нужды создаваемой вами программы. Насильное отжатие адресов памяти возможно с помощью операции new. Если вы не регистрировали поселенца в программе, т. е. вы никак не именовали отдельного прописанта, то в случае особой в нём нужды следует использовать операцию new. Тут важно не запутаться. Или вы используете кусочек памяти уже используемый программой, или вы прописываете адрес для нового прописанта. Любой прописанный вами адрес следует убирать при ненужности прописантовой прописки.
Всегда, когда используется new, должно быть delete, завершающее потребность адреса прописантом.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//clang Указатели Листинг #3
#include <iostream>
usingnamespacestd;
intmain(){
int*p1;
p1=newint;//Отжатие памяти у операционной системы для нужд программы
delete p1;//Возврат памяти операционной системе
int*p2=newint;//Отжатие памяти у операционной системы для нужд программы
delete p2;//Возврат памяти операционной системе
}
Отжимается у операционной системы, конечно же, свободная память. Самый распространённый вопрос всех, наверное, новичков: "Когда нужно использовать delete?". Чтобы понимать нужный момент использования delete, вам необходимо осознавать области действия определённых участков кода. На совсем простом уровне подобной областью является самый обычный блок программы, заключаемый вовнутрь фигурных скобок. Код, заключнный вовнутрь фигурных скобок, как бы не существует вне пределов скобок. С этим новички много раз сталкиваются, поэтому подобное ограничение должно быть вполне себе ясным. Такой же принцип работает и для пары new…delete. Т. е. пара new…delete оказывается непрямым аналогом фигурных скобок, разница лишь в том, что если забыть закрыть фигурную скобку, то код компилироваться не станет, а если забыть delete, то компилятор скомпилирует программу. Если вы поймёте этот момент с областями, то вам проще будет понимать стандартную отговорку, что delete используется, когда указатель уже не нужен. Эта аналогия с фигурными скобками мне представляется очень хорошей и подходящей именно новичкам.
Из-за невнимательности программиста отжатая память может быть не отдана операционной системе, хотя прописант может быть уже совсем не нужен. Когда прописантов мало, это не особо заметно, но то, чего мало, легко может разрастись со скоростью пложения китайцев. Например, при многократном выполнени какого-то участка кода. Во многих примерах, описываемых на просторах интернета, я наблюдал пренебрежение выпиской прописанта. Из-за такой, казалось бы, мелочи, многие новички не научаются правильной выписке. Запомните, что если прописали прописанту адрес, то нужна обязательная выписка прописанта с того адреса по достижению ненужности ему (прописанту) жилья.
Указательная переменная может иметь в себе прописанта, являющегося указательной переменной. Это потому что у любой указательной переменной всегда есть свой собственный адрес.
C++
1
2
3
4
5
6
7
int**p1;//В указательной переменной "p"
//живёт прописант, хранящий
//прописанта, являющегося целым числом int
int***p2;//В указательной переменной "p"
//живёт прописант, в котором живёт прописант,
//в котором живёт прописант, хранящий целое число int
Сложно вопринимать подобную объяснительную матрёшку человеку. Слишком легко запутаться. Говорят проще:
p1 — это указатель на указатель
p2 — это указатель на указатель на указатель
Меня напрягает немного объясняться словами "указатели на на указатели на указатели, указывающие на что-то". Система прописант в случае многозвёздочности работает плохо, поэтому использую систему "Матрёшка".
В случае необходимости прописки прописанта в двухуровневой матрёшке память, в самом простом виде, выделяется следующим образом:
C++
1
2
3
4
5
6
7
8
9
10
11
intmain(){
int**p=newint*;
*p=newint;
**p=200;
cout<<**p;
delete*p;
deletep;
}
Сколько бы не было звёздочек, конечное значение всегда будет не указательного типа. Это конечное значение сейчас я буду называть прописантом. Каждая указательная переменная, являющаяся прописантом в указательной переменной, будет называться матрёшкой.
Уровень вложенности матрёшек равен количеству звёздочек. Матрёшка, хранящая внутри себя матрёшку, требует для хранимой в себе матрёшки прописки. В противном случае вложенная матрёшка оказывается нелегалом. Весь процесс происходит по тому же принципу, который описывался ранее. При прописке адреса для каждой новой матрёшки следует указывать количество матрёшек и тип конечного значения, главного прописанта. При уровне вложенности в три матрёшки, простейший случай выделения памяти выглядит следующим образом:
C++
1
2
3
4
5
6
7
8
9
10
11
12
intmain(){
int***p=newint**;//три матрёшки
*p=newint*;//две матрёшки
**p=newint;//последняя матрёшка, в которой адрес главного прописанта
***p=200;//обращение к главному прописанту
cout<<***p<<'\n';
delete**p;//выписка первой изнутри матрёшки
delete*p;//выписка второй изнутри матрёшки
deletep;//выписка третьей (главной) матрёшки
Вы обязательно разберётесь. Примеры на двух и трёх матрёшках показывают общий принцип выделения памяти и очистки выделенной памяти. Очень важно чистить память (делать выписку) в обратном выделению порядке. Т. е. выделение памяти происходит как бы с самой большой матрёшки, хранящей внутри себя все остальные, а очистка памяти выполняется изнутри.
Указатели на указатели хороши тем, что могут использоваться в качестве многомерных массивов. Количество звёздочек может означать мерность массива. Одна звёздочка — одномерный массив, две — двумерный, три — трёхмерный и т. д. Как правило, слишком многомерные массивы никто не использует из-за сложности их обработки. Сложна не столько их обработка, сколько чтение их в пишемом коде. Чтобы использовать указатели на указатели как массивы, память нужно выделять для массивов, т. е. следует использовать операцию индексации []. В остальном всё как с матрёшками:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Указатели #Листинг #4
intmain(){
int**p=newint*[3];//Всего две матрёшки
//При этом главная матрёшка на одном уровне вложенности хранит три матрёшки сразу
p[0]=newint[10];//для каждой отдельной матрёшки нужно выделять память
p[1]=newint[5];//при этом каждая из матрёшек может хранить в себе какое-то ещё матрёшек
p[2]=newint;//или хранить главного прописанта
deletep[2];//каждую матрёшку нужно выписывать, вычёркивая адреса
delete[]p[1];
delete[]p[0];
delete[]p;//включая главную
}
Так, правда, никто обычно не пишет. Но этот код, листинг #4, вам может стать полезным сведением способа выписки. Матрёшки у нас как бы оказываются массивами. Сами посудите: некоторая матрёшка хранит внутри себя однотипные объекты: матрёшки. На одном уровне вложенности в главной (самой большой) матрёшке оказалось три матрёшки. Фактически это массив из трёх элементов. При этом каждая из трёх матрёшек способна хранить внутри себя других матрёшек, причём каждая из трёх может иметь собственную ёмкость, определяющую степень вложенности. Каждая из трёх матрёшек фактически оказывается массивом, исключением можно назвать матрёшку, хранящую внутри себя главного прописанта. (Примечание. Хотя ничто не запрещает существовать массиву в 1 элемент, в листинге #4 такой элемент не использован как массив в одно значение, а использован как просто значение, p[2]]). Поскольку все матрёшки легко приводятся к типу массив из скольки-то элементов, то выписка этих матрёшек из памяти происходит спообом выписки массивов, т. е. с применением операции []. Одна из матрёшек массивом не оказалась, поэтому для выписки не используется операция [], но при этом вытаскивание главного прописанта происходит при помощи обращения к элементу массива по индексу. Не спутайте пустые квадратные скобки со скобками, в которых указывается число.
В основном, при использовании многомерных массивов, не делают так, чтобы матрёшки содержали внутри себя разные количества матрёшек. Человеческому воображению достаточно легко представлять двумерный массив в виде таблицы, трёхмерный массив в иде куба, но чем больше мерность, тем труднее человеческому мозгу. Все эти указателевы матрёшки массивами вовсе не являются, но общее их представление в конечном итоге выливается в представление массивоподобной структуры, поэтому очень много действий, применяемых к массиву, возможно использовать к указателевым матрёшкам.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//clang Указатели Листинг #5
#include <iostream>
#include <iomanip>
usingstd::cout;
intmain(){
intROW=3;
intCOL=5;
int**p=newint*[ROW];//Уровень вложенности 2, ибо две звёздочки
//прописка для внутренней матрёшки
//внутри которой ROW прописантов
/*
каждый прописант из самой большой матрёшки, т.е. из **p
сам по себе матрёшка: *p
каждой матрёшке лежащей в **p выписывается адрес
*/
for(inti=0;i<ROW;i++){
p[i]=newint[COL];//матрёшке *p даётся привелегия содержать COL прописантов
}
intcount=0;//прописант обычное целое int
/*Заселение матрёшек*/
for(inti=0;i<ROW;i++){
for(intj=0;j<COL;j++){
p[i][j]=count++;//прописали в матрёшку значение
}
}
/*Инспекиця матрёшек*/
for(inti=0;i<ROW;i++){
for(intj=0;j<COL;j++){
if(j)cout.width(5);//эта строчка для красоты вывода
cout<<p[i][j]<<' ';//проинспектировали матрёшку
}
cout<<'\n';
}
/*Уничтожение матрёшек*/
for(inti=0;i<ROW;i++){
delete[]p[i];
}
delete[]p;//включая главную
}
Русские матрёшки настолько суровы, что объясняют массивы, формируемые указателями, лучше самих массивов.
Не забывайте только, что схожесть указателей и массивов велика, но массивы всё-таки не указатели, а указатели на указатели просто в конечном представлении оказываются массивоподобной структурой данных.
Указатели и массивы очень тесно связаны между собой. Из-за очень большого сходства новички слишком часто не различают указатели и массивы. Возможно создавать массивы указателей, а возможно массивы массивов. В объяснении будет использоваться двумерное представление.
Указатель на указатель мы с вами разбирали только что на примере матрёшек. Массив, в отличие от указателя является жирной структурой данных, требующей некоторого числа своих внутренних составляющих, при этом все внутренние составляющие оказываются сущностями одного типа.
C++
1
2
3
int**p;//Матрёшки, уровень вложенности 2
int*p[10]//Массив из 10 матрёшек, каждая матрёшка в массиве с уровнем вложенности 1
intp[5][10];//Массив из 5 массивов, каждый массив из пятёрки является массивом в 10 элементов (хранит 10 целых чисел типа int)
Каждая матрёшка требует себе прописки в памяти. Об этом уже много раз было написано: или используется уже отжатое у операционной системы программой пространство, или используется ручное отжатие у операционной системы адресного пространства под нужы программы операцией new.
Массив не может хранить в себе массивы, которые отличаются количеством своих элементов, т. е. все массивы в массиве массивов имеют одинаковую ёмкость. Матрёшки не имеют такого ограничения. Как было показано ранее, на одном уровне вложенности может быть несколько матрёшек, каждая из которых различается количеством внутренных своих матрёшек. Но матрёшки требуют выделения себе адресов. Ещё у матрёшек есть преимущество, что ёмкости у них как таковой нет, вы можете отжимать кусочки памяти у операционной системы, а вот с массивами дела похуже, всю доступную в ОС память С++ массив использовать не может.
В современных компиляторах, которые поддерживают стандарт С++11 и выше, возможно инициализировать массивы указателей следующим способом:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//clang Указатели Листинг #5
#include <iostream>
usingstd::cout;
intmain(){
int*S[]={
newint[3]{0,1,2},newint[5]{9,8,7,6,5}//В старых компиляторах не работает
//для S память не отжималась с помощью new, поэтому delete не нужно
//кроме того, delete []S сейчас - будет двойное delete, а это запрещено, если указатель указывает не вникуда
}
Почему произойдёт двойное удаление? Потому что указательная переменная указывает на начало чего-либо. В массиве, например, она указывает на первый элемент массива. Поскольку в цикле произошла выписка всех элементов массива, то и первый элемент попал под раздачу, следующая его выписка оказывается повторной выпиской.
В более старых компиляторах фигурные скобки в качестве инициализаторов не дружили с операцией new, из-за чего массив указателей возможно было инициализировать только заранее подготовленными указателями или указателями на символьные строки:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Borland C++ 3.1 Массив указателей Листинг #6.1
//clang Указатели Листинг #5
#include <iostream.h>
#include <conio.h>
intmain(){
clrscr();
int*x=newint(10);//Указатели возможно подготовить заранее
int*y=newint(20);
int*z=newint(30);
int*arr_int[]={x,y,z};//Инициализировать ими массив указателей
cout<<*arr_int[0]<<'\n';//Вывод прописантов на экран
Всё работает по одному принципу. Просто в случае с символьными строками приходит особенность символьных строк: признак окончания строки. Из-за того, что у символьных строк есть такой признак, любую символьную строку можно вывести на экран зараз. Не нужно делать специальных обходов. При посимвольном манипулировании всё возращается на круги своя.
Массив может содержать внутри себя множество указателей, ибо массив изначально является некоторой структурой хранения конечного множества элементов. Указательная переменная не может хранить никакиих множеств, это очень важное отличие. Зато указательная переменная может хранить адрес любой сложной структуры данных. В случае с массивами имя массива умеет неявно приводиться к указательной переменной, поэтому можно присваивать в указатели массивы.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
intmain(){
intarr1[10];
// int **p1 = arr1; //Ошибка
int*p1_=arr1;//OK
intarr2[10][20];
// int *p2 = arr2; //Ошибка
// int **p2_ = arr2; //Ошибка
int(*p2__)[20]=arr2;//OK
intarr3[10][20][30];
int(*ptr)[20][30]=arr3;//OK
}
Как вы можете сами увидеть, нельзя в матрёшки второго уровня и выше сохранять многомерные массивы. Причина того только-лишь в том, что массивы не указатели, хотя имена массивов часто неявно преобразовывываются в указательные переменные. Указательная переменная хранит единственное значение — адрес памяти. Массив хранит набор значений.
Цель, которую я преследую, научить вас различать указатели и массивы. Если вы будете говорить правильно, то будете иметь определённый вес в кругах специалистов. Не нужно называть указатели массивами.
Основной причиной невозможности использования указателя на указатель в качестве объекта нацеленного на многомерные массивы является сам способ хранения многомерных массивов в памяти компьютера. Многомерны такие массивы только для нас, для компьютера память линейна, отчего все многомерные массивы в действительности являются одномерными. Если мы можем представить, например, двумерный массив в виде таблицы, как-то обозначить строки: первая, вторая, третья, — то компьютер не различает что где есть. Для компьютера есть начало массива и размер массива, и только одна как бы строчка. Из-за этого все подмассивы многомерных массивов не воспринимаются как отдельные сущности. Все подмассивы являются единым целым, представляющим весь свой массив. Подмассивы они только для нас. Из-за этого, в свою очередь, нигде не хранится адресов на эти подмассивы. А если нет адресов, то и в указатели сохранить нечего.
C++
1
2
3
4
5
//Листинг #z
intmain(){
intarr[10][20][30];
int(*p)[20][30]=p;
}
У массива arr[10][20][30] берётся имя arr, оно неявно приводится к указателю на первый элемент массива, т. е. полученная указательная переменная хранит адрес arr[0]. Именно адрес.
Первые квадратные скобки из arr[10][20][30] вообще отбрасываются. Они указателям не нужны. Не бывает указательных массивов. Бывают только указательные переменные и массивы указателей.
В следствие неявного преобразования имени массива arr к указательной переменной p, у нас на данный момент главенствует не массив arr, а указательная переменная, указывающая на массив [20][30]. Т. е. вне нашего ведома была создана указательная переменная, прописантом которой может стать любой массив из 20 элементов. При этом каждый из 20 прописантов оказывается массивом, хранящим 30 значений.
Преобразованием затронуты только первые скобки: arr[10][20][30], остальные все скобки силу свою не теряют и остаются как есть.
Поскольку неявное преобразование уже выполнялось, вторично оно не происходит: в С++ есть ограничение на количество неявных преобразований за раз.
Есть указательная переменная, указывающая на массив, к указательной переменной иных типов такие переменные неявно не приводятся.
C++
1
2
3
4
5
6
7
8
9
10
intmain(){
inta=10;
longb=20;
b=a;//В обычных условиях может происходить неявное преобразование типов
int*p1;
long*p2;
p2=p1;//Но в случае указательных переменных всё немного строже
}
Поэтому у вас получается ограничение в одну указательную переменную, которую возможно связать с массивом. При этом указательная переменная дотаточно строга, требуется указывать правильный тип данных, для который она сможет хранить адреса. Весь хвост оставшихся квадратных скобок оказывается идентификатором правильного типа, адреса на которые умеет сохранять выведенный из имени массива указатель.
Хорошо переваривается в сознании указатель при понимании того, что указательная переменная хранит адрес на начало чего-либо. Сохранённый внутри указательной переменной адрес всегда можно назвать началом чего-то: начало массива, начало функции, начало объекта класса, начало списка и т. п.
Возвращаемся к основной теме.
Напоминаю, термин указатель я подменяю термином "Матрёшка". Матрёшка может съесть другую матрёшку, может функцию, может съесть массив. Но есть и такие элементы, которые матрёшка съесть не может:
Нельзя создать указатель на ссылку
Нельзя создать указатель на битовое поле
Нельзя создать указатель на класс, можно только на объект класса
Указатель на функцию-член класса ведёт себя не как указатель
Нельзя создать указатель на шаблон функции
У ссылки нет своего адреса, поэтому нельзя сохранить в указательную переменную саму ссылку. Взятие адреса у ссылочного имени даёт ложный эффект взятия адреса ссылки. На самом деле берётся адрес объекта, к которому ссылка привязана, а не адрес самой ссылки.
C++
1
2
3
4
5
6
7
8
inta=100;
int&x = a;//создание ссылки.
int*p=&x;//в указателе сохраняется адрес a, но не адрес x
//у x вообще нет адреса
cout<<&a << '\n';
cout<<p<<'\n';
Если адреса у некоторой сущности просто физически быть не может, то и сохранять в указатель нечего.
Чтобы различать ссылку и взятие адреса, разделяйте момент зарождения имени переменной и момент использования переменной. В момент зарождения имени знак амперсанда, приставляемый к имени, обозначает то, что имя переменной только-лишь ссылка, а в месте использований имён знак амперсанда, приставляемый к именам, обозначает взятие адреса.
27 комментариев на «“Указатели в C++ для начинающих. Массивы указателей. Указатели на указатели”»
а есть ли разница чему присваивать адрес памяти,разыменованному указателю или просто указателю? у меня в программе например:*ptr=&a;а вы пишете:ptr=&a;
Автор сайта отвечает:
Да. *ptr=&a; — В сам элемент с которым работает указатель записано новое значение равное адресу памяти. ptr=&a; — Указатель сменил свои позиции и ссылается на новый элемент, который расположен по адресу на который ссылается a
Виктория, думаю вам полезно будет посмотреть на код
void main(){
int a=100;
int *ptr;
*ptr=&
a; //cout<<*ptr<<endl;
}
Маленький и простой, но не должен выполнится.
*ptr=&a; //Тут ошибка. Это вы в int пытаетесь присвоить адрес памяти
ptr=&a; //Ошибок нет. Так вы сообщаете — меняя указатель — менять переменную int aДело в двойственности указателя. Надеюсь не запутал
После обьявления указателю обязательно должно присваиваться какое-либо значение. Если заранее неизвестно ,какой адрес должен храниться в указателе ,ему присваивается значение 0. Неинициализированные указатели в дальнейшем могут стать причиной больших неприятностей.
Автор сайта отвечает:
Инициализацию после объявления предполагают правила хорошего тона программирования, но это не обозначает обязательность. Абсолютно также как с обычными переменными.
И лучше, действительно, инициализировать.
int x=20; //Переменная x = 20
int *ptr; //ptr есть указатель на int
void main()
{
clrscr();
//Выводим на экран различные значения
cout<<"ptr = "<<ptr<<endl; //Указатель ptr= Адрес памяти
cout<<"*ptr"<<*ptr<<endl; //Разыменованный указатель ptr= Значение по адресу
getch();
return;
}
Для начала нужно присвоить указателю хоть какое-либо значение , нужно дописать перед cout — ptr = &x;
5 int *ptr; //ptr есть указатель на int
— неинициализированный указатель !
Инициализация указателей :
Использование неинициализированных указателей чрезвычайно опасно. Вы можете легко перезаписать произвольную область памяти через неинициализированный указатель. Полученный ущерб при этом зависит лишь от степени вашего везения, поэтому инициализировать указатели — более чем хорошая идея. Очень легко инициализировать указатель адресом переменной, которая уже определена.
pnumber = &numberl; // Сохранить адрес в указателе
Конечно, объявляя указатель, вы можете решить не инициализировать его адресом определенной переменной. В этом случае его можно инициализировать указателем, эквивалентным нулю. Для этого в Visual C++ предусмотрен символ NULL, который уже определен как 0, поэтому указатель можно объявлять и инициализировать с помощью следующего оператора вместо того, что вы видели ранее:
int* pnumber = NULL; // Указатель, не указывающий ни на что
Автор сайта отвечает:
Да. неицинализорованный указатель и указывает он на тип int/
конечно неправильно так говорить, но так его читают.
по-русски он читается, как: Указатель, указывающий на ячейку памяти, где тип значения чего-то там лежащего должен быть int, но, блин, это долго и длинно.
из-за того-что в ячейке лежит фиг знает что, и фиг знает какой у этого фига тип, указатель просто может психануть и уронить программу., но может и не уронить.
там в вашей цитате слово «произвольную», а «совершенно случайную» лучше подходит.
А вы знаете почему предпочтение отдают инициализацией NULL, а не нулем?
и еще, чтоб все знали NULL не символ
NULL в языках программирования Си и C++ — макрос
Символ — это один какой-то единый знак, а тут целое слово из 4 символов.
Не могу понять, почему совмещение с инициализацией выглядит так: тип *ptr=имя переменной. Но ведь указатель под звёздочкой есть величина переменной.
Автор сайта отвечает:
Потому что это не присваивание, а инициализация указателя. Во время инициализации не происходит разыменования, а уже после инициализации звездочка перед переменной является указанием к разыменованию. Так уж решили.
int main(){
int ***p = new int**; //Три матрёшки.
**p = new int*; //две матрёшки
*p = new int; //последняя матрёшка, в которой адрес главного прописанта
***p = 200; //обращение к главному прописанту
cout << ***p;
delete ***p; //выписка первой (изнутри) матрёшки
delete *p; //выписка второй (изнутри) матрёшки
delete p; //выписка третьей (главной) матрёшки
Ниже вы писали: Т. е. выделение памяти происходит как бы с самой большой матрёшки, хранящей внутри себя все остальные, а очистка памяти выполняется изнутри.
Операции delete выполняются в неправильном порядке, или я что-то не правильно понял?
Объясните пожалуйста, я не могу разобраться со звездочками в данном коде:
intmain(){
int***p=newint**;//три матрёшки *p=newint*;//две матрёшки **p=newint;//последняя матрёшка, в которой адрес главного прописанта ***p=200;//обращение к главному прописанту cout<<***p<<‘\n’; delete**p;//выписка первой изнутри матрёшки delete*p;//выписка второй изнутри матрёшки deletep;//выписка третьей (главной) матрёшки
Почему это не выглядит так (комментарии пишу так как я понимаю, но раз не работает то что-то понимаю не так):
intmain(){
int***p=newint**;//здесь мы выделяем память для большой матрешки, в которой будет храниться указатель на указатель на int **p=newint*;//выделяем память для указателя на указатель на int *p=newint;//и выделение последней матрёшке, адреса для прописанта
А у Вас получается, что в одноуровневую матрёшку (вмещающую только конечное значение), происходит попытка записать указатель int*, а не неуказательное значение int
C++
1
2
int***p=newint**;//вкладываем в трёхуровневую матрёшку двухуровневую матрёшку
**p=newint*;//конечное значение, которое не указатель, нельзя использовать как указатель
Но это полбеды. Поскольку память для самого конечного значения не выделялась, то даже при попытке записать туда значение, ждём беды:
C++
1
2
int***p=newint**;//вкладываем в трёхуровневую матрёшку двухуровневую матрёшку
**p=newint(100);//конечное значение, которое не указатель, записываем туда число == запись значения в сырой указатель
Нужно быть строго-последовательным. И при выделении памяти идти от, так сказать, наибольшего указателя к меньшему, а при очистке наоборот (при этой формуле: чем больше указатель, тем меньше звёзд).
Спасибо, разобрался!
Получается выделение памяти идет сначала от большой матрешки к средней, а от средней к малой и лишь потом выделение памяти для значения. Нельзя выделить память для значения создав только большую матрешку без создания средней и малой.
int*b=newint(a);//обращаем внимание на new, пробуем без
intww[4]={10,20,30,40};
int*arr[2]={b,ww+3};//4 - выход за границы массива
delete arr[0];// то же, что delete b
//delete arr[1]; // нельзя, потому что для arr[1] нет new
}
delete применятся только в паре с new.
Там, где new выделяется для одиночного значения, там delete, а там, где для массива, там delete []имя_массива:
C++
1
2
3
4
5
newintx;//не массив
deletex;
newintarr[10];//массив
delete[]arr;
Поскольку вы в своём примере не выделяете указателю участок памяти (не используете new), то delete использовать нельзя, а вы используете.
Чтобы использовать delete, нужно, чтобы у указателя был свой собственный выделенный участок, как показано уже в моём примере.
Кроме того, указатели внутри массива указателей в Вашем примере указывают на целые чисда int, а не на массивы int[], но вы пытаетесь чистить память массивов.
И конечно же, у Вас в примере имеет место выход за пределы массива: отсчёт индексов ведётся с нуля, т. е. если объявлено int arr[11], то последний вытаскиваемый элемент cout << arr[10]
я не могу понять как можно в массив указателей, к одному из указателей приписать сторонний обычный массив и потом вывести его(обычного массива) элементы на экран с помощью указателя
я думал, что когда вы пишите имя массива + какое то число- это запись массива целиком т.к. я спрашивал про это. А оказывается, что это только индекс массива?!
а есть ли разница чему присваивать адрес памяти,разыменованному указателю или просто указателю? у меня в программе например:*ptr=&a;а вы пишете:ptr=&a;
После обьявления указателю обязательно должно присваиваться какое-либо значение. Если заранее неизвестно ,какой адрес должен храниться в указателе ,ему присваивается значение 0. Неинициализированные указатели в дальнейшем могут стать причиной больших неприятностей.
подскажите пожалуйста книги где хорошо выложена эта тема. заранее спасибо.
В этом коде ошибка
Указателю нужно сначала присвоить хоть какое-то значение, нужно дописать перед cout строку — ptr = &x;
Ошибка в коде
#include
#include
int x=20; //Переменная x = 20
int *ptr; //ptr есть указатель на int
void main()
{
clrscr();
//Выводим на экран различные значения
cout<<"ptr = "<<ptr<<endl; //Указатель ptr= Адрес памяти
cout<<"*ptr"<<*ptr<<endl; //Разыменованный указатель ptr= Значение по адресу
getch();
return;
}
Для начала нужно присвоить указателю хоть какое-либо значение , нужно дописать перед cout — ptr = &x;
😈 😈 😈 😈 😈 😈 😈 😈
Здравствуйте!
Я никак не могу понять, что значит &p->name, это значит значение переменной name записать по адресу p? Надеюсь на подсказку
5 int *ptr; //ptr есть указатель на int
— неинициализированный указатель !
Инициализация указателей :
Использование неинициализированных указателей чрезвычайно опасно. Вы можете легко перезаписать произвольную область памяти через неинициализированный указатель. Полученный ущерб при этом зависит лишь от степени вашего везения, поэтому инициализировать указатели — более чем хорошая идея. Очень легко инициализировать указатель адресом переменной, которая уже определена.
pnumber = &numberl; // Сохранить адрес в указателе
Конечно, объявляя указатель, вы можете решить не инициализировать его адресом определенной переменной. В этом случае его можно инициализировать указателем, эквивалентным нулю. Для этого в Visual C++ предусмотрен символ NULL, который уже определен как 0, поэтому указатель можно объявлять и инициализировать с помощью следующего оператора вместо того, что вы видели ранее:
int* pnumber = NULL; // Указатель, не указывающий ни на что
(http://develab.narod.ru/cpp/189.htm)
так как указатель хранит адрес памяти, то попытка обращения к указателю вернет этот адрес. Если x объявлен как указатель, то
у меня ошибку выводит Ошибка 1 error C4700: использована неинициализированная локальная переменная “tt” c:\users\sррр\documents\visual studio 2013\projects\consoleapplication8\consoleapplication8\consoleapplication8.cpp 9 1 ConsoleApplication8
Где-то в коде у вас переменная tt
У меня в примерах такой переменной нету.
Нужно найти и присвоить ей начальное значение. int tt=0;
Не могу понять, почему совмещение с инициализацией выглядит так: тип *ptr=имя переменной. Но ведь указатель под звёздочкой есть величина переменной.
Всё классно. Разжёвано.. Спасибо
Ваш код:
Ниже вы писали: Т. е. выделение памяти происходит как бы с самой большой матрёшки, хранящей внутри себя все остальные, а очистка памяти выполняется изнутри.
Операции delete выполняются в неправильном порядке, или я что-то не правильно понял?
Заранее спасибо за ответ.
В этом моём коде, написанном Вами, мной допущена ошибка в порядке выделения памяти.
Удаляется не в неправильном, а в симметричном.
Объясните пожалуйста, я не могу разобраться со звездочками в данном коде:
int main(){
int ***p = new int**; //три матрёшки
*p = new int*; //две матрёшки
**p = new int; //последняя матрёшка, в которой адрес главного прописанта
***p = 200; //обращение к главному прописанту
cout << ***p << ‘\n’;
delete **p; //выписка первой изнутри матрёшки
delete *p; //выписка второй изнутри матрёшки
delete p; //выписка третьей (главной) матрёшки
Почему это не выглядит так (комментарии пишу так как я понимаю, но раз не работает то что-то понимаю не так):
Потому что с толку сбивает инициализация. Если объяснять словами, то всё сложно получается, просто посмотрите без инициализации:
А у Вас получается, что в одноуровневую матрёшку (вмещающую только конечное значение), происходит попытка записать указатель int*, а не неуказательное значение int
Но это полбеды. Поскольку память для самого конечного значения не выделялась, то даже при попытке записать туда значение, ждём беды:
Нужно быть строго-последовательным. И при выделении памяти идти от, так сказать, наибольшего указателя к меньшему, а при очистке наоборот (при этой формуле: чем больше указатель, тем меньше звёзд).
Спасибо, разобрался!
Получается выделение памяти идет сначала от большой матрешки к средней, а от средней к малой и лишь потом выделение памяти для значения. Нельзя выделить память для значения создав только большую матрешку без создания средней и малой.
Здравствуйте! подскажите почему ложится программа ( сразу выдает, что надо закрыть ее)
#include <iostream>
using namespace std;
int main(){
int a = 2;
int *b = &a;
int as[4] = { 2, 6, 44, 99 };
int *ww[2] = { b, &as[4] };
delete[]ww[0];
delete[]ww[1];
system(«pause»);
return 0;
}
и если можете, пишите синтаксис обясняемых команд, так лучше будет пониматься
delete применятся только в паре с new.
Там, где new выделяется для одиночного значения, там delete, а там, где для массива, там delete []имя_массива:
Поскольку вы в своём примере не выделяете указателю участок памяти (не используете new), то delete использовать нельзя, а вы используете.
Чтобы использовать delete, нужно, чтобы у указателя был свой собственный выделенный участок, как показано уже в моём примере.
Кроме того, указатели внутри массива указателей в Вашем примере указывают на целые чисда int, а не на массивы int[], но вы пытаетесь чистить память массивов.
И конечно же, у Вас в примере имеет место выход за пределы массива: отсчёт индексов ведётся с нуля, т. е. если объявлено int arr[11], то последний вытаскиваемый элемент cout << arr[10]
я не могу понять как можно в массив указателей, к одному из указателей приписать сторонний обычный массив и потом вывести его(обычного массива) элементы на экран с помощью указателя
Здравствуйте! Сделал по аналогии по 2 примеру как вы писали, но что то не правильно выводится:
#include <iostream>
using namespace std;
int main(){
int r[2] = { 44, 66 };
int e[3] = { 2, 5, 11 };
int *mas[2] = { r + 1, e + 2 };
int *t = mas[1];
cout << t[0] << endl; — при задании t[0] выводит последний элемент массива, а если написать t[-1] то выводит предпоследний элемент массива и т.д.
system(«pause»);
return 0;
}
В t[0] что лежит? mas[1]
В mas[1] что лежит? e+2 (т. е. e[2])
В e[2] что лежит? 11
В e2[-1] что лежит? e[2-1], т. е. e[1], т. е. 5
я думал, что когда вы пишите имя массива + какое то число- это запись массива целиком т.к. я спрашивал про это. А оказывается, что это только индекс массива?!
Это арифметика указателей.
Добавление числа к указателю или вычитание от указателя числа смещает указатель на некоторое число байт.