C Ordenação (qsort)
Ordenar é pôr em ordem: as notas da turma da maior para a menor, os nomes em ordem alfabética, os produtos do mais barato ao mais caro. É uma das tarefas mais comuns da programação, e também a base para procurar depressa. Nesta aula você vai ordenar à mão uma vez, para entender o que acontece por dentro, e depois usar o qsort da biblioteca padrão para ordenar qualquer coisa: números, textos e structs, com o critério que você quiser.
- Como funciona um algoritmo de ordenação simples, a ordenação por inserção.
- Os quatro argumentos do
qsorte a regra da função de comparação (negativo, zero, positivo). - Como comparar sem estourar o
int, e como inverter a ordem. - Como ordenar
double, textos (char *echar[][N]) e structs. - Como desempatar por um segundo critério, e por que o
qsortnão é estável. - Os erros clássicos: assinatura errada,
strcmpno ponteiro errado,x - ye tamanho errado.
Esta aula continua a aula Ponteiros para Funções, que apresentou o qsort, e usa ponteiro void* e structs e ponteiros. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: as cartas na mão
A Ana está jogando baralho e recebe as cartas uma de cada vez. A cada carta nova, ela a encaixa no lugar certo entre as que já estão na mão: passa pelas maiores, empurrando-as um pouco para o lado, até achar uma menor. A mão está sempre em ordem, e no fim todas as cartas estão ordenadas.
Qualquer jeito de ordenar, por mais esperto que seja, se resume a duas operações: comparar dois itens ("qual vem antes?") e mudar itens de lugar. O que muda de um algoritmo para outro é quais pares ele compara e quantas vezes.
1. Ordenar à mão: a inserção
O jeito da Ana tem nome: ordenação por inserção (insertion sort). Em C, a "mão" é o começo do array, que já está ordenado, e a "carta nova" é o próximo item:
#include <stdio.h>
void mostrar(const int *v, int n) {
for (int i = 0; i < n; i++) {
printf(" %d", v[i]);
}
printf("\n");
}
void ordenar_insercao(int *v, int n) {
for (int i = 1; i < n; i++) {
int carta = v[i];
int j = i - 1;
while (j >= 0 && v[j] > carta) {
v[j + 1] = v[j];
j--;
}
v[j + 1] = carta;
printf("depois de encaixar o %2d:", carta);
mostrar(v, n);
}
}
int main(void) {
int cartas[] = {7, 3, 10, 5, 9, 1};
int n = sizeof cartas / sizeof cartas[0];
printf("no começo: ");
mostrar(cartas, n);
ordenar_insercao(cartas, n);
return 0;
}
$ gcc -std=c23 -Wall -Wextra insercao.c -o insercao $ ./insercao no começo: 7 3 10 5 9 1 depois de encaixar o 3: 3 7 10 5 9 1 depois de encaixar o 10: 3 7 10 5 9 1 depois de encaixar o 5: 3 5 7 10 9 1 depois de encaixar o 9: 3 5 7 9 10 1 depois de encaixar o 1: 1 3 5 7 9 10
- for (int i = 1; i < n; i++)
- Começa no segundo item: um array de um item só (o
v[0]) já está ordenado. A cada volta,v[0]av[i - 1]é a "mão" ordenada. - int carta = v[i];
- Guarda a carta nova, porque a posição dela vai ser ocupada por quem andar para a direita.
- while (j >= 0 && v[j] > carta)
- Enquanto houver carta na mão e ela for maior que a nova, continua procurando para a esquerda. O
j >= 0vem antes: sejfor -1, o&¶ ali ev[-1]nunca é lido (veja Operadores Lógicos). - v[j + 1] = v[j]; j--;
- Empurra a carta maior uma casa para a direita e olha a próxima à esquerda.
- v[j + 1] = carta;
- Achou o lugar: logo depois da primeira carta que não é maior. Encaixa ali.
Repare no "encaixar o 1": ele é o menor de todos, então todas as outras cinco cartas andaram uma casa. A inserção é ótima para arrays pequenos ou quase ordenados, mas, num array grande e bagunçado, cada carta nova pode empurrar quase todas as anteriores. Com o dobro de itens, o trabalho fica quatro vezes maior. A seção 6 mede isso.
2. O qsort: a ordenação pronta
A biblioteca padrão já tem uma ordenação rápida e testada: o qsort, de <stdlib.h>. O nome vem de quicksort ("ordenação rápida"), o algoritmo das primeiras versões; hoje cada biblioteca usa o algoritmo que quiser, e o padrão do C só promete que o array termina ordenado. O que torna o qsort especial é que ele ordena arrays de qualquer tipo:
#include <stdio.h>
#include <stdlib.h>
int crescente(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main(void) {
int notas[] = {7, 3, 10, 5, 9, 1, 8};
size_t n = sizeof notas / sizeof notas[0];
qsort(notas, n, sizeof notas[0], crescente);
for (size_t i = 0; i < n; i++) {
printf("%d ", notas[i]);
}
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra qsort_int.c -o qsort_int $ ./qsort_int 1 3 5 7 8 9 10
O protótipo, como está no stdlib.h, é: void qsort(void *base, size_t n, size_t tamanho, int (*comparar)(const void *, const void *));
| Argumento | Na chamada | O que é |
|---|---|---|
void *base | notas | Onde o array começa. Como é void *, aceita o endereço de qualquer array (aula Ponteiro void*). |
size_t n | n | Quantos itens ordenar (não quantos bytes). Aqui, 7, calculado com sizeof notas / sizeof notas[0]. |
size_t tamanho | sizeof notas[0] | Quantos bytes tem cada item. O qsort não sabe o tipo, então precisa disso para pular de um item para o outro e para trocá-los de lugar. |
comparar | crescente | A sua função que diz qual de dois itens vem antes. O qsort a chama sempre que precisa comparar (ela é um callback; veja Ponteiros para Funções). |
sizeof notas[0], não sizeof(int)
Os dois dão 4 aqui. Mas, se um dia o array virar double, o sizeof notas[0] acompanha sozinho, e o sizeof(int) fica errado sem nenhum aviso (é um dos erros da seção Quando dá errado).
3. A função de comparação
O qsort entrega à sua função os endereços de dois itens do array, a e b, como const void *. A função devolve um int que segue uma regra fixa:
| Devolve | Quer dizer |
|---|---|
| Um número negativo (qualquer um: -1, -50…) | a vem antes de b |
| Zero | Tanto faz: para este critério, os dois são iguais |
| Um número positivo | a vem depois de b |
- int crescente(const void *a, const void *b)
- A forma é obrigatória: dois
const void *e retornoint. Oconstpromete que a comparação só lê, sem mudar os itens. - int x = *(const int *)a;
- Converte o
void *para o tipo verdadeiro dos itens (const int *) e lê o valor com o*. Sem a conversão, o C não saberia quantos bytes ler. - return (x > y) - (x < y);
- Uma comparação em C vale 1 (verdadeiro) ou 0 (falso). Se
x > y:1 - 0 = 1. Sex < y:0 - 1 = -1. Se iguais:0 - 0 = 0. É o mesmo que os doisifda aula Ponteiros para Funções, numa linha.
Três jeitos de escrever, e um que parece certo
| Corpo da comparação | Funciona? |
|---|---|
if (x < y) return -1; if (x > y) return 1; return 0; | Sim. O mais fácil de ler. |
return (x > y) - (x < y); | Sim. Curto, e muito usado em código C. |
return x < y ? -1 : x > y; | Sim, com o ternário. Menos comum. |
return x - y; | Só com números pequenos. Com números grandes, a subtração estoura o int e o sinal sai errado (veja Quando dá errado). |
Para inverter a ordem (do maior para o menor), troque x e y de lugar: return (y > x) - (y < x);. O qsort é o mesmo; só o critério mudou.
Ordenar double
Para outros tipos, a receita é a mesma: converta para o ponteiro do tipo certo e compare. Com double, a forma (x > y) - (x < y) é ainda mais importante, porque x - y daria um double como 0,3, que, devolvido como int, vira 0 ("tanto faz").
Missão: a Ana quer os preços da lojinha do mais barato ao mais caro. Complete a função crescente para comparar dois double: converta a e b para const double *, leia os valores e devolva (x > y) - (x < y). A saída deve ser:
0.75 3.50 5.99 18.90 25.90
#include <stdio.h>
#include <stdlib.h>
int crescente(const void *a, const void *b) {
// COMPLETE: converta a e b para ponteiros de double, leia os dois valores
// e devolva negativo, zero ou positivo, como no crescente dos inteiros
(void)a;
(void)b;
return 0;
}
int main(void) {
double precos[] = {25.90, 0.75, 18.90, 3.50, 5.99};
size_t n = sizeof precos / sizeof precos[0];
qsort(precos, n, sizeof precos[0], crescente);
for (size_t i = 0; i < n; i++) {
printf("%.2f ", precos[i]);
}
printf("\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int crescente(const void *a, const void *b) {
double x = *(const double *)a;
double y = *(const double *)b;
return (x > y) - (x < y);
}
int main(void) {
double precos[] = {25.90, 0.75, 18.90, 3.50, 5.99};
size_t n = sizeof precos / sizeof precos[0];
qsort(precos, n, sizeof precos[0], crescente);
for (size_t i = 0; i < n; i++) {
printf("%.2f ", precos[i]);
}
printf("\n");
return 0;
}
4. Ordenar textos
Textos se comparam com strcmp (aula Funções de String), que já segue a regra do qsort: negativo se o primeiro vem antes no alfabeto, zero se iguais, positivo se vem depois. A pegadinha é o que o qsort entrega para a sua função, e isso depende de como os textos estão guardados.
Um array de ponteiros: const char *turma[]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int por_nome(const void *a, const void *b) {
const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
return strcmp(x, y);
}
int main(void) {
const char *turma[] = {"Rui", "Ana", "Lia", "Bruno", "Davi", "Ana Clara"};
size_t n = sizeof turma / sizeof turma[0];
qsort(turma, n, sizeof turma[0], por_nome);
for (size_t i = 0; i < n; i++) {
printf("%s\n", turma[i]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra nomes.c -o nomes $ ./nomes Ana Ana Clara Bruno Davi Lia Rui
- const char *turma[]
- Cada item do array é um ponteiro para um texto. É isso que o
qsortordena: os ponteiros mudam de lugar, os textos ficam onde estão. - *(const char *const *)a
- O
qsortentrega o endereço de um item. Como cada item é umconst char *,aé, na verdade, um ponteiro para ponteiro (const char *const *; aula Ponteiro para Ponteiro). O*da frente lê o item: o ponteiro para o texto. - strcmp(x, y)
- Agora sim, compara os dois textos. "Ana" vem antes de "Ana Clara" porque é um pedaço dela: o texto mais curto termina primeiro.
Um array de textos: char turma[][12]
Se os textos estiverem guardados dentro do array (um array de duas dimensões), cada item já é o texto, e o qsort entrega o endereço do primeiro caractere dele:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int por_nome(const void *a, const void *b) {
return strcmp((const char *)a, (const char *)b);
}
int main(void) {
char turma[][12] = {"Rui", "Ana", "Lia", "Bruno", "Davi", "Ana Clara"};
size_t n = sizeof turma / sizeof turma[0];
qsort(turma, n, sizeof turma[0], por_nome);
for (size_t i = 0; i < n; i++) {
printf("%s\n", turma[i]);
}
printf("Cada item tem %zu bytes\n", sizeof turma[0]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra nomes_matriz.c -o nomes_matriz $ ./nomes_matriz Ana Ana Clara Bruno Davi Lia Rui Cada item tem 12 bytes
Aqui a já aponta para o texto, então basta strcmp(a, b), sem o * extra. E o qsort troca de lugar os 12 bytes de cada nome, não um ponteiro. O resultado é o mesmo; a função de comparação é diferente:
| O array | Cada item é | a é, na verdade | Comparação |
|---|---|---|---|
const char *t[] | um ponteiro para texto | const char *const * | strcmp(*(const char *const *)a, *(const char *const *)b) |
char t[][12] | um texto de até 11 letras | const char * | strcmp(a, b) |
O strcmp compara os códigos dos caracteres. Todas as maiúsculas vêm antes de todas as minúsculas ("Zeca" antes de "ana"), e letras acentuadas (que em UTF-8 têm códigos altos) vão para o fim: "Ícaro" depois de "Zeca". Para uma ordem "de dicionário", é preciso comparar de outro jeito (por exemplo, convertendo para minúsculas com tolower antes; veja ctype.h).
5. Ordenar structs e desempatar
Com structs, a função de comparação escolhe o campo que decide a ordem. O ranking da turma da Ana: maior nota primeiro e, entre notas iguais, ordem alfabética:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char nome[20];
int nota;
} Aluno;
int ranking(const void *a, const void *b) {
const Aluno *x = a;
const Aluno *y = b;
if (x->nota != y->nota) {
return (y->nota > x->nota) - (y->nota < x->nota);
}
return strcmp(x->nome, y->nome);
}
int main(void) {
Aluno turma[] = {
{"Rui", 7}, {"Lia", 9}, {"Bruno", 7}, {"Ana", 10},
{"Davi", 9}, {"Caio", 5}, {"Bia", 7},
};
size_t n = sizeof turma / sizeof turma[0];
qsort(turma, n, sizeof turma[0], ranking);
for (size_t i = 0; i < n; i++) {
printf("%zuº %-6s %2d\n", i + 1, turma[i].nome, turma[i].nota);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra alunos.c -o alunos $ ./alunos 1º Ana 10 2º Davi 9 3º Lia 9 4º Bia 7 5º Bruno 7 6º Rui 7 7º Caio 5
- const Aluno *x = a;
- Em C, um
void *vira outro ponteiro sozinho, sem cast (aula Ponteiro void*). Daí em diante,x->notaex->nome. - if (x->nota != y->nota)
- O primeiro critério: a nota. Se forem diferentes, ela decide sozinha.
- (y->nota > x->nota) - (y->nota < x->nota)
- Com
yna frente: ordem decrescente (maior nota primeiro). - return strcmp(x->nome, y->nome);
- O desempate: só chega aqui com notas iguais. Davi e Lia (9) e Bia, Bruno e Rui (7) saíram em ordem alfabética.
- %zuº
- O
%zué o formato dosize_t(veja Formatos do printf); oºé só texto.
Sem desempate: o qsort não é estável
Uma ordenação é estável quando itens empatados continuam na ordem em que estavam. O padrão do C não promete que o qsort seja estável, e cada biblioteca faz de um jeito. Este programa ordena só pela nota uma turma em que todos estão em ordem alfabética:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char nome[8];
int nota;
} Aluno;
int por_nota(const void *a, const void *b) {
const Aluno *x = a;
const Aluno *y = b;
return (x->nota > y->nota) - (x->nota < y->nota);
}
int main(void) {
Aluno turma[] = {
{"Ana", 8}, {"Bia", 6}, {"Caio", 8}, {"Davi", 6}, {"Edu", 8},
{"Fabi", 6}, {"Gil", 8}, {"Hugo", 6}, {"Iris", 8}, {"Joao", 6},
};
size_t n = sizeof turma / sizeof turma[0];
qsort(turma, n, sizeof turma[0], por_nota);
for (size_t i = 0; i < n; i++) {
printf("%s(%d) ", turma[i].nome, turma[i].nota);
}
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra estavel.c -o estavel $ ./estavel Bia(6) Davi(6) Fabi(6) Hugo(6) Joao(6) Ana(8) Caio(8) Edu(8) Gil(8) Iris(8)
No gcc da Ana (com a glibc, a biblioteca C do Linux), os empatados mantiveram a ordem alfabética de antes. Agora rode o mesmo programa no editor do guia, que usa outra biblioteca C (a wasi-libc, baseada na musl):
Experimente: rode e compare com a saída do gcc acima. No editor, os alunos com nota 6 saem Joao(6) Bia(6) Fabi(6) Davi(6) Hugo(6): a ordem alfabética dos empatados se perdeu. Depois acrescente o desempate por nome na função (como no alunos.c, com #include <string.h>) e rode de novo: a saída fica igual à do gcc.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char nome[8];
int nota;
} Aluno;
int por_nota(const void *a, const void *b) {
const Aluno *x = a;
const Aluno *y = b;
return (x->nota > y->nota) - (x->nota < y->nota);
}
int main(void) {
Aluno turma[] = {
{"Ana", 8}, {"Bia", 6}, {"Caio", 8}, {"Davi", 6}, {"Edu", 8},
{"Fabi", 6}, {"Gil", 8}, {"Hugo", 6}, {"Iris", 8}, {"Joao", 6},
};
size_t n = sizeof turma / sizeof turma[0];
qsort(turma, n, sizeof turma[0], por_nota);
for (size_t i = 0; i < n; i++) {
printf("%s(%d) ", turma[i].nome, turma[i].nota);
}
printf("\n");
return 0;
}
Nenhuma das duas está errada: para o critério "nota", empatados são iguais, e o qsort pode pô-los em qualquer ordem. A lição é: se a ordem dos empatados importa, ela precisa estar na função de comparação. Um bom critério de desempate é um campo que não se repete (o nome, uma matrícula). Assim, o resultado é o mesmo em qualquer computador.
Ordenar só um pedaço
Como o qsort recebe um endereço e uma quantidade, dá para ordenar só uma parte do array: passe o endereço do primeiro item do pedaço (aritmética de ponteiros) e quantos itens ele tem.
#include <stdio.h>
#include <stdlib.h>
int crescente(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main(void) {
int v[] = {9, 8, 7, 6, 5, 4, 3};
qsort(v + 2, 3, sizeof v[0], crescente);
for (int i = 0; i < 7; i++) {
printf("%d ", v[i]);
}
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra parte.c -o parte $ ./parte 9 8 5 6 7 4 3
v + 2 é o endereço do v[2]; com 3 itens, só v[2], v[3] e v[4] (o 7, o 6 e o 5) foram ordenados. O resto ficou como estava.
6. Quanto tempo leva
Este programa ordena os mesmos 100 mil números duas vezes, com a inserção da seção 1 e com o qsort, e mede o tempo de cada uma com clock() (aula Data e Hora). Os números vêm de um sorteador próprio, para serem os mesmos a cada execução:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int crescente(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
void ordenar_insercao(int *v, int n) {
for (int i = 1; i < n; i++) {
int carta = v[i];
int j = i - 1;
while (j >= 0 && v[j] > carta) {
v[j + 1] = v[j];
j--;
}
v[j + 1] = carta;
}
}
int main(void) {
const int n = 100000;
int *a = malloc(n * sizeof *a);
int *b = malloc(n * sizeof *b);
if (a == NULL || b == NULL) {
free(a);
free(b);
return 1;
}
unsigned int semente = 42;
for (int i = 0; i < n; i++) {
semente = semente * 1103515245u + 12345u;
a[i] = (int)(semente / 65536u % 1000000u);
}
memcpy(b, a, n * sizeof *a);
clock_t t0 = clock();
ordenar_insercao(a, n);
clock_t t1 = clock();
qsort(b, n, sizeof *b, crescente);
clock_t t2 = clock();
printf("inserção: %.3f s\n", (double)(t1 - t0) / CLOCKS_PER_SEC);
printf("qsort: %.3f s\n", (double)(t2 - t1) / CLOCKS_PER_SEC);
printf("iguais? %s\n", memcmp(a, b, n * sizeof *a) == 0 ? "sim" : "não");
free(a);
free(b);
return 0;
}
$ gcc -std=c23 -Wall -Wextra tempo.c -o tempo $ ./tempo inserção: 7.738 s qsort: 0.019 s iguais? sim
O mesmo resultado (iguais? sim, conferido com memcmp), umas 400 vezes mais depressa. Os tempos mudam de um computador para outro, mas a diferença continua enorme. A inserção compara cada número com uma boa parte dos anteriores: com 100 mil números, são bilhões de comparações. Os algoritmos do qsort (quicksort, mergesort e parentes) dividem o array em pedaços e precisam só de uns 2 milhões. (No editor do guia este programa passa do limite de 15 segundos; deixe-o para o terminal.)
Formas diferentes e qual usar
| Situação | Prefira |
|---|---|
| Ordenar um array de qualquer tamanho, de qualquer tipo | qsort com uma função de comparação. É testado, rápido e já está na biblioteca. |
| Aprender como a ordenação funciona por dentro | Escrever a inserção (ou a seleção, ou o bubble sort) à mão, uma vez. |
| Poucos itens (dezenas) chegando um a um, e a lista precisa estar sempre ordenada | Inserir cada novo no lugar certo, como a inserção faz. |
| Ordem dos empatados importa | qsort com desempate na comparação (um campo que não se repete). |
| Os dados chegam e saem o tempo todo, e você quer sempre a ordem | Uma árvore de busca: a em ordem já sai ordenada. |
| Vários critérios ("por nome", "por preço") escolhidos pelo usuário | Várias funções de comparação e um ponteiro de função para escolher qual passar ao qsort. |
Quando dá errado
error: passing argument 4 of ‘qsort’ from incompatible pointer type
int crescente(const int *a, const int *b) {
return (*a > *b) - (*a < *b);
}
…
qsort(notas, 5, sizeof notas[0], crescente);
$ gcc -std=c23 -Wall -Wextra assinatura.c -o assinatura
assinatura.c: In function ‘main’:
assinatura.c:10:38: error: passing argument 4 of ‘qsort’ from incompatible pointer type [-Wincompatible-pointer-types]
10 | qsort(notas, 5, sizeof notas[0], crescente);
| ^~~~~~~~~
| |
| int (*)(const int *, const int *)
In file included from assinatura.c:2:
/usr/include/stdlib.h:999:34: note: expected ‘__compar_fn_t’ {aka ‘int (*)(const void *, const void *)’} but argument is of type ‘int (*)(const int *, const int *)’
999 | __compar_fn_t __compar) __nonnull ((1, 4));
| ~~~~~~~~~~~~~~^~~~~~~~
assinatura.c:4:5: note: ‘crescente’ declared here
4 | int crescente(const int *a, const int *b) {
| ^~~~~~~~~
/usr/include/stdlib.h:970:15: note: ‘__compar_fn_t’ declared here
970 | typedef int (*__compar_fn_t) (const void *, const void *);
| ^~~~~~~~~~~~~
O que significa: parece uma boa ideia receber logo const int * e evitar os casts, mas o qsort exige uma função que receba dois const void *. "Passando o argumento 4 do qsort com tipo de ponteiro incompatível": a nota mostra o tipo esperado, int (*)(const void *, const void *) (a glibc o chama de __compar_fn_t), e o que você passou. Desde o gcc 14 isso é erro. O Clang do editor diz incompatible function pointer types passing 'int (const int *, const int *)' to parameter of type 'int (*)(const void *, const void *)'.
Como consertar: receba const void *a, const void *b e converta dentro da função: int x = *(const int *)a;. Não "conserte" com um cast na chamada ((int (*)(const void *, const void *))crescente): o erro some, mas chamar uma função pelo tipo errado é comportamento indefinido.
strcmp(a, b) num array de ponteiros
int por_nome(const void *a, const void *b) {
return strcmp(a, b);
}
…
const char *turma[] = {"Rui", "Ana", "Lia", "Bruno", "Davi", "Ana Clara"};
$ gcc -std=c23 -Wall -Wextra nomes_errado.c -o nomes_errado $ ./nomes_errado Rui Ana Lia Bruno Davi Ana Clara
O que significa: nenhum aviso (um void * vira const char * sem reclamar), e a lista saiu na ordem original. Num array de ponteiros, a e b são os endereços dos ponteiros, não dos textos. O strcmp comparou os bytes dos endereços, como se fossem letras, e o resultado não tem nada a ver com os nomes. No editor do guia, os endereços são outros e sai outra ordem, também sem sentido (Bruno, Davi, Rui, Ana Clara, Ana, Lia).
Como consertar: com const char *t[], leia o ponteiro antes: strcmp(*(const char *const *)a, *(const char *const *)b). O strcmp(a, b) direto só serve para char t[][N] (seção 4).
return x - y; com números grandes
#include <stdio.h>
#include <stdlib.h>
int crescente(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int main(void) {
int saldos[] = {2000000000, -7, -2000000000, 500, 1500000000, -1500000000};
size_t n = sizeof saldos / sizeof saldos[0];
qsort(saldos, n, sizeof saldos[0], crescente);
for (size_t i = 0; i < n; i++) {
printf("%d\n", saldos[i]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra subtracao.c -o subtracao $ ./subtracao 500 1500000000 2000000000 -2000000000 -1500000000 -7 $ gcc -std=c23 -Wall -Wextra -fsanitize=undefined subtracao.c -o subtracao $ ./subtracao 2>&1 | head -3 subtracao.c:5:28: runtime error: signed integer overflow: 2000000000 - -2000000000 cannot be represented in type 'int' 500 1500000000
O que significa: os negativos foram parar no fim. 2000000000 - (-2000000000) daria 4 bilhões, que não cabe num int (o máximo é 2147483647; aula Inteiros). A conta estourou e deu um número negativo, e o qsort entendeu "2 bilhões vem antes de -2 bilhões". O sanitizer de comportamento indefinido (-fsanitize=undefined) aponta a linha 5. No editor, a ordem errada é outra, porque a biblioteca compara pares diferentes.
Como consertar: compare, não subtraia: return (x > y) - (x < y);. Comparações nunca estouram.
sizeof(int) num array de double
double precos[] = {25.90, 0.75, 18.90, 3.50, 5.99};
size_t n = sizeof precos / sizeof precos[0];
qsort(precos, n, sizeof(int), crescente);
$ gcc -std=c23 -Wall -Wextra tamanho.c -o tamanho $ ./tamanho 0.75 25.90 18.90 3.50 5.99
O que significa: nenhum aviso: para o compilador, sizeof(int) é só um número. Mas cada double tem 8 bytes, e o qsort achou que cada item tinha 4. Ele trocou de lugar metades de double e só mexeu nos primeiros 20 bytes do array (5 itens de 4 bytes). No gcc, o resultado "parece quase certo", o que é o pior caso: passa despercebido. No editor, uma das trocas montou um double com a metade de um e a metade de outro, e apareceu no lugar do 0,75 um número com 186 dígitos.
Como consertar: use sempre sizeof v[0] (o tamanho do item do próprio array) e sizeof v / sizeof v[0] para a quantidade.
Referência rápida
| O que ordenar | Corpo da comparação (crescente) |
|---|---|
int v[] | int x = *(const int *)a, y = *(const int *)b; return (x > y) - (x < y); |
double v[] | double x = *(const double *)a, y = *(const double *)b; return (x > y) - (x < y); |
const char *v[] | return strcmp(*(const char *const *)a, *(const char *const *)b); |
char v[][N] | return strcmp(a, b); |
Aluno v[] por um campo | const Aluno *x = a, *y = b; return (x->nota > y->nota) - (x->nota < y->nota); |
| Decrescente | Troque x e y (ou a e b) de lugar |
| Com desempate | if (x->nota != y->nota) return …; return strcmp(x->nome, y->nome); |
| Chamada | O que faz |
|---|---|
qsort(v, n, sizeof v[0], cmp); | Ordena os n itens de v segundo cmp (<stdlib.h>) |
qsort(v + i, k, sizeof v[0], cmp); | Ordena só os k itens a partir de v[i] |
sizeof v / sizeof v[0] | Quantos itens tem o array (só funciona com o array de verdade, não com um ponteiro recebido por parâmetro) |
Exercícios
1. O tamanho do item
Complete a chamada que ordena o array alturas, de n itens:
2. Ler o valor
Na função de comparação de um array de int, complete a linha que lê o valor apontado por a:
3. O que devolver
A função de comparação recebeu a = 9 e b = 4, numa ordenação crescente. O resultado deve ser negativo, zero ou positivo?
4. Qual comparação ordena do maior para o menor?
5. O array é const char *nomes[]. O que o qsort entrega em a?
6. Dois alunos têm a mesma nota e a comparação só olha a nota. Em que ordem eles saem?
Missão final: a vitrine da lojinha
Missão: a Ana quer a lista de produtos do mais barato ao mais caro e, entre os de mesmo preço, em ordem alfabética. Complete a função comparar: se os preços forem diferentes, o mais barato vem antes; se forem iguais, decida pelo nome com strcmp. Com o desempate, a saída é a mesma em qualquer biblioteca:
bala R$ 0,75 pao R$ 0,75 agua R$ 4,50 sabao R$ 4,50 sal R$ 4,50 leite R$ 5,99 cafe R$ 18,90 arroz R$ 25,90
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char nome[20];
int preco;
} Produto;
int comparar(const void *a, const void *b) {
const Produto *x = a;
const Produto *y = b;
// COMPLETE: 1. preços diferentes: o mais barato vem antes
// 2. mesmo preço: ordem alfabética do nome (strcmp)
(void)x;
(void)y;
return 0;
}
int main(void) {
Produto loja[] = {
{"sabao", 450}, {"arroz", 2590}, {"pao", 75}, {"sal", 450},
{"leite", 599}, {"bala", 75}, {"cafe", 1890}, {"agua", 450},
};
size_t n = sizeof loja / sizeof loja[0];
qsort(loja, n, sizeof loja[0], comparar);
for (size_t i = 0; i < n; i++) {
printf("%-6s R$ %2d,%02d\n", loja[i].nome, loja[i].preco / 100, loja[i].preco % 100);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char nome[20];
int preco;
} Produto;
int comparar(const void *a, const void *b) {
const Produto *x = a;
const Produto *y = b;
if (x->preco != y->preco) {
return (x->preco > y->preco) - (x->preco < y->preco);
}
return strcmp(x->nome, y->nome);
}
int main(void) {
Produto loja[] = {
{"sabao", 450}, {"arroz", 2590}, {"pao", 75}, {"sal", 450},
{"leite", 599}, {"bala", 75}, {"cafe", 1890}, {"agua", 450},
};
size_t n = sizeof loja / sizeof loja[0];
qsort(loja, n, sizeof loja[0], comparar);
for (size_t i = 0; i < n; i++) {
printf("%-6s R$ %2d,%02d\n", loja[i].nome, loja[i].preco / 100, loja[i].preco % 100);
}
return 0;
}
Quer ir além? Escreva também uma por_nome e deixe o usuário escolher a ordem com um ponteiro int (*criterio)(const void *, const void *), como na calculadora da aula Ponteiros para Funções. Com a lista ordenada por nome, a próxima aula, Busca (bsearch), acha qualquer produto em poucos passos.
Resumo
- Ordenar é comparar e mudar de lugar. A inserção encaixa cada item entre os já ordenados; é simples, mas lenta com muitos itens.
qsort(v, n, sizeof v[0], comparar)ordena um array de qualquer tipo: o endereço, quantos itens, o tamanho de cada um e a sua função de comparação.- A comparação recebe dois
const void *, converte para o tipo certo e devolve negativo (aantes), zero (tanto faz) ou positivo (adepois). Use(x > y) - (x < y);x - ypode estourar. Para inverter, troquexey. - Textos:
strcmp. Numconst char *v[],aé um ponteiro para ponteiro (*(const char *const *)a); numchar v[][N],ajá é o texto. - Structs: compare o campo que decide e desempate por outro. O
qsortnão é estável: sem desempate, empatados podem sair em ordens diferentes em cada biblioteca.