Fechei o artigo Um Nome, Vários Sentidos — Sobrecarga de Funções prometendo aposentar o char*. Cumpro agora. De todas as dores que você carrega do C, poucas são tão constantes quanto lidar com texto: char nome[50], torcer para caber, strcpy sem estourar, strlen percorrendo tudo à procura do '\0', strcat que exige espaço pré-alocado, e a memória que você aloca e você libera. O std::string do C++ varre quase tudo isso para debaixo do tapete — e o mais importante: ele cuida da própria memória. Ao entender como, você vai provar pela primeira vez o mecanismo que domina toda a Fase 2, o RAII. Guarde essa sensação; ela é a chave do curso inteiro.
O texto que sabe seu próprio tamanho e cresce sozinho
Em C, uma string é um vetor de char com um '\0' no fim, e o tamanho você descobre percorrendo. Em C++, std::string é um objeto que guarda seu tamanho e gerencia seu próprio buffer:
#include <iostream>
#include <string>
int main() {
std::string s = "Olá"; // aloca o necessário; nada de tamanho fixo
s += ", mundo"; // cresce sozinha — sem strcat, sem buffer manual
s += "!";
std::cout << s << '\n'; // Olá, mundo!
std::cout << "tamanho: " << s.size() << '\n'; // O(1): já sabe o tamanho
std::cout << "vazia? " << (s.empty() ? "sim" : "não") << '\n';
return 0;
}
Compare com o equivalente em C: você declararia um buffer, checaria capacidade antes de cada strcat, chamaria strlen toda vez que precisasse do tamanho. Aqui, += concatena e realoca se preciso, .size() responde na hora, e não há um único malloc à vista. O objeto faz o trabalho sujo.
Comparar, buscar, fatiar — sem funções soltas
O que em C exigia strcmp, strstr e aritmética de ponteiro vira método ou operador:
#include <iostream>
#include <string>
int main() {
std::string a = "banana";
std::string b = "banana";
// Comparação direta com == (em C seria strcmp(a,b) == 0)
if (a == b) std::cout << "iguais\n";
// Busca: retorna o índice, ou std::string::npos se não achar
std::string frase = "o rato roeu a roupa";
auto pos = frase.find("roeu");
if (pos != std::string::npos)
std::cout << "achei em " << pos << '\n'; // achei em 8
// Fatia: substr(inicio, quantidade)
std::cout << frase.substr(2, 4) << '\n'; // "rato"
// Índice individual, como em C, mas com .at() opcionalmente checado
std::cout << frase[0] << '\n'; // 'o'
return 0;
}
Uma honestidade importante sobre índices: frase[i] não verifica limites — passar do fim é comportamento indefinido, exatamente como indexar fora de um vetor em C. Se quiser a checagem, frase.at(i) lança uma exceção ao ultrapassar o tamanho (exceções são assunto da Fase 6). Use [] no caminho quente onde você já garantiu o índice, e .at() quando a segurança vale o custo.
Lendo linhas inteiras: std::getline
No artigo A Filosofia por Trás do C++ e a Saída Elegante do printf vimos que std::cin >> s para no primeiro espaço. Para ler uma linha completa, existe std::getline:
#include <iostream>
#include <string>
int main() {
std::cout << "Digite uma frase: ";
std::string linha;
std::getline(std::cin, linha); // lê até o '\n', espaços incluídos
std::cout << "Você escreveu: " << linha << '\n';
return 0;
}
Isso resolve, sem drama, o que em C exigia fgets com um buffer de tamanho arbitrário e o risco de a linha não caber.
A primeira degustação de RAII
Chegamos ao ponto central. Pergunte-se: quando aquela std::string s do primeiro exemplo é destruída, quem libera a memória que ela alocou para crescer? A resposta é o próprio std::string. Quando o objeto sai de escopo — quando a chave } que o contém termina —, um método especial dele, o destrutor, roda automaticamente e devolve a memória ao sistema. Você não escreve free. Você não pode esquecer o free. Não existe vazamento possível, porque a liberação está amarrada ao fim de vida do objeto:
#include <string>
void demonstra() {
std::string temp = "aloco bastante texto aqui...";
// temp usa memória do heap para guardar o texto.
// Ao chegar no '}', o destrutor de temp roda e libera tudo.
// Nenhum free() escrito por você. Impossível vazar.
} // ← aqui a memória de 'temp' é devolvida, automaticamente
Esse é o RAII — Resource Acquisition Is Initialization: o recurso (a memória) é adquirido quando o objeto nasce e liberado quando ele morre, e o "morrer" acontece sozinho no fim do escopo. É a resposta do C++ ao par malloc/free do C, e a Fase 2 inteira, a partir do próximo bloco de artigos, é dedicada a você aprender a construir seus próprios tipos que se comportam assim. std::string é só o primeiro de muitos.
Onde o char* ainda aparece: conversando com C
Seria desonesto dizer que o char* sumiu de vez. Bibliotecas de C — e há montanhas delas — esperam const char*. Para entregar sua std::string a elas, use .c_str(), que devolve um ponteiro para o conteúdo terminado em '\0':
#include <string>
#include <cstdio> // printf, uma API de C
int main() {
std::string nome = "Marcelo";
// printf espera const char* — .c_str() faz a ponte
std::printf("Olá, %s\n", nome.c_str());
return 0;
}
Uma ressalva honesta e importante: o ponteiro de .c_str() é válido apenas enquanto a std::string existir e não for modificada. Guardá-lo e usá-lo depois de a string mudar ou morrer é o mesmo dangling pointer de sempre. Use-o na hora, para a chamada, e não o estoque.
O que o std::string entrega não é conveniência de sintaxe: é a memória deixando de ser problema seu. Ele adquire o buffer ao nascer, redimensiona quando precisa e libera ao sair de escopo — e é por isso que serve de primeira amostra do RAII. O char* não desaparece do mundo: continua sendo a fronteira com as bibliotecas escritas em C, e o c_str() existe para essa travessia, com a ressalva de que o ponteiro devolvido pertence à string e morre junto com ela.
Fontes e leituras recomendadas
- cppreference.com/w/cpp/string/basic_string: a referência completa de
std::string, com todos os métodos citados (find,substr,at,c_str) e suas garantias. - Bjarne Stroustrup, A Tour of C++ (3ª ed.), seção "Strings and Regular Expressions": a visão moderna do tipo, incluindo
std::string_view(que veremos adiante). - cppreference.com/w/cpp/string/basic_string/c_str: os detalhes precisos sobre a validade do ponteiro retornado — leitura obrigatória antes de interoperar com C.
- Scott Meyers, Effective C++ (3ª ed.), itens sobre gerenciamento de recursos: contextualizam por que tipos como
std::stringeliminam classes inteiras de bugs de memória. - ISO C++ Core Guidelines, regra SL.str.1 ("Use
std::stringto own character sequences"): a recomendação oficial de preferirstd::stringa arranjos dechar.
Exercícios
Exercício 1
Leia uma frase inteira com std::getline e imprima quantos caracteres ela tem, usando .size().
Ver resposta
✓ Resposta: Direto ao ponto:
#include <iostream>
#include <string>
int main() {
std::string linha;
std::cout << "Frase: ";
std::getline(std::cin, linha);
std::cout << "Caracteres: " << linha.size() << '\n';
return 0;
}
.size() responde em tempo constante — o objeto já conhece o próprio tamanho, ao contrário do strlen, que percorre.
Exercício 2
Dadas duas strings nome e sobrenome lidas do usuário, monte uma terceira completo = nome + " " + sobrenome e imprima-a com sua primeira letra maiúscula (reaproveite a ideia do artigo Referências e Ponteiros, Frente a Frente).
Ver resposta
✓ Resposta: Concatenação com + e ajuste do primeiro caractere:
#include <iostream>
#include <string>
#include <cctype>
int main() {
std::string nome, sobrenome;
std::cout << "Nome: "; std::cin >> nome;
std::cout << "Sobrenome: "; std::cin >> sobrenome;
std::string completo = nome + " " + sobrenome;
if (!completo.empty())
completo[0] = std::toupper(completo[0]);
std::cout << completo << '\n';
return 0;
}
Exercício 3
Escreva uma função bool contem(const std::string& texto, const std::string& agulha) que devolve true se agulha aparece em texto. Use find e npos, e explique por que os parâmetros são const&.
Ver resposta
✓ Resposta: A função e a justificativa:
#include <string>
bool contem(const std::string& texto, const std::string& agulha) {
return texto.find(agulha) != std::string::npos;
}
Os parâmetros são const& porque a função apenas lê as duas strings: a referência evita copiar textos possivelmente grandes a cada chamada, e o const garante — verificado pelo compilador — que nenhuma delas será alterada. É o padrão do artigo Referências e Ponteiros, Frente a Frente aplicado a um caso real.
Exercício 4
O programa abaixo tem um bug de tempo de vida. Aponte-o e corrija:
#include <cstdio>
#include <string>
const char* saudacao() {
std::string s = "oi";
return s.c_str(); // devolve ponteiro para o conteúdo de s
}
int main() {
std::printf("%s\n", saudacao());
return 0;
}
Ver resposta
✓ Resposta: O bug: saudacao devolve o ponteiro de .c_str() de uma std::string local, que é destruída ao fim da função. O printf no main usa memória já liberada — comportamento indefinido. Correção: devolver a própria std::string (que copia/move o conteúdo para o chamador) e chamar .c_str() só na hora do uso:
#include <cstdio>
#include <string>
std::string saudacao() { // devolve por valor: o conteúdo sobrevive
std::string s = "oi";
return s;
}
int main() {
std::string g = saudacao();
std::printf("%s\n", g.c_str()); // c_str() usado enquanto 'g' está vivo
return 0;
}
É a mesma lição da referência pendente do artigo Referências e Ponteiros, Frente a Frente, agora com ponteiro para o buffer interno.
Exercício 5
Sem usar nenhuma função da biblioteca além de .size() e indexação, escreva std::string invertida(const std::string& s) que devolve s de trás para frente. Depois reflita: em C, o que você teria de gerenciar manualmente que aqui o std::string cuida sozinho?
Ver resposta
✓ Resposta: A inversão manual:
#include <iostream>
#include <string>
std::string invertida(const std::string& s) {
std::string r; // começa vazia
for (std::size_t i = s.size(); i > 0; --i)
r += s[i - 1]; // acrescenta do fim para o começo
return r; // devolve por valor
}
int main() {
std::cout << invertida("mundo") << '\n'; // odnum
return 0;
}
Em C, você teria de alocar um buffer do tamanho certo (mais o '\0'), copiar caractere a caractere e lembrar de free no chamador — três oportunidades de bug (tamanho errado, esquecer o terminador, vazar). Aqui, r cresce sozinha com +=, conhece o próprio tamanho e libera a memória automaticamente ao fim de vida. É RAII trabalhando por você.