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

Указатели в C/C++ сложная вещь для новичков. Без указателей программировать серьезные программы на С/С++ просто нельзя. Кроме того, указатели помогут понять как устроена память в компьютере, и как с ней работает процессор.Поэтому я сегодня попытаюсь объяснить вам доходчиво как они работают и зачем нужны. Прошу под кат.В первой части я расскажу об общем устройстве указателей, как они работают и зачем нужны.И так, начнем с базовой теории. Указатель это переменная указывающая на область памяти. Немножко кода:

С синтаксисом все понятно, достаточно просто поставить звездочку после типа данных. Возникает вопрос, а на что указывает указатель? Ответ прост — не на что. Почему? Потому, что мы его только объявили, но не указали на что ему указывать. Как все запутанно, правда? Когда мы объявляем переменную компьютер выделяет память для этой переменной в стеке. То есть код:

Указывает компьютеру, что нужно выделить память для переменной myVar1 типа int. Размер памяти выделяемой для нее зависит от самого компьютера. Например на 32-х разрядном компьютере он равен 4 байтам(32 бит). Когда мы пишем:

Компьютер значение 25 записывает по адресу выделенном для переменной myVar1; Когда мы пишем например:

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

Указатель же — это тоже переменная. И компьютер так же выделяет для нее память. Но в ней хранится адрес памяти. Что бы это проверить, сделаем следующее:

Программа объявляет указатель, а потом выводит его содержимое.Формат «%p» выводит на экран адрес памяти. Пока что, указатель указывает на случайный адрес, так как мы только создали, но не объявили ему на что указывать.

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

При создании указателей советуется делать так:

NULL — это так называемый «нулевой» указатель. Это поможет избежать ошибок. Так как в случайном адресе может храниться все что угодно. Кстати, для условных операторов NULL означает ложь. Напишем две программы. Первая будет выглядеть так:

А вторая так же, только заменим строку объявления указателя:

Откомпилируйте и запустите. В первом случае на экран выведеться адрес памяти, а во втором «Указатель не определен». Такое простое правило, как инициализация указателей NULL’ом и проверка через if(или любой другой условный оператор) помогает избежать ошибок.

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

Раз указатель указывает на переменную, то есть хранит в себе ее адрес памяти, значит можно как то получить ее адрес? Да, все верно. Адрес переменной получается с помощью символа «&», например:

При выполнении этого кода, выведется в консоль адрес переменной var1. Как вы уже наверно догадались, в указатель можно записать адрес переменной. Например так:

Программа выведет на экран:

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

Что нам это дает? Ну есть у нас указатель на переменную, там ее адрес памяти, что с ним делать? С помощью указателя, мы можем изменить саму переменную, например так:

Программа выведет на экран:

Как видите, переменной можно управлять с помощью указателя. И кстати, количество указателей не ограничено. Можно хоть сотню создать и все они могут указывать на одну и туже переменную. Указатели начинают приносить пользу, когда функция должна изменить значение какой-то переменной, переданной ей в качестве аргумента. Например наша функция func будет принимать 3 аргумента, первые два складывает, а значение записывает в третий, функция main создаст 3 переменные и передаст их функции:

Компилируем, запускаем. Наш ждет разочарование:

 

Попробуем найти ошибку. Добавим пару printf функций в нашу программу:


Компилируем, запускам, получается:

А все потому, что функция во время передачи ей переменных, просто создала копии. То есть a копия var1, b копия var2, ну и sum копия mySum. После выхода из функции переменные a, b, sum просто уничтожаются. Что же делать? Выход есть! Изменим нашу функцию:

Теперь наша функция принимает два аргумента типа int, а третий указатель на тип int. Функцию main изменим вот так:

Откомпилируем и запустим:

Все работает!

И так, подведем итоги:

  1. Указатель — это переменная, хранящая адрес памяти;
  2. Указатель может хранить адрес другой переменной, с его помощью можно менять значение переменной. Если быть более точным, меняется значение лежащее по указанному адресу;
  3. Указатели при создании желательно инициализировать либо NULL либо необходимым адресом;
  4. Указатели могут быть опасны для программы/системы, если с ними работать невнимательно.

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

Спрашивайте свои ответы. Пишите об ошибках.

Комментарии 13

  • Здраствуйте у меня к вам три вопроса:

    void func(int a, int b, int* sum)
    {
    *sum = a + b;
    printf("Значение sum внутри функции func %in", *sum);
    }

    1)третий аргумент функции int* sum содержит в себе адрес переменной?
    2)знак * перед sum сообщает программе,что нужно обратиться к переменной?
    3)функция имеет тип void,который не возвращает значения,как и почему задали такой тип?

    P.S Да вопросы немного детские, но мне надо с чего то начинать…

    • 1. Да. Объявление вида TYPE * Variable говорит программе о том, что переменная будет содержать в себе адрес другой переменной с типом TYPE ( то есть указатель на нее).
      2. Да. Если вы создали указатель, а потом при операциях с ним используете знак * (звездочка), то программа воспользуется значением переменной по указателю.
      3. Тип void используется для функции тогда, когда не нужно что бы она значения возвращала. Как видите, В примере в этом нет необходимости, поэтому я объявил ее как void.

  • А вторая так же, только заменим строку объявления указателя:
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    #include

    int main()
    {
    int* pointer = NULL //инициализируем указатель
    if (pointer)
    printf(«%pn», pointer);
    else
    printf(«Указатель не определен!n»);
    return 0;
    }
    В этом примере пропущено » ; » после int* pointer = NULL

    Если пользоваться gcc for Ubuntu, то для перевода строки лучше использовать «\n»

    Спасибо.

    • Пожалуйста.

      В Unix, Linux системах по стандарту POSIX можно использовать перенос строки «\n». Стандарт ANSI — «\r\n»;

      Если я конечно же не ошибаюсь.

      А за ошибку — спасибо :).

  • Жутко непонятный пример. А не проще ли переменные сделать глобальными? Просто вынести их за рамки main и функции? Ведь так будет в сто раз легче и не будет никаких копий создаваться. Или сделать функцию int и присваивать ей результат. Тоже проще, чем через указатели. Так что полезного применения я тут не увидел. А пример ваще не понял.

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

      Все, когда учились программировать, читали книги по программированию. И я уверен, что большая часть информации воспринималась с непониманием и вставал вопрос «зачем это нужно?» Так вот. «Зачем» приходит с опытом. Если вы знаете, хотя бы образно, какую-то технологию, решая задачу — вы можете понять, что эта технология вам необходима. А вот если вы о ней ничего не знаете, то возможно будете изобретать велосипед для решения задачи.

      Надеюсь донес до вас свою мысль.

  • Спасибо за статью! Вы мне очень помогли!

  • Спасибо. Я понял теперь их! Преследовали меня как демоны ) я, чтобы не мучаться, работал через массив. Я понимаю, что это те же указатели, но все же было как-то проще.тяжело использовать то, что не понимаешь. Теперь проблема решена 🙂 ура!

  • Ничего не понятно. Бред из учебников. Так и не понятно как их использовать

  • Спасибо автору за добрый посыл!
    Но к сожалению у меня в visual studio 2017 все эти линуксовые прибамбасы не работают.
    Ошибки выдает на этапе компиляции. А жаль, хоть и дельная статья, да ставить из-за нее линукс что-то не тянет.

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

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