Biến
Kiểu dữ liệu
Hệ thống kiểu của C++ gồm các phần sau:
- Kiểu cơ bản (trong ngoặc là từ khóa/kiểu đại diện)
- Kiểu vô loại/
void(void) - (Từ C++11) kiểu con trỏ rỗng (
std::nullptr_t) - Kiểu số học
- Kiểu số nguyên (
int) - Kiểu bool (
bool) - Kiểu ký tự (
char) - Kiểu số thực (
float,double)
- Kiểu số nguyên (
- Kiểu vô loại/
- Kiểu phức hợp2
Kiểu bool
Một biến kiểu bool chỉ có thể là true hoặc false.
Thông thường, một biến bool chiếm \(1\) byte (thường \(1\) byte = \(8\) bit).
Mẹo
Có thể dùng macro CHAR_BIT trong <climits>(C++)/<limits.h>(C) để biết số bit mỗi byte.
Kiểu bool trong C
Xem thêm Khác biệt giữa C++ và các ngôn ngữ thường dùng - bool.
Ban đầu C không có kiểu bool; đến C99 mới thêm từ khóa _Bool làm kiểu bool, được coi là kiểu số nguyên không dấu.
Ghi chú
Từ C23, kiểu bool không còn dùng quy ước 0/khác 0, mà là kiểu đủ để chứa hai hằng true và false.
Để tiện dùng, stdbool.h cung cấp 3 macro bool,true,false như sau:
1 2 3 | |
Các macro này bị loại bỏ trong C23, và từ C23 đưa true,false,bool thành từ khóa, đồng thời vẫn giữ _Bool như một cách viết thay thế1.
Ngoài ra, từ C23 còn có thể dùng macro BOOL_WIDTH trong <limits.h> để lấy độ rộng bit của kiểu bool.
Kiểu số nguyên
Dùng để lưu số nguyên. Kiểu số nguyên cơ bản nhất là int.
Lưu ý
Do lịch sử, trong C++ kiểu bool và kiểu ký tự được xem là các kiểu số nguyên đặc biệt.
Trong hầu hết mọi trường hợp, không nên dùng các kiểu ký tự (trừ signed char và unsigned char) như kiểu số nguyên.
Các kiểu số nguyên thường có 5 mức theo độ rộng: char,short,int,long,long long.
C++ đảm bảo 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
Do lịch sử, độ rộng bit của số nguyên có nhiều mô hình phổ biến; để giải quyết, C99/C++11 đưa ra kiểu số nguyên định rộng.
Kích thước kiểu int
Chuẩn C++ quy định int có ít nhất \(16\) bit.
Thực tế, trên đa số nền tảng hiện đại, int là \(32\) bit.
Với từ khóa int, có thể dùng các từ khóa sửa đổi sau:
Dấu:
signed: số nguyên có dấu (mặc định);unsigned: số nguyên không dấu.
Kích thước:
short: ít nhất \(16\) bit;long: ít nhất \(32\) bit;- (Từ C++11)
long long: ít nhất \(64\) bit.
Bảng sau là độ rộng và phạm vi trong trường hợp phổ biến (một số nền tảng có thể khác):
| Tên kiểu | Kiểu tương đương | Độ rộng (chuẩn C++) | Phổ biến | Hiếm gặp |
|---|---|---|---|---|
signed char |
signed char |
\(8\) | - | - |
unsigned char |
unsigned char |
\(8\) | - | - |
short,short int,signed short,signed short int |
short int |
\(\geq 16\) | \(16\) | - |
unsigned short,unsigned short int |
unsigned short int |
\(\geq 16\) | \(16\) | - |
int,signed,signed int |
int |
\(\geq 16\) | \(32\) | \(16\) (thường gặp ở Win16 API) |
unsigned,unsigned int |
unsigned int |
\(\geq 16\) | \(32\) | \(16\) (thường gặp ở Win16 API) |
long,long int,signed long,signed long int |
long int |
\(\geq 32\) | \(32\) | \(64\) (thường gặp ở Linux/macOS 64-bit) |
unsigned long,unsigned long int |
unsigned long int |
\(\geq 32\) | \(32\) | \(64\) (thường gặp ở Linux/macOS 64-bit) |
long long,long long int,signed long long,signed long long int |
long long int |
\(\geq 64\) | \(64\) | - |
unsigned long long,unsigned long long int |
unsigned long long int |
\(\geq 64\) | \(64\) | - |
Với độ rộng \(x\), phạm vi của kiểu có dấu là \(-2^{x-1}\sim 2^{x-1}-1\)7, và kiểu không dấu là \(0 \sim 2^x-1\). Cụ thể:
| Độ rộng | Phạm vi |
|---|---|
| \(8\) | Có dấu: \(-2^{7}\sim 2^{7}-1\), Không dấu: \(0 \sim 2^{8}-1\) |
| \(16\) | Có dấu: \(-2^{15}\sim 2^{15}-1\), Không dấu: \(0 \sim 2^{16}-1\) |
| \(32\) | Có dấu: \(-2^{31}\sim 2^{31}-1\), Không dấu: \(0 \sim 2^{32}-1\) |
| \(64\) | Có dấu: \(-2^{63}\sim 2^{63}-1\), Không dấu: \(0 \sim 2^{64}-1\) |
Cách viết kiểu tương đương
Khi không gây nhập nhằng, có thể bỏ bớt từ khóa sửa đổi hoặc đổi thứ tự.
Ví dụ int,signed,int signed,signed int là cùng một kiểu; unsigned long và unsigned long int là cùng một kiểu.
Một số trình biên dịch có kiểu số nguyên mở rộng, ví dụ GCC có số nguyên 128-bit: __int128_t (có dấu) và __uint128_t (không dấu). Nếu muốn dùng trong thi đấu, hãy đọc kỹ quy định để biết có được phép hay không.
Lưu ý
STL không nhất thiết hỗ trợ tốt kiểu số nguyên mở rộng, nên cần đặc biệt cẩn thận.
Mã ví dụ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Các vấn đề trong ví dụ:
__int128_t f3(__int128_t)dùng hàm trị tuyệt đối kiểu C với chữ kýint abs(int), nênnbị ép vềinttrước rồi mới gọiabs.__int128_t f4(__int128_t)dùngstd::abskiểu C++, nhưng không có overload__int128_t std::abs(__int128_t), nên không biên dịch được.- I/O dạng stream của C++ không hỗ trợ
__int128_tvà__uint128_t.
Một cách khắc phục:
Mã sửa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Kiểu ký tự
Chia thành “ký tự hẹp” và “ký tự rộng”; do thi đấu thuật toán gần như không dùng ký tự rộng, nên chỉ giới thiệu ký tự hẹp.
Ký tự hẹp thường có \(8\) bit; thực chất lưu trữ như số nguyên, thường dùng ASCII để ánh xạ ký tự ↔ số nguyên:
signed char: ký tự có dấu, phạm vi \(-128 \sim 127\).unsigned char: ký tự không dấu, phạm vi \(0 \sim 255\).-
charcó cùng biểu diễn và căn chỉnh với một trong hai kiểu trên, nhưng là một kiểu độc lập.Dấu của
charphụ thuộc trình biên dịch và nền tảng: ARM/PowerPC thường mặc định không dấu, x86/x64 thường có dấu.GCC có thể thêm
-fsigned-charhoặc-funsigned-charđể buộccharthànhsigned charhoặcunsigned char; trình biên dịch khác xem tài liệu. Lưu ý thay đổi dấu khác mặc định có thể phá ABI và làm chương trình không chạy đúng.
Lưu ý
Khác với các kiểu số nguyên khác, char、signed char、unsigned char là ba kiểu khác nhau.
Thông thường signed char,unsigned char không nên dùng để lưu ký tự, đa số trường hợp chúng được xem như kiểu số nguyên.
Kiểu số thực
Dùng để lưu “số thực” (thực ra là xấp xỉ theo quy tắc nhất định), gồm:
float: đơn chính xác. Nếu hỗ trợ thì khớp IEEE-754 binary32.double: kép chính xác. Nếu hỗ trợ thì khớp IEEE-754 binary64.long double: chính xác mở rộng. Nếu hỗ trợ thì khớp IEEE-754 binary128; nếu không thì có thể khớp binary64 mở rộng; nếu không nữa thì là một định dạng mở rộng không theo IEEE-754 nhưng tốt hơn/ít nhất ngang binary64; nếu không thì khớp binary64.
| Định dạng | Độ rộng | Số dương lớn nhất | Số chữ số chính xác |
|---|---|---|---|
| IEEE-754 binary32 | \(32\) | \(3.4\times 10^{38}\) | \(6\sim 9\) |
| IEEE-754 binary64 | \(64\) | \(1.8\times 10^{308}\) | \(15\sim 17\) |
| IEEE-754 binary64 mở rộng | \(\geq 80\) | \(\geq 1.2\times 10^{4932}\) | \(\geq 18\sim 21\) |
| IEEE-754 binary128 | \(128\) | \(1.2\times 10^{4932}\) | \(33\sim 36\) |
Số âm nhỏ nhất (theo nghĩa giá trị) của IEEE-754 là đối của số dương lớn nhất.
Vì float có phạm vi nhỏ và độ chính xác thấp, thực tế thường dùng double.
Ngoài ra, số thực hỗ trợ các giá trị đặc biệt:
- Vô cùng (dương/âm):
INFINITY. - Âm 0:
-0.0, ví dụ1.0 / 0.0 == INFINITY,1.0 / -0.0 == -INFINITY. - NaN (Not a Number):
std::nan,NAN, thường sinh từ0.0 / 0.0… Nó không bằng bất kỳ giá trị nào (kể cả chính nó); từ C++11 có thể dùngstd::isnanđể kiểm tra.
Kiểu vô loại
void là kiểu vô loại, không thể khai báo biến kiểu void. Tuy nhiên, hàm có thể trả về void để chỉ ra không có giá trị trả về.
Kiểu con trỏ rỗng
Xem phần con trỏ rỗng.
Kiểu số nguyên định rộng
Từ C++11 hỗ trợ số nguyên định rộng:
<cstdint>: cung cấp kiểu định rộng và macro max/min…<cinttypes>: macro định dạng chostd::fprintf/std::fscanf.
Có các nhóm:
intN_t: có độ rộng chính xác \(N\) bit, ví dụint32_t.int_fastN_t: có độ rộng ít nhất \(N\) bit và nhanh nhất.int_leastN_t: có độ rộng ít nhất \(N\) bit và nhỏ nhất.
Bản không dấu thêm tiền tố u, ví dụ uint32_t,uint_least8_t.
Chuẩn yêu cầu 16 kiểu sau:
int_fast8_t,int_fast16_t,int_fast32_t,int_fast64_t,
int_least8_t,int_least16_t,int_least32_t,int_least64_t,
uint_fast8_t,uint_fast16_t,uint_fast32_t,uint_fast64_t,
uint_least8_t,uint_least16_t,uint_least32_t,uint_least64_t.
Đa số trình biên dịch còn có 8 kiểu:
int8_t,int16_t,int32_t,int64_t,
uint8_t,uint16_t,uint32_t,uint64_t.
Nếu có kiểu tương ứng, chuẩn yêu cầu macro max/min/bit-width theo dạng bỏ _t, viết hoa và thêm hậu tố:
_MAXlà giá trị lớn nhất, ví dụINT32_MAX._MINlà giá trị nhỏ nhất, ví dụINT32_MIN.
Lưu ý
Kiểu định rộng là alias của kiểu nguyên thường, nên trộn lẫn có thể ảnh hưởng tính đa nền tảng, ví dụ:
Mã ví dụ
1 2 3 4 5 6 7 8 9 10 11 | |
int64_t trên Windows 64-bit thường là long long int, còn trên Linux 64-bit thường là long int, nên đoạn code này sẽ không biên dịch trên GCC/Linux 64-bit, nhưng lại biên dịch được trên MSVC/Windows 64-bit vì std::max yêu cầu hai tham số cùng kiểu.
Ngoài ra, từ C++17, <limits> cung cấp std::numeric_limits để truy vấn thuộc tính kiểu số học: max/min, có dấu hay không, v.v.
1 2 3 4 5 6 7 8 9 | |
Chuyển đổi kiểu
Đôi khi ta cần chuyển một kiểu sang kiểu khác (ví dụ hàm nhận int nhưng truyền double). Cơ chế chuyển đổi kiểu trong C++ khá phức tạp; ở đây chỉ giới thiệu hai loại cho kiểu cơ bản: nâng kiểu và chuyển kiểu số.
Nâng kiểu
Nâng kiểu giữ nguyên giá trị.
Ghi chú
Biến tham số kiểu C với dấu ... sẽ tự động nâng kiểu khi truyền. Ví dụ:
Mã ví dụ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Khi gọi test, f được nâng lên double, nên nội dung lưu trữ giống fd, kết quả là:
1 2 3 | |
Nếu đổi double xx = va_arg(valist, double); thành float xx = va_arg(valist, float);, GCC thường cảnh báo:
1 2 3 4 5 6 7 | |
Khi đó chương trình sẽ dừng trước khi in kết quả.
Điều này cũng giải thích vì sao %f của printf có thể khớp cả float và double.
Nâng kiểu số nguyên
Các prvalue số nguyên nhỏ (như char) có thể nâng lên kiểu lớn hơn (như int).
Cụ thể, toán tử số học không nhận kiểu nhỏ hơn int làm toán hạng; sau chuyển lvalue-to-rvalue, nếu áp dụng được thì sẽ tự động nâng kiểu số nguyên.
Quy tắc:
signed char,signed short / shortcó thể nâng lênint.unsigned char,unsigned shortnếuintbao trùm được phạm vi nguồn thì nâng lênint, ngược lại nâng lênunsigned int(từ C++20,char8_tcũng theo quy tắc này).charphụ thuộc kiểu nền tảng làsigned charhayunsigned char.boolnâng lênint:false→0,true→1.- Nếu kiểu đích bao trùm kiểu nguồn, và phạm vi nguồn không được
int/unsigned intbao trùm, thì có thể nâng lên kiểu đích.3
Lưu ý
char->short không phải nâng kiểu số, vì char ưu tiên nâng lên int/unsigned int, rồi mới từ int/unsigned int -> short, không thỏa điều kiện nâng kiểu.
Ví dụ (giả sử int 32 bit, unsigned short 16 bit, signed char/unsigned char 8 bit, bool 1 bit):
(signed char)'\0' - (signed char)'\xff'nâng thành(int)0và(int)-1, kết quả(int)1.(unsigned char)'\0' - (unsigned char)'\xff'nâng thành(int)0và(int)255, kết quả(int)-255.false - (unsigned short)12nâng thành(int)0và(int)12, kết quả(int)-12.
Nâng kiểu số thực
Số thực nhỏ hơn có thể nâng lên số thực lớn hơn (ví dụ khi float và double tính toán, float được nâng lên double), giá trị không đổi.
Chuyển đổi số
Chuyển đổi số có thể làm thay đổi giá trị.
Lưu ý
Nâng kiểu có ưu tiên cao hơn chuyển đổi số. Ví dụ bool->int là nâng kiểu chứ không phải chuyển đổi số.
Chuyển đổi số nguyên
-
Nếu kiểu đích là số nguyên không dấu độ rộng \(x\), kết quả là giá trị gốc \(\bmod 2^x\).
-
Nếu độ rộng đích lớn hơn nguồn:
-
Nếu nguồn có dấu, thường cần mở rộng bit dấu rồi chuyển đổi.
Ví dụ:
(short)-1((short)0b1111'1111'1111'1111) sangunsigned int: mở rộng dấu thành0b1111'1111'1111'1111'1111'1111'1111'1111, rồi chuyển, kết quả(unsigned int)4'294'967'295(0b1111'1111'1111'1111'1111'1111'1111'1111).(short)32'767(0b0111'1111'1111'1111) sangunsigned int: mở rộng dấu thành0b0000'0000'0000'0000'0111'1111'1111'1111, kết quả(unsigned int)32'767.
-
Nếu nguồn không dấu, cần mở rộng 0 rồi chuyển đổi.
Ví dụ
(unsigned short)65'535(0b1111'1111'1111'1111) sangunsigned int: mở rộng 0 thành0b0000'0000'0000'0000'1111'1111'1111'1111, kết quả(unsigned int)65'535.
-
-
Nếu độ rộng đích không lớn hơn nguồn, cần cắt bớt rồi chuyển.
Ví dụ
(unsigned int)4'294'967'295(0b1111'1111'1111'1111'1111'1111'1111'1111) sangunsigned short: cắt còn0b1111'1111'1111'1111, kết quả(unsigned short)65'535.
-
-
Nếu kiểu đích là số nguyên có dấu độ rộng \(x\), thông thường có thể coi kết quả là \(\bmod 2^x\).4
Ví dụ
(unsigned int)4'294'967'295sangshortcho(short)-1(0b1111'1111'1111'1111). -
Nếu kiểu đích là
boolthì là chuyển đổi bool. -
Nếu nguồn là
bool,false→ 0,true→ 1 trong kiểu đích.
Chuyển đổi số thực
Số thực lớn sang số thực nhỏ sẽ làm tròn về giá trị gần nhất trong kiểu đích.
Chuyển đổi giữa số thực và số nguyên
-
Số thực sang số nguyên: bỏ phần thập phân.
Nếu đích là
bool, thì là chuyển đổi bool. -
Số nguyên sang số thực: làm tròn đến giá trị gần nhất trong kiểu đích.
Nếu không biểu diễn được trong kiểu đích, hành vi không xác định.
Nếu nguồn là
bool,false→ 0,true→ 1.
Chuyển đổi bool
Chuyển kiểu khác sang bool: giá trị 0 → false, khác 0 → true.
Định nghĩa biến
Nói đơn giản5, định nghĩa biến cần có kiểu (type specifier) và tên biến.
Ví dụ:
1 2 3 | |
Trong các đoạn chương trình thường gặp, biến định nghĩa trong dấu {} là biến cục bộ, còn ngoài {} là biến toàn cục. Có ngoại lệ, nhưng hiện chưa cần quan tâm.
Biến toàn cục không khởi tạo sẽ tự về 0. Biến cục bộ thì không, cần gán giá trị ban đầu nếu không dễ sinh bug.
Phạm vi biến
Phạm vi là khối mã mà biến có hiệu lực.
Biến toàn cục có phạm vi từ nơi định nghĩa6 đến hết file.
Biến cục bộ có phạm vi từ nơi định nghĩa đến hết khối lệnh.
Một khối lệnh là các câu lệnh được bao bởi {}.
1 2 3 4 5 6 7 | |
Nếu trong khối lồng nhau có biến trùng tên, biến ở khối trong sẽ che biến ở khối ngoài.
Ví dụ trên sẽ in ra \(g = 10\). Vì vậy nên tránh đặt tên biến cục bộ trùng tên biến toàn cục.
Hằng số
Hằng số là giá trị cố định, không thay đổi trong quá trình chạy.
Hằng số không thể bị sửa; thêm const khi định nghĩa.
1 2 | |
Nếu sửa hằng, trình biên dịch báo lỗi: error: assignment of read-only variable‘a’.
Tài liệu tham khảo và chú thích
- Working Draft, Standard for Programming Language C++
- Type - cppreference.com
- Arithmetic types (C) - cppreference.com
- Fundamental types - cppreference.com
- Fixed-width integer types (since C++11) - cppreference.com
- William Kahan (1 October 1997)."Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic".
- Implicit conversions - cppreference.com
- Declarations - cppreference
- Scope - cppreference.com
-
Xem https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf ↩
-
Gồm kiểu mảng, tham chiếu, con trỏ, lớp, hàm... Bài này dành cho người mới nên không trình bày chi tiết; xem Type - cppreference.com ↩
-
Không bao gồm kiểu ký tự rộng, bit-field và enum; xem Integral conversion - cppreference. ↩
-
Có hiệu lực từ C++20. Trước C++20 là phụ thuộc hiện thực. Xem Integral conversion - cppreference. ↩
-
Khi định nghĩa biến, ngoài type specifier còn có thể có các specifier khác. Xem Declarations - cppreference. ↩
-
Cách nói chính xác hơn là điểm khai báo. ↩
-
Trước C++20, số nguyên có dấu phải bao phủ phạm vi của bù một (tức \(-2^{x-1}+1\sim 2^{x-1}-1\)), nhưng thực tế đa số dùng bù hai; từ C++20 yêu cầu bắt buộc dùng bù hai. Xem Range of values - cppreference. ```` ↩
Last updated on this page:, Update history
Found an error? Want to help improve? Edit this page on GitHub!
Contributors to this page:OI-wiki
All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply