Mandar um cafézinho para o programador:


Me ajude a transformar café em código!
Mostrando postagens com marcador Argumentos. Mostrar todas as postagens
Mostrando postagens com marcador Argumentos. Mostrar todas as postagens

Structs e Funções em C++

 Agora que já aprendemos como declarar e usar structs no tutorial passado, vamos ver neste como usar as estruturas de dados com as funções.

Structs em Parâmetros e Argumentos de uma Função

Quando explicamos que as structs e suas instâncias funcionam como variáveis (mas variáveis especiais, que você mesmo criou), isso vale também para funções.

Por exemplo, uma função que vai receber e retornar um inteiro, tem o seguinte cabeçalho:

  • int myFunction(int num);

Ou seja, especificamos o tipo de dado que a função vai receber. Até o momento trabalhamos com int, double, float, char, ponteiros...agora vamos usar o tipo de dado...que nós mesmos vamos criar!

Por exemplo, uma função que vai receber a struct Student, do tutorial anterior, e exibir seus elementos, tem o seguinte cabeçalho:

  • void showStudent(Student alumn);

Por exemplo, o programa abaixo exibe os dados de uma struct de nome 'alumn', do tipo 'Student':

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

int main()
{
    Student Alex;

    cout<<"Nome: ";
    cin.get(Alex.name,50);

    cout<<"Nota em Matemática: ";
    cin >> Alex.math;

    cout<<"Nota em Física: ";
    cin >> Alex.physics;

    cout<<"Nota em Ciências: ";
    cin >> Alex.science;

    showStudent(Alex);

    return 0;
}

Só para dizer que não é exatamente igual como fizemos com outras variáveis, note que nossa função usar a struct Student...ou seja, ela já deve EXISTIR antes de declararmos a função, por isso colocamos a declaração da struct ANTES da função que vai usar ela, entendido?

Retornando uma Struct de uma Função

Assim como as funções servem para receber informações e performar tarefas específicas, elas também servem para retornar informações. Até o momento, fizemos retorno com int, double, char, void...mas as funções podem retornar qualquer tipo de dado, desde que a sua struct tenha sido declarada antes.

A função getStudent() não recebe nenhum parâmetro/argumento, mas retorna uma variável do tipo Student, e é usada para retornar uma struct preenchida, veja como funciona:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

Student getStudent()
{
    Student alumn;

    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;

    return alumn;
}

int main()
{
    Student Geddy = getStudent();
    showStudent(Geddy);

    return 0;
}

Veja como nossa main() ficou enxuta, apenas declaramos um aluno, fazemos ele receber os dados da função getStudent(), e catapimbas, exibimos o seu conteúdo, tá feito.

Passagem por Valor e por Referência de Structs

Vamos criar uma função, chamada fillStudent() que tem como argumento uma instancia da struct Student, nessa função, vamos solicitar ao usuário os dados do aluno.

Nosso código fica:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

void fillStudent(Student alumn)
{
    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;
}

int main()
{
    Student Geddy;
    fillStudent(Geddy);
    showStudent(Geddy);

    return 0;
}

Na main(), criamos o estudante 'Geddy' e enviamos para a fillStudent() para seus dados serem preenchidos. Porém, quando invocamos a função showStudent(), ela exibe o seguinte resultado:

Dados:
Nome      : Ћ���
Matemática: 3.0844e-41
Física    : 6.11017e-29
Ciência   : 3.0844e-41

Ou seja, os membros da struct 'Geddy' não foram preenchidos! O motivo disso é que a passagem de structs foi feita por valor!

Quando fazemos fillStudent(Geddy), a função pega essa instância, cria uma cópia internamente na função, e altera os valores nessa cópia, e não na struct original.

E como fazer para essa função alterar, de fato, os membros da struct? Simples, basta fazer uma passagem por referência, da struct:

#include <iostream>
using namespace std;

struct Student
{
   char name[50];
   float math,
         physics,
         science;
};

void showStudent(Student alumn)
{
    cout<<"\nDados:"<<endl;
    cout<<"Nome      : "<<alumn.name<<endl;
    cout<<"Matemática: "<<alumn.math<<endl;
    cout<<"Física    : "<<alumn.physics<<endl;
    cout<<"Ciência   : "<<alumn.science<<endl;
}

void fillStudent(Student &alumn)
{
    cout<<"Nome: ";
    cin.get(alumn.name,50);

    cout<<"Nota em Matemática: ";
    cin >> alumn.math;

    cout<<"Nota em Física: ";
    cin >> alumn.physics;

    cout<<"Nota em Ciências: ";
    cin >> alumn.science;
}

int main()
{
    Student Geddy;
    fillStudent(Geddy);
    showStudent(Geddy);

    return 0;
}

Apenas adicionamos & ao parâmetro 'alumn'. Agora, toda struct que mandarmos para a função fillStudent() será alterada.

Sobrecarga de Funções: Parâmetros e Argumentos diferentes

Neste tutorial de nossa apostila de C++, vamos aprender o que é sobrecarga de funções, para que servem e como usar esta importante técnica de programação.

Tamanhos de Parâmetros e Argumentos em Funções no C++

No tutorial sobre Argumentos Padrão, vimos que é possível enviar uma quantidade variada de argumentos para uma função, desde que ela esteja usando parâmetros de referência.

Por exemplo, o código abaixo é de uma função que calcula uma média aritmética:
#include <iostream>
using namespace std;

float average(float a, float b, float c, float d = 0.0)
{
    return (a+b+c+d)/4;
}

int main()
{
    cout<<"Media de 3 numeros: "<<average(10, 9, 7)<<endl;
    cout<<"Media de 4 numeros: "<<average(10, 9, 7, 6)<<endl;

    return 0;
}
Ela pode receber 3 ou 4 argumentos. Se enviar apenas 3, o 4 argumento é o padrão, de valor 0.
Porém, note um erro.

Mesmo que enviemos apenas 3 argumentos, a média aritmética é calculada como se houvessem 4 notas.

Seria interessante se, caso eu enviasse 3 argumentos, ela retornasse:
(a+b+c)/3

E caso eu enviasse 4 argumentos, ela retornasse:
(a+b+c+d)/4

E isso é possível, com sobrecarga de funções.

Sobrecarga de Funções em C++

Sobrecarga, ou overloading, é a técnica que permite que tenhamos funções com mesmo nome, desde que seus parâmetros sejam diferentes.

Veja como ficaria o exemplo do programa que calcula as médias de 3 ou 4 notas:
#include <iostream>
using namespace std;

float average(float a, float b, float c)
{
    return (a+b+c)/3;
}

float average(float a, float b, float c, float d)
{
    return (a+b+c+d)/4;
}

int main()
{
    cout<<"Media de 3 numeros: "<<average(10, 9, 7)<<endl;
    cout<<"Media de 4 numeros: "<<average(10, 9, 7, 6)<<endl;

    return 0;
}
Note que invocamos a função average(), a única diferença é que na primeira chamada passamos 3 argumentos, e na segunda chamada da função passamos 4 argumentos.

Como o C++ é maroto e inteligente, ele sabe qual função rodar, corretamente.
Ele é tão esperto que, mesmo que exista o mesmo número de parâmetros/argumentos, ele consegue diferenciar através do tipo de dado que estamos usando.

Veja:
#include <iostream>
using namespace std;

double average(double a, double b, double c)
{
    cout<<"Media de 3 double   : ";
    return (a+b+c)/3;
}

int average(int a, int b, int c)
{
    cout<<"Media de 3 inteiros : ";
    return (a+b+c)/3;
}

int main()
{
    cout<<average(10.0, 9.0, 7.0)<<endl;
    cout<<average(10, 9, 7)<<endl;

    return 0;
}
Quando passamos variáveis do tipo double, ele chama a double average()
Quando passamos variáveis do tipo int, ele chama a int average()

O C++ consegue diferenciar pois as assinaturas de cada função são diferentes (seja no número de parâmetros ou no tipo que vai trabalhar).

Aliás, até se as funções tiverem o mesmo nome, o mesmo número de parâmetros e os mesmos tipos de dados, podemos fazer sobrecarga de funções, desde que a ordem dos parâmetros seja diferente:
#include <iostream>
using namespace std;

void func(int a, double b)
{
    cout<<"Primeiro é int  : "<<a<<endl;
    cout<<"Segundo é double: "<<b<<endl;
}

void func(double b, int a)
{
    cout<<"Primeiro é double: "<<b<<endl;
    cout<<"Segundo é int    : "<<a<<endl;
}

int main()
{
    func(1, 2.5);
    cout<<endl;
    func(2.5, 1);

    return 0;
}
No exemplo assim temos: func(int, double)
E também: func(double, int)

Basta que a assinatura de uma função para outra seja diferente, para podermos usar sobrecarga, onde assinatura é um conjunto de dados: nome da função, tipo de dados, número de dados e ordem das informações.

No que se refere as boas práticas de programação, você deve usar sobrecarga de funções sempre que precisar usar funções com mesmo propósito e mesma lógica, mas para número e/ou tipos e/ou ordem de dados diferentes.

Parâmetros e Variáveis de Referência de Funções em C++

Neste tutorial de nosso curso de C++, vamos aprender a passagem de argumentos por referência, em funções.

Passagem por Valor

Já aprendemos como enviar informações para funções, através dos argumentos e parâmetros.
E vimos que esta passagem é chamada passagem por valor, pois só passamos o valor para a função.

Se enviamos uma variável pra uma função, e dentro dessa função essa variável é alterada, ela não é alterada fora da função. Teste o seguinte código, que eleva um número ao quadrado:
#include <iostream>
using namespace std;

void square(int num);

int main()
{
    int number = 6;

    cout<<"Antes  : num = "<<number<<endl;
    square(number);
    cout<<"Depois : num = "<<number<<endl;

    return 0;
}

void square(int num)
{
    num = num*num;
    cout<<"Durante: num = "<<num<<endl;
}
Quando invocamos a função: square(number), estamos na verdade, passando uma cópia do valor da variável number.

Dentro da função, a variável num vai receber uma cópia do valor de number. Ou seja, ela não vai ter acesso a variável original number, somente ao seu valor! Por isso, passagem por valor.

Parâmetro de Referência: &

Seja a declaração de variável:

  • int num = 6;


O que estamos fazendo aí é alocando, reservando, um espaço na memória do seu computador.
Assim, quando usamos 'num', o C++ entende que devemos ir no endereço para onde essa variável aponta e pegar o número que está dentro daquele local da memória.

Como vimos, quando passamos essa variável normal para uma função que espera uma variável normal, ela pega apenas uma cópia de seu valor, e não mexe no conteúdo original.

Porém, existe outro tipo especial de variável, a variável de referência.
Ela é especial, pois se passarmos uma variável para uma função e a função quiser obter a variável de referência, através de um parâmetro de referência, ela vai ter acesso ao endereço da memória (a referência), onde esta variável está originalmente armazenada.

Ou seja: vamos ter acesso, de verdade, a variável, e não somente a sua cópia.

Para pegarmos a referência de uma variável, basta usarmos o operador & antes do nome da variável, no parâmetro da função! É o parâmetro de referência. Tanto no protótipo quanto na declaração da função.

Assim, o exemplo de código anterior, fica:
#include <iostream>
using namespace std;

void square(int &);

int main()
{
    int number = 6;

    cout<<"Antes  : num = "<<number<<endl;
    square(number);
    cout<<"Depois : num = "<<number<<endl;

    return 0;
}

void square(int &num)
{
    num = num*num;
    cout<<"Durante: num = "<<num<<endl;
}
Veja agora o resultado:
Variável de referência em C++ : &

De fato, a função conseguiu mudar o valor da variável, pois essa passagem de argumento para o parâmetro foi via parâmetro de referência.

O que acontece aqui é que o parâmetro não vai capturar o valor do argumento, mas sim sua referência, para onde ele está apontando na memória. Falando em apontar para um endereço, estudaremos mais sobre esse assunto nas seções de ponteiros e arrays (vetores) em C++, onde estudaremos sobre passagem por referência, usando ponteiros.

Mais sobre parâmetros de referência em C++

Alguns programadores também preferem declarar o protótipo assim:

  • square (int&);

Você também pode usar assim no protótipo:

  • square(int &num);
  • square(int& num);


Também pode, o importante é que o & esteja tanto no protótipo quanto na declaração da função, ok?

Lembrando que se seu parâmetro é de referência, ele só pode trabalhar com variável de referência.
Seria um erro se você usar um argumento que não é uma variável, como um literal, uma expressão ou uma constante, por exemplo:

  • square(10); // tem que usar square(number);
  • square(number+1); // é uma expressão, evite
Quando fazemos: square(int &num)
Leia-se: "num é uma referência para um inteiro", ou seja, ele está referenciando, apontando, indicando um local da memória em que está armazenado um inteiro. Como você tem o local da variável original, você consegue mudar esse valor.

Exercício de passagem de valor e referência

Crie um programa que pede um inteiro ao usuário. Em seguida, ele deve transformar esse valor em seu cubo. Faça isso usando uma função que recebe passagem por valor e outra que usa parâmetro e variável de referência. Deixe bem claro que uma altera o valor somente dentro da função (valor) e a outra altera o valor original da variável (referência). 

Cole nos comentários, sua solução.

Argumentos Padrão e Omissão de Argumentos

Neste tutorial de C++, vamos aprender o que é um argumento padrão, para que serve e como usar, bem como omitir um argumento numa chamada de uma função.

Argumento Padrão em C++

Já aprendemos como enviar informações para funções, através do uso de parâmetros e argumentos.
Vamos supor que queiramos somar dois números, a e b, a função seria:
float sum2(float a, float b)
{
    return a+b;
}
Agora vamos supor que queiramos calcular a soma de três variáveis, teríamos que fazer uma função assim:
float sum3(float a, float b, float c)
{
    return a+b+c;
}
Note que teríamos que usar outra função, com outro nome, pro mesmo propósito: somar os argumentos. Não é algo muito inteligente, concorda?
Seria legal se uma mesma função somasse 2 ou 3 argumentos, o tanto que o usuário quiser.

Para somar dois números, seria interessante fazer: sum(1,2);
Para somar três números, faríamos: sum(1,2,3);

É aí que entra o conceito de argumento padrão.
Basta declararmos o protótipo da função assima:

  • float sum(float a, float b, float c = 0.0);

E seu escopo:
float sum(float a, float b, float c = 0.0)
{
    return a+b+c;
}
O que ocorre é o seguinte:
O valor padrão de c é 0.0

Caso você faça: sum(1,2, 3), o valor de c será 3.
Caso faça: suma (1,2), você não estará definindo valor para c, logo ele vai assumir o valor padrão, que é 0. Entendeu? Argumento com valor padrão, caso você não forneça esse valor.

Teste:
#include <iostream>
using namespace std;

float sum(float a, float b, float c = 0.0)
{
    return a+b+c;
}

int main()
{
    cout<<sum(1,2)<<endl;
    cout<<sum(1,2,3)<<endl;

    return 0;
}
Funciona pra 2 ou 3 variáveis, a gosto do freguês!

Omissão de argumentos

O código anterior funciona para somar 2 ou 3 números.
Para somarmos 2, 3 ou 4 números, poderíamos fazer:
float sum(float a, float b, float c = 0.0, float d = 0.0)
{
    return a+b+c+d;
}
Agora você pode fazer:
sum(1,2);
sum(1,2,3);
sum(1,2,3,4);

No primeiro caso, omitimos o argumento c e o d.
No segundo exemplo, omitimos o argumento d.
No último exemplo, não omitimos nenhum argumento.

Ou seja, argumentos padrões são automaticamente substituídos, quando informamos os argumentos.

Regras no uso de argumentos padrão

Quando informamos e passamos argumentos para uma função, eles são copiados da esquerda pra direita.

Por exemplo: sum(1,2,3)
O 1 vai pro 'a', o 2 pro 'b' e o valor 3 vai pro parâmetro 'c'. O valor do argumento 'd', então, é o argumento padrão, que definimos como 0.

Outra regra é que, uma vez que usemos um argumento padrão em um parâmetro, todos os outros parâmetros subsequentes deverão também ter argumentos padrão também. Por exemplo, o protótipo de função a seguir é válido:

  • float volume(float base, float height=1, float width=1);


'height' é um argumento padrão, e o seguinte também.

Já o seguinte protótipo de função é inválido:

  • float volume(float height=1, float base, float width=1);


Como o primeiro argumento é padrão, todos os outros devem ser, e o 'base' é um parâmetro normal, que deve ser necessariamente fornecido pela chamada da função.

Outro ponto é que os parâmetros com argumentos padrão devem ser declarados na primeira ocorrência da declaração da função. Ou seja, se você usar um protótipo de uma função e depois em outro lugar vai definir o escopo da sua função, os argumentos padrão devem ser definidos já no protótipo, que vai vir antes. Por exemplo, você pode inclusive abreviar assim:

Protótipo: double area( double = 1.0, float 2.0);
Definição da função: double area (double length, float witdh) { return length*width; }

Assim, recapitulando, seja a função:
double area (double length = 1.0, float witdh = 2.0) 
{ 
   return length*width; 
} 
Se fizermos as seguintes chamadas de função:

  1. area() - serão usados os valores 1.0 e 2.0 para calcular a área, respectivamente, ou seja, estamos usando os dois argumentos padrão.
  2. area(3) - serão usados os valores 3 e 2.0 para calcular a área, ou seja, o primeiro argumento padrão foi substituído por 3
  3. area(3,6) - serão usados os valores 3 e 6 para calcular a área, ou seja, os dois argumentos padrão foram substituídos.
Vamos ver isso na prática? Teste o seguinte código:

#include <iostream>
using namespace std;

void area(float = 1.0, float = 1.0);

int main()
{
    cout << "Nenhum argumento passado:"<<endl;
    area();

    cout << "\nPrimeiro argumento passado:"<<endl;
    area(2.0);

    cout << "\nAmbos argumentos passados:"<<endl;
    area(2.0, 3.0);

    return 0;
}

void area(float length, float width)
{
    cout << "Area: " << length * width << endl;
}

Deu tudo certo? Sim? Então vamos seguir em nossos tutoriais de C++.

Enviando dados para Funções em C++ - Parâmetros e Argumentos

Neste tutorial, vamos aprender como enviar dados para as funções, em linguagem de programação C++.

Enviando Dados para Funções

No tutorial passado, de nosso curso de C++, aprendemos como as funções retornam informações para quem as invocou, através do comando return.

As funções podem receber informações através do uso de parâmetros, que é uma variável especial, declarada no cabeçalho da função, que ficará responsável por armazenar os valores que serão passados para estas funções. Esses valores são os argumentos.

Para usarmos os argumentos (ou seja, passarmos algum valor para funções trabalharem), devemos informar a lista de parâmetros, dentro dos parêntesis da declaração da função.

Por exemplo, uma função que retorna um inteiro, recebe um inteiro e se chama func, se declara assim:

  • int func(int var) { ... }


Para invocar, passando o número 2112, por exemplo, fazemos:

  • func(2112);


var é o parâmetro, já 2112 é um argumento.

Agora uma função que não retorna nada, se chama func2 e recebe um inteiro, um float e um booleano:

  • void func2(int var, float num, bool status) { ... }


Se quisermos passar o inteiro 1, o float 21.12 e o booleano true, para dentro desta função, fazemos:

  • func2(1, 21.12, true);


Lista de parâmetros: var, num e status
Argumentos (valores): 1, 21.12 e true

Veja a ordem: um inteiro, um float e um booleano! Respeite a ordem da lista de parâmetros do cabeçalho da função!

Exemplo de parâmetros e argumentos em C++

Vamos criar uma função que recebe um número qualquer, e retorna ele ao quadrado:
#include <iostream>
using namespace std;

float quadrado(float num)
{
    return num*num;
}

int main()
{
    float var;

    cout<<"Numero para elevar ao quadrado: ";
    cin >> var;

    cout<<var<<"*"<<var<<" = " << quadrado(var)<<endl;
}
Note que passamos a variável var, que é um float, para a função quadrado(), que recebe um float como argumento.

Embora tenhamos passado a variável var, o que acontece na verdade, por debaixo dos panos, é que passamos um número, um valor, que foi digitado pelo usuário.

Se passarmos var = 5, o que vai pra função é 5.
É como se tivéssemos feito: quadrado(5);

Dentro da função, esse valor é armazzenado no parâmetro num, ok?
A função não 'vê' a variável var, nem sabe de sua existência, só pega seu valor e copia para a variável float num.

Parâmetros e Argumentos em C++

Vamos agora criar uma função que recebe um número, eleva ele ao cubo e retorna esse valor.
#include <iostream>
using namespace std;

float cubo(float num)
{
    num = num*num*num;

    return num;
}

int main()
{
    float num;

    cout<<"Numero para elevar ao cubo: ";
    cin >> num;

    cout<<"Numero informado: "<<num<<endl;
    cout<<"Cubo: "<< cubo(num)<<endl;
    cout<<"Valor de num: "<<num<<endl;
}
Fizemos um teste nesse exemplo.
O parâmetro é num, dentro da função.
Ao invocar a função, também passar uma variável de nome num.

Dentro da função, alteramos o valor de num.
Antes, era num, depois passa a ser (num*num*num), e retornamos esse novo valor de num.

Na main(), vamos exibir o número que o usuário digitar (num), o cubo (invocando a função cubo() ), e depois exibimos novamente o valor de num.

Para num=4, o resultado é:
Como passar um argumento em uma função no C++

Note que o valor de num foi alterado SOMENTE dentro da função!
Quando passamos um argumento pra uma função, a função faz uma cópia dele e atribui ao parâmetro específico. Dentro da função, você alterou o valor do parâmetro, a variável num original, não é alterada!

Ah...agora você já consegue entender um pouco mais a main():
int main(){
...
   return 0;
}

Ou seja, ela é uma função que não recebe nenhum argumento (pois não tem parâmetro) e retorna um inteiro, no caso, ela retorna 0.

Dois adendos:

  1. Universalmente, quando uma função retorna 0, é porque deu tudo certo, tudo ok. Quando seu código funcionar ok, deverá retorna 0
  2. Nesse caso específico, a main() não tem parâmetros, mas tem versões dela recebendo argumentos sim, veremos mais adiante em nosso curso

Ajude o C++ Progressivo

Que tal apoiar e fazer crescer o ensino da programação no Brasil ?

Ajudar nosso país a crescer e se desenvolver cada vez mais, tecnologicamente?

Clica abaixo pra saber mais!

Apoiar o Projeto Progressivo