Trong các bài học trước về số nguyên, chúng ta đã đề cập rằng C ++ chỉ đảm bảo rằng các biến số nguyên sẽ có kích thước tối thiểu – nhưng chúng có thể lớn hơn, tùy thuộc vào hệ thống chạy ứng dụng C++.

1. Tại sao kích thước của các biến số nguyên không cố định?

Câu trả lời ngắn gọn là điều này quay trở lại C, khi máy tính chạy chậm và hiệu suất là mối quan tâm hàng đầu. C đã chọn cố ý để mở kích thước của một số nguyên để trình triển khai trình biên dịch có thể chọn kích thước cho int hoạt động tốt nhất trên kiến ​​trúc máy tính đích.

2. Điều này không tệ sao?

Theo tiêu chuẩn hiện đại, có. Là một lập trình viên, hơi nực cười khi phải đối mặt với các loại có phạm vi không chắc chắn. Một chương trình sử dụng nhiều hơn phạm vi được đảm bảo tối thiểu có thể hoạt động trên một kiến ​​trúc nhưng không hoạt động trên một kiến ​​trúc khác.

3. Số nguyên có độ rộng cố định

Để giúp mang lại tính di động trên nhiều nền tảng, C99 đã xác định một tập hợp các số nguyên có chiều rộng cố định (trong header stdint.h) được đảm bảo có cùng kích thước trên bất kỳ kiến ​​trúc nào.

Chúng được định nghĩa như sau:

TênKiểuphạm vi độ lớnGhi chú
std::int8_t1 byte signed-128 to 127Được đối xử như một kiểu char có dấu trên nhiều hệ thống. 
std::uint8_t1 byte unsigned0 to 255Được đối xử như một ký tự không dấu trên nhiều hệ thống.
std::int16_t2 byte signed-32,768 to 32,767
std::uint16_t2 byte unsigned0 to 65,535
std::int32_t4 byte signed-2,147,483,648 to 2,147,483,647
std::uint32_t4 byte unsigned0 to 4,294,967,295
std::int64_t8 byte signed-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
std::uint64_t8 byte unsigned0 to 18,446,744,073,709,551,615

C ++ đã chính thức sử dụng các số nguyên có độ rộng cố định này như một phần của C ++ 11. Chúng có thể được truy cập bằng cách include header cstdint, nơi chúng được khai báo bên trong namespace  std. Đây là một ví dụ:

#include <cstdint>
#include <iostream>
 
int main()
{
    std::int16_t i{5};
    std::cout << i;
    return 0;
}

Các số nguyên có độ rộng cố định có hai nhược điểm: Thứ nhất, chúng là tùy chọn và chỉ tồn tại nếu có các kiểu cơ bản phù hợp với độ rộng của chúng và tuân theo một biểu diễn nhị phân nhất định. Sử dụng một số nguyên có độ rộng cố định làm cho code của bạn ít di động hơn, nó có thể không được biên dịch trên các hệ thống khác.

Thứ hai, nếu bạn sử dụng một số nguyên có chiều rộng cố định, nó cũng có thể chậm hơn một kiểu rộng hơn trên một số kiến ​​trúc. Nếu bạn cần một số nguyên để giữ các giá trị từ -10 đến 20, bạn có thể muốn sử dụng std :: int8_t. Nhưng CPU của bạn có thể xử lý số nguyên rộng 32 bit tốt hơn, vì vậy bạn chỉ bị mất tốc độ khi đưa ra một hạn chế không cần thiết.

Lưu ý: Nên tránh các số nguyên có chiều rộng cố định ở trên, vì chúng có thể không được xác định trên tất cả các kiến ​​trúc đích.

4. Số nguyên nhanh và ít nhất

Để giúp giải quyết những nhược điểm trên, C ++ cũng định nghĩa hai tập hợp số nguyên thay thế.

Kiểu nhanh (std :: int_fast # _t) cung cấp kiểu số nguyên có dấu nhanh nhất với độ rộng ít nhất là # bit (trong đó # = 8, 16, 32 hoặc 64). Ví dụ: std :: int_fast32_t sẽ cung cấp cho bạn kiểu số nguyên có dấu nhanh nhất có ít nhất 32 bit.

Kiểu nhỏ nhất (std :: int_least # _t) cung cấp kiểu số nguyên có dấu nhỏ nhất với độ rộng ít nhất là # bit (trong đó # = 8, 16, 32 hoặc 64). Ví dụ: std :: int_least32_t sẽ cung cấp cho bạn kiểu số nguyên có dấu nhỏ nhất có ít nhất 32 bit.

Dưới đây là một ví dụ từ Visual Studio của tác giả (ứng dụng bảng điều khiển 32-bit):

/*
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 <cstdint>
#include <iostream>
 
int main()
{
	std::cout << "fast 8: " << sizeof(std::int_fast8_t) * 8 << " bits\n";
	std::cout << "fast 16: " << sizeof(std::int_fast16_t) * 8 << " bits\n";
	std::cout << "fast 32: " << sizeof(std::int_fast32_t) * 8 << " bits\n";
 
	std::cout << "least 8: " << sizeof(std::int_least8_t) * 8 << " bits\n";
	std::cout << "least 16: " << sizeof(std::int_least16_t) * 8 << " bits\n";
	std::cout << "least 32: " << sizeof(std::int_least32_t) * 8 << " bits\n";
 
	return 0;
}

Điều này tạo ra kết quả:

fast 8: 8 bits
fast 16: 32 bits
fast 32: 32 bits
least 8: 8 bits
least 16: 16 bits
least 32: 32 bits

Bạn có thể thấy rằng std :: int_fast16_t là 32 bit, trong khi std :: int_least16_t là 16 bit.

Ngoài ra còn có một tập hợp các kiểu nhanh và ít không dấu (std :: uint_fast # _t và std :: uint_least # _t).

Những loại nhanh và ít nhất này được đảm bảo là đã được khai báo và an toàn khi sử dụng.

Bạn nên

Ưu tiên các số nguyên std :: int_fast # _t và std :: int_least # _t khi bạn cần một số nguyên được đảm bảo có ít nhất một kích thước tối thiểu nhất định.

Cảnh báo: std :: int8_t và std :: uint8_t có thể hoạt động giống như ký tự thay vì số nguyên

Do có sự giám sát trong đặc tả C ++, hầu hết các trình biên dịch khai báo và xử lý std :: int8_t và std :: uint8_t (và các kiểu có chiều rộng cố định nhanh và ít nhất tương ứng) giống hệt với các kiểu char có dấu và char không dấu tương ứng. Do đó, std :: cin và std :: cout có thể hoạt động khác với những gì bạn mong đợi. Đây là một chương trình mẫu hiển thị điều này:

#include <cstdint>
#include <iostream>
 
int main()
{
    std::int8_t myint{65};
    std::cout << myint;
 
    return 0;
}

Trên hầu hết các hệ thống, chương trình này sẽ in ‘A’ (coi myint như một ký tự). Tuy nhiên, trên một số hệ thống, điều này có thể in 65.

Để đơn giản, tốt nhất nên tránh hoàn toàn std :: int8_t và std :: uint8_t (và các loại nhanh và ít liên quan) (sử dụng std :: int16_t hoặc std :: uint16_t để thay thế). Tuy nhiên, nếu bạn sử dụng std :: int8_t hoặc std :: uint8_t, bạn nên cẩn thận với bất kỳ thứ gì sẽ diễn ra std :: int8_t hoặc std :: uint8_t dưới dạng ký tự thay vì số nguyên (điều này bao gồm std :: cout và std :: cin).

Lưu ý

Tránh các loại số nguyên có độ rộng cố định 8 bit. Nếu bạn sử dụng chúng, hãy lưu ý rằng chúng thường được coi như ký tự.

Các cách sử dụng hay nhất về số nguyên

Bây giờ các số nguyên có độ rộng cố định đã được thêm vào C ++, cách sử dụng hay nhất cho các số nguyên trong C ++ như sau:

  • int nên được ưu tiên khi kích thước của số nguyên không quan trọng (ví dụ: số sẽ luôn nằm trong phạm vi của số nguyên có dấu 2 byte). Ví dụ: nếu bạn yêu cầu người dùng nhập tuổi của họ hoặc đếm từ 1 đến 10, thì không thành vấn đề liệu int là 16 hay 32 bit (các con số sẽ phù hợp với cả hai cách). Điều này sẽ bao gồm phần lớn các trường hợp mà bạn có thể gặp phải.
  • Nếu bạn cần một biến được đảm bảo là một kích thước cụ thể và muốn tăng hiệu suất, hãy sử dụng std :: int_fast # _t.
  • Nếu bạn cần một biến được đảm bảo có kích thước cụ thể và muốn bảo toàn bộ nhớ hơn hiệu suất, hãy sử dụng std :: int_least # _t. Điều này được sử dụng thường xuyên nhất khi phân bổ nhiều biến.

Tránh những điều sau nếu có thể:

  • Kiểu không dấu, trừ khi bạn có lý do thuyết phục.
  • Các kiểu số nguyên có độ rộng cố định 8 bit.
  • Mọi số nguyên có độ rộng cố định dành riêng cho trình biên dịch – ví dụ: Visual Studio xác định __int8, __int16, v.v.

5. Std :: size_t là gì?

Hãy xem xét đoạn code sau:

#include <iostream>
 
int main()
{
    std::cout << sizeof(int) << '\n';
 
    return 0;
}

trên máy của mình, bản in này:

4

Khá đơn giản phải không? Chúng ta có thể suy ra rằng toán tử sizeof trả về một giá trị nguyên – nhưng giá trị đó là kiểu số nguyên nào? Một int? Một đoạn ngắn? Câu trả lời là sizeof (và nhiều hàm trả về giá trị kích thước hoặc chiều dài) trả về giá trị kiểu std :: size_t. std :: size_t được định nghĩa là một kiểu tích phân không dấu và nó thường được sử dụng để biểu thị kích thước hoặc chiều dài của các đối tượng.

Thật thú vị, chúng ta có thể sử dụng toán tử sizeof (trả về giá trị kiểu std :: size_t) để yêu cầu kích thước của chính std :: size_t:

#include <cstddef> // std::size_t
#include <iostream>
 
int main()
{
	std::cout << sizeof(std::size_t) << '\n';
 
	return 0;
}

Được biên soạn dưới dạng ứng dụng bảng điều khiển 32 bit (4 byte) trên hệ thống của mình, bản in này:

4

Giống như một số nguyên có thể thay đổi kích thước tùy thuộc vào hệ thống, std :: size_t cũng khác nhau về kích thước. std :: size_t được đảm bảo là không có dấu và ít nhất 16 bit, nhưng trên hầu hết các hệ thống sẽ tương đương với chiều rộng địa chỉ của ứng dụng. Nghĩa là, đối với các ứng dụng 32 bit, std :: size_t thường sẽ là một số nguyên không dấu 32 bit và đối với ứng dụng 64 bit, size_t thường sẽ là một số nguyên không dấu 64 bit. size_t được định nghĩa là đủ lớn để chứa kích thước của đối tượng lớn nhất có thể tạo trên hệ thống của bạn (tính bằng byte). Ví dụ: nếu std :: size_t rộng 4 byte, thì đối tượng lớn nhất có thể tạo trên hệ thống của bạn không được lớn hơn 4,294,967,295 byte, vì đây là giá trị cao nhất mà số nguyên không dấu 4 byte có thể lưu trữ. Đây chỉ là giới hạn trên cùng của kích thước đối tượng, giới hạn kích thước thực có thể thấp hơn tùy thuộc vào trình biên dịch bạn đang sử dụng.

Theo định nghĩa, bất kỳ đối tượng nào lớn hơn giá trị lớn nhất mà size_t có thể chứa đều được coi là không đúng hình thức (và sẽ gây ra lỗi biên dịch), vì toán tử sizeof sẽ không thể trả về kích thước không đúng thực tế hiện tại.

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!