ฉันสามารถใช้อะไรกับการแปลงอินพุตแทน scanf ได้บ้าง


125

ฉันเห็นผู้คนบ่อยครั้งทำให้ท้อแท้ไม่ให้ผู้อื่นใช้scanfและบอกว่ามีทางเลือกที่ดีกว่า อย่างไรก็ตามสิ่งที่ฉันเห็นคือ"ไม่ใช้scanf"หรือ"นี่คือสตริงรูปแบบที่ถูกต้อง"และไม่เคยมีตัวอย่างของ"ทางเลือกที่ดีกว่า"กล่าวถึงเลย

ตัวอย่างเช่นลองใช้โค้ดตัวอย่างนี้:

scanf("%c", &c);

สิ่งนี้อ่านช่องว่างที่เหลืออยู่ในสตรีมอินพุตหลังจากการแปลงครั้งล่าสุด วิธีแก้ปัญหาที่แนะนำตามปกติสำหรับสิ่งนี้คือการใช้:

scanf(" %c", &c);

scanfหรือไม่ได้ใช้งาน

ตั้งแต่scanfไม่ดีสิ่งที่บางตัวเลือก ANSI C สำหรับการแปลงรูปแบบการป้อนข้อมูลที่scanfจะสามารถจัดการ (เช่นจำนวนเต็มจำนวนจุดลอยตัวและสตริง) โดยไม่ต้องใช้scanf?

คำตอบ:


87

วิธีการอ่านอินพุตที่พบบ่อยที่สุดคือ:

  • ใช้fgetsกับขนาดคงที่ซึ่งเป็นสิ่งที่มักจะแนะนำและ

  • ใช้ซึ่งอาจจะเป็นประโยชน์ถ้าคุณเท่านั้นอ่านเพียงครั้งเดียวfgetcchar

ในการแปลงอินพุตมีฟังก์ชั่นหลากหลายที่คุณสามารถใช้:

  • strtollเพื่อแปลงสตริงเป็นจำนวนเต็ม

  • strtof/ d/ ld, เพื่อแปลงสตริงเป็นตัวเลขทศนิยม

  • sscanfซึ่งไม่เป็นเลวเป็นเพียงแค่ใช้scanfแม้ว่ามันจะไม่ได้มากที่สุดของ downfalls กล่าวถึงข้างล่าง

  • ไม่มีวิธีที่ดีในการแยกวิเคราะห์อินพุตที่คั่นด้วยตัวคั่นใน ANSI C ธรรมดาไม่ว่าจะใช้strtok_rจาก POSIX หรือstrtokซึ่งไม่ปลอดภัยต่อเธรด คุณสามารถม้วนชุดตัวเลือกเธรดที่ปลอดภัยของคุณเองโดยใช้strcspnและstrspnเนื่องจากstrtok_rไม่เกี่ยวข้องกับระบบปฏิบัติการพิเศษใด ๆ

  • มันอาจ overkill แต่คุณสามารถใช้ lexers และ parsers ( flexและbisonเป็นตัวอย่างที่พบบ่อยที่สุด)

  • ไม่มีการแปลงเพียงแค่ใช้สตริง


เนื่องจากฉันไม่ได้ไปว่าทำไม scanfคำถามของฉันไม่ดีฉันจะทำอย่างละเอียด:

  • ด้วย specifiers แปลง%[...]และ%c, scanfไม่กินช่องว่าง เห็นได้ชัดว่าเรื่องนี้ไม่เป็นที่รู้จักอย่างกว้างขวางตามหลักฐานที่ซ้ำซ้อนของคำถามนี้

  • มีความสับสนเกี่ยวกับเวลาที่จะใช้&โอเปอเรเตอร์unary เมื่ออ้างถึงscanfอาร์กิวเมนต์ของ (โดยเฉพาะกับสตริง)

  • scanfมันง่ายมากที่จะไม่สนใจค่าตอบแทนจาก สิ่งนี้อาจทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดได้อย่างง่ายดายจากการอ่านตัวแปร

  • scanfมันง่ายมากที่จะลืมที่จะป้องกันไม่ให้หน่วยความจำล้นใน scanf("%s", str)มันก็แย่เหมือนกันถ้าไม่เลวร้ายยิ่งกว่าgets.

  • scanfคุณไม่สามารถตรวจสอบได้เมื่อมีการแปลงล้นจำนวนเต็มด้วย ในความเป็นจริงการล้นทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดในฟังก์ชั่นเหล่านี้



56

ทำไมถึงscanfไม่ดี?

ปัญหาหลักคือscanfไม่เคยตั้งใจที่จะจัดการกับการป้อนข้อมูลของผู้ใช้ มันตั้งใจที่จะใช้กับข้อมูลที่จัดรูปแบบ "สมบูรณ์แบบ" ฉันยกคำว่า "สมบูรณ์แบบ" เพราะมันไม่เป็นความจริงอย่างสมบูรณ์ แต่ไม่ได้ออกแบบมาเพื่อแยกวิเคราะห์ข้อมูลที่ไม่น่าเชื่อถือเท่ากับการป้อนข้อมูลของผู้ใช้ ตามธรรมชาติแล้วการป้อนข้อมูลของผู้ใช้ไม่สามารถคาดเดาได้ ผู้ใช้เข้าใจผิดคำแนะนำทำให้ความผิดพลาดโดยไม่ตั้งใจกดก่อนที่พวกเขาจะทำ ฯลฯ stdinเหตุผลหนึ่งอาจจะถามว่าทำไมฟังก์ชั่นที่ไม่ควรนำมาใช้สำหรับการป้อนข้อมูลของผู้ใช้อ่านจาก หากคุณเป็นผู้ใช้ที่มีประสบการณ์ * คำอธิบายจะไม่แปลกใจ แต่อาจทำให้ผู้ใช้ Windows สับสน ในระบบ * nix มันเป็นเรื่องธรรมดามากที่จะสร้างโปรแกรมที่ทำงานผ่าน pipingstdoutstdinของสอง ด้วยวิธีนี้คุณสามารถมั่นใจได้ว่าเอาต์พุตและอินพุตนั้นสามารถคาดเดาได้ ในสถานการณ์เหล่านี้scanfใช้งานได้ดีจริง ๆ แต่เมื่อทำงานกับอินพุตที่ไม่สามารถคาดเดาได้คุณจะเสี่ยงกับปัญหาทุกประเภท

เหตุใดจึงไม่มีฟังก์ชั่นมาตรฐานที่ใช้งานง่ายสำหรับการป้อนข้อมูลผู้ใช้ มีเพียงเดาได้ที่นี่ แต่ฉันคิดว่าแฮ็กเกอร์ C ไม่ยอมใครง่ายๆอายุเพียงแค่คิดว่าฟังก์ชั่นที่มีอยู่นั้นดีพอถึงแม้ว่าพวกเขาจะ clunky มาก stdinนอกจากนี้เมื่อคุณมองไปที่การใช้งานขั้วโดยทั่วไปพวกเขามากไม่ค่อยอ่านเข้าจากผู้ใช้ บ่อยที่สุดคุณผ่านการป้อนข้อมูลผู้ใช้ทั้งหมดเป็นอาร์กิวเมนต์บรรทัดคำสั่ง แน่นอนว่ามีข้อยกเว้น แต่สำหรับแอปพลิเคชันส่วนใหญ่การป้อนข้อมูลผู้ใช้เป็นสิ่งเล็กน้อย

แล้วคุณจะทำอย่างไร

ที่ฉันชอบคือร่วมกับfgets sscanfฉันเคยเขียนคำตอบเกี่ยวกับเรื่องนั้น แต่ฉันจะโพสต์รหัสที่สมบูรณ์ใหม่อีกครั้ง นี่คือตัวอย่างที่มีการตรวจสอบและแยกวิเคราะห์ข้อผิดพลาดที่เหมาะสม ดีพอสำหรับการแก้ไขจุดบกพร่อง

บันทึก

ฉันไม่ต้องการให้ผู้ใช้ป้อนข้อมูลสองอย่างที่แตกต่างกันในบรรทัดเดียว ฉันทำอย่างนั้นก็ต่อเมื่อพวกเขาเป็นของกันและกันอย่างเป็นธรรมชาติ เช่นเดียวกับตัวอย่างและการใช้งานแล้วprintf("Enter the price in the format <dollars>.<cent>: ") ฉันจะไม่ทำสิ่งที่ชอบsscanf(buffer "%d.%d", &dollar, &cent) printf("Enter height and base of the triangle: ")จุดหลักของการใช้fgetsด้านล่างคือการใส่แค็ปซูลอินพุตเพื่อให้แน่ใจว่าอินพุตหนึ่งรายการไม่มีผลกระทบต่อไป

#define bsize 100

void error_function(const char *buffer, int no_conversions) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", no_conversions);
        exit(EXIT_FAILURE);
}

char c, buffer[bsize];
int x,y;
float f, g;
int r;

printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Unless the input buffer was to small we can be sure that stdin is empty
// when we come here.
printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Reading single characters can be especially tricky if the input buffer
// is not emptied before. But since we're using fgets, we're safe.
printf("Enter a char: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%c", &c)) != 1) error_function(buffer, r);

printf("You entered %d %d %f %c\n", x, y, f, c);

หากคุณทำสิ่งเหล่านี้มากฉันขอแนะนำให้สร้างเสื้อคลุมที่วูบวาบเสมอ:

int printfflush (const char *format, ...)
{
   va_list arg;
   int done;
   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   fflush(stdout);
   va_end (arg);
   return done;
}```

การทำเช่นนี้จะช่วยขจัดปัญหาที่พบบ่อยซึ่งเป็นบรรทัดใหม่ต่อท้ายที่สามารถยุ่งกับอินพุตรัง bsizeแต่มันก็มีปัญหาอื่นซึ่งก็คือถ้าสายมีความยาวมากกว่า if(buffer[strlen(buffer)-1] != '\n')คุณสามารถตรวจสอบว่ามีการ buffer[strcspn(buffer, "\n")] = 0หากคุณต้องการที่จะลบขึ้นบรรทัดใหม่ที่คุณสามารถทำกับ

โดยทั่วไปฉันจะแนะนำไม่คาดหวังให้ผู้ใช้ป้อนข้อมูลในรูปแบบแปลก ๆ ที่คุณควรแยกตัวแปรที่แตกต่างกัน หากคุณต้องการกำหนดตัวแปรheightและwidthอย่าถามทั้งสองอย่างพร้อมกัน อนุญาตให้ผู้ใช้กด Enter ระหว่างพวกเขา นอกจากนี้วิธีการนี้เป็นธรรมชาติมากในแง่หนึ่ง คุณจะไม่ได้รับอินพุตจากstdinจนกว่าคุณจะกด Enter ดังนั้นทำไมไม่อ่านตลอดทั้งบรรทัด? หลักสูตรนี้ยังสามารถนำไปสู่ปัญหาหากบรรทัดยาวกว่าบัฟเฟอร์ ฉันจำได้หรือไม่ว่าการป้อนข้อมูลของผู้ใช้เป็น clunky ใน C? :)

เพื่อหลีกเลี่ยงปัญหาที่มีเส้นยาวกว่า buffer getline()คุณสามารถใช้ฟังก์ชั่นที่จะจัดสรรกันชนของขนาดที่เหมาะสมที่คุณสามารถใช้ ข้อเสียคือคุณจะต้องได้freeผลลัพธ์ในภายหลัง

ก้าวขึ้นเกม

หากคุณร้ายแรงเกี่ยวกับการสร้างโปรแกรมใน C ncursesมีการป้อนข้อมูลของผู้ใช้ผมจะแนะนำให้มีลักษณะที่เหมือนห้องสมุดที่ เพราะคุณอาจต้องการสร้างแอปพลิเคชั่นด้วยกราฟิกเทอร์มินัลบางตัว น่าเสียดายที่คุณจะสูญเสียการพกพาบางส่วนหากทำเช่นนั้น แต่จะช่วยให้คุณสามารถควบคุมการป้อนข้อมูลของผู้ใช้ได้ดีขึ้น ตัวอย่างเช่นมันช่วยให้คุณสามารถอ่านการกดปุ่มได้ทันทีแทนที่จะรอให้ผู้ใช้กด Enter


โปรดทราบว่า(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2ไม่สามารถตรวจจับข้อความที่ไม่ใช่ตัวเลขต่อท้ายได้ไม่ดี
chux - Reinstate Monica

1
@chux แก้ไข% f% f สิ่งแรกที่คุณหมายถึงอะไร
klutt

ด้วยfgets()ของ"1 2 junk", if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) {ไม่รายงานผิดอะไรกับการป้อนข้อมูลแม้ว่ามันจะมี "ขยะ"
chux - Reinstate Monica

@ chux Ah ตอนนี้ฉันเห็นแล้ว นั่นคือเจตนา
klutt

1
scanfมีวัตถุประสงค์เพื่อใช้กับข้อมูลที่จัดรูปแบบอย่างสมบูรณ์แบบแต่แม้จะไม่เป็นจริง นอกจากปัญหาเรื่อง "junk" ตามที่กล่าวไว้โดย @chux แล้วยังมีความจริงที่ว่ารูปแบบที่ชอบ"%d %d %d"มีความสุขที่ได้อ่านอินพุตจากหนึ่งสองหรือสามบรรทัด (หรือมากกว่านั้นถ้ามีการแทรกบรรทัดว่าง) ที่ไม่มี วิธีบังคับ (พูด) อินพุตสองบรรทัดด้วยการทำบางอย่างเช่น"%d\n%d %d"ฯลฯ scanfอาจเหมาะสมสำหรับอินพุตสตรีมที่จัดรูปแบบแล้ว แต่ก็ไม่ดีสำหรับทุกบรรทัด
Steve Summit

18

scanfยอดเยี่ยมเมื่อคุณรู้ว่าข้อมูลของคุณมีโครงสร้างที่ดีและมีความประพฤติดีอยู่เสมอ มิฉะนั้น...

IMO ต่อไปนี้เป็นปัญหาที่ใหญ่ที่สุดของscanf:

  • ความเสี่ยงของบัฟเฟอร์ล้น - หากคุณไม่ได้ระบุความกว้างของฟิลด์สำหรับตัวระบุการแปลง%sและ%[คุณจะเสี่ยงต่อการบัฟเฟอร์ล้น (พยายามอ่านอินพุตมากกว่าบัฟเฟอร์ที่กำหนดขนาดไว้) น่าเสียดายที่ไม่มีวิธีที่ดีในการระบุว่าเป็นอาร์กิวเมนต์ (เช่นเดียวกับprintf) - คุณต้องฮาร์ดโค้ดเป็นส่วนหนึ่งของตัวระบุการแปลงหรือทำมาโคร shenanigans

  • ยอมรับอินพุตที่ควรถูกปฏิเสธ - หากคุณกำลังอ่านอินพุตด้วยตัว%dระบุการแปลงและคุณพิมพ์บางอย่างเช่น12w4คุณจะคาดว่า scanfจะปฏิเสธอินพุตนั้น แต่มันไม่ได้ - มันจะแปลงและกำหนดค่าเรียบร้อย12แล้วปล่อยให้w4อยู่ในอินพุตสตรีม เพื่อเหม็นขึ้นอ่านต่อไป

ดังนั้นสิ่งที่คุณควรใช้แทน

ฉันมักจะแนะนำให้อ่านอินพุตแบบอินเทอร์แอคทีฟทั้งหมดเป็นข้อความโดยใช้fgets- ช่วยให้คุณระบุจำนวนสูงสุดของอักขระที่จะอ่านในแต่ละครั้งดังนั้นคุณสามารถป้องกันบัฟเฟอร์ล้นได้อย่างง่ายดาย:

char input[100];
if ( !fgets( input, sizeof input, stdin ) )
{
  // error reading from input stream, handle as appropriate
}
else
{
  // process input buffer
}

สิ่งหนึ่งfgetsที่แปลกประหลาดคือมันจะเก็บบรรทัดใหม่ที่ตามมาในบัฟเฟอร์ถ้ามีที่ว่างดังนั้นคุณสามารถตรวจสอบได้ง่ายเพื่อดูว่ามีใครบางคนพิมพ์ข้อมูลเข้ามากกว่าที่คุณคาดไว้หรือไม่:

char *newline = strchr( input, '\n' );
if ( !newline )
{
  // input longer than we expected
}

วิธีที่คุณจัดการกับสิ่งนั้นขึ้นอยู่กับคุณ - คุณสามารถปฏิเสธอินพุตทั้งหมดออกจากมือและสะบัดอินพุตที่เหลือด้วยgetchar:

while ( getchar() != '\n' ) 
  ; // empty loop

หรือคุณสามารถประมวลผลอินพุตที่คุณได้รับแล้วอ่านอีกครั้ง ขึ้นอยู่กับปัญหาที่คุณพยายามแก้ไข

ในการทำโทเค็นอินพุต (แยกขึ้นอยู่กับตัวคั่นอย่างน้อยหนึ่งตัว) คุณสามารถใช้strtokแต่ระวัง - strtokปรับเปลี่ยนอินพุต (มันเขียนทับตัวคั่นด้วยตัวคั่นสตริง) และคุณไม่สามารถคงสถานะไว้ได้ (เช่นคุณสามารถ ' ไม่โทเค็นบางส่วนของสตริงจากนั้นเริ่มทำโทเค็นอื่นจากนั้นเลือกตำแหน่งที่คุณค้างไว้ในสตริงเดิม) มีตัวแปรstrtok_sที่รักษาสถานะของ tokenizer แต่การใช้งาน AFAIK นั้นเป็นตัวเลือก (คุณจะต้องตรวจสอบว่า__STDC_LIB_EXT1__มีการกำหนดไว้เพื่อดูว่ามีหรือไม่)

เมื่อคุณโทเค็นอินพุตของคุณแล้วหากคุณต้องการแปลงสตริงเป็นตัวเลข (เช่น"1234"=> 1234) คุณจะมีตัวเลือก strtolและstrtodจะแปลงการแทนค่าสตริงของจำนวนเต็มและจำนวนจริงเป็นประเภทที่เกี่ยวข้อง พวกเขายังอนุญาตให้คุณจับ12w4ปัญหาที่ฉันกล่าวถึงข้างต้น - หนึ่งในข้อโต้แย้งของพวกเขาคือตัวชี้ไปยังตัวละครตัวแรกที่ไม่ได้แปลงในสตริง:

char *text = "12w4";
char *chk;
long val;
long tmp = strtol( text, &chk, 10 );
if ( !isspace( *chk ) && *chk != 0 )
  // input is not a valid integer string, reject the entire input
else
  val = tmp;

หากคุณไม่ได้ระบุความกว้างของฟิลด์ ... - หรือการยับยั้งการแปลง (เช่น%*[%\n]ซึ่งมีประโยชน์สำหรับการจัดการกับบรรทัดที่ยาวเกินไปในคำตอบ)
Toby Speight

มีวิธีรับสเปคแบบรันไทม์ของความกว้างของเขตข้อมูล แต่ไม่ดี คุณต้องสร้างสตริงรูปแบบในรหัสของคุณ (อาจใช้snprintf()),
Toby Speight

5
คุณทำผิดพลาดบ่อยที่สุดในที่isspace()นั้น - ยอมรับอักขระที่ไม่ได้ลงชื่อซึ่งแสดงเป็นintดังนั้นคุณต้องส่งไปunsigned charเพื่อหลีกเลี่ยง UB บนแพลตฟอร์มที่charลงชื่อ
Toby Speight

9

ในคำตอบนี้ฉันจะคิดว่าคุณกำลังอ่านและการตีความบรรทัดของข้อความ บางทีคุณอาจแจ้งผู้ใช้ที่กำลังพิมพ์บางอย่างและกดปุ่ม RETURN หรือบางทีคุณกำลังอ่านบรรทัดข้อความที่มีโครงสร้างจากไฟล์ข้อมูลบางชนิด

เนื่องจากคุณกำลังอ่านบรรทัดข้อความคุณควรจัดระเบียบโค้ดของคุณรอบ ๆ ฟังก์ชั่นห้องสมุดที่อ่านได้และเป็นบรรทัดข้อความ ฟังก์ชั่นมาตรฐานคือfgets()แม้ว่าจะมีคนอื่น ๆ (รวมถึงgetline) จากนั้นขั้นตอนต่อไปคือการตีความบรรทัดข้อความนั้น

นี่คือสูตรพื้นฐานสำหรับการโทรfgetsเพื่ออ่านข้อความ:

char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);

ข้อความนี้อ่านในบรรทัดเดียวแล้วพิมพ์ออกมา ตามที่เขียนไว้มีข้อ จำกัด สองสามข้อซึ่งเราจะไปได้ในไม่กี่นาที นอกจากนี้ยังมีคุณสมบัติที่ยอดเยี่ยมมาก: หมายเลข 512 ที่เราส่งผ่านเป็นอาร์กิวเมนต์ที่สองfgetsคือขนาดของอาร์เรย์ที่ lineเราขอfgetsให้อ่าน ข้อเท็จจริงนี้ - ที่เราสามารถบอกได้fgetsว่าได้รับอนุญาตให้อ่านมากแค่ไหน - หมายความว่าเรามั่นใจได้ว่าfgetsจะไม่ล้นอาร์เรย์โดยการอ่านมากเกินไป

ดังนั้นตอนนี้เรารู้วิธีอ่านบรรทัดข้อความ แต่ถ้าเราต้องการอ่านจำนวนเต็มหรือจำนวนเลขทศนิยมหรืออักขระเดี่ยวหรือคำเดียว? (นั่นคือสิ่งที่ถ้า scanfโทรที่เรากำลังพยายามที่จะปรับปรุงได้รับใช้ระบุรูปแบบเช่น%d, %f, %cหรือ%s?)

ง่ายต่อการตีความบรรทัดของข้อความ - สตริง - เป็นสิ่งเหล่านี้ การแปลงสตริงเป็นจำนวนเต็มที่ง่าย ( แต่ไม่สมบูรณ์) atoi()วิธีการทำก็คือการโทร หากต้องการแปลงเป็นตัวเลขทศนิยมให้ทำatof()ดังนี้ (และยังมีวิธีที่ดีกว่าดังที่เราจะเห็นในอีกสักครู่) นี่เป็นตัวอย่างง่ายๆ:

printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);

หากคุณต้องการให้ผู้ใช้พิมพ์อักขระเดียว ( yหรือ อาจnเป็นการตอบสนองแบบใช่ / ไม่ใช่) คุณสามารถคว้าตัวอักษรตัวแรกของบรรทัดเช่นนี้:

printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);

(แน่นอนว่าสิ่งนี้ละเว้นความเป็นไปได้ที่ผู้ใช้พิมพ์การตอบสนองแบบหลายตัวอักษรโดยจะละเว้นอักขระพิเศษที่พิมพ์อย่างเงียบ ๆ )

ในที่สุดหากคุณต้องการให้ผู้ใช้พิมพ์สตริงแน่นอนไม่มีช่องว่างถ้าคุณต้องการที่จะปฏิบัติต่อสายอินพุต

hello world!

เป็นสตริง"hello"ตามด้วยอย่างอื่น (ซึ่งเป็นscanfรูปแบบที่%sจะทำ) ดีในกรณีนั้นฉันเป็นใยเล็ก ๆ น้อย ๆ มันไม่ง่ายนักที่จะตีความบรรทัดใหม่ในลักษณะนั้นท้ายที่สุดดังนั้นคำตอบก็คือ ส่วนหนึ่งของคำถามจะต้องรอสักครู่

แต่ก่อนอื่นฉันต้องการย้อนกลับไปสามสิ่งที่ฉันข้ามไป

(1) เราโทรมา

fgets(line, 512, stdin);

เพื่ออ่านเข้าไปในอาเรlineย์และโดยที่ 512 คือขนาดของอาเรย์lineจึงfgetsไม่รู้ที่จะล้นมัน แต่เพื่อให้แน่ใจว่า 512 เป็นหมายเลขที่ถูกต้อง (โดยเฉพาะอย่างยิ่งเพื่อตรวจสอบว่ามีใครบางคน tweaked โปรแกรมเพื่อเปลี่ยนขนาด) คุณต้องอ่านกลับไปทุกที่ที่lineมีการประกาศ นั่นเป็นเรื่องน่ารำคาญดังนั้นจึงมีวิธีที่ดีกว่าสองวิธีในการซิงค์ขนาด คุณสามารถ (a) ใช้ตัวประมวลผลล่วงหน้าเพื่อสร้างชื่อสำหรับขนาด:

#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);

หรือ (b) ใช้sizeofโอเปอเรเตอร์ของ C :

fgets(line, sizeof(line), stdin);

(2) ปัญหาที่สองคือเราไม่ได้ตรวจสอบข้อผิดพลาด เมื่อคุณกำลังอ่านอินพุตคุณควรเสมอตรวจสอบความเป็นไปได้ของข้อผิดพลาด หากเหตุผลใดก็ตามfgetsไม่สามารถอ่านบรรทัดข้อความที่คุณขอให้มันแสดงถึงสิ่งนี้โดยส่งคืนพอยน์เตอร์พอยน์เตอร์ ดังนั้นเราควรทำสิ่งที่ชอบ

printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
    printf("Well, never mind, then.\n");
    exit(1);
}

ในที่สุดก็มีปัญหาที่ต้องอ่านบรรทัดของข้อความ fgetsอ่านตัวอักษรและเติมเข้าไปในอาร์เรย์ของคุณจนกว่าจะพบ\nอักขระที่ยุติบรรทัดและเติม\nอักขระลงในอาร์เรย์ของคุณเช่นกัน คุณสามารถดูสิ่งนี้หากคุณปรับเปลี่ยนตัวอย่างก่อนหน้าของเราเล็กน้อย:

printf("you typed: \"%s\"\n", line);

ถ้าฉันเรียกใช้และพิมพ์ "Steve" เมื่อมันแจ้งให้ฉันมันจะพิมพ์ออกมา

you typed: "Steve
"

ว่าในบรรทัดที่สองเป็นเพราะสตริงอ่านและพิมพ์ออกมาเป็นจริง""Steve\n"

บางครั้งการขึ้นบรรทัดใหม่ไม่สำคัญ (เช่นเมื่อเราโทรหา atoiหรือatofเนื่องจากทั้งคู่ไม่สนใจการป้อนข้อมูลที่ไม่ใช่ตัวเลขพิเศษหลังจากตัวเลข) แต่บางครั้งมันก็สำคัญมาก บ่อยครั้งที่เราต้องการตัดการขึ้นบรรทัดใหม่นั้น มีหลายวิธีที่จะทำซึ่งฉันจะไปในนาที (ฉันรู้ว่าฉันพูดมาเยอะมาก แต่ฉันจะกลับไปที่สิ่งเหล่านั้นทั้งหมดฉันสัญญา)

เมื่อถึงจุดนี้คุณอาจกำลังคิดว่า: "ฉันคิดว่าคุณพูดว่าscanf ไม่ดีและวิธีอื่นจะดีกว่านี้มาก แต่fgetsเริ่มมีอาการรำคาญการโทรscanfเป็นเรื่องง่ายมาก ! "

แน่นอนว่าคุณสามารถใช้ต่อscanfไปได้หากต้องการ (และสำหรับ สิ่งที่เรียบง่ายจริงๆในบางวิธีมันก็ง่ายกว่า) แต่โปรดอย่าร้องไห้ให้กับฉันเมื่อมันทำให้คุณล้มเหลวเนื่องจากหนึ่งใน 17 นิสัยใจคอและความผิดพลาดหรือเข้าสู่วงวนไม่สิ้นสุดเนื่องจากการป้อนข้อมูลของคุณ ไม่คาดหวังหรือเมื่อคุณไม่สามารถหาวิธีใช้เพื่อทำสิ่งที่ซับซ้อนมากขึ้น ลองดูที่fgetsจริงของความรำคาญ:

  1. คุณต้องระบุขนาดอาร์เรย์เสมอ แน่นอนว่านั่นไม่ใช่เรื่องน่ารำคาญเลย - นั่นเป็นคุณสมบัติเพราะบัฟเฟอร์ล้นเป็นสิ่งเลวร้ายจริงๆ

  2. คุณต้องตรวจสอบค่าส่งคืน ที่จริงแล้วนั่นคือการล้างเนื่องจากการใช้scanfอย่างถูกต้องคุณต้องตรวจสอบค่าตอบแทนด้วย

  3. คุณต้องถอด\nหลังออก นี่คือฉันยอมรับว่าเป็นความรำคาญที่แท้จริง ฉันหวังว่าจะมีฟังก์ชั่นมาตรฐานที่ฉันสามารถชี้ให้คุณทราบว่าไม่มีปัญหาเล็กน้อยนี้ (โปรดไม่มีใครนำขึ้นมาgets) แต่เมื่อเปรียบเทียบกับscanf'sสิ่งรบกวน 17 แบบที่แตกต่างกันฉันจะใช้สิ่งนี้สร้างความรำคาญfgetsทุกวัน

ดังนั้นคุณจะตัดการขึ้นบรรทัดใหม่ได้อย่างไร สามวิธี:

(a) วิธีที่ชัดเจน:

char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';

(b) วิธีที่ยุ่งยากและกะทัดรัด:

strtok(line, "\n");

น่าเสียดายที่อันนี้ไม่ได้ผลเสมอไป

(c) อีกวิธีหนึ่งที่กะทัดรัดและไม่ชัดเจน:

line[strcspn(line, "\n")] = '\0';

และตอนนี้ว่าที่ออกจากทางที่เราจะได้รับกลับไปเป็นอีกสิ่งหนึ่งที่ผมข้ามไป: ความไม่สมบูรณ์ของและatoi() atof()ปัญหาที่เกิดขึ้นกับสิ่งเหล่านี้คือพวกเขาไม่ได้ให้การบ่งชี้ที่เป็นประโยชน์ใด ๆ ถึงความสำเร็จของความสำเร็จหรือความล้มเหลว: พวกเขาละเว้นการป้อนข้อมูลที่ไม่ใช่ตัวเลขต่อท้ายและพวกเขาจะคืนค่า 0 เป็นเงียบ ๆ หากไม่มีการป้อนตัวเลข ทางเลือกที่แนะนำ - ซึ่งยังมีข้อดีอื่น ๆ บางอย่าง - มีและ strtol ยังช่วยให้คุณใช้ฐานอื่นที่ไม่ใช่ 10 ซึ่งหมายความว่าคุณจะได้รับผลกระทบจาก (เหนือสิ่งอื่นใด) หรือด้วยstrtodstrtol%o%xscanf. แต่การแสดงวิธีการใช้ฟังก์ชั่นเหล่านี้อย่างถูกต้องนั้นเป็นเรื่องราวในตัวเองและจะทำให้เสียสมาธิมากเกินไปจากสิ่งที่เปลี่ยนไปแล้วในการเล่าเรื่องที่ค่อนข้างกระจัดกระจายดังนั้นฉันจะไม่พูดอะไรเกี่ยวกับพวกเขาอีกเลย

ข้อมูลที่เหลือเกี่ยวกับการบรรยายหลักที่เหลือคุณอาจพยายามวิเคราะห์ว่ามันซับซ้อนกว่าตัวเลขหรือตัวละครเพียงตัวเดียว ถ้าคุณต้องการอ่านบรรทัดที่มีตัวเลขสองตัวหรือคำที่คั่นด้วยช่องว่างหลายคำหรือเครื่องหมายวรรคตอนกำหนดกรอบเฉพาะ นั่นคือสิ่งที่น่าสนใจและสิ่งที่อาจจะซับซ้อนหากคุณพยายามทำสิ่งต่าง ๆ โดยใช้scanfและที่มีตัวเลือกมากมายตอนนี้คุณได้อ่านข้อความหนึ่งบรรทัดโดยใช้อย่างหมดจดfgetsแม้ว่าเรื่องราวทั้งหมดในตัวเลือกเหล่านั้นทั้งหมด อาจเติมหนังสือได้ดังนั้นเราจะทำได้แค่ขูดผิวที่นี่

  1. เทคนิคที่ฉันโปรดปรานคือการแบ่งบรรทัดออกเป็น "คำ" ที่คั่นด้วยช่องว่างจากนั้นทำอะไรเพิ่มเติมกับ "คำ" แต่ละคำ หนึ่งฟังก์ชันหลักที่เป็นมาตรฐานสำหรับการทำเช่นนี้คือ strtok(ซึ่งก็มีปัญหาและมีอัตราการอภิปรายแยกต่างหากทั้งหมด) การตั้งค่าของตัวเองเป็นฟังก์ชั่นเฉพาะสำหรับการสร้างอาร์เรย์ของตัวชี้ไปที่แต่ละหักออกจากกัน "คำว่า" ฟังก์ชั่นผมอธิบายใน บันทึกหลักสูตรเหล่านี้ เมื่อใดก็ตามที่คุณมีคำว่า "word" คุณจะสามารถดำเนินการแต่ละอย่างได้ต่อไปอาจจะมีatoi/ atof/ strtol/ strtod ฟังก์ชั่นเดียวกันที่เราได้ดูไปแล้ว

  2. ขัดแย้งแม้ว่าเราจะได้รับค่าใช้จ่ายเป็นจำนวนเงินที่ยุติธรรมของเวลาและความพยายามที่นี่หาวิธีที่จะย้ายออกไปจากscanfวิธีที่ดีอีกที่จะจัดการกับบรรทัดของข้อความที่เราเพิ่งอ่านด้วย คือการผ่านไปfgets sscanfด้วยวิธีนี้คุณจะได้ข้อได้เปรียบscanfเกือบทั้งหมด แต่ไม่มีข้อเสียส่วนใหญ่

  3. หากไวยากรณ์อินพุตของคุณซับซ้อนเป็นพิเศษคุณอาจใช้ไลบรารี่ "regexp" เพื่อแยกวิเคราะห์

  4. สุดท้ายคุณสามารถใช้โซลูชันแยกวิเคราะห์เฉพาะกิจที่เหมาะกับคุณ คุณสามารถเลื่อนอักขระทีละบรรทัดโดยใช้ char *ตัวชี้ตรวจสอบอักขระที่คุณต้องการ หรือคุณสามารถค้นหาตัวอักษรที่เฉพาะเจาะจงโดยใช้ฟังก์ชั่นเช่นstrchrหรือstrrchrหรือstrspnหรือหรือstrcspn strpbrkหรือคุณสามารถแยก / แปลงและข้ามกลุ่มอักขระตัวเลขโดยใช้strtolหรือ strtodฟังก์ชั่นที่เราข้ามไปก่อนหน้านี้

เห็นได้ชัดว่ามีอีกมากมายที่สามารถพูดได้ แต่หวังว่าการแนะนำนี้จะช่วยให้คุณเริ่มต้นได้


มีเหตุผลที่ดีในการเขียนsizeof (line)มากกว่าเพียงแค่sizeof line? อดีตทำให้ดูเหมือนว่าlineเป็นชื่อประเภท!
Toby Speight

@TobySpeight เหตุผลที่ดี? ไม่ฉันสงสัยมัน วงเล็บเป็นนิสัยของฉันเพราะฉันไม่สามารถจะจำได้ว่ามันเป็นวัตถุหรือชื่อประเภทที่พวกเขาต้องการ แต่โปรแกรมเมอร์จำนวนมากปล่อยพวกเขาออกมาเมื่อพวกเขาสามารถ (สำหรับฉันมันเป็นเรื่องของความชอบและสไตล์ส่วนตัวและเป็นผู้เยาว์ที่น่ารัก)
Steve Summit

+1 สำหรับใช้sscanfเป็นเครื่องมือแปลง แต่รวบรวม (และอาจเป็นไปได้ที่จะนวด) อินพุตด้วยเครื่องมืออื่น แต่น่าจะพูดถึงgetlineในบริบท
dmckee --- ผู้ดูแลอดีตลูกแมว

เมื่อคุณพูดถึง " fscanfความรำคาญจริง" คุณหมายถึงfgetsอะไร และความรำคาญ # 3 ทำให้ฉันรำคาญจริงๆโดยเฉพาะอย่างยิ่งเมื่อscanfส่งคืนตัวชี้ที่ไร้ประโยชน์ไปยังบัฟเฟอร์แทนที่จะส่งกลับจำนวนอักขระที่ป้อน (ซึ่งจะทำให้การลอกบรรทัดใหม่สะอาดขึ้นมาก)
supercat

1
ขอบคุณสำหรับคำอธิบายsizeofสไตล์ของคุณ สำหรับฉันการจำได้เมื่อคุณต้องการ parens เป็นเรื่องง่าย: ฉันคิดว่า(type)เป็นเหมือนนักแสดงที่ไม่มีค่า (เพราะเราสนใจในประเภทเท่านั้น) อีกสิ่งหนึ่ง: คุณบอกว่าstrtok(line, "\n")มันไม่ได้ผลเสมอไป แต่ก็ไม่ชัดเจนเมื่อมันอาจไม่ทำงาน ฉันเดาว่าคุณกำลังนึกถึงกรณีที่บรรทัดนั้นยาวกว่าบัฟเฟอร์ดังนั้นเราจึงไม่มีบรรทัดใหม่และstrtok()ส่งคืน null มันน่าเสียดายจริง ๆ แล้วfgets()ไม่ได้คืนคุณค่าที่มีประโยชน์มากขึ้นดังนั้นเราจึงสามารถรู้ได้ว่ามีการขึ้นบรรทัดใหม่หรือไม่
Toby Speight

7

ฉันจะใช้อะไรในการวิเคราะห์คำแทนการสแกนแทน scanf

แทนที่จะscanf(some_format, ...)พิจารณาfgets()ด้วยsscanf(buffer, some_format_and %n, ...)

โดยการใช้" %n"รหัสสามารถตรวจจับได้ว่ารูปแบบทั้งหมดนั้นถูกสแกนสำเร็จแล้วและไม่มีจุดจบที่ไม่ใช่พื้นที่สีขาวเป็นพิเศษในตอนท้าย

// scanf("%d %f fred", &some_int, &some_float);
#define EXPECTED_LINE_MAX 100
char buffer[EXPECTED_LINE_MAX * 2];  // Suggest 2x, no real need to be stingy.

if (fgets(buffer, sizeof buffer, stdin)) {
  int n = 0;
  // add ------------->    " %n" 
  sscanf(buffer, "%d %f fred %n", &some_int, &some_float, &n);
  // Did scan complete, and to the end?
  if (n > 0 && buffer[n] == '\0') {
    // success, use `some_int, some_float`
  } else {
    ; // Report bad input and handle desired.
  }

6

ให้ระบุข้อกำหนดของการแยกวิเคราะห์เป็น:

  • ต้องป้อนข้อมูลที่ถูกต้อง (และแปลงเป็นรูปแบบอื่น ๆ )

  • อินพุตที่ไม่ถูกต้องจะต้องถูกปฏิเสธ

  • เมื่อการป้อนข้อมูลใด ๆ ถูกปฏิเสธจำเป็นต้องให้ข้อความอธิบายที่ผู้ใช้อธิบาย (ชัดเจน "เข้าใจได้ง่ายโดยคนทั่วไปที่ไม่ใช่ภาษา" โปรแกรมเมอร์) ทำไมมันถูกปฏิเสธ (เพื่อให้ผู้คนสามารถหาวิธีแก้ไข ปัญหา)

ในการทำให้สิ่งต่าง ๆ เป็นเรื่องง่ายให้พิจารณาการแยกเลขจำนวนเต็มฐานสิบแบบง่ายๆ (ที่ผู้ใช้พิมพ์) และไม่มีอะไรอื่น สาเหตุที่เป็นไปได้สำหรับการป้อนข้อมูลของผู้ใช้ที่จะถูกปฏิเสธคือ:

  • อินพุตมีอักขระที่ยอมรับไม่ได้
  • อินพุตหมายถึงตัวเลขที่ต่ำกว่าค่าต่ำสุดที่ยอมรับ
  • อินพุตหมายถึงตัวเลขที่สูงกว่าค่าสูงสุดที่ยอมรับได้
  • อินพุตหมายถึงตัวเลขที่มีส่วนที่ไม่ใช่ศูนย์

ให้เรานิยาม "อินพุตที่มีอักขระที่ยอมรับไม่ได้" อย่างถูกต้อง และพูดว่า:

  • ช่องว่างนำหน้าและช่องว่างต่อท้ายจะถูกละเว้น (เช่น "
    5" จะถือว่าเป็น "5")
  • อนุญาตให้ใช้ศูนย์หรือทศนิยมหนึ่งตำแหน่ง (เช่น "1234" และ "1234.000" ทั้งคู่ถือว่าเหมือนกับ "1234")
  • ต้องมีอย่างน้อยหนึ่งหลัก (เช่น "." ถูกปฏิเสธ)
  • ไม่อนุญาตให้มีจุดทศนิยมมากกว่าหนึ่งจุด (เช่น "1.2.3" ถูกปฏิเสธ)
  • เครื่องหมายจุลภาคที่ไม่ใช่ตัวเลขระหว่างหลักจะถูกปฏิเสธ (เช่น ", 1234" ถูกปฏิเสธ)
  • เครื่องหมายจุลภาคที่อยู่หลังจุดทศนิยมจะถูกปฏิเสธ (เช่น "1234.000,000" ถูกปฏิเสธ)
  • เครื่องหมายจุลภาคที่อยู่หลังเครื่องหมายจุลภาคอื่นถูกปฏิเสธ (เช่น "1, 234" ถูกปฏิเสธ)
  • เครื่องหมายจุลภาคอื่น ๆ ทั้งหมดจะถูกละเว้น (เช่น "1,234" จะถือว่าเป็น "1234")
  • เครื่องหมายลบที่ไม่ใช่อักขระที่ไม่ใช่ช่องว่างตัวแรกจะถูกปฏิเสธ
  • เครื่องหมายบวกที่ไม่ใช่อักขระที่ไม่ใช่ช่องว่างตัวแรกถูกปฏิเสธ

จากนี้เราสามารถระบุได้ว่าต้องการข้อความแสดงข้อผิดพลาดต่อไปนี้:

  • "อักขระที่ไม่รู้จักที่จุดเริ่มต้นของการป้อนข้อมูล"
  • "อักขระที่ไม่รู้จักที่ส่วนท้ายของอินพุต"
  • "อักขระที่ไม่รู้จักอยู่ตรงกลางของการป้อนข้อมูล"
  • "หมายเลขต่ำเกินไป (ขั้นต่ำคือ .... )"
  • "หมายเลขสูงเกินไป (สูงสุดคือ .... )"
  • "Number ไม่ใช่จำนวนเต็ม"
  • "ทศนิยมมากเกินไป"
  • "ไม่มีเลขทศนิยม"
  • "เครื่องหมายจุลภาคเริ่มต้นไม่ถูกต้อง"
  • "เครื่องหมายจุลภาคไม่ถูกต้องเมื่อสิ้นสุดหมายเลข"
  • "เครื่องหมายจุลภาคไม่ถูกต้องในจำนวนกลาง"
  • "เครื่องหมายจุลภาคไม่ถูกต้องหลังจุดทศนิยม"

จากจุดนี้เราจะเห็นได้ว่าฟังก์ชั่นที่เหมาะสมในการแปลงสตริงเป็นจำนวนเต็มจะต้องแยกแยะระหว่างข้อผิดพลาดประเภทต่าง ๆ ; และบางอย่างเช่น " scanf()" หรือ " atoi()" หรือ " strtoll()" นั้นไร้ค่าอย่างสมบูรณ์และไร้ค่าเพราะพวกเขาไม่ได้ให้สิ่งบ่งชี้ว่ามีอะไรผิดปกติกับสิ่งที่ป้อนเข้า (และใช้คำจำกัดความที่ไม่เกี่ยวข้องอย่างสมบูรณ์และไม่เหมาะสม การป้อนข้อมูล ")

ให้เริ่มเขียนสิ่งที่ไม่มีประโยชน์แทน:

char *convertStringToInteger(int *outValue, char *string, int minValue, int maxValue) {
    return "Code not implemented yet!";
}

int main(int argc, char *argv[]) {
    char *errorString;
    int value;

    if(argc < 2) {
        printf("ERROR: No command line argument.\n");
        return EXIT_FAILURE;
    }
    errorString = convertStringToInteger(&value, argv[1], -10, 2000);
    if(errorString != NULL) {
        printf("ERROR: %s\n", errorString);
        return EXIT_FAILURE;
    }
    printf("SUCCESS: Your number is %d\n", value);
    return EXIT_SUCCESS;
}

เพื่อตอบสนองความต้องการที่ระบุไว้; นี้convertStringToInteger()ฟังก์ชั่นมีแนวโน้มที่จะจบลงด้วยการหลายร้อยสายรหัสทั้งหมดด้วยตัวเอง

ตอนนี้นี่เป็นเพียง "การแยกเลขจำนวนเต็มทศนิยมแบบง่าย" ลองนึกภาพถ้าคุณต้องการแยกวิเคราะห์สิ่งที่ซับซ้อน เช่นรายการ "ชื่อที่อยู่หมายเลขโทรศัพท์ที่อยู่อีเมล" โครงสร้าง หรืออาจจะชอบภาษาโปรแกรม สำหรับกรณีเหล่านี้คุณอาจต้องเขียนโค้ดหลายพันบรรทัดเพื่อสร้างการแยกวิเคราะห์ที่ไม่ใช่เรื่องตลกที่พิการ

กล่าวอีกนัยหนึ่ง ...

ฉันจะใช้อะไรในการวิเคราะห์คำแทนการสแกนแทน scanf

เขียน (อาจเป็นพันบรรทัด) ของรหัสด้วยตัวเองเพื่อให้เหมาะกับความต้องการของคุณ


5

นี่คือตัวอย่างของการใช้flexเพื่อสแกนอินพุตอย่างง่ายในกรณีนี้ไฟล์ของตัวเลขทศนิยมแบบ ASCII ที่อาจอยู่ในรูปแบบ US ( n,nnn.dd) หรือ European ( n.nnn,dd) นี่เป็นเพียงการคัดลอกจากโปรแกรมที่มีขนาดใหญ่กว่ามากดังนั้นอาจมีการอ้างอิงที่ไม่ได้แก้ไขบางส่วน:

/* This scanner reads a file of numbers, expecting one number per line.  It  */
/* allows for the use of European-style comma as decimal point.              */

%{
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #ifdef WINDOWS
    #include <io.h>
  #endif
  #include "Point.h"

  #define YY_NO_UNPUT
  #define YY_DECL int f_lex (double *val)

  double atofEuro (char *);
%}

%option prefix="f_"
%option nounput
%option noinput

EURONUM [-+]?[0-9]*[,]?[0-9]+([eE][+-]?[0-9]+)?
NUMBER  [-+]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?
WS      [ \t\x0d]

%%

[!@#%&*/].*\n

^{WS}*{EURONUM}{WS}*  { *val = atofEuro (yytext); return (1); }
^{WS}*{NUMBER}{WS}*   { *val = atof (yytext); return (1); }

[\n]
.


%%

/*------------------------------------------------------------------------*/

int scan_f (FILE *in, double *vals, int max)
{
  double *val;
  int npts, rc;

  f_in = in;
  val  = vals;
  npts = 0;
  while (npts < max)
  {
    rc = f_lex (val);

    if (rc == 0)
      break;
    npts++;
    val++;
  }

  return (npts);
}

/*------------------------------------------------------------------------*/

int f_wrap ()
{
  return (1);
}

-5

คำตอบอื่น ๆ ให้รายละเอียดในระดับต่ำที่ถูกต้องดังนั้นฉันจะ จำกัด ตัวเองให้อยู่ในระดับที่สูงกว่า: อันดับแรกวิเคราะห์สิ่งที่คุณคาดหวังว่าแต่ละบรรทัดอินพุตมีหน้าตาเป็นอย่างไร พยายามที่จะอธิบายการป้อนข้อมูลที่มีไวยากรณ์อย่างเป็นทางการ - ด้วยความโชคดีที่คุณจะพบว่ามันสามารถอธิบายโดยใช้ไวยากรณ์ปกติหรืออย่างน้อยไวยากรณ์บริบทฟรี หากไวยากรณ์ปกติเพียงพอคุณสามารถกำหนดรหัสของเครื่องจักรสถานะ จำกัด ได้ซึ่งรับรู้และตีความแต่ละบรรทัดคำสั่งทีละตัวอักษร รหัสของคุณจะอ่านบรรทัด (ตามที่อธิบายในคำตอบอื่น ๆ ) จากนั้นสแกนตัวอักษรในบัฟเฟอร์ผ่านเครื่องสถานะ ในบางสถานะคุณหยุดและแปลงซับสตริงที่สแกนจนถึงจำนวนหรืออะไรก็ตาม คุณอาจจะ 'ม้วนตัวเอง' ถ้ามันง่าย หากคุณพบว่าคุณต้องการไวยากรณ์ที่ไม่ต้องใช้บริบทแบบสมบูรณ์คุณควรหาวิธีใช้เครื่องมือการแยกวิเคราะห์ที่มีอยู่แล้ว (ใหม่: lexและyaccหรือตัวแปร)


เครื่องสถานะ จำกัด อาจ overkill; วิธีที่ง่ายขึ้นในการตรวจสอบการล้นในการแปลง (เช่นการตรวจสอบว่าerrno == EOVERFLOWหลังจากใช้strtoll) เป็นไปได้
SS Anne

1
เหตุใดคุณต้องเขียนรหัสสถานะเครื่องจักรของคุณเองเมื่อ flex ทำให้การเขียนง่ายขึ้นเล็กน้อย
jamesqf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.