长生栈 长生栈
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Living Team

编程技术分享
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Hello world
  • C++ 数据类型
    • 基本内置类型
      • 算术类型
      • 类型转换
      • 隐式类型转换
      • 显式类型转换
      • 字面值常量
      • 指定字面值的类型
    • 变量
      • 变量定义
      • 初始值
      • 列表初始化
      • 默认初始化
      • 变量声明和定义的关系
      • 标识符
      • 名字的作用域
      • 嵌套的作用域
    • 复合类型
      • 引用
      • 引用即别名
      • 引用的定义
      • 指针
      • 获取对象地址和通过指针访问对象
      • 指针值
      • 空指针
      • void*指针
      • 理解复合类型的声明
      • 指向指针的指针
      • 指向指针的引用
    • const 限定符
    • 处理类型
    • 自定义数据结构
  • 运算符优先级
  • 命名空间
  • 多线程的基本用法(C++11)
  • C++类对象的成员内存占用
  • C++
DC Wang
2022-10-08
目录

C++ 数据类型

# C++ 变量和基本类型

# 基本内置类型

C++规定,基本数据类型包括算数类型和空类型(void),算数类型包含字符、整型数、布尔值和浮点数。空类型补丁应具体的值,仅用于一些特殊场合,例如当函数不返回任何值时使用空类型作为返回类型。整理如下图:

image-20221005164259206

# 算术类型

算数类型分为两类:整型(integral type, 包括字符和布尔类型在内)和浮点型。

类型 关键字
布尔型 bool
字符型 char
整型 int
浮点型 float
双浮点型 double
无类型 void
宽字符型 wchar_t

其中字符型、整型和浮点型都分为有符号和无符号。详细类型及其大小如下表:

类型 位 范围
char 1 个字节 -128 到 127 或者 0 到 255
unsigned char 1 个字节 0 到 255
signed char 1 个字节 -128 到 127
int 4 个字节 -2147483648 到 2147483647
unsigned int 4 个字节 0 到 4294967295
signed int 4 个字节 -2147483648 到 2147483647
short int 2 个字节 -32768 到 32767
unsigned short int 2 个字节 0 到 65,535
signed short int 2 个字节 -32768 到 32767
long 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
signed long int 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long int 8 个字节 0 到 18,446,744,073,709,551,615
float 4 个字节 单精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字)
double 8 个字节 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字)
long double 16 个字节 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。
wchar_t 2 或 4 个字节 1 个宽字符

我们也可以在自己电脑上尝试打印这些基本类型的大小。

#include <iostream>
using namespace std;

int main()
{
    cout << "sizeof(bool)          : " << sizeof(bool) << endl;
    cout << "sizeof(char)          : " << sizeof(char) << endl;
    cout << "sizeof(int)           : " << sizeof(int) << endl;
    cout << "sizeof(unsigned int)  : " << sizeof(unsigned int) << endl;
    cout << "sizeof(short int)     : " << sizeof(short int) << endl;
    cout << "sizeof(long int)      : " << sizeof(long int) << endl;
    cout << "sizeof(float)         : " << sizeof(float) << endl;
    cout << "sizeof(double)        : " << sizeof(double) << endl;

    cout << "min(bool)          : " << numeric_limits<bool>::min() << endl;
    cout << "min(int)           : " << numeric_limits<int>::min() << endl;
    cout << "min(unsigned int)  : " << numeric_limits<unsigned int>::min() << endl;
    cout << "min(short int)     : " << numeric_limits<short int>::min() << endl;
    cout << "min(long int)      : " << numeric_limits<long int>::min() << endl;
    cout << "min(float)         : " << numeric_limits<float>::min() << endl;
    cout << "min(double)        : " << numeric_limits<double>::min() << endl;

    cout << "max(bool)          : " << numeric_limits<bool>::max() << endl;
    cout << "max(int)           : " << numeric_limits<int>::max() << endl;
    cout << "max(unsigned int)  : " << numeric_limits<unsigned int>::max() << endl;
    cout << "max(short int)     : " << numeric_limits<short int>::max() << endl;
    cout << "max(long int)      : " << numeric_limits<long int>::max() << endl;
    cout << "max(float)         : " << numeric_limits<float>::max() << endl;
    cout << "max(double)        : " << numeric_limits<double>::max() << endl;

    return 0;
}
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
32

# 类型转换

# 隐式类型转换

算术表达式隐式转换顺序为:

1、char - int - long - double

2、float - double

bool b = 42;            // b is true
int i = b;              // i has value 1
i = 3.14;               // i has value 3
double pi = i;          // pi has value 3.0
unsigned char c = -1;   // assuming 8-bit chars, c has value 255
signed char c2 = 256;   // assuming 8-bit chars, the value of c2 is undefined
1
2
3
4
5
6

当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。

此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。

# 显式类型转换

四种强制类型转换操作符:static_cast、const_cast、dynamic_cast、reinterpret_cast

static_cast<new_type>(expression)
dynamic_cast<new_type>(expression) 
const_cast<new_type>(expression) 
reinterpret_cast<new_type>(expression)
1
2
3
4

# 字面值常量

C++中字面值常量是一类特殊的常量,它们没有名字,只能用它们的值来称呼,因此得名“字面值常量”。常见的字面值常量包括以下几类:

  • 整型字面值常量:20,024,0x14,20u,20l,20ll 等等
  • 浮点型字面值常量:3.14,3.14f,3.14l 等等
  • 布尔类型字面值常量:true,false
  • 字符字面值常量:'a','b','c','d'等等
  • 字符串字面值常量:"abc","def"等等

其中只有字符串字面值常量存储在全局区,可以取地址,其他的字面值常量都放在寄存器上,不能取内存地址。

比起字面值常量,使用const等定义的常量有一个可以称呼的名字,如const int a=2;名字就是a

# 指定字面值的类型

  • 字符和字符串字面值

    前缀 含义 类型
    u Unicode 16 字符 char16_t
    U Unicode 32 字符 char32_t
    L 宽字符 wchar_t
    u8 utf-8 (仅用于字符串字面常量) char
  • 整型字面值

    后缀 最小匹配值
    u or U unsigned
    l or L long
    ll or LL long long
  • 浮点型字面值

    后缀 类型
    f or F float
    l or L long double

# 变量

变量是一个具名的、可供程序操作的储存空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。在c++,变量(variable)和对象(object)一般可以互换使用。对象:是指一块能储存数据并具有某种类型的内存空间。

变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为 C++ 是大小写敏感的。

# 变量定义

变量定义的基本形式是:首先是类型说明符,随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。列表中的每个变量名的类型都有类型说明符限定,定义时还可以为一个或多个变量赋初值。

int i=0,j,k=0;					//i,j.k都是int,i和k初值为0
Student s;						//Student是一个class,假设事先已经定义class Student{...};
std:string name("xiao ming");	//string是一种库类型,表示一个可变长的字符序列。name通过一个string字面值初始化
1
2
3

# 初始值

当对象在创建时获得了一个特定的值,我们说这个对象被初始化了。用于初始化变量的值可以是任意复杂表达式。当一次定义了两个或多个变量时,从左到右,右边的变量可以使用左边变量名来初始化,即可以用先定义的变量值去初始化后定义的其他变量。

//定义一个长是宽2倍的长方形边长
float width = 12.6, length = width * 2;
//定义一个长方形的面积S,使用调用函数squareRectangle后的返回值来初始化S
float S = squareRectangle(width, length);
1
2
3
4

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而复制的含义是把对象的当前值擦除,而以一个新值来替代。

# 列表初始化

C++中,以下4条语句都可以定义一个名为age的int变量并初始化为18.

int age = 18;
int age = {18};
int age{18};
int age(18);
1
2
3
4

C++11标准之后,用花括号来初始化变量得到了全面应用。这种初始化的形式被称为列表初始化(list initialization)。

使用列表初始化,如果初始化值存在丢失信息的风险,则编译器报错。例如使用一个double值初始化一个int变量。

double pi = 3.14159;
//错误: 列表初始化,使用一个double值初始化一个int变量,存在丢失信息的风险
int a{pi}, b = {pi};
//正确: 转换执行,且确实丢失了部分值
int c(pi), d = pi;
1
2
3
4
5

# 默认初始化

如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了默认值。默认值由变量类型决定,也会受定义变量的位置影响。

如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义域任何函数体之外的变量被初始化成0.定义在函数体内部的内置类型变量将不被初始化(uninitiated)。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类型将引发错误。

每个类各自决定其初始化对象的方式,即类的构造函数(Constructor)。

总之,需要注意的是,定义于函数体内的内置类型对象如果没有初始化,则其值未定义。类的对象如果没有显式初始化,则其值由类决定。

# 变量声明和定义的关系

分离时编译(separate compilation)机制允许将程序分割为若干个文件,每个文件可被独立编译。

为了支持分离式编译,C++将声明和定义区分开来。声明(declaration)使得名字为程序所知。定义(definition)负责创建与名字关联的实体。

extern int i;	//声明i而非定义i
int j;			//声明并定义j
1
2

任何包含了显示初始化的声明即成为定义。

extern double pi =3.14159;	//定义
1

在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。

变量能且只能被定义一次,但可以被声明多次。

# 标识符

C++的标识符(identifier)由字母、数字和下划线组成,其中名字必须以字母或下划线开头。标识符的长度没有限制,但是对大小写敏感。

C++保留了一些名字,这些名字不能作为标识符。

alignas alignof and and_eq asm
atomic_cancel atomic_commit atomic_noexcept auto bitand
bitor bool break case catch
char char8_t char16_t char32_t class
compl concept const consteval constexpr
constinit const_cast continue co_await co_return
co_yield decltype default delete do
double dynamic_cast else enum explicit
export extern false float for
friend goto if inline int
long mutable namespace new noexcept
not not_eq nullptr operator or
or_eq private protected public reflexpr
register reinterpret_cast requires return short
signed sizeof static static_assert static_cast
struct switch synchronized template this
thread_local throw true try typedef
typeid typename union unsigned using
virtual void volatile wchar_t while
xor xor_eq

# 名字的作用域

C++语言中大多数作用域(scope)都以花括号分隔。

# 嵌套的作用域

作用域能彼此包含,被包含的作用域称为内层作用域(inner scope),包含着别的作用域的作用域称为外层作用域(outer scope)。

内层作用域能够使用外层作用域的名字,内层作用域还可以覆盖外层作用域的名字。

#include<iostream>

int age = 18;      //外层作用域定义一个age,(全局变量)
int main()
{
    std::cout << age;   //外层的变量age,(全局变量)
	int age = 19;  //内层作用域重新定义一个age,(局部变量),覆盖外层的age
    std::cout << age;   //内层的age,(局部变量)
    std::cout << ::age; //显式访问外层的age,(全局变量)
}
1
2
3
4
5
6
7
8
9
10

# 复合类型

复合类型(compound type)是指基于其他类型定义的类型。例如,引用和指针。

# 引用

引用为对象起了另一个名字。通过将声明符写成&d的形式定义引用类型,其中d是声明的变量名。

int ival = 2048;
int &refVal = ival; //refVal指向ival(是ival的另一个名字)
int &refVal2;       //报错:应用必须被初始化
1
2
3

# 引用即别名

定义一个引用之后,对其所有操作都相当于操作与之绑定的对象。

refVal = 1024;      //把1024赋值给refVal,即是赋值给了ival
int i = refVal;     //于i = ival; 执行结果一样

// 正确: refVal3绑定到那个与refVal绑定的对象上,即refVal3绑定到iVal上
int &refVal3 = refVal; 
int j = refVal;//正确: j被初始化为iVal的值
1
2
3
4
5
6

因为引用本身不是对象,所以不能定义引用的引用。所谓“引用的引用”指向的都是同一个对象。

# 引用的定义

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头:

int i = 1024, i2 = 2048; //i和i2都是int
int &r = i, r2 = i2;     //r是一个引用,与i绑定在一起,r2是int
int i3 = 1024, &ri = i3; //i3是int,ri是引用,与i3绑定在一起
int &r3 = i3, &r4 = i2;  //r3和r4都是引用
1
2
3
4

一般来说,引用类型与绑定对象要严格匹配。而且引用只能绑定在对象上,而不能是字面值或者表达式的计算结果。

int &refVal4 = 100; //错误: 引用只能绑定在对象上
double pi = 3.14;
int &refVal5 = pi;  //错误: 引用的初始值应该为int对象
1
2
3

# 指针

指针(pointer)是“指向(point to)”另外一种类型的复合类型。

指针和引用相同点 指针和引用不同点
实现了对其他对象的简介访问。 指针本身是一个对象,允许对指针的赋值和拷贝。
指针的生命周期内它可以先后指向几个不同的对象。
指针无须在定义时赋初值。

和其他内置类型一样,在块用域定义的指针如果没有被初始化,也将拥有一个不确定的值。

定义指针类型时,如果一条语句定义了多个指针变量,每个变量前都要有符号*。

int *p1; *p2;    //p1, p2 都是指向int型对象的指针。
double dp, *dp2; //dp是double型的对象, dp2是指向double型对象的指针。
1
2

# 获取对象地址和通过指针访问对象

取地址符 解引用符
& *

# 指针值

指针的值(即地址)应属于下列4中状态之一:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置。
  3. 空指针,意味着没有指向任何对象。
  4. 无效指针,也就是上述情况之外的其他值。

# 空指针

C++11中,可以使用字面值 nullptr初始化指针来得到空指针。

C++11之前的程序还会用到一个名为NULL的预处理变量(preprocessor variable)来给指针赋值,这个变量在头文件cstdlib中定义,他的值就是0。

建议初始化所有指针,未经初始化的指针是引发运行时错误的一大原因。

# void*指针

void*是一种特殊的指针类型,可用于存放任意对象的地址。

我们并不知道这个对象到底是什么类型,也无法确定能在这个对象上做哪些操作。以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

# 理解复合类型的声明

变量的定义包括一个基本数据类型(base type)和一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式可以不同。

// i是一个int型的书=数, p是一个int型的指针,r是一个i型的引用
int i = 1024, *p = &i, &r = i;
1
2

引用本身不是对象,因此没有指向引用的指针。

指针是对象,所以有指向指针的指针和指向指针的引用。

# 指向指针的指针

int iVal = 1024;
int *pi = &iVal; //pi指向一个int型的数
int *ppi = &pi;  //ppi指向一个int型的指针
1
2
3

# 指向指针的引用

int i = 18;
int *p;			//p是一个int型的指针
int *&r = p;	//r是一个对指针p的引用
r = &i;			//与p=&i结果一样。r引用了一个指针,因此给r赋值&i就是另p指向i
*r = 0;			//解引用r得到i,也就是p指向的对象,将i的值改为0
1
2
3
4
5

面对一条比较复杂的指针或引用的声明语句时,从右往左读有助于弄清楚它的真实含义。

# const 限定符

# 处理类型

# 自定义数据结构

编辑 (opens new window)
上次更新: 2023/03/31, 22:34:04
Hello world
运算符优先级

← Hello world 运算符优先级→

最近更新
01
ESP32-网络摄像头方案
06-14
02
ESP32-PWM驱动SG90舵机
06-14
03
ESP32-实时操作系统freertos
06-14
更多文章>
Theme by Vdoing | Copyright © 2019-2025 DC Wang All right reserved | 辽公网安备 21021102001125号 | 吉ICP备20001966号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式