Законно ли обращаться с указателем как с массивом? - PullRequest
1 голос
/ 18 апреля 2020
void uc(char* s)
{
    int i;

    for( i=0; i < strlen(s); i++ )
        if (97 <= s[i] && s[i] <= 122)
            s[i] = s[i] - 32;

    return;
}

Мой профессор показал нашему классу этот оператор.

char* s копирует массив, это нормально, потому что имя массива - это его первый элемент памяти.

Теперь моя проблема это: почему мы рассматриваем указатель s как массив в цикле for?
Указатели хранят адреса, но я узнал, что они не очень интуитивно понятны ...

My Проблема в том, что я рассматриваю их как переменную типа int, поскольку адрес памяти - это целые числа в шестнадцатеричном формате (верно?), но я знаю, что это не так просто.

Редактировать: спасибо всем за ответы, Я люблю этот сайт и сообщество <3 Как вы видели, я новичок ie, поэтому спасибо за терпение и хорошие объяснения </p>

Ответы [ 4 ]

1 голос
/ 18 апреля 2020

char* s копирует массив - нет, это не так.

Аргументом для этой функции является указатель на char. Вот и все . Синтаксис разыменования для указателя может принимать две формы: *(p + n) и p[n]. Две формы эквивалент . в обоих случаях адрес в p берется по значению, корректируется с помощью шага типа элемента, и результирующий адрес затем разыменовывается для чтения или хранения в зависимости от контекста использования.

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

void uc(char* s)
{
    for (; *s; ++s)
    {
        if (97 <= *s && *s <= 122)
            *s -= 32;
    }
}

Это идет последовательность char, начинающаяся с входного адреса, удерживаемого s до тех пор, пока *s (который продвигается с каждой итерацией в l oop с использованием ++s) равняется завершающему нулевому символу (нулевой октет) , Поскольку мы продвигаемся s с каждой итерацией, она всегда находится на символе, обрабатываемом для этой итерации.

Как и все в C, аргументы функции передаются по значению . Просто бывает, что «значение» массива id, когда оно используется в контексте выражения ( почти везде), является базовым адресом его первого элемента. Это, следовательно, обеспечивает наличие мутации данных, переданных с этого адреса.

Поэтому:

#include <stdio.h> // for puts

void uc(char* s)
{
    for (; *s; ++s)
    {
        if (97 <= *s && *s <= 122)
            *s -= 32;
    }
}

int main()
{
    char s[] = "lower";
    uc(s);
    puts(s);
    return 0;
}

напечатает LOWER на платформе, совместимой с ascii. Я умоляю запустить этот код в отладчике, помня следующее:

  • Базовый адрес s[] в main()
  • значение s в списке аргументов для uc при первом входе в него.
  • Что происходит с s в uc, когда l oop повторяется
  • значение *s при использовании в различных контекстах указывается в uc

Честно говоря, это лучшее, что я могу сделать, объясняя это. Желаем удачи.

1 голос
/ 18 апреля 2020

Перво-наперво и совершенно тупо:

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

char* s копирует массив,

Это заблуждение. s является указателем на char. Это может быть один char или целый массив. Точный тип нижележащего объекта теряется при получении адреса.

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

это нормально, потому что имя массива - это его первый элемент памяти.

Массивы не имеют имен! Символы имеют. Символ в массиве будет распадаться на указатель на элементарный тип , из которого сделан массив. Этот распад - вот почему вы можете писать char somearray[123]; char *p = somearray, не беря его адрес.

, почему мы рассматриваем указатель s как массив в цикле for?

Потому что мы можем. Более конкретно, из-за этой вещи, которая называется "арифметика указателя c" . Expession s + 1 приведет к указателю, который указывает один элемент за адресом элемента, на который указывает указатель. Он работает для любого числа (в диапазоне значений ptrdiff_t).

Когда вы пишете a_pointer[i] в C, оно буквально переводится (это не гипербола, стандарт C требует, чтобы оно было обрабатывается компилятором так!) в *(a_pointer + i). Итак, что происходит, когда вы пишете a_pointer[i], вы говорите компилятору: * "предположим, что a_pointer указывает на объект массива и что a_pointer + i все еще находится внутри границ этого объекта массива: с этим предположением разыменование местоположение и создайте там значение. "

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

Арифметика указателя c указывает на указатель, который не взят из массива? Не определено!

Создать указатель, который находится за пределами массива? Undefined!

Моя проблема в том, что я считаю их "переменными типа int",

Это не так! Технически указатели могут быть реализованы прахом единорога и магией c. В них есть несколько очень специфических c правил, когда речь идет о смешивании их с числами. На языке программирования C эти правила (упрощены):

  • Указатели можно переводить в целые числа размером sizeof(uintptr_t) и наоборот.

  • Числовое значение c 0 преобразуется в нулевой указатель , а нулевые указатели преобразуются в численное значение c 0.

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

  • Указатели могут быть вычтены друг от друга, в результате чего получается целое число, совместимое с ptrdiff_t, и значение полученного результата integer - это расстояние в элементах между этими двумя указателями, при условии, что оба указателя ссылаются на один и тот же объект. Написано "типами" ⟪ptrdiff_t⟫ = ⟪pointer A⟫ - ⟪pointer B⟫, действительны только арифметические значения c. Допустимые перестановки.

  • Вы не можете добавлять указатели

  • Вы не можете умножить указатели

  • Нет мандата, что числовые представления указателей могут использоваться для арифметики указателей c. Т.е. вы не должны предполагать, что (pointer_A - pointer_B) == k*((uintptr_t)pointer_A - (uintptr_t)pointer_B)) для любого значения k.

, поскольку адрес памяти - это целые числа в шестнадцатеричном формате (верно?),

Ха?!? Это не так.

Да, вы можете использовать целые числа для адресации памяти. Нет, вам не нужно писать их как шестнадцатеричные. Шестнадцатеричный - это просто другая числовая база и 0xF == 15 = 0o17 == 0b1111. В наши дни мы обычно пишем адреса в шестнадцатеричном формате, потому что он хорошо согласуется с размерами слов наших нынешних компьютерных архитектур, равными степеням 2. Один шестнадцатеричный ди git равен 4 битам. Но есть и другие архитектуры, в которых используются слова разных размеров и которые лучше подходят для этих других числовых баз.

И это все еще предполагает линейные адресные пространства. Однако существуют также компьютерные архитектуры, которые поддерживают сегментированные адресные пространства. На самом деле, весьма вероятно, что компьютер, на котором вы читаете это, является таким компьютером. Если он использует процессор от Intel или AMD, он понимает сегментированные адреса https://en.wikipedia.org/wiki/X86_memory_segmentation

В сегментированном адресном пространстве x86 адрес фактически состоит из двух чисел образует вектор. Это означает, что, если вы компилируете программу C для запуска в среде с указателями сегментированного адресного пространства, больше не будут простые числа с единичными значениями. C все еще требует, чтобы они были переведены в uintptr_t, подумайте над этим!

0 голосов
/ 18 апреля 2020

За исключением случаев, когда это операнд операторов sizeof или унарных & или строковый литерал, используемый для инициализации массива символов в объявлении, N-элемент выражение типа "N" массив T "(T [N]) преобразуется (" распадается ") в выражение типа" указатель на T "(T *), а значением выражения является адрес первого элемента массив.

Массив объекты не являются указателями. Если вы объявите массив как

char foo[] = "hello";

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

        +–––+
0x1000: |'h'|
        +–––+
0x1001: |'e'|
        +–––+
0x1002: |'l'|
        +–––+
0x1003: |'l'|
        +–––+
0x1004: |'o'|
        +–––+
0x1005: | 0 |          
        +–––+

Объект foo не указатель; он не выделяет места для указателя. Выражение foo в большинстве случаев преобразуется в указатель, в том числе при передаче в качестве аргумента функции:

uc( foo );

То, что получает uc, является адресом первого элемента, следовательно, объявление

void uc( char *s ) { ... }

Что касается оператора индекса [], это то же самое - выражение массива преобразуется в указатель на первый элемент, и операция с индексом применяется к этому указателю. Подстрочная операция определена как

a[i] == *(a + i)

При заданном начальном адресе a, вычислить адрес i-го объекта указанного типа ( а не i '-й байт ) после этого адреса и разыменования результата.

Таким образом, вы можете использовать оператор индекса 10 * в индексе указателя, а также выражение массива.

Указатели не имеют , которые должны быть представлены как целые числа - на некоторых старых сегментированных архитектурах они были представлены в виде пары значений (номер страницы и смещение). Кроме того, указатели на разные типы могут иметь разные представления - например, char * может не выглядеть как int *, который может не выглядеть как double *, et c. На настольных системах, таких как x86, они есть, но это не гарантировано.

Редактировать

Из комментария:

при инициализации вектора типа int следующим образом: for( int i=0; i < size; ++i); scanf("%d", &vector[i]) калькулятор использует этот указатель " механизм "для цикла корыта?"

Да, точно. scanf ожидает, что аргумент, соответствующий спецификатору преобразования %d, будет адресом объекта int, то есть выражением типа int *. Унарный оператор & возвращает адрес объекта, поэтому, если vector было объявлено

int vector[N]; // for some value of N

, тогда выражение &vector[i] вычисляется по адресу i '-ого элемента массив и тип выражения: int *.

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

void foo( T x ) // for any type T
{ 
  x = new_value;
}

void bar( void )
{
  T var;
  foo( var );
}

формальный параметр x в foo - это другой объект в памяти, чем var, поэтому изменение на x не влияет на var. Если мы хотим, чтобы foo мог писать в var, то мы должны передать на него указатель:

void foo( T *ptr )
{
  *ptr = new_value; // write a new value to the thing ptr *points to*
}

void bar( void )
{
  T var;
  foo( &var ); writes a new value to var
}

Унарный оператор * в *ptr = new_value разыменования ptr, поэтому выражение *ptr в foo эквивалентно var:

*ptr ==  var  // T   == T
 ptr == &var  // T * == T *

В объявлении , * просто означает, что объект ptr имеет тип указателя - он не разыменовывается, поэтому вы можете написать что-то вроде

int x;
int *ptr = &x; // ptr is *not* being dereferenced
int y = 5;
*ptr = y;      // ptr *is* being dereferenced
0 голосов
/ 18 апреля 2020

s - это указатель, поэтому мы можем использовать его как массив, если он выделен.

Два параметра ниже аналогичны:

s[i] = s[i] - 32;

и

*(s+i) = *(s+i) -32

, поскольку адрес памяти - это целые числа в шестнадцатеричном формате (верно?)

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

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