- 在C++中,类型的概念非常重要。每个变量、函数自变量和函数返回值必须具有一个类型以进行编译。
- 此外,在计算表达式前,编译器会给出每个表达式的(包括文本)的隐式类型。
- C++是一种强类型语言,它也是静态类型化的。每个对象都有一个类型,在声明变量是,必须显示指定其类型,或者使用
auto
指示编译器从初始值设定项推断类型
术语
- 变量:数据量的符号名,以便可以访问它在定义它的代码范围中所引起的数据。在C++中,变量通常用于引用标量数据类型的示例,而其他类型的实例通常叫做对象
- 对象:
- POD对象(普通旧数据):
- POD类没有不是POD的静态数据成员,没有用户定义的析构函数、用户定义的构造函数或者用户定义的赋值运算符
- POD类无虚函数、基类、私有的或者所保护的非静态数据成员。
- POD类型通常用于外部数据交换,比如与C语言编写的模块(仅具有POD类型)进行的数据交换
- 在声明POD类型时,强烈建议你将其初始化,也就是为其指定初始值。 在初始化某个变量之前,该变量会有一个“垃圾”值,该值包含之前正好位于该内存位置的位数。 这是需要注意的 C++ 的一个重要方面,尤其是当你使用另一种语言来处理初始化时。 如果声明非 POD 类类型的变量,则构造函数会处理初始化。
类型
类型的分类
- 基础类型(参阅 std::is_fundamental):
- void 类型(参阅 std::is_void);
- std::nullptr_t 类型(C++11 起) (参阅std::is_null_pointer);
- 算术类型(参阅 std::is_arithmetic):
- 浮点类型(float、double、long double)(参阅std::is_floating_point);
- 整数类型(参阅 std::is_integral):
- bool 类型;
- 字符类型:
- 窄字符类型:
- 通常字符类型(char、signed char、unsigned char)
- char8_t 类型(C++20 起)
- 宽字符类型(char16_t、char32_t、 (C++11 起)wchar_t);
- 有符号整数类型(short int、int、long int、long long int (C++11 起));
- 无符号整数类型(unsigned short int、unsigned int、unsigned long int、unsigned long long int (C++11 起));
基础类型
示例:
- 判断是不是基础类型
#include <iostream>
#include <type_traits>
class A {};
int main()
{
std::cout << std::boolalpha;
std::cout << "A\t" << std::is_fundamental<A>::value << '\n';
std::cout << "int\t" << std::is_fundamental<int>::value << '\n';
std::cout << "int&\t" << std::is_fundamental<int&>::value << '\n';
std::cout << "int*\t" << std::is_fundamental<int*>::value << '\n';
std::cout << "float\t" << std::is_fundamental<float>::value << '\n';
std::cout << "float&\t" << std::is_fundamental<float&>::value << '\n';
std::cout << "float*\t" << std::is_fundamental<float*>::value << '\n';
}
std::nullptr_t类型
nullptr
注意:nullptr不是指针
为什么要引入nullptr:
nullptr
出现的目的是为了替代NULL。在某种意义上说,传统C++会把NULL、0视为同一种东西,这却决于编译器如何定义NULL,有时编译器会将NULL定义为((void *) 0),有的会直接定义为0
- C++不允许直接将(void *)隐式转换为其他类型。但是如果编译器尝试将NULL定义为((void *) 0),那么在下面代码中:
char *ch = NULL;
- 没有了(void *)隐式转换的C++只好将NULL定义为0.二这依然会有新的问题,将NULL定义成0会导致C++中重载特性发生混乱。如下:
void foo(char *);
void foo(int);
- 为了解决这个问题,C++11引入了nullptr关键字,专门用来区分空指针、0。而nullptr的类型为nullptr_t,能够隐式转换为任何指针或者成员指针的类型,也能和它们进行相等或者不相等的比较。
示例:
- 引入null_ptr的必要性
#include <iostream>
#include <type_traits>
void foo(char *){ printf("%s", "foo(char*) is called\n");}
void foo(int){printf("%s", "foo(int*) is called\n");}
int main()
{
foo(0); // 调用foo(int)
//foo(NULL); // 该行不能通过编译
foo(nullptr); //调用foo(char*)
}
- NULL与null_ptr
#include <cstddef>
#include <iostream>
void f(int*)
{
std::cout << "Pointer to integer overload\n";
}
void f(double*)
{
std::cout << "Pointer to double overload\n";
}
void f(std::nullptr_t)
{
std::cout << "null pointer overload\n";
}
int main()
{
int* pi {}; double* pd {};
f(pi);
f(pd);
f(nullptr); // 无 void f(nullptr_t) 可能有歧义
// f(0); // 歧义调用:三个函数全部为候选
// f(NULL); // 若 NULL 是整数空指针常量则为歧义
// (如在大部分实现中的情况)
std::cout << typeid(NULL).name() << std::endl; //int
std::cout << typeid(nullptr).name() << std::endl; //int *
}
- NULL不是0,也不是null_ptr
if(std::is_same<decltype(NULL), decltype(0)>::value){
printf("%s", "NULL == 0");
}
if(std::is_same<decltype(NULL), decltype((void *)0)>::value){
printf("%s", "NULL == (void *)0");
}
if(std::is_same<decltype(NULL), std::nullptr_t>::value){
printf("%s", "NULL == nullptr_t");
}
// std::is_same:比较两个类型是否相同
std::nullptr_t
是什么:
#include <cstddef>
typedef decltype(nullptr) nullptr_t;
std::nullptr_t
是空指针字面量 nullptr 的类型。它是既非指针类型亦非指向成员指针类型的独立类型。- 包含
<stddef.h>
时 nullptr_t 在全局命名空间可用,即使它不是 C 的一部分。
示例:
- 检查 T 是否为 std::nullptr_t 类型(
std::nullptr_t 、 const std::nullptr_t 、 volatile std::nullptr_t 或 const volatile std::nullptr_t
)。
#include <cstddef>
#include <iostream>
void f(int*)
{
std::cout << "Pointer to integer overload\n";
}
void f(double*)
{
std::cout << "Pointer to double overload\n";
}
void f(std::nullptr_t)
{
std::cout << "null pointer overload\n";
}
int main()
{
std::cout << std::boolalpha
<< std::is_null_pointer< decltype(nullptr) >::value << ' '
<< std::is_null_pointer< int* >::value << '\n'
<< std::is_pointer< decltype(nullptr) >::value << ' '
<< std::is_pointer<int*>::value << '\n';
}
void类型
作用:
- void
- 值为空集的类型。
- 它是无法变为完整的不完整类型,也就是说不允许存在void类型的对象
- 不存在含有 void 的数组以及到 void 的引用。可以存在 指向 void 的指针和返回 void 类型的函数
- 如果指针的类型为
void *
,则指针可以指向未使用const
或者volatile
声明的任何变量
void *
可以转换为任何其他类型的数据指针void *
可以指向函数,但是不能指向C++中的类成员
// void.cpp
void vobject; // C2182
void *pv; // okay
int *pint; int i;
int main() {
pv = &i;
// Cast optional in C required in C++
pint = (int *)pv;
}
- 判断是不是void类型(
void 、 const void 、 volatile void 或 const volatile void
)
#include <iostream>
#include <type_traits>
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_void<void>::value << '\n';
std::cout << std::is_void<const void>::value << '\n';
std::cout << std::is_void<int>::value << '\n';
}
- void类型主要用于:
- 声明不返回值的函数
- 声明指向非类型化的指针
- 声明指向任意类型数据的泛型指针
bool类型
作用:
- bool
- 足以存放两个值 true 或 false 之一的类型
示例:
- 判断是不是整数类型: 若 T 为 bool 、 char 、 char8_t 、 char16_t 、 char32_t 、 wchar_t 、 short 、 int 、 long 、 long long 类型,或任何实现定义的扩展整数类型,包含任何有符号、无符号及 cv 限定的变体。则返回 true
#include <iostream>
#include <type_traits>
class A {};
enum E : int {};
template <class T>
T f(T i)
{
static_assert(std::is_integral<T>::value, "Integral required.");
return i;
}
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_integral<A>::value << '\n';
std::cout << std::is_integral<E>::value << '\n';
std::cout << std::is_integral<float>::value << '\n';
std::cout << std::is_integral<int>::value << '\n';
std::cout << std::is_integral<bool>::value << '\n';
std::cout << f(123) << '\n';
}
整数类型
作用:
- int
- 基本整数类型
若使用了下列任何修饰符则可省略关键词 int
修饰符:
- 修饰整数类型。
- 能以任何顺序混合使用。unsigned long long int 与 long int unsigned long 指名同一类型。
- 类型名中每组只能有一个。
- 包括如下:
- 符号性:
signed
- 目标类型将拥有有符号表示(若省略则此为默认)
unsigned
- 目标类型将拥有无符号表示- 大小:
short
- 目标类型将为空间优化,且将有至少 16 位的宽度。long
- 目标类型将有至少 32 位的宽度。long long
- 目标类型将有至少 64 位的宽度
字符类型
分类:
signed char
:有符号字符表示的类型。unsigned char
:无符号字符表示的类型。亦用于审查对象表示(无修饰内存)。char
:
- 能在目标系统上最有效地处理的字符表示的类型
- char 的符号性取决于编译器和目标平台:
- ARM 和 PowerPC 的默认设置常为无符号
- x86 与 x64 的默认设置常为有符号
wchar_t
:
- 宽字符表示的类型
- 要求大到足以表示任何受支持的字符编码位点
char8_t
- C++20引入
- UTF-8 字符表示的类型,要求大到足以表示任何 UTF-8 编码单元( 8 位)。它与 unsigned char 具有相同的大小、符号性和对齐(从而与 char 和 signed char 具有相同的大小和对齐),但它是独立的类型。
char16_t
- C++11引入
- UTF-16 字符表示的类型,要求大到足以表示任何 UTF-16 编码单元( 16 位)。它与std::uint_least16_t 具有相同的大小、符号性和对齐,但它是独立的类型。
char32_t
- C++11引入
- UTF-32 字符表示的类型,要求大到足以表示任何 UTF-32 编码单元( 32 位)。它与 std::uint_least32_t 具有相同的大小、符号性和对齐,但它是独立的类型
大小:
- C++ 标准保证:
1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
浮点类型
float
- 单精度浮点类型。double
- 双精度浮点类型。long double
- 扩展精度浮点类型。
- 判断是不是浮点类型(
float 、 double 、 long double
)
#include <iostream>
#include <type_traits>
class A {};
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_floating_point<A>::value << '\n';
std::cout << std::is_floating_point<float>::value << '\n';
std::cout << std::is_floating_point<float&>::value << '\n';
std::cout << std::is_floating_point<double>::value << '\n';
std::cout << std::is_floating_point<double&>::value << '\n';
std::cout << std::is_floating_point<int>::value << '\n';
}
复合类型
引用类型
引用:既存对象或函数的别名
语法:
注意:
- 引用必须被初始化为指代一个有效的对象或函数:见
引用初始化
。- 引用不是对象;它们不必占用存储,尽管若需要分配存储以实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需),则编译器会这么做。
- 因为引用不是对象,故不存在引用的数组,不存在指向引用的指针,不存在引用的引用:
int& a[3]; // 错误 int&* p; // 错误 int& &r; // 错误
- 不存在 void 的引用。
- 引用类型无法在顶层被const 与 volatile限定;声明中没有为此而设的语法,而若将限定性添加到 typedef 名、
decltype
说明符或类型模板形参,则忽略它。
引用坍缩:
容许通过模板或者typedef中的类型操作
扩展的整型
- 经常会在代码中发现一些整形的名字,比如
UINT/__int16/u64/int64_t
等等,这些类型有的源自编译器自行扩展,有的则是来自某些编程环境(比如工作在linux内核中),实际上,C++11中只定义了如下5种标准的有符号整型:signed char
、short int
、int
、long int
、long long int
- 标准同时规定,每一种有符号整型都有自动对应的无符号整数版本,而且有符号整型与其对应的无符号整型具有相同的存储空间大小。
- 实际上,由于这5种基本的整型适用性有限,所以有时编译器出于需要,也会自行扩展一些整型。 C++11对这些扩展做出了一些规定:
- 运行编译器扩展除标准整型之外的整型。扩展整型的长度不做限制,可以比最长的标准整型(long long int)还长,也可以位于两个标准整数的位数的中间(比如48位)
- C++11规定,扩展的整型必须和标准类型一样,有符号和无符号类型占用同样同样大小的内存空间
- 当运算、传参等类型不匹配时,整型将会发生隐式转换,这种过程叫做整型提升。整型提升的规则如下:
- 长度越大等级越高。比如long long int > int
- 长度相同额情况下,标准整型的等级高于扩展整型。比如long long int > _int64
- 相同大小的有符号类型和无符号类型等级相同。比如long long int == unsigned long long int
- 进行隐式的整型转换时,低等级的转换为高等级的,有符号的转换为无符号的
类型的命名
- 通过下面方式来声明一个指代类型的名字
- 类声明
- 联合体声明
- 枚举类型
- typedef类型
- 类型别名声明
- 在C++程序中对没有名字的类型需要被涉指;为此而设的语法被称为 类型标识。
https://docs.microsoft.com/zh-cn/cpp/cpp/cpp-type-system-modern-cpp?view=msvc-160