หลีกเลี่ยงผู้ประกอบการเพิ่ม Postfix


25

ฉันได้อ่านแล้วว่าควรหลีกเลี่ยงตัวดำเนินการเพิ่ม postfixเนื่องจากเหตุผลด้านประสิทธิภาพ (ในบางกรณี)

แต่สิ่งนี้จะไม่ส่งผลกระทบต่อการอ่านรหัสหรือไม่ ในความเห็นของฉัน:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

ดูดีกว่า:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

แต่นี่อาจเป็นเพียงนิสัย เป็นที่ยอมรับฉันไม่ได้เห็นการใช้งาน++iมากมาย

ในกรณีนี้ประสิทธิภาพที่ไม่ดีพอหรือไม่ หรือฉันเพียงคนตาบอดและ++iสามารถอ่านได้มากกว่าi++?


1
ฉันใช้i++ก่อนที่ฉันจะรู้ว่าอาจส่งผลต่อประสิทธิภาพการทำงานมากกว่า++iดังนั้นฉันจึงเปลี่ยน ตอนแรกหลังได้ดูแปลก ๆ หน่อย ๆ แต่หลังจากที่เล็ก ๆ น้อย ๆ i++ในขณะที่ฉันได้ใช้มันและตอนนี้มันให้ความรู้สึกเป็นธรรมชาติ
gablin

15
++iและi++ทำสิ่งต่าง ๆ ในบริบทบางอย่างอย่าคิดว่าพวกเขาเหมือนกัน
Orbling

2
มันเกี่ยวกับ C หรือ C ++ หรือไม่ พวกเขาเป็นสองภาษาที่แตกต่างกันมาก ! :-) ใน C ++ for (type i = 0; i != 42; ++i)ห่วงสำหรับสำนวนคือ ไม่เพียง แต่สามารถoperator++จะมากเกินไป แต่เพื่อให้สามารถและoperator!= operator<การเพิ่มคำนำหน้าไม่แพงกว่า postfix ไม่เท่ากันไม่แพงกว่าน้อยกว่า เราควรใช้อันไหนดี?
โบเพอร์สัน

7
ไม่ควรเรียกว่า ++ C ใช่หรือไม่
Armand

21
@Stephen: C ++ วิธีการใช้ C เพิ่มให้มันแล้วใช้คนเก่า
supercat

คำตอบ:


58

ข้อเท็จจริง:

  1. i ++ และ ++ ฉันอ่านง่ายพอ ๆ กัน คุณไม่ชอบเพราะคุณไม่คุ้นเคย แต่ไม่มีอะไรที่คุณสามารถตีความผิดได้ดังนั้นจึงไม่มีงานอ่านหรือเขียนอีกต่อไป

  2. อย่างน้อยในบางกรณีผู้ประกอบการ postfix จะมีประสิทธิภาพน้อยลง

  3. อย่างไรก็ตามในกรณี 99.99% มันจะไม่สำคัญเพราะ (a) มันจะทำหน้าที่ในรูปแบบที่เรียบง่ายหรือดั้งเดิม แต่อย่างไรก็ตามและเป็นปัญหาเฉพาะถ้ามันเป็นการคัดลอกวัตถุขนาดใหญ่ (b) มันจะไม่ทำงาน ส่วนสำคัญของรหัส (c) คุณไม่รู้ว่าคอมไพเลอร์จะปรับให้เหมาะสมหรือไม่มันอาจทำ

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

คุณควรใช้สามัญสำนึกของคุณและไม่ควรใช้ micro-optimization จนกว่าคุณจะต้องการ แต่ไม่ควรใช้อย่างไม่มีประสิทธิภาพเพื่อประโยชน์ โดยทั่วไปแล้วนี่หมายถึง: ก่อนออกกฎการสร้างโค้ดใด ๆ ซึ่งไม่มีประสิทธิภาพอย่างไม่สามารถยอมรับได้แม้ในรหัสที่ไม่ได้เป็นช่วงเวลาที่วิกฤติ (โดยปกติจะเป็นสิ่งที่แสดงถึงข้อผิดพลาดทางแนวคิดพื้นฐานเช่นผ่านวัตถุ 500MB โดยไม่มีค่า) และวิธีที่สองในการเขียนโค้ดเลือกวิธีที่ชัดเจนที่สุด

อย่างไรก็ตามที่นี่ฉันเชื่อว่าคำตอบนั้นง่าย: ฉันเชื่อว่าการเขียนคำนำหน้ายกเว้นว่าคุณต้องการ postfix โดยเฉพาะคือ (a) ชัดเจนยิ่งขึ้นและ (b) มีแนวโน้มที่จะมีประสิทธิภาพมากขึ้นดังนั้นคุณควรเขียนตามค่าเริ่มต้นเสมอ ไม่ต้องกังวลถ้าคุณลืม

หกเดือนที่แล้วฉันคิดเหมือนคุณว่า i ++ นั้นดูเป็นธรรมชาติมากกว่า แต่ก็เป็นสิ่งที่คุณคุ้นเคย

แก้ไข 1: Scott Meyers ใน "C ++ ที่มีประสิทธิภาพมากกว่า" ซึ่งโดยทั่วไปฉันเชื่อในสิ่งนี้บอกว่าคุณควรหลีกเลี่ยงการใช้ตัวดำเนินการ postfix กับประเภทที่ผู้ใช้กำหนด (เพราะการใช้งานที่เพิ่มขึ้นอย่างมีสติเท่านั้น คัดลอกวัตถุเรียกใช้ฟังก์ชันการเพิ่มส่วนนำหน้าเพื่อดำเนินการเพิ่มและส่งคืนสำเนา แต่การทำสำเนาอาจมีราคาแพง)

ดังนั้นเราไม่ทราบว่ามีกฎทั่วไปเกี่ยวกับ (a) หรือไม่ว่าเป็นจริงในวันนี้หรือไม่ (b) ใช้กับประเภทที่แท้จริง (c) ไม่ว่าคุณจะใช้ "++" หรือไม่ อะไรก็ได้มากกว่าคลาส iterator ที่มีน้ำหนักเบาเลยทีเดียว แต่ด้วยเหตุผลทั้งหมดที่ฉันอธิบายไว้ข้างต้นมันไม่สำคัญเลยทำในสิ่งที่ฉันพูดไปก่อนหน้านี้

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


การโพสต์ของคุณถูกต้องกับเงิน ในนิพจน์ที่ตัวดำเนินการ infix + และ post-increment ++ ถูกโหลดมากเกินไปเช่น aClassInst = someOtherClassInst + yetAnotherClassInst ++ ตัวแยกวิเคราะห์จะสร้างรหัสเพื่อดำเนินการเพิ่มเติมหลังการสร้างรหัสเพื่อดำเนินการเพิ่มเติมภายหลัง สร้างสำเนาชั่วคราว เครื่องมือเพิ่มประสิทธิภาพที่นี่ไม่ได้เพิ่มขึ้นภายหลัง มันคือการใช้ตัวดำเนินการมัดแน่นเกินไป ตัวดำเนินการมัดสร้างอินสแตนซ์ใหม่
bit-twiddler

2
ฉันต้องสงสัยเป็นอย่างยิ่งว่าเหตุผลที่คนที่มี 'ใช้' to i++มากกว่า++iเป็นเพราะชื่อของภาษาการเขียนโปรแกรมบางอย่างที่นิยมอ้างอิงในคำถามนี้ / คำตอบ ...
เงา

61

รหัสสำหรับโปรแกรมเมอร์เสมอก่อนและคอมพิวเตอร์ที่สอง

หากมีความแตกต่างประสิทธิภาพหลังจากที่คอมไพเลอร์ที่มีการโยนตาผู้เชี่ยวชาญกว่ารหัสของคุณและคุณสามารถวัดได้และมันเป็นเรื่องสำคัญ - แล้วคุณสามารถเปลี่ยนได้


7
คำสั่งสุดยอด !!!
เดฟ

8
@ มาร์ติน: ซึ่งเป็นเหตุผลที่ฉันต้องการใช้การเพิ่มคำนำหน้า ความหมายของ Postfix หมายถึงการรักษาคุณค่าเก่าไว้และหากไม่จำเป็นต้องใช้มันก็ไม่ถูกต้องที่จะใช้
Matthieu M.

1
สำหรับดัชนีลูปที่ชัดเจนยิ่งขึ้น - แต่ถ้าคุณวนซ้ำอาร์เรย์โดยการเพิ่มพอยน์เตอร์และใช้คำนำหน้าหมายถึงการเริ่มต้นที่ที่อยู่ที่ผิดกฎหมายก่อนที่จะเริ่มต้นซึ่งจะแย่มากโดยไม่คำนึงถึงประสิทธิภาพที่เพิ่มขึ้น
Martin Beckett

5
@ Matthew: มันไม่เป็นความจริงเลยที่การโพสต์เพิ่มขึ้นหมายถึงการเก็บสำเนาของค่าเก่า ๆ ไม่มีใครแน่ใจได้ว่าคอมไพเลอร์จัดการค่ากลางได้อย่างไรจนกระทั่งมีคนดูเอาต์พุต หากคุณใช้เวลาในการดู GCC ที่สร้างหมายเหตุประกอบรายการภาษาประกอบคุณจะเห็นว่า GCC สร้างรหัสเครื่องเดียวกันสำหรับลูปทั้งสอง เรื่องไร้สาระนี้เกี่ยวกับความนิยมที่เพิ่มขึ้นล่วงหน้ามากกว่าการเพิ่มโพสต์เพราะมันมีประสิทธิภาพมากขึ้นน้อยกว่าการคาดเดา
bit-twiddler

2
@Mathhieu: รหัสที่ฉันโพสต์ถูกสร้างขึ้นด้วยการเพิ่มประสิทธิภาพปิด ข้อมูลจำเพาะ C ++ ไม่ได้ระบุว่าคอมไพเลอร์ต้องสร้างอินสแตนซ์ชั่วคราวของค่าเมื่อใช้การเพิ่มภายหลัง เพียง แต่ระบุถึงความสำคัญของโอเปอเรเตอร์ก่อนและหลังเพิ่ม
bit-twiddler

13

GCC สร้างรหัสเครื่องเดียวกันสำหรับลูปทั้งสอง

รหัส C

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

รหัสการประกอบ (พร้อมกับความคิดเห็นของฉัน)

    cstring
LC0:
    .ascii "i = %d\12\0"
    .text
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    call    L9
"L00000000001$pb":
L9:
    popl    %ebx
    movl    $0, -16(%ebp)  // -16(%ebp) is "i" for the first loop 
    jmp L2
L3:
    movl    -16(%ebp), %eax   // move i for the first loop to the eax register 
    movl    %eax, 4(%esp)     // push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // push the address of the format string onto the stack
    call    L_printf$stub    // call printf
    leal    -16(%ebp), %eax  // make the eax register point to i
    incl    (%eax)           // increment i
L2:
    cmpl    $41, -16(%ebp)  // compare i to the number 41
    jle L3              // jump to L3 if less than or equal to 41
    movl    $0, -12(%ebp)   // -12(%ebp) is "i" for the second loop  
    jmp L5
L6:
    movl    -12(%ebp), %eax   // move i for the second loop to the eax register 
    movl    %eax, 4(%esp)     // push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // push the address of the format string onto the stack
    call    L_printf$stub     // call printf
    leal    -12(%ebp), %eax  // make eax point to i
    incl    (%eax)           // increment i
L5:
    cmpl    $41, -12(%ebp)   // compare i to 41 
    jle L6               // jump to L6 if less than or equal to 41
    movl    $0, %eax
    addl    $36, %esp
    popl    %ebx
    leave
    ret
    .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
    .indirect_symbol _printf
    hlt ; hlt ; hlt ; hlt ; hlt
    .subsections_via_symbols

วิธีการเกี่ยวกับการเพิ่มประสิทธิภาพเปิด?
serv-inc

2
@ ผู้ใช้: อาจไม่มีการเปลี่ยนแปลง แต่จริง ๆ แล้วคุณคาดหวังว่า bit-twiddler จะกลับมาทุกเวลาเร็ว ๆ นี้ไหม?
Deduplicator

2
ดูแล: ในขณะที่ใน C ไม่มีประเภทที่ผู้ใช้กำหนดกับผู้ประกอบการมากเกินไปใน C ++ มีและ generalizing จากประเภทพื้นฐานประเภทที่ผู้ใช้กำหนดคือไม่ถูกต้องเพียง
Deduplicator

@Dupuplicator: ขอบคุณสำหรับการชี้ให้เห็นว่าคำตอบนี้ไม่ได้พูดถึงประเภทที่ผู้ใช้กำหนด ฉันไม่ได้ดูหน้าผู้ใช้ของเขาก่อนถาม
serv-inc

12

ไม่ต้องกังวลกับประสิทธิภาพพูด 97% ของเวลา การปรับให้เหมาะสมก่อนกำหนดเป็นรากของความชั่วทั้งหมด

- Donald Knuth

ตอนนี้ที่ว่านี้จะออกจากทางของเราขอให้ทางเลือกของเราsanely :

  • ++i: เพิ่มส่วนนำหน้าเพิ่มค่าปัจจุบันและเพิ่มผลลัพธ์
  • i++: การเพิ่มค่า postfix , คัดลอกค่า, เพิ่มค่าปัจจุบัน, ให้ค่าการคัดลอก

ยกเว้นกรณีที่จำเป็นต้องมีการคัดลอกค่าเก่าการใช้การเพิ่ม postfixเป็นวิธีการทำสิ่งต่างๆให้สำเร็จ

ความไม่ถูกต้องมาจากความเกียจคร้านมักใช้สิ่งก่อสร้างที่แสดงออกถึงความตั้งใจของคุณในแบบที่ตรงที่สุดมีโอกาสน้อยกว่าผู้ดูแลในอนาคตอาจเข้าใจผิดเจตนาเดิมของคุณ

แม้ว่ามันจะน้อย (จริง ๆ ) ที่นี่มีบางครั้งที่ฉันสับสนกับการอ่านรหัส: ฉันสงสัยจริงๆว่าเจตนาและการแสดงออกที่แท้จริงเกิดขึ้นจริงและแน่นอนหลังจากนั้นไม่กี่เดือนพวกเขา (หรือฉัน) จำไม่ได้ ...

ดังนั้นไม่สำคัญว่าคุณจะถูกต้องหรือไม่ อ้อมกอดจูบ ในอีกไม่กี่เดือนคุณจะต้องหลีกเลี่ยงการฝึกฝนแบบเดิม ๆ


4

ใน C ++ คุณสามารถสร้างความแตกต่างด้านประสิทธิภาพได้อย่างมากหากมีโอเปอเรเตอร์โอเวอร์โหลดที่เกี่ยวข้องโดยเฉพาะอย่างยิ่งถ้าคุณกำลังเขียนโค้ดเทมเพลตและไม่ทราบว่าตัวทำซ้ำอาจส่งผ่านอะไรตรรกะเบื้องหลัง iterator X นั่นคือช้าและไม่สามารถจับเวลาได้โดยคอมไพเลอร์

แต่นี่ไม่ใช่กรณีใน C ที่คุณรู้ว่ามันเป็นแค่เรื่องเล็กน้อยและความแตกต่างด้านประสิทธิภาพนั้นเล็กน้อยและคอมไพเลอร์สามารถปรับให้เหมาะ

ดังนั้นเคล็ดลับ: คุณเขียนโปรแกรมใน C หรือ C ++ และคำถามเกี่ยวข้องกับคำถามใดคำถามหนึ่งไม่ใช่คำถามทั้งสอง


2

ประสิทธิภาพของการดำเนินการอย่างใดอย่างหนึ่งขึ้นอยู่กับสถาปัตยกรรมพื้นฐาน หนึ่งต้องเพิ่มค่าที่เก็บไว้ในหน่วยความจำซึ่งหมายความว่าคอขวด von Neumann เป็นปัจจัย จำกัด ในทั้งสองกรณี

ในกรณีของ ++ i เราต้องทำ

Fetch i from memory 
Increment i
Store i back to memory
Use i

ในกรณีของ i ++ เราต้องทำ

Fetch i from memory
Use i
Increment i
Store i back to memory

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


2

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

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

จากเหตุผล # 1 โดยเฉพาะฉันเดาว่าเมื่อคุณทำเวลาจริงพวกเขาจะอยู่ติดกัน


-1

ประการแรกมันไม่ส่งผลกระทบต่อการอ่าน IMO ไม่ใช่สิ่งที่คุณเคยเห็น แต่มันจะเป็นเพียงช่วงสั้น ๆ ก่อนที่คุณจะคุ้นเคยกับมัน

ข้อสองเว้นแต่ว่าคุณใช้โอเปอเรเตอร์ postfix ในรหัสของคุณคุณอาจไม่เห็นความแตกต่างมากนัก อาร์กิวเมนต์หลักที่ไม่ใช้เมื่อเป็นไปได้คือสำเนาของค่า var ดั้งเดิมต้องถูกเก็บไว้จนกว่าจะสิ้นสุดของอาร์กิวเมนต์ที่ยังคงใช้ var เดิม นั่นคือ 32 บิตหรือ 64 บิตขึ้นอยู่กับสถาปัตยกรรม นั่นเท่ากับ 4 หรือ 8 ไบต์หรือ 0.00390625 หรือ 0.0078125 MB มีโอกาสสูงมากที่คุณจะไม่ต้องใช้มันเป็นจำนวนมากซึ่งจำเป็นต้องได้รับการบันทึกเป็นระยะเวลานานด้วยทรัพยากรคอมพิวเตอร์และความเร็วของวันนี้

แก้ไข: ลืมส่วนที่เหลือนี้เนื่องจากข้อสรุปของฉันได้รับการพิสูจน์แล้วว่าเป็นเท็จ (ยกเว้นส่วนของ ++ i และ i ++ ไม่ได้ทำสิ่งเดียวกันเสมอ ... ซึ่งยังคงเป็นจริง)

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

for (int i=0; i < 10; i++) //the set of i values here will be {0,1,2,3,4,5,6,7,8,9}
for (int i=0; i < 10; ++i) //the set of i values here will be {1,2,3,4,5,6,7,8,9,10}

4
การดำเนินการที่เพิ่มขึ้นเกิดขึ้นในตอนท้ายของ for loop ดังนั้นพวกมันจะมีเอาต์พุตเหมือนกันทุกประการ มันไม่ได้ขึ้นอยู่กับคอมไพเลอร์ / ล่าม
jsternberg

@ jsternberg ... ขอบคุณฉันไม่แน่ใจว่าเมื่อเกิดการเพิ่มขึ้นเนื่องจากฉันไม่เคยมีเหตุผลที่จะลองทดสอบ มันนานเกินไปแล้วที่ฉันจะรวบรวมผลงานในวิทยาลัย! lol
Kenneth

ผิดผิดผิด
ruohola

-1

ฉันคิดว่า semantically, ++iมีเหตุผลมากกว่าi++ดังนั้นฉันจะยึดติดกับอันแรกยกเว้นว่าเป็นเรื่องธรรมดาที่จะไม่ทำเช่นนั้น (เช่นใน Java, ที่คุณควรใช้i++เพราะมันใช้กันอย่างแพร่หลาย)


-2

มันไม่ได้เกี่ยวกับประสิทธิภาพเท่านั้น

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

และใช้การเพิ่มทีละน้อยสำหรับประเภทดั้งเดิมและประเภทที่ซับซ้อน ... ซึ่งไม่สามารถอ่านได้จริงๆ


-2

ถ้าคุณไม่ต้องการมันจริงๆฉันก็จะใช้ ++ ฉัน ในกรณีส่วนใหญ่นี่คือสิ่งที่เราตั้งใจไว้ ไม่ใช่บ่อยครั้งที่คุณต้องใช้ i ++ และคุณต้องคิดสองครั้งเสมอเมื่ออ่านโครงสร้าง ด้วย ++ i มันง่าย: คุณเพิ่ม 1 ใช้แล้วฉันยังคงเหมือนเดิม

ดังนั้นฉันเห็นด้วยอย่างเต็มที่กับ @martin beckett: ทำให้มันง่ายขึ้นสำหรับตัวคุณเองมันยากพออยู่แล้ว

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