Как передать объекты в функции на С++?

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

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

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

Ответ 1

Правила для С++ 11:

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

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

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

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


Правила для С++ 03:

Передайте аргументы с помощью const справки, за исключением случаев, когда

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

(здесь "pass by value" называется "pass by copy", потому что передача по значению всегда создает копию в С++ 03)


Там больше, но эти несколько правил для начинающих доставят вас довольно далеко.

Ответ 2

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

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

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

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

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

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

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

Добавление константы в уравнение

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

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

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

Это следующие основные правила:

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

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

Боковое примечание

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

// 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-указатели без арифметики указателей, которые передаются по значению в функции.

Ответ 3

Можно рассмотреть несколько случаев.

Параметры, измененные ( "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);

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

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

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);

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

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

Ответ 4

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

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. Пример:

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

Как и все, передаются по ссылке/указателю, не потокобезопасны

Ответ 5

Поскольку никто не упоминает, я добавляю его. Когда вы передаете объект функции в С++, конструктор экземпляра по умолчанию объекта вызывается, если у вас нет того, который создает клон объекта, а затем передает его в метод, поэтому, когда вы изменяете значения объектов, которые будут отображаться на копии объекта вместо исходного объекта, это проблема в С++. Поэтому, если вы сделаете все атрибуты класса указателями, то конструкторы копирования будут копировать адреса атрибутов указателя, поэтому при вызове метода на объект, который управляет значениями, хранящимися в адресах атрибутов указателя, изменения также отражают исходный объект, который передается как параметр, поэтому он может вести себя одинаково с 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
}

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

Ответ 6

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

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

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

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;
}

Вывод:

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

Ответ 7

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

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();
}