C/C++编程:类型

1059 篇文章 278 订阅
  • 在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说明符或类型模板形参,则忽略它。

官方文档:https://zh.cppreference.com/w/cpp/language/reference

引用坍缩:

容许通过模板或者typedef中的类型操作

扩展的整型

  • 经常会在代码中发现一些整形的名字,比如UINT/__int16/u64/int64_t等等,这些类型有的源自编译器自行扩展,有的则是来自某些编程环境(比如工作在linux内核中),实际上,C++11中只定义了如下5种标准的有符号整型: signed charshort intintlong intlong 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值