11.2 อาเรย์ของ struct
struct ตัวเดียวเก็บนักเรียนได้หนึ่งคน แต่โจทย์ให้นักเรียนมาเป็นร้อยเป็นพันคน เหมือนตอนที่ตัวแปร int ตัวเดียวไม่พอ แล้วเราใช้อาเรย์ในบทที่ 5 struct ก็ทำเป็นอาเรย์ได้เหมือนกัน หน้านี้อ่านข้อมูลนักเรียน N คน แล้วทำงานที่เจอในโจทย์บ่อยที่สุด คือหาคนที่ดีที่สุด เลือกคนที่ผ่านเกณฑ์ ค้นหาด้วยชื่อ และย้ายข้อมูลทั้งคน
ประกาศและอ่านข้อมูล
Student students[100]; คืออาเรย์ 100 ช่อง แต่ละช่องเป็น Student หนึ่งก้อน students[2] คือนักเรียนคนที่สาม และ students[2].score คือคะแนนของคนนั้น อ่านจากซ้ายไปขวา คือ "เลือกช่องที่ 2 ก่อน แล้วค่อยหยิบ score ออกจากช่องนั้น"
หาคนคะแนนสูงสุด
นักเรียน N คน แต่ละคนมีชื่อกับคะแนน ให้พิมพ์ชื่อและคะแนนของคนที่ได้คะแนนสูงสุด ถ้ามีหลายคนเท่ากัน ให้ตอบคนที่มาก่อน วิธีเหมือนหาตำแหน่งของค่ามากสุดในบทที่ 5 ทุกอย่าง จำ index ของคนที่ดีที่สุดไว้ ต่างกันแค่ตอนเปรียบเทียบต้องบอกว่าเทียบสมาชิกตัวไหน
#include <stdio.h>
typedef struct {
char name[21];
int score;
} Student;
Student students[100]; // (1)!
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s %d", students[i].name, &students[i].score); // (2)!
}
int best = 0;
for (int i = 1; i < n; i++) {
if (students[i].score > students[best].score) { // (3)!
best = i;
}
}
printf("top = %s %d\n", students[best].name, students[best].score);
return 0;
}
- ประกาศนอก
mainเหมือนอาเรย์ใหญ่ในบทที่ 5 หนึ่งช่องใช้หน่วยความจำเท่ากับ struct หนึ่งก้อน อาเรย์ของ struct จึงใหญ่เร็วกว่าอาเรย์ของintมาก ขนาดจริงของหนึ่งก้อนดูได้ในหน้า 11.3 - ชื่อเป็นอาเรย์ของ
charอยู่แล้ว ไม่มี&ส่วนคะแนนเป็นintต้องมี&เหมือนเดิม - ใช้
>คนที่คะแนนเท่ากับคนที่ดีที่สุดตอนนี้จะไม่ได้แทนที่ จึงได้คนที่มาก่อน ถ้าโจทย์ให้ตอบคนหลังสุด เปลี่ยนเป็น>=
5
Mali 87
Krit 92
Ploy 78
Nat 92
Beam 65
top = Krit 92
Krit กับ Nat ได้ 92 เท่ากัน โปรแกรมตอบ Krit เพราะมาก่อน ข้อดีของการเก็บเป็น struct เห็นชัดตรงบรรทัดพิมพ์ ได้ index ตัวเดียว ก็หยิบได้ทั้งชื่อทั้งคะแนน ไม่มีโอกาสหยิบชื่อของคนหนึ่งคู่กับคะแนนของอีกคน
จำทั้งก้อนแทนการจำ index
ในหน้า 11.1 เห็นแล้วว่า struct คัดลอกด้วย = ได้ทั้งก้อน จึงจำ "นักเรียนที่ดีที่สุด" ไว้ในตัวแปร Student ตัวเดียวได้เลย และถ้างานมีแค่นี้ ไม่ต้องใช้อาเรย์ด้วยซ้ำ อ่านทีละคนแล้วเทียบทันที
#include <stdio.h>
typedef struct {
char name[21];
int score;
} Student;
int main()
{
int n;
scanf("%d", &n);
Student best;
scanf("%s %d", best.name, &best.score);
for (int i = 1; i < n; i++) {
Student s;
scanf("%s %d", s.name, &s.score);
if (s.score > best.score) {
best = s;
}
}
printf("top = %s %d\n", best.name, best.score);
return 0;
}
5
Mali 87
Krit 92
Ploy 78
Nat 92
Beam 65
top = Krit 92
คนแรกอ่านเข้า best ตรงๆ เพราะตอนเริ่มยังไม่มีใครให้เทียบ แล้วคนที่เหลือค่อยมาท้าชิง บรรทัด best = s; คัดลอกทั้งชื่อและคะแนนในคำสั่งเดียว ถ้าเก็บแยกเป็น char กับ int ต้องใช้ strcpy สำหรับชื่อ ดูในส่วนลองทำให้พังว่าลืมแล้วเป็นอย่างไร
งานที่ต้องย้อนกลับไปดูข้อมูลซ้ำ
บางงานอ่านรอบเดียวไม่พอ เช่น "พิมพ์ชื่อทุกคนที่ได้คะแนนมากกว่าค่าเฉลี่ยของห้อง" ต้องอ่านครบทุกคนก่อนถึงจะรู้ค่าเฉลี่ย แล้วค่อยย้อนกลับไปดูทีละคน งานแบบนี้ต้องเก็บทุกคนไว้ในอาเรย์
#include <stdio.h>
typedef struct {
char name[21];
int score;
} Student;
Student students[100];
int main()
{
int n;
scanf("%d", &n);
int total = 0;
for (int i = 0; i < n; i++) {
scanf("%s %d", students[i].name, &students[i].score);
total += students[i].score;
}
for (int i = 0; i < n; i++) {
if (students[i].score * n > total) { // (1)!
printf("%s\n", students[i].name);
}
}
return 0;
}
- อยากรู้ว่า score มากกว่า total / n ไหม คูณ n ทั้งสองข้างได้ score × n มากกว่า total ซึ่งเป็นจำนวนเต็มล้วน ไม่ต้องใช้
doubleและไม่มีปัญหาทศนิยมคลาดเคลื่อน ใช้ได้เพราะ n เป็นบวก ถ้าคูณด้วยค่าติดลบ เครื่องหมายมากกว่าจะกลับด้าน
5
Mali 87
Krit 92
Ploy 78
Nat 92
Beam 65
Mali
Krit
Nat
ผลรวมคือ 414 ค่าเฉลี่ยคือ 82.8 คนที่ได้มากกว่าคือ Mali Krit และ Nat ลองเช็กด้วยวิธีคูณ Mali ได้ 87 × 5 = 435 มากกว่า 414 จึงผ่าน Ploy ได้ 78 × 5 = 390 ไม่ผ่าน
ค้นหาด้วยชื่อ
อีกงานที่เจอบ่อยคือถามข้อมูลด้วยชื่อหรือรหัส เช่น "Ploy ได้กี่คะแนน" วิธีตรงไปตรงมาคือไล่ดูทีละช่องจนเจอชื่อที่ตรง เทียบสตริงด้วย strcmp จากบทที่ 6 ซึ่งคืนค่า 0 เมื่อสองข้อความเหมือนกัน
#include <stdio.h>
#include <string.h>
typedef struct {
char name[21];
int score;
} Student;
Student students[100];
int main()
{
int n, q;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s %d", students[i].name, &students[i].score);
}
scanf("%d", &q);
for (int k = 0; k < q; k++) {
char wanted[21];
scanf("%s", wanted);
int found = -1;
for (int i = 0; i < n; i++) {
if (strcmp(students[i].name, wanted) == 0) {
found = i;
break;
}
}
if (found == -1) {
printf("%s not found\n", wanted);
} else {
printf("%s %d\n", students[found].name, students[found].score);
}
}
return 0;
}
5
Mali 87
Krit 92
Ploy 78
Nat 92
Beam 65
3
Ploy
Somchai
Beam
Ploy 78
Somchai not found
Beam 65
ตั้ง found เป็น -1 ไว้ก่อน ซึ่งไม่ใช่ index ที่มีจริง ถ้าไล่จนหมดแล้วยังเป็น -1 แปลว่าไม่เจอ วิธีนี้ถามหนึ่งครั้งต้องไล่ดูได้ถึง N ช่อง ถ้า N กับจำนวนคำถามเป็นหลักร้อยหรือหลักพันยังเร็วพอ ถ้าเป็นแสนทั้งคู่จะช้าเกินไป วิธีค้นหาที่เร็วกว่าจะได้เรียนในบทที่ 19 และบทที่ 20
สลับข้อมูลทั้งคนในสามบรรทัด
ถ้าใช้อาเรย์ขนาน การสลับนักเรียนสองคนต้องสลับชื่อด้วย strcpy สามครั้ง และสลับคะแนนอีกสามบรรทัด ถ้ามีห้องหรืออายุก็ต้องเพิ่มอีก แต่ struct สลับแบบเดียวกับสลับ int สองตัว เพราะ = คัดลอกทั้งก้อน
ตัวอย่างนี้ย้ายคนที่คะแนนสูงสุดขึ้นมาอยู่หัวแถว แล้วพิมพ์ทั้งแถว
#include <stdio.h>
typedef struct {
char name[21];
int score;
int room;
} Student;
Student students[100];
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s %d %d", students[i].name, &students[i].score, &students[i].room);
}
int best = 0;
for (int i = 1; i < n; i++) {
if (students[i].score > students[best].score) {
best = i;
}
}
Student temp = students[0];
students[0] = students[best];
students[best] = temp;
for (int i = 0; i < n; i++) {
printf("%s %d %d\n", students[i].name, students[i].score, students[i].room);
}
return 0;
}
4
Mali 87 3
Krit 92 1
Ploy 78 3
Beam 65 2
Krit 92 1
Mali 87 3
Ploy 78 3
Beam 65 2
Krit กับ Mali สลับที่กันทั้งชื่อ คะแนน และห้อง คนอื่นอยู่ที่เดิม ถ้าทำแบบนี้ซ้ำกับแถวที่เหลือ คือหาคนที่ดีที่สุดในช่องที่ 1 ถึง N - 1 แล้วสลับมาไว้ช่องที่ 1 ไปเรื่อยๆ ก็จะได้ทั้งแถวเรียงจากคะแนนมากไปน้อย วิธีนี้คือการเรียงข้อมูลแบบหนึ่ง ซึ่งจะเรียนเต็มๆ ในบทที่ 13
ลองทำให้พัง
แบบที่ 1 วาง [i] ผิดที่
ในโปรแกรมหาคนคะแนนสูงสุด เขียนบรรทัดอ่านข้อมูลเป็น
scanf("%s %d", students.name[i], &students.score[i]);
top.c:15:32: error: '(Student *)&students' is a pointer; did you mean to use '->'?
15 | scanf("%s %d", students.name[i], &students.score[i]);
| ^
| ->
students เป็นอาเรย์ ไม่ใช่ struct จึงไม่มีสมาชิกชื่อ name ต้องเลือกช่องก่อนแล้วค่อยหยิบสมาชิก คือ students[i].name ระวังว่าคำแนะนำของ compiler ที่ให้ใช้ -> ไม่ใช่วิธีแก้ที่ถูก ในกรณีนี้ compiler มองชื่ออาเรย์เป็นที่อยู่ของช่องแรก (เรื่องเดียวกับในบทที่ 10) เลยเดาว่าเราอยากใช้ pointer ถ้าทำตาม จะได้ error ใหม่ว่า subscripted value is neither array nor pointer nor vector แทน คำแนะนำของ compiler ช่วยได้บ่อย แต่ต้องคิดก่อนทำตามเสมอ -> คืออะไรจะได้เรียนในหน้า 11.4
แบบที่ 2 จำชื่อด้วย =
ถ้าไม่จำทั้งก้อน แต่จำชื่อกับคะแนนไว้คนละตัวแปร แบบนี้
char best_name[21];
int best_score = -1;
for (int i = 0; i < n; i++) {
if (students[i].score > best_score) {
best_score = students[i].score;
best_name = students[i].name;
}
}
top_name.c:22:23: error: assignment to expression with array type
22 | best_name = students[i].name;
| ^
error เดียวกับที่เจอในบทที่ 6 เพราะ best_name กับ students[i].name เป็นอาเรย์ทั้งคู่ แก้ได้สองทาง ใช้ strcpy(best_name, students[i].name); หรือดีกว่านั้นคือเก็บทั้งก้อนใน Student best; แล้วใช้ best = students[i]; แบบที่ทำข้างบน กฎที่ต้องจำคือ อาเรย์ใช้ = ไม่ได้ แต่ struct ที่มีอาเรย์อยู่ข้างในใช้ได้
สรุป
Student students[100];คืออาเรย์ของ struct ประกาศนอกmainเหมือนอาเรย์ใหญ่ทั่วไปstudents[i].scoreเลือกช่องก่อน แล้วค่อยหยิบสมาชิก- หาคนที่ดีที่สุดด้วยการจำ index หรือจำทั้งก้อนด้วย
best = s;ก็ได้ เครื่องหมาย>กับ>=ตัดสินว่าได้คนแรกหรือคนหลัง - งานที่ต้องย้อนกลับไปดูข้อมูลหลังอ่านครบ เช่นเทียบกับค่าเฉลี่ย ต้องเก็บไว้ในอาเรย์
- ค้นหาด้วยชื่อใช้
strcmpไล่ทีละช่อง - สลับสอง struct ด้วยตัวแปรพักสามบรรทัด ได้ครบทุกสมาชิก