[C++ Basic] Types
导言
- 除开int、char、float、double、bool、void基本类型,还有枚举、结构体、auto、lambda
User-Defined Types, UDTs¶
枚举类型 enum¶
class MemComponent
{
public:
enum component_t
{
INVALID_MEM_COMPONENT = 0,
MIN_MEM_COMPONENT,
CORE = MIN_MEM_COMPONENT,
FIRST_LEVEL_CACHE,
L1_ICACHE = FIRST_LEVEL_CACHE,
L1_DCACHE,
L2_CACHE,
L3_CACHE,
L4_CACHE,
/* more, unnamed stuff follows.
make sure that MAX_MEM_COMPONENT < 32 as pr_l2_cache_block_info.h contains a 32-bit bitfield of these things
*/
LAST_LEVEL_CACHE = 20,
TAG_DIR,
NUCA_CACHE,
DRAM_CACHE,
DRAM,
MAX_MEM_COMPONENT = DRAM,
NUM_MEM_COMPONENTS = MAX_MEM_COMPONENT - MIN_MEM_COMPONENT + 1
};
};
// Usage
MemComponent::component_t A = MemComponent::LAST_LEVEL_CACHE;
结构体¶
常见结构体使用
struct GrassTp {
int x, y;
int dist;
//重载运算符 a < other
bool operator < (const GrassTp& other) const {
if (dist == other.dist) {
if (x == other.x)
return y < other.y;
else
return x < other.x;
}
return dist < other.dist;
}
};
struct status{
int x, y;
int direct;
//初始化
status(int x, int y) : x(x), y(y) { direct=0;}
//重载运算符, 结构体能直接赋值(但是结构体内有指针的时候,可能指针指向空间在其他结构体free的时候释放了)
// a == b
bool operator==(const status b) const
{
return (this->x == b.x) && (this->y == b.y) &&
(this->direct == b.direct);
}
//允许在结构体当中定义函数, struct中定义的函数和变量都是默认为public的
void forward(){
x += direction[direct][0];
y += direction[direct][1];
}
};
//初始化1
status cur(0,0);
status* cur = new status(0,0);
//初始化2
struct SegmentTp {
int x1, y1;
int x2, y2;
};
SegmentTp segment1 = (SegmentTp){midx - day, midy - day, midx - day, midy + day};
自定义结构体cout
1. 基本类型 (Built-in Types)¶
这些类型由语言本身提供,不需要用户定义。包括:
- 整型 (Integer types):如
int
、short
、long
、unsigned int
等。 - 浮点型 (Floating-point types):如
float
、double
。 - 字符型 (Character types):如
char
。 - 布尔型 (Boolean types):
bool
,值为true
或false
。 - 空类型 (Void type):
void
,表示无类型,用于函数不返回值时。
2. 复合类型 (Compound Types)¶
复合类型可以通过将多个基本类型或其他类型组合来创建。包括:
- 数组 (Array):固定大小的元素序列,所有元素的类型相同。例如:
int arr[10];
。 - 指针 (Pointer):存储内存地址的类型,表示对另一种类型的间接访问。例如:
int* ptr;
。 - 引用 (Reference):另一种对变量的别名。例如:
int& ref = x;
。 - 函数类型 (Function type):函数的声明或定义,也可以将函数作为返回值或参数传递。例如:
int foo(int a);
。 - 联合体 (Union):类似
struct
,但在任何时刻只能存储一个成员。例如:union Data { int i; float f; };
。
四种智能指针¶
C++中常用的四种智能指针及其区别简介如下:
-
unique_ptr
- 独占式拥有权的智能指针,adopted对象只能被一个unique_ptr所拥有
- 不能复制,只能移动(move semantics)
- 删除时会自动释放对象
- 不能释放获取(get)到的原始指针
-
shared_ptr
- 共享式拥有权的智能指针,多个shared_ptr可以共享同一个对象
- 使用引用计数进行自动内存管理
- 获取原始指针不会导致引用计数更改
- 线程安全,可在多线程环境下使用
-
weak_ptr
- 弱引用智能指针,指向shared_ptr管理的对象
- 不会影响对象的生命周期,不增加引用计数
- 可以判断共享对象是否已被释放
-
auto_ptr (C++03后废弃)
- 采用所有权转移的方式
- 失去作用域后会自动释放对象
- 赋值或者复制会导致所有权的转移,无法实现共享
上述智能指针各有特点,使用场景不同,合理利用可以简化资源管理,提高代码安全性。
unique_ptr 基本使用
unique_ptr
是 C++11 引入的智能指针,用于管理动态分配的内存。你提到的代码片段是一个常见的用法,但有一些问题需要注意。
错误的用法:
unique_ptr
不能直接赋值给另一个指针。unique_ptr
是一个拥有所有权的智能指针,不能被简单地赋值或拷贝。你应该使用 std::make_unique
来创建 unique_ptr
,或者直接初始化。
正确的用法:
- 初始化时使用
std::make_unique
(推荐方式):
#include <memory> // 需要包含这个头文件
std::unique_ptr<ProcessGroup> procGroups[3];
procGroups[i] = std::make_unique<ProcessGroup>(procNum, maxMemSize);
- 直接使用
new
创建unique_ptr
(不推荐):
#include <memory> // 需要包含这个头文件
std::unique_ptr<ProcessGroup> procGroups[3];
procGroups[i] = std::unique_ptr<ProcessGroup>(new ProcessGroup(procNum, maxMemSize));
需要的头文件:
#include <memory>
:这是使用std::unique_ptr
的必备头文件。
unique_ptr 不可以复制
指它不支持复制构造函数和复制赋值操作符。
这ensure了一个对象只能被一个unique_ptr独占式拥有,避免出现两个unique_ptr同时管理同一个对象导致双重释放等问题。
例如下面的代码会报错:
但是,unique_ptr支持移动(move)语义。移动语义是C++11新增的特性,它可以避免复制的开销。
例如:
std::unique_ptr<int> p1(new int(1));
std::unique_ptr<int> p2 = std::move(p1); // ok, p1移动到p2
// p1现在为空
move函数会将p1的内容“转移”给p2,此后p1为空,p2获得了对象的独占式拥有权。
移动语义可以将对象的资源“传递”给另一个指针,避免拷贝,提高性能。
这个特性可以利用std::move函数实现。所以unique_ptr可以移动但不可以复制。
使用shared_ptr来管理对象的生命周期
可以这样修改:
#include <memory>
class TrieWithPos {
public:
TrieWithPos(TreeNode* root, int pos) : pos(pos) {
if (root->left) {
left = std::make_shared<TrieWithPos>(root->left, 2 * pos);
}
if (root->right) {
right = std::make_shared<TrieWithPos>(root->right, 2 * pos + 1);
}
}
private:
int pos;
std::shared_ptr<TrieWithPos> left;
std::shared_ptr<TrieWithPos> right;
};
主要改动:
- left和right成员改为shared_ptr类型
- 使用make_shared来创建对象,自动管理生命周期
- 析构函数不再需要手动delete
- shared_ptr能自动释放对象,不需担心内存泄漏
这样就可以通过shared_ptr来简化生命周期的管理,使代码更加安全。
二维指针与二维数组¶
声明
int **p;
p = (int**)malloc(sizeof(int*)*m); //开辟行
for(i = 0; i < m; i++)
{
*(p+i) = (int*)malloc(sizeof(int)*n);//开辟列
}
相互复制
#define N 2048
int a[N][N] = {0};
int (*A)[2048];
A=a;
A[i][j] // 如果A声明没有2048,就int **A那么A[i][j]不知道每行多少个元素
二维vector传参¶
vector<vector<double>> copy_off_tree_edge;
adjust_similarity_tree(i, &bfs_process1, &bfs_process2, similarity_tree, ©_off_tree_edge);
void adjust_similarity_tree(int i, std::vector<int> *bfs_process1, std::vector<int> *bfs_process2 ,\
int *similarity_tree, vector<vector<double>> *copy_off_tree_edge){
...
(* copy_off_tree_edge)[z][0]
3. 类型别名 (Type Aliases)¶
typedef
和using
:用于为现有的类型定义别名。例如:
4. 模板类型 (Template Types)¶
- 类模板 (Class Template):允许定义泛型类,可以处理不同的数据类型。例如:
std::vector<T>
。 -
函数模板 (Function Template):定义可用于多个类型的函数。例如:
条件启用/禁用¶
PyTorch
template <
typename U = T,
typename = typename std::enable_if<
!std::is_same<U, VirtualGuardImpl>::value>::type>
explicit InlineDeviceGuard(DeviceIndex device_index)
: InlineDeviceGuard(Device(U::static_type, device_index)) {}
这段代码使用了SFINAE(Substitution Failure Is Not An Error)技巧,结合了std::enable_if
和std::is_same
,以便在某些条件下启用或禁用特定的模板函数。让我们逐步分析这段代码,理解它的含义:
typename U = T
这里定义了一个模板参数 U
,默认值为 T
,即类模板的主模板参数。U
是 T
的别名,允许在模板中对 T
进行操作和约束。通过定义 U
,这允许对 T
进行额外的类型检查或限制,而不改变 T
的默认行为。
typename = typename std::enable_if<...>::type
这个部分是SFINAE的核心部分,使用了 std::enable_if
来控制模板的启用条件。
std::enable_if
是一个模板工具,它有两个模板参数:一个条件(布尔值)和一个返回的类型。通常用于条件约束。- 如果条件为真(
true
),std::enable_if
定义了一个内嵌类型type
,并且模板实例化会成功; -
如果条件为假(
false
),则type
不存在,实例化会失败,编译器会忽略这个模板,转而寻找其他可用的模板。 -
typename = typename
:这个语法定义了一个匿名的类型参数,它的类型由std::enable_if
的结果决定。 - 如果
std::enable_if
的条件成立,则typename
会被成功替代为std::enable_if
的type
。 -
如果条件不成立,那么这个模板函数不会参与重载决议。
-
std::is_same<U, VirtualGuardImpl>::value
-
std::is_same<U, VirtualGuardImpl>::value
是一个类型比较的工具,它用来检查类型U
和VirtualGuardImpl
是否相同。 -
如果
U
和VirtualGuardImpl
是同一个类型,则std::is_same<U, VirtualGuardImpl>::value
返回true
,否则返回false
。 -
!std::is_same<U, VirtualGuardImpl>::value
-
这个条件表达式是在
std::is_same<U, VirtualGuardImpl>::value
的基础上取反,表示 “U
不是VirtualGuardImpl
” 的情况。 -
如果
U
不是VirtualGuardImpl
,则条件成立,std::enable_if
会定义type
,允许这个模板函数的实例化。 -
整体解释
这个部分的整体逻辑是通过 std::enable_if
和 std::is_same
的组合来限制模板的使用。具体来说,它确保 只有当 U
不是 VirtualGuardImpl
时,这个构造函数才能被实例化和使用。换句话说,这个构造函数在 U == VirtualGuardImpl
的情况下是被禁用的。
- 为什么使用这个限制?
这个限制的目的是防止在 VirtualGuardImpl
这种特殊情况下错误地调用这个构造函数。VirtualGuardImpl
可能有特殊的初始化方式,不适合使用普通的 device_index
进行构造。因此,代码通过 enable_if
禁用了在 U
是 VirtualGuardImpl
时的这个构造函数,以确保程序不会在不合适的情况下调用它。
示例
假设 T
是某种具体的 DeviceGuardImpl
实现类,如 CUDAGuardImpl
:
InlineDeviceGuard<CUDAGuardImpl> guard(0); // 可以正常实例化
InlineDeviceGuard<VirtualGuardImpl> guard(0); // 编译失败,因为该构造函数被禁用
对于 CUDAGuardImpl
等普通实现,该构造函数可以通过 device_index
正常实例化。而对于 VirtualGuardImpl
,该构造函数由于 enable_if
的限制被禁用,确保不会误用。
5. decltype
和 auto
类型¶
decltype
:用于推导表达式的类型。例如:decltype(x)
将推导出x
的类型。auto
:让编译器自动推断变量的类型。例如:auto x = 10;
会自动推断x
为int
。
decltype¶
// 不是lambdas的,定义函数写法
auto hash = [](const std::pair<int, int>& p){ return p.first * 31 + p.second; };
std::unordered_set<std::pair<int, int>, decltype(hash)> u_edge_(8, hash);
decltype 生成指定表达式的类型,有点类似auto。
6. std::tuple
和 std::pair
¶
- 元组 (Tuple):存储多个不同类型的数据。例如:
std::tuple<int, std::string, float> tup;
。 - 对组 (Pair):存储两个可能是不同类型的值。例如:
std::pair<int, std::string> p;
。
7. std::variant
和 std::any
¶
std::variant
:可以存储多个类型中的一个值,但在任何时刻只能存储一个。例如:std::variant<int, float> v;
。std::any
:可以存储任意类型的值,允许更动态地处理类型。例如:std::any a = 42;
。
8. Lambda 表达式类型¶
- Lambda 表达式:一种匿名函数,可以捕获所在作用域中的变量并执行。例如:
auto f = [](int x) { return x + 1; };
。
匿名函数由以下几个部分组成,其中只有 1, 2, 6 三个部分是必须的,其余部分可以省略:
- 捕获子句 capture clause / lambda introducer
- 捕获子句用于捕获外部变量,使得匿名函数体可以使用这些变量,捕获的方法分为引用捕获和值(拷贝)捕获两种,使用方法如下:
[]
不捕获任何变量;- 使用默认捕获模式来指示如何捕获 Lambda 体中引用的任何外部变量, 使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。
[&]
按引用捕获所有外部变量;不建议使用 2,3 这两种方式进行捕获(对性能影响较大),应该明确地指出需要按引用捕获的变量;- 按引用捕获的变量(或按值捕获的指针),如果该引用变量(或指针指向的对象)在外部被析构,那么匿名函数中的引用变量(或指针)则会成为悬空引用/指针(Dangling Pointer)
[=]
按值捕获所有外部变量- 按值捕获的变量是 read-only (const) 的,只有当匿名函数的可变规格被显式声明为 mutable 的时候才可以修改按值捕获的变量;
- 按值捕获的变量的值在匿名函数生成的时候就已经确定了,如果在匿名函数生成后修改外部变量的值,则不会影响到匿名函数内被捕获的变量值
[&, var]
默认按引用捕获,仅按值捕获 var;[=, &var]
默认按值捕获,仅按引用捕获 var;- 由于使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。下列四种表达等价
[&total, factor] [factor, &total] [&, factor] [=, &total]
- 参数列表 parameter list / lambda declarator
- 可变规格 mutable specification,被 mutable 修饰的匿名函数可以修改按值捕获的变量
- 异常设定 exception specification
- 尾随返回类型 trailing-return-type
- 匿名函数体 lambda body
可以公用grid等数组,而且相当于private
#include<functional>
int getMaximumGold(vector<vector<int>>& grid) {
// 将lambda表达式赋值给 函数变量 dfs
function<void(int, int, int)> dfs = [&](int x, int y, int gold) {
gold += grid[x][y];
……
grid[x][y] = rec;
};
dfs(10, 30, 0);
}
//C++11 using lambdas
sort(arr.begin(), arr.end(),
[](const pair<int, char> & p1, const pair<int, char> & p2) {
return p1.first > p2.first;
}
);
9. enum class
(Scoped Enum)¶
- 与传统的
enum
不同,enum class
是作用域枚举,不能隐式转换为整数,提供更强的类型安全。例如: