15.5 bool reference และ auto
หน้าสุดท้ายของบทนี้รวมของเล็กๆ ที่ C++ เพิ่มให้ภาษา C แต่ใช้บ่อยมากในโค้ดแข่งขัน คือชนิด bool สำหรับจริงเท็จ reference ที่ให้ฟังก์ชันแก้ตัวแปรของคนเรียกได้โดยไม่ต้องใช้ * กับ & แบบ pointer ในบทที่ 10 ลูป for แบบใหม่ที่เดินทีละตัวในข้อความหรืออาเรย์ คำว่า auto และปิดท้ายด้วย new กับ delete ที่ใช้แทน malloc กับ free
bool เก็บจริงหรือเท็จ
ในบทที่ 3 เราใช้ int เก็บผลของเงื่อนไข จริงคือ 1 เท็จคือ 0 C++ มีชนิด bool สำหรับงานนี้โดยเฉพาะ มีค่าได้แค่สองค่าคือ true กับ false เห็นชื่อชนิดก็รู้ทันทีว่าตัวแปรนี้เป็นคำตอบแบบใช่หรือไม่ใช่
#include <bits/stdc++.h>
using namespace std;
bool is_vowel(char c) // (1)!
{
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int score;
string word;
cin >> score >> word;
bool passed = score >= 50; // (2)!
cout << passed << '\n'; // (3)!
if (passed) {
cout << "pass\n";
}
bool has_vowel = false;
int n = word.size();
for (int i = 0; i < n; i++) {
if (is_vowel(word[i])) {
has_vowel = true;
}
}
if (!has_vowel) {
cout << "no vowels in " << word << '\n';
}
cout << (int)sizeof(passed) << '\n';
return 0;
}
- ฟังก์ชันที่ตอบใช่หรือไม่ใช่ คืน
boolอ่านง่ายกว่าคืนintมาก เวลาเรียกก็เขียนในเงื่อนไขได้ตรงๆ - ผลของการเปรียบเทียบเก็บลง
boolได้เลย coutพิมพ์trueเป็น1และfalseเป็น0ถ้าโจทย์ให้ตอบเป็นคำ ต้องใช้ifเลือกพิมพ์เอง
72 rhythm
1
pass
no vowels in rhythm
1
บรรทัดสุดท้ายบอกว่า bool ใช้แค่ 1 byte เทียบกับ int ที่ใช้ 4 byte อาเรย์ที่จดว่าอะไรเคยเจอแล้ว อย่าง bool seen[1000001]; จึงใช้หน่วยความจำแค่หนึ่งในสี่ของอาเรย์ int ขนาดเท่ากัน ส่วน ! && || ใช้กับ bool ได้เหมือนที่ใช้กับเงื่อนไขในบทที่ 3 ทุกอย่าง
reference ชื่อที่สองของตัวแปร
reference คือการตั้งชื่อเพิ่มให้กล่องที่มีอยู่แล้ว ประกาศด้วย & หลังชื่อชนิด ไม่ได้สร้างกล่องใหม่ ทั้งสองชื่อชี้กล่องเดียวกัน แก้ผ่านชื่อไหนก็เปลี่ยนที่กล่องเดียวกัน
#include <bits/stdc++.h>
using namespace std;
int main()
{
int score = 70;
int &bonus_target = score;
bonus_target += 5;
cout << score << '\n';
score = 90;
cout << bonus_target << '\n';
return 0;
}
75
90
int &bonus_target = score; แปลว่า bonus_target คือชื่อเล่นของ score บวกผ่านชื่อเล่น score ก็เปลี่ยน เปลี่ยน score ชื่อเล่นก็เห็น reference ต้องผูกกับตัวแปรตั้งแต่ตอนประกาศ และผูกแล้วเปลี่ยนไปผูกตัวอื่นไม่ได้
ใช้ในฟังก์ชันเดียวๆ แบบนี้ไม่ค่อยมีประโยชน์ ประโยชน์จริงอยู่ที่พารามิเตอร์ของฟังก์ชัน
ส่งตัวแปรให้ฟังก์ชันแก้
หน้า 8.2 เห็นแล้วว่าฟังก์ชันได้แค่สำเนา สลับค่าในฟังก์ชันแล้วตัวแปรของคนเรียกไม่เปลี่ยน บทที่ 10 แก้ด้วยการส่งที่อยู่ไปเป็น pointer ใน C++ ทำได้ง่ายกว่านั้น ให้พารามิเตอร์เป็น reference
#include <bits/stdc++.h>
using namespace std;
void swap_values(int &a, int &b) // (1)!
{
int temp = a;
a = b;
b = temp;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int first, second;
cin >> first >> second;
swap_values(first, second); // (2)!
cout << first << ' ' << second << '\n';
return 0;
}
aกับbเป็น reference จึงไม่ใช่สำเนา แต่เป็นชื่อเล่นของตัวแปรที่คนเรียกส่งมา ในฟังก์ชันใช้abเหมือนตัวแปรธรรมดา ไม่ต้องมี*- เรียกด้วยชื่อตัวแปรตรงๆ ไม่ต้องใส่
&หน้าfirstกับsecond
3 8
8 3
เทียบกับฟังก์ชัน swap แบบ pointer ในหน้า 10.2
| pointer (บทที่ 10) | reference (C++) | |
|---|---|---|
| ประกาศพารามิเตอร์ | void swap(int *x, int *y) | void swap_values(int &a, int &b) |
| ใช้ในฟังก์ชัน | *x = *y; | a = b; |
| เรียก | swap(&first, &second); | swap_values(first, second); |
| ส่งของที่ไม่มีอยู่ได้ไหม | ได้ ส่ง NULL แล้วพังตอนรัน | ไม่ได้ ต้องผูกกับตัวแปรจริงเสมอ |
ข้างในทำงานแบบเดียวกัน คือส่งที่อยู่ของกล่องไป แต่ compiler จัดการ & กับ * ให้ทั้งหมด โอกาสลืมดอกจันหรือลืมเครื่องหมายแอนด์จึงหายไป ฟังก์ชันที่ต้องส่งผลกลับหลายค่า ซึ่งหน้า 8.2 บอกว่าต้องรอ pointer ก็ใช้ reference ได้ เช่น void min_max(int score[], int n, int &lowest, int &highest)
C++ มี swap ให้แล้ว
ตัวอย่างนี้ตั้งชื่อว่า swap_values เพราะใน std มีฟังก์ชัน swap ที่ทำงานแบบเดียวกันอยู่แล้ว เขียน swap(first, second); ได้เลยโดยไม่ต้องเขียนฟังก์ชันเอง และใช้กับ string หรือชนิดอื่นได้ด้วย
ส่ง string แบบไม่ต้องคัดลอก
ส่ง string ให้ฟังก์ชันแบบธรรมดา ฟังก์ชันจะได้สำเนาทั้งข้อความ ต่างจากอาเรย์ char ในบทที่ 8 ที่ส่งไปแค่ที่อยู่ ข้อความยาวแสนตัว เรียกฟังก์ชันหนึ่งครั้งก็คัดลอกแสนตัว ถ้าเรียกเป็นแสนครั้งจะช้าลงชัดเจน วิธีแก้คือส่งเป็น reference และใส่ const (ค่าคงที่) ไว้ข้างหน้า เพื่อสัญญาว่าฟังก์ชันจะอ่านอย่างเดียว ไม่แก้ข้อความของคนเรียก
#include <bits/stdc++.h>
using namespace std;
bool is_vowel_at(const string &text, int i)
{
char c = text[i];
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string text;
int q;
cin >> text >> q;
int count = 0;
for (int k = 0; k < q; k++) {
int i;
cin >> i;
if (is_vowel_at(text, i)) {
count++;
}
}
cout << count << '\n';
return 0;
}
programming 4
0 2 5 8
3
ทดลองกับข้อความยาว 105 ตัวและคำถาม 105 ข้อ บนเครื่องเดียวกับหน้า 15.3 ด้วยวิธีวัดแบบเดียวกัน
| พารามิเตอร์ | แบบ Code::Blocks | แบบ -O2 |
|---|---|---|
string text (คัดลอกทุกครั้ง) | 0.26 วินาที | 0.45 วินาที |
const string &text | 0.04 วินาที | 0.03 วินาที |
คำตอบเท่ากัน แต่แบบคัดลอกช้ากว่าราวสิบเท่า และจะยิ่งแย่ลงถ้าข้อความยาวขึ้น ถ้าใส่ const แล้วเผลอแก้ text ในฟังก์ชัน compiler จะไม่ยอมคอมไพล์ ช่วยกันพลาดได้อีกชั้น หลักง่ายๆ คือ string ที่ส่งให้ฟังก์ชันอ่าน ให้ส่งเป็น const string & ส่วน int double char ส่งแบบธรรมดาได้ เพราะคัดลอกตัวเลขตัวเดียวเร็วมาก
for แบบเดินทีละตัว
ลูปที่เดินดูทุกตัวในข้อความตั้งแต่ตัวแรกถึงตัวสุดท้ายเขียนบ่อยจน C++ มีรูปแบบสั้นให้ เรียกว่า range-based for
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string word;
cin >> word;
int vowels = 0;
for (char c : word) { // (1)!
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
vowels++;
}
}
cout << vowels << '\n';
for (char &c : word) { // (2)!
c = toupper(c);
}
cout << word << '\n';
int score[4] = {72, 85, 64, 90};
int total = 0;
for (int x : score) { // (3)!
total += x;
}
cout << total << '\n';
return 0;
}
- อ่านว่า "สำหรับ
cแต่ละตัวในword" รอบแรกcคือตัวที่ 0 รอบถัดไปคือตัวที่ 1 ไปจนครบ ไม่ต้องมีiไม่ต้องดูความยาว จึงไม่มีทางเกินขอบ แต่cเป็นสำเนาของแต่ละตัว - ใส่
&แล้วcเป็น reference ของตัวอักษรในข้อความจริงๆ แก้cก็คือแก้word - ใช้กับอาเรย์ที่ประกาศขนาดไว้ได้ด้วย ลูปจะเดินครบทั้ง 4 ช่อง
education
5
EDUCATION
311
ลูปแบบนี้ใช้ได้เมื่อต้องการดูทุกตัวตามลำดับเท่านั้น ถ้าต้องรู้ว่าตอนนี้อยู่ช่องไหน ต้องดูตัวข้างเคียง หรือเดินถอยหลัง ยังต้องใช้ for แบบมี i เหมือนเดิม และใช้กับอาเรย์ที่ส่งเข้ามาเป็นพารามิเตอร์ของฟังก์ชันไม่ได้ เพราะในฟังก์ชันไม่รู้ขนาดของอาเรย์ ส่วนบทที่ 16 จะใช้ลูปแบบนี้กับ vector ตลอด
auto ให้ compiler เลือกชนิดให้
ถ้าประกาศตัวแปรพร้อมค่าเริ่มต้น เขียน auto แทนชื่อชนิดได้ compiler จะดูจากค่าเริ่มต้นว่าควรเป็นชนิดอะไร
#include <bits/stdc++.h>
using namespace std;
int main()
{
auto count = 3;
auto height = 1.75;
auto grade = 'A';
string name = "Malee";
auto copy = name;
copy += "!";
cout << count << ' ' << height << ' ' << grade << ' ' << copy << '\n';
for (auto c : name) {
cout << c << '.';
}
cout << '\n';
return 0;
}
3 1.75 A Malee!
M.a.l.e.e.
3 เป็น int 1.75 เป็น double 'A' เป็น char และ copy เป็น string ตาม name ตอนนี้ auto ช่วยได้ไม่มาก เพราะชื่อชนิดที่เรารู้จักสั้นอยู่แล้ว มันจะมีประโยชน์จริงในบทที่ 16 ถึง 20 ที่ชื่อชนิดของ STL ยาวเป็นบรรทัด ระหว่างนี้ใช้ใน for (auto c : s) และ for (auto &c : s) ก็พอ ส่วนตัวแปรที่เก็บตัวเลข ให้เขียนชนิดเองเสมอ เหตุผลอยู่ในแบบที่ 3 ของส่วนลองทำให้พัง
new และ delete
C++ มีคำสั่งขอและคืนหน่วยความจำของตัวเอง ใช้แทน malloc กับ free ไม่ต้องคำนวณขนาดด้วย sizeof และไม่ต้อง cast แบบที่เจอในแบบที่ 3 ของหน้า 15.1
#include <bits/stdc++.h>
using namespace std;
struct Node { // (1)!
int value;
Node *next;
};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int *score = new int[n]; // (2)!
Node *head = nullptr;
for (int i = 0; i < n; i++) {
cin >> score[i];
Node *node = new Node; // (3)!
node->value = score[i];
node->next = head;
head = node;
}
for (Node *p = head; p != nullptr; p = p->next) {
cout << p->value << ' ';
}
cout << '\n';
cout << score[0] + score[n - 1] << '\n';
while (head != nullptr) {
Node *next_node = head->next;
delete head; // (4)!
head = next_node;
}
delete[] score; // (5)!
return 0;
}
- ใน C++ ชื่อ struct ใช้เป็นชื่อชนิดได้เลย ไม่ต้องมี
typedefและไม่ต้องเขียนstruct Nodeเต็มๆ ข้างในก็ได้ - ขออาเรย์
intขนาดnช่อง แทน(int *)malloc(n * sizeof(int)) - ขอ node หนึ่งตัว แทน
malloc(sizeof(Node))ในหน้า 14.1 ซึ่งถ้าย้ายมาเป็น C++ ต้องเติม cast เป็น(Node *)malloc(sizeof(Node))ตามหน้า 15.1 - คืน node ที่ได้จาก
newทีละตัว แทนfree - ของที่ขอเป็นอาเรย์ด้วย
new ... [n]ต้องคืนด้วยdelete[]มีวงเล็บเหลี่ยม
4
3 9 2 7
7 2 9 3
10
ในการแข่งขันใช้ new ไม่บ่อย อาเรย์ที่ไม่รู้ขนาดมักประกาศไว้นอก main ตามขนาดมากสุดในโจทย์ หรือใช้ vector ในบทที่ 16 ซึ่งยืดได้เองและคืนหน่วยความจำให้เอง ที่ยังเจออยู่คือการสร้าง node ของ linked list หรือต้นไม้ ให้รู้จักไว้พออ่านโค้ดคนอื่นออก
ลองทำให้พัง
แบบที่ 1 ส่งตัวเลขให้พารามิเตอร์ที่เป็น reference
#include <bits/stdc++.h>
using namespace std;
void add_bonus(int &score, int bonus)
{
score += bonus;
}
int main()
{
int math = 70;
add_bonus(math, 5);
add_bonus(75, 5);
cout << math << '\n';
return 0;
}
bonus.cpp:13:15: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
13 | add_bonus(75, 5);
| ^~
bonus.cpp:4:21: note: initializing argument 1 of 'void add_bonus(int&, int)'
4 | void add_bonus(int &score, int bonus)
| ~~~~~^~~~~
บรรทัดที่ 12 ผ่าน แต่บรรทัดที่ 13 ไม่ผ่าน reference ต้องเป็นชื่อเล่นของกล่องที่มีอยู่จริง 75 ไม่ใช่กล่อง จะบวกโบนัสเข้าไปเก็บที่ไหนก็ไม่ได้ rvalue ในข้อความหมายถึงค่าที่ไม่มีกล่องของตัวเองแบบนี้ ผลคำนวณอย่าง math + 1 ก็ส่งไม่ได้ด้วยเหตุผลเดียวกัน ส่วน note บรรทัดถัดมาชี้ไปที่พารามิเตอร์ตัวที่ทำให้เกิดปัญหา
แบบที่ 2 ลืม & ใน for แบบเดินทีละตัว
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string name;
cin >> name;
for (char c : name) {
c = toupper(c);
}
cout << name << '\n';
return 0;
}
somchai
somchai
ไม่มีอะไรเปลี่ยนเลย และไม่มีคำเตือนแม้เปิด -Wextra เพราะ c เป็นสำเนาของแต่ละตัว แก้สำเนาแล้วสำเนาก็หายไปตอนจบรอบ แบบเดียวกับฟังก์ชันที่ได้สำเนาในหน้า 8.2 ต้องการแก้ข้อความจริงต้องเป็น for (char &c : name)
แบบที่ 3 ใช้ auto กับผลรวม
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
auto total = 0;
for (int i = 0; i < n; i++) {
long long price;
cin >> price;
total += price;
}
cout << total << '\n';
return 0;
}
3
1000000000 1000000000 1000000000
-1294967296
ราคาอ่านเป็น long long ถูกแล้ว แต่ auto total = 0; ได้ชนิดจากเลข 0 ซึ่งเป็น int ผลรวมสามพันล้านจึงล้นแบบเดียวกับหน้า 1.3 ไม่มีคำเตือนสักบรรทัด auto ไม่ได้ดูว่าเราจะเอาตัวแปรไปเก็บอะไรในอนาคต ดูแค่ค่าเริ่มต้นเท่านั้น ตัวแปรที่เก็บตัวเลขให้เขียน long long total = 0; เองเสมอ
สรุป
boolเก็บtrueหรือfalseใช้ 1 bytecoutพิมพ์เป็น1กับ0int &r = x;คือชื่อที่สองของxพารามิเตอร์แบบint &aให้ฟังก์ชันแก้ตัวแปรของคนเรียกได้ เรียกด้วยชื่อตัวแปรตรงๆstringที่ส่งให้ฟังก์ชันอ่าน ส่งเป็นconst string &จะได้ไม่คัดลอกทั้งข้อความทุกครั้งfor (char c : s)ได้สำเนาทีละตัวfor (char &c : s)แก้ตัวจริงได้autoเลือกชนิดจากค่าเริ่มต้น ใช้ในลูปแบบเดินทีละตัวได้ แต่ตัวแปรเก็บตัวเลขให้เขียนชนิดเองnewกับdeleteใช้แทนmallocกับfreeอาเรย์ต้องคืนด้วยdelete[]
กลับไปเช็กรายการ ผ่านบทนี้เมื่อ แล้วไปทำแบบฝึกหัด