Как вы объясните C ++ указатели разработчику C # / Java? - PullRequest
16 голосов
/ 03 марта 2011

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

Ответы [ 10 ]

16 голосов
/ 03 марта 2011

Java-объекты в C ++

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

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

C ++ объекты.

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

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

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

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

6 голосов
/ 03 марта 2011

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

Лучший способ решить проблему - подумать о том, как работает компьютер. Забудьте все причудливые вещи .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. Получение значения называется разыменование .

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

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

char *str = "hello, world!";

Что говорит следующее:

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

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

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

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

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

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

array[5]

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

*(array + 5)
4 голосов
/ 03 марта 2011

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

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

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

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

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

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

C ++ также имеет то, что он называет «ссылками», которые совместно используют эти свойства для косвенного обращения, но не совпадают со ссылками в Java. Они не такие же, как указатели в C ++ (это другой вопрос ).

«Меня поразила мысль, что я должен был разобраться с этой концепцией раньше»

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

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

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

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

2 голосов
/ 03 марта 2011

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

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

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

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

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

Готов?

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' */

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

2 голосов
/ 03 марта 2011

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

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

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

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

2 голосов
/ 03 марта 2011

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

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

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 (в отличие от типов значений, которые копируются).

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

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
}
2 голосов
/ 03 марта 2011

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

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

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

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

0 голосов
/ 03 марта 2011

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

0 голосов
/ 03 марта 2011

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

0 голосов
/ 03 марта 2011

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

C #:

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

C ++:

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

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

C #:

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

C ++:

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

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

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