Fråga om Strängar.

C, C++, Pascal, Assembly, Raspberry, Java, Matlab, Python, BASIC, SQL, PHP, etc.
sodjan
EF Sponsor
Inlägg: 43241
Blev medlem: 10 maj 2005, 16:29:20
Ort: Söderköping

Re: Fråga om Strängar.

Inlägg av sodjan »

> Hur blir det om du istället för en loop som indexerar strängen och lägger in ett tecken i taget?

Du kan bara indexera inom den aktuella längden på strängen. Alltså inte den deklarerade
längen utan den *aktuella* längden. Detta är OK (tredje tecknet är ett "c") :

Kod: Markera allt

$ typ STR_PAS.pas
program str(output);
var
  text : string(3);
  x : char;
begin
  text := "abc";
  x := text[3];
  writeln(x);
end.
$ pas STR_PAS.pas
$ link STR_PAS
$ run STR_PAS
c
$
Men detta ger ett fel (det finns inget tredje tecken!) :

Kod: Markera allt

program str(output);
var
  text : string(3);
  x : char;
begin
  text := "ab";
  x := text[3];
  writeln(x);
end.
$ pas STR_PAS.pas
$ link STR_PAS
$ run STR_PAS
%PAS-F-VARINDVAL, varying index value is greater than current length
%TRACE-F-TRACEBACK, symbolic stack dump follows
  image    module    routine             line      rel PC           abs PC      
 STR_PAS  STR  STR                          7 0000000000000038 0000000000020038
                                            0 FFFFFFFF8038BC44 FFFFFFFF8038BC44
%TRACE-I-END, end of TRACE stack dump
$
Man kan inte heller öka längden genom att bara tilldela text[3] något,
det ger samma felmeddelande. text[3] existerar helt enkelt inte...

Kod: Markera allt

VARINDVAL, VARYING index value exceeds current length
  Explanation: The index value specified for a VARYING OF CHAR string
    is greater than the string’s current length.
  User Action: Correct the index value so that it specifies a legal character
    in the string.
Användarvisningsbild
kimmen
Inlägg: 2042
Blev medlem: 25 augusti 2007, 16:53:51
Ort: Stockholm (Kista)

Re: Fråga om Strängar.

Inlägg av kimmen »

SeniorLemuren skrev:Ja men meningen är ju att jag skall skriva något på displayen. Jag skickar ju inte en tom sträng för utskrift, det känns ju lite onödigt.
Oftast är det ju så, men jag skulle förväntat mig att en sådan funktion fungerar korrekt i det gränsfallet, särskilt eftersom alla funktioner i C:s standardbibliotek gör det och att beskrivningen av vad funktionen gör (funktionens "kontrakt") blir lättare. Har du helst en funktion som "skriver ut en sträng till displayen" eller en som "skriver ut en sträng till displayen, men strängen får inte vara tom"?

När man bara skriver ut fasta strängar som i ditt exempel är det väl inte så stor risk att man skickar in en tom sträng, men när man genererar strängar på andra sätt kommer man nog ganska snabbt till en punkt där man hade önskat att utskriftsfunktionen skulle hantera tomma strängar korrekt. Det kanske är lite OT, men varje gång jag hållit på med listor av saker har jag tackat för att sorteringsfunktioner och liknande (det var nog i Javascript senast) fungerade korrekt för tomma listor. Att memcpy fungerar korrekt om man säger åt den att kopiera 0 tecken är också trevligt har jag märkt.

En annan faktor är att enligt många kodningsstandarder och god praxis bör do-while-slingor undvikas i den grad det går om det nu inte uttryckligen är ett sådant beteende som önskas. Att hitta do-while-slingor i annan kod än sådan som försöker någonting igen vid misslyckande brukar vara ett tecken på att det finns en bugg, vad jag varit med om. Om antalet iterationer är känt i förväg, som här, brukar for-slingor rekommenderas.
Användarvisningsbild
Icecap
Inlägg: 26622
Blev medlem: 10 januari 2005, 14:52:15
Ort: Starup (Haderslev), Danmark

Re: Fråga om Strängar.

Inlägg av Icecap »

Senior:

Kod: Markera allt

void skriv_LCD(int rad, int col, char *text )
  {
  antChar = strlen(text);
  if (rad == 1) rad = 128 ;
  if (rad == 2) rad = 192 ;
  if (rad == 3) rad = 148 ;
  if (rad == 4) rad = 212 ;
  rad = rad + col - 1 ;
  LCD_CMD(rad); // startrad och startkolumn
  Lcd_Out(4, 1, text);  
  }
   
void main(void)
  {
  TRISA  =  0b00000000;
  TRISB  =  0b00000000;
  TRISC  =  0b00000000;
  TRISD  =  0b00000000;
  TRISE  =  0b00000111;
  Lcd_Init();
  load_char(); // load custom char.
  Lcd_Cmd(_LCD_CLEAR);               // Clear display
  Lcd_Cmd(_LCD_CURSOR_OFF);          // Cursor off
  skriv_LCD(1, 1, "\x03 \x04 \x05 och \x08 \x01 \x02 yes!") ;  // rad 1 kolumn 1 + texten
  }
gör samma sak utan att använda programplats och tid på att kolla och konvertera tecken. Som du ser har jag INTE använd 0x00 som teckenvärde, detta då det fungerar som EOL i C och displayen är ju gjort så att tecknen som ligger på 0x00-0x07 är de samma som finns på 0x08-0x0F.

Inte lika enkelt att läsa källkoden men en snabb kommentar hade löst detta. Det kan vara en idé att testa en #define:
#define ä \x01
#define å \x08
#define ö \x02
#define Å \x03
#define Ä \x04
#define Ö \x05

Fungerar det rätt ska du kunde skriva texterna med dessa tecken och de bytts sedan ut under kompileringen så att du dels läser källkoden rätt och dels inte behöver använda tid på att kolla varje tecken i utskriften.
Användarvisningsbild
kimmen
Inlägg: 2042
Blev medlem: 25 augusti 2007, 16:53:51
Ort: Stockholm (Kista)

Re: Fråga om Strängar.

Inlägg av kimmen »

Hur tänkte du att man skriver koden då? Preprocessorn gör inte makrosubstitution inuti stränglitteraler.
Användarvisningsbild
Icecap
Inlägg: 26622
Blev medlem: 10 januari 2005, 14:52:15
Ort: Starup (Haderslev), Danmark

Re: Fråga om Strängar.

Inlägg av Icecap »

Det kan mycket väl vara så - men SeniorLemuren använder MikroC som ju ibland har lite egenheter för sig.

Och som jag skrev: "Det kan vara en idé att testa en #define"
Jag är alltså definitivt inte säker på att det fungerar med de #define, själv skriver jag bokstäverna och när allt är klart gör jag en manuell search-&-replace.
En av orsakerna är att den kompiler jag använder inte accepterar teckenkoder över 127.
Användarvisningsbild
bit96
Inlägg: 2527
Blev medlem: 3 september 2007, 10:04:29
Ort: Säffle

Re: Fråga om Strängar.

Inlägg av bit96 »

sodjan skrev:> Pascal-strängar är ju inte bättre, om du deklarerar en sträng på 10 tecken så finns det inget
> som hindrar att du skriver 40 i första byten och därmed hamnar långt utanför strängen.

Jag vet inte hur man "skriver i första byten", posta gärna ett exempel. Jag tror
normalt inte att man över huvudtaget kommer åt den, det är helt internt.

Vi kan kolla några små exempel. Först i C:

Kod: Markera allt

$ type str_c.c
# include "stdio"
char *text [5];
void main()
{
  *text = "ABCDEFGH\0";
  printf("%s", *text);
}
$ cc str_c
$ link str_c
$ run str_c
ABCDEFGH
$ 
Vi har alltså sagt att vi vill ha en sträng med 5 tecken, men det är inget problem
att skriva en längre sträng, så länge som vi får det, *C* hindrar oss inte.
Men om vi försöker med att lägga ett tecken lite "längre bort" så spricker det.
Visst, vi får ett "informal message", men det går att undertrycka med en switch...

Kod: Markera allt

$ type str_c.c
# include "stdio"
char *text [5];
void main()
{
  text[100000] = "A";
  printf("%s", *text);
}
$ cc/nowarnings str_c
$ link str_c
$ run str_c
%SYSTEM-F-ACCVIO, access violation, reason mask=04, virtual address=0000000000091A80, PC=00000000000200C8, PS=0000001B
%TRACE-F-TRACEBACK, symbolic stack dump follows
  image    module    routine             line      rel PC           abs PC      
 STR_C  STR_C  main                      1608 00000000000000C8 00000000000200C8
 STR_C  STR_C  __main                    1606 0000000000000064 0000000000020064
                                            0 FFFFFFFF8038BC44 FFFFFFFF8038BC44
%TRACE-I-END, end of TRACE stack dump
$ 
"ACCVIO", d.v.s att vi har försökt skriva där vi inte får skriva, men *försökt*.
Nja, i din kod skapar du ingen stäng med 5 tecken utan ett fält med 5 teckenpekare:

Kod: Markera allt

char *text [5];
Längre ner i koden skriver du

Kod: Markera allt

*text = "ABCDEFGH\0";
vilket därför är helt ok.
Eftersom du låter första teckenpekaren i fältet peka på en ny sträng. Och den strängen kan vara hur lång som helst.
:)
sodjan
EF Sponsor
Inlägg: 43241
Blev medlem: 10 maj 2005, 16:29:20
Ort: Söderköping

Re: Fråga om Strängar.

Inlägg av sodjan »

> Nja, i din kod skapar du ingen stäng...

OK. :-)
Visa ett kort exempel (i C) som gör det då.
Användarvisningsbild
bit96
Inlägg: 2527
Blev medlem: 3 september 2007, 10:04:29
Ort: Säffle

Re: Fråga om Strängar.

Inlägg av bit96 »

Jag får väl försöka dra mitt strå till stacken och försöka förklara detta med strängar i C, vilket inte är helt självklart.

Kod: Markera allt

char text[5];
skapar ett fält med plats för 5 tecken.
Själva ordet 'text' är egentligen en pekare till till en plats i minnet där det reserverats plats för 5 tecken.
Index i ett fält börjar alltid på 0 (noll).

Det finns egentligen inga strängar i C men eftersom vi behöver strängar inför man "strängar" i form av "fält som rymmer tecken".
Sista tecknet i "strängen" bör vara '\0' (noll-tecknet).
Det är dock inte alltid nödvändigt att ha noll-tecknet i slutet av en "sträng", men väldigt många funktioner är beroende av detta noll-tecken.
T.ex. utskriftsfunktioner måste veta var "strängen" slutar.

För att hantera "strängar" kan man använda funktionsbiblioteket "string.h".
Där finns funktioner för stränghantering som kopiering, slå ihop, jämför, dela, sök tecken i sträng, sök sträng i sträng m.m.
OBS! Studera varje funktion noggrant, en del kräver noll-tecken, andra kräver att man anger längd eller antal tecken, en del kopierar noll-tecknet, en del kopierar nolltecknet under viss förutsättningar m.m.

Kod: Markera allt

char text[10]; /* Teckenfält med plats för 10 tecken, positionerna numreras från 0 till 9 */
char *textp; /* Teckenpekare */
char *texter[5]; /* Fält med plats för 5 teckenpekare */
char *nytext="Slut."; /* Teckenpekare som pekar på text vi skapar vid kompileringen */

/* Gör en "sträng" som innehåller texten "Hej!" */
text[0]='H';
text[1]='e';
text[2]='j';
text[3]='!';
text[4]='\0' /* Observera att här lägger vi noll-tecknet för att markera strängens slut */
/* positionerna text[5] till och med text[9] är alltså "outnyttjade" nu.

printf("%s\n", text); /* Kommer att skriva ut "Hej!" och ett nyradstecken */

textp=&text[2]; /* Teckenpekaren pekar en bit in i fältet */
printf("%s\n", textp); /* Kommer att skriva ut "j!" och ett nyradstecken */

texter[2]=nytext; /* Låt en av teckenpekarna i fältet med teckenpekare peka på samma sak som 'nytext' */
printf("%s\n", texter[2]); /* Kommer att skriva ut "Slut." och ett nyradstecken */
Här kommer ett nybörjarmisstag/bugg/klantighet

Kod: Markera allt

nytext=text; /* Teckenpekaren 'nytext' pekar nu på samma sak som 'text' */
printf("%s\n", nytext); /* Kommer att skriva ut "Hej!" och ett nyradstecken */
Men nu är texten "Slut." förlorad och kan aldrig hittas mer. :)
Eftersom teckenpekaren 'nytext' 'förstörs' när den tilldelades ett nytt värde.

Och just detta misstag gör trådskaparen i sin först post när han skapar 'text' som en lång sträng med mellanslag.
Men några rader längre ner låter han 'text' peka på en ny sträng med innehåll "min text här!"
Mellanslags-strängen skapas i minnet, tar plats, men kan aldrig hittas mer. :)
sodjan
EF Sponsor
Inlägg: 43241
Blev medlem: 10 maj 2005, 16:29:20
Ort: Söderköping

Re: Fråga om Strängar.

Inlägg av sodjan »

Ja, det stämmer ju allt det där. Och det understryker väl bara problemet
med att använda "strängar" i C, det är något som aldrig var med från
början och har byggts till senare med en massa fixar. :-)
Sen att det finns en generation programmerare som inte förstår bättre och
som tror att så som C hanterar det är "rätt", det är val kanske inte deras fel...

> ...en del kräver noll-tecken, andra kräver att man anger längd eller antal tecken, en del
> kopierar noll-tecknet, en del kopierar nolltecknet under viss förutsättningar m.m.

C i ett nötskal... :-)
Användarvisningsbild
bit96
Inlägg: 2527
Blev medlem: 3 september 2007, 10:04:29
Ort: Säffle

Re: Fråga om Strängar.

Inlägg av bit96 »

Jag tycker beskrivningarna som en del gav innan att C är som avancerad assembler eller som en sylvass skalpell är mycket bra.
Men det är nog en vanesak detta med strängar i C.

Jag tycker det är självklart att om jag skapar en 16-bitars teckensatt heltalsvariabel (t.ex. 'short int' i vissa system) så måste jag veta att mina beräkningar ryms i +/- 15 bitar. Jag vill inte ha 'kontrollkod' som kollar varenda addition så det inte blir för stora tal.
På samma sätt ser jag på strängar i C (eller fält av tecken). Man måste veta att texten får plats.

Alla som lär sig programmera C gör lektion 1 i stil med

Kod: Markera allt

printf("Hello World\n");
Lektion 2 kan vara

Kod: Markera allt

char t[30];
printf("Vad heter du? ")
scanf("%s", t);
printf("Hej %s\n", t);
Men sen borde lektion 3 vara en träpåle med silverspets mitt i 'a':et i scanf(), begrav, och använd aldrig mer. :evil:

Lektion 4 är att skriva sin egen inmatningsfunktion av text med hjälp av t.ex. getc() eller motsvarande funktion, det finns flera liknande.
Till inmatningsfunktionen skickar du en pekare till en teckenfält samt hur många tecken fältet rymmer.
Loopa:
Ta in ett tecken med getc() åt gången i en intern liten buffert.
Kasta allt utom första tecknet i bufferten (vissa system kan ge två eller fler tecken vid t.ex. funktionstangenter)
Om det är ett 'vettigt ASCII' tecken (t.ex. ASCII 32-127) spara det.
Om det är ett 'vettigt extratecken (t.ex. åäö m.fl) spara det.
Är det 'Enter'/EOF eller liknande så avbryt.
Kasta allt annat.
Har maxlängd uppnåtts avbryt.
Lägg dit ett noll-tecken.
Returnera antal inlästa tecken

Man gör alltså motsvarande en sorts 'avstudsningsfunktion' för tryckknappar, fast för textinmatning. :)
Gör man så kan någon försöka mata in hela Internet i din textbuffert utan att det buggar ur. :)
sodjan
EF Sponsor
Inlägg: 43241
Blev medlem: 10 maj 2005, 16:29:20
Ort: Söderköping

Re: Fråga om Strängar.

Inlägg av sodjan »

> att C är som avancerad assembler

Visst, det är också vad det var tänkt som från början.
Och för att implementera systemprogramvara primärt.
Och visst är det en vanesak, men en onödig vanesak. :-)

> Jag vill inte ha 'kontrollkod' som kollar varenda addition så det inte blir för stora tal.

Och många anser att det är bättre att få ett fel än att det hela bara "silently" trunkeras.

> Man måste veta att texten får plats.

Och gör den *ändå* inte det så är det bättre att ett fel triggas än att den bara
klipps av, att något annat skrivs över eller något annat mystiskt inträffar.

Det hela handlar om inställning och hur insnöad man är i C's brister. :-)
Användarvisningsbild
adent
Inlägg: 4242
Blev medlem: 27 november 2008, 22:56:23
Ort: Utanför Jönköping
Kontakt:

Re: Fråga om Strängar.

Inlägg av adent »

Åh, strängar i C är trevligt. Så länge man vet vad man gör och håller tungan precis rätt i mun.
Sen har man ju några skotthål i fötterna också, men det är oundvikligt.

C's stränghantering passar C väldigt bra i egenskap av snäppet-över-assembler-språk.

Här kommer ytterligare en redundant förklaring av allt som redan sagts, men olika förklaringar
och infallsvinklar kan inte skada (jo det kan det nog men jag vill inte lägga mig, dessutom borde
jag ha något att tillföra när jag lärt ut både Pascal och C på högskola i 5 år).

Man måste ha lite koll på pekare för att förstå strängar i C då de är intimt förknippade.
(Även arrayer och pekare är ganska intimt förknippade.)

Skriver man: "Min fina sträng" någonstans i C så kommer denna text att bakas in i binären och
med hjälp av cinit() kommer den när programmet körs att hamna i RAM som en radda med bytes efter
varandra och sista byten blir automagiskt 0x00 eller '\0' för att indikera att strängen är slut. ("":arna säger att det är en sträng, därmed tillkommer '\0'). (cinit() är den lilla lilla "runtime" som finns i C och den fixar i ordning lite saker innan main() körs, det är
enda gången den körs)

Det är ju trevligt med ett handtag till texten:

char *minfinastrang = "Min fina sträng";

Nu har vi en pekare mycket riktigt är en char-pekare, d.v.s. den pekar på
ett tecken. minfinastrang kommer att peka på bokstaven 'M', den första i strängen.

Eftersom man kan dereferera en pekare med * (få fram det pekaren pekar på) så är följande rad korrekt:

printf("%c", *minafinastrang); //Skriv ut det tecken som minfinastrang pekar på. (%c anger att vi vill skriva ut ett tecken)

Raden ovan skriver ut ett M. printf() kommer att få tecknet 'M' skickat till sig, derefereringen sker före anropet.

Strängar i C är helt enkelt en pekare till första tecknet. Att det kommer fler efter det första tecknet och att de avslutas med '\0'
är bara en "tyst" överenskommelse. Så för att skriva ut strängen säger vi bara att vi vill skriva ut en sträng (%s) och så skickar vi in pekaren till första tecknet i strängen. printf() kommer nu se att minfinastrang pekar på ett M, skriva ut den, kolla vad som ligger "efter" i minnet och skriva ut det, o.s.v. tills printf() hittar en '\0' (d.v.s. byten 0x00)

printf("%s", minfinastrang); // Här ska vi skriva ut en sträng (%s anger det).

Är man med på detta så har man full frihet att göra precis vad man vill sen :)

http://www.math.psu.edu/tseng/program1.html

Se speciellt C och Pascal :)

MVH: Mikael
Nerre
Inlägg: 27168
Blev medlem: 19 maj 2008, 07:51:04
Ort: Upplands väsby

Re: Fråga om Strängar.

Inlägg av Nerre »

sodjan skrev: Det hela handlar om inställning och hur insnöad man är i C's brister. :-)
Nej, det handlar om kontroll. I C kan jag själv bestämma vad som ska hända om data inte får plats i mitt fält.

Visst, om jag glömmer bort att lägga in en kontroll så skiter det sig. Men det beror ju inte på att C är ett dåligt språk utan på att jag är en dålig programmerare.
Användarvisningsbild
Icecap
Inlägg: 26622
Blev medlem: 10 januari 2005, 14:52:15
Ort: Starup (Haderslev), Danmark

Re: Fråga om Strängar.

Inlägg av Icecap »

Pascal har (ofta) en run-time kontroll av stack, variabler och annat som kan behövas, detta finns inte i C.

Och vad är då bäst?

Smaken är som baken, en minnesläcka kan vara ett helvete att finna - men å andra sidan uppskattar jag att inget sker bortom min kontroll så vad som är bäst beror på vana, krav, önskemål, personlighet och månens position.

Ju "starkare" (okontrollerat) ett språk är ju mer kan man vrida det sista av prestanda ur µC'n - men samtidig kan man klanta sig mer kapitalt.
Användarvisningsbild
MiaM
Inlägg: 12643
Blev medlem: 6 maj 2009, 22:19:19

Re: Fråga om Strängar.

Inlägg av MiaM »

Både pascal och C har runtimekod, skillnaden är att pascal's kod gör range-check och skriver ut felmeddelanden på skärmen samt avbryter ifall man inte slängt in någon egen felhanterare, medan C's standardbibliotek normalt inte skickar upp någon information till användaren utan bara i förekommande fall meddelar felmeddelanden till anropande program.

Vad gäller "C är en avancerad assembler" o.s.v. så försökte C++ införa bättre stränghantering, men den stränghanteringen blev väl inte så populär.

Rangecheck på alla arrayer (inklusive strängar som egentligen är en array of char) vore bra om man kunde välja till i C...

P.S. jag gillar att Sodjan kör exemplen i VMS. :tumupp:
Skriv svar