Добавлено преимущество указателя, когда его использовать и почему - PullRequest
1 голос
/ 06 февраля 2010

В данный момент я изучаю C ++, и хотя я понимаю концепцию указателей и ссылок для лучшей части, некоторые вещи неясны. Скажем, у меня есть следующий код (предположим, что прямоугольник действителен, фактический код не важен):

#include <iostream>
#include "Rectangle.h"

void changestuff(Rectangle& rec);

int main()
{
    Rectangle rect;
    rect.set_x(50);
    rect.set_y(75);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << sizeof(rect) << std::endl;
    changestuff(rect);

    std::cout << "x,y: " << rect.get_x() << rect.get_y() << std::endl;
    Rectangle* rectTwo = new Rectangle();
    rectTwo->set_x(15);
    rectTwo->set_y(30);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << std::endl;
    changestuff(*rectTwo);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << std::endl;
    std::cout << rectTwo << std::endl;
}

void changestuff(Rectangle& rec)
{
    rec.set_x(10);
    rec.set_y(11);
}

Теперь фактический объект Rectangle не передается, просто ссылка на него; это адрес. Почему я должен использовать 2-й метод по сравнению с первым? Почему я не могу передать rectTwo к изменениям, кроме * rectTwo? Чем rectTwo отличается от rect?

Ответы [ 6 ]

4 голосов
/ 06 февраля 2010

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

Давайте возьмем ваш код и добавим в него новую функцию:

#include <iostream>
#include "Rectangle.h"

void changestuff(Rectangle& rec);
void changestuffbyPtr(Rectangle* rec);

int main()
{
    Rectangle rect;
    rect.set_x(50);
    rect.set_y(75);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << sizeof(rect) << std::endl;
    changestuff(rect);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << std::endl;

    changestuffbyPtr(&rect);
    std::cout << "x,y: " << rect.get_x() << rect.get_y() << std::endl;

    Rectangle* rectTwo = new Rectangle();
    rectTwo->set_x(15);
    rectTwo->set_y(30);
    std::cout << "x,y: " << rectTwo->get_x() << rectTwo->get_y() << std::endl;
    changestuff(*rectTwo);
    std::cout << "x,y: " << rectTwo->get_x() << rectTwo->get_y() << std::endl;

    changestuffbyPtr(rectTwo);
    std::cout << "x,y: " << rectTwo->get_x() << rectTwo->get_y() << std::endl;
    std::cout << rectTwo << std::endl;
}

void changestuff(Rectangle& rec)
{
    rec.set_x(10);
    rec.set_y(11);
}

void changestuffbyPtr(Rectangle* rec)
{
    rec->set_x(10);
    rec->set_y(11);
}

Разница между использованием стека и кучи:

 #include <iostream>
#include "Rectangle.h"

Rectangle* createARect1();
Rectangle* createARect2();

int main()
{
    // this is being created on the stack which because it is being created in main,
    // belongs to the stack for main. This object will be automatically destroyed 
    // when main exits, because the stack that main uses will be destroyed.
    Rectangle rect;

    // rectTwo is being created on the heap. The memory here will *not* be released
    // after main exits (well technically it will be by the operating system)
    Rectangle* rectTwo = new Rectangle();

    // this is going to create a memory leak unless we explicitly call delete on r1.
    Rectangle* r1 = createARectangle();

    // this should cause a compiler warning:
    Rectangle* r2 = createARectangle();
}

Rectangle* createARect1()
{
    // this will be creating a memory leak unless we remember to explicitly delete it:
    Rectangle* r = new Rectangl;
    return r;
}

Rectangle* createARect2()
{
    // this is not allowed, since when the function returns the rect will no longer
    // exist since its stack was destroyed after the function returns:
    Rectangle r;
    return &r;
}

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

int *b;

, а это не так:

int& b;

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

// let's assume A is some interface:
class A 
{
public:
    void doSomething() = 0;
}

class B : public A
{
public:
    void doSomething() {}
}

class C : public A
{
public:
    void doSomething() {}
}

int main()
{
    // since A contains a pure virtual function, we can't instantiate it. But we can    
    // instantiate B and C
    B* b = new B;
    C* c = new C;

    // or
    A* ab = new B;
    A* ac = new C;

    // but what if we didn't know at compile time which one to create? B or C?
    // we have to use pointers here, since a reference can't point to null or
    // be uninitialized
    A* a1 = 0;
    if (decideWhatToCreate() == CREATE_B)
        a1 = new B;
    else
        a1 = new C;
}
3 голосов
/ 06 февраля 2010

В C ++ объекты могут быть размещены в куче или в стеке. Стек действителен только локально, то есть когда вы покидаете текущую функцию, стек и все содержимое будут уничтожены.

Напротив, объекты кучи (которые должны быть специально выделены с помощью new) будут жить до тех пор, пока вы не delete их.

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

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

2 голосов
/ 06 февраля 2010

Вы должны понимать, что ссылки НЕ являются указателями. Они могут быть реализованы с их использованием (или не могут), но ссылка в C ++ - это совсем не то, что указатель.

При этом любая функция, которая берет ссылку, может использоваться с указателями просто путем разыменования их (и наоборот). Дано:

class A {};
void f1( A & a ) {}     // parameter is reference
void f2( A * a ) {}     // parameter is pointer

Вы можете сказать:

A a;
f1( a )
f2 ( &a );

и

A * p = new A;
f1( *a )
f2 ( a );

Что вы должны использовать, когда? Хорошо, что сводится к опыту, но общая хорошая практика такова:

  • предпочитают размещать объекты в стеке автоматически, а не по возможности new
  • передавать объекты, используя ссылки (предпочтительно константные ссылки) всякий раз, когда это возможно
1 голос
/ 06 февраля 2010

rectTwo отличается от rect тем, что rect является экземпляром Rectangle в стеке, а rectTwo является адресом Rectangle в куче. Если вы передадите Rectangle по значению, его копия будет сделана, и вы не сможете вносить какие-либо изменения, существующие за пределами области действия changestuff().

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

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

Из Википедии:

Невозможно напрямую обратиться к эталонному объекту после того, как он определены; любое вхождение его имени относится непосредственно к объекту это ссылки.

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

Ссылки не могут быть нулевыми, тогда как указатели могут; каждая ссылка относится к некоторому объекту, хотя это может или может не быть действительным.

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

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

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

0 голосов
/ 06 февраля 2010

Вы правы, говоря, что реальный объект Rectangle не передан, просто ссылка на него. На самом деле вы никогда не сможете «передать» какой-либо объект или что-либо еще на самом деле. Вы можете только «передать» копию чего-либо в качестве параметра функции.

То, что вы можете передать, может быть копией значения, например int, или копией объекта, или копией указателя или ссылки. Поэтому, на мой взгляд, передача копии либо указателя, либо ссылки - это логически одно и то же - синтаксически он другой, следовательно, параметр является либо rect, либо *rectTwo.

Ссылки в C ++ являются явным преимуществом перед C, поскольку они позволяют программисту объявлять и определять операторы, которые синтаксически выглядят идентично тем, которые доступны для целых чисел.
например. форма: a=b+c может использоваться для int с или Rectangle с.

Вот почему вы можете иметь changestuff(rect);, поскольку параметр является ссылкой, а ссылка на (указатель на) прямоугольника берется автоматически. Когда у вас есть указатель Rectangle* rectTwo;, он сам по себе является «объектом», и вы можете работать с ним, например, переназначать или увеличивать его. C ++ решил не преобразовывать это в ссылку на объект, вы должны сделать это вручную, разыменовав указатель, чтобы добраться до объекта, который затем автоматически преобразуется в ссылку. Вот что означает *rectTwo: разыменование указателя.

Итак, rectTwo - это указатель на Rectangle, но rect - это прямоугольник или ссылка на Rectangle.

0 голосов
/ 06 февраля 2010

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

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

Примеры доменов:

  1. Базы данных предприятия.
  2. Дизайн ядра.
  3. Драйверы.
  4. Линейная алгебра общего назначения.
  5. Сериализация двоичных данных.
  6. Распределители Slab Memory для обработки транзакций (веб-серверы).
  7. Движки для видеоигр.
  8. Встроенное программирование в реальном времени.
  9. Обработка изображений
  10. Функции Unicode Utility.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...