Подтвердить что ты не робот

Как вы объясняете указатели С++ разработчику С#/Java?

Я разработчик С#/Java, пытающийся изучить С++. Когда я пытаюсь понять концепцию указателей, меня поражает мысль, что я, должно быть, раньше занимался этой концепцией. Как можно пояснить указатели, используя только понятия, знакомые разработчику .NET или Java? Разве я действительно никогда не занимался этим, это просто скрыто для меня, или я все время использую его, не называя это?

4b9b3361

Ответ 1

Объекты Java в С++

Объект Java является эквивалентом общего указателя на С++.

Указатель С++ похож на объект Java без встроенной сборки мусора.

Объекты С++.

С++ имеет три способа выделения объектов:

  • Объекты продолжительности статического хранения.
    • Они создаются при запуске (до основного) и умирают после основных выходов.
      Для этого есть некоторые технические оговорки, но это основы.
  • Объекты продолжительности автоматического хранения.
    • Они создаются при объявлении и уничтожении, когда они выходят за рамки.
      Я считаю, что это похоже на С# structs
  • Объекты продолжительности динамического хранения

    • Они создаются с помощью нового и самого близкого к объекту С#/Java (указатели AKA)
      Технически указатели необходимо уничтожить вручную с помощью delete. Но это считается плохой практикой, и в нормальных ситуациях они помещаются в объекты автоматической хранения данных (обычно называемые интеллектуальными указателями), которые контролируют их продолжительность жизни. Когда интеллектуальный указатель выходит из области видимости, он разрушается, и его деструктор может вызывать delete на указателе. Разумные указатели могут быть как сборщиками мусора.

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

Ответ 2

Вы постоянно используете указатели на С#, они просто скрыты от вас.

Лучший способ, с которым я рассчитываю подойти к проблеме, - подумать о том, как работает компьютер. Забудьте о всех фантастических вещах .NET: у вас есть память, в которой просто хранятся значения байтов, и процессор, который просто делает вещи для этих байтовых значений.

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

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

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

Address [0] [1] [2] [3] [4] [5] [6] [7]
Data    5   3   1   8   2   7   9   4

Определите переменную x, которую компилятор выбрал для адреса 2. Можно видеть, что значение x равно 1.

Теперь определим указатель p, который компилятор выбрал для размещения по адресу 7. Значение p равно 4. Значение, на которое указывает p, - это значение по адресу 4, которое является значением 2. Получение значения называется разыменованием.

Важным понятием является то, что в отношении памяти нет такой вещи, как тип: есть только байтовые значения. Вы можете интерпретировать эти байтовые значения, как вам нравится. Например, разыменование указателя char будет просто получить 1 байт, представляющий код ASCII, но разыменование указателя int может получить 4 байта, составляющих 32-битное значение.

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

char *str = "hello, world!";

Что это значит, говорится следующее:

  • Отложите несколько байтов в нашем стеке стека для переменной, которую мы будем называть str.
  • Эта переменная будет содержать адрес памяти, который мы хотим интерпретировать как символ.
  • Скопировать адрес первого символа строки в переменную.
  • (Строка "hello, world!" будет сохранена в исполняемом файле и, следовательно, будет загружена в память при загрузке программы)

Если вы посмотрите на значение str, вы получите целочисленное значение, которое представляет адрес первого символа строки. Однако, если мы разыграем указатель (то есть посмотрим, на что он указывает), мы получим букву "h".

Если вы увеличиваете указатель, str++;, он теперь укажет на следующий символ. Обратите внимание, что арифметика указателя масштабируется. Это означает, что когда вы выполняете арифметику на указателе, эффект умножается на размер типа, на который он ссылается. Таким образом, предполагая, что int имеет ширину 4 байта в вашей системе, следующий код фактически добавит 4 к указателю:

int *ptr = get_me_an_int_ptr();
ptr++;

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

Последний полезный совет: массивы и арифметика указателей - одно и то же, это просто синтаксический сахар. Если у вас есть переменная, char *array, то

array[5]

полностью эквивалентна

*(array + 5)

Ответ 3

Указатель - это адрес объекта.

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

[ "Объект" в С++ включает экземпляры типов классов, а также встроенных типов (и массивов и т.д.). Переменная int - это объект на С++, если вам не нравится то, что вам сложно, потому что вам нужно жить с ним; -)]

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

Какой адрес? Это одна из тех 0x-вещей с цифрами и буквами, которую вы могли бы иногда видеть в отладчике. Для большинства архитектур мы можем рассматривать память (ОЗУ, чрезмерно упростить) как большую последовательность байтов. Объект хранится в области памяти. Адрес объекта - это индекс первого байта, занятого этим объектом. Поэтому, если у вас есть адрес, аппаратное обеспечение может получить все, что хранится в объекте.

Последствия использования указателей в некотором роде совпадают с последствиями использования ссылок в Java и С# - вы косвенным образом ссылаетесь на объект. Таким образом, вы можете скопировать значение указателя между вызовами функций, не копируя весь объект. Вы можете изменить объект с помощью одного указателя, а другие биты кода с указателями на один и тот же объект будут видеть изменения. Совместное использование неизменяемых объектов может сэкономить память по сравнению с множеством разных объектов, все из которых имеют свою собственную копию тех же данных, которые им нужны.

В С++ также есть что-то, что он называет "ссылками", которые используют эти свойства для косвенности, но не являются такими же, как ссылки на Java. Они также не совпадают с указателями на С++ (еще один вопрос).

"Меня поразила мысль, что я должен был заниматься этим понятием до"

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

Если вы хотите имитировать модель памяти C на Java или С#, я полагаю, вы создали бы очень большой массив байтов. Указатели будут индексами в массиве. Загрузка указателя int из указателя будет включать в себя 4 байта, начиная с этого индекса, и умножая их на последовательные полномочия 256, чтобы получить общее количество (как это происходит при десериализации int из байта в Java). Если это звучит как смехотворная вещь, то это потому, что раньше вы не рассматривали концепцию, но тем не менее это то, что ваше оборудование делало все вместе в ответ на ваш код Java и С# [*]. Если вы этого не заметили, то это потому, что эти языки отлично справились с созданием других абстракций для вас.

Буквально самым близким языком Java к "адресу объекта" является то, что по умолчанию hashCode in java.lang.Object, согласно документам, "обычно реализуется путем преобразования внутреннего адреса объекта в целое число". Но в Java вы не можете использовать хэш-код объекта для доступа к объекту. Вы, конечно же, не можете добавить или вычесть небольшое число в хэш-код для доступа к памяти внутри или рядом с исходным объектом. Вы не можете ошибаться, когда считаете, что ваш указатель ссылается на объект, на который вы его намереваетесь, но на самом деле это относится к некоторому совершенно несвязанной ячейке памяти, значение которой вы собираетесь писать на всем протяжении. В С++ вы можете делать все это.

[*] ну, не умножая и добавляя 4 байта, чтобы получить int, даже не сдвигая и ORing, а "загружая" int из 4 байтов памяти.

Ответ 4

Объясните разницу между стеком и кучей и куда идут объекты.

Типы значений, такие как structs (как С++, так и С#), идут в стек. Типы ссылок (экземпляры классов) попадают в кучу. Указатель (или ссылка) указывает на ячейку памяти в куче для этого конкретного экземпляра.

Тип ссылки - ключевое слово. Использование указателя в С++ похоже на использование ключевого слова ref в С#.

Управляемые приложения упрощают работу с этим материалом, поэтому разработчики .NET избавлены от хлопот и путаницы. Рад, что я больше не занимаюсь С.

Ответ 5

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

Рассмотрим следующий код С#:

public class A
{
    public int x;
}

public void AnotherFunc(A a)
{
    a.x = 2;
}

public void SomeFunc()
{
    A a = new A();
    a.x = 1;

    AnotherFunc(a);
    // a.x is now 2
}

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

В С++ мы используем указатели, чтобы сделать это явным:

class A
{
public:
    int x;
};

void AnotherFunc(A* a) // notice we are pointing to an existing instance of A
{
    a->x = 2;
}

void SomeFunc()
{
    A a;
    a.x = 1;

    AnotherFunc(&a);
    // a.x is now 2
}

Ответ 6

"Как объяснить указатели с помощью только понятий, знакомых разработчику .NET или Java?" Я бы предположил, что есть действительно две разные вещи, которые нужно изучить.

Во-первых, как использовать указатели и кучу выделенной памяти для решения конкретных проблем. В соответствующем стиле, используя shared_ptr < > , например, это можно сделать аналогично тому, как это делается в Java. У shared_ptr < > есть много общего с дескриптором объекта Java.

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

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

Ответ 7

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

Каждый квадрат на листе бумаги представляет собой один байт.

Один лист - это стек.

Другой лист - это куча. Дайте кучу своему другу - он менеджер памяти.

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

Готов?

void main() {
    int  a;                       /* Take four bytes from the stack. */
    int *b = malloc(sizeof(int)); /* Take four bytes from the heap. */

    a = 1;  /* Write on your first little bit of graph paper, WRITE IT! */
    *b = 2; /* Get writing (on the other bit of paper) */

    b = malloc(sizeof(int)); /* Take another four bytes from the heap. 
                                Throw the first 'b' away. Do NOT give it 
                                back to your friend */

    free(b); /* Give the four bytes back to your friend */
    *b = 3;  /* Your friend must now kill you and bury the body */
} /* Give back the four bytes that were 'a' */

Попробуйте выполнить несколько более сложных программ.

Ответ 8

В С# все ссылки на классы примерно эквивалентны указателям в мире С++. Для типов значений (structs, ints и т.д.) Это не так.

С#:

void func1(string parameter)
void func2(int parameter)

С++:

void func1(string* parameter)
void func2(int parameter)

Передача параметра с использованием ключевого слова ref в С# эквивалентна передаче параметра по ссылке в С++.

С#:

void func1(ref string parameter)
void func2(ref int parameter)

С++:

void func1((string*)& parameter)
void func2(int& parameter)

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

Ответ 9

Ключом для меня было понять, как работает память. Переменные хранятся в памяти. Места, в которые вы можете поместить переменные в память, нумеруются. Указатель - это переменная, которая содержит это число.

Ответ 10

Любой программист на С#, который понимает семантические различия между классами и структурами, должен уметь понимать указатели. I.e., объясняя в терминах значения по сравнению с ссылочной семантикой (в терминах .NET), следует получить точку; Я бы не усложнил ситуацию, пытаясь объяснить в терминах ref (или out).