C++ Type Conversion
Link bài viết tham khảo
- https://www.w3schools.com/cpp//cpp_type_casting.asp
- https://www.geeksforgeeks.org/cpp/casting-operators-in-cpp/#1-static_cast
Trong C++ có 2 kiểu
- Implicit Conversion (automatically).
- Explicit Conversion (manually)
Ở đây tôi chỉ nói về Explicit Conversion
I. static_cast
Thường được sử dụng trong c++ để chuyển đổi những kiểu dữ liệu quen thuộc. Nó kiểm tra dữ liệu tại thời điểm biên dịch.
1. Chuyển đổi các kiểu dữ liệu cơ bản
double pi = 3.14159;
int _pi = static_cast<int>(pi);
cout << _pi;
2. Downcasting & Upcasting
Upcasting
- Rất an toàn khi ta chuyển một con trỏ từ lớp con lên lớp cha
#include <iostream>
#include <string>
class File {
public:
virtual void open() { std::cout << "Opening a standard file..." << std::endl; }
};
class EncryptedFile : public File {
public:
void open() override { std::cout << "Decrypting and opening file..." << std::endl; }
void getHeader() { std::cout << "Reading encryption header..." << std::endl; }
};
int main() {
// Khai báo một con trỏ ở lớp con
EncryptedFile* myFile = new EncryptedFile();
// Chuyển con trỏ từ lớp derived lên lớp base
File* basePtr = static_cast<File*>(myFile);
basePtr->open();
// basePtr->getHeader(); // Error compile : Lớp File không có hàm getHeader
return 0;
}
Output
Decrypting and opening file...
Downcasting
Không an toàn. Chuyển từ lớp base sang lớp derived. Chỉ nên dùng khi biết chắc đối tượng thực sự là lớp derived.
#include <iostream>
using namespace std;
// Base class
class Sensor {
public:
virtual void read() {
cout << "Reading generic sensor\n";
}
virtual ~Sensor() {}
};
// Derived class 1
class TemperatureSensor : public Sensor {
public:
void read() override {
cout << "Reading temperature\n";
}
void calibrate() {
cout << "Calibrating temperature sensor\n";
}
};
// Derived class 2
class PressureSensor : public Sensor {
public:
void read() override {
cout << "Reading pressure\n";
}
};
// Factory (giống kiểu driver create trong embedded)
Sensor* createSensor(int type) {
if (type == 1)
return new TemperatureSensor();
else
return new PressureSensor();
}
int main() {
// ✅ CASE 1: Downcast đúng
Sensor* s1 = createSensor(1); // thực sự là TemperatureSensor
TemperatureSensor* t1 = static_cast<TemperatureSensor*>(s1);
t1->read(); // OK
t1->calibrate(); // OK
cout << "------------------\n";
// =========================
// ❌ CASE 2: Downcast sai
// =========================
Sensor* s2 = createSensor(2); // thực sự là PressureSensor
// Ép sai kiểu
TemperatureSensor* t2 = static_cast<TemperatureSensor*>(s2);
t2->read();
t2->calibrate();
delete s1;
delete s2;
return 0;
}
Việc t2 được ép kiểu như vậy thì hên xui. Vì memory layout có thể khác khiến ta truy cập sai các trường bên trong
3. Chuyển qua lại với kiểu void *
int value = 100;
void* vPtr = static_cast<void*>(&value); // Lưu địa chỉ vào void*
// Khi cần dùng lại, phải ép về kiểu ban đầu
int* iPtr = static_cast<int*>(vPtr);
II. dynamic_cast
III. reinterpret_cast
Nó đơn giản chỉ là xem lại vùng memory theo gốc nhìn khác
- Không check typte
- Không quan tâm tính kế thừa
- Không quan tâm layout
- Chỉ quan tâm cách diễn giải các bit
Ví dụ
#include <iostream>
using namespace std;
int main() {
int a = 0x12345678;
char* p = reinterpret_cast<char*>(&a);
cout << hex << (int)p[0] << endl; // byte đầu tiên
}
//OUTPUT
78
Bài tập
#include <iostream>
#include <stdint.h>
#include <iomanip>
using namespace std;
#pragma pack(push, 1)
struct Packet {
uint8_t id;
uint16_t value;
uint32_t timestamp;
};
#pragma pack(pop)
alignas(Packet) uint8_t buffer[7] = {
0x01, // id
0x34, 0x12, // value (uint16_t)
0x78, 0x56, 0x34, 0x12 // timestamp (uint32_t)
};
int main() {
// 3. Cast trực tiếp
Packet *p = reinterpret_cast<Packet *>(buffer);
// 4. Print giá trị
cout << "id = 0x"
<< hex << setw(2) << setfill('0') << (int)p->id << endl;
cout << "value = 0x"
<< hex << setw(4) << setfill('0') << p->value << endl;
cout << "timestamp = 0x"
<< hex << setw(8) << setfill('0') << p->timestamp << endl;
cout << "sizeof(Packet) = " << sizeof(Packet) << endl;
// 5. Debug layout (rất quan trọng)
cout << "\n=== OFFSET ===" << endl;
cout << "id = " << offsetof(Packet, id) << endl;
cout << "value = " << offsetof(Packet, value) << endl;
cout << "timestamp = " << offsetof(Packet, timestamp) << endl;
// 6. Dump raw memory
cout << "\n=== RAW MEMORY ===" << endl;
for (int i = 0; i < sizeof(Packet); i++) {
printf("%02X ", ((uint8_t*)p)[i]);
}
cout << endl;
return 0;
}
// OUTPUT
id = 0x01
value = 0x1234
timestamp = 0x12345678
sizeof(Packet) = 7
=== OFFSET ===
id = 0
value = 1
timestamp = 3
=== RAW MEMORY ===
01 34 12 78 56 34 12
All rights reserved