/ Forside / Teknologi / Udvikling / C/C++ / Nyhedsindlæg
Login
Glemt dit kodeord?
Brugernavn

Kodeord


Reklame
Top 10 brugere
C/C++
#NavnPoint
BertelBra.. 2425
pmbruun 695
Master_of.. 501
jdjespers.. 500
kyllekylle 500
Bech_bb 500
scootergr.. 300
gibson 300
molokyle 287
10  strarup 270
Kopiere indholdet af en ASCII fil til en b~
Fra : Thomas Braad Toft


Dato : 18-11-01 12:48

Hejsa!

Jeg er ved at skrive en klasse, som bla. skal kunne erstatte en specifik
linie i en simpel ASCII fil. Det volder mig en anelse problemer.
Funktionaliteten er implementeret vha. fstream og med følgende prototype:

bool FileIO::writeLine(int lineNumber, char* string)

Min ide var at søge ind til den linie, som ønskes erstattet - kopiere
resten af filen til en buffer, indsætte den ønskede linie (string) og
indsætte indholdet fra bufferen. Har indsat min kode i bunden af dette
indlæg.

Såvidt jeg lige umiddelbart kan gennemskue, så bør jeg ikke bruge get()
til at få indholdet af filen kopieret over i min buffer, da den ikke
gemmer '\n'. Jeg bør nok hellere bruge read(), men af en eller anden
årsag, som jeg ikke kan gennemskue, så kan jeg ikke skrive til filen
efter eksekvering af read? Ingen compiler fejl, men der bliver ganske
simpelt ikke skrevet noget i filen? Hvad skyldes det ? Er jeg helt galt
på den ?

På forhånd tak :)

Her følger min kode:

bool FileIO::writeLine(int lineNumber, char* string) {

   int i = 0;
   int j = 0;
   int appendPlace = 0;
   int bufferStart = 0;
   int endOfFile = 0;
   bool found = false;
   
   char tegn;

   // Hvis der ikke er oprettet et object, så return false.
   if(!this->file)
      return false;
   
   // Søg hen til linien, som ønskes erstattet.
   while(i != lineNumber) {
         while(!found) {
            this->file->seekg(j);
            this->file->read(&tegn,1);
            if(tegn=='\n') {
               found = true;
               j = j+2;   
            }
            else
               j++;
         }
         i++;
         found = false;
   }
   
   // Sæt læse-pointeren til at pege på første karakter i den linie, som
ønskes erstattet.
   this->file->seekg(j);
   // Gem positionen i appendPlace.
   appendPlace = this->file->tellg();

   found = false;
   // Søg for at finde starte af næste linie. Resten af filen herfra skal
gemmes i buffer.
   while(!found) {
      this->file->seekg(j);
      this->file->read(&tegn,1);
      if(tegn=='\n') {
         found = true;
         j = j+2;
      }
      else
         j++;   
   }
   
   // Sæt læse-pointeren til at pege på første karakter i den del af filen,
som skal gemmes i buffer.
   this->file->seekg(j);
   // Gem position i buffer.
   bufferStart = this->file->tellg();
   
   // Find den sidste karakter i filen.
   this->file->seekg(0,ifstream::end);
   endOfFile = this->file->tellg();
   
   // Opret buffer med plads til resten af filen.
   buffer = new char [endOfFile-bufferStart];
   
   //Sæt læse-pointer til at pege på den første karakter af filen, som skal
smides i buffer.
   this->file->seekg(bufferStart);
   //Sæt skrive-pointer til at pege på den første karakter af filen, som
skal smides i buffer (burde være                //overflødig...).
   this->file->seekp(bufferStart);
   
   // Kopier indholdet over i buffer. HER KNÆKKER FILMEN, tror jeg...!
   this->file->read(buffer,(endOfFile-bufferStart));

   // Flyt skrive-pointeren til det sted, hvor linien skal erstatttes.
   this->file->seekp(appendPlace);
   
   // Skriv den indkomne streng.
   this->file->write(string,strlen(string));
   // Lav linieskift.
   this->file->write("\n",1);
   // Skriv bufferens indhold.
   this->file->write(buffer,strlen(buffer));
   // Lav linieskift.
   this->file->write("\n",1);

   // Nedlæg hukommelsesområde til buffer.
   delete buffer;

   if(!this->file)
      return false;
   else
      return true;
}


 
 
Igor V. Rafienko (20-11-2001)
Kommentar
Fra : Igor V. Rafienko


Dato : 20-11-01 00:49

[ Thomas Braad Toft ]

[ snip ]

> Jeg er ved at skrive en klasse, som bla. skal kunne erstatte en
> specifik linie i en simpel ASCII fil.


Ugh. Jeg tenkte først at svaret var veldig enkelt, men ved nærmere
ettertanke er det noen interessante punkter som dukker opp.


> Det volder mig en anelse problemer. Funktionaliteten er
> implementeret vha. fstream og med følgende prototype:
>
> bool FileIO::writeLine(int lineNumber, char* string)


2 ting:

lineNumber bør være en size_t (eller helst streamsize)
'string' bør ikke brukes som navn pga. kollisjon med std::string. Kall
denne "line" elns.


> Min ide var at søge ind til den linie, som ønskes erstattet -
> kopiere resten af filen til en buffer, indsætte den ønskede linie
> (string) og indsætte indholdet fra bufferen. Har indsat min kode i
> bunden af dette indlæg.


Det forutsetter at filen er åpnet i read/write modus. Siden du først
skal lese iallfall deler av filen i hukommelsen, foreslår jeg denne
løsningen som er mye enklere (konseptuelt sett) på bekostning av
hukommelse (mao. dersom filene, som man skal leke med, er en stor
andel av hukommelsen på systemet, burde man prøve andre løsninger):

* les hele filen inn i hukommelsen (linjevis)

* erstatt den riktige linjen med det du vil

* skriv hele filen ut igjen.

Dessverre er ikke disse aksjonene atomiske[*] (men det er ikke din
løsning heller), så man burde være klar over mulig datatap.


bool
writeLine( std::streamsize lineNumber,
    std::string line )
{
using namespace std;

<open file in read mode>
vector< string > file_content( (line_iterator< string >( file )),
             (line_iterator< string >()) );

file_content[ lineNumber ] = line;

<close file>
<open file in write (truncate) mode>
copy( file_content.begin(),
    file_content.end(),
    ostream_iterator< string >( file ) );

return file;
}


En annen løsning er å gå via en temporært fil, og da bruker man plass
på disken framfor i RAM:


bool
writeLine( std::streamsize lineNumber,
    std::string line )
{
using namespace std;

string scratch_name = tmpnam( 0 );
ofstream scratch_file( scratch_name.c_str() );

line_iterator< string > pos = copy_n( line_iterator< string >( file ),
            lineNumber,
            ostream_iterator< string >( scratch_file ) );

scratch_file << line;

copy( pos,
    line_iterator< string >(),
    ostream_iterator< string >( scratch_file ) );

<reopen file as write / truncate>
ifstream scratch2( scratch_name.c_str() );

file << scratch2.rdbuf();
}


copy_n er min egen utvidelse av std::copy, som kopierer N elementer
heller enn en iterator range. line_iterator er rappet fra Matt Austern
sin "Generic Programming and the STL". Alternativt kan du bruke copy
men forandre på line_iterator (slik at den drar linjenummeret med seg).

Jeg tenkte en stund på std::transform, men det er vel ikke lurt, med
tanke på hvordan linjene ser ut på en fil (og så kan man vel ikke
blande read og write på samme stream uten en flush/seek innimellom).


> Såvidt jeg lige umiddelbart kan gennemskue, så bør jeg ikke bruge
> get() til at få indholdet af filen kopieret over i min buffer, da
> den ikke gemmer '\n'. Jeg bør nok hellere bruge read(),


Siden filen er linjeoppdelt forstår jeg ikke vitsen med å bruke noe
annet enn getline -- den gjør det du vil, nemlig fremstille en fil som
en sekvens av linjer. Alt dette med read() vil resulterer i mye
hodepine.


> men af en eller anden årsag, som jeg ikke kan gennemskue, så kan jeg
> ikke skrive til filen efter eksekvering af read? Ingen compiler
> fejl, men der bliver ganske simpelt ikke skrevet noget i filen? Hvad
> skyldes det ? Er jeg helt galt på den ?


Det er litt vanskelig å si, men:

* er 'file' åpnet for "read" OG "write"?
* hva er 'file' sine status flagg? (dvs. er 'file' bad, failed, eof?)

[ snip ]


> bool FileIO::writeLine(int lineNumber, char* string) {
>
>    int i = 0;
>    int j = 0;
>    int appendPlace = 0;
>    int bufferStart = 0;
>    int endOfFile = 0;
>    bool found = false;


Bytt ut "int" med streampos. Det er _meget_ vesentlig på platformer
som tillater noenlunde store filer og der int er på 16 bit.


>    char tegn;
>
>    // Hvis der ikke er oprettet et object, så return false.
>    if(!this->file)
>       return false;


if ( !file )
return false;

   
>    // Søg hen til linien, som ønskes erstattet.
>    while(i != lineNumber) {
>          while(!found) {
>             this->file->seekg(j);
>             this->file->read(&tegn,1);
>             if(tegn=='\n') {
>                found = true;
>                j = j+2;   
>             }
>             else
>                j++;
>          }
>          i++;
>          found = false;
>    }


Huh (jeg forstår ærlig talt ikke hvordan dette er påtenkt å fungere)?
Hvorfor ikke bare:

for ( size_t i = 0; i != lineNumber && file; ++i )
getline( file, buffer );

? (evt. lineNumber - 1 (det er litt sent for å ikke gjøre off-by-one feil)).


>    // Sæt læse-pointeren til at pege på første karakter i den linie, som
> // ønskes erstattet.
>    this->file->seekg(j);
>    // Gem positionen i appendPlace.
>    appendPlace = this->file->tellg();
>
>    found = false;
>    // Søg for at finde starte af næste linie. Resten af filen herfra skal
> // gemmes i buffer.


Huh? Starten av neste linje er jo _umiddelbart_ etter '\n'. Hva er det
å finne?


>    while(!found) {
>       this->file->seekg(j);
>       this->file->read(&tegn,1);
>       if(tegn=='\n') {
>          found = true;
>          j = j+2;
>       }
>       else
>          j++;   
>    }


For det første trenger du ikke 'found' her (bruk 'break' med en evig
løkke). For det andre er dette _altfor_ mye strev bare for å lese seg
videre til neste linje (igjen, getline).


>    // Sæt læse-pointeren til at pege på første karakter i den del af filen,
> // som skal gemmes i buffer.
>    this->file->seekg(j);
>    // Gem position i buffer.
>    bufferStart = this->file->tellg();
>    
>    // Find den sidste karakter i filen.
>    this->file->seekg(0,ifstream::end);
>    endOfFile = this->file->tellg();
>    
>    // Opret buffer med plads til resten af filen.
>    buffer = new char [endOfFile-bufferStart];
>    
>    //Sæt læse-pointer til at pege på den første karakter af filen, som skal
> // smides i buffer.
>    this->file->seekg(bufferStart);
>    //Sæt skrive-pointer til at pege på den første karakter af filen, som
> // skal smides i buffer (burde være overflødig...).
>    this->file->seekp(bufferStart);
>    
>    // Kopier indholdet over i buffer. HER KNÆKKER FILMEN, tror jeg...!
>    this->file->read(buffer,(endOfFile-bufferStart));


read() (så vidt jeg ser) burde nå EOF. Hvis det er tilfellet, er det en
viss sjanse for at file ikke er "good" og da må man fjerne eof-flagget
eksplisitt (det holder vel med å kalle clear).

Som sagt, sjekk status på 'file' før og etter kallet. Fortell hva du
finner.

Og for å være helt ærlig -- dette er det en _sinnsvak_ måte å gjøre
ting på. Hvorfor gidder du dette med å søke fram og tilbake, allokere
hukommelsen osv. når du kan:

string buffer;
copy( istreambuf_iterator< char >( file ),
istreambuf_iterator< char >(),
back_inserter( buffer ) );

(svjs vil en istreambuf_iterator IKKE sette get position til
begynnelsen av filen).


>    // Flyt skrive-pointeren til det sted, hvor linien skal erstatttes.
>    this->file->seekp(appendPlace);
>    
>    // Skriv den indkomne streng.
>    this->file->write(string,strlen(string));
>    // Lav linieskift.
>    this->file->write("\n",1);
>    // Skriv bufferens indhold.
>    this->file->write(buffer,strlen(buffer));


read() nulterminerer ikke. strlen() kan tryne.


>    // Lav linieskift.
>    this->file->write("\n",1);
>
>    // Nedlæg hukommelsesområde til buffer.
>    delete buffer;


delete [] buffer;

Som jeg nevnte, du vil _ikke_ dille med dette selv. La
std::string/std::vector bry seg om den type ting.


>    if(!this->file)
>       return false;
>    else
>       return true;


return file;

holder.





ivr
--
Partial Nudity is allowed by female users.

Søg
Reklame
Statistik
Spørgsmål : 177558
Tips : 31968
Nyheder : 719565
Indlæg : 6408925
Brugere : 218888

Månedens bedste
Årets bedste
Sidste års bedste