paulo1205
(usa Ubuntu)
Enviado em 02/10/2014 - 14:11h
Sem um parâmetro auxiliar, em C, não dá para fazer. Pegue exemplos de funções como
printf () e
scanf (). O string de formatação é a única forma pela qual a função tem como saber como lidar com os demais argumentos.
Note, porém, que todas as possibilidades têm de estar previstas de antemão. Se você quiser estender sua função para outros tipos de dados além dos simples tipos nativos, teria de colocar dentro dessa função o código específico para cada tipo de dados derivado que você viesse a criar. No fim das contas, sua tentativa de simplificação se tornaria um pesadelo, dando mais trabalho de implementação e manutenção do que daria criar uma função separada para cada tipo.
Caminhos que você pode seguir dependem do que você quer ou precisa fazer. Se você trabalhar basicamente com expressões matemáticas relativamente simples e tipos nativos, macros do preprocessador podem ser suficientes. Por exemplo:
/*
O tipo devolvido por ABS() é, por construção, o mesmo tipo do argumento
com que for chamada.
*/
#define ABS(x) ((x)<0? -(x): (x))
/*
SIGNAL(x) pode ser chamada com qualquer tipo nativo, mas vemos que,
também por construção, qualquer valor devolvido será do tipo “int”.
*/
#define SIGNAL(x) ((x)<0? -1: (x)>0? 1: 0)
Macros porém têm seus problemas. Um deles é que é possível indefinir e redefinir seu significado, sem que o compilador emita qualquer alerta. Outro são os famigerados efeitos colaterais (por exemplo: se
a==1 e eu chamo
ABS(--a) , o valor devolvido será
-1 , que é um absurdo, mas decorrência necessária e incontornável do fato de que o argumento entre parênteses é substituído exatamente do modo como foi escrito no momento da invocação da macro).
--
Eu gosto de C++ e advogo seu uso, não apenas por causa do caso em questão. Mas, no caso em questão, o C++ tem uma notação pronta para definição de funções e tipos de dados genéricos, por meio de
templates (ou gabaritos). Junto com isso, ele permite estender os operadores nativos da linguagem (como
+ ,
* ,
== etc.) para tipo de dados definidos pelo usuário, e isso facilita ainda mais a aplicação de
templates . Veja o seguinte exemplo.
// my_abs() retorna um dado do mesmo tipo do argumento
template <typename T> inline T my_abs(const T &x){
// Antes de comparar com 0, transforma o 0 num valor compatível com o tipo T.
return x<T(0)? -x: x;
}
// my_signal() retorna sempre int, embora aceite diferentes tipos de argumento
template <typename T> inline int my_signal(const T &x){
// Antes de comparar com 0, transforma o 0 num valor compatível com o tipo T.
const T z(0);
return x<z? -1: x>z? 1: 0;
}
// Tipo de dados que representa um número racional (fração com numerador e
// denomindor inteiros) -- só para dar exemplo de uso dos templates acima.
class rational_number {
private:
int numerator, denominator;
public:
// Constrói um número racional a partir de um inteiro.
rational_number(int x){
numerator=x;
denominator=1;
}
// Constrói um número racional a partir de um numerador e um denominador.
rational_number(int n, int d){
if(d<0){
numerator=-n; denominator=-d;
}
else{
numerator=n; denominator=d;
}
}
inline bool operator <(const rational_number &other){
return numerator*other.denominator < other.numerator*denominator;
}
inline bool operator >(const rational_number &other){
// Aproveito o fato de que (a>b)=(b<a) e reutilizo a função acima.
// Como são funções inline, isso não gera overhead no momento da
// execução do programa.
return other < *this;
}
inline bool operator !=(const rational_number &other){
return (*this<other) || (*this>other);
}
inline bool operator ==(const rational_number &other){
return !(*this!=other);
}
inline rational_number operator -(){
return rational_number(-numerator, denominator);
}
};
/*
Com as definições do construtor de conversão de tipo e dos operadores
de comparação, você pode chamar my_abs(valor_rational_number) e
my_signal(valor_rational_number), do mesmo modo como pode chamar
my_abs(valor_int), my_abs(valor_double), my_abs(valor_char),
my_abs(valor_float) etc.
*/
Só que não existe mágica. Apesar de o C++ facilitar a notação, no momento em que você aplica uma operação genérica a um determinado tipo de dado, o compilador gera código específico para aquele tipo de dado, como se você mesmo tivesse escrito aquele código literalmente. Você não vê isso no programa fonte que escreve e lê, mas a versão de
my_abs () aplicada a um valor do tipo
rational_number é diferente da versão de
my_abs () que recebe argumento inteiro, e ambas são diferentes da que recebe argumento
double , e assim por diante. No entanto, todas as diferentes versões que porventura forem usadas ao longo do programa estarão presentes no programa executável gerado, sendo absolutamente distintas entre si.