Como passar objetos para funções em C ++?

249

Eu sou novo na programação C ++, mas tenho experiência em Java. Preciso de orientações sobre como passar objetos para funções em C ++.

Preciso passar ponteiros, referências ou valores sem ponteiro e sem referência? Lembro que em Java não existem problemas, pois passamos apenas a variável que contém referência aos objetos.

Seria ótimo se você também pudesse explicar onde usar cada uma dessas opções.

Rakesh K
fonte
6
De qual livro você está aprendendo C ++?
17
Esse livro não é altamente recomendado. Vá para o C ++ Primer de Stan Lippman.
Prasoon Saurav
23
Bem, esse é o seu problema. Schildt é basicamente cr * p - obtenha Accelerated C ++ da Koenig & Moo.
9
Gostaria de saber como ninguém mencionou a linguagem de programação C ++ de Bjarne Stroustrup. Bjarne Stroustrup é o criador do C ++. Um livro realmente bom para aprender C ++.
George
15
@George: TC ++ PL não é para iniciantes, mas é considerada a Bíblia para C ++. XD
Prasoon Saurav

Respostas:

277

Regras práticas para C ++ 11:

Passe por valor , exceto quando

  1. você não precisa da propriedade do objeto e um alias simples será necessário; nesse caso, você passa por constreferência ,
  2. você deve alterar o objeto; nesse caso, use passar por uma constreferência sem valor ,
  3. você passa objetos de classes derivadas como classes base; nesse caso, você precisa passar por referência . (Use as regras anteriores para determinar se passa por constreferência ou não.)

Passar pelo ponteiro praticamente nunca é aconselhável. Os parâmetros opcionais são melhor expressos como umstd::optional ( boost::optionalpara bibliotecas std mais antigas) e o alias é feito bem por referência.

A semântica de movimentação do C ++ 11 torna a passagem e o retorno por valor muito mais atraentes, mesmo para objetos complexos.


Regras básicas para C ++ 03:

Passe argumentos por constreferência , exceto quando

  1. elas devem ser alteradas dentro da função e essas alterações devem ser refletidas fora; nesse caso, você passa por não constreferência
  2. a função deve ser solicitada sem nenhum argumento; nesse caso, você passa pelo ponteiro, para que os usuários possam passar NULL/ 0/ nullptr; aplique a regra anterior para determinar se você deve passar por um ponteiro para um constargumento
  3. eles são do tipo interno, que podem ser passados ​​por cópia
  4. elas devem ser alteradas dentro da função e essas alterações não devem ser refletidas externamente; nesse caso, você pode passar por cópia (uma alternativa seria passar de acordo com as regras anteriores e fazer uma cópia dentro da função)

(aqui, "passar por valor" é chamado "passar por cópia", porque passar por valor sempre cria uma cópia em C ++ 03)


Há mais do que isso, mas essas poucas regras para iniciantes o levarão muito longe.

sbi
fonte
17
+1 - Eu também observaria que alguns (isto é, Google) acham que os objetos que serão alterados dentro da função devem ser passados ​​por um ponteiro em vez de uma referência não-const. O raciocínio é que, quando o endereço de um objeto é passado para uma função, é mais evidente que a referida função pode alterá-lo. Exemplo: com referências, a chamada é foo (bar); se a referência é const ou não, com um ponteiro é foo (& bar); e é mais aparente que foo está passando um objeto mutável.
RC.
19
@RC Ainda não diz se o ponteiro é const ou não. As diretrizes do Google foram alvo de muitas críticas nas várias comunidades on-line em C ++ - justificadamente, IMHO.
14
Enquanto em outros contextos o Google pode estar liderando o caminho, em C ++, seu guia de estilo não é tão bom assim.
David Rodríguez - dribeas
4
@ArunSaha: Como um guia de estilo puro, a Stroustrup possui um guia que foi desenvolvido para uma empresa aeroespacial. Naveguei pelo guia do Google e não gostei por alguns motivos. Os Padrões de Codificação Sutter & Alexandrescu C ++ são um ótimo livro para ler e você pode obter alguns bons conselhos, mas não é realmente um guia de estilo . Não conheço nenhum verificador automático de estilo , além de humanos e bom senso.
David Rodríguez - dribeas 30/10
3
@anon No entanto, você tem a garantia de que, se um argumento não for passado por um ponteiro, NÃO será alterado. Esse é um IMHO bastante valioso; caso contrário, ao tentar rastrear o que acontece com uma variável em uma função, você deve examinar os arquivos de cabeçalho de todas as funções para as quais é passado para determinar se foi alterado. Dessa forma, você só precisa olhar para as que foram passadas via ponteiro.
precisa saber é o seguinte
107

Existem algumas diferenças nas convenções de chamada em C ++ e Java. Em C ++, tecnicamente, existem apenas duas convenções: passagem por valor e passagem por referência, com alguma literatura incluindo uma terceira convenção de passagem por ponteiro (que é na verdade passagem por valor de um tipo de ponteiro). Além disso, você pode adicionar constância ao tipo de argumento, aprimorando a semântica.

Passe por referência

Passar por referência significa que a função receberá conceitualmente sua instância do objeto e não uma cópia dela. A referência é conceitualmente um alias para o objeto que foi usado no contexto de chamada e não pode ser nulo. Todas as operações executadas dentro da função se aplicam ao objeto fora da função. Esta convenção não está disponível em Java ou C.

Passagem por valor (e passagem por ponteiro)

O compilador irá gerar uma cópia do objeto no contexto de chamada e usar essa cópia dentro da função. Todas as operações executadas dentro da função são feitas na cópia, não no elemento externo. Esta é a convenção para tipos primitivos em Java.

Uma versão especial dele está passando um ponteiro (endereço do objeto) para uma função. A função recebe o ponteiro e todas e quaisquer operações aplicadas ao ponteiro são aplicadas à cópia (ponteiro), por outro lado, as operações aplicadas ao ponteiro sem referência serão aplicadas à instância do objeto nesse local da memória, portanto, a função pode ter efeitos colaterais. O efeito do uso da passagem por valor de um ponteiro para o objeto permitirá que a função interna modifique valores externos, como ocorre com a passagem por referência e também permitirá valores opcionais (passe um ponteiro nulo).

Esta é a convenção usada em C quando uma função precisa modificar uma variável externa e a convenção usada em Java com tipos de referência: a referência é copiada, mas o objeto referido é o mesmo: as alterações na referência / ponteiro não são visíveis fora a função, mas as alterações na memória apontada são.

Adicionando const à equação

No C ++, você pode atribuir constante a objetos ao definir variáveis, ponteiros e referências em diferentes níveis. Você pode declarar uma variável como constante, pode declarar uma referência para uma instância constante e pode definir todos os ponteiros para objetos constantes, ponteiros constantes para objetos mutáveis ​​e ponteiros constantes para elementos constantes. Por outro lado, em Java, você pode definir apenas um nível de constante (palavra-chave final): o da variável (instância para tipos primitivos, referência para tipos de referência), mas não é possível definir uma referência para um elemento imutável (a menos que a própria classe seja imutável).

Isso é amplamente usado em convenções de chamada C ++. Quando os objetos são pequenos, você pode passar o objeto por valor. O compilador irá gerar uma cópia, mas essa cópia não é uma operação cara. Para qualquer outro tipo, se a função não alterar o objeto, você pode passar uma referência para uma instância constante (geralmente chamada referência constante) do tipo. Isso não copiará o objeto, mas passará para a função Mas, ao mesmo tempo, o compilador garantirá que o objeto não seja alterado dentro da função.

Regras de ouro

Estas são algumas regras básicas a seguir:

  • Preferir passagem por valor para tipos primitivos
  • Prefira passagem por referência com referências a constante para outros tipos
  • Se a função precisar modificar o argumento, use passagem por referência
  • Se o argumento for opcional, use passagem por ponteiro (para constante se o valor opcional não deve ser modificado)

Existem outros pequenos desvios dessas regras, a primeira delas é lidar com a propriedade de um objeto. Quando um objeto é alocado dinamicamente com new, ele deve ser desalocado com delete (ou suas versões []). O objeto ou função responsável pela destruição do objeto é considerado o proprietário do recurso. Quando um objeto alocado dinamicamente é criado em um trecho de código, mas a propriedade é transferida para um elemento diferente, geralmente isso é feito com semântica de passagem por ponteiro ou, se possível, com ponteiros inteligentes.

Nota

É importante insistir na importância da diferença entre as referências C ++ e Java. No C ++, as referências são conceitualmente a instância do objeto, não um acessador. O exemplo mais simples é implementar uma função de troca:

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

A função de troca acima altera ambos os seus argumentos através do uso de referências. O código mais próximo em 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
   }
}

A versão Java do código modificará as cópias das referências internamente, mas não modificará os objetos reais externamente. As referências Java são ponteiros C sem aritmética de ponteiro que são passados ​​por valor para funções.

David Rodríguez - dribeas
fonte
4
@ david-rodriguez-dribeas Eu gosto da seção de regras básicas, especialmente "Preferir valor por valor para tipos primitivos"
yadab 13/10/10
De acordo comigo, esta é uma resposta muito melhor para a pergunta.
precisa saber é o seguinte
22

Existem vários casos a considerar.

Parâmetro modificado (parâmetros "out" e "in / out")

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

Este caso é principalmente sobre estilo: você deseja que o código pareça chamar (obj) ou chamar (& obj) ? No entanto, há dois pontos em que a diferença importa: o caso opcional, abaixo, e você deseja usar uma referência ao sobrecarregar os operadores.

... e opcional

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

Parâmetro não modificado

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

Este é o caso interessante. A regra geral é "copiar barato" tipos são passados ​​por valor - geralmente são tipos pequenos (mas nem sempre) - enquanto outros são passados ​​por const ref. No entanto, se você precisar fazer uma cópia dentro de sua função, deve passar por valor . (Sim, isso expõe um pouco de detalhes da implementação. C'est le C ++. )

... e opcional

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

Aqui há a menor diferença entre todas as situações; portanto, escolha o que facilitar sua vida.

Const por valor é um detalhe de implementação

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

Essas declarações são exatamente a mesma função! Ao passar por valor, const é puramente um detalhe de implementação. Experimente:

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

fonte
3
+1 Eu não sabia constser uma implementação ao passar por valor.
balki
20

Passe por valor:

void func (vector v)

Passe variáveis ​​por valor quando a função precisar de um isolamento completo do ambiente, ou seja, para impedir que a função modifique a variável original e também para outros threads modificarem seu valor enquanto a função estiver sendo executada.

A desvantagem são os ciclos da CPU e a memória extra gasta para copiar o objeto.

Passe por referência const:

void func (const vector& v);

Este formulário emula o comportamento de passagem por valor ao remover a sobrecarga de cópia. A função obtém acesso de leitura ao objeto original, mas não pode modificar seu valor.

A desvantagem é a segurança do thread: qualquer alteração feita no objeto original por outro thread aparecerá dentro da função enquanto ela ainda estiver em execução.

Passe por referência não const:

void func (vector& v)

Use isso quando a função precisar gravar algum valor na variável, que será usada pelo chamador.

Assim como o caso de referência const, isso não é seguro para threads.

Passe pelo ponteiro const:

void func (const vector* vp);

Funcionalmente, o mesmo que passar por referência constante, exceto pela sintaxe diferente, além do fato de que a função de chamada pode passar o ponteiro NULL para indicar que não possui dados válidos para passar.

Não é seguro para threads.

Passe pelo ponteiro não const:

void func (vector* vp);

Semelhante à referência não-const. O chamador normalmente define a variável como NULL quando a função não deve gravar um valor novamente. Essa convenção é vista em muitas APIs glibc. Exemplo:

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

Assim como todos passam por referência / ponteiro, não são seguros para threads.

nav
fonte
0

Como ninguém mencionou a adição, quando você passa um objeto para uma função em c ++, o construtor de cópia padrão do objeto é chamado se você não tiver um que crie um clone do objeto e depois o passe para o método, portanto quando você altera os valores do objeto que refletirão na cópia do objeto em vez do objeto original, esse é o problema em c ++. Portanto, se você fizer com que todos os atributos da classe sejam ponteiros, os construtores de cópia copiarão os endereços dos atributos do ponteiro, portanto, quando o método invoca o objeto que manipula os valores armazenados nos endereços dos atributos do ponteiro, as alterações também refletem no objeto original que é passado como parâmetro, para que isso possa se comportar como um Java, mas não esqueça que toda a sua classe atributos devem ser ponteiros, você também deve alterar os valores dos ponteiros,ficará muito claro com a explicação do código.

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
}

Mas isso não é uma boa ideia, pois você acabará escrevendo muito código envolvendo ponteiros, propensos a vazamentos de memória e não se esqueça de chamar destruidores. E para evitar isso, o c ++ possui construtores de cópia nos quais você cria uma nova memória quando os objetos que contêm ponteiros são passados ​​para argumentos de função que param de manipular dados de outros objetos, o Java passa por valor e o valor é referência, portanto, não requer construtores de cópia.

murali krish
fonte
-1

Existem três métodos para passar um objeto para uma função como parâmetro:

  1. Passe por referência
  2. passar por valor
  3. adicionando constante no parâmetro

Siga o exemplo a seguir:

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

Resultado:

Digamos que eu esteja em someFunc
O valor do ponteiro é -17891602
O valor da variável é 4

Amerr
fonte
Existem apenas 2 métodos (os 2 primeiros que você mencionou). Não faço idéia do que você quis dizer com "passando constante no parâmetro"
MM
-1

A seguir estão as maneiras de passar argumentos / parâmetros para funcionar em C ++.

1. por valor.

// passing parameters by value . . .

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

2. por referência.

// 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. por objeto.

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();
}
Yogeesh HT
fonte
1
"passar por objeto" não é uma coisa. Há apenas passagem por valor e passagem por referência. Seu "caso 3" na verdade mostra um caso de passagem por valor e um caso de passagem por referência.
MM