Как передать адрес переменной в функцию c
Перейти к содержимому

Как передать адрес переменной в функцию c

  • автор:

Как передать адрес переменной в функцию c

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

Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:

#include void increment(int x) < x = x + 1; printf("increment function: %d \n", x); >int main(void)

Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:

increment function: 11 main function: 10

Теперь изменим функцию increment, использовав в качестве параметра указатель:

#include void increment(int *x) < *x = *x + 1; printf("increment function: %d \n", *x); >int main(void)

Теперь в функции increment разыменовываем указатель, получаем его значение и увеличиваем его на единицу.

*x = *x + 1;

Это изменяет значение, которое находится по адресу, хранимому в указателе x.

Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n); .

В итоге изменение параметра x также повлияет на переменную n:

increment function: 11 main function: 11

Еще один показательный пример применения указателей в параметрах — функция обмена значений:

#include void swap(int *a, int *b) < int temp = *a; *a = *b; *b=temp; >int main(void)

Функция swap() в качестве параметров принимает два указателя. Посредством переменной temp происходит обмен значениями.

При вызове функции swap в нее передаются адреса переменных x и y, и в итоге их значения будут изменены.

Константые параметры

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

#include // константный параметр int twice(const int *x) < //*x = *x * *x; // так нельзя, так как x - константный параметр int y = *x + *x; return y; >int main(void) < int n = 10; int m = twice(&n); printf("n = %d \n", n); // n = 10 printf("m = %d \n", m); // m = 20 return 0; >

Фактически такой константный параметр будет работать как указатель на константу — мы не сможем изменить его значение внутри функции.

Массивы в параметрах

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

Например, определим функцию для увеличения элементов массива в два раза:

#include void twice(size_t n, int p[]) < for(size_t i = 0; i < n; i++) < p[i]= p[i] * 2; >> int main(void) < int nums[] = ; // получаем количество элементов массива size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(size_t i=0; i return 0; >

Функция twice в качестве параметров принимает массив и число его элементов и в цикле увеличивает их в два раза.

В функции main передаем массив в функцию twice и затем выводим его на консоль. В результате мы увидим, что массив nums был изменен:

2 4 6 8 10

Так как передача массива в функцию фактически представляет передачу адреса первого элемента, то массивы в параметрах мы можем заменить указателями:

#include void twice(size_t n, int *p) < for(size_t i=0; i> int main(void) < int nums[] = ; size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(size_t i=0; i return 0; >

В итоге в данном случае не будет большой разницы, какой тип имеет параметр — массив или указатель. Компилятор в любом случае будет рассматривать параметр типа int array[] как указатель int* array

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

#include // функция ожидает получить массив как минимум из 4 элементов void twice(int numbers[4]) < for(size_t i = 0; i < 4; i++) < numbers[i]= numbers[i] * 2; printf("%d \t", numbers[i] * 2); >> int main(void) < int nums[5] = ; twice(nums); return 0; >

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

Начиная со стандарта C99 Си позволяет установить минимальную длину массива с помощью оператора static :

// ожидаем массив как минимум с 4 элементами void twice(int numbers[static 4])
Константный массив

Массив также можно передавать как константный — в этом случае значения его элементов нельзя изменить:

#include // массив p - константный void twice(size_t n, const int p[]) < for(size_t i = 0; i < n; i++) < // p[i]= p[i] * 2; // Так нельзя - массив константный printf("%d \t", p[i] * 2); >> int main(void) < int nums[] = ; // получаем количество элементов массива size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); return 0; >

Как передать адрес переменной в функцию c

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

Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:

#include void increment(int); int main() < int n ; increment(n); std::cout void increment(int x)

Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:

increment function: 11 main function: 10

Теперь изменим функцию increment, использовав в качестве параметра указатель:

#include void increment(int*); int main() < int n ; increment(&n); std::cout void increment(int *x) < (*x)++; // получаем значение по адресу в x и увеличиваем его на 1 std::cout

Для изменения значения параметра применяется операция разыменования с последующим инкрементом: (*x)++ . Это изменяет значение, которое находится по адресу, хранимому в указателе x.

Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n); .

В итоге изменение параметра x также повлияет на переменную n, потому что оба они хранят адрес на один и тот же участок памяти:

increment function: 11 main function: 11

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

#include void increment(int*); int main() < int n ; int *ptr ; increment(ptr); std::cout void increment(int *x) < int z ; x = &z; // переустанавливаем адрес указателя x std::cout

В функцию increment передается указатель ptr, который хранит адрес переменной n. При вызове функция increment получает копию этого указателя через параметр x. В функции изменяется адрес указателя x на адрес переменной z. Но это никак не затронет указатель ptr, так как он предствляет другую копию. В итоге поле переустановки адреса указатели x и ptr будут хранить разные адреса.

Результат работы программы:

increment function: 6 main function: 10

Константные параметры-указатели

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

#include void print(const int*); // константный параметр int main() < int n ; print(&n); > void print(const int *x)

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

#include void print(const int*); // константный параметр int main() < const int n ; print(&n); // передаем адрес константы > void print(const int *x)

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

void print(const int *x) < int z; x = &z; // меняем адрес в указателе на адрес переменной z std::cout 

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

#include void print(const int*); // константный параметр int main() < const int n ; print(&n); > void print(const int* const x) // константный указатель на константу < int z; //x = &z; // значение указателя нельзя изменить 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 = 0; printf ("i=%d, &i=%p \n", i, &i);

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

    i=0, &i=0x7fffa40c5fac

    Знак амперсанда & перед переменной позволяет получить ее адрес в памяти. Для вывода адреса переменной на экран используется специальный формат %p . Адреса обычных переменных (не указателей) в процессе выполнения программы никогда не меняются. В этом можно убедиться:

    int a = 6; float b = 10.11; char c = 'k'; printf("%5d - %p\n", a, &a); printf("%5.2f - %p\n", b, &b); printf("%5c - %p\n", c, &c); a = 2; b = 50.99; c = 'z'; printf("%5d - %p\n", a, &a); printf("%5.2f - %p\n", b, &b); printf("%5c - %p\n", c, &c);
    6 - 0x7fff653532e0 10.11 - 0x7fff653532e4 k - 0x7fff653532df 2 - 0x7fff653532e0 50.99 - 0x7fff653532e4 z - 0x7fff653532df

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

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

    int a = 8; printf("%d \n", *&a);

    На экране будет выведено число 8.

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

    Указатели в языке C , как и другие переменные, являются типизированными, т.е. при объявлении указателя надо указывать его тип. Как мы узнаем позже, с указателями можно выполнять некоторые арифметические операции, и именно точное определение их типа позволяет протекать им корректно. Чтобы объявить указатель, надо перед его именем поставить знак * . Например:

    int *pi; float *pf;

    Обратите внимание на то, что в данном случае * говорит о том, что объявляется переменная-указатель. Когда * используется перед указателем не при его объявлении, а в выражениях, то обозначает совсем иное — "получить значение (данные) по адресу, который присвоен указателю". Посмотрите на код ниже:

    int x = 1, y, z = 3; int *p, *q; p = &x; printf("%d\n", *p); // 1 y = *p; printf("%d\n", y); // 1 *p = 0; printf("%d %d\n", x, y); // 0 1 q = &z; printf("%d\n", *q); // 3 p = q; *p = 4; printf("%d\n", z); // 4 printf("%p %p\n", p, q); // p == q

    С помощью комментариев указаны текущие значения ячеек памяти. Подробно опишем, что происходит:

    1. Выделяется память под пять переменных: три типа int и два указателя на int . В ячейки x и z записываются числа 1 и 3 соответственно.
    2. Указателю p присваивается адрес ячейки x . Извлекая оттуда значение ( *p ), получаем 1.
    3. В область памяти, которая названа именем у , помещают значение равное содержимому ячейки, на которую ссылается указатель p . В результате имеем две области памяти ( x и y ), в которые записаны единицы.
    4. В качестве значения по адресу p записываем 0. Поскольку p указывает на x , то значение xменяется. Переменная p не указывает на y , поэтому там остается прежнее значение.
    5. Указателю q присваивается адрес переменной z . Извлекая оттуда значение ( *q ), получаем 3.
    6. Переменной p присваивается значение, хранимое в q . Это значит, что p начинает ссылаться на тот же участок памяти, что и q . Поскольку q ссылается на z , то и p начинает ссылаться туда же.
    7. В качестве значения по адресу p записываем 4. Т.к. p является указателем на z , следовательно, меняется значение z .
    8. Проверяем, p и q являются указателями на одну и туже ячейку памяти.

    Под сам указатель (там, где хранится адрес) также должна быть выделена память. Объем этой памяти можно узнать с помощью функции sizeof() :

    int *pi; float *pf; printf("%lu\n", sizeof(pi)); printf("%lu\n", sizeof(pf));

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

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

    int *pa, *pb; float *pc; printf(" %p %p %p\n", pa, pc, pb); // может возникнуть ошибка printf(" %d %f\n", *pa, *pc); 

    Результат (в Ubuntu):

    0x64 0x1000 0x55ac036d1060 Ошибка сегментирования (образ памяти сброшен на диск)

    Использование неопределенных указателей в программе при вычислениях чревато возникновением серьезных ошибок. Чтобы избежать этого, указателю можно присвоить значение, говорящее, что указатель никуда не ссылается ( NULL ). Использовать такой указатель в выражениях не получится, пока ему не будет присвоено конкретное значение:

    int a = 5; float c = 6.98; int *pa; float *pc; pa = NULL; pc = NULL; printf(" %15p %15p\n", pa, pc); // Error // printf(" %15d %15f\n", *pa, *pc); pa = &a; pc = &c; printf(" %15p %15p\n", pa, pc); printf(" %15d %15f\n", *pa, *pc);

    Результат (в Ubuntu):

    (nil) (nil) 0x7ffd8e77e550 0x7ffd8e77e554 5 6.980000

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

    На этом уроке вы должны понять, что такое адрес переменной и как его получить ( &var ), что такое переменная-указатель ( type *p_var; p_var = &var ) и как получить значение, хранимое в памяти, зная адрес ячейки ( *p_var ). Однако у вас может остаться неприятный осадок из-за непонимания, зачем все это надо? Это нормально. Понимание практической значимости указателей придет позже по мере знакомства с новым материалом.

    Курс с решением задач:
    pdf-версия

    Указатели в C++: объясняем простыми словами

    обложка статьи

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

    Адрес переменной в C++

    Поголовно у каждой переменной имеется свой индивидуальный адрес. Адрес переменной - это путь, по которому находится значение самой переменной. Он записывается в шестнадцатеричном виде. Так, компьютер может записать переменную, как в такой адрес 0x155, так и в такой 0x212 .

    Давайте приведем аналогию с круизным лайнером. В нем, как и в отеле, имеются номера. Вот, например, при покупке номера вам могут дать номер - 0x155 (да, мы понимаем, что не в одном лайнере или отеле не станут записывать номера в шестнадцатеричном виде, но давайте все таки немного отвлечемся). А друг может оказаться в номере 0x212 - так и с переменными, они могут получить разный путь. И только сам компьютер при создании переменной знает, где она находится.

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

    Пример удаления переменных

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

    Поэтому в C/C++ присутствует возможность обратиться к переменной, и, если требует ситуация, удалить и создать её вовсе в другом участке программы, когда это, конечно, нам будет нужно.

    Что такое указатели в C++

    Указатели - это с самого начала переменные, уже в которых хранится адрес других переменных.

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

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

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