Data Types
The C++ type system consists of the following parts:
- Basic types (representative keywords/types are given in parentheses)
- No type/
voidtype (void) - (Since C++11) Null pointer type (
std::nullptr_t) - Arithmetic types
- Integer types (
int) - Boolean/
booltype (bool) - Character types (
char) - Floating-point types (
float,double)
- Integer types (
- No type/
- Compound types1
Boolean Type
A variable of type bool can have only two possible values: true and false.
In general, a variable of type bool occupies byte of space (in general, byte = bits).
Tip
The number of bits in a byte can be obtained through the macro constant CHAR_BIT in the <climits> (C++) / <limits.h> (C) header files.
Boolean Type in C
See also Differences between C++ and other common languages - bool.
C originally had no Boolean type. It was not until C99 that the _Bool keyword was introduced as a Boolean type, and it is treated as an unsigned integer type.
Note
Since C23, the bool type in C is no longer defined using the integer zero and nonzero values; instead, it is defined as a type sufficient to store the two constants true and false.
For convenience, stdbool.h provides three macros, bool,true,false, defined as follows:
#define bool _Bool
#define true 1
#define false 0
These macros were removed in C23. Since C23, true,false, and bool have been introduced as keywords, while _Bool has been retained as an alternative spelling2.
In addition, since C23, the bit width of the Boolean type can be obtained through the macro constant BOOL_WIDTH in <limits.h>.
Integer Types
Used to store integers. The most basic integer type is int.
Note
For historical reasons, Boolean types and character types are treated as special integer types in C++.
In almost all cases, character types other than signed char and unsigned char should not be used as integer types.
Integer types generally have five width levels: char,short,int,long,long long.
The C++ standard guarantees 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long).
For historical reasons, integer types have multiple popular width models. To solve this problem, C99/C++11 introduced fixed-width integer types.
Size of the int Type
The C++ standard specifies that int has at least bits.
In fact, on the vast majority of current platforms, int has bits.
The int keyword can be modified with the following modifiers:
Signedness:
signed: denotes a signed integer (the default);unsigned: denotes an unsigned integer.
Size:
short: denotes an integer of at least bits;long: denotes an integer of at least bits;- (Since C++11)
long long: denotes an integer of at least bits.
The following table gives the widths and representation ranges of the integer types in general cases (on a small number of platforms, the representation ranges of some types may differ from this table):
| Type name | Equivalent type | Width (C++ standard) | Common width | Less common width |
|---|---|---|---|---|
signed char | signed char | - | - | |
unsigned char | unsigned char | - | - | |
short,short int,signed short,signed short int | short int | - | ||
unsigned short,unsigned short int | unsigned short int | - | ||
int,signed,signed int | int | (common in Win16 APIs) | ||
unsigned,unsigned int | unsigned int | (common in Win16 APIs) | ||
long,long int,signed long,signed long int | long int | (common on 64-bit Linux and macOS) | ||
unsigned long,unsigned long int | unsigned long int | (common on 64-bit Linux and macOS) | ||
long long,long long int,signed long long,signed long long int | long long int | - | ||
unsigned long long,unsigned long long int | unsigned long long int | - |
When the width is , the representation range of a signed type is 3, and that of an unsigned type is . Specifically:
| Width | Representation range |
|---|---|
| Signed: , unsigned: | |
| Signed: , unsigned: | |
| Signed: , unsigned: | |
| Signed: , unsigned: |
Equivalent Type Notations
When no ambiguity is caused, some modifiers may be omitted, or the order of modifiers may be adjusted. This means that the same type can have multiple equivalent notations.
For example, int, signed, int signed, and signed int denote the same type, while unsigned long and unsigned long int denote the same type.
In addition, some compilers implement extended integer types. For example, GCC implements 128-bit integers: the signed version __int128_t and the unsigned version __uint128_t. If you want to use these types in a contest, read the contest rules carefully to determine whether using extended integer types is permitted or supported.
Note
The STL may not provide sufficient support for extended integer types, so be particularly careful when using them.
Example Code
#include <cmath>
#include <iostream>
int f1(int n) {
return abs(n); // Good
}
int f2(int n) {
return std::abs(n); // Good
}
__int128_t f3(__int128_t n) {
return abs(n); // Bad
}
// Wrong
// __int128_t f4(__int128_t n) {
// return std::abs(n);
// }
int main() {
std::cout << "f1: " << f1(-42) << std::endl;
std::cout << "f2: " << f2(-42) << std::endl;
// std::cout << "f3: " << f3(-42) << std::endl; // Wrong
// std::cout << "f4: " << f4(-42) << std::endl; // Wrong
return 0;
}
The example code above has the following problems:
__int128_t f3(__int128_t)uses the C-style absolute value function, whose signature isint abs(int), sonis first forcibly converted toint, and only then is theabsfunction called.__int128_t f4(__int128_t)uses the C++-style absolute value function, but it has no overload with the signature__int128_t std::abs(__int128_t), so it cannot compile.- C++ stream output does not support
__int128_tand__uint128_t.
One solution is as follows:
Corrected Code
#include <cmath>
#include <iostream>
__int128_t abs(__int128_t n) { return n < 0 ? -n : n; }
std::ostream &operator<<(std::ostream &os, __uint128_t n) {
if (n > 9) os << n / 10;
os << (int)(n % 10);
return os;
}
std::ostream &operator<<(std::ostream &os, __int128_t n) {
if (n < 0) {
os << '-';
n = -n;
}
return os << (__uint128_t)n;
}
int f1(int n) { return abs(n); }
int f2(int n) { return std::abs(n); }
__int128_t f3(__int128_t n) { return abs(n); }
int main() {
std::cout << "f1: " << f1(-42) << std::endl;
std::cout << "f2: " << f2(-42) << std::endl;
std::cout << "f3: " << f3(-42) << std::endl;
}
Character Types
They are divided into “narrow character types” and “wide character types”. Since wide character types are almost never used in algorithm contests, only narrow character types are introduced here.
A narrow character type generally has bits. Its underlying storage is still an integer, and the one-to-one correspondence between characters and integers is generally implemented through ASCII encoding. There are three such types:
-
signed char: the type represented by signed characters, with a representation range of . -
unsigned char: the type represented by unsigned characters, with a representation range of . -
charhas the same representation and alignment as eithersigned charorunsigned char, but is always a distinct type.The signedness of
chardepends on the compiler and target platform: the default setting usually has no signedness on ARM and PowerPC, while it usually has signedness on x86 and x64.GCC can be given
-fsigned-charor-funsigned-charas a compiler argument to specify thatcharis treated assigned charorunsigned char; for other compilers, consult the documentation. Note that specifying signedness different from the architecture default may break the ABI and cause the program to malfunction.
Note
Unlike other integer types, char,signed char,unsigned char are three different types.
Generally speaking, signed char,unsigned char should not be used to store characters. In the vast majority of cases, these two types are treated as integer types.
Floating-Point Types
Used to store “real numbers” (note that these are not real numbers in the strict mathematical sense, but approximations of real numbers under certain rules), including the following three types:
float: single-precision floating-point type. If supported, it corresponds to the IEEE-754 binary32 format.double: double-precision floating-point type. If supported, it corresponds to the IEEE-754 binary64 format.long double: extended-precision floating-point type. If supported, it corresponds to IEEE-754 binary128; otherwise, if supported, it corresponds to the IEEE-754 binary64 extended format; otherwise, it corresponds to a non-IEEE-754 extended floating-point format whose precision is better than binary64 and whose value range is at least as good as binary64; otherwise, it corresponds to IEEE-754 binary64.
| Floating-point format | Width | Maximum positive number | Number of precision digits |
|---|---|---|---|
| IEEE-754 binary32 format | |||
| IEEE-754 binary64 format | |||
| IEEE-754 binary64 extended format | |||
| IEEE-754 binary128 format |
The smallest negative number in an IEEE-754 floating-point format is the opposite of the largest positive number.
Because the representation range of float is small and its precision is low, double is commonly used to represent floating-point numbers in practice.
In addition, floating-point types can support some special values:
- Infinity (positive or negative):
INFINITY. - Negative zero:
-0.0, for example1.0 / 0.0 == INFINITY,1.0 / -0.0 == -INFINITY. - Not a number (NaN):
std::nan,NAN, which can generally be produced by operations such as0.0 / 0.0. It is unequal to any value (including itself); since C++11,std::isnancan be used to determine whether a floating-point number is NaN.
No Type
The void type is the no-type type. Unlike the types above, a variable cannot be declared as type void. However, a function may have return type void, indicating that the function has no return value.
Null Pointer Type
See the corresponding section on pointers.
Fixed-Width Integer Types
Support for fixed-width integers has been provided since C++11, specifically:
<cstdint>: provides several fixed-width integer types, as well as macro constants for the maximum and minimum values of each fixed-width integer type.<cinttypes>: provides format macro constants for fixed-width integer types for thestd::fprintffamily and thestd::fscanffamily of functions.
There are the following kinds of fixed-width integers:
intN_t: a signed integer type whose width is exactly bits, such asint32_t.int_fastN_t: the fastest signed integer type whose width is at least bits, such asint_fast32_t.int_leastN_t: the smallest signed integer type whose width is at least bits, such asint_least32_t.
For the unsigned versions, simply add the letter u before the signed version, such as uint32_t,uint_least8_t.
The standard requires the following 16 types to be implemented:
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.
On this basis, the vast majority of compilers also implement the following 8 types:
int8_t,int16_t,int32_t,int64_t,
uint8_t,uint16_t,uint32_t,uint64_t.
When the corresponding type is implemented, the C++ standard requires the macro constants for its maximum value, minimum value, and width. Their names are formed by removing the final _t from the type name, converting the remainder to uppercase, and adding a suffix:
_MAXdenotes the maximum value, for example,INT32_MAXis the maximum value ofint32_t._MINdenotes the minimum value, for example,INT32_MINis the minimum value ofint32_t.
Note
Fixed-width integer types are essentially aliases for ordinary integer types, so mixing fixed-width integer types and ordinary integer types may affect cross-platform compilation. For example:
Example Code
#include <algorithm>
#include <cstdint>
#include <iostream>
int main() {
long long a;
int64_t b;
std::cin >> a >> b;
std::cout << std::max(a, b) << std::endl;
return 0;
}
On 64-bit Windows, int64_t is generally long long int, while on 64-bit Linux it is generally long int. Therefore, this code cannot compile when using GCC on 64-bit Linux, but can compile when using MSVC on 64-bit Windows, because std::max requires its two input parameter types to be the same.
In addition, since C++17, the class template std::numeric_limits has been provided in <limits> for querying properties of various arithmetic types, such as their maximum value, minimum value, whether they are integer types, and whether they are signed.
#include <cstdint>
#include <limits>
std::numeric_limits<int32_t>::max(); // Maximum value of int32_t, 2'147'483'647
std::numeric_limits<int32_t>::min(); // Minimum value of int32_t, -2'147'483'648
std::numeric_limits<double>::min(); // Minimum value of double, approximately 2.22507e-308
std::numeric_limits<double>::epsilon(); // Difference between 1.0 and the next representable value of double,
// approximately 2.22045e-16
Type Conversion
Sometimes (for example, when a function accepts an int parameter but is passed a variable of type double), we need to convert one type to another.
C++'s type conversion mechanism is relatively complex. Here we mainly introduce two conversions for basic data types: numeric promotion and numeric conversion.
Numeric Promotion
During numeric promotion, the value itself remains unchanged.
Note
C-style variadic arguments undergo default argument promotion during argument passing. For example:
Example Code
#include <stdarg.h>
#include <stdio.h>
void test(int tot, ...) {
va_list valist;
int i;
// Initialize the variadic argument list
va_start(valist, tot);
for (i = 0; i < tot; ++i) {
// Get the value of the i-th variable
double xx = va_arg(valist, double); // Correct
// float xx = va_arg(valist, float); // Wrong
// Output the underlying storage contents of the i-th variable
printf("i = %d, value = 0x%016llx\n", i, *(long long *)(&xx));
}
// Clean up the memory of the variadic argument list
va_end(valist);
}
int main() {
float f;
double fd, d;
f = 123.; // 0x42f60000
fd = 123.; // 0x405ec00000000000
d = 456.; // 0x407c800000000000
test(3, f, fd, d);
}
When test is called, f is promoted to double, so its underlying storage contents are the same as those of fd, and the output is
i = 0, value = 0x405ec00000000000
i = 1, value = 0x405ec00000000000
i = 2, value = 0x407c800000000000
If double xx = va_arg(valist, double); is changed to float xx = va_arg(valist, float);, GCC should issue a warning similar to the following:
In file included from test.c:2:
test.c: In function 'test':
test.c:14:35: warning: 'float' is promoted to 'double' when passed through '...'
14 | float xx = va_arg(valist, float);
| ^
test.c:14:35: note: (so you should pass 'double' not 'float' to 'va_arg')
test.c:14:35: note: if this code is reached, the program will abort
The program will terminate before output at this point.
This also explains why printf's %f can match both float and double.
Integer Promotion
A prvalue of a small integer type (such as char) can be converted to a prvalue of a larger integer type (such as int).
More specifically, arithmetic operators do not accept types smaller than int as their arguments; after lvalue-to-rvalue conversion, integer promotion is performed automatically when applicable.
More specifically, the following rules apply:
- When the source type is
signed char,signed short / short, it can be promoted toint. - When the source type is
unsigned char,unsigned short, ifintcan hold the value range of the source type, it can be promoted toint; otherwise, it can be promoted tounsigned int. (SinceC++20,char8_talso follows this rule.) - The promotion rule for
chardepends on whether its underlying type issigned charorunsigned char. - A
booltype can be converted toint:falsebecomes0, andtruebecomes1. - If the value range of the destination type contains that of the source type, and the value range of the source type cannot be contained by
intorunsigned int, the source type can be promoted to the destination type.4
Note
char -> short is not numeric promotion, because char must first be promoted to int / unsigned int, followed by int / unsigned int -> short, which does not satisfy the conditions for numeric promotion.
For example (assume below that int is 32 bits, unsigned short is 16 bits, signed char and unsigned char are 8 bits, and bool is 1 bit):
(signed char)'\0' - (signed char)'\xff'first promotes(signed char)'\0'to(int)0and(signed char)'\xff'to(int)-1, then performs the operation betweenintvalues, with the final result(int)1.(unsigned char)'\0' - (unsigned char)'\xff'first promotes(unsigned char)'\0'to(int)0and(unsigned char)'\xff'to(int)255, then performs the operation betweenintvalues, with the final result(int)-255.false - (unsigned short)12first promotesfalseto(int)0and(unsigned short)12to(int)12, then performs the operation betweenintvalues, with the final result(int)-12.
Floating-Point Promotion
A floating-point number with a smaller width can be promoted to one with a larger width (for example, when variables of type float and double undergo arithmetic, the float variable is promoted to double), without changing its value.
Numeric Conversion
During numeric conversion, the value may change.
Note
Numeric promotion takes precedence over numeric conversion. For example, bool -> int is numeric promotion rather than numeric conversion.
Integer Conversion
<!-- scripts.linter.preprocess.fix_details off -->-
If the destination type is an unsigned integer type of width , the result is the original value .
-
If the destination type is wider than the source type:
-
If the source type is signed, sign extension is generally performed first, followed by the conversion.
For example:
- When
(short)-1((short)0b1111'1111'1111'1111) is converted tounsigned int, sign extension is first performed, yielding0b1111'1111'1111'1111'1111'1111'1111'1111, followed by integer conversion, yielding(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111). - When
(short)32'767((short)0b0111'1111'1111'1111) is converted tounsigned int, sign extension is first performed, yielding0b0000'0000'0000'0000'0111'1111'1111'1111, followed by integer conversion, yielding(unsigned int)32'767((unsigned int)0b0000'0000'0000'0000'0111'1111'1111'1111).
- When
-
If the source type is unsigned, zero extension is first performed, followed by the conversion.
For example, when
(unsigned short)65'535((unsigned short)0b1111'1111'1111'1111) is converted tounsigned int, zero extension is first performed, yielding0b0000'0000'0000'0000'1111'1111'1111'1111, followed by integer conversion, yielding(unsigned int)65'535((unsigned int)0b0000'0000'0000'0000'1111'1111'1111'1111).
-
-
If the destination type is no wider than the source type, truncation is first performed, followed by the conversion.
For example, when
(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111) is converted tounsigned short, truncation is first performed, yielding0b1111'1111'1111'1111, followed by integer conversion, yielding(unsigned short)65'535((unsigned short)0b1111'1111'1111'1111).
-
-
If the destination type is a signed integer type of width , in general, the result can be considered the original value .5
For example, when
(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111) is converted toshort, the result is(short)-1((short)0b1111'1111'1111'1111). -
If the destination type is
bool, this is Boolean conversion. -
If the source type is
bool,falseis converted to the corresponding type's 0, andtrueto the corresponding type's 1.
Floating-Point Conversion
Converting a floating-point number with a larger width to one with a smaller width rounds it to the closest value representable by the destination type.
Floating-Point-Integer Conversion
-
When a floating-point number is converted to an integer, all fractional digits of the floating-point number are discarded.
If the destination type is
bool, this is Boolean conversion. -
When an integer is converted to a floating-point number, it is rounded to the closest value representable by the destination type.
If the value cannot fit in the destination type, the behavior is undefined.
If the source type is
bool,falseis converted to zero, whiletrueis converted to one.
Boolean Conversion
When another type is converted to type bool, a zero value is converted to false, and a nonzero value is converted to true.
Defining Variables
Simply speaking6, defining a variable requires a type specifier (indicating the variable's type) and the name of the variable to be defined.
For example, each of the following statements is a variable definition statement.
int oi;
double wiki;
char org = 'c';
In the program fragments we have encountered so far, variables defined inside braces are local variables, while variables defined outside braces are global variables. There are actual exceptions, but they need not be understood yet.
Global variables without an initialization value at definition are initialized to . Local variables do not have this property and need to be assigned an initial value manually; otherwise, they may cause bugs that are difficult to detect.
Variable Scope
The scope is the block of code in which a variable can take effect.
The scope of a global variable begins at its definition7 and continues to the end of the file.
The scope of a local variable begins at its definition and continues to the end of the code block.
A code block consists of several statements enclosed by a pair of braces.
int g = 20; // Define the global variable
int main() {
int g = 10; // Define the local variable
printf("%d\n", g); // Output g
return 0;
}
If a variable with the same name is defined in a nested block of a code block, the inner block cannot access the variable with the same name in the outer block.
For example, in the code above, the value of output is . Therefore, to prevent unexpected errors, try to avoid giving local and global variables the same name.
Constants
A constant is a fixed value that does not change during program execution.
The value of a constant cannot be modified after its definition. Add the const keyword when defining it.
const int a = 2;
a = 3;
If the value of a constant is modified, a compilation error is reported: error: assignment of read-only variable 'a'.
References and notes
- Working Draft, Standard for Programming Language C++
- Types - cppreference.com
- Arithmetic types - cppreference.com in C
- 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
Footnotes
-
Includes array types, reference types, pointer types, class types, function types, and so on. Since this article is intended for beginners, they are not introduced specifically here. See Types - cppreference.com for details. ↩
-
See https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf ↩
-
Before C++20, it was specified that signed integers must at least cover the representation range of ones' complement (that is, ), but in fact the vast majority of implementations use two's complement; since C++20, it has further been specified that signed integers must use two's complement. See Range of values - cppreference for details. ↩
-
This excludes wide character types, bit-fields, and enumeration types; see Integral conversions - cppreference for details. ↩
-
Effective since C++20. Before C++20, the result was implementation-defined. See Integral conversions - cppreference for details. ↩
-
When defining a variable, specifiers other than the type specifier may also be included. See Declarations - cppreference for details. ↩
-
More precisely, the point of declaration. ↩