Funções, arrays e texto

Contratos, passagem de argumentos, arrays C, estruturas, strings e leitura de linhas.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Uma função de contagem pode receber as notas já lidas e devolver um total. Não trata a entrada nem imprime.

O contrato diz que dados recebe, se os pode alterar e que resultado devolve. Separar estas decisões permite testar a contagem sem fornecer dados pelo teclado.

Parâmetros e argumentos

Os parâmetros são os nomes declarados na função: a e b em int maximo(int a, int b). Os argumentos são as expressões fornecidas na chamada: 4 e 9 em maximo(4, 9). O parâmetro recebe uma cópia ou acesso conforme o contrato; o argumento diz qual o valor ou objeto usado nessa chamada.

Declarar e chamar funções

A declaração int maximo(int a, int b); anuncia o tipo do resultado e dos parâmetros. A definição acrescenta o corpo. A declaração tem de estar visível antes da chamada; a definição necessária tem de existir quando o programa é ligado.

// Excerto: definição de uma função.
int maximo(int a, int b) {
    return a > b ? a : b;
}

O operador condicional condicao ? a : b escolhe a se a condição é verdadeira e b caso contrário. Aqui 4 > 9 é falso, por isso maximo(4, 9) devolve 9; return termina a chamada sem imprimir, por isso o chamador decide se guarda, calcula ou imprime. Uma função void não devolve valor.

Uma sobrecarga usa o mesmo nome com listas de parâmetros diferentes. O compilador escolhe pela chamada; o tipo de retorno, por si só, não distingue duas sobrecargas. Argumentos por defeito permitem omitir os últimos argumentos, por exemplo void imprimir(int n, char separador = ' ');. O valor por defeito tem de estar visível no ponto da chamada. Não o repitas na definição se já o declaraste num cabeçalho.

Valor, referência e const

Cópia, referência e leitura
#include <iostream>
#include <string>

void incrementar_copia(int x) { ++x; }
void incrementar(int& x) { ++x; }
std::size_t comprimento(const std::string& texto) { return texto.size(); }

int main() {
    int n = 4;
    incrementar_copia(n);
    std::cout << n << "\n";
    incrementar(n);
    std::cout << n << " " << comprimento("Ana Maria") << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

4
5 9

n começa em 4. incrementar_copia(n) cria um parâmetro independente com o valor 4. Incrementar esse parâmetro não toca em n, que continua 4.

incrementar(n) recebe uma referência ao original e deixa n com 5. comprimento("Ana Maria") conta 9 unidades: 3 de Ana, 1 espaço e 5 de Maria.

Uma referência é um nome alternativo para um objeto. Tem de ser inicializada e nunca muda de destino. Atribuir à referência altera o objeto referido.

ParâmetroO que a função recebeUso habitual
int nUm valor independenteNúmeros pequenos, só de leitura
T& objetoAcesso ao originalAlteração faz parte da operação
const T& objetoAcesso de leitura ao originalEvitar copiar uma string ou coleção
T* objetoUm endereço, que pode ser nuloOpcionalidade ou acesso a um intervalo

const T& impede alterações através dessa referência; não torna constantes todas as outras formas de acesso ao objeto. Evitar uma cópia de um int raramente justifica uma referência.

Retornar um std::string por valor é seguro e permite ao compilador evitar cópias. Retornar uma referência a uma variável local deixa acesso a um objeto que já morreu.

Parâmetros apontadores

A secção seguinte passa arrays como const int* valores, que se lê assim: valores guarda o endereço do primeiro inteiro e valores[i] acede ao elemento nessa posição. Ainda não precisas de aritmética de apontadores, que a página Apontadores e memória explica depois com objetos e setas.

Arrays C e dimensões

Um array int v[4] contém quatro inteiros contíguos. Os índices válidos são 0, 1, 2 e 3. v[4] está fora do array. Um array não aumenta de tamanho e não transporta automaticamente o seu comprimento quando é passado como argumento.

Numa lista de parâmetros, const int valores[] é ajustado para const int* valores. Por isso, a função abaixo recebe também n. std::size_t, de <cstddef>, é o tipo inteiro sem sinal usado para tamanhos e índices. Não representa valores negativos.

Somar um intervalo de array
#include <cstddef>
#include <iostream>

int soma(const int* valores, std::size_t n) {
    int total = 0;
    for (std::size_t i = 0; i < n; ++i) total += valores[i];
    return total;
}

int main() {
    int valores[] = {4, -2, 7, 1};
    std::cout << soma(valores, 4) << "\n";
    std::cout << soma(valores + 1, 2) << "\n";
    std::cout << soma(nullptr, 0) << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

10
5
0

O contrato de soma exige n elementos válidos e que todas as somas parciais caibam em int. Na segunda chamada, o intervalo contém -2 e 7, por isso soma 5. Na chamada vazia, o ciclo não desreferencia o apontador.

IntervaloÍndices originaisEvolução de total
Array completo0, 1, 2, 30 → 4 → 2 → 9 → 10
Dois valores a partir de -21, 20 → -2 → 5
Vazionenhum0

O índice i é relativo ao início recebido. Na segunda chamada, valores[0] dentro da função é o -2 que está no índice 1 do array do chamador.

sizeof(valores) dentro desta função mede o apontador, não o array do chamador. Para um tamanho fixo com interface que o conserva, usa std::array<T, N>. Para tamanho variável, usa std::vector<T>. Um std::vector<int> guarda uma sequência de inteiros; std::vector<int> v{4, -2, 7} cria três elementos. A página Templates e STL desenvolve esta coleção.

Um for por intervalo, como for (int x : valores), visita cada elemento de um array cujo tamanho é conhecido ou de uma coleção. x recebe uma cópia de cada inteiro; alterar x não altera o elemento. Com for (int& x : valores), x refere-se ao elemento e permite alterá-lo. Um apontador isolado não fornece o tamanho para este percurso.

Para percorrer índices de trás para a frente com std::size_t, que não representa negativos, começa em size() e repete enquanto i > 0. Decrementa primeiro e lê v[i]; em alternativa, lê v[i - 1] e só depois decrementa. Qualquer das ordens evita subtrair um a zero e evita testar i >= 0, que num tipo sem sinal nunca falha.

for (std::size_t i = n; i > 0; ) {
    --i;
    std::cout << valores[i] << '\n';
}

O exercício de inverter texto treina este percurso com uma string.

Um array C multidimensional tem os elementos da última dimensão contíguos. Em int matriz[2][3], há duas linhas de três inteiros. Percorre com dois índices, um por dimensão:

// Excerto: soma todos os elementos de uma matriz 2x3.
int soma_matriz(const int matriz[][3], int linhas) {
    int total = 0;
    for (int l = 0; l < linhas; ++l)
        for (int c = 0; c < 3; ++c) total += matriz[l][c];
    return total;
}

Com {{1, 2, 3}, {4, 5, 6}}, a soma é 21. Uma função que recebe const int matriz[][3] precisa de conhecer a dimensão interior para avançar entre linhas. Isto não equivale a int**: um apontador para linhas de três inteiros e um apontador para apontadores têm representações e tipos diferentes.

Arrays de entrada e de saída

Uma função pode escrever o resultado num array reservado pelo chamador e devolver quantas posições ficaram válidas. O tamanho físico e o número de resultados são diferentes: se há espaço para seis inteiros mas só quatro foram escritos, só essas quatro posições pertencem à saída.

Vamos conservar todas as ocorrências dos valores repetidos, pela ordem original. Em 4, 9, 4, 2, 9, 7, o resultado é 4, 9, 4, 9, não apenas os valores distintos 4, 9. A ocorrência final de 4 também conta, mesmo sem outro 4 depois dela: existe uma ocorrência anterior.

O contrato exige n >= 0, uma entrada com n elementos e uma saída com capacidade para pelo menos n. Os arrays não se sobrepõem e a origem fica intacta. Para n == 0, a função não lê nem escreve, por isso aceita apontadores nulos. Neste algoritmo, usamos só ciclos e arrays, como num enunciado que não permite contentores ou funções da biblioteca.

Conservar todas as ocorrências repetidas
#include <iostream>

// n >= 0; entrada e saida nao se sobrepoem, com n posicoes validas.
int filtrar_repetidos(const int entrada[], int n, int saida[]) {
    int usados = 0;
    for (int i = 0; i < n; ++i) {
        bool repetido = false;
        for (int j = 0; j < n; ++j) {
            if (i != j && entrada[i] == entrada[j]) {
                repetido = true;
                break;
            }
        }
        if (repetido) saida[usados++] = entrada[i];
    }
    return usados;
}

int main() {
    int entrada[] = {4, 9, 4, 2, 9, 7};
    int saida[6];
    int usados = filtrar_repetidos(entrada, 6, saida);
    std::cout << usados << ":";
    for (int i = 0; i < usados; ++i) std::cout << " " << saida[i];
    std::cout << "\n";
    std::cout << filtrar_repetidos(nullptr, 0, nullptr) << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

4: 4 9 4 9
0

Para cada índice i, procuramos um índice diferente j com o mesmo valor. Não basta comparar o elemento consigo próprio, nem procurar só à sua direita. Ao encontrar uma correspondência, copiamos entrada[i] uma única vez para saida[usados] e avançamos usados.

iValorExiste noutra posição?Prefixo de saída
04Sim, em 24
19Sim, em 44, 9
24Sim, em 04, 9, 4
32Não4, 9, 4
49Sim, em 14, 9, 4, 9
57Não4, 9, 4, 9

Antes da iteração i, o prefixo de saida com índices em [0, usados) contém exatamente as ocorrências que passaram o critério nos índices anteriores. Percorrer i por ordem conserva a ordem de entrada. O pior caso faz uma procura linear para cada elemento, com custo quadrático em n; a memória adicional da função é constante, além do destino recebido. A capacidade disponível não inicializa a cauda nem a transforma em resultados. O chamador usa o número devolvido para a percorrer.

Structs e tipos compostos

// Excerto: tipo e acesso aos seus campos.
struct Ponto {
    double x;
    double y;
};
Ponto p{2.0, 3.0};

A struct reúne campos que descrevem um valor: Ponto é um tipo, p é um objeto desse tipo e p.x vale 2.0. O compilador pode introduzir padding, bytes entre campos ou no fim para alinhar os objetos. Por isso, sizeof(Ponto) não deve ser deduzido somando tamanhos de campos. Não graves a memória bruta de uma struct como formato portátil de ficheiro.

struct e class permitem membros e métodos. A diferença sintática central é o acesso por defeito: público na struct, privado na class. Uma enumeração com âmbito, como enum class Estado { livre, ocupado };, cria valores nomeados e evita confundir um estado com um inteiro qualquer.

Strings e linhas

std::string guarda uma sequência de unidades char. size() conta essas unidades, não caracteres Unicode visuais. Para ler ou alterar unidades do texto, usa índices de 0 a size() - 1; numa string vazia não há nenhuma. at() verifica esse limite e lança uma exceção. texto[texto.size()] permite consultar o terminador nulo, mas não é mais uma unidade do texto nem permite guardar outro carácter nessa posição. + concatena strings e == compara o conteúdo. substr(pos, quantidade) extrai uma parte; find devolve uma posição ou std::string::npos quando não encontra.

Depois de >> ler um número, o fim de linha pode continuar na entrada, por isso um getline imediato leria esse resto vazio. Neste formato, a idade ocupa a primeira linha e o nome a segunda. Descarta o resto da primeira linha antes de leres o nome.

Ler número e linha
#include <iostream>
#include <limits>
#include <string>

int main() {
    int idade = 0;
    if (!(std::cin >> idade)) {
        std::cout << "Idade invalida\n";
        return 0;
    }
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::string nome;
    if (!std::getline(std::cin, nome)) {
        std::cout << "Nome em falta\n";
        return 0;
    }
    std::cout << nome << " | " << idade << " | " << nome.size() << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

Ana Maria | 19 | 9

ignore descarta até ao \n, incluindo-o. std::getline guarda os espaços interiores e retira o terminador. A alternativa getline(std::cin >> std::ws, nome) descarta também espaços iniciais e linhas vazias; só a uses quando isso corresponde ao formato pretendido.

Para separar campos dentro de uma linha, find localiza um separador e substr extrai cada parte. Com s = "azul,verde", s.find(',') devolve 4. s.substr(0, 4) é "azul" e s.substr(5) é "verde". Quando find não encontra o carácter, devolve std::string::npos; nesse caso não há segundo campo.

Strings de C e o terminador

Uma string de C é uma sequência de char terminada por \0. O array char nome[] = "Ana"; tem quatro posições: A, n, a, \0. O terminador não aparece no texto impresso, mas ocupa memória. <cstring> fornece strlen, que percorre até ao terminador, e operações de cópia. Estas funções exigem que os apontadores e capacidades estejam corretos.

Arrays de char comparados com == não comparam o texto, comparam endereços depois da conversão para apontadores. Usa std::string, ou std::strcmp quando o contrato pede strings de C válidas. strcpy não verifica o tamanho do destino. Reservar só três posições para copiar "Ana" é insuficiente.

Presença de letras em dois textos

Agora queremos as letras que aparecem num dos textos, mas não nos dois. Trata-se da diferença simétrica dos conjuntos de letras. Contar repetições ou retirar caracteres na ordem em que aparecem responderia a outra pergunta.

O alfabeto deste exemplo tem as 26 letras de a a z; a maiúscula correspondente conta como a mesma letra. Espaços, números e pontuação são ignorados. Não estamos a tratar todo o texto Unicode. As duas entradas são strings de C válidas, não nulas, com até 1000 unidades char. O destino não se sobrepõe às entradas e tem pelo menos 27 posições: uma por letra possível e outra para \0.

Guardamos duas tabelas de presença, inicialmente falsas. Depois de ler um texto, em_a[i] diz se a letra de índice i apareceu pelo menos uma vez. Marcar novamente a mesma posição não muda o conjunto. indice_letra procura em dois alfabetos explícitos, sem depender de subtrair códigos dos caracteres nem de chamar uma função da biblioteca.

Comparar conjuntos de letras
#include <iostream>

int indice_letra(char letra) {
    const char minusculas[] = "abcdefghijklmnopqrstuvwxyz";
    const char maiusculas[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < 26; ++i)
        if (letra == minusculas[i] || letra == maiusculas[i]) return i;
    return -1;
}

// texto e uma string de C valida com, no maximo, 1000 unidades char.
void marcar_letras(const char texto[], bool presentes[]) {
    for (int i = 0; texto[i] != '\0'; ++i) {
        int indice = indice_letra(texto[i]);
        if (indice >= 0) presentes[indice] = true;
    }
}

// a e b sao validas; saida tem 27 posicoes e nao se sobrepoe as entradas.
int diferenca_letras(const char a[], const char b[], char saida[]) {
    bool em_a[26]{};
    bool em_b[26]{};
    marcar_letras(a, em_a);
    marcar_letras(b, em_b);
    const char alfabeto[] = "abcdefghijklmnopqrstuvwxyz";
    int usados = 0;
    for (int i = 0; i < 26; ++i)
        if (em_a[i] != em_b[i]) saida[usados++] = alfabeto[i];
    saida[usados] = '\0';
    return usados;
}

int main() {
    char saida[27];
    int n = diferenca_letras("ac AB!", "B-cd", saida);
    std::cout << n << " [" << saida << "]\n";
    n = diferenca_letras("abAB", "ba", saida);
    std::cout << n << " [" << saida << "]\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

2 [ad]
0 []

Em "ac AB!", ficam presentes a, b, c; em "B-cd", b, c, d. As letras b e c aparecem nos dois e são excluídas. A condição em_a[i] != em_b[i] é verdadeira só para a e d. Percorrer a tabela do índice 0 ao 25 escreve o resultado alfabeticamente, uma vez por letra, sem ordenar o texto original.

No segundo caso, "abAB" repete letras que "ba" contém uma vez, mas os conjuntos são iguais. A saída é vazia e continua a precisar de saida[0] = '\0'. O número devolvido conta letras, sem incluir o terminador. No maior resultado, escrevemos 26 letras e o terminador na posição 26; reservar só 26 posições falharia precisamente nesse caso.

A procura de cada unidade percorre no máximo 26 posições do alfabeto, comparando maiúscula e minúscula. No fim, percorremos as 26 presenças. Com este alfabeto fixo, o tempo é linear no comprimento total dos textos e a memória adicional é constante. O algoritmo não usa string, set, sort nem tolower; o main apenas usa cout para mostrar o resultado. As ferramentas permitidas fazem parte do enunciado, além do resultado esperado.

Recursão e progresso

Uma função recursiva chama-se a si própria e precisa de um caso base com uma medida que diminua até esse caso. A soma recursiva de 1 até n pode usar soma(0) = 0 e soma(n) = n + soma(n - 1) para n > 0. O contrato exige n >= 0 e uma soma representável no tipo escolhido. Se não tratares 0, podes criar chamadas sem fim.

Para n = 3, a descida constrói chamadas e a subida combina resultados:

PassoChamadaResultado
descersoma(3) = 3 + soma(2)pendente
descersoma(2) = 2 + soma(1)pendente
descersoma(1) = 1 + soma(0)pendente
basesoma(0) = 00
subir1 + 01
subir2 + 13
subir3 + 36

A descida reduz n e a subida combina os resultados. Uma entrada negativa nunca se aproxima do caso base.

Cada chamada conserva os seus parâmetros e variáveis locais. Um grande número de chamadas pode esgotar a pilha. Quando um ciclo resolve o mesmo problema com um acumulador, não precisas de recursão. Usa-a quando a decomposição do problema a torna clara, não como substituição automática de qualquer ciclo.

Exercícios

A referência não muda de destino

Segue int a = 4; int b = 9; int& r = a; r = b; b = 2;. Quanto vale a no fim?

Pista

Lembra-te de que r é um nome alternativo para a.

Solução

r = b guarda 9 em a. A última atribuição altera apenas b para 2. O resultado é a = 9, b = 2 e r = 9.

Voltar à explicação
Passar um subintervalo válido

Um array v contém 1, 2, 3, 4. Qual chamada pode ler o intervalo inteiro sem sair do array? A função soma(inicio, n) lê n inteiros a partir de inicio e não lê nenhum quando n é zero.

A tua resposta

Pista

Marca os índices 0 a 3 e a posição logo depois do fim.

Solução

soma(v + 2, 2) lê os índices 2 e 3. O tamanho deve acompanhar o intervalo real, não o tamanho inicial do array.

  • soma(v + 2, 2). Os dois elementos são os índices 2 e 3.
  • soma(v + 2, 3). A terceira leitura seria v[4], fora do array.
  • soma(v + 4, 1). v + 4 marca o fim e não aponta para um elemento.
  • soma(nullptr, 1). Uma leitura por um apontador nulo viola o contrato.

Erros frequentes

Formar uma posição depois do fim pode ser válido; ler a partir dela não é.

Voltar à explicação
Programar uma contagem num intervalo fechado

O contrato pede o número de valores em [minimo, maximo], incluindo os extremos: um valor igual a minimo ou a maximo conta. Corrige a função sem alterar os dados de teste. O programa inicial compila e corre, mas os resultados nos limites estão errados. A técnica de testar limites é desenvolvida em Exceções e testes.

Contar os extremos do intervalo
#include <iostream>
#include <vector>

int contar(const std::vector<int>& valores, int minimo, int maximo) {
    int total = 0;
    for (int x : valores)
        if (minimo < x && x < maximo) ++total;
    return total;
}

int main() {
    std::cout << "Vazio: " << contar({}, 1, 3) << " (esperado 0)\n";
    std::cout << "Limites: " << contar({1, 2, 3}, 1, 3) << " (esperado 3)\n";
    std::cout << "Repetidos: " << contar({2, 2, 2}, 2, 2) << " (esperado 3)\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Pista

Compara a condição com a definição de intervalo fechado.

Solução

A condição correta é minimo <= x && x <= maximo . O vazio continua a dar zero, o intervalo de 1 a 3 inclui os três valores e o intervalo reduzido a 2 inclui as três repetições. A referência constante conserva a coleção original.

Confere a tua resposta:

  • Incluí os dois limites.
  • Obtive 0, 3 e 3 nos casos apresentados.
  • Mantive a coleção sem alterações e sem cópias desnecessárias.

Erros frequentes

Não alteres só um dos limites. Não mudes os resultados esperados para esconder a falha.

Voltar à explicação
Construir uma string invertida

Define std::string inverter(const std::string& texto) para devolver as unidades do texto pela ordem inversa. Usa apenas texto ASCII, como abc, e conserva o argumento. A string vazia devolve uma string vazia. Esta resposta é de autoavaliação.

Escrever a função inverter
#include <iostream>
#include <string>

std::string inverter(const std::string& texto) {
    return texto; // Substitui pelo algoritmo.
}

int main() {
    std::string original = "abc";
    std::cout << "[" << inverter("") << "] (esperado [])\n";
    std::cout << inverter("x") << " (esperado x)\n";
    std::cout << inverter(original) << " (esperado cba)\n";
    std::cout << inverter("abcd") << " (esperado dcba)\n";
    std::cout << original << " (esperado abc)\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Pista
Constrói outra string e percorre a entrada do fim para o início.
Mais uma pista

Começa com i = texto.size(). Enquanto i > 0, lê texto[i - 1] e diminui i. Assim, nunca subtrais um a zero para formar um índice.

Solução
std::string inverter(const std::string& texto) {
    std::string resultado;
    for (std::size_t i = texto.size(); i > 0; --i)
        resultado += texto[i - 1];
    return resultado;
}

Para abc, os índices lidos são 2, 1 e 0. No vazio, o ciclo não começa. O argumento só é lido; o resultado tem armazenamento próprio. Outra solução válida copia o texto e troca pares de posições simétricas.

Confere a tua resposta:

  • Devolvi uma string e não imprimi dentro da função.
  • Tratei o vazio, um carácter e comprimentos pares e ímpares.
  • Conservei o texto original e justifiquei os índices usados.

Erros frequentes

size() usa um tipo sem sinal. Começar em size() - 1 no vazio, ou testar i >= 0 para terminar uma contagem descendente sem sinal, dá limites errados. Inverter bytes de texto UTF-8 também pode estragar caracteres, por isso o contrato deste exercício limita a entrada a ASCII.

Voltar à explicação
Somar uma linha de uma matriz

A matriz recebida tem duas linhas válidas, linha é 0 ou 1 e a soma cabe em int. Escreve int soma_linha(const int matriz[][3], int linha) que devolve a soma das três colunas dessa linha. Usa a matriz {{1, 2, 3}, {4, 5, 6}} como caso de teste.

Pista

Um ciclo de c = 0 até c < 3 chega para uma linha fixa.

Solução
int soma_linha(const int matriz[][3], int linha) {
    int total = 0;
    for (int c = 0; c < 3; ++c) total += matriz[linha][c];
    return total;
}

// Teste: com m = {{1, 2, 3}, {4, 5, 6}},
// soma_linha(m, 0) devolve 6 e soma_linha(m, 1) devolve 15.

Com a matriz do enunciado, a chamada com linha 0 soma 1 + 2 + 3 e devolve 6; com linha 1 soma 4 + 5 + 6 e devolve 15. A dimensão interior 3 tem de estar na assinatura para o compilador calcular o endereço de matriz[linha][c].

Confere a tua resposta:

  • Percorri as três colunas da linha indicada.
  • Obtive 6 para a primeira linha de {{1, 2, 3}, {4, 5, 6}}.
  • Justifiquei por que a função precisa da dimensão interior.

Erros frequentes

Não declares o parâmetro como int**. São representações diferentes.

Voltar à explicação
A última ocorrência também conta

Uma função deve copiar todas as ocorrências dos valores que aparecem mais de uma vez, pela ordem de entrada. Recebe n >= 0, arrays distintos e sem sobreposição, com n posições válidas em cada um. Não pode usar contentores nem funções da biblioteca. A entrada vazia deve devolver zero sem aceder aos apontadores.

Analisa esta proposta com 3, -1, 3, 8, -1, -1, 5. Prevê o que escreve, calcula o resultado pedido e corrige a condição de procura. Explica também quantas posições do destino podem ser lidas depois da chamada.

int filtrar(const int entrada[], int n, int saida[]) {
    int usados = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (entrada[i] == entrada[j]) {
                saida[usados++] = entrada[i];
                break;
            }
        }
    }
    return usados;
}

Pista

A ocorrência de índice 2 precisa de procurar antes ou depois de si?

Solução

A proposta escreve 3, -1, -1 e devolve 3: encontra parceiros à direita dos índices 0, 1 e 4. Perde o 3 do índice 2 e o -1 do índice 5, que só têm parceiros anteriores. O resultado pedido é 3, -1, 3, -1, -1, com cinco elementos.

Para cada i, percorre todos os j de 0 a n - 1 e exige j != i além da igualdade dos valores. Encontrar o próprio elemento não demonstra repetição. Ao encontrar outro, copia uma vez e termina essa procura. O percurso exterior conserva a ordem e não altera a entrada. Não uses uma coleção de valores únicos, pois o contrato conserva cada ocorrência repetida.

O chamador pode ler só o prefixo de tamanho devolvido, neste caso cinco posições, mesmo que tenha reservado sete. No vazio, o ciclo exterior não executa e o resultado é zero. Casos com um elemento e com todos distintos também dão zero; um caso em que todos os valores se repetem conserva o array completo.

Confere a tua resposta:

  • Obtive 3, -1, 3, -1, -1, com cinco posições válidas.
  • Identifiquei as últimas ocorrências perdidas pela procura só à direita.
  • Corrigi a procura, conservei a origem e respeitei a capacidade e o vazio.
Voltar à explicação
Repetir uma letra não muda o conjunto

Define int diferenca_letras(const char a[], const char b[], char saida[]) que escreve as letras presentes num dos textos, mas não nos dois, e devolve o número de letras escritas. Usa as 26 letras de a a z, ignora os restantes caracteres e trata maiúsculas como as minúsculas correspondentes. As entradas são strings de C válidas com até 1000 unidades char; o destino, sem sobreposição, tem 27 posições. Não uses contentores nem funções da biblioteca.

Prevê o resultado para a = "Bbb+c" e b = "b D!". Uma proposta subtrai as frequências e inclui b porque aparece três vezes no primeiro e uma no segundo. Explica o erro e testa também textos sem letras, conjuntos iguais e uma saída com as 26 letras.

Solução

O primeiro conjunto é b,c e o segundo é b,d. A letra b pertence aos dois, por isso fica excluída apesar da frequência diferente. Só c e d pertencem a um dos textos. A função escreve "cd", acrescenta \0 e devolve 2.

Inicializa duas tabelas de 26 valores falsos. Percorre cada texto até \0, localiza a letra num alfabeto de maiúsculas/minúsculas e marca a sua presença; ignorar um carácter é diferente de o usar como índice. Percorre depois o alfabeto e copia apenas as posições em que as duas presenças diferem. Isto produz ordem alfabética sem ordenar as entradas.

Textos sem letras e conjuntos iguais produzem uma string vazia, com saida[0] = '\0'. Se uma entrada contém todo o alfabeto e a outra não tem letras, o resultado ocupa as posições 0 a 25 e o terminador ocupa a 26. O comprimento devolvido não inclui esse terminador. Compara estes casos com a implementação da lição, que mantém as entradas intactas.

Confere a tua resposta:

  • Obtive duas letras, cd, ignorando maiúsculas e repetições.
  • Usei presença por texto e não uma diferença de frequências.
  • Escrevi em ordem alfabética e reservei o terminador, incluindo no vazio e no resultado de 26 letras.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.