Ir ao conteúdo

C Como concatenar uma string sem usar biblioteca especializada?


Ir à solução Resolvido por arfneto,

Posts recomendados

Postado

Fala galera... Queria saber a lógica para concatenar strings em C usando só as bibliotecas padrão, me deu uma curiosidade de como é a lógica por trás disso. Se alguém puder explicar eu agradeço!

Postado

string.h é uma biblioteca padrão, e a lógica padrão é usar strcat e passar o endereço das duas strings. O primeiro parâmetro é o destino. O retorno é o endereço do destino. Não é uma rotina segura porque não tem como controlar se no destino cabem as duas strings.

 

Não sei o que dizer de uma biblioteca especializada. O que seria?

 

Se quer saber a lógica de strcat, é bem linear:

  • Navega até o fim do destino, que é onde está o zero, já que é uma string. 
  • Começa a copiar do destino até e incluindo o zero do destino
  • retorna.

 

  • Curtir 1
Postado
4 horas atrás, André Gomides disse:

Fala galera... Queria saber a lógica para concatenar strings em C usando só as bibliotecas padrão, me deu uma curiosidade de como é a lógica por trás disso. Se alguém puder explicar eu agradeço!


Na verdade você nem precisa das bibliotecas padrões porque uma string é apenas um array de elementos do tipo char. Então, normalmente, representa-se uma string assim:

 

struct string
{
	char *array;
	size_t length, max_length;
};

 

Aqui max_length é a capacidade máxima de caracteres que podem ser guardados no array, ou seja, um tamanho bem grande alocado com malloc(). E length é a quantidade real de caracteres utilizados no array, de maneira que length < max_length. Efetivamente lenght é a posição do \0 que delimita o fim da string.

Então, caso você queira concatenar a string B numa string A, basta testar se
 

B.length < (A.max_length - A.length)


e, caso positivo, copiar os elementos de B pra A usando um loop:

 

for (n = 0; n < B.length; ++n)
	A.array[A.length + n] = B.array[n];

 

E atualizar o novo tamanho de A:
 

A.length += B.length;

A.array[A.length] = '\0';


Caso o teste seja negativo, você aumenta max_length primeiro, realoca um array maior com a função realloc() e apenas então faz uma copia (do mesmo jeito).

Caso você queira usar as bibliotecas padrões a todo custo, basta substituir a variável length por uma chamada da função strlen(), substituir o loop por uma chamada da função strcpy() etc. E por fim strcat() que copia um array de char pro fim de um outro (como fizemos acima) mas não faz o aumento da capacidade do destino automaticamente.

Postado

@arfneto Talvez eu tenha me expressado mal... Quando digo biblioteca especializada é uma biblioteca que já tenha a função de juntar strings tipo a string.h. Quero saber a lógica usando só a stdio.h.

@V!OLADOR Muito bom amigão! Estou aprendendo a usar ponteiro agora, quando eu uso o char* eu estou apontando para o primeiro endereço de uma array (me corrija se eu estiver errado), se eu quiser percorrer essa array como faço?

Postado
43 minutos atrás, André Gomides disse:

Estou aprendendo a usar ponteiro agora, quando eu uso o char* eu estou apontando para o primeiro endereço de uma array (me corrija se eu estiver errado)


Correto mas não apenas no caso de char. Um ponteiro sempre aponta pro primeiro elemento independente do tipo em questão. 
 

45 minutos atrás, André Gomides disse:

se eu quiser percorrer essa array como faço?


No caso específico de uma string, você pode fazer isso de várias formas. Tradicionalmente, você atravessa o array e checa se o respectivo elemento é '\0'. Em C, usa-se essa convenção de colocar esse carácter especial ao final das strings pra representar exatamente isso, o fim. Então, usando um loop:
 

for (n = 0; string[n] != '\0'; ++n)

 
Efeito colateral desse loop: n automaticamente também conta o número de caracteres na string.

Postado
7 horas atrás, V!OLADOR disse:

basta substituir a variável length por uma chamada da função strlen(), substituir o loop por uma chamada da função strcpy() etc. E por fim strcat() que copia um array de char pro fim de um outro (como fizemos acima) mas não faz o aumento da capacidade do destino automaticamente

 

O texto como está deixa uma certa noção de que strcpy seja em algo diferente de strcat em relação ao "aumento automático da capacidade do destino".

 

Não é.

 

Essas rotinas são frágeis. Nenhuma delas muda o tamanho de nada e nem teria como, do modo como isso é implementado.

 

  • nenhuma delas aumenta a string.
  • nenhuma delas testa se uma string está no meio da outra na chamada
  • strings em C não tem tamanho como tal. Dependendo de como alocou a variável ela terá um tamanho dado por sizeof, e esse não será o tamanho da string mas apenas o tamanho do array. Considere o programa

    

#include <stdio.h>
#include <string.h>
int main(void)
{ 
    double v1 = 42.41;
    char string[8] = "1234567";
    double v2 = 42.42;
    string[7] = '8';
    printf( "sizeof: %u, sltrlen = %u\n",
            sizeof string, strlen(string) );
    string[2] = 0;
    printf( "sizeof: %u, sltrlen = %u\n",
            sizeof string, strlen(string) );
    return (0);
}


A cada vez que compilar e rodar vai dar um resultado. Um possível
 

sizeof: 8, sltrlen = 25
sizeof: 8, sltrlen = 2


Vai depender de onde estiver o próximo 0 na memória no primeiro printf().
 

  • o "tamanho" da string não existe e é computado a cada uso pela distância entre o início dela e o primeiro zero, e o zero pode estar depois da área alocada para ela.

    @V!OLADORessa estrutura E o código todo como mostrou

 

    struct string
    {
    	char *array;
    	size_t length, max_length;
    };

 

não deixam claro que é problema do programa colocar valores corretos nessas variáveis, o que nas strings em C++ é feito pelo construtor de modo transparente. Mas é problema do programa e não há qualquer garantia da linguagem...

 

Por exemplo, acima a única coisa que se sabe e que *array é um char.

 

strcat

 

Esse é o protótipo de strcat:
 

    char* strcat ( char* destination, const char* source );

 

53 minutos atrás, André Gomides disse:

Quero saber a lógica usando só a stdio.h

 

Não precisa de stdio.h para isso. A razão de ter stdio.h num teste desses é para poder usar printf(). Nada tem a ver com a string.

 

Uma implementação comum de strcat()

 

Isso eu escrevi agora, não é oficial. Acho que strcat() em geral é escrita usando strlen() e memcpy()

 

char* strcat ( char* dest, const char* nova )
{ 
    char* p = dest; // vai retornar dest
    while ( *p != 0 ) p+=1; // avanca
    do { *p++ = *nova++; } while ( *nova != 0 );// copia o resto
    return dest; // retorna o mesmo
};

 

Só 4 linhas. A lógica é linear. Veja os comentários. Acho que é a maneira clássica de escrever isso.

 

Um programa de teste completo

 

#include <stdio.h>

char* strcat ( char* dest, const char* src);

int main(void)
{ 
    char origem[70] = "Forum";
    char resto[] = " Clube do Hardware";
    printf("strings originais: A = \"%s\" B = \"%s\"\n", origem, resto );
    char* p = strcat( origem, resto);
    printf( "A+B = \"%s\"\n", p );
    printf( "A string nova = \"%s\"\n", origem );
    return (0);
}


char* strcat ( char* dest, const char* nova )
{   char* p = dest; // vai retornar dest
    while ( *p != 0 ) p+=1; // avanca
    do { *p++ = *nova++; } while ( *nova != 0 );// copia o resto
    return dest; // retorna o mesmo
};

 

Que deve mostrar

 


strings originais: A = "Forum" B = " Clube do Hardware"
A+B = "Forum Clube do Hardware"
A string nova = "Forum Clube do Hardware"

 

Postado
55 minutos atrás, arfneto disse:

O texto como está deixa uma certa noção de que strcpy seja em algo diferente de strcat em relação ao "aumento automático da capacidade do destino".

 

Não é.


Salve salve, Arfneto! Bom, como o texto diz claramente
 

Citação

(...) mas não faz o aumento da capacidade do destino automaticamente


acho que não há essa suposta noção de ele diria exatamente o contrário. 😅

Apenas citei as duas, strcpy() e strcat(), em contextos diferentes porque elas existem na API e pretendem fazer coisas diferentes quando basicamente fazem a mesma. Uma questão de conhecimentos gerais, digamos. Mas você tem razão sobre elas não serem tão seguras. Pessoalmente, não recomendo o uso.

 

55 minutos atrás, arfneto disse:

não deixam claro que é problema do programa colocar valores corretos nessas variáveis,


Não sei se entendi essa parte. O que você quer dizer com "valores corretos"? Obviamente aquele trecho de código tá propositalmente incompleto porque ele existe apenas pra ilustrar outra coisa, precisamente a resposta da pegunta especifica feita pelo autor do tópico (concatenação) e nada mais.

Postado
2 horas atrás, V!OLADOR disse:

Não sei se entendi essa parte. O que você quer dizer com "valore corretos"

 

:) Olá

 

Eu escrevi isso porque pode parecer que o código que você escreveu funcione sem mais nada. Tem uma cara séria e até a struct chama string :) 

 

struct string
{
	char *array;
	size_t length, max_length;
};

 

13 horas atrás, V!OLADOR disse:

Aqui max_length é a capacidade máxima de caracteres que podem ser guardados no array, ou seja, um tamanho bem grande alocado com malloc(). E length é a quantidade real de caracteres utilizados no array, de maneira que length < max_length. Efetivamente lenght é a posição do \0 que delimita o fim da string

 

 

No entanto isso é consequência do programa e não algo que de fato exista. E no texto não está claro isso, ou eu simplesmente não consegui ver, o que é também possível :) 

 

Note que essa struct string está bem mais próxima da string em Pascal do que em C. Em Pascal a string tem um indicador de tamanho no início. Até você escrever 

 

    A.array[A.length] = '\0';


 

E aí tem as duas coisas: um campo com o tamanho na struct, length, e um 0 marcando o fim. E um campo marcando o máximo.

 

Note que '\0' é a mesma coisa que \0 que é a mesma coisa que 0. Só que tem 4 letras.

 

Claro que isso funciona como você escreveu, desde que seja construído.

 

Eu só queria deixar isso claro.

 

Em C++ string é algo assim, e por isso existe um construtor, um destrutor e tal: para gerenciar a memória. E construtores de cópia e atribuição e tal, isso para fazer coisas como copiar do jeito que você mostrou. E até o operador + que é exatamente o strcat() para duas strings: A+B.

 

Como seria usar uma coisa dessas em C?

 

Como cada função tem umas 10 linhas se tanto vou colocar dentro do meu exemplo de antes. 

 

Esse seria o comum de implementar isso em C, quase como em C++

 


typedef struct
{
    size_t length;
    size_t max_length;

    char* array;

} String;

String*     apaga(String*);
String*     cria(size_t, const char*);
String*     resize(String*, size_t);
String*     strcat(String* dest, const String* src);
size_t      strlen(String*);

 

Não vou escrever cópias e outras funções, é só um exemplo. Mas claro que se poderia escrever ponteiros para as funções e colocar dentro da struct e aí ficaria como em C++, que foi escrita... em C.

 

O exemplo, de volta
 

    char origem[70] = "Forum";
    char resto[] = " Clube do Hardware";

 

vira 

 

    String* origem = cria(5, "Forum");
    String* resto  = cria(25, " Clube do Hardware");

 

O teste do exemplo fica, usando String:

 


    printf( "\
strings originais: A = \"%s\" (len = %zu, size = %zu)\n\
                   B = \"%s\" (len = %zu, size = %zu)\n",
        origem->array, strlen(origem), origem->max_length,
        resto->array, strlen(resto), resto->max_length
        );
    strcat(origem, resto);
    printf( "\
Depois de strcat   A = \"%s\" (len = %zu), size= %zu\n",
        origem->array, strlen(origem), origem->max_length
    );
    origem = apaga(origem);
    resto = apaga(resto);
    return (0);
}

 

E mostra

 

strings originais: A = "Forum" (len = 5, size = 5)
                   B = " Clube do Hardware" (len = 18, size = 25)
Depois de strcat   A = "Forum Clube do Hardware" (len = 23), size= 23

 

Deixei assim para forçar um resize() dentro de strcat(), como @V!OLADORcitou.

 

Eis o programa todo

 

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    size_t length;
    size_t max_length;

    char* array;

} String;

String*     apaga(String*);
String*     cria(size_t, const char*);
String*     resize(String*, size_t);
String*     strcat(String* dest, const String* src);
size_t      strlen(String*);

int main(void)
{
    String* origem = cria(5, "Forum");
    String* resto  = cria(25, " Clube do Hardware");

    String* vazia = cria(300,NULL); // vazia
    vazia         = apaga(vazia); // exemplo

    printf( "\
strings originais: A = \"%s\" (len = %zu, size = %zu)\n\
                   B = \"%s\" (len = %zu, size = %zu)\n",
        origem->array, strlen(origem), origem->max_length,
        resto->array, strlen(resto), resto->max_length
        );
    strcat(origem, resto);
    printf( "\
Depois de strcat   A = \"%s\" (len = %zu), size= %zu\n",
        origem->array, strlen(origem), origem->max_length
    );
    origem = apaga(origem);
    resto = apaga(resto);
    return (0);
}

String*     apaga(String* alvo)
{
    if (alvo == NULL) return NULL;
    free(alvo->array);  // apaga os dados
    return NULL;
}

String*     cria(size_t tamanho, const char* valor)
{
    String* nova = (String*)malloc(sizeof(String));
    nova->max_length = tamanho;
    nova->length = 0;
    nova->array = (char*) malloc (1 + tamanho*sizeof(char));
    *nova->array = 0;
    *(nova->array + tamanho) = 0; // for safety
    if ( valor == 0 ) return nova; // estava em branco
    size_t ix = 0; // veio algo, copia
    for( ix=0; valor[ix] != 0; ix+=1)
    {
        nova->array[ix] = valor[ix];
        if (ix == tamanho)
        {
            nova->length = ix;
            return nova;
        }
    }
    nova->array[ix] = 0;
    nova->length = ix;
    return nova;
}

String*     resize(String* antes, size_t tam)
{
    if (antes == NULL) return NULL;  // sem string
    char* novo = (char*)realloc(antes->array, 1 + tam * sizeof(char));
    if (novo == NULL) return antes;  // erro
    antes->array = novo;
    *(antes->array + tam) = 0;
    antes->max_length     = tam;
    return antes;
}

String*     strcat(String* dest, const String* src)
{
    if (dest == NULL) return NULL;
    size_t novo = dest->length + src->length;
    if (novo > dest->max_length) resize(dest,novo); // aumenta
    char* p  = dest->array;  // vai retornar dest
    char* or = src->array;
    while (*p != 0) p += 1;  // avanca
    do {
        *p++ = *or++;
    } while (*or != 0);  // copia o resto
    *p = 0; // termina essa
    dest->length = novo;
    return dest;          // retorna o mesmo
};

size_t      strlen(String* S)
{
    if (S == NULL) return 0;
    return S->length;
}

 

  • Obrigado 1
Postado
1 hora atrás, arfneto disse:

Eu só queria deixar isso claro.


Entendi. Tudo clarificado.

 

1 hora atrás, arfneto disse:

Tem uma cara séria e até a struct chama string :)


Sim, tem uma cara séria mas só a cara mesmo. Não foi planejada pra se transformar numa biblioteca propriamente dita muito embora eu tenha querido deixar isto implícito, de maneira que o leitor pudesse imaginar que, possivelmente, struct string vem acompanhada de uma API pra manipulá-la em vários cenários distintos. Mas nesse tópico o intuito era apenas um cenário: concatenação.

Fun fact: aquela struct realmente faz parte de uma biblioteca que eu escrevi anos atrás pra um projeto de um compilador. E ela realmente tem uma longa API com várias funções pra fazer todo tipo de coisa (tokens, concatenar, inserir, cortar, trocar etc.). Talvez a unica diferença seja que o código cliente tem acesso a um ponteiro opaco, através do header .h, e a struct é mantida apenas dentro da implementação .c, de maneira que não é possível acessar diretamente o array, length e max_length. Ah, a struct original tem também um outro membro, chamado use_omp do tipo bool, pra ligar (ou desligar) o uso de threads que aceleram o funcionamento interno. 

  • Curtir 1
Postado
5 horas atrás, arfneto disse:
char* strcat ( char* dest, const char* nova )
{   char* p = dest; // vai retornar dest
    while ( *p != 0 ) p+=1; // avanca
    do { *p++ = *nova++; } while ( *nova != 0 );// copia o resto
    return dest; // retorna o mesmo
};
  • Cara, muito massa! Deixa eu só tirar algumas duvidas na lógica aqui... quando atribui dest a p você está jogando o endereço de memoria da primeira string para p, correto?
  • Dai você faz uma condição enquanto *p for diferente de 0 (porque o zero? ele representa um espaço??) ele vai avançar, mas como isso tem como avançar sem ser um arranjo?
  • Também, quando você faz o *p++= *nova++ o que está acontecendo?  
  • Solução
Postado

:) São 2 linhas na prática, mas veja assim

 

  1   char* strcat ( char* dest, const char* nova )
  2   {   char* p = dest; // vai retornar dest
  3       while ( *p != 0 ) p+=1; // avanca
  4       do { *p++ = *nova++; } while ( *nova != 0 );// copia o resto
  5       return dest; // retorna o mesmo
  6   };

 

Está claro que strcat() retorna dest, certo?

 

E porque isso? Para poder usar numa expressão., como

 

Do exemplo:
 

    char* p = strcat( origem, resto);
    printf( "A+B = \"%s\"\n", p );

 

podia ser

 

    printf( "A+B = \"%s\"\n", strcat( origem, resto) );

 

Então é claro que o return vai ser
 

        return dest;


a linha 5. E então tem que salvar esse valor ou não vai ter o que retornar. Por isso a linha 2

 

    char* p = dest;

 

Porque assim usa p em vez de dest para poder retornar o cara inalterado.... 

 

Uma string em C não é uma coisa que exista: é uma convenção: uma série de coisas terminada por um zero.

 

Então pra juntar duas, nesse caso para colocar uma segunda no fim da primeira, tem que fazer duas coisas

  • achar o fim da primeira
  • copiar até achar o fim da segunda

É só isso.

 

Como no final da string concatenada vai ter um 0 porque é uma string afinal, e se sabe que no final da segunda já tem um zero porque também é uma string, isso resolve a escolha dos loops:

  • o while() testa antes, porque precisa parar ao achar o zero.
  • O do() testa depois porque precisa incluir o 0, mesmo que a segunda string seja vazia, porque tem que ter um zero no fim.

A linha 3 avança p, o ponteiro para a letrinha na string de destino, até encontrar um 0. Mas não passa do 0, porque é de onde começa a copiar a outra :) 

 

O asterisco é um operador e *p significa o conteúdo de seja lá para onde p aponte. Como p é char* será uma letra.

 

  3       while ( *p != 0 ) p+=1; // avanca

 

A linha 4 copia as letras da string nova, até e incluindo o 0 para terminar a string de saída sem ficar fazendo testes.

 

  4       do { *p++ = *nova++; } while ( *nova != 0 );// copia o resto

 

quando o while encontra *nova == 0 e sai já  copiou o 0 e a string de saída está completa. Os ++ avançam os ponteiros, e como vem DEPOIS da variável ele copia primeiro e soma depois.

 

E assim essa rotina deve ser bem mais rápida que a strcat() original, que usa memcpy() e strlen().

Postado
Em 11/09/2021 às 19:15, André Gomides disse:
  • Cara, muito massa! Deixa eu só tirar algumas duvidas na lógica aqui... quando atribui dest a p você está jogando o endereço de memoria da primeira string para p, correto?
  • Dai você faz uma condição enquanto *p for diferente de 0 (porque o zero? ele representa um espaço??) ele vai avançar, mas como isso tem como avançar sem ser um arranjo?
  • Também, quando você faz o *p++= *nova++ o que está acontecendo?  

O que eu poderia dizer sobre esse *p++=*nova++ é mais ou menos assim... os dois são caixinhas compartimentadas. Imagina que uma sequencia de compartimentos um do lado da outra. Se elas tiverem o mesmo tamanho ou pp sendo maior é o limite... Mas se o tamannho do buffer armazenado de nova for maior, vai dar caca... Entenda... as vezes o buffer deve ser maior ou igual a string que ele guarda.

o ++ diz que deve ser lida a posição seguinte desse compartimento.
Esse exemplo é mais elegante que o que posto aqui, mas acho que vale porque é da propria linguagem c.

Então, agora que você entende que a caixinha tem o tamanho limite de buffer deve entender que esse zero então pergunta se atingiu o limite inicial da string com que compara.

Já tentou imprimir algo assim pra ver o que acontece e incluir essa string no lugar de nova?
printf("%s\n", "Olá! \0 tudo bem?");

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	// array com as strings, substitue pelo que quiser... 
	char *minhasStrings[4] = {"Wonderfull ", "World, ", "Beautifull ", "people!"};

	// copiando "Hello! para buffer... um jeito... feio!
	char buffer[50]="Hello! ";
	// pega o tamanho do array. você substitui pelo que quiser...
	int quantidade = sizeof(minhasStrings)/sizeof(char*);
	for(int i=0; i<quantidade; i++)
	{
		// concatena
		strcat(buffer, minhasStrings[i]);
	}
	// imprime resultado de buffer
	printf("%s\n", buffer);
	// copia buffer para char *str
	char *str = buffer;
	printf("Repito: %s\n", str);
	// copia "tchau!" para buffer... um jeito um pouco mais bonito... 
	strcpy(buffer, "tchau!");
	printf("%s\n", buffer);

	return 0 ;
}


 

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...