0

C++ Type Conversion

Link bài viết tham khảo

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí