🧩 ภาพรวม
Variable Scope (ขอบเขตของตัวแปร) + Variable Qualifiers (คุณสมบัติตัวแปร)
ใช้ได้ทั้ง Arduino UNO (C++) และ ESP32 (C++/FreeRTOS)
🔹 1. Variable Scope คืออะไร
Scope (ขอบเขต) หมายถึง “พื้นที่ในโปรแกรมที่สามารถเข้าถึงตัวแปรได้”
หรือพูดง่าย ๆ ว่า
ตัวแปรตัวนี้ “มองเห็นและใช้ได้” ที่ไหนบ้าง
🔸 ประเภทของ Scope
| ประเภท | อยู่ที่ไหน | อายุการใช้งาน | ใช้ได้ที่ไหน |
|---|---|---|---|
| Local Variable | ประกาศในฟังก์ชัน | เมื่อฟังก์ชันทำงาน → หายเมื่อออก | ใช้ได้เฉพาะในฟังก์ชันนั้น |
| Global Variable | ประกาศนอกฟังก์ชัน | อยู่ตลอดโปรแกรม | ใช้ได้ทุกฟังก์ชัน |
| Block Variable | ประกาศใน { ... } ภายใน | อยู่จนกว่าจะออกจากบล็อก | ใช้ได้เฉพาะในบล็อกนั้น |
| Static Local Variable | ประกาศในฟังก์ชัน + static | อยู่คงที่ตลอดโปรแกรม แต่ใช้ได้เฉพาะในฟังก์ชัน | เก็บค่าครั้งล่าสุดไว้แม้ออกจากฟังก์ชันแล้ว |
🔹 1.1 ตัวอย่าง Local Variable
void setup() {
int a = 10; // ตัวแปรภายใน setup()
Serial.begin(9600);
Serial.println(a); // ใช้ได้
}
void loop() {
// Serial.println(a); ❌ error: มองไม่เห็นตัวแปร a
}
ตัวแปร
aใช้ได้เฉพาะในsetup()เท่านั้น
🔹 1.2 ตัวอย่าง Global Variable
int counter = 0; // ประกาศนอกฟังก์ชัน (global)
void setup() {
Serial.begin(9600);
}
void loop() {
counter++; // ใช้ได้ทุกที่
Serial.println(counter); // แสดงค่าที่เพิ่มขึ้นเรื่อย ๆ
delay(1000);
}
Global อยู่ในหน่วยความจำตลอดเวลา ใช้ร่วมกันได้ทุกฟังก์ชัน
🔹 1.3 ตัวอย่าง Block Variable
void loop() {
if (true) {
int x = 5; // อยู่ใน block นี้เท่านั้น
Serial.println(x);
}
// Serial.println(x); ❌ ใช้ไม่ได้
}
🔹 1.4 ตัวอย่าง Static Local Variable
void blinkCounter() {
static int count = 0; // ค่าคงอยู่แม้ออกจากฟังก์ชัน
count++;
Serial.println(count);
}
void loop() {
blinkCounter(); // เรียกทุก 1 วินาที
delay(1000);
}
ผลลัพธ์:
1
2
3
4
...
ถ้าไม่ใส่
staticค่านับจะเริ่มใหม่ทุกครั้งที่เรียกฟังก์ชัน
🔹 2. การซ่อนตัวแปร (Variable Shadowing)
ถ้าประกาศชื่อเดียวกันทั้งใน local และ global → ตัวแปร “ในบล็อก” จะถูกใช้งานแทน
int num = 100; // global
void loop() {
int num = 5; // local ซ้อนชื่อกัน
Serial.println(num); // แสดง 5 (ไม่ใช่ 100)
}
ถ้าอยากเข้าถึง global จริง ๆ ให้ใช้
::เช่นSerial.println(::num);
🔹 3. การเก็บตัวแปรไว้ในหน่วยความจำ (Storage Duration)
| คีย์เวิร์ด | พื้นที่เก็บ | อายุของตัวแปร | ตัวอย่าง |
|---|---|---|---|
| (ปกติ) | Stack | หายเมื่อออกจากฟังก์ชัน | int x=0; |
static | Data Segment | อยู่ตลอดโปรแกรม | static int x=0; |
extern | Data Segment | อยู่ตลอดโปรแกรม (ประกาศไว้ที่อื่น) | extern int count; |
register | Register CPU (อาจจะจริงหรือไม่) | ชั่วคราวในฟังก์ชัน | register int i; |
🧠 4. Variable Qualifiers คืออะไร
Qualifiers คือ “คุณสมบัติพิเศษ” ที่บอก วิธีการจัดการ ตัวแปร เช่น
ไม่ให้เปลี่ยนค่า, อยู่ในหน่วยความจำพิเศษ, หรือแชร์ระหว่างไฟล์
🔸 ตัวอย่างตัวสำคัญ (ใช้ได้ทั้ง Arduino / ESP32)
| Qualifier | ความหมาย | ตัวอย่าง | หมายเหตุ |
|---|---|---|---|
const | ค่าคงที่ เปลี่ยนไม่ได้ | const int led = 2; | ปลอดภัย |
volatile | บอก compiler ว่าค่านี้ “อาจเปลี่ยนได้เอง” | volatile bool flag; | ใช้กับ interrupt |
static | คงอยู่ตลอดโปรแกรม แต่ใช้ได้เฉพาะขอบเขตนั้น | static int count; | นับครั้งเรียกฟังก์ชัน |
extern | ตัวแปรประกาศไว้ในไฟล์อื่น | extern int sensorValue; | ใช้ข้ามไฟล์ |
register | ขอให้เก็บในรีจิสเตอร์ CPU (เร็วขึ้น) | register byte i; | แนะนำให้ปล่อยให้ compiler จัดการเอง |
constexpr (ESP32/modern C++) | ค่าคงที่คำนวณได้ตอนคอมไพล์ | constexpr float PI = 3.14159; | เร็วมาก |
🔹 4.1 ตัวอย่าง volatile
ใช้เมื่อค่าของตัวแปร ถูกเปลี่ยนโดย ISR หรือฮาร์ดแวร์
เช่น ตัวแปรที่อัปเดตใน attachInterrupt()
volatile bool buttonPressed = false;
void IRAM_ATTR onPress() { // ISR
buttonPressed = true;
}
void setup() {
pinMode(15, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(15), onPress, FALLING);
}
void loop() {
if (buttonPressed) {
buttonPressed = false;
Serial.println("Button Interrupt!");
}
}
ถ้าไม่ใส่
volatile— Compiler อาจ optimize ทิ้ง เพราะไม่เห็นการเปลี่ยนค่าในloop()
🔹 4.2 ตัวอย่าง extern
ใช้เมื่อมีหลายไฟล์ (โปรเจกต์ใหญ่) เช่น:
ไฟล์ main.ino
#include "sensor.h"
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(sensorValue);
delay(1000);
}
ไฟล์ sensor.h
extern int sensorValue;
ไฟล์ sensor.cpp
int sensorValue = 42;
externช่วยให้ไฟล์หลายไฟล์ใช้ตัวแปรเดียวกันโดยไม่ประกาศซ้ำ
🔹 4.3 ตัวอย่าง static
void counter() {
static int n = 0;
n++;
Serial.println(n);
}
void loop() {
counter(); // จะนับเพิ่มเรื่อย ๆ
delay(500);
}
ตัวแปร
nจะไม่หายไปหลังจบฟังก์ชัน
🔹 4.4 ตัวอย่าง constexpr (ESP32 รองรับ)
constexpr float PI = 3.1415926;
constexpr int LED_PIN = 2;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
constexprเหมือนconstแต่คำนวณค่าได้ตั้งแต่ตอนคอมไพล์
ประสิทธิภาพสูง เหมาะกับโปรเจกต์ใหญ่ใน ESP32
🔹 5. ความแตกต่างของ “const” vs “#define”
| ประเด็น | #define | const |
|---|---|---|
| ประมวลผลเมื่อ | ก่อนคอมไพล์ (Preprocessor) | ระหว่างคอมไพล์ |
| มีชนิดข้อมูลไหม | ❌ ไม่มี | ✅ มี |
| ตรวจสอบข้อผิดพลาดได้ไหม | ❌ ไม่ได้ | ✅ ได้ |
| ใช้กับ pointer/struct ได้ไหม | ❌ ไม่ได้ | ✅ ได้ |
| ควรใช้ในโปรเจกต์ใหญ่ | ไม่แนะนำ | แนะนำมาก |
🔹 6. ตารางสรุป Scope + Qualifier
| ประเภท | ตัวอย่าง | อายุการใช้งาน | เข้าถึงได้จาก | หมายเหตุ |
|---|---|---|---|---|
| Local | int a=0; ในฟังก์ชัน | ชั่วคราว | ฟังก์ชันนั้น | หายเมื่อออกจากฟังก์ชัน |
| Global | int a=0; นอกฟังก์ชัน | ตลอดโปรแกรม | ทุกฟังก์ชัน | ใช้ระวังชนกัน |
| Static Local | static int c; | ตลอดโปรแกรม | ฟังก์ชันนั้น | จำค่าครั้งล่าสุดได้ |
| Const | const int LED=2; | ตลอดโปรแกรม | ทุกที่ (ตาม scope) | ป้องกันการแก้ค่า |
| Volatile | volatile int x; | ตลอดโปรแกรม | ทุกที่ | ใช้กับ interrupt |
| Extern | extern int x; | ตลอดโปรแกรม | ไฟล์อื่น | ใช้ข้ามไฟล์ |
| Register | register int i; | ชั่วคราว | ฟังก์ชัน | ปล่อย compiler จัดการเอง |
| constexpr | constexpr float PI=3.14; | ตอนคอมไพล์ | ทุกที่ | เร็วและปลอดภัย (ESP32) |
🎯 7. ตัวอย่างรวม Scope + Qualifier
int globalCount = 0; // Global
const int LED_PIN = 2; // Constant global
volatile bool flag = false; // ใช้ใน interrupt
void IRAM_ATTR onPress() { // Interrupt
flag = true;
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(15, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(15), onPress, FALLING);
Serial.begin(115200);
}
void loop() {
static int localCount = 0; // Static local
if (flag) {
flag = false;
localCount++;
globalCount++;
Serial.printf("Local=%d Global=%d\n", localCount, globalCount);
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
}
🧠 8. เพิ่มเติม
- ทดลอง “ประกาศตัวแปรชื่อเดียวกัน” ใน
setup()กับloop()
→ สังเกตว่า scope ต่างกัน - สร้างฟังก์ชัน “นับครั้งกดปุ่ม” ด้วย
static - ใช้
volatileกับ interrupt แล้วอธิบายว่าทำไมต้องใช้ - เขียนโปรแกรม 2 ไฟล์ และใช้
externร่วมกัน - ทดลอง
constvs#defineแล้วดู compile error
