Страницы

Поиск по вопросам

среда, 4 декабря 2019 г.

В чём разница между одиночными и двойными указателями?

#c #указатели


Здравствуйте!
Прошу объяснить разницу между двойными и одиночными указателями на примере двух приведённых
ниже программ: они одинаковые, только первая использует одиночные указатели, а вторая
- двойные.
Меня интересует, почему в первой программе строка ++(currentChar) не приводит к движению
по строке чисел, а во второй соответствующая строка ++(*currentChar) приводит, другими
словами, почему "область действия одиночного указателя ограничивается функцией, в пределах
которой он задан" (кавычки, потому что знаю, что это может быть не точно) ?
Просьба не ругать за отсутствие элементарных познаний в языке C - я постепенно спускаюсь
к нему с более высокого уровня (Objective-C), и до сих пор не выдавалось время изучить
C системно.
Спасибо. 

После ответов и обсуждения выяснилось, что на самом деле ключевая разница (ох, незнание
основ!!) состоит в том, как передаются аргументы: в первом случае это просто currentChar,
а во втором это ¤tChar. См. ответы и обсуждение.

Программа №1
void func1(const char *currentChar) {
    printf("char is %c\n", *currentChar);

    ++(currentChar);
}

int main(int argc, const char * argv[]) {
    char *JSONCString2 = "123456789";

    const char *currentChar = JSONCString2;

    while (currentChar != NULL && *currentChar != '\0') {
        func1(currentChar);
    }

    return 0;
}

Её вывод: бесконечное повторяющееся char is 1.
Программа №2
void func1(const char **currentChar) {
    printf("char is %c\n", **currentChar);

    ++(*currentChar);
}

int main(int argc, const char * argv[]) {
    char *JSONCString2 = "123456789";

    const char *currentChar = JSONCString2;

    while (currentChar != NULL && *currentChar != '\0') {
        func1(¤tChar);
    }

    return 0;
}

char is 1
char is 2
char is 3
char is 4
char is 5
char is 6
char is 7
char is 8
char is 9
Program ended with exit code: 0
    


Ответы

Ответ 1



Указатель - это обыкновенная переменная, которая хранит не данные, а некий адрес, который ссылается на ячейку данных. "Размер" этой ячейки зависит от типа указателя (не сам размер, а размер ячейки, которую он адресует, это важно для адресной арифметики, см. далее). Логично, что мы можем делать указатель на указатель с условно бесконечной глубиной вложенности. Можно получать адреса, относительно указателя "сдвигая" его вправо (прибавляя целое число) и влево (отнимая целое число) на размер типа указателя. UPD В первый раз мы передаем указатель по значению (каждый раз копируем значение), второй раз - по ссылке (работаем с ним непосредственно). Поэтому инкремент в первом случае не работает мы будем "топтаться на месте". из аргумента). Ну и при обращении к строке (в printf) в первый раз мы один раз извлекаем указатель (разыменовываем), а во второй - дважды, получая сначала адрес указателя на данные, а затем - и сами данные.

Ответ 2



Давайте-ка и я добавлю свои пять копеек. Дело в том, что указатель в C используется в нескольких различных смыслах. Во-первых, это настоящий указатель — адрес какой-то переменной в памяти. Затем, это может быть массив неопределённой длины (которую приходится передавать отдельно). Вместо отдельного типа данных для массива в C пользуются указателем на первый элемент (то есть элемент с индексом 0). Как специальный случай, строка в C очень похожа по логике на массив: это массив символов (который обычным образом представляется в виде указателя на начальный символ), но текущая длина строки определяется неявно, позицией первого нулевого символа (и может не совпадать с длиной выделенного куска памяти). Наконец, указатель используется для семантики «передать переменную так, чтобы вызываемая процедура могла её поменять». Для этого передаётся указатель на эту переменную, а вызывающий код разыменовывает этот указатель. Таким образом, каждая из * в коде может иметь одно из упомянутых выше значений. А значит, значение ** не фиксировано, его приходится каждый раз определять из контекста. Конкретно для вашего случая: в C всё, в том числе и указатели, передаётся по значению: параметр является копией аргумента, а не самим аргументом. Изменения в копии указателя не приводят к изменению самого указателя. Передачи «по ссылке» нет, и её приходится эмулировать, как описано выше. У вас во втором примере так и происходит: вы передаёте указатель на ваш указатель (то есть, конечно, его копию). Но и копия, и оригинал указателя указывают на одни и те же данные (то есть, на один и тот же char *), поэтому изменения в этих данных, понятно, «видны» всем.

Комментариев нет:

Отправить комментарий