Escrevendo personalizado I / O

Alguns programadores não são grandes fãs de sobrecarregar os operadores padrão para classes definidas pelo usuário C ++. Isto inclui escrever inserters personalizados e exaustores. No entanto, muitos programadores preferem criar seus operadores de entrada / saída próprio costume. Esta introdução leva você através das etapas de criação de um primeiro dispositivo de inserção personalizado (saída) e, em seguida, um extrator de costume (entrada).

Criar a classe

Crie sua classe, sem que se refere à entrada ou saída. Incluir obter e conjunto funções para recuperar e definir valores individuais dentro de um objeto. O exemplo que se segue é um Aluna classe para uso nesta secção. Eis o conteúdo da Student.h arquivo de inclusão:

classe Student {public: Estudante explícita (char const * pszName = "", longa id = 0, duplo GPA = 0,0) - // get e funções definidas para este classstring getName () const-vazio setName (string s) -long getID () const-vazio setId (long nID) -duplo getGPA () const-vazio setGPA (DGPA duplo) -} -

Este código não inclui a implementação do obter e conjunto métodos. Os detalhes não devem influenciar a criação do dispositivo de inserção e de extracção.

A hipótese acima é que a conjunto funções executar algum tipo de controlo para detectar entrada inválida - por exemplo, setGPA () não deve permitir-lhe definir o ponto de classe média para um valor fora da faixa de 0,0 a 4,0.

Criar um dispositivo de inserção simples

O insersor deve debitar um Aluna objeto, quer para exibição ou para um arquivo. O seguinte protótipo seria adicionado ao Student.h arquivo de inclusão:

ostream operatorlt; lt; (ostream para fora, Student const s) -

A inserção é declarado com um tipo de retorno ostream e retorna o objeto passado para ele. Caso contrário, um comando como o seguinte não iria funcionar:

cout lt; lt; "Meu aluno é" lt; lt; meu aluno lt; lt; endl-

O objeto retornado de lt; lt; meu aluno é utilizado na lt; lt; endl que se segue.

A implementação deste dispositivo de inserção é simples (normalmente isso iria aparecer no Student.cpp Arquivo):

ostream operatorlt; lt; (ostream para fora, Student const s) {int prev = out.precision (2) Check-out lt; lt; s.getName () lt; lt; "(" lt; lt; s.getID () lt; lt; ")" Lt; lt; "/" lt; lt; s.getGPA () - retornar saída}

Experimentá-lo e fazer ajustes

Então, vamos ver como isso funciona inserção:

// CustomIO - desenvolver um inserter personalizado e extrator # incluem #incluir #incluir #include "student.h" using namespace std-int main (int argc, char * pArgs []) {s1 Student ( "Davis", 123456, 3.5) -cout lt; lt; s1 lt; lt; endl-Student s2 ( "Eddins", 1, 3) -cout lt; lt; s2 lt; lt; endl-cout lt; lt; "Pressione Enter para continuar ..." lt; lt; endl-cin.ignore (10, ' n') - cin.get () - retornar 0-}

Isso gera o seguinte resultado:

Davis (123456) /3.5Eddins (1) / 3

Se isso é bom, então você está feito. No entanto, para aplicações profissionais, você provavelmente vai querer implementar algumas regras de saída como a seguinte (estes são apenas alguns exemplos):

  • # 42-A escola em que IDs de estudante são seis dígitos. Se o número for inferior a um total de seis dígitos, em seguida, o número deve ser preenchido à esquerda com zeros.

  • # 42-Grade ponto médias são normalmente apresentados com dois dígitos depois do ponto decimal.

Felizmente, existem controles que implementam esses recursos. No entanto, ser um pouco cuidadoso antes de ajustar a formatação de saída. Por exemplo, suponha que a inserção você queria saída de um parâmetro inteiro é em formato hexadecimal. Os usuários do dispositivo de inserção seria muito surpreendido se toda a saída subsequente apareceu em hexadecimal ao invés de decimal. Portanto, é importante registrar que as configurações anteriores são e restaurá-los antes de voltar da nossa inserção.

Uma versão da inserção gussied aparece da seguinte forma:

ostream operatorlt; lt; (ostream para fora, Student const s) {fora lt; lt; s.getName () lt; lt; "(" - // Forçar o ID para ser um de seis dígitos fieldchar prevFill = out.fill ( '0') - out.width (6) Check-out lt; lt; s.getID () - out.fill (prevFill) - // agora a saída do resto do Studentint prevPrec = out.precision (3) -ios_base :: fmtflags prev = out.setf (ios_base :: showpoint) Check-out lt; lt; ")" lt; lt; "/" lt; lt; s.getGPA () - out.precision (prevPrec) -out.setf (ant) -Retornar saída}

Você pode ver que a inserção emite o nome do aluno, assim como antes. No entanto, antes de emitir ID do aluno, ele define a largura do campo para seis caracteres e define o caractere de preenchimento para a esquerda para 0.

A largura do campo aplica-se apenas a muito próxima saída por isso é importante para definir esse valor imediatamente antes do campo que deseja impactar. Porque ele tem a duração de apenas um campo, não é necessário para gravar e restaurar a largura do campo.

Uma vez que o campo tem sido de saída, o caractere de preenchimento é restaurado para o que era antes. Da mesma forma a precisão é definido para três dígitos e o ponto decimal é forçado sobre antes de exibir o ponto de classe média. Isso força a 3 para exibir como 3.00.

A saída resultante aparece como se segue:

Davis (123456) /3.50Eddins (000001) /3.00

o extractor

O trabalho de criar o extrator na verdade começa com a inserção. Observe que, ao criar o formato de saída para o meu SLUNO objeto, o programador adicionou alguns marcações especiais que permitam um extrator para se certificar de que o que ele está lendo é realmente uma Aluna.Por exemplo, ela incluiu parênteses em torno da carteira de estudante e uma barra entre ele eo GPA.

Meu extractor pode ler estes campos para certificar-se de que ele vai ficar em sincronia com o fluxo de dados. Na visão geral, o extrator será necessário realizar as seguintes tarefas:

  • # 42-Leia o nome (uma cadeia de caracteres).

  • # 42 Leia um parêntese de abertura.

  • # 42 Ler um ID (um inteiro).

  • # 42 Leia um parêntese fechado.

  • # 42-Leia uma barra.

  • # 42-Leia o GPA (um número de ponto flutuante).

Ele terá de fazer isso, embora conscientes do facto de um problema de formatação ocorre o tempo todo. O que se segue é a minha versão do exaustor Student:

istream operador >> (istream in, Estudante s) {// ler valores (ignorar espaço em branco extra) cadeia de nome de comprimento nID-double DGPA-char openParen = 0, closedParen = 0, slash = 0-ios_base :: fmtflags prev = in.setf (ios_base :: skipws) -in >> nome >> openParen >> nID >> closedParen >> cortar >> DGPA-in.setf (ant)! - // se os marcadores não correspondem ... if (openParen = '(' || closedParen = ')' || cortar = '/') {// ... então este não é um Studentin.setstate legal (ios_base :: failbit) -Retornar in -}! // tentar definir o valuestry estudante {s.setName (nome) -s.setID (nID) -s.setGPA (DGPA) -} catch (...) {// algo não está certo - bandeira do failurein.setstate (ios_base :: failbit) -throw- } retornar in-}

Esta inserção começa lendo cada um dos campos esperados como anteriormente delineado no fluxograma. Se qualquer um dos caracteres de marcador está em falta (o parêntese aberto, fechado parêntese, e barra), então este não é um legal Aluna objeto. A maneira aprovado para indicar tal falha é definir o failbit no objecto de entrada. Isto irá parar toda entrada adicional até que a falha seja apagada.

O próximo passo é realmente tentar armazenar esses valores na Aluna. Por exemplo, todos os marcadores pode ter estado presente, mas o valor lido para o ponto de classe média pode ter sido 8,0, um valor que está claramente fora da nossa gama de predefinido de 0 a 4.0. Assumindo que o Aluna objeto inclui cheques para valores fora da faixa em seus métodos set, a chamada para setGPA () irá lançar uma exceção que é pego no extractor. Mais uma vez, o extractor define o failbit. Se o extractor relança a exceção é até o programador.

É muito melhor que contar com a setGPA () método para detectar o problema alcance do que para implementar essa verificação no próprio extractor. Melhor deixar o Aluna objeto proteger seu próprio estado de contar com funções externas.

Use esses novos métodos

O seguinte (muito) simples CustomIO programa mostra como esses métodos inserter e exaustor são usados. Este exemplo, juntamente com o costume Aluna inserção e exaustor estão disponíveis em Dummies.com:

int main (int argc, char * pArgs []) {s1 Student ( "Davis", 123456, 3.5) -cout lt; lt; s1 lt; lt; endl-Student s2 ( "Eddins", 1, 3) -cout lt; lt; s2 lt; lt; endl-cout lt; lt; "Input um objeto estudante:" - cin >> S1-se (cin.fail ()) {cout lt; lt; "Estudante de leitura de erro" lt; lt; endl-cin.clear () -} else {cout lt; lt; "Ler: " lt; lt; s1 lt; lt; endl-} cout lt; lt; "Pressione Enter para continuar ..." lt; lt; endl-cin.ignore (10, ' n') - cin.get () - retornar 0-}

A saída deste programa (quando fornecido um estudante legal como entrada aparece da seguinte forma:

Davis (123456) /3.50Eddins (000001) /3.00Input um objeto estudante: Laskowski (234567) /3.75Read: Laskowski (234567) /3.75Press Enter para continuar ...

Observe que o estudante Davis mostra apenas o queria: a carteira de estudante é rodeado por parênteses e o ponto de classe média aparece com dois dígitos depois do ponto decimal. Este último é verdade mesmo para o estudante Eddins um pouco problemático.

O estudante Laskowski é aceito como entrada porque tem todas as marcas próprias de um estudante válida: parênteses e barras em todos os lugares certos. Confira o que acontece se deixamos algo fora, no entanto:

Entrada de um objeto estudante: Laskowski 234567 / 3.75Error estudante de leitura

Este pequeno programa mostra dois hábitos muito importantes que você deve desenvolver:

  • Verifique o sinalizador falha chamando falhou() após cada entrada.

  • Limpar o sinalizador falha assim que você já reconheceu o fracasso chamando Claro().

    Todos os pedidos subsequentes para entrada será ignorada até que você limpou a bandeira falhar.

menu