跳转至

[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

ostream& operator<<(ostream& os, const pair<int, int>& p)
{
    return os << p.first << "\t" << p.second << endl;
}

1. 基本类型 (Built-in Types)

这些类型由语言本身提供,不需要用户定义。包括:

  • 整型 (Integer types):如 intshortlongunsigned int 等。
  • 浮点型 (Floating-point types):如 floatdouble
  • 字符型 (Character types):如 char
  • 布尔型 (Boolean types)bool,值为 truefalse
  • 空类型 (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++中常用的四种智能指针及其区别简介如下:

  1. unique_ptr

    • 独占式拥有权的智能指针,adopted对象只能被一个unique_ptr所拥有
    • 不能复制,只能移动(move semantics)
    • 删除时会自动释放对象
    • 不能释放获取(get)到的原始指针
  2. shared_ptr

    • 共享式拥有权的智能指针,多个shared_ptr可以共享同一个对象
    • 使用引用计数进行自动内存管理
    • 获取原始指针不会导致引用计数更改
    • 线程安全,可在多线程环境下使用
  3. weak_ptr

    • 弱引用智能指针,指向shared_ptr管理的对象
    • 不会影响对象的生命周期,不增加引用计数
    • 可以判断共享对象是否已被释放
  4. auto_ptr (C++03后废弃)

    • 采用所有权转移的方式
    • 失去作用域后会自动释放对象
    • 赋值或者复制会导致所有权的转移,无法实现共享

上述智能指针各有特点,使用场景不同,合理利用可以简化资源管理,提高代码安全性。

unique_ptr 基本使用

unique_ptr 是 C++11 引入的智能指针,用于管理动态分配的内存。你提到的代码片段是一个常见的用法,但有一些问题需要注意。

错误的用法:

unique_ptr<ProcessGroup> procGroups[3];
procGroups[i] = new ProcessGroup(procNum, maxMemSize);

unique_ptr 不能直接赋值给另一个指针。unique_ptr 是一个拥有所有权的智能指针,不能被简单地赋值或拷贝。你应该使用 std::make_unique 来创建 unique_ptr,或者直接初始化。

正确的用法:

  1. 初始化时使用 std::make_unique(推荐方式):
#include <memory> // 需要包含这个头文件

std::unique_ptr<ProcessGroup> procGroups[3];
procGroups[i] = std::make_unique<ProcessGroup>(procNum, maxMemSize);
  1. 直接使用 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同时管理同一个对象导致双重释放等问题。

例如下面的代码会报错:

std::unique_ptr<int> p1(new int(1));
std::unique_ptr<int> p2 = p1; // 错误,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, &copy_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)

  • typedefusing:用于为现有的类型定义别名。例如:
  • typedef unsigned long ulong;
    using ulong = unsigned long;
    

4. 模板类型 (Template Types)

  • 类模板 (Class Template):允许定义泛型类,可以处理不同的数据类型。例如:std::vector<T>
  • 函数模板 (Function Template):定义可用于多个类型的函数。例如:

    template <typename T>
    T add(T a, T b) {
        return a + b;
    }
    

条件启用/禁用

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_ifstd::is_same,以便在某些条件下启用或禁用特定的模板函数。让我们逐步分析这段代码,理解它的含义:

  1. typename U = T

这里定义了一个模板参数 U,默认值为 T,即类模板的主模板参数。UT 的别名,允许在模板中对 T 进行操作和约束。通过定义 U,这允许对 T 进行额外的类型检查或限制,而不改变 T 的默认行为。

  1. 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_iftype
  • 如果条件不成立,那么这个模板函数不会参与重载决议。

  • std::is_same<U, VirtualGuardImpl>::value

  • std::is_same<U, VirtualGuardImpl>::value 是一个类型比较的工具,它用来检查类型 UVirtualGuardImpl 是否相同。

  • 如果 UVirtualGuardImpl 是同一个类型,则 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_ifstd::is_same 的组合来限制模板的使用。具体来说,它确保 只有当 U 不是 VirtualGuardImpl 时,这个构造函数才能被实例化和使用。换句话说,这个构造函数在 U == VirtualGuardImpl 的情况下是被禁用的。

  1. 为什么使用这个限制?

这个限制的目的是防止在 VirtualGuardImpl 这种特殊情况下错误地调用这个构造函数。VirtualGuardImpl 可能有特殊的初始化方式,不适合使用普通的 device_index 进行构造。因此,代码通过 enable_if 禁用了在 UVirtualGuardImpl 时的这个构造函数,以确保程序不会在不合适的情况下调用它。

示例

假设 T 是某种具体的 DeviceGuardImpl 实现类,如 CUDAGuardImpl

InlineDeviceGuard<CUDAGuardImpl> guard(0);  // 可以正常实例化
InlineDeviceGuard<VirtualGuardImpl> guard(0);  // 编译失败,因为该构造函数被禁用

对于 CUDAGuardImpl 等普通实现,该构造函数可以通过 device_index 正常实例化。而对于 VirtualGuardImpl,该构造函数由于 enable_if 的限制被禁用,确保不会误用。

5. decltypeauto 类型

  • decltype:用于推导表达式的类型。例如:decltype(x) 将推导出 x 的类型。
  • auto:让编译器自动推断变量的类型。例如:auto x = 10; 会自动推断 xint

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::tuplestd::pair

  • 元组 (Tuple):存储多个不同类型的数据。例如:std::tuple<int, std::string, float> tup;
  • 对组 (Pair):存储两个可能是不同类型的值。例如:std::pair<int, std::string> p;

7. std::variantstd::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 三个部分是必须的,其余部分可以省略:

  1. 捕获子句 capture clause / lambda introducer
  2. 捕获子句用于捕获外部变量,使得匿名函数体可以使用这些变量,捕获的方法分为引用捕获值(拷贝)捕获两种,使用方法如下:
    1. [] 不捕获任何变量;
    2. 使用默认捕获模式来指示如何捕获 Lambda 体中引用的任何外部变量, 使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。
      1. [&] 按引用捕获所有外部变量;
        1. 不建议使用 2,3 这两种方式进行捕获(对性能影响较大),应该明确地指出需要按引用捕获的变量;
        2. 按引用捕获的变量(或按值捕获的指针),如果该引用变量(或指针指向的对象)在外部被析构,那么匿名函数中的引用变量(或指针)则会成为悬空引用/指针(Dangling Pointer)
      2. [=] 按值捕获所有外部变量
        1. 按值捕获的变量是 read-only (const) 的,只有当匿名函数的可变规格被显式声明为 mutable 的时候才可以修改按值捕获的变量;
        2. 按值捕获的变量的值在匿名函数生成的时候就已经确定了,如果在匿名函数生成后修改外部变量的值,则不会影响到匿名函数内被捕获的变量值
    3. [&, var] 默认按引用捕获,仅按值捕获 var;
    4. [=, &var] 默认按值捕获,仅按引用捕获 var;
    5. 由于使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。下列四种表达等价[&total, factor] [factor, &total] [&, factor] [=, &total]
  3. 参数列表 parameter list / lambda declarator
  4. 可变规格 mutable specification,被 mutable 修饰的匿名函数可以修改按值捕获的变量
  5. 异常设定 exception specification
  6. 尾随返回类型 trailing-return-type
  7. 匿名函数体 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 是作用域枚举,不能隐式转换为整数,提供更强的类型安全。例如:
    enum class Color { Red, Green, Blue };
    

参考文献

评论