2010-04-14 18 views
14

Sé cómo cambiar 2 variables en C++, es decir, usa std::swap(a,b).¿Existe una forma incorporada de cambiar dos variables en C

pregunta:

¿Tiene la biblioteca estándar C tiene una función similar a C++ std::swap() o tengo que definir yo mismo.

+1

Hay una pregunta más nueva con una respuesta interesante no representada aquí: http://stackoverflow.com/questions/3982348/implement-generic-swap-macro-in-c –

+1

Más respuestas (probablemente deberían combinarse): http: //stackoverflow.com/questions/3982348/implement-generic-swap-macro-in-c –

Respuesta

10

No existe un equivalente en C, de hecho no puede existir, ya que C no tiene funciones de plantilla. Tendrá que escribir funciones separadas para todos los tipos que quiera intercambiar.

+0

entonces c no tiene algoritmos de biblioteca estándar? el intercambio es una característica de uso común –

+1

@Dr: C no tiene una biblioteca de estructura de datos predeterminada. – kennytm

+6

@Dr Deo C no tiene muchas cosas que C++ hace, ese es el punto. –

20

Sí, tiene que definirlo usted mismo.

  1. C no tiene plantillas.
  2. Si tal función existe, se vería como void swap(void* a, void* b, size_t length), pero a diferencia de std::swap, no es seguro.
  3. Y no hay ninguna pista de que dicha función pueda estar en línea, lo cual es importante si el intercambio es frecuente (en C99 hay inline palabra clave).
  4. también podríamos definir una macro como

    #define SWAP(a,b,type) {type ttttttttt=a;a=b;b=ttttttttt;} 
    

    sombras pero la variable ttttttttt, y hay que repetir el tipo de a. (En gcc hay typeof(a) para resolver esto, pero todavía no se puede SWAP(ttttttttt,anything_else);.)

  5. Y escribir un canje en el lugar no es tan difícil, ya sea - es sólo 3 simples líneas de código!

+1

¿Por qué el voto a favor? Responde a la pregunta de operación ... – Tom

+2

Para evitar sombrear 'ttttttttt', (o causar un error si esa variable se pasa como un argumento). también puede llamar a la variable 'SWAP'. – ideasman42

9

Puede hacer algo similar con una macro si no les importa usar una extensión de gcc para el lenguaje C, typeof:

#include <stdio.h> 

#define SWAP(a, b) do { typeof(a) temp = a; a = b; b = temp; } while (0) 

int main(void) 
{ 
    int a = 4, b = 5; 
    float x = 4.0f, y = 5.0f; 
    char *p1 = "Hello"; 
    char *p2 = "World"; 

    SWAP(a, b); // swap two ints, a and b 
    SWAP(x, y); // swap two floats, x and y 
    SWAP(p1, p2); // swap two char * pointers, p1 and p2 

    printf("a = %d, b = %d\n", a, b); 
    printf("x = %g, y = %g\n", x, y); 
    printf("p1 = %s, p2 = %s\n", p1, p2); 

    return 0; 
} 
+0

¿Por qué votar a la baja? Y sin comentarios? –

+0

El problema con esta implementación (aparte de la portabilidad) es que cambiará ciegamente cualquier cosa, sin tener en cuenta la semántica (por ejemplo, si se trata de asignación de memoria dinámica). El intercambio de C++ llamará a constructores y destructores de manera apropiada para manejar esto. Downvote no fui yo, por cierto. –

+2

@Neil: cierto, pero el OP solicitaba específicamente una implementación 'C'. Está lejos de ser una solución perfecta, pero es una solución razonable para un subconjunto significativo de aplicaciones C (IMNVHO). –

0

Otra macro no ya se ha mencionado aquí: No es necesario dar el tipo si le da la variable temporal en su lugar. Además, el operador de coma es útil aquí para evitar el truco do-while (0). Pero normalmente no me importa y simplemente escribo los tres comandos. Por otro lado, una macro temporal es útil si a y b son más complejos.

#define SWAP(a,b,t) ((t)=(a), (a)=(b), (b)=(t)) 

void mix_the_array (....) 
{ 
    int tmp; 
    ..... 
    SWAP(pointer->array[counter+17], pointer->array[counter+20], tmp); 
    ..... 
} 

#undef SWAP 
0

Compruebe la documentación de su compilador. El compilador puede tener una función swapb para intercambiar bytes y proporcionar otras funciones similares.

Peor caso, desperdicie un día y escriba algunas funciones genéricas de intercambio. No consumirá una cantidad significativa del cronograma de su proyecto.

7

Esto funciona rápidamente en Clang y gcc (pero no icc, que no reconoce esta función de intercambio, sin embargo, se compilará en cualquier compilador C99 estándar), siempre que las optimizaciones realmente reconozcan el intercambio (lo hacen en alta suficientes niveles de optimización).

#include <string.h> 

#define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b))) 
static inline void swap_internal(void *a, void *b, size_t size) { 
    char tmp[size]; 
    memcpy(tmp, a, size); 
    memmove(a, b, size); 
    memcpy(b, tmp, size); 
} 

Ahora para explicar cómo funciona. En primer lugar, la línea SWAP() es relativamente extraña, pero en realidad es relativamente simple. &(a) es el argumento a pasado como un puntero. Del mismo modo, &(b) es el argumento b pasado como un puntero.

La pieza de código más interesante es sizeof *(1 ? &(a) : &(b)). Este es en realidad un informe de error relativamente inteligente. Si no se necesitaran informes de errores, podría ser solo sizeof(a). El operador ternario requiere que sus operaciones tengan tipos compatibles. En este caso, verifico dos argumentos diferentes para su compatibilidad de tipo convirtiéndolos en puntero (de lo contrario, int y double serían compatibles). Como int * y double * no son compatibles, la compilación fallará ... siempre que sea un compilador de C estándar. Lamentablemente, muchos compiladores asumen el tipo void * en este caso, por lo que falla, pero al menos con una advertencia (que está habilitada por defecto). Para garantizar el tamaño correcto del resultado, el valor se desreferencia y se aplica al sizeof, por lo que no hay efectos secundarios.

~/c/swap $ gcc swap.c 
swap.c: In function ‘main’: 
swap.c:5:64: warning: pointer type mismatch in conditional expression [enabled by default] 
#define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b))) 
                   ^
swap.c:16:5: note: in expansion of macro ‘SWAP’ 
    SWAP(cat, dog); 
    ^
~/c/swap $ clang swap.c 
swap.c:16:5: warning: pointer type mismatch ('int *' and 'double *') [-Wpointer-type-mismatch] 
    SWAP(cat, dog); 
    ^~~~~~~~~~~~~~ 
swap.c:5:57: note: expanded from macro 'SWAP' 
#define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b))) 
                 ^~~~~ ~~~~ 
1 warning generated. 
~/c/swap $ icc swap.c 
swap.c(16): warning #42: operand types are incompatible ("int *" and "double *") 
     SWAP(cat, dog); 
    ^

Esta macro evalúa todo exactamente una vez (sizeof es especial, ya que no evalúa sus argumentos). Esto proporciona seguridad contra argumentos como array[something()]. La única limitación que se me ocurre es que no funciona en las variables register porque depende de los punteros, pero aparte de eso, es genérico; incluso puede usarlo para matrices de longitud variable. Incluso puede manejar el intercambio de variables idénticas, no es que desee hacer eso.

+0

Probablemente esto vencerá algunas optimizaciones en la mayoría de los compiladores. Muchos son bastante buenos para ver este tipo de uso de memcpy, pero definitivamente no es perfecto. Recomiendo encarecidamente no usar esto si te preocupa el rendimiento. En el mejor de los casos, esto podría generar un retroceso '# ifdef' para los compiladores que no admiten extensiones de GNU, específicamente' typeof() '. –

4

En C Esto se suele hacer mediante una macro,
hay ejemplos muy simplistas, por ejemplo:
#define SWAP(type,a,b) {type _tmp=a;a=b;b=_tmp;}
... pero yo no recomendaría el uso de ellos, ya que tienen algunos defectos no evidentes.

Esta es una macro escrita para evitar errores accidentales.

#define SWAP(type, a_, b_) \ 
do { \ 
    struct { type *a; type *b; type t; } SWAP; \ 
    SWAP.a = &(a_); \ 
    SWAP.b = &(b_); \ 
    SWAP.t = *SWAP.a; \ 
    *SWAP.a = *SWAP.b; \ 
    *SWAP.b = SWAP.t; \ 
} while (0) 
  • Cada argumento se instancia una sola vez, por lo
    SWAP(a[i++], b[j++]) no da efectos secundarios problemáticos.
  • nombre de variable de temperatura también es SWAP, para no causar errores si un nombre diferente colisiona con el nombre codificado elegido.
  • No llama a memcpy (que de hecho terminó haciendo llamadas a funciones reales en mis pruebas, aunque un compilador las puede optimizar).
  • Su tipo verificado
    (comparar como punteros hace que el compilador advierta si no coinciden).
+0

Está utilizando una estructura en lugar de tres variables separadas, ya que SWAP es el único nombre que está absolutamente garantizado que es seguro de usar, ¿no? (Entonces solo puedes tener una variable). Otra opción es usar cosas como '_a',' _b' y '_tmp', ya que el código que utiliza su macro no debe tocar ninguna variable _prefixed. Está bien sombrear un global llamado '_a' dentro de su macro, ya que ningún uso de la macro debería involucrar' SWAP (_a, x) '. Pero tu camino evita esta fuente de rotura incluso en programas malos que usan nombres reservados para sus propios vars. –

+0

Correcto, y algunos proyectos usan '-Wshadow' así que es bueno poder descartar esto incluso como una posibilidad poco probable. – ideasman42

0

en esencia, la función de intercambio es intercambiar dos bloques de memoria. con dos direcciones y el tamaño del bloque en bytes, podemos intercambiar punteros, enteros, dobles, matrices, estructuras, ...

un puntero tiene tres partes, p. podemos descomponer en tres pedazos short* p

  1. dirección: void * p
  2. tamaño: la lectura de dos bytes en void*p, obtenemos un entero corto.
  3. uso: p. imprimir un entero corto con %hu

usando las dos primeras partes, que será capaz de construir una función de intercambio genérico:

#include<stdint.h> 
#ifdef _WIN32 
#define alloca _alloca 
#else 
#include <alloca.h> 
#endif 

void gswap(void * const a, void * const b, int const sz) { 
    // for most case, 8 bytes will be sufficient. 
    int64_t tmp; // equivalent to char tmp[8]; 
    void * p; 
    bool needfree = false; 
    if (sz > sizeof(int64_t)) { 
     // if sz exceed 8 bytes, we allocate memory in stack with little cost. 
     p = alloca(sz); 
     if (p == NULL) { 
      // if sz is too large to fit in stack, we fall back to use heap. 
      p = malloc(sz); 
      //assert(p != NULL, "not enough memory"); 
      needfree = true; 
     } 
    } 
    else { 
     p = &tmp; 
    } 

    memcpy(p, b, sz); 
    memcpy(b, a, sz); 
    memcpy(a, p, sz); 

    if (needfree) { 
     free(p); 
    } 

} 

por ejemplo:

{// swap int 
    int a = 3; 
    int b = 4; 
    printf("%d,%d\n", a, b);//3,4 
    gswap(&a, &b, sizeof(int)); 
    printf("%d,%d\n", a, b);//4,3 
} 
{// swap int64 
    int64_t a = 3; 
    int64_t b = 4; 
    printf("%lld,%lld\n", a, b);//3,4 
    gswap(&a, &b, sizeof(int64_t)); 
    printf("%lld,%lld\n", a, b);//4,3 
} 
{// swap arrays 
    int64_t a[2] = { 3,4 }; 
    int64_t b[2] = { 5,6 }; 
    printf("%lld,%lld,%lld,%lld\n", a[0], a[1], b[0], b[1]);//3,4,5,6 
    gswap(&a, &b, sizeof(a)); 
    printf("%lld,%lld,%lld,%lld\n", a[0], a[1], b[0], b[1]);//5,6,3,4 
} 
{// swap arrays 
    double a[2] = { 3.,4. }; 
    double b[2] = { 5.,6. }; 
    printf("%lf,%lf,%lf,%lf\n", a[0], a[1], b[0], b[1]);//3.000000, 4.000000, 5.000000, 6.000000 
    arrswap(&a, &b, sizeof(a)); 
    printf("%lf,%lf,%lf,%lf\n", a[0], a[1], b[0], b[1]);//5.000000, 6.000000, 3.000000, 4.000000 
} 
-1

Puede hacer algo similar con una macro sin usar variables temporales.

#include <stdio.h> 

#define SWAP(a, b) {a=a+b;b=a-b;a=a-b;} //swap macro 
int main(void) 
{ 
    int a = 4, b = 5; 
    float x = 4.0f, y = 5.0f; 
    char *p1 = "Hello"; 
    char *p2 = "World"; 

    a = 4, b = 5,x = 4.0f, y = 5.0f,*p1 = "Hello",*p2="world"; 
    SWAP(a, b); // swap two ints, a and b 
    SWAP(x, y); // swap two floats, x and y 
    SWAP1p1, p2); // swap two char * pointers, p1 and p2 
    printf("a = %d, b = %d\n", a, b); 
    printf("x = %g, y = %g\n", x, y); 
    printf("p1 = %s, p2 = %s\n", p1, p2); 

    return 0; 
} 
+0

la solución puede causar un desbordamiento aritmético –

-1

En caso de valores numéricos (por lo menos):

Sé que esto no es una respuesta real o completa, pero hasta ahora todo el mundo ha estado haciendo uso de variables temporales, así que pensé Chris Taylors blog podría ser relevante para mención, ciertamente lo es eliminar la necesidad de typeof(), etc.

a = a^b; 
b = a^b; 
a = a^b; 

o

a = a + b; 
b = a - b; 
a = a - b; 

En teoría, supongo que estas técnicas también se pueden aplicar a cadenas y otros tipos ...

Solo quedan tres operaciones.

+0

Probablemente sea incorrecto para los valores de coma flotante, por lo que no todos los tipos 'numéricos'. Y el segundo puede ser UB para tipos firmados (debido al desbordamiento). –

Cuestiones relacionadas