Понедельник, 25.09.2017, 23:22
Хранилще данных
Приветствую Вас Гость | RSS
Главная | Каталог статей | Регистрация | Вход
Меню сайта

Категории раздела
Статьи о программировании [4]
Игры [0]
Новости техники и IT [0]

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Главная » Статьи » Статьи о программировании

Указатели C++ для чайников Часть 1

Указатели в C++. Введение из песочницы

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


/* Ребят, в статье было найдено много ошибок. Спасибо тем людям, которые внесли свои замечания. В связи с этим — после прочтения статьи обязательно перечитайте комментарии */

 

1. Общие сведения


Итак, что же такое указатель? Указатель — это та же переменная, только инициализируется она не значением одного из множества типов данных в C++, а адресом, адресом некоторой переменной, которая была объявлена в коде ранее. Разберем на примере:
 
void main(){
 int i_val = 7;
}

# Здесь ниже, конечно, я ребятки вам соврал. Переменная i_val — статическая, она явно будет размещена в стеке. В куче место выделяется под динамические объекты. Это важные вещи! Но в данном контексте, я, сделав сам себе замечание, позволю оставить себе все как есть, так что сильно не ругайтесь.

Мы объявили переменную типа int и здесь же ее проинициализировали. Что же произойдет при компиляции программы? В оперативной памяти, в куче, будет выделено свободное место такого размера, что там можно будет беспрепятственно разместить значение нашей переменной i_val. Переменная займет некоторый участок памяти, разместившись в нескольких ячейках в зависимости от своего типа; учитывая, что каждая такая ячейка имеет адрес, мы можем узнать диапазон адресов, в пределах которого разместилось значение переменной. В данном случае, при работе с указателями нам нужен лишь один адрес — адрес первой ячейки, именно он и послужит значением, которым мы проинициализируем указатель. Итак:

 
void main(){
 // 1
 int i_val = 7;
 int* i_ptr = &i_val;
 // 2
 void* v_ptr = (int *)&i_val
}

Используя унарную операцию взятия адреса &, мы извлекаем адрес переменной i_val и присваиваем ее указателю. Здесь стоит обратить внимание на следующие вещи:
  1. Тип, используемый при объявлении указателя в точности должен соответствовать типу переменной, адрес которой мы присваиваем указателю.
  2. В качестве типа, который используется при объявлении указателя, можно выбрать тип void. Но в этом случае при инициализации указателя придется приводить его к типу переменной, на которую он указывает.
  3. Не следует путать оператор взятия адреса со ссылкой на некоторое значение, которое так же визуально отображается символом &.

Теперь, когда мы имеем указатель на переменную i_val мы можем оперировать ее значением не только непосредственно с помощью самой переменной, но и с помощью указателя на нее. Посмотрим, как это работает на простом примере:
 
#include <iostream>

using namespace std;

void main(){
 int i_val = 7;
 int* i_ptr = &i_val;
 
 // выведем на экран значение переменной i_val
 cout << i_val << endl; // C1
 cout << *i_ptr << endl; // C2
}
 
  1. Здесь все ясно — используем саму переменную.
  2. Во втором случае — мы обращаемся к значению переменной i_val через указатель. Но, как вы заметили, мы не просто используем имя указателя — здесь используется операция разыменования: она позволяет перейти от адреса к значению.

В предыдущем примере был организован только вывод значения переменной на экран. Можем ли мы непосредственно через указатель оперировать с значением переменной, на которую он указывает? Да, конечно, для этого они и реализованы (однако, не только для этого — но об этом чуть позже). Все, что нужно — сделать разыменование указателя: 
 
 (*i_ptr)++; // результат эквивалентен операции инкремента самой переменной: i_val++
 // т.е. в данном случае в i_val сейчас хранится значение не 7, а 8.
 

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;
 }
Замечания:
  1. Запись array[i] эквивалентна записи *(array + i). Никто не запрещает использовать их комбинированно: (array + i)[1] — в этом случае смещение идет на i, и еще на единичку. Однако, в данном случае перед выражением (array + i) ставить * не нужно. Наличие скобок это «компенсирует.
  2. Следите за вашими „перемещениями“ по элементам массива — особенно если вам захочется использовать порнографический такой метод записи, как (array + i)[j].

 

Категория: Статьи о программировании | Добавил: TypicalUbuntu (23.11.2015)
Просмотров: 106 | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Вход на сайт

Поиск

Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz

  • Copyright MyCorp © 2017 uCoz