C++ 数据类型
# C++ 变量和基本类型
# 基本内置类型
C++规定,基本数据类型包括算数类型和空类型(void),算数类型包含字符、整型数、布尔值和浮点数。空类型补丁应具体的值,仅用于一些特殊场合,例如当函数不返回任何值时使用空类型作为返回类型。整理如下图:
# 算术类型
算数类型分为两类:整型(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;
}
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
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)
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字面值初始化
2
3
# 初始值
当对象在创建时获得了一个特定的值,我们说这个对象被初始化了。用于初始化变量的值可以是任意复杂表达式。当一次定义了两个或多个变量时,从左到右,右边的变量可以使用左边变量名来初始化,即可以用先定义的变量值去初始化后定义的其他变量。
//定义一个长是宽2倍的长方形边长
float width = 12.6, length = width * 2;
//定义一个长方形的面积S,使用调用函数squareRectangle后的返回值来初始化S
float S = squareRectangle(width, length);
2
3
4
初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而复制的含义是把对象的当前值擦除,而以一个新值来替代。
# 列表初始化
C++中,以下4条语句都可以定义一个名为age的int变量并初始化为18.
int age = 18;
int age = {18};
int age{18};
int age(18);
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;
2
3
4
5
# 默认初始化
如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了默认值。默认值由变量类型决定,也会受定义变量的位置影响。
如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义域任何函数体之外的变量被初始化成0.定义在函数体内部的内置类型变量将不被初始化(uninitiated)。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类型将引发错误。
每个类各自决定其初始化对象的方式,即类的构造函数(Constructor)。
总之,需要注意的是,定义于函数体内的内置类型对象如果没有初始化,则其值未定义。类的对象如果没有显式初始化,则其值由类决定。
# 变量声明和定义的关系
分离时编译(separate compilation)机制允许将程序分割为若干个文件,每个文件可被独立编译。
为了支持分离式编译,C++将声明和定义区分开来。声明(declaration)使得名字为程序所知。定义(definition)负责创建与名字关联的实体。
extern int i; //声明i而非定义i
int j; //声明并定义j
2
任何包含了显示初始化的声明即成为定义。
extern double pi =3.14159; //定义
在函数体内部,如果试图初始化一个由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,(全局变量)
}
2
3
4
5
6
7
8
9
10
# 复合类型
复合类型(compound type)是指基于其他类型定义的类型。例如,引用和指针。
# 引用
引用为对象起了另一个名字。通过将声明符写成&d的形式定义引用类型,其中d是声明的变量名。
int ival = 2048;
int &refVal = ival; //refVal指向ival(是ival的另一个名字)
int &refVal2; //报错:应用必须被初始化
2
3
# 引用即别名
定义一个引用之后,对其所有操作都相当于操作与之绑定的对象。
refVal = 1024; //把1024赋值给refVal,即是赋值给了ival
int i = refVal; //于i = ival; 执行结果一样
// 正确: refVal3绑定到那个与refVal绑定的对象上,即refVal3绑定到iVal上
int &refVal3 = refVal;
int j = refVal;//正确: j被初始化为iVal的值
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都是引用
2
3
4
一般来说,引用类型与绑定对象要严格匹配。而且引用只能绑定在对象上,而不能是字面值或者表达式的计算结果。
int &refVal4 = 100; //错误: 引用只能绑定在对象上
double pi = 3.14;
int &refVal5 = pi; //错误: 引用的初始值应该为int对象
2
3
# 指针
指针(pointer)是“指向(point to)”另外一种类型的复合类型。
指针和引用相同点 | 指针和引用不同点 |
---|---|
实现了对其他对象的简介访问。 | 指针本身是一个对象,允许对指针的赋值和拷贝。 |
指针的生命周期内它可以先后指向几个不同的对象。 | |
指针无须在定义时赋初值。 |
和其他内置类型一样,在块用域定义的指针如果没有被初始化,也将拥有一个不确定的值。
定义指针类型时,如果一条语句定义了多个指针变量,每个变量前都要有符号*
。
int *p1; *p2; //p1, p2 都是指向int型对象的指针。
double dp, *dp2; //dp是double型的对象, dp2是指向double型对象的指针。
2
# 获取对象地址和通过指针访问对象
取地址符 | 解引用符 |
---|---|
& | * |
# 指针值
指针的值(即地址)应属于下列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;
2
引用本身不是对象,因此没有指向引用的指针。
指针是对象,所以有指向指针的指针和指向指针的引用。
# 指向指针的指针
int iVal = 1024;
int *pi = &iVal; //pi指向一个int型的数
int *ppi = π //ppi指向一个int型的指针
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
2
3
4
5
面对一条比较复杂的指针或引用的声明语句时,从右往左读有助于弄清楚它的真实含义。
# const 限定符
# 处理类型
# 自定义数据结构
- 01
- Linux系统移植(五)--- 制作、烧录镜像并启动Linux02-05
- 03
- Linux系统移植(三)--- Linux kernel移植02-05