| Меню сайта |
|
 |
| Категории раздела |
|
 |
| Статистика |
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
 |
|
 | |  |
|
Указатели C++ для чайников Часть 1
Указатели в C++. Введение из песочницы
Хотелось бы с самого начала прояснить одну вещь — я не отношу себя к категории true-кодеров, сам учусь по специальности, не связанной с разработкой ПО, и это мой первый пост. Прошу судить по всей строгости. Итак, в свое время то ли по причине того, что я спал на лекциях, то ли я не особо вникал в эту тему, но у меня возникали некоторые сложности при работе с указателями в плюсах. Теперь же ни одна моя даже самая крохотнаябыдлокодерская программа не обходится без указателей. В данной статье я попытаюсь рассказать базовые вещи: что такое указатели, как с ними работать и где их можно применять. Повторюсь, изложенный ниже материал предназначен для новичков.
/* Ребят, в статье было найдено много ошибок. Спасибо тем людям, которые внесли свои замечания. В связи с этим — после прочтения статьи обязательно перечитайте комментарии */
1. Общие сведения
Итак, что же такое указатель? Указатель — это та же переменная, только инициализируется она не значением одного из множества типов данных в C++, а адресом, адресом некоторой переменной, которая была объявлена в коде ранее. Разберем на примере:
void main(){
int i_val = 7;
}
# Здесь ниже, конечно, я ребятки вам соврал. Переменная i_val — статическая, она явно будет размещена в стеке. В куче место выделяется под динамические объекты. Это важные вещи! Но в данном контексте, я, сделав сам себе замечание, позволю оставить себе все как есть, так что сильно не ругайтесь.
Мы объявили переменную типа int и здесь же ее проинициализировали. Что же произойдет при компиляции программы? В оперативной памяти, в куче, будет выделено свободное место такого размера, что там можно будет беспрепятственно разместить значение нашей переменной i_val. Переменная займет некоторый участок памяти, разместившись в нескольких ячейках в зависимости от своего типа; учитывая, что каждая такая ячейка имеет адрес, мы можем узнать диапазон адресов, в пределах которого разместилось значение переменной. В данном случае, при работе с указателями нам нужен лишь один адрес — адрес первой ячейки, именно он и послужит значением, которым мы проинициализируем указатель. Итак:
void main(){
int i_val = 7;
int* i_ptr = &i_val;
void* v_ptr = (int *)&i_val
}
Используя унарную операцию взятия адреса &, мы извлекаем адрес переменной i_val и присваиваем ее указателю. Здесь стоит обратить внимание на следующие вещи:
- Тип, используемый при объявлении указателя в точности должен соответствовать типу переменной, адрес которой мы присваиваем указателю.
- В качестве типа, который используется при объявлении указателя, можно выбрать тип void. Но в этом случае при инициализации указателя придется приводить его к типу переменной, на которую он указывает.
- Не следует путать оператор взятия адреса со ссылкой на некоторое значение, которое так же визуально отображается символом &.
Теперь, когда мы имеем указатель на переменную i_val мы можем оперировать ее значением не только непосредственно с помощью самой переменной, но и с помощью указателя на нее. Посмотрим, как это работает на простом примере:
#include <iostream>
using namespace std;
void main(){
int i_val = 7;
int* i_ptr = &i_val;
cout << i_val << endl;
cout << *i_ptr << endl;
}
- Здесь все ясно — используем саму переменную.
- Во втором случае — мы обращаемся к значению переменной i_val через указатель. Но, как вы заметили, мы не просто используем имя указателя — здесь используется операция разыменования: она позволяет перейти от адреса к значению.
В предыдущем примере был организован только вывод значения переменной на экран. Можем ли мы непосредственно через указатель оперировать с значением переменной, на которую он указывает? Да, конечно, для этого они и реализованы (однако, не только для этого — но об этом чуть позже). Все, что нужно — сделать разыменование указателя:
(*i_ptr)++;
2. Массивы
Сразу перейдем к примеру — рассмотрим статичный одномерный массив определенной длинны и инициализируем его элементы:
void main(){
const int size = 7;
int i_array[size];
for (int i = 0; i != size; i++){
i_array[i] = i;
}
}
А теперь будем обращаться к элементам массива, используя указатели:
int* arr_ptr = i_array;
for (int i = 0; i != size; i++){
cout << *(arr_ptr + i) << endl;
}
Что здесь происходит: мы инициализируем указатель arr_ptr адресом начала массива i_array. Затем, в цикле мы выводим элементы, обращаясь к каждому с помощью начального адреса и смещения. То есть:
*(arr_ptr + 0)
это тот же самый нулевой элемент, смещение нулевое (i = 0),
*(arr_ptr + 1)
— первый (i = 1), и так далее.
Однако, здесь возникает естественный вопрос — почему присваивая указателю адрес начала массива, мы не используем операцию взятия адреса? Ответ прост — использование идентификатора массива без указания квадратных скобок эквивалентно указанию адреса его первого элемента. Тот же самый пример, только в указатель «явно» занесем адрес первого элемента массива:
int* arr_ptr_null = &i_array[0];
for (int i = 0; i != size; i++){
cout << *(arr_ptr_null + i) << endl;
}
Пройдем по элементам с конца массива:
int* arr_ptr_end = &i_array[size - 1];
for (int i = 0; i != size; i++){
cout << *(arr_ptr_end - i) << endl;
}
Замечания:
- Запись array[i] эквивалентна записи *(array + i). Никто не запрещает использовать их комбинированно: (array + i)[1] — в этом случае смещение идет на i, и еще на единичку. Однако, в данном случае перед выражением (array + i) ставить * не нужно. Наличие скобок это «компенсирует.
- Следите за вашими „перемещениями“ по элементам массива — особенно если вам захочется использовать
порнографический такой метод записи, как (array + i)[j].
|
| Категория: Статьи о программировании | Добавил: TypicalUbuntu (23.11.2015)
|
| Просмотров: 497
| Рейтинг: 0.0/0 |
|
|
 | |  |
|
| Вход на сайт |
|
 |
| Поиск |
|
 |
|