Trước đây, bạn đã biết rằng một giá trị của một biến được lưu trữ dưới dạng một chuỗi các bit và kiểu dữ liệu của biến sẽ cho trình biên dịch biết cách diễn giải các bit đó thành các giá trị có nghĩa. Các kiểu dữ liệu khác nhau có thể đại diện cho cùng một số khác nhau – ví dụ: giá trị số nguyên 3 và giá trị thực 3.0 được lưu trữ dưới dạng các mẫu nhị phân hoàn toàn khác nhau.

Vậy điều gì sẽ xảy ra khi chúng ta làm điều gì đó như thế này?

float f{ 3 }; // initialize floating point variable with int 3

Trong trường hợp như vậy, trình biên dịch không thể chỉ sao chép các bit đại diện cho giá trị int 3 vào biến float f. Thay vào đó, nó cần chuyển giá trị số nguyên 3 thành số dấu phẩy động, sau đó có thể được gán cho biến float f.

Quá trình chuyển đổi một giá trị từ kiểu dữ liệu này sang kiểu dữ liệu khác được gọi là chuyển đổi kiểu. Chuyển đổi loại có thể xảy ra trong nhiều trường hợp khác nhau:

  • Khi gán hoặc khởi tạo một biến có giá trị thuộc kiểu dữ liệu khác:
double d{ 3 }; // initialize double variable with integer value 3
d = 6; // assign double variable the integer value 6
  • Khi truyền giá trị cho một hàm trong đó tham số hàm thuộc kiểu dữ liệu khác:
void doSomething(long l)
{
}
 
doSomething(3); // pass integer value 3 to a function expecting a long parameter
  • Khi trả về giá trị từ một hàm mà kiểu trả về của hàm là một kiểu dữ liệu khác:
float doSomething()
{
    return 3.0; // Return double value 3.0 back to caller through float return type
}
  • Sử dụng toán tử nhị phân với các toán hạng thuộc các kiểu khác nhau:
double division{ 4.0 / 3 }; // division with a double and an integer

Trong tất cả các trường hợp này (và khá nhiều trường hợp khác), C ++ sẽ sử dụng chuyển đổi kiểu để chuyển đổi một kiểu dữ liệu này sang kiểu dữ liệu khác.

Có hai loại chuyển đổi kiểu cơ bản: chuyển đổi kiểu ngầm định, trong đó trình biên dịch tự động chuyển đổi kiểu dữ liệu này thành kiểu dữ liệu khác và chuyển đổi kiểu rõ ràng, trong đó developer sử dụng toán tử truyền để chỉ đạo chuyển đổi.

Chúng ta sẽ đề cập đến chuyển đổi loại ngầm trong bài học này và chuyển đổi loại rõ ràng trong bài học tiếp theo.

1. Chuyển đổi kiểu ngầm định

Chuyển đổi kiểu ngầm định (còn được gọi là chuyển đổi kiểu tự động hoặc cưỡng chế) được thực hiện bất cứ khi nào một kiểu dữ liệu được mong đợi, nhưng một kiểu dữ liệu khác được cung cấp. Nếu trình biên dịch có thể tìm ra cách thực hiện chuyển đổi giữa hai loại, nó sẽ làm được. Nếu nó không biết cách thì nó sẽ không thành công với lỗi biên dịch.

Tất cả các ví dụ trên là các trường hợp mà chuyển đổi kiểu ngầm định sẽ được sử dụng.

Có hai loại chuyển đổi kiểu ngầm cơ bản: promotions(quảng cáo) và conversions.

2. promotions số

Bất cứ khi nào giá trị từ một kiểu dữ liệu cơ bản được chuyển đổi thành giá trị của kiểu dữ liệu cơ bản lớn hơn từ cùng một họ, thì điều này được gọi là thăng hạng số (hoặc mở rộng, mặc dù thuật ngữ này thường được dành cho số nguyên). Ví dụ: một int có thể được mở rộng thành một long hoặc một float được thăng cấp thành một double:

Trong khi thuật ngữ quảng cáo số bao gồm bất kỳ loại khuyến mại nào, có hai thuật ngữ khác có ý nghĩa cụ thể trong C ++:

  • Quảng cáo tích phân liên quan đến việc chuyển đổi các kiểu số nguyên hẹp hơn int (bao gồm bool, char, unsigned char, char có dấu, unsigned short và sign short) thành int (nếu có thể) hoặc int không dấu (nếu không).
  • Quảng cáo dấu phẩy động liên quan đến việc chuyển đổi số thực thành số kép.
  • Quảng cáo tích phân và thăng hạng dấu phẩy động được sử dụng trong các trường hợp cụ thể để chuyển đổi các kiểu dữ liệu nhỏ hơn thành int / unsigned int hoặc double, bởi vì int và double thường là những kiểu hoạt động hiệu quả nhất để thực hiện các phép toán.

Điều quan trọng cần nhớ về các chương trình khuyến mãi là chúng luôn an toàn và sẽ không bị mất dữ liệu.

Dành cho người học nâng cao

Về cơ bản, quảng cáo thường liên quan đến việc mở rộng biểu diễn nhị phân của một số (ví dụ: đối với số nguyên, thêm các số 0 ở đầu).

3. Chuyển đổi số

Khi chúng ta chuyển đổi giá trị từ một loại lớn hơn sang một loại nhỏ hơn tương tự hoặc giữa các loại khác nhau, đây được gọi là chuyển đổi số. Ví dụ:

double d{ 3 }; // convert integer 3 to a double (between different types)
short s{ 2 }; // convert integer 2 to a short (from larger to smaller type within same type family)

Không giống như các chương trình khuyến mại luôn an toàn, các chuyển đổi có thể dẫn đến mất dữ liệu hoặc không. Do đó, mã gây ra một chuyển đổi ngầm được thực hiện thường sẽ khiến trình biên dịch đưa ra cảnh báo.

Các quy tắc cho chuyển đổi rất phức tạp và nhiều, vì vậy chúng ta sẽ chỉ đề cập đến các trường hợp phổ biến ở đây.

Trong mọi trường hợp, việc chuyển đổi một giá trị thành một loại không có phạm vi đủ lớn để hỗ trợ giá trị sẽ dẫn đến kết quả không mong muốn. Ví dụ:

int main()
{
    int i{ 30000 };
    char c = i; // chars have range -128 to 127
 
    std::cout << static_cast<int>(c);
 
    return 0;
}

Trong ví dụ này, chúng ta đã gán một số nguyên lớn cho một char (có phạm vi -128 đến 127). Điều này làm cho char bị tràn và tạo ra kết quả không mong muốn:

48

Tuy nhiên, việc chuyển đổi từ kiểu tích phân hoặc dấu phẩy động lớn hơn sang kiểu tương tự nhỏ hơn nói chung sẽ hoạt động miễn là giá trị phù hợp với phạm vi của kiểu nhỏ hơn. Ví dụ:

 int i{ 2 };
    short s = i; // convert from int to short
    std::cout << s << '\n';
 
    double d{ 0.1234 };
    float f = d;
    std::cout << f << '\n';

Kết quả

2
0.1234

Trong trường hợp giá trị dấu phẩy động, một số làm tròn có thể xảy ra do mất độ chính xác trong kiểu nhỏ hơn. Ví dụ:

 float f = 0.123456789; // double value 0.123456789 has 9 significant digits, but float can only support about 7
    std::cout << std::setprecision(9) << f << '\n'; // std::setprecision defined in iomanip header

Trong trường hợp này, chúng ta thấy mất độ chính xác vì phao không thể giữ được độ chính xác nhiều như một đôi:

0.123456791

Chuyển đổi từ một số nguyên sang một số dấu phẩy động thường hoạt động miễn là giá trị phù hợp với phạm vi của kiểu số động. Ví dụ:

 int i{ 10 };
    float f = i;
    std::cout << f;

Kết quả

10

Việc chuyển đổi từ dấu phẩy động sang số nguyên hoạt động miễn là giá trị nằm trong phạm vi của số nguyên, nhưng bất kỳ giá trị phân số nào sẽ bị mất. Ví dụ:

int i = 3.5;
std::cout << i << '\n';

Trong ví dụ này, giá trị phân số (.5) bị mất, để lại kết quả sau:

3

Các chuyển đổi có thể gây mất thông tin, ví dụ: dấu chấm động thành số nguyên, được gọi là chuyển đổi thu hẹp. Vì việc mất thông tin nói chung là không mong muốn, việc khởi tạo dấu ngoặc nhọn không cho phép thu hẹp chuyển đổi.

double d{ 10.0 };
int i{ d }; // Error: A double can store values that don't fit into an int

4. Đánh giá biểu thức số học

Khi đánh giá biểu thức, trình biên dịch chia nhỏ từng biểu thức thành các biểu thức con riêng lẻ. Các toán tử số học yêu cầu các toán hạng của chúng phải cùng kiểu. Để đảm bảo điều này, trình biên dịch sử dụng các quy tắc sau:

Nếu một toán hạng là một số nguyên hẹp hơn một số nguyên, nó sẽ được thăng hạng tích phân (như được mô tả ở trên) thành int hoặc unsigned int.

Nếu các toán hạng vẫn không khớp, thì trình biên dịch sẽ tìm toán hạng ưu tiên cao nhất và chuyển đổi ngầm toán hạng khác để khớp.

Mức độ ưu tiên của các toán hạng như sau:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/
#include <iostream>
#include <typeinfo> // for typeid()
 
int main()
{
    short a{ 4 };
    short b{ 5 };
    std::cout << typeid(a + b).name() << ' ' << a + b << '\n'; // show us the type of a + b
 
    return 0;
}

Chúng ta có thể thấy quá trình chuyển đổi số học thông thường diễn ra thông qua việc sử dụng toán tử typeid (có trong header <typeinfo>), có thể được sử dụng để hiển thị kiểu kết quả của một biểu thức.

Trong ví dụ sau, chúng ta thêm hai short:

#include <iostream>
#include <typeinfo> // for typeid()
 
int main()
{
    double d{ 4.0 };
    short s{ 2 };
    std::cout << typeid(d + s).name() << ' ' << d + s << '\n'; // show us the type of d + s
 
    return 0;
}

Bởi vì short là số nguyên, chúng trải qua quảng cáo tích hợp thành int trước khi được thêm vào. Kết quả của việc thêm hai int là một int, như bạn mong đợi:

int 9

Lưu ý: Trình biên dịch của bạn có thể hiển thị một cái gì đó hơi khác, vì định dạng của typeid.name () được để cho trình biên dịch.

Chúng ta hãy xem xét một trường hợp khác:

#include <iostream>
#include <typeinfo> // for typeid()
 
int main()
{
    double d{ 4.0 };
    short s{ 2 };
    std::cout << typeid(d + s).name() << ' ' << d + s << '\n'; // show us the type of d + s
 
    return 0;
}

Trong trường hợp này, short được thăng hạng tích hợp thành int. Tuy nhiên, int và double vẫn không khớp nhau. Vì double cao hơn trên hệ thống phân cấp của các loại, số nguyên 2 được chuyển đổi thành double 2.0 và các số nhân đôi được thêm vào để tạo ra kết quả kép.

double 6.0

Hệ thống phân cấp này có thể gây ra một số vấn đề thú vị. Ví dụ: hãy xem đoạn code sau:

std::cout << 5u - 10; // 5u means treat 5 as an unsigned integer

bạn có thể mong đợi biểu thức 5u – 10 đánh giá thành -5 vì 5 – 10 = -5. Nhưng đây là những gì thực sự xảy ra:

4294967291

Trong trường hợp này, số nguyên có dấu 10 được thăng cấp thành số nguyên không dấu (có mức độ ưu tiên cao hơn) và biểu thức được đánh giá là số nguyên không dấu. Vì -5 không thể được lưu trữ trong một số nguyên chưa ký, nên phép tính kết thúc và chúng ta nhận được câu trả lời mà chúng ta không mong đợi.

Đây là một trong nhiều lý do tốt để tránh các số nguyên không dấu nói chung.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!