C++ 中的类型详细

目录
  • 一、初始化与赋值
  • 二、 类型概述
  • 三、类型分类
    • 1、数值类型
    • 2、void类型
  • 四、字面值及其类型
  • 五、变量及其类型
  • 六、复合类型
    • 1、指针:一种间接类型;
    • 2、引用
  • 七、常量类型
  • 八、类型别名
  • 九、类型自动推导
  • 十、域与对象声明周期
    • 1、思考下下面关于指针的两行代码的含义
    • 2、经过指针的思考后,我们看看关于引用的思考
    • 3、经过了指针和引用的思考
    • 4、思考以下代码中&x是什么数据类型?
    • 5、思考下面函数传参的区别?
    • 6、下面常量表示底层常量还是顶层常量?
    • 7、下面auto&自动推导出的y是什么类型?
    • 8、下面来看看decltype自动推导的类型是什么?

前言:

 类型一直是C++中最重要的部分,相比于其他高级语言,C++的类型会复杂许多,往往一个类型匹配错误就会导致程序报错,本篇主要讲解一些常用类型的概念以及细节,如果对于C++有一定基础的,可以跳转到思考部分,从中了解自己的掌握程度;

一、初始化与赋值

定义:初始化与赋值语句是程序中最基本的语句,功能是将某个值与一个对象关联起来;

  • 值:字面量、对象(变量或常量)所表示的值等
  • 标识符(对象):变量、常量、引用

初始化的基本操作:

  • 1、在内存中开辟空间、保存相应的数值;
  • 2、在编译器中构造符号表、将标识符与相关内存空间关联起来;

二、 类型概述

下面通过几点概要说明:

1、类型是编译期概念,可执行程序中不存在类型的概念;

2、C++是强类型语言;

强类型语言定义: 一旦一个变量被定义类型,如果不经过强制转换,那么它永远就是该数据类型;

弱类型语言定义: 某一变量被定义类型,该变量可根据环境变化自动进行转换,不需要强转;

3、引入类型是为了更好描述程序,防止误用;

4、类型描述的信息:

存储所需要的大小: (sizeof,标准没有严格限制,根据硬件不同字节数也不同)
取值空间: (可用std::numeric_limits来判断,超过范围可能产生溢出)

#include<iostream>
#include<limits>

int main() {
    int x = 10;

    std::cout << std::numeric_limits<int>::min() << std::endl;    //-2147483648
    std::cout << std::numeric_limits<int>::max() << std::endl;    //2147483647

    std::cout << std::numeric_limits<unsigned int>::min() << std::endl;  //0
    std::cout << std::numeric_limits<unsigned int>::max() << std::endl;  //4294967295
}

由上面程序运行结果可知,无符号int类型占4个字节,也就是32个比特位,所以最大范围为232,在不同的硬件下可能不同;

  • 对齐信息(一般存放在内存中按类型的对齐信息的整数倍存储,比如int的对齐信息为4个字节,那存储的空间首地址为4的倍数,在结构体中,因为存在对齐信息,char也会按4个字节保存)
  • 类型可执行的操作

三、类型分类

类型可以划分为基本类型和复杂类型;

基本(内建)类型:C++语言中支持的类型,包含以下几种:

1、数值类型

字符类型:charwchar_tchar16_tchar32_t,通常为1个字节,表示256个值,也就是ASCII编码的字符;
整数类型:带符号整数类型(shortintlonglong long),无符号整数类型(unsigned+带符号整数类型)
浮点类型:floatdoublelong double

注意:在C++11中引入了固定尺寸的整数类型,如int32_t等,之前在针对开发板的程序中有见过该类型,主要是便于硬件的可移植性:

2、void类型

复杂类型:由基本类型组合、变种所产生的类型,可能是标准库引入,或自定义类型;

四、字面值及其类型

字面值:在程序中直接表示为一个具体数值或字符串的值;

每个字面值都有其类型,例子如下:

  • 整数字面值(int):20(十进制)、024(八进制)、0x14(十六进制)
  • 浮点数(double):1.3、1e8
  • 字符字面值(char): ‘c'、'\n'
  • 字符串字面值(char[4]): “cpp”,注意这里字符串后会默认加/0,所以是四个字符长度
  • 布尔字面值(bool): True、False

像如果想要定义float类型的数,可以加入后缀如1.3f

C++提供了用户创建自定义后缀的函数:

#include<iostream>

// 后缀可自行定义,我这里用_bang
int operator "" _bang(long double x)
{
    return (int)x * 2;
}

int main() {
    int x = 7.14_bang;
    std::cout << x << std::endl;
}

上面代码将7.14的浮点类型转换成整型并增大一倍,可自行定义后缀试一下;

五、变量及其类型

变量:对应一段存储空间,可以改变其中内容;

声明与定义的区别:不能重定义已经初始化的变量,需要加入extern用来声明;

初始化:全局变量会默认初始化为0,局部变量会缺省初始化(随机数值);

六、复合类型

1、指针:一种间接类型;

如上图为一个指针p指向一段内存,p保存的为val的地址,我们通过打印尺寸可知,指针p为8个字节;

特点:

  • 可以"指向"不同的对象;
  • 具有相同的尺寸;
  • 指针与bool的隐式转换:非空指针可以转换为true、空指针可以转换为false

注意:两个符号:*(解引用符)、&(取地址符);

解引用符在不同环境下含义不同,看如下代码:

int x = 10;
int* p = &x;  // 表示p为一个int指针类型
*p;       // 表示解引用,获取指针指向地址的值

关于nullptr:

  • 一个特殊的对象(类型为nullptr_t),表示空指针;
  • 类似于C中的NULL,但更加安全;

void 指针*:没有记录对象的尺寸,可以表示任意类型指针,一般作为形参或返回值;

指针对比对象:指针复制成本低,引用成本高;

总结:指针在程序中的作用,最重要的就是作为参数传入,由于数据类型可能很大,传入指针大小固定为8个字节,并且指针值为地址可复制,复制成本低,并且可在函数中改变变量的值;

2、引用

取地址符&也有两个含义:

int x = 10;
&x;      // 取地址符
int& ret = x;    // 定义ret为一个引用类型

特点:

  • 是对象的别名,不能绑定字面值(指针也不能指向字面值);
  • 构造时绑定对象,在其生命周期内不能绑定其他对象(赋值操作会改变对象内容);
  • 不存在空引用,但可能存在非法引用,总体比指针安全;
  • 属于编译期概念,在底层还是通过指针实现;

七、常量类型

  • 使用关键字const声明常量对象;
  • 是编译期概念,由编译器保证,作用为防止非法操作、优化程序逻辑;

常量指针(顶层常量):

int* const p = &x;

常量指针表示指针为常量,指针不能更改指向;

底层常量:

const int* p = &x;

底层常量表示指针指向的地址的内容不能发生改变,指针指向可改变;

常量引用:

const int&定义一个常量引用;
主要用于函数形参(对于较复杂的数据类型);
可以绑定字面值;

常量表达式:

constexpr int x = 1;  // x的类型仍为const int

声明的是编译期的常量,编译器可以对其进行优化;

八、类型别名

类型别名:引入特殊的含义或便于使用,例如size_t

引入类型别名的两种方式:

1、typedef int Mytype;

2、using Mytype = int;(C++11后)

第二种方式更好;

  • 应将指针类型别名视为一个整体,引入常量const表示指针为常量的类型;
  • 不能通过类型别名构造引用的引用;

九、类型自动推导

定义:通过初始化表达式定义对象类型,编译器会自动推导得到;(C++11开始)

推导得到的类型还是强类型,并不是弱类型;

自动推导的几种形式:

1、auto:最常用的形式,会产生类型退化(由于左值右值的类型区别);

2、const autoconstexpr auto:推导出的是常量、常量表达式类型;

3、auto&:推导出引用类型,避免类型退化;

4、decltype(exp) :返回exp表达式的类型(左值加引用);

5、decltype(val) :返回val的类型;

6、decltype(auto) :简化decltype的使用,C++14开始支持;

补充:类型退化表示一个变量作为左值和右值时类型不同,例如数组作为右值为指针;

十、域与对象声明周期

域(scope):表示程序中的一部分,其中的名称有唯一含义,有全局域、块域等;

域可以嵌套,嵌套域中定义的名称可以隐藏外部域中定义的名称;
对象的生命周期起始于被初始的时刻,终止于被销毁的时刻;
全局对象的生命周期是整个程序运行期间,局部对象终止在所在域执行完成;
思考

1、思考下下面关于指针的两行代码的含义

int x = 1;
int* p = &x;
int y = 0;
*p = y;    // 第一行
p = &y;    // 第二行

这两行表明了指针的一个特定,可改变性,每一行的含义如下:

第一行:将指针p指向的内存地址的值改变为y;

第二行:不改变x的值,而是将指针p的指向改成y;

2、经过指针的思考后,我们看看关于引用的思考

int x = 1;
int& f = x;
int y = 0;
f = y;    // 思考一下这一行的作用,是改变了引用f的绑定吗?

上面这行代码并不改变f的绑定,而是改变了f的值,同时引用对象x的值也发生改变;

3、经过了指针和引用的思考

下面思考下两者在底层有什么关联:

int x;
int* p = &x; *p = 1;
int& f = x; f = 1;

分析下上面两行代码,他们底层实现会相同吗?

这是两者的汇编代码实现,可以发现是完全相同的,引用底层也是通过指针实现的;

4、思考以下代码中&x是什么数据类型?

int x = 1;
const int* p = &x;

如果我们只考虑&x的话,这是一个int*的类型,但由于第二行代码执行拷贝构造,隐式地将&x转换为左值所需要的 const int *类型;

5、思考下面函数传参的区别?

void fun(int x){}
void fun(const int& x){}

从本质上来说,上面两种传参实现的作用是一致的,第一个进行拷贝构造传递,所以在函数内部无法改变外部x变量的值,而下面的传参传入引用可以在函数内部改变外部x的值,加入const强制成变量;第二种其实是画蛇添足地做法,但常量引用对于复杂的数据类型来说,是能够节省很多空间的,比如自定义的结构体;

6、下面常量表示底层常量还是顶层常量?

using mytype = int*;
int x = 1;
const mytype p = &x;

这里我们容易误导,还会认为这是一个底层常量,但由于别名的定义,这里其实是一个顶层常量,我们可以将mytype看作一个整体,那么指针的指向不可发生改变;

7、下面auto&自动推导出的y是什么类型?

const int x = 1;
auto& y = x;

相信大部分人会认为x会类型退化,从而y为int&类型,实际上这里类型不会退化,所以y为const int&类型;

8、下面来看看decltype自动推导的类型是什么?

int x = 1;
decltype(x);  // 1
decltype((x));  // 2

decltype在传入参数为左值时加入引用,那么第一行为一个变量,所以为int类型,第二行为表达式,所以加入引用为int&类型;

总结:

本篇讲解的类型知识点很杂,并且涵盖很多小的知识点,很多细节部分在实际工程中不一定会接触到,当然在工程中也会遇到很多自己不理解的类型转换,需要多通过debug模式来查看类型;

本篇知识点较多,可以选择自己想了解的部分进行查看,后续会继续推出更深层次的内容;

到此这篇关于C++ 中的类型详细的文章就介绍到这了,更多相关C++ 类型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++模板非类型形参的详细讲解

    前言 关于模板的非类型形参,网上有很多内容,C++primer只有大概一页的阐述,但是都不够清晰详细.下面我尽可能从自己的角度去给大家描述一下非类型形参的相关细节.如果想进一步理解非类型形参以及模板内容可以阅读C++template这本书,在4.1节,8.3.3节,13.2节都有相关解释. 模板除了定义类型参数,我们还可以在模板定义非类型参数. 什么是非类型形参?顾名思义,就是表示一个固定类型的常量而不是一个类型. 先举一个简单的例子(模板类与模板函数都可以用非类型形参) //例子1: temp

  • C++中的string类型

    目录 1.string 类 1.1 和char *的异同 1.2 C++11初始化 1.3 拼接 1.4 长度 1.5 IO 1.6 原始字符串 1.string 类 1.1 和char *的异同 在C++当中,除了char *类型,还有专门的字符串类型,就叫做string. 通过包含头文件string就可以使用: include<string> 在很多方面,string类型的使用方法和char *一样,例如: string str1; string str2 = "hello wo

  • 详解C++函数类型与重载函数

    目录 1. 2. 问题: 总结 1. 首先对重载函数,明确函数的返回类型不能决定重载函数的类别,即 int F(int ,int) ://一个返回 int 类型的函数 void F(int ,int)://一个无返回值的函数 //两者形参列表相同,返回值类型不同,但两者不构成重载函数 2. 注意形参列表中的默认值,使用含默认参数的重载函数时可能会产生二义性.例: int a = 0; int Max(int,int); int Max(int,int,int = 0); //则对Max(3,5)

  • 手把手带你学习C++的数据类型

    目录 数据类型 01 整型: 02 sizeof关键字 03 实型(浮点型) 04 字符型 05 转义字符 06 字符串型 07 布尔型 08 数据的输入 总结 数据类型 C++规定在创建一个变量或者常量时,必须要指定相应的数据类型,否则无法给变量分配内存空间. 01 整型: 数据类型 占用空间 取值范围 short(短整型) 2字节 -2^15~2^15-1 int(整型) 4字节 -2^31~2^31-1 long(长整型) 4字节/8字节 -2^31~2^31-1 long long(长长

  • C++ 中的类型详细

    目录 一.初始化与赋值 二. 类型概述 三.类型分类 1.数值类型 2.void类型 四.字面值及其类型 五.变量及其类型 六.复合类型 1.指针:一种间接类型: 2.引用 七.常量类型 八.类型别名 九.类型自动推导 十.域与对象声明周期 1.思考下下面关于指针的两行代码的含义 2.经过指针的思考后,我们看看关于引用的思考 3.经过了指针和引用的思考 4.思考以下代码中&x是什么数据类型? 5.思考下面函数传参的区别? 6.下面常量表示底层常量还是顶层常量? 7.下面auto&自动推导出

  • SpringBoot Entity中枚举类型详细使用介绍

    目录 简介方案对比 枚举用法示例 建表 Entity Enum Controller Service Mapper 测试 1.正常操作 2.前端传空字符串 3.前端传null 简介方案对比 本处列举表示类型或状态的常用方法的对比. 法1:使用数字表示(不推荐) //1:支付宝支付:2:微信支付:3:银行卡支付 private Integer payType; 这种方法的缺点:可读性极差,排查问题也麻烦.比如:前端页面上看到了2这个类型,还要看接口文档或者问后端这是什么意思,浪费时间! 法2:使用

  • java中的枚举类型详细介绍

    枚举中有values方法用于按照枚举定义的顺序生成一个数组,可以用来历遍.我们自定义的枚举类都是继承自java.lang.Enum,拥有一下实例中的功能: 复制代码 代码如下: //: enumerated/EnumClass.java // Capabilities of the Enum class import static net.mindview.util.Print.*; enum Shrubbery { GROUND, CRAWLING, HANGING } public clas

  • Go语言中的复合类型详细介绍

    golang复合类型包括:结构体.数组.切片.Maps. 1.数组 数组 golang中的数组与C语言中的数组差异很大,倒更类似Pascal中的数组. (Slice,下个话题,有些像C语言中的数组) 复制代码 代码如下: var ar [3]int 声明ar为一个拥有三个整型数的数组,所有元素初始化为0. 大小是类型的一个组成部分. 内置的函数len可以用于获取数组大小: 复制代码 代码如下: len(ar) = 3 数组是值类型 golang中的数组是值,而非C语言中的隐式指针.你可以获得数组

  • PHP数组内存利用率低和弱类型详细解读

    这两天任务提前完成,可以喘口气沉淀一下,深入学习学习PHP.其实本来是想了解一下PHP性能优化相关的东西,但被网上的一句"PHP数组内存利用率低,C语言100MB的内存数组,PHP里需要1G"惊到了.PHP真的这么耗内存么?于是借此机会了解了PHP的数据类型实现方式. 先来做个测试: <?php echo memory_get_usage() , '<br>'; $start = memory_get_usage(); $a = Array(); for ($i=0;

  • JavaScript高级教程5.6之基本包装类型(详细)

    为了便于操作基本类型值,ECMAScript还提供了3个特殊的引用类型:Boolean,Number,String. 实际上,每当读取一个基本类型值的时候,后台应付创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据. var s1="some text"; var s2=s1.substring(2); console.log(s2);//me text 这个例子中s1包含了一个字符串,字符串是基本类型值.第二行调用了s1的subsstring()方法,并将返回

  • Java编程中的构造函数详细介绍

    本文主要是为新手.对java语言感兴趣的人和那些没有系统学习过java基础知识的人进行一个总结,在文章中对构造函数进行了较为详细的说明和讨论,也包含了我个人对于java面向对象中构造函数的一些看法.希望走在java学习道路上的同行者可以有一个较为清晰的认知和理解.当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 1.构造函数的概念 很多java新手谈到构造函数就会犯晕,我们先来看看什么是构造函数. 首先,构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回类型

  • golang中值类型/指针类型的变量区别总结

    前言 值类型:所有像int.float.bool和string这些类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中.当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝.可以通过 &i 获取变量 i 的内存地址 指针类型:简单地说go语言的指针类型和C/C++的指针类型用法是一样的,除了出去安全性的考虑,go语言增加了一些限制,包括如下几条: 不同类型的指针不能互相转化,例如*int, int32, 以及int

  • c#中值类型和引用类型的基础教程

    前言 值类型和引用类型,是c#比较基础,也必须掌握的知识点,但是也不是那么轻易就能掌握,今天跟着老胡一起来看看吧. 典型类型 首先我们看看这两种不同的类型有哪些比较典型的代表. 典型值类型 int, long, float, double等原始类型中表示数字的类型都是值类型,表示时间的datatime也是值类型,除此之外我们还可以通过关键字struct自定义值类型. 典型引用类型 原始类型中,array, list, dictionary, queue, stack和string都是引用类型,除

  • Mysql中varchar类型一些需要注意的地方

    varchar的存储规则 4.0版本以下,varchar(20),指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节). 5.0版本以上,varchar(20),指的是20字符,无论存放的是数字.字母还是UTF8汉字(每个汉字3字节),都可以存放20个,最大大小是65532字节. varchar 字段是将实际内容单独存储在聚簇索引之外,内容开头用1到2个字节表示实际长度. 官方是这么说的: Values in VARCHAR columns are variable-length

随机推荐