Вопрос

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

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

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

Это было полезно?

Решение

Эмпирические правила для С++11:

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

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

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

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


Эмпирические правила для С++03:

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

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

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


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

Другие советы

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

Перейти по ссылке

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

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

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

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

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

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

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

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

Эмпирические правила

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

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

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

Примечание

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

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

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

Параметр изменен (параметры «выход» и «вход/выход»)

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

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

... и опционально

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++.)

... и опционально

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

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

void func (vector v)

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

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

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

void func (const vector& v);

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

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

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

void func (vector& v)

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

Как и в случае с константной ссылкой, это не является потокобезопасным.

Передача указателя const:

void func (const vector* vp);

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

Не потокобезопасный.

Передача неконстантного указателя:

void func (vector* vp);

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

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

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

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

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

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

Выход:

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

Ниже приведены способы передачи аргументов/параметров функции в 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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top