Revista Do Linux
 
  
  
 

  Capa
  Evento
  Serviço Público
  Tutorial Programação Shell
  Tutorial PHP
  Software
  Entrevista
  Filosofia
assinantes
 

Standard Template Library parte II

No primeiro artigo desta série, apresentamos a STL, suas vantagens e usos, além do contêiner std::vector. Neste artigo, apresentaremos o contêiner std::string.

Aprender um novo contêiner depois de conhecer a definição de outro é muito fácil, visto que todos possuem quase as mesmas funções. Mesmo parecidos e com funções idênticas em alguns casos, nunca tente escrever um programa que seja independente do contêiner escolhido, pois é exatamente nas pequenas diferenças entre eles que se encontram sérias restrições de uso e desempenho quando se tenta escrever um programa que seja independente do contêiner.

No livro "A linguagem de programação C++", de Bjarne Stroustrup, é possível encontrar essas regras e um capítulo inteiro dedicado aos gabaritos e suas especializações.

std::string

Imagine a seguinte situação: Você deve criar um programa que receba pelo teclado o primeiro nome do usuário e o imprima na tela, junto com outras informações. Uma solução seria (não tente compilar este código, ele está incompleto):

#include <iostream> char nome[10]; cout << &quot;Informe seu 1º nome: &quot;; cin >> nome; cout << endl << endl << &quot;Olá &quot; << nome << endl;

Para nomes curtos, este programa funcionará perfeitamente e, provavelmente, por muito tempo. Os erros sempre acontecem na hora de apresentá-lo ao cliente (para variar).

Para uma pessoa que tenha o primeiro nome com mais de 9 letras, o resultado será indefinido. Conforme a especificação da linguagem C++, indefinido significa que o resultado depende do compilador e da plataforma. Em alguns casos, o programa funcionará normalmente, até mesmo imprimindo o nome do usuário com mais de 9 letras corretamente; em outros, pode gerar um erro e encerrar o programa; e em outros derrubar o sistema operacional.

Para resolver esse problema, o programador preguiçoso poderá alterar a 2ª linha do programa para:

cout << &quot;Informe seu 1º nome com até 9 letras: &quot;;

Um outro programador poderá alterar a declaração do array nome para nome[31], e o programador cuidadoso criará um array temporário com espaço suficiente para armazenar um nome grande e alocar dinamicamente o array nome.

Descartando a primeira opção, que deve deixar as pessoas com nomes grandes furiosas, pois devem odiar abreviar o seu primeiro nome, a segunda opção é válida apenas para pequenos programas ou protótipos, já que gasta recursos desnecessariamente.

A terceira opção é melhor do que a segunda, mas não é válida por motivos de segurança, imagine que o array temporário possa armazenar até 1024 caracteres. Agora imagine que alguém mal intencionado entre com o primeiro nome contendo mais de 1024 caracteres. E é por aí que a maioria desses mal-intencionados consegue derrubar e até mesmo tomar conta de servidores.

Outra solução possível é (este código também está incompleto):

#include <iostream> #include <string> std::string nome; cout << &quot;Informe seu 1º nome: &quot;; cin >> nome; cout << endl << endl << &quot;Olá &quot; << nome << endl;

Uma classe string funciona normalmente com os arquivos de entrada e saída padrões (cin, cout, cerr) e possui quase as mesmas características de um vetor. A diferença neste programa é que o nome não tem um tamanho fixo, variando conforme o informado pelo usuário, e o limite de caracteres que o programa aceita é o imposto pela plataforma, e, no caso do Linux, chega a 4.294.967.294 caracteres (4Gb, nome.max_size()). É um pouco difícil o usuário atingir este limite.

Mas a classe string não se limita somente a isso. Ela ainda fornece suporte limitado a funções legadas do C, como o exemplo inútil abaixo:

cout << &quot;seu nome contém &quot; << strlen(nome.c_str()) << &quot; letras&quot;;

A função membro da classe string c_str() retorna um array do tipo const char*, que em alguns casos é uma cópia da string armazenada na variável, podendo ser utilizada em funções legadas que não alterem o conteúdo do array. Seu compilador lhe alertará sobre isto no momento de emitir o aviso (warning) da criação de um temporário na chamada da função.

O exemplo é considerado inútil porque é de se esperar que a classe string tenha alguma função que retorne o tamanho da mesma. No caso, nome.length().

Para os programadores preocupados com desempenho e consumo de recursos, pode-se dizer que em alguns casos, especialmente com relação aos algoritmos fornecidos com a STL, a classe string pode ser mais rápida que o array char[], e, em termos de recursos, isso depende da implementação.

Algumas implementações da classe string reservam internamente até 15 bytes para a string, e se este valor for excedido, mais espaço é dinamicamente alocado. Outras implementações criam este espaço dinâmico para qualquer que seja o tamanho da string.

Para quem não teme olhar o código fonte de biblioteca, basta dar uma olhada nos cabeçalhos da classe basic_string para descobrir qual a sua implementação. A classe string ainda pode ter um valor definido na inicialização através de seu construtor:

std::string nome2(&quot;paulo assis&quot;); std::string nome3(nome);

E pode também ser declarada const:

const std::string nome4(nome2);

Algoritmos

Os algoritmos fornecidos pela classe string e pela STL formam um poderoso aliado. Escreva um programa para fazer as seguintes tarefas com um array char[] e depois imagine as mesmas tarefas com a classe string:

  • Inverter a ordem das letras dentro da string;
  • Trocar a segunda letra por "b";
  • Adicionar "..." ao final da string (cuidado);
  • Trocar todos os "a" por "b";
  • Procurar pelo primeiro "b" e mudar para "x";
  • Procurar pelo primeiro "b" começando do final da string e trocar por "x";
  • Apagar a terceira letra.

    Segue abaixo a solução utilizando a classe std::string. Para compilar o código, são necessários os includes abaixo:

    #include <iostream> #include <string> #include <algorithm> #include <functional> //Esta é apenas uma das várias soluções possíveis para os problemas. A classe string possui diversos algoritmos além dos genéricos. Normalmente, a melhor escolha é utilizar o algoritmo do próprio contêiner ao invés de um genérico: // inverte a ordem da string reverse( nome.begin(), nome.end() ); // troca a segunda letra por &quot;b&quot; nome[1] = 'b'; // adiciona &quot;...&quot; ao final da string nome += &quot;...&quot;; // troca todos os &quot;a&quot; por &quot;b&quot; replace_if( nome.begin(), nome.end(), bind2nd(equal_to<char>(),'a'),'b'); // procura pelo primeiro &quot;b&quot; e troca por &quot;x&quot; string::iterator pos; pos = find_if( nome.begin(), nome.end(), bind2nd(equal_to<char>(),'b') ); // ver também std::basic_string::find_first_of // e std::basic_string::find // e std::find if( pos != nome.end() ) *pos = 'x'; // procura pelo primeiro &quot;b&quot; começando // do final e troca por &quot;x&quot; string::reverse_iterator rpos; rpos = find_if( nome.rbegin(),nome.rend(),bind2nd(equal_to<char>(),'b')); // ver também std::basic_string::find_last_of // e std::basic_string::rfind // e std::find if( rpos != nome.rend() ) *rpos = 'x'; // apaga a terceira letra nome.erase( nome.begin() + 2 );

    Com este exemplo, é possível ver que a STL guarda um enorme poder em suas classes e algoritmos. Para aqueles que acham que a linha que troca "a" por "b" é um defeito de impressão, a explicação para tal linha é que replace_if deve receber um predicado unário como seu terceiro argumento e bind2nd(equal_to<char>(),'a') fornece esse predicado. Bind2nd fornece um método de ligação de predicados binários a um predicado unário, sendo seu primeiro parâmetro um predicado binário e o segundo um argumento a ser fornecido para o mesmo. O predicado binário equal_to<char>() executa uma comparação de dois valores retornando verdadeiro ou falso.

    Alguns predicados binários: equal_to, not_equal_to, greater, less, greater_equal, less_equal, logical_and, logical_or e logical_not. Esses predicados são utilizados por exemplo no algoritmo sort, que recebe como parâmetro um predicado de comparação, que por default é less.

    Algumas funções de ligação e adaptação: bind2nd, bind1st, mem_fun, mem_fun_ref, ptr_fun. Bind2nd já foi mostrado, bind1st cria o predicado unário a partir de um binário fornecendo o primeiro parâmetro ao invés do segundo. Mem_fun e mem_fun_ref são úteis quando é necessário fornecer uma função membro ao invés de uma função global a um algoritmo.

    char_traits e alocadores

    A classe std::string na verdade é um typedef para basic_string<char> e a classe std::wstring também é um typedef para basic_string<wchar>. A classe basic_string pode receber como parâmetro de gabarito 3 argumentos: tipo de caractere, estrutura de funções para o tipo de caractere informado (char_traits) e alocador de memória.

    O char_traits é um estrutura que fornece várias funções membros, definições de tipo e conversão, possibilitando a criação de strings para o tipo char ou wchar_t ou qualquer tipo criado pelo programador.

    O alocador, já discutido no artigo anterior, possibilita ao programador criar rotinas específicas para gerenciamento avançado de memória, como a criação de reserva de memória ou utilização de memória específica ou compartilhada.

    Os tipos std::string e std::wstring são normalmente os mais utilizados e dificilmente será necessário mexer com char_traits ou alocadores.

    Este segundo artigo da série apresentou uma breve introdução à classe string e seus usos. É recomendável a leitura de livros específicos ao assunto como The C++ programming language - 3ª edição, de Bjorne Stroustrup, The C++ Standard Library: A Tutorial and Reference, de Nicolai M. Josuttis, Effective STL, de Scott Meyers, dentre outros. Alguns destes livros são encontrados traduzidos para português.

    Após compreender a classe string e seus usos, será difícil encontrar em um programa C++ os arrays char[]. O uso da classe string continua sendo vantajoso até em programas que ainda utilizem funções legadas em C, em vista da facilidade de se converter uma string em um array char[] e vice-versa.

    Para os sistemas que desenvolvo, a STL assim como a Biblioteca Padrão C++ têm sido excepcionalmente úteis e eficientes, tanto na escrita quanto na execução destes softwares. Tornaram-se indispensáveis no dia a dia.

    Até o próximo artigo sobre a STL!

    Para saber mais:
    www.sgi.com/tech/stl/
    www.stlport.org/
    www.boost.org/


    Paulo Machado Ottani Assis - paulo@coral.srv.br


  • A Revista do Linux é editada pela Conectiva S/A
    Todos os Direitos Reservados.

    Política de Privacidade
    Anuncie na Revista do Linux