Какой тип имеет разыменованный указатель
Перейти к содержимому

Какой тип имеет разыменованный указатель

  • автор:

C++. Указатели. Часть 1. Общие понятия. Типы указателей. Управляемые и неуправляемые указатели. Указатели на функцию. Примеры использования

Указатели. Часть 1. Общие понятия. Типы указателей. Управляемые и неуправляемые указатели. Указатели на функцию. Примеры использования

Поиск на других ресурсах:

1. Что такое указатель? Определение указателя. Способы доступа к переменной

Язык C++ поддерживает два способа доступа к переменной: ссылка на переменную по ее имени и использование механизма указателей.

Одним из преимуществ языка C++ в сравнении с другими языками программирования есть использование указателей для доступа к переменной. Также, как известно, доступ к переменной можно получить по ее имени.

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

Указатель – это переменная, которая содержит адрес другой переменной в памяти. Например, если переменная a содержит адрес переменной b , то это означает, что переменная a указывает на переменную b .

2. Какие типы указателей поддерживаются в C++ ?

В Visual C++ ( CLR – Common Language Runtime ) поддерживаются три типа указателей:

  • управляемые указатели ( managed pointers );
  • неуправляемые указатели ( unmanaged pointers );
  • неуправляемые указатели на функции ( unmanaged function pointers ).
3. Какое отличие между управляемыми и неуправляемыми указателями?

Отличие между управляемыми и неуправляемыми указателями состоит в следующем:

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

В этом случае память есть нерегулируемой.

В случае регулируемой памяти управляемому указателю присвоить адрес за пределами исполнительной среды не удастся.

4. Что такое указатели ссылочного типа или управляемые указатели ( managed pointers )?

Управляемые указатели – это указатели ссылочного типа. Эти указатели передаются для аргументов, методов, которые передаются по ссылке. Эти указатели являются совместимыми со спецификацией Common Language Runtime ( CLR ). Управляемые указатели являются ссылками на объекты. Эти объекты размещаются в общей управляемой памяти, которая выделяется для программы в момент ее выполнения.

В таких указателях вместо символа ‘*’ применяется символ ‘^’ . Для выделения памяти под управляемый указатель используется утилита gcnew .

5. Пример использования управляемых указателей. Утилита gcnew и ключевое слово ref в Visual Studio C++

В управляемых указателях утилита gcnew формирует экземпляр некоторого объекта. Утилита gcnew выделяет память для экземпляра объекта и возвращает ссылку на этот объект.

Описание этого объекта должно начинаться с ключевого слова ref . Ключевое слово ref дает информацию о том, что описание есть ссылочного типа. Общая форма использования ключевого слова ref имеет вид:

ref описание;

После такого описания память под объект выделяется с помощью утилиты gcnew .

Пример. В примере демонстрируется:

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

Пусть за пределами описания класса описывается следующая структура:

ref struct BOOK // ref - обозначает, что структура есть ссылочного типа < System::String Title; // заголовок книги System::String Author; // фамилия автора книги int year; // год издания float price; // стоимость книги >;

Тогда в методах этого класса можно использовать структуру BOOK приблизительно следующим образом:

. BOOK ^B; // переменная B есть типа "управляемый указатель" B = gcnew BOOK; // выделение памяти под структуру // заполнение полей структуры B->Title = "Title of Book"; B->Author = "Author of Book"; B->year = 1998; B->price = 399.85f; .
6. Что такое неуправляемые указатели ( unmanaged pointers ) и неуправляемые указатели на функции ( unmanaged function pointers )?

Неуправляемые указатели – это традиционные указатели C/C++ . Они являются указателями на объекты в неуправляемом объеме памяти, которая выделяется для выполнения программы. Неуправляемые указатели не являются совместимыми со спецификацией CLR .

Неуправляемые указатели на функции – это указатели на функции, которые можно обрабатывать таким же образом как и неуправляемые указатели на объекты (данные).

Особенности использования неуправляемых указателей описываются в следующей теме .

7. Какая общая форма объявления неуправляемого указателя ( unmanaged pointer )? Пример

Общая форма объявления указателя:

тип_переменной * имя_указателя;
  • тип_переменной – это тип той переменной, на которую указывает указатель. Его еще называют базовый тип указателя;
  • имя_указателя – имя переменной-указателя.

Пример. Описание указателей, которые указывают на разные типы переменных.

int * pi; // указатель с именем pi на тип int float * pf; // указатель с именем pf на тип float
8. Неуправляемые указатели. Операция взятия адреса & . Пример

Для получения адреса переменной в памяти используется операция взятия адреса & . Эта операция есть унарной. Она возвращает адрес своего операнда в памяти.

Пример операции взятия адреса и использование указателя для доступа к переменной.

int * pi; // указатель с именем pi на тип int float * pf; // указатель с именем pf на тип float float f; int i; i = 8; pi = &i; // pi указывает на i *pi = 10; // i = 10 f = 2.93f; pf = &f; // pf указывает на f *pf = 8.85f; // f = 8.85

Рисунок 1 схематично показывает использование указателей для типа int .

C++ указатель int рисунок

Рисунок 1. Использование указателя на тип int

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

9. Пример использования неуправляемого указателя на функцию.

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

  • иметь одинаковое количество и типы аргументов;
  • возвращать значение одинакового типа.

При объявлении, указатель на функцию записывается следующим чином:

тип (* имя_функции) (список_параметров);
  • тип – тип значения, которое возвращается функцией.

Пример. Использование неуправляемого указателя на функцию для консольного приложения.

Описание и использование указателя на функцию с именем func_ptr , которая получает два целочисленных параметра x , y и возвращает значение типа double .

Пусть даны 2 функции, которые имеют одинаковое число параметров и возвращают одинаковое значение:

// функция, которая возводит число x в степень y double Power(int x, int y) < double res; res = System::Math::Pow((double)x, (double)y); return res; > // функция, которая делит x на y double Div(int x, int y) < double res; res = (double)x / (double)y; return res; >

В консольном приложении можно использовать данный указатель следующим образом:

// описать указатель на функцию, которая получает 2 параметры типа int double (*func_ptr)(int x, int y); double res; . // вызов функции возведения в степень func_ptr = Power; // указывает на Power - возведение в степень res = (*func_ptr)(4,3); // res = 64 // вызов функции деления чисел через указатель func_ptr = Div; // func_ptr указывает на Div res = (*func_ptr)(4,3); // res = 1.33333333333333

Такой способ использования указателей на функцию подходит только для консольных приложений.

10. Пример использования управляемого указателя на функцию в среде C++/CLI

Использование указателя на функцию в среде C++/CLI для приложений, созданных по шаблону Windows Forms . В этом случае нужно использовать так называемые делегаты. Делегаты поддерживаются в .NET Framework .

Пусть даны 2 функции, которые имеют одинаковое количество параметров и возвращают одинаковое значение:

// функция, которая возводит x в степень y public: double Power(int x, int y) < double res; res = System::Math::Pow((double)x, (double)y); return res; > // функция, которая делит x на y public: double Div(int x, int y) < double res; res = (double)x / (double)y; return res; >

Чтобы использовать делегат нужно сначала в некотором месте класса формы (или другого класса) дать следующее описание:

// описание делегата DelFun, который получает два параметра типа int // и возвращает значение типа double public: delegate double DelFun(int , int); .

После этого, в некотором методе (например, обработчике события клика на кнопке) можно использовать этот указатель следующим образом:

double result; DelFun ^ PFun = gcnew DelFun(this, &Lab1::Form1::Power); result = PFun(3,2); // result = 9 PFun = gcnew DelFun(this, &Lab1::Form1::Div); result = PFun(3,2); // result = 1.5

В вышеприведенном примере в строках

&Lab1::Form1::Power . &Lab1::Form1::Div

идентификаторы имеют следующее назначение:

  • Lab1 – название проекта;
  • Form1 – название главной формы приложения (имя переменной);
  • Power – название функции.
11. Какое отличие между указателем-переменной и указателем-константой?

Указатель-переменная (указатель) – это переменная, которая предназначена для сохранения адреса в памяти.

Указатель-константа – это значение адреса оперативной памяти.

Связанные темы
  • Указатели.Часть 2. Неуправляемые указатели. Операции над указателями. Указатель на типvoid. Выделениепамяти. Нулевой указатель. Операция взятия адреса &
  • Указатели. Часть 3. Неуправляемые указатели и массивы. Указатель на структуру. Указатель на класс
  • Указатели. Часть 4.Указатели и строки символов. Использование указателей при преобразовании строк
  • Указатели. Часть 5. Выделение памяти для указателя. Массивы указателей на базовые типы, функции, структуры, классы
  • Указатели. Часть 6. Составные native и managed типы данных. Управляемые указатели ( ^ ) в среде CLR . Выделение памяти. Квалификаторы ref и value . Управляемые указатели ( ^ ) на структуры и классы
  • Массивы. Часть 1.Определение массива. Одномерные массивы. Инициализация массива
  • Массивы. Часть 2. Двумерные массивы. Массивы строк. Многомерные массивы

Программирование: теория и практика

  • C# (176)
    • Практика (46)
      • MS Visual Studio 2010 (34)
      • MS Visual Studio 2017 (7)
      • MS Visual Studio 2019 (13)
      • ADO .NET (7)
      • Практика (31)
        • Borland C++ Builder 2007 (16)
        • MS Visual Studio 2010 (18)
        • Visual C++ (136)
        • Практика (6)
        • Теория (102)
        • Практика (1)
        • Теория (21)
        • Практика (1)
        • Теория (21)
        • Практика (20)
          • Delphi-7 (4)
          • Embarcadero RAD Studio 2010 (18)
          • Практика (4)
          • Теория (92)
          • SQL (5)
          • Паттерн Command. Реализация структуры для двух получателей 26 апреля, 2024
          • Patterns. Паттерн Command. Реализация структуры 30 ноября, 2023
          • Patterns. Паттерн Template Method 25 ноября, 2023
          • Patterns. Паттерн State (Состояние). Структура. Реализация на C++ 19 ноября, 2023
          • Patterns. Паттерн Abstract Factory. Решение задачи о составляющих компьютера 7 октября, 2023
          • Patterns. Паттерн Prototype. Реализация схемы на C++ 22 сентября, 2023
          • Patterns. Паттерн Composite (Компоновщик). Дерево. Реализация на C++ 18 сентября, 2023
          • Patterns. Паттерны. Сравнение паттернов Abstract Factory, Factory Method и Prototype 12 сентября, 2023
          • Patterns. Паттерн Factory Method (Фабричный метод). Реализация структуры, простейший случай 6 сентября, 2023
          • Patterns. Паттерн Bridge. Реализация структуры на C++ 6 сентября, 2023

          При использовании материалов сайта, ссылка на сайт обязательна.

          Какой тип имеет разыменованный указатель

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

          Присваивание адреса

          Указателю можно присвоить адрес объекта того же типа, либо значение другого указателя. Для получения адреса объекта используется операция & :

          int a ; int *pa ; // указатель pa хранит адрес переменной a

          При этом указатель и переменная должны иметь один и тот же тип, в данном случае это тип int.

          Разыменование указателя

          Операция разыменования указателя представляет выражение в виде *имя_указателя . Эта операция позволяет получить объект по адресу, который хранится в указателе.

          #include int main() < int a ; int *pa ; // хранит адрес переменной a std::cout #include int main() < int a ; int b ; int *pa ; // указатель на переменную a int *pb ; // указатель на переменную b std::cout pa: address=0x56347ffc5c value=10 pb: address=0x56347ffc58 value=2 pa: address=0x56347ffc58 value=2 b value=125

          Нулевые указатели

          Нулевой указатель (null pointer) - это указатель, который не указывает ни на какой объект. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес, то можно присвоить ему условное нулевое значение. Для определения нулевого указателя можно инициализировать указатель нулем или константой nullptr :

          int *p1; int *p2<>;

          Ссылки на указатели

          Так как ссылка не является объектом, то нельзя определить указатель на ссылку, однако можно определить ссылку на указатель. Через подобную ссылку можно изменять значение, на которое указывает указатель или изменять адрес самого указателя:

          #include int main() < int a ; int b ; int *p<>; // указатель int *&pRef

          ; // ссылка на указатель pRef = &a; // через ссылку указателю p присваивается адрес переменной a std::cout &:

          int a ; int *pa ; std::cout >, >=, , ,==, !=. Операции сравнения применяются только к указателям одного типа. Для сравнения используются номера адресов:

          #include int main() < int a ; int b ; int *pa ; int *pb ; if(pa > pb) std::cout

          Консольный вывод в моем случае:

          pa (0xa9da5ffdac) is greater than pb (0xa9da5ffda8)

          Приведение типов

          Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае следует выполнить операцию приведения типов с помощью операции (тип_указателя *) :

          #include int main() < char c ; char *pc ; // указатель на символ int *pd <(int *)pc>; // указатель на int void *pv ; // указатель на void std::cout std::cout

          • Глава 1. Введение в С++
            • Язык программирования С++
            • Первая программа на Windows. Компилятор g++
            • Первая программа на Windows. Компилятор Clang
            • Первая программа на Windows. Компилятор Microsoft Visual C++
            • Первая программа на Linux. Компилятор g++
            • Первая программа на MacOS. Компилятор Clang
            • Настройка параметров компиляции
            • Локализация и кириллица в консоли
            • Структура программы
            • Переменные
            • Типы данных
            • Константы
            • Ввод и вывод в консоли
            • using. Подключение пространств имен и определение псевдонимов
            • Арифметические операции
            • Статическая типизация и преобразования типов
            • Поразрядные операции
            • Операции присваивания
            • Условные выражения
            • Конструкция if-else и тернарный оператор
            • Конструкция switch-case
            • Циклы
            • Ссылки
            • Массивы
            • Многомерные массивы
            • Массивы символов
            • Введение в строки
            • Что такое указатели
            • Операции с указателями
            • Арифметика указателей
            • Константы и указатели
            • Указатели и массивы
            • Определение и объявление функций
            • Область видимости объектов
            • Параметры функции
            • Передача аргументов по значению и по ссылке
            • Константные параметры
            • Оператор return и возвращение результата
            • Указатели в параметрах функции
            • Массивы в параметрах функции
            • Параметры функции main
            • Возвращение указателей и ссылок
            • Перегрузка функций
            • Рекурсивные функции
            • Рекурсия на примере быстрой сортировки
            • Указатели на функции
            • Указатели на функции как параметры
            • Тип функции
            • Указатель на функцию как возвращаемое значение
            • Разделение программы на файлы
            • Внешние объекты
            • Динамические объекты
            • Динамические массивы
            • unique_ptr
            • shared_ptr
            • Определение классов
            • Конструкторы и инициализация объектов
            • Управление доступом. Инкапсуляция
            • Объявление и определение функций класса
            • Конструктор копирования
            • Константные объекты и функции
            • Ключевое слово this
            • Дружественные функции и классы
            • Статические члены класса
            • Деструктор
            • Структуры
            • Перечисления
            • Наследование
            • Управление доступом в базовых и производных классах
            • Скрытие функционала базового класса
            • Множественное наследование
            • Виртуальные функции и их переопределение
            • Преобразование типов
            • Динамическое преобразование
            • Особенности динамического связывания
            • Чистые виртуальные функции и абстрактные классы
            • Перегрузка операторов
            • Операторы преобразования типов
            • Оператор индексирования
            • Переопределение оператора присваивания
            • Пространства имен
            • Вложенные классы
            • Обработка исключений
            • Вложенные try-catch
            • Создание своих типов исключений
            • Тип exception
            • Типы исключений
            • Шаблоны функций
            • Шаблон класса
            • Специализация шаблона класса
            • Наследование и шаблоны классов
            • Типы контейнеров
            • Вектор
            • Итераторы
            • Операции с векторами
            • Array
            • List
            • Forward_list
            • Deque
            • Стек std::stack
            • Очередь std::queue
            • Очередь приоритетов std::priority_queue
            • Множества
            • Словарь std::map
            • Span
            • Определение строк
            • Строки с поддержкой Unicode
            • Преобразование типов и строки
            • Сравнение строк
            • Получение подстроки и проверка начала и конца строки
            • Поиск подстроки
            • Изменение строки
            • Операции с символами
            • Программа подсчета слов
            • Тип std:string_view
            • rvalue
            • Конструктор перемещения
            • Оператор присваивания с перемещением
            • Роль noexcept при перемещении
            • Объекты функций
            • Лямбда-выражения
            • Захват внешних значений в лямбда-выражениях
            • Шаблон std::function<>
            • Минимальный и максимальный элементы
            • Поиск элементов
            • Копирование элементов
            • Удаление элементов и идиома Remove-Erase Idiom
            • Сортировка
            • Представления. Фильтрация
            • Проекция данных
            • Пропуск элементов. drop_view и drop_while_view
            • Извлечение диапазона элементов. take_view и take_while_view
            • Цепочки представлений
            • Оператор requires
            • Концепты
            • Выражение requires
            • Ограничения типа для auto
            • Базовые типы для работы с потоками
            • Файловые потоки. Открытие и закрытие
            • Чтение и запись текстовых файлов
            • Переопределение операторов ввода и вывода
            • Математические константы и операции
            • Форматирование строк и функция format
            • std::optional
            • Управление ресурсами. Идиома RAII
            • Идиома копирования и замены
            • Идиома Move-and-Swap
            • Первая программа в Visual Studio
            • Первая программа в Qt Creator

            C++: Указатели

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

            Объявление указателя

            Указатель — это переменная, которая в качестве значения хранит адрес памяти.

            Переменные-указатели объявляются так же, как обычные переменные. Только в этом случае ставится звездочка между типом данных и именем переменной. Эта звездочка не является косвенным обращением. Это часть синтаксиса объявления указателя:

            int *i_ptr <>; // указатель на значение типа int double *d_ptr <>; // указатель на значение типа double int* i_ptr2 <>; // тоже допустимый синтаксис int * iPtr3<>; // тоже допустимый синтаксис (но не делайте так, это похоже на умножение) 

            Синтаксически C++ принимает звездочку рядом с типом данных, рядом с именем переменной или даже в середине.

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

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

            Указатель X (где X – какой-либо тип) — это обычно используемое сокращение для «указателя на X». Поэтому, когда мы говорим «указатель int», мы на самом деле имеем в виду «указатель на значение типа int».

            Хорошей практикой считается инициализировать указатель значением.

            Присвоение значения указателю

            Поскольку указатели содержат только адреса, когда мы присваиваем значение указателю, это значение должно быть адресом. Одна из самых распространенных вещей, которые делают с указателями, – это хранение в них адреса другой переменной.

            Чтобы получить адрес переменной, мы используем оператор адреса:

            #include int main() < int num < 5 >; int* ptr < &num >; // инициализируем ptr адресом переменной num std::cout 

            Эта программа создает следующий вывод:

            0x7ffc5d336fc8 0x7ffc5d336fc8

            ptr содержит адрес значения переменной, поэтому мы говорим, что ptr «указывает на» num .

            Тип указателя должен соответствовать типу переменной, на которую он указывает:

            int i_value < 5 >; double d_value < 7.0 >; int* i_ptr < &iValue >; // ok double* d_ptr < &dValue >; // ok i_ptr = &d_value; // ошибка 

            Тип double не может указывать на адрес переменной типа int . Следующее также некорректно:

            int* ptr < 5 >; 

            Это связано с тем, что указатели могут содержать только адреса, а целочисленный литерал 5 не имеет адреса памяти. Если попробовать сделать это, компилятор сообщит, что он не может преобразовать int в указатель int .

            Вопрос на засыпку: Можно ли инициализировать указатель, явно указав адрес ячейки памяти?

            double* d_ptr< 0x0012FF7C >; 

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

            Возвращение указателя оператором адреса

            Оператор адреса (&) не возвращает адрес своего операнда в виде литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого является производным от аргумента. Например, взятие адреса значения int вернет адрес в указателе int .

            Мы можем увидеть это в следующем примере:

            #include #include int main() < int num < 4 >; std::cout

            В Visual Studio этот код напечатал:

            При компиляции gcc вместо этого выводит "pi" («pointer to int», указатель на int).

            Одной из основных операций является получение значения переменной через указатель - косвенное обращение.

            Косвенное обращение через указатели

            У нас есть переменная-указатель, которая указывает на что-то. Значит, другая распространенная вещь, которую мы делаем с ней, — это косвенное обращение через указатель. Это нужно, чтобы получить значение того, на что он указывает.

            Косвенное обращение через указатель вычисляет содержимое адреса, на который он указывает:

            int value < 5 >; std::cout ; // ptr указывает на value std::cout  

            Эта программа создает следующий вывод:

            0x7ffcc0b6824c 5 0x7ffcc0b6824c 5

            Без типа при косвенном обращении через указатель он не знал бы, как интерпретировать содержимое, на которое он указывает. По этой же причине тип указателя и тип переменной, адрес которой ему присваивается, должны совпадать. Если бы это было не так, косвенное обращение через указатель неверно интерпретировало бы биты как другой тип.

            После присваивания значению указателя можно присвоить другое значение:

            int value1< 5 >; int value2< 7 >; int* ptr<>; ptr = &value1; // ptr указывает на value1 std::cout  

            Когда адрес переменной value присваивается указателю ptr , верно следующее:

            • ptr равен &value
            • *ptr обрабатывается так же, как value

            Поскольку *ptr обрабатывается так же, как value , можно присваивать ему значения, как если бы это была переменная value .

            Следующая программа напечатает 7:

            int value < 5 >; int* ptr < &value >; // ptr указывает на value *ptr = 7; // *ptr - это то же, что и value, которому присвоено 7 std::cout  

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

            Такой мощный механизм имеет свои минусы.

            Предупреждение о косвенном обращении через недействительные указатели

            Указатели в C++ по своей сути небезопасны. Неправильное использование указателей — один из лучших способов вывести приложение из строя.

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

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

            Следующая программа иллюстрирует это и вероятнее всего упадет с ошибкой:

            #include // Мы рассмотрим & позже. Пока не беспокойтесь об этом. Мы используем его только для того, // чтобы заставить компилятор думать, что p имеет значение. void foo(int*&p) < // p — ссылка на указатель. Мы рассмотрим ссылки (и ссылки на указатели) позже в этой главе. // Мы используем ее, чтобы заставить компилятор думать, что p мог быть изменен, // поэтому он не будет жаловаться на то, что p неинициализирован. >int main() < int* p; // Создаем неинициализированный указатель (указывающий на мусор) foo(p); // Обманываем компилятор, заставляя его думать, что мы собираемся присвоить указателю допустимое значение std::cout 

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

            Размер указателей

            Размер указателя зависит от архитектуры, для которой скомпилирован исполняемый файл — 32-битный исполняемый файл использует 32-битные адреса памяти. Следовательно, указатель на 32-битной машине занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет 64-битным (8 байтов). Это независимо от размера объекта, на который он указывает:

            char* ch_ptr <>; // char равен 1 байту int* i_ptr <>; // int обычно равен 4 байтам std::cout  

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

            Что хорошего в указателях:

            • Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву
            • Указатели в C++ — это единственный способ динамического выделения памяти
            • Их можно использовать для передачи функции в качестве параметра другой функци
            • Их можно использовать для достижения полиморфизма при работе с наследованием
            • Их можно использовать, чтобы иметь указатель на одну структуру/класс в другой структуре/классе, чтобы сформировать цепочку. Это полезно в некоторых более сложных структурах данных, таких как связанные списки и деревья

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

            Задание

            Поменяйте значения переменных first_num и second_num местами. Попробуйте это сделать с помощью уже созданных указателей.

            Упражнение не проходит проверку — что делать? ��

            Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

            В моей среде код работает, а здесь нет ��

            Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

            Мой код отличается от решения учителя ��

            Это нормально ��, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

            В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

            Прочитал урок — ничего не понятно ��

            Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

            Кратко об указателях в Си: присваивание, разыменование и перемещение по массивам

            Приветствую вас, дорогие читатели. В данной статье кратко описаны основные сведения об указателях в языке Си. Кроме основных операций с указателями (объявление, взятие адреса, разыменование) рассмотрены вопросы безопасности типов при работе с ними. К сожалению, в данной статье вы не найдёте информацию по операциям сравнений указателей. Однако, статья будет полезна новичкам, а также тем, кто работает с массивами. Все примеры в данной статье компилировались компилятором gcc (восьмой версии).

            Введение

            Указатель - переменная, которая хранит адрес сущностей (т.е. других переменных любого типа, будь то структура, или массив), и над которой возможно выполнять операцию разыменования (dereferencing). Адрес обычно выражен целым положительным числом. Диапазон адресов зависит от архитектуры компьютера. Указателю надо указать тип переменной, адрес которой он хранит, или же использовать ключевое слово void, для обозначения указателя, хранящего адрес чего-угодно (т.е. разрешён любой тип). Указатели объявляются как и обычные переменные, с той разницей, что имя типа переменной указателя имеет префикс, состоящий как минимум из одной звёздочки (*). Например:

            int a = 12; /* usual variable */ int * ptr = &a; /* ptr-variable which contains address of variable a */ int **pptr = &ptr; /* ptr-variable which contains address of variable ptr */ int aval = **pptr; /* get value by adress which is contained in pptr. */ int aval2 = *ptr; /* get value of a by address (value of ptr) */

            Количество звёздочек лишь указывает на длину цепочек хранимых адресов. Поскольку указатель также является переменной и имеет адрес, то его адрес также можно хранить в другом указателе. В выше приведённом примере адрес переменной a сохраняется в переменной-указателе ptr. Адрес же самой переменной ptr сохраняется в другом указателе pptr. Чтобы получить адрес переменной, перед её именем надо поставить знак амперсанда (&). Наконец, чтобы выполнить обратную операцию, т.е. получить значение (содержимое) по адресу, хранимому в указателе, имя указателя предваряется звёздочкой, почти как при объявлении. Почти, потому что одной звёздочки достаточно чтобы "распаковать" указатель. Поскольку pptr указывает по адресу на значение, хранимое в ptr, то необходимо два раза применить операцию разыменования.

            Указатели в предыдущем примере хранят адрес переменной определённого типа. В случае, когда применяются указатели типа void (любого типа), то прежде чем распаковать значение по адресу, необходимо выполнить приведение к типизированному указателю. Следующий пример является версией предыдущего, но с использованием указателя любого типа.

            int b = 0xff; void *pb = &b; void **ppb = &pb; int bval1 = *((int *) pb); int bval2 = *((int *) *ppb);

            В данном примере адреса хранятся в указателе типа void. Перед получением значения по адресу, хранимым в pb, необходимо привести указатель pb к типу int*. Затем, воспользоваться стандартной операцией разыменования. Что касается указателя ppb, то он разыменовывается два раза. Первый раз до приведения к типу, для получения содержимого переменной pb, на которую он указывает. Второй раз - после приведения к типу int*.

            Изменения значения переменной через указатель.

            Так как указатель хранит адрес переменной, мы можем через адрес не только получить значение самой переменной, но также его изменить. Например:

            char a = 'x'; char *pa = &a; /* save address of a into pa */ *pa = 'y'; /* change content of variable a */ printf("%c\n", a); /* prints: y */

            Как было сказано выше, указатели хранят адреса. Естественно, что адреса могут указывать не только на ячейки данных переменных в вашей программе, но и на другие вещи: адрес стека процедур, адрес начала сегмента кода, адрес какой-то процедуры ядра ОС, адрес в куче и т. д. Логично, что не все адреса можно использовать напрямую в программе, поскольку некоторые из них указывают на те участки памяти, которые нельзя изменять (доступ для чтения), или которые нельзя затирать. В случае, при обращении к участку, доступному только для чтения, при попытке изменить значение получим ошибку Segmentation Fault (SF).

            Кроме того, в языке Си определён макрос с именем NULL, для обозначения указателя с нулевым адресом. Данный адрес обычно используется операционной системой для сигнала об ошибке при работе с памятью. При попытке что либо читать по этому адресу, программа может получить неопределённое поведение. Поэтому ни в коем случае не пытайтесь извлечь значение по пустому указателю.

            И ещё, указатели могут указывать на один и тот же объект. Например:

            int a = 123; int *p1 = &a; //Теперь p2 хранит тот же адрес, что и p1. int *p2 = &a; *p1 -= 3; // a = 123 - 3. printf("*p2 = %d\n", *p2); //Выведет 120

            Этот простой пример показывает, что через адреса можно менять содержимое простых переменных, а также остальных указателей, ссылающихся на тоже самое. Таким образом, указатель p2 как бы является псевдонимом (alias) для p1.

            Передача параметров через указатели.

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

            int swap(int *a, int *b)

            Здесь переменные а и b меняются своими значениями друг с другом (при условии, что параметры содержат не нулевой адрес). Отметим ещё раз, что мы можем изменить содержимое, указываемое по параметру-указателю методов. И, конечно, мы можем стереть данный адрес, присвоив параметру новое значение.

            Проверка типов и массивы

            Как было сказано, указатели хранят адреса переменных. Несмотря на указание типа для переменной указателя, это не мешает присвоить ему адрес переменной другого типа, если вы компилируете БЕЗ флагов. Например, следующий код не скомпилируется, если вы включили флаги -Werror -Wall .

            #include #include int main(int argc, char **argv)

            Конечно, компилятор gcc и без -Wall заметит недопустимую операцию в 7 строке кода. Флаг -Wall покажет все предупреждения компилятора. Главный флаг -Werror не позволит компилировать код, если есть предупреждения.

            Что же касается массивов, то для массива не нужно предварять имя переменной амперсандом, поскольку компилятор автоматически при присваивании адреса массива присвоит адрес первого его элемента в указатель. Для многомерных массивов потребуются указатели на массивы, а не массивы указателей. Первые имеют форму объявления вида int (*arr)[] , а вторые вида int *arr[] . В квадратных скобках обязательно нужно указать размер массива. Для трёхмерных массивов потребуется уже две пары скобок, например int (*arr)[2][2] . Для четырёхмерных - три и так далее.

            // В ПУСТОМ теле метода main. int A[2] = ; // A -> (int *) ptr to A[0] element, &A -> (int (*)[]) -> ptr to whole Array. int *ptr = A; printf("ptr -> A[1] = %d\n", *(ptr + 1)); // A[1] => 20. //Illegal usage of A. // int a_2 = ++A; //expected lvalue. //But with ptr you can do this. int b_2 = *++ptr; //Now ptr contains address of A[1]. (b_2 = A[1]); int (*ptr2)[2] = &A; //ptr to array, not to literal element. //*ptr2 => get array. //**ptr2 => get first element of array. //*ptr2 + 1 => get address of second element of array. printf("ptr2 -> A[1] = %d\n", *( *ptr2 + 1) ); int M[2][2] = < , >; // (*mp)[k] => (*mp)[k] => mp[0][k]. int (*mp)[2] = M; //again you must not add '&' to variable M. printf("M[0][0] = %d\n", **mp);//get array and extract it first element printf("M[1][0] = %d\n", **(mp + 1));//move to the address of second element printf("M[1][1] = %d\n", *( *(mp + 1) + 1));

            В выше приведённом коде даны примеры для работы с массивами (одномерными и двумерными). В квадратных скобках указывается размер последнего измерения. Важно помнить, что первое разыменование приводит вас ко всему массиву (т. е. к типу int * ). А второе разыменование распаковывает элемент данного массива. В случае одномерного массива, у нас всего одна ячейка, и указатель ссылается на неё. В случае двумерного массива, у нас две ячейки - массивы, а указатель ссылается на первую. Для перемещения на второй массив, достаточно прибавить единицу к адресу, хранимому в переменной mp, например, так mp + 1 . Чтобы получить первый элемент второго массива, надо два раза распаковать указатель с соответствующим адресом массива, т.е. **(mp + 1) .

            Постоянные (const) и указатели.

            Напомним, чтобы сделать переменную с постоянным, фиксированным значением, надо добавить ключевое слово const перед её именем (до имени типа или после). Например:

            const int i1 = 10; int const i2 = 222; // Warning: variable e3 is unitialized. With -Werror it won't be compiled. // (Внимание: переменной e3 не присвоено значение. С флагом gcc -Werror // данный код не скомпилируется). // const int e3;

            Для объявления указателя на постоянное значение, ключевое слово const должно быть ПЕРЕД звёздочкой.

            int A[2] = ; const int *a0 = A; printf("content of a0 = %d\n", *a0); //*a0 *= 10; //error: cannot change constant value. a0 = (A + 1); // A[1] printf("content of a0 = %d\n", *a0); //prints: A[1]

            В примере выше была создана переменная-указатель, ссылающееся на постоянное значение. Слово const перед звёздочкой указывает, что нельзя менять содержимое напрямую (путём разыменования, обращения к ячейке). Но сама переменная указатель постоянной не является. А значит, ей можно присвоить новый адрес. Например, адрес следующей ячейки в массиве.

            Чтобы запретить менять адрес (значение переменной) указателя, надо добавить слово const ПОСЛЕ звёздочки. Кроме того, можно добавить ключевые слова const перед и после '*' , чтобы сделать переменную фиксированной ещё сильнее, например так:

            // Переменная с постоянным адресом и постоянным содержимым. const int *const ptr = A; // constant address with constant content // Переменная с постоянным адресом (содержимое можно менять) int *const ptr2 = A; // constant address only. // Переменная с постоянным содержимым, но с изменяемым адресом (значение справа) const int *ptr3 = A; // constant content only (can change address (rvalue))
            • Программирование
            • Системное программирование
            • C

Добавить комментарий

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