Как передать объекты в функции в C ++? - PullRequest
240 голосов
/ 26 января 2010

Я новичок в программировании на C ++, но у меня есть опыт работы с Java. Мне нужно руководство о том, как передавать объекты в функции в C ++.

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

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

Ответы [ 7 ]

267 голосов
/ 26 января 2010

Практические правила для C ++ 11:

Пропуск по значению , кроме случаев, когда

  1. вам не нужно владеть объектом, и подойдет простой псевдоним, и в этом случае вы передадите const ссылку ,
  2. Вы должны мутировать объект, в этом случае используйте , передавая не-const значение lvalue ,
  3. вы передаете объекты производных классов как базовые классы, в этом случае вам нужно передать по ссылке . (Используйте предыдущие правила, чтобы определить, следует ли передавать по ссылке const или нет.)

Передача по указателю практически никогда не рекомендуется. Необязательные параметры лучше всего выражать как std::optional (boost::optional для старых стандартных библиотек), а сглаживание выполняется в порядке ссылки.

Семантика перемещения в C ++ 11 делает передачу и возврат по значению намного привлекательнее даже для сложных объектов.


Практические правила для C ++ 03:

Передача аргументов по const ссылка , кроме случаев, когда

  1. они должны быть изменены внутри функции, и такие изменения должны быть отражены снаружи, и в этом случае вы проходите мимо не-const ссылки
  2. функция должна вызываться без каких-либо аргументов, в этом случае вы передаете указатель, чтобы пользователи могли вместо нее передавать NULL / 0 / nullptr; примените предыдущее правило, чтобы определить, следует ли передавать указатель на const аргумент
  3. они имеют встроенные типы, которые могут быть переданы копией
  4. они должны быть изменены внутри функции, и такие изменения должны не отражаться снаружи, и в этом случае вы можете передать копию (альтернативой может быть передача в соответствии с предыдущими правилами и создание копии внутри функции)

(здесь «передача по значению» называется «передача по копии», поскольку передача по значению всегда создает копию в C ++ 03)


Это еще не все, но правила этих нескольких начинающих помогут вам довольно далеко.

105 голосов
/ 26 января 2010

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

Передать по ссылке

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

Передача по значению (и передача по указателю)

Компилятор сгенерирует копию объекта в вызывающем контексте и использует эту копию внутри функции. Все операции, выполняемые внутри функции, выполняются для копии, а не для внешнего элемента. Это соглашение для примитивных типов в Java.

Специальная версия передает указатель (адрес объекта) в функцию. Функция получает указатель, и любые и все операции, примененные к самому указателю, применяются к копии (указателю), с другой стороны, операции, примененные к разыменованному указателю, будут применяться к экземпляру объекта в этом месте памяти, поэтому функция может иметь побочные эффекты. Эффект использования передачи по значению указателя на объект позволит внутренней функции изменять внешние значения, как при передаче по ссылке, а также позволит использовать необязательные значения (передать нулевой указатель).

Это соглашение, используемое в C, когда функции необходимо изменить внешнюю переменную, и соглашение, используемое в Java с ссылочными типами: ссылка копируется, но указанный объект является тем же: изменения в ссылке / указателе за пределами функции не видны, но изменения в указанной памяти:

Добавление const к уравнению

В C ++ вы можете присваивать объектам постоянство при определении переменных, указателей и ссылок на разных уровнях. Вы можете объявить переменную постоянной, вы можете объявить ссылку на постоянный экземпляр, и вы можете определить все указатели на постоянные объекты, постоянные указатели на изменяемые объекты и постоянные указатели на постоянные элементы. И наоборот, в Java вы можете определить только один уровень константы (конечное ключевое слово): уровень переменной (экземпляр для примитивных типов, ссылка для ссылочных типов), но вы не можете определить ссылку на неизменный элемент (если сам класс не является неизменны).

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

Правила большого пальца

Вот некоторые основные правила, которым нужно следовать:

  • Предпочитать передачу по значению для примитивных типов
  • Предпочитают передачу по ссылке со ссылками на константу для других типов
  • Если функции необходимо изменить аргумент, используйте передачу по ссылке
  • Если аргумент является необязательным, используйте передачу по указателю (в константу, если необязательное значение не следует изменять)

Существуют и другие небольшие отклонения от этих правил, первое из которых касается владения объектом. Когда объект динамически размещается с новым, он должен быть освобожден с удалением (или его версиями []). Объект или функция, которая отвечает за уничтожение объекта, считается владельцем ресурса. Когда динамически размещенный объект создается в фрагменте кода, но право собственности передается другому элементу, это обычно делается с помощью семантики передачи по указателю или, если возможно, с помощью интеллектуальных указателей.

Примечание

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

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

Вышеуказанная функция подкачки изменяет оба своих аргумента с помощью ссылок. Ближайший код на Java:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

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

22 голосов
/ 19 октября 2010

Есть несколько случаев для рассмотрения.

Параметр изменен (параметры "out" и "in / out")

void modifies(T &param);
// vs
void modifies(T *param);

Этот случай в основном касается стиля: вы хотите, чтобы код выглядел как call (obj) или call (& obj) ? Однако есть две точки, где разница имеет значение: необязательный случай, приведенный ниже, и вы хотите использовать ссылку при перегрузке операторов.

... и необязательно

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Параметр не изменен

void uses(T const &param);
// vs
void uses(T param);

Это интересный случай. Практическое правило гласит, что типы «дешевого копирования» передаются по значению - обычно это небольшие типы (но не всегда), в то время как другие передаются по const ref. Тем не менее, если вам необходимо сделать копию внутри вашей функции независимо, вы должны передать значение . (Да, это раскрывает некоторые детали реализации. C'est le C ++. )

... и необязательно

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

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

Константа по значению - деталь реализации

void f(T);
void f(T const);

Эти объявления на самом деле являются точно такой же функцией! При передаче по значению const является просто деталью реализации. Попробуйте:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types
19 голосов
/ 06 ноября 2014

Передать по значению:

void func (vector v)

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

Недостатком является количество циклов ЦП и дополнительная память, затраченная на копирование объекта.

Передача по константной ссылке:

void func (const vector& v);

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

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

Передать по неконстантной ссылке:

void func (vector& v)

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

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

Передача по константному указателю:

void func (const vector* vp);

Функционально аналогично передаче по const-reference, за исключением другого синтаксиса, плюс тот факт, что вызывающая функция может передавать указатель NULL, чтобы указать, что она не имеет допустимых данных для передачи.

Не поточно-ориентирован.

Передача по неконстантному указателю:

void func (vector* vp);

Аналогично неконстантной ссылке. Вызывающая сторона обычно устанавливает переменную в NULL, когда функция не должна записывать значение. Это соглашение встречается во многих API glibc. Пример: * +1032 *

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Точно так же, как и все, передаваемые по ссылке / указателю, не потокобезопасны.

0 голосов
/ 04 декабря 2015

Ниже приведены способы передачи аргументов / параметров для работы в C ++.

1.по значению.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2.по ссылке.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3.по объекту.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}
0 голосов
/ 04 февраля 2015

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

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

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

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

Существует три способа передачи объекта в функцию в качестве параметра:

  1. Передать по ссылке
  2. передать по значению
  3. добавление константы в параметр

Просмотрите следующий пример:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Выход:

Скажи, что я нахожусь в каком-то Func
Значение указателя -17891602
Значение переменной 4

...