C++ 报错:'xxx' has not been declared

‘xxx’ has not been declared

原因是头文件互相包含

1
2
3
[build] D:/a-mycode/C++/DDZ-NET/client-ddz/thread/include/Communication.h:87:5: error: 'DataManager' has not been declared
[build] DataManager::getInstance()->getCommunication()->setCards(cards, last3Cards);
[build] ^~~~~~~~~~~

首先我们需要知道为什么会有头文件互相包含这种错误,假如我们有两个类,类A和类B,在类A的头文件中包含类B的头文件,这意味着,类B是先于类A出现的,即类B需要先于类A被定义。如果类B也包含了类A的头文件,这就出现问题了,因为类A要求类B先出现,而类B又要求类A先出现,这就出现了循环,使编译器无法正确解析类型定义。

具体原因是:C++预处理器会将头文件内容直接插入到包含它的文件中,当存在循环依赖时,编译器会在某一点找不到完整的类型定义

解决方案:删除其中一个头文件,防止头文件互相包含

C++单例类实现

一、懒汉模式

懒汉模式存在线程安全问题

1、双重检测锁(一坨)

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef DOUBLE_CHECKED_SINGLETON_H
#define DOUBLE_CHECKED_SINGLETON_H

#include <iostream>
#include <mutex>
#include <atomic>
#include <memory>

class DoubleCheckedSingleton {
public:
// 禁用复制构造和赋值运算符
DoubleCheckedSingleton(const DoubleCheckedSingleton&) = delete;
DoubleCheckedSingleton& operator=(const DoubleCheckedSingleton&) = delete;

// 获取单例实例
static DoubleCheckedSingleton* getInstance() {
DoubleCheckedSingleton* p = instance.load(std::memory_order_acquire);
if (p == nullptr) { // 第一次检查(无锁)
std::lock_guard<std::mutex> lock(mutex);
p = instance.load(std::memory_order_relaxed);
if (p == nullptr) { // 第二次检查(有锁)
p = new DoubleCheckedSingleton();
instance.store(p, std::memory_order_release);
}
}
return p;
}

// 释放单例(在程序结束时调用)
static void destroyInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (instance != nullptr) {
delete instance.load();
instance = nullptr;
}
}

// 析构函数
~DoubleCheckedSingleton() {
std::cout << "双重检测锁单例被销毁" << std::endl;
}

private:
// 私有构造函数,防止外部实例化
DoubleCheckedSingleton() = default;

static std::atomic<DoubleCheckedSingleton*> instance;
static std::mutex mutex;
};

// 静态成员变量初始化
std::atomic<DoubleCheckedSingleton*> DoubleCheckedSingleton::instance{nullptr};
std::mutex DoubleCheckedSingleton::mutex;

#endif // DOUBLE_CHECKED_SINGLETON_H

双重检测锁在C++中存在内存顺序问题。例如,instance = new DoubleCheckedSingleton()这行代码在编译器优化下可能会重排序为:

  1. 分配内存
  2. 将指针赋值给instance变量
  3. 构造对象

如果执行顺序变为1→2→3,在进行到2时实例已经非空,但实例还未构建,如果此时有第三个线程想要获取实例,就会返回一个空的实例!

2、静态局部变量(最推荐的方式)

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
#ifndef STATIC_LOCAL_SINGLETON_H
#define STATIC_LOCAL_SINGLETON_H

#include <iostream>

class StaticLocalSingleton {
public:
// 禁用复制构造和赋值运算符
StaticLocalSingleton(const StaticLocalSingleton&) = delete;
StaticLocalSingleton& operator=(const StaticLocalSingleton&) = delete;

// 获取单例实例
static StaticLocalSingleton& getInstance() {
// C++11保证静态局部变量的初始化是线程安全的
static StaticLocalSingleton instance;
return instance;
}

// 析构函数
~StaticLocalSingleton() {
std::cout << "静态局部变量单例被销毁" << std::endl;
}

private:
// 私有构造函数
StaticLocalSingleton() = default;
};

#endif // STATIC_LOCAL_SINGLETON_H

二、饿汉模式

在程序加载时就创建,天然线程安全,无需加锁

1、引用方式

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
33
#ifndef EAGER_SINGLETON_H
#define EAGER_SINGLETON_H

#include <iostream>

class EagerSingleton {
public:
// 禁用复制构造和赋值运算符
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;

// 获取单例实例
static EagerSingleton& getInstance() {
return instance;
}

// 析构函数
~EagerSingleton() {
std::cout << "饿汉单例被销毁" << std::endl;
}

private:
// 私有构造函数
EagerSingleton() = default;

// 静态实例(在程序加载时就创建)先创建对象,不管用与不用
static EagerSingleton instance;
};

// 静态成员变量初始化
EagerSingleton EagerSingleton::instance;

#endif // EAGER_SINGLETON_H

2、指针方式

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
33
34
35
36
37
38
39
40
41
#ifndef EAGER_SINGLETON_PTR_H
#define EAGER_SINGLETON_PTR_H

#include <iostream>

class EagerSingletonPtr {
public:
// 禁用复制构造和赋值运算符
EagerSingletonPtr(const EagerSingletonPtr&) = delete;
EagerSingletonPtr& operator=(const EagerSingletonPtr&) = delete;

// 获取单例实例
static EagerSingletonPtr* getInstance() {
return instance;
}

// 释放单例资源(解决内存泄漏问题)
static void destroyInstance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}

// 析构函数
~EagerSingletonPtr() {
std::cout << "饿汉单例被销毁" << std::endl;
}

private:
// 私有构造函数
EagerSingletonPtr() = default;

// 静态实例(在程序加载时就创建)先创建对象,不管用与不用
static EagerSingletonPtr* instance;
};

// 静态成员变量初始化
EagerSingletonPtr* EagerSingletonPtr::instance = new EagerSingletonPtr();

#endif // EAGER_SINGLETON_PTR_H

三、补充

C++单例类初始化顺序会导致一些问题,详细可以查看这篇文章

redis安装和基本使用

一、安装redis

GitHub地址:https://github.com/redis/redis

下载压缩包:https://github.com/redis/redis/archive/refs/tags/7.4.2.tar.gz

1
2
3
4
tar -zxvf 7.4.2.tar.gz
cd redis-7.4.2/
make -j$(nproc)
sudo make install
  • 测试

    • 启动服务器
    1
    redis-server
    • 连接服务器
    1
    redis-cli

二、安裝hiredis

GitHub地址:https://github.com/redis/hiredis

下载压缩包:https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz

1
2
3
4
tar -zxvf v1.2.0.tar.gz
cd hiredis-1.2.0
make -j$(nproc)
sudo make install

三、安装redis plus plus

GitHub地址:https://github.com/sewenew/redis-plus-plus

下载压缩包:https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz

1
2
3
4
5
6
tar -zxvf 1.3.13.tar.gz
cd redis-plus-plus-1.3.13
mkdir build && cd build
cmake -G "Unix Makefiles" ..
make -j$(nproc)
sudo make install

四、CMake使用

1
2
3
4
#添加Redis++
include_directories(/usr/local/include/sw/redis++)
link_directories(/usr/local/lib)
target_link_libraries(${PROJECT_NAME} redis++ hiredis)

C++ 报错:fstream打开相对路径文件发生错误

问题:C++使用fstream打开相对路径文件发生错误,只能使用绝对路径,下面是项目结构

image-20250318231044365

如上图,原以为相对路径是相对于二进制文件(server-ddz.exe)的路径,所以使用相对路径../config/config.json,但是经过测试,还是相对路径不正确的问题。

下面的代码是查看程序运行时的路径

1
2
3
4
5
6
char buffer[256];
char* val = getcwd(buffer, sizeof(buffer));
if (val)
{
std::cout << buffer << std::endl;
}

经过测试,发现运行时的路径是这样的:/home/jianzhe/code/server-ddz,并不在bin目录,所以导致相对路径错误。

正确的相对路径应该是:config/config.json

C++ 报错:‘xxx’ does not name a type

‘xxx’ does not name a type

原因是由于头文件互相包含(循环依赖)导致的

1
2
3
/mnt/d/a-mycode/C++/DDZ-NET/server-ddz/tcp/include/TcpConnection.h:31:5: error: ‘Communication’ does not name a type
31 | Communication* m_reply = nullptr;
| ^~~~~~~~~~~~~

首先我们需要知道为什么会有头文件互相包含这种错误,假如我们有两个类,类A和类B,在类A的头文件中包含类B的头文件,这意味着,类B是先于类A出现的,即类B需要先于类A被定义。如果类B也包含了类A的头文件,这就出现问题了,因为类A要求类B先出现,而类B又要求类A先出现,这就出现了循环,使编译器无法正确解析类型定义。

具体原因是:C++预处理器会将头文件内容直接插入到包含它的文件中,当存在循环依赖时,编译器会在某一点找不到完整的类型定义

解决方案:删除其中一个头文件,防止头文件互相包含

ubuntu彻底卸载MySQL

1. 停止MySQL服务

1
sudo systemctl stop mysql

2. 卸载MySQL软件包

删除所有MySQL相关的软件包(根据你的安装版本调整包名):

1
sudo apt purge mysql-server mysql-client mysql-common mysql-server-core-* mysql-client-core-*

3. 删除残留文件和目录

手动删除MySQL的配置、数据和日志文件:

1
sudo rm -rf /etc/mysql /var/lib/mysql /var/log/mysql

4. 清理依赖和缓存

1
2
sudo apt autoremove  # 删除不再需要的依赖包
sudo apt autoclean # 清理软件包缓存

5. 检查是否彻底删除

验证是否还有残留的MySQL文件:

1
dpkg -l | grep mysql  # 检查是否有未卸载的包

如果仍有残留,手动删除相关文件

验证卸载是否成功

运行 mysql 命令:

1
mysql

提示 Command 'mysql' not found,则表示卸载成功。

如何在CMake中正确的添加库文件:MSVC与MinGW混用陷阱

记一次在windows+cmake+MinGW环境下使用openssl添加库文件错误的经历

一、库文件介绍

OpenSSL下载路径:https://slproweb.com/products/Win32OpenSSL.html

image-20250314170922728

安装后的库文件是这样的

  • bin目录

image-20250314171205286

  • lib目录

image-20250314171309001

可以看到,这是一个使用MSVC编译套件编译的库

二、遇到的问题

根据经验,添加库文件通常需要”掐头去尾”——即去掉lib前缀和.lib后缀。例如,对于libcrypto.lib,我尝试这样添加:

1
target_link_libraries(${PROJECT_NAME} crypto)

结果报错:cannot find -lcrypto

后面我尝试指定完整文件名

1
target_link_libraries(${PROJECT_NAME} libcrypto.lib)

依然报错:cannot find -lcrypto

三、问题分析

3.1 不同系统的库命名约定

  • MSVC (Windows): 库通常命名为crypto.liblibcrypto.lib
  • GCC/MinGW/Linux: 库通常命名为libcrypto.solibcrypto.a

3.2、CMake的库名处理机制

CMake会根据使用的生成器和编译器自动处理库名转换:

  1. 使用不带前缀和后缀的名称(crypto):
    • MSVC生成器会查找crypto.lib
    • GCC/MinGW生成器会查找libcrypto.alibcrypto.dll.a
  2. 使用带lib前缀的名称(libcrypto):
    • MinGW会查找libcrypto.alibcrypto.dll.a
    • MSVC会查找libcrypto.lib
  3. 使用完整文件名带扩展名(libcrypto.lib):
    • MinGW会错误地查找liblibcrypto.lib.aliblibcrypto.lib.dll.a

3.3、问题根源

跨工具链使用导致的命名不匹配:我使用MinGW编译套件,但链接了MSVC构建的OpenSSL库。

  1. 当使用crypto引用时:

    • MinGW尝试查找libcrypto.alibcrypto.dll.a,但实际文件是libcrypto.lib

    • “掐头去尾”规则只适用于同一工具链

  2. 当使用libcrypto.lib引用时:

    • MinGW尝试查找liblibcrypto.lib.aliblibcrypto.lib.dll.a,这显然不存在

四、解决方案

  1. 方案一:使用不带扩展名的库名

    1
    target_link_libraries(${PROJECT_NAME} Qt5::Core libcrypto)

    MinGW会正确地查找libcrypto.alibcrypto.dll.a

  2. 方案二:使用绝对路径直接指定库文件

    1
    target_link_libraries(${PROJECT_NAME} ${OPENSSL_INSTALL_DIR}/lib/libcrypto.lib)

总结:当使用与库构建时不同的编译器工具链时,库命名约定可能不匹配,需要特别注意库的引用方式。

C++ 报错:cannot found -lxxx

cannot found -lxxx动态库

找不到动态库文件,排查方向

  1. 在当前CMakeLists.txt文件下使用link_directories指定动态库路径

    1
    2
    set(PROTOBUF_PATH "D:/protobuf-cpp-3.21.12")
    link_directories(${PROTOBUF_PATH}/lib)
  2. 如果还是找不到,将link_directories的实现添加到顶层CMakeLists.txt文件中

    原因:你在子目录serialize中添加的上述的动态库路径,但还是报错,可能是其他同级子目录或者上层目录使用了serialize目录的相关代码,因此其他目录也需要链接该动态库,但是由于CMake添加的link_directories只在该目录及其子目录下生效,在其他同级目录和上层目录是不生效的,所以其他目录也会报出错误:cannot found -lxxx

  3. 确保库文件名正确,详细信息可以查看这篇文章:如何在CMake中正确的添加库文件:MSVC与MinGW混用陷阱

C++ 报错:undefined reference to ‘xxxx’

undefined reference to ‘xxx’函数

1
2
3
4
5
6
#情况1,库链接顺序不正确导致,明显特征是报错的函数是我们自己实现的函数,而不是底层函数
[build] D:/a-mycode/C++/DDZ-NET/client-ddz/window/login.cpp:186: undefined reference to `DataManager::getInstance()'
[build] D:/a-mycode/C++/DDZ-NET/client-ddz/window/login.cpp:187: undefined reference to `DataManager::setIp(QByteArray const&)'
#情况2,没有找到动态库文件,明显特征是报错的函数是底层函数,而不是我们自己实现的函数
[build] tcp/libtcp.a(TcpSocket.cpp.obj): In function `TcpSocket::TcpSocket(QObject*)':
[build] D:/a-mycode/C++/DDZ-NET/client-ddz/tcp/TcpSocket.cpp:11: undefined reference to `__imp_WSAStartup'

**原因是库文件找不到,**排查方向:

  1. 确保不是因为函数没有实现导致的

  2. 确保target_link_libraries函数设置的要链接的对象<target>

  3. 确保库链接顺序正确,需要在target_link_libraries修改摆放位置,左边库的依赖右边库

    例如,A库中用到了B库中的函数,这时就是A库依赖于B库,A库应该在左边,B库在A库右边,如果B库在A库左边,就会报undefined reference to 错误,而且有个明显特征是报错的函数是我们自己实现的函数,而不是底层函数

  4. 确保在target_link_libraries已经添加了导入库

  5. 确保target_link_libraries的lib库或dll.a导入库文件能被找到,使用绝对路径试试

  6. 确保dll或so动态库文件能找到,试试添加到环境变量或添加到build目录下(与exe文件同级目录)

C++封装OpenSSL哈希类

一、 基本用法

1
2
3
4
5
6
7
8
9
10
// 创建MD5哈希对象
CryptographicHash hash(CryptographicHash::HashType::Md5);

// 添加数据
hash.addData("Hello World");

// 获取十六进制结果
std::string hexResult = hash.result();
// 获取二进制结果
std::string binResult = hash.result(CryptographicHash::Type::Binary);

必要头文件

1
2
3
4
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <map>

通过枚举关联算法长度和实现

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
33
34
35
36
37
38
39
40
enum class HashType : char {
Md5,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
};

using hashFunc = const EVP_MD* (*)();
const std::map<HashType, hashFunc> HashMethods = {
{HashType::Md5, EVP_md5},
{HashType::Sha1, EVP_sha1},
{HashType::Sha224, EVP_sha224},
{HashType::Sha256, EVP_sha256},
{HashType::Sha384, EVP_sha384},
{HashType::Sha512, EVP_sha512},
{HashType::Sha3_224, EVP_sha3_224},
{HashType::Sha3_256, EVP_sha3_256},
{HashType::Sha3_384, EVP_sha3_384},
{HashType::Sha3_512, EVP_sha3_512},
};

const std::map<HashType, int> HashLength
{
{HashType::Md5,MD5_DIGEST_LENGTH},
{HashType::Sha1,SHA_DIGEST_LENGTH},
{HashType::Sha224,SHA224_DIGEST_LENGTH},
{HashType::Sha256,SHA256_DIGEST_LENGTH},
{HashType::Sha384,SHA384_DIGEST_LENGTH},
{HashType::Sha512,SHA512_DIGEST_LENGTH},
{HashType::Sha3_224,SHA224_DIGEST_LENGTH},
{HashType::Sha3_256,SHA256_DIGEST_LENGTH},
{HashType::Sha3_384,SHA384_DIGEST_LENGTH},
{HashType::Sha3_512,SHA512_DIGEST_LENGTH},
};

二、核心实现步骤

2.1、EVP_MD_CTX_new

创建并初始化一个哈希函数上下文 EVP_MD_CTX 对象

1
EVP_MD_CTX *EVP_MD_CTX_new(void);

返回值:一个指向 EVP_MD_CTX 结构体对象的指针

EVP_MD_CTX 是 OpenSSL 中用于保存哈希函数上下文信息的结构体。它包含了执行哈希计算所需的各种状态、缓冲区和上下文信息。

调用 EVP_MD_CTX_new 函数会分配内存,并对 EVP_MD_CTX 结构体对象进行初始化。之后,可以将该对象传递给其他 OpenSSL 哈希函数相关的函数,以在其中进行进一步的处理和计算。

1
2
m_ctx = EVP_MD_CTX_new();
assert(m_ctx);

2.2、EVP_DigestInit_ex

用于初始化哈希(散列)函数的上下文

1
int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);

参数解释:

  • ctx:指向要初始化的 EVP_MD_CTX 结构体指针,该结构体用于保存哈希函数的上下文信息。
  • type:指向 EVP_MD 函数指针,表示要使用的哈希函数的类型。可以使用 OpenSSL 提供的各种哈希函数类型,如 SHA256、SHA512、MD5 等。
  • impl:可选参数,指定在初始化哈希函数上下文时要使用的加密引擎(如果有)。

返回值:

  • 1:成功
  • 0:失败
1
2
int ret = EVP_DigestInit_ex(m_ctx, HashMethods.at(hashType)(),NULL);
assert(ret == 1);

2.3、EVP_DigestUpdate

用于更新哈希函数的上下文,EVP_DigestUpdate 函数将 data 指向的数据添加到哈希函数的上下文中,并在计算摘要时使用这些数据。可以多次调用此函数以处理连续的数据块。

1
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t count);

参数解释:

  • ctx:指向哈希函数的上下文对象的指针。
  • data:指向要计算摘要的数据的指针。
  • count:要处理的数据的字节数。

返回值:

  • 1:成功。
  • 0:失败。
1
const int ret = EVP_DigestUpdate(m_ctx, data, length);

2.4、EVP_DigestFinal_ex

用于计算哈希函数的最终摘要,并将结果存储在指定的md缓冲区中,s记录数据长度,md和s都是传入传出参数

1
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);

参数解释:

  • ctx:指向哈希函数的上下文对象的指针。
  • md:指向存储摘要结果的缓冲区的指针。
  • s:指向用于存储摘要结果长度的变量的指针。

返回值:

  • 1:成功
  • 0:失败
1
2
3
unsigned int len = 0;
unsigned char md[HashLength.at(m_hashType)];
const int ret = EVP_DigestFinal_ex(m_ctx, md, &len);

在调用 EVP_DigestFinal_ex 函数之前,必须先调用 EVP_DigestInit_exEVP_DigestUpdate 函数,以便初始化哈希函数上下文并处理要计算摘要的数据。

2.5、EVP_MD_CTX_free

用于释放哈希函数的上下文对象所占用的内存空间

1
void EVP_MD_CTX_free(EVP_MD_CTX *ctx);

参数解释:

  • ctx:指向哈希函数的上下文对象的指针。
1
2
3
4
5
CryptographicHash::~CryptographicHash() {
if (m_ctx) {
EVP_MD_CTX_free(m_ctx);
}
}

完整示例:

  • CryptographicHash.h
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#ifndef CRYPTOGRAPHICHASH_H
#define CRYPTOGRAPHICHASH_H

#include <map>
#include <string>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/md5.h>

enum class HashType : char {
Md5,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
};

using hashFunc = const EVP_MD* (*)();
const std::map<HashType, hashFunc> HashMethods = {
{HashType::Md5, EVP_md5},
{HashType::Sha1, EVP_sha1},
{HashType::Sha224, EVP_sha224},
{HashType::Sha256, EVP_sha256},
{HashType::Sha384, EVP_sha384},
{HashType::Sha512, EVP_sha512},
{HashType::Sha3_224, EVP_sha3_224},
{HashType::Sha3_256, EVP_sha3_256},
{HashType::Sha3_384, EVP_sha3_384},
{HashType::Sha3_512, EVP_sha3_512},
};

const std::map<HashType, int> HashLength
{
{HashType::Md5,MD5_DIGEST_LENGTH},
{HashType::Sha1,SHA_DIGEST_LENGTH},
{HashType::Sha224,SHA224_DIGEST_LENGTH},
{HashType::Sha256,SHA256_DIGEST_LENGTH},
{HashType::Sha384,SHA384_DIGEST_LENGTH},
{HashType::Sha512,SHA512_DIGEST_LENGTH},
{HashType::Sha3_224,SHA224_DIGEST_LENGTH},
{HashType::Sha3_256,SHA256_DIGEST_LENGTH},
{HashType::Sha3_384,SHA384_DIGEST_LENGTH},
{HashType::Sha3_512,SHA512_DIGEST_LENGTH},
};

class CryptographicHash {
public:
enum class Type : char { Binary, Hex };

explicit CryptographicHash(HashType hashType);

~CryptographicHash();

//添加数据
void addData(const std::string &data) const;

void addData(const char *data, int length) const;

std::string result(Type type = Type::Hex) const;

private:
EVP_MD_CTX *m_ctx;
HashType m_hashType;
};


#endif //CRYPTOGRAPHICHASH_H

  • CryptographicHash.cpp
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "CryptographicHash.h"

#include <cassert>

CryptographicHash::CryptographicHash(const HashType hashType) {
m_hashType = hashType;
m_ctx = EVP_MD_CTX_new();
assert(m_ctx);

int ret = EVP_DigestInit_ex(m_ctx, HashMethods.at(hashType)(),NULL);
assert(ret == 1);
}

CryptographicHash::~CryptographicHash() {
if (m_ctx) {
EVP_MD_CTX_free(m_ctx);
}
}

void CryptographicHash::addData(const std::string &data) const {
addData(data.data(), data.size());
}

void CryptographicHash::addData(const char *data, const int length) const {
const int ret = EVP_DigestUpdate(m_ctx, data, length);
assert(ret == 1);
}

std::string CryptographicHash::result(const Type type) const {
unsigned int len = 0;
unsigned char md[HashLength.at(m_hashType)];
const int ret = EVP_DigestFinal_ex(m_ctx, md, &len);
assert(ret == 1);

//在将二进制哈希值转换为十六进制字符串时,二进制数据的每个字节(8 位)都会扩展为 2 个十六进制字符(每个字符 4 位)。
//md 是包含原始二进制摘要数据的数组,长度为 len 字节。将其转换为十六进制字符串时:
//md 中每个字节的范围是 0 到 255(0x00 到 0xFF),用十六进制表示时,每个字节正好需要2个字符:
//因此: 一个字节的最大值是 255,即十六进制的 FF,占两个字符。因此,它是 res[len*2]
if (type == Type::Hex) {
char res[len * 2];
for (int i = 0; i < len; ++i) {
sprintf(&res[i * 2], "%02x", md[i]);
}
return std::string(res, len * 2);
}
return std::string(reinterpret_cast<char *>(md), len);
}