主题
C++数据类型
基础数据类型
注意:以下数据类型的位数以64位操作系统为准。
| 基础数据类型 | Java | C# | C++ |
|---|---|---|---|
| 布尔类型 | bool | ||
| 8位字符类型 | x | char(取值范围:[-128, 127]或[0, 255]) | |
| 8位有符号字符类型 | signed char(取值范围:[-128, 127]) | ||
| 8位无符号字符类型 | unsigned char(取值范围:[0, 255]) | ||
| 16位Unicode字符类型 | char | char16_t | |
| 32位Unicode字符类型 | x | char32_t | |
| 宽字符类型 | x | wchar_t(2或4字节,存储Unicode字符) | |
| 8位有符号整数 | byte | sbyte | byte |
| 16位有符号整数 | short | ||
| 32位有符号整数 | int | ||
| 64位有符号整数 | long | long [long] | |
| 32位单精度浮点类型 | float | ||
| 64位双精度浮点类型 | double | ||
| 扩展精度浮点类型 | x | long double(8、12或16字节) | |
| 精准浮点类型 | x | decimal | x |
| 8位无符号整数 | sbyte | unsigned byte | |
| 16位无符号整数 | ushort | unsigned short | |
| 32位无符号整数 | uint | unsigned int | |
| 64位无符号整数 | ulong | unsigned long [long] | |
- Java没有有无符号之分,都是有符号的,取值范围从负数到正数。
- C++的
long类型可能是4字节或8字节,具体取决于平台,但long long一定是8字节。
复合数据类型
数组
数组名表示数组首元素的地址,而&数组名表示整个数组的首地址。虽然值相同,但含义不同。- 数组名可看做是一个指向不变的指针(相关内容见const关键字)。
c++
int arr1[3] = {1, 2, 3};
cout << "arr1 = " << arr1 << endl;
cout << "&arr1 = " << &arr1 << endl;
cout << "arr1 + 1 = " << arr1 + 1 << endl; // 第二个元素地址,相差4个字节(int类型)
cout << "&arr1 + 1 = " << &arr1 + 1 << endl; // 相差整个数组长度的字节数,即3*4字节
cout << "*(arr1 + 1) = " << *(arr1 + 1) << endl; // 第二个元素的值定义数组的长度必须是编译时常量。常见的编译时常量:
- 字面量
- const常量
- constexpr常量
- 枚举常量
- 常量表达式
字符数组
字符数组是C风格的字符串。
- 字符数组末尾需要有结束标识符号'\0'。
- 字符数组是可以修改的。
c++
// 系统自动在末尾加上'\0',所以数组长度为4
char name1[] = "dog";
// {}形式初始化,系统不会在末尾加上'\0',需要自己加上。
char name2[] = { 'd', 'o', 'g', '\0'};
// 字符串长度小于数组长度,数组剩余空间全被初始化为'\0'
char name3[10] = "dog";
cout << name1 << " " << strlen(name1) << " " << sizeof(name1) << endl; // 3 3 4
cout << name2 << " " << strlen(name2) << " " << sizeof(name2) << endl; // 3 3 4
cout << name3 << " " << strlen(name3) << " " << sizeof(name3) << endl; // 3 3 10
name3[3] = 's';
name3[5] = 'x';
cout << "modified name3 = " << name3 << endl; // dogs提示
在获取了字符串的长度后,切记长度+1来初始化字符数组的长度!
指针
指针是一个变量,其存储的是值的地址,而不是值本身。
c++
int x = 10;
int* ptr = &x;
int** pptr = &ptr; // 指针是变量,所以它也有地址,pptr是二级指针,即指向指针的指针。
// &x表示x的地址,即ptr变量的值。
cout << "&x = " << x << ", ptr = " << ptr << endl;
// *运算符用于指针表示解除引用,可以获取指针所指向的地址空间的值。即x = *ptr。
cout << "x = " << x << ", *ptr = " << *ptr << endl;指针多用于使用new在堆中动态分配内存,如:
c++
int* p1 = new int(10);
int* p2 = new int[10];
// do something;
delete p1; // 释放内存
delete[] p2; // 使用new[]为数组分配内存,则应使用delete[]释放内存。- new分配的内存用delete来释放;new[]为数组分配的内存用delete[]来释放。
- 不要使用delete来释放不是new分配的内存
- 不要使用delete释放同一内存块两次。
- 对空指针应用delete是安全的。
常见指针问题
- 野指针:未被初始化或已经被释放的指针,其指向的内存地址是未知的。声明指针时要初始化为nullptr或有效的地址。
- 悬挂指针:指针指向的内存空间已经被释放,但指针仍然指向该内存空间。新手常见的错误是函数返回了局部变量(栈空间)的指针。
c++
static int* buildX(int n=1)
{
int x = 10 * n; // 在栈空间上分配的内存,函数执行完之后将被释放。
cout << "&x=" << &x << endl;
return &x;
}
static int* buildY(int n=1)
{
int* y = new int(10 * n); // 在堆空间上分配的内存,由程序员自己释放。
cout << "y=" << y << endl;
return y;
}
int main()
{
int* xp = buildX(10); // xp是悬挂指针,所指向的内存空间已经被释放
cout << "xp=" << xp << ", *xp=" << *xp << endl;
int* yp = buildY(10);
cout << "yp=" << yp << ", *yp=" << *yp << endl;
delete yp; // 释放指针所指向的内存空间,但指针本身的值还是指向该内存空间。
yp = nullptr; // 避免悬挂指针
}指向函数的指针
最简形式:void (*p_func)()
较为复杂的函数指针数组:
c++
const double* (*pf[3])(const double* arr, int n) = { func1, func2, func3 };
const double* (*(*pf2)[3])(const double* arr, int n) = &pf;解读
首先要知道运算符的优先级:() > [] > *。
引用【左值引用】
引用是已定义的变量的别名,其主要用途是作为函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本,提高效率。
c++
int a = 10;
int &ra = a; // ra是a的引用
int* pa = &a; // pa是a的指针提示
必须在声明引用变量时进行初始化,并且不能重新绑定到另一个变量。
枚举
C++11引入了增强型枚举,枚举值限定在类作用域内,避免了命名冲突。
c++
enum class State : int
{
Unknown = 0,
Success = 1,
Failed = 2,
Running = 3
};
auto state = State::Running;尽量少用传统枚举类型,但有些场合挺好用,比如在类内定义编译时常量:
c++
class Stack
{
public:
enum { MAX_SIZE = 100 };
// static constexpr int MAX_SIZE = 100;
int arr[MAX_SIZE];
}结构体
C++的结构体和类基本相同,唯一的区别是默认的访问权限:结构体成员和继承的默认访问权限都是public;而类成员和继承的默认访问权限都是private。
结构体主要用于对数据的简单封装,较为复杂的业务逻辑应该使用类。
c++
struct Point
{
float x;
float y;
void display() const
{
cout << "(" << x << ", " << y << ")" << endl;
}
};
void test_struct()
{
Point point = {1.0, 2.5};
point.display();
}联合体
多个成员共享同一块内存。
标准库数据类型
string
string类是对字符数组的封装,支持自动扩容,提供了更加简单的API。
pair
std::pair<T1, T2>是一个模板类,适用于需要将两个元素(可以不同类型)组合在一起的场景。
c++
#include <utility>
using namespace std;
std::pair<int, char> tuple(1, 'a');
cout << "first val:" << tuple.first << endl;
cout << "second val:" << tuple.second << endl;tuple
元组是pair的泛化,支持任意数量的元素组合。用法比较奇怪,用的时候去查吧!
容器
容器是存储其它对象的对象,储存的对象类型必须是可复制构造的和可赋值的。
序列容器
| 基本容器 | 描述 | 内存结构 | 固定大小 | 使用场景 |
|---|---|---|---|---|
| array | 固定数组 | 连续存储 | 是 | 数组大小固定的随机索引访问 |
| vector | 动态数组 | 连续存储 | 否 | 动态扩容且随机索引访问 |
| list | 双向链表 | 非连续存储 | 否 | 双向遍历,随机插入或删除元素 |
| forward_list | 单向链表 | 非连续存储 | 否 | 单向遍历,随机插入或删除元素 |
| deque | 双端队列 | 分段连续内存 | 否 | 头尾两端频繁插入或删除元素 |
容器适配器是基于基本容器的简化接口,它们提供了一种特定的接口来访问底层容器的数据,其底层容器通常可以有多种选择。
| 容器适配器 | 描述 | 特点 | 默认底层容器 |
|---|---|---|---|
| stack | 栈 | 后进先出 | deque |
| queue | 队列 | 先进先出 | deque |
| priority_queue | 优先级队列 | 快速访问优先级最高的元素 | vector实现的最大堆 |
关联容器
基于键值对的容器,它们通过键来存储和访问元素。关联容器分为集合和映射,集合使用红黑树(对数级时间复杂度),映射使用哈希表(常数级时间复杂度)。
| 关联容器 | 描述 |
|---|---|
| set | 有序集合 |
| unordered_set | 无序集合 |
| multipset | 允许重复值的有序集合 |
| unordered_multiset | 允许重复值的无序集合 |
| map | 有序映射 |
| unordered_map | 无序映射 |
| multipmap | 同一个键可关联多个值的有序映射 |
| unordered_multimap | 同一个键可关联多个值的无序映射 |
数据类型转换
| 类型转换 | 描述 |
|---|---|
静态类型转换static_cast | 将一种数据类型的值强制转换为另一种近似的数据类型的值。 |
动态类型转换dynamic_cast | 通常用于将一个基类指针或引用转换为派生类指针或引用。指针类型转换失败返回空指针;引用类型转换失败会抛出std::bad_cast异常 |
常量转换const_cast | 将const类型的对象转换为非const类型的对象,不改变对象类型。 |
重新解释转换reinterpret_cast | 将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。 |