// 如果指针没有保存数据元素个数,可通过设置变量(num)保存指针起始地址到结束地址之间元素个数,
// 进行指针偏移来进行元素个数遍历时,可使用数组的形式通过数组下标(0~num)进行遍历
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef void (* cbPrint)(int id, const std::string & str);
typedef struct {
int tskId;
bool run;
cbPrint print;
std::string str;
} TaskMsg;
void Print(int id, const std::string & str)
{
std::cout << "info[" << id << "] " << str << std::endl;
}
TaskMsg tsk1[] = {
{0, true, Print, "hello world 0"},\
{1, true, Print, "hello world 1"},\
{2, true, Print, "hello world 2"}\
};
TaskMsg tsk2[] = {
{0, true, Print, "hello c++ 0"},\
{1, true, Print, "hello c++ 1"},\
};
class Rpt {
public:
#define TYPE 0
// 对指针`TaskMsg*`类型赋值的时候只能取数组的第一个元素的地址。
// 这个指针并没有记录数组的长度,因此需要添加一个成员变量记录数组的长度,以便在遍历时知道循环的次数。
typedef struct {
int num;
TaskMsg *task;
} TaskInfo;
public:
Rpt() {
m_task = new TaskInfo;
m_task->num = (!TYPE) ? (sizeof(tsk1) / sizeof(tsk1[0])) : (sizeof(tsk2) / sizeof(tsk2[0]));
// 对指针`TaskMsg*`类型赋值的时候只能取数组的第一个元素的地址。
m_task->task = (!TYPE) ? tsk1 : tsk2;
}
~Rpt() {
delete m_task;
}
void run() {
for (int i = 0; i < m_task->num; i++)
{
if (m_task->task[i].run && m_task->task[i].print)
{
m_task->task[i].print(m_task->task[i].tskId, m_task->task[i].str);
}
}
}
private:
TaskInfo *m_task;
};
int main()
{
Rpt rptObj;
rptObj.run();
return 0;
}
// 如果指针没有保存数据元素个数,可通过设置变量(num)保存指针起始地址到结束地址之间元素个数,
// 进行指针偏移来进行元素个数遍历时,可使用指针偏移(offset[0~num-1])的形式进行元素遍历
// 在 C++ 中,指针的加法运算实际上是指针类型和偏移量的乘积,即:p + n = p + n * sizeof(*p)
// 也就是说,每次对指针进行加 1 操作时,指针实际上会向后偏移 `sizeof(*p)` 个字节
// 所以,每次遍历完当前的任务时,要将 `m_task->task` 指针向右移动一个 `TaskInfo` 的长度,
// 即 `m_task->task++`。这样才能正确地指向下一个任务的信息。
// 在 C++ 中,指针的长度和指针变量的长度是不一样的。
// 指针的长度和系统的位数有关系,32位的系统通常采用4字节的指针,64位的系统通常采用8字节的指针。
// 指针的长度仅与系统的位数有关系,而指针变量的长度是根据指针指向的数据类型来计算的,即为指针指向类型的大小。
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef void (* cbPrint)(int id, const std::string & str);
typedef struct {
int tskId;
bool run;
cbPrint print;
std::string str;
} TaskMsg;
void Print(int id, const std::string & str)
{
std::cout << "info[" << id << "] " << str << std::endl;
}
TaskMsg tsk1[] = {
{0, true, Print, "hello world 0"},\
{1, true, Print, "hello world 1"},\
{2, true, Print, "hello world 2"}\
};
TaskMsg tsk2[] = {
{0, true, Print, "hello c++ 0"},\
{1, true, Print, "hello c++ 1"},\
};
class Rpt {
public:
#define TYPE 0
// 对指针`TaskMsg*`类型赋值的时候只能取数组的第一个元素的地址。
// 这个指针并没有记录数组的长度,因此需要添加一个成员变量记录数组的长度,以便在遍历时知道循环的次数。
typedef struct {
int num;
TaskMsg *task;
} TaskInfo;
public:
Rpt() {
m_task = new TaskInfo;
m_task->num = (!TYPE) ? (sizeof(tsk1) / sizeof(tsk1[0])) : (sizeof(tsk2) / sizeof(tsk2[0]));
// 对指针`TaskMsg*`类型赋值的时候只能取数组的第一个元素的地址。
m_task->task = (!TYPE) ? tsk1 : tsk2;
}
~Rpt() {
delete m_task;
}
void run() {
for (int i = 0; i < m_task->num; i++)
{
if (m_task->task->run && m_task->task->print)
{
m_task->task->print(m_task->task->tskId, m_task->task->str);
}
m_task->task++;
}
}
private:
TaskInfo *m_task;
};
int main()
{
Rpt rptObj;
rptObj.run();
return 0;
}
// 在 C++ 中,定义的指针变量并不知道指向数组的长度,因此必须要借助 `num` 这样的变量存储数组长度。
// 如果希望不依赖 `num` 来遍历 `m_task` 中的元素,可以在 `TaskMsg` 数组的最后一个元素后面插入一个 `NULL` 指针,表示数组的结束。
// 在遍历的时候,判断指针值是否为 `NULL`,遇到 `NULL` 就退出遍历即可。
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef void (* cbPrint)(int id, const std::string & str);
typedef struct {
int tskId;
bool run;
cbPrint print;
std::string str;
} TaskMsg;
void Print(int id, const std::string & str)
{
std::cout << "info[" << id << "] " << str << std::endl;
}
TaskMsg tsk1[] = {
{0, true, Print, "hello world 0"},\
{1, true, Print, "hello world 1"},\
{2, true, Print, "hello world 2"},\
{0, false, nullptr, ""}\
};
TaskMsg tsk2[] = {
{0, true, Print, "hello c++ 0"},\
{1, true, Print, "hello c++ 1"},\
{0, false, nullptr, ""}\
};
class Rpt {
public:
#define TYPE 1
Rpt() {
// m_task = new TaskMsg;
// 构造函数中,不需要使用 `new` 关键字动态分配内存给 `m_task`,因为 `m_task` 所指向的内存是静态分配的数组,不需要释放内存。
// 正确的实现方式是在构造函数中直接让 `m_task` 指向静态分配的数组,而在析构函数中不进行任何操作。
// 对指针`TaskMsg*`类型赋值的时候只能取数组的第一个元素的地址。进行遍历时需要进行指针偏移
m_task = (!TYPE) ? tsk1 : tsk2;
}
~Rpt() {
// free(m_task);
// 如果在这儿释放内存会出现如下报错
// free(): invalid pointer
// Aborted (core dumped)
// 原因是因为在析构函数中使用了 `free` 函数来释放 `m_task` 所指向的内存,
// 但是 `m_task` 所指向的内存是静态分配的数组,不是使用 `malloc` 等动态内存分配函数分配的内存,因此不能使用 `free` 函数对其进行释放。
// 注意,只有使用 `malloc`、`calloc`、`realloc` 等动态内存分配函数分配的内存才需要使用 `free` 函数进行释放,
// 使用 `new` 关键字动态分配的内存需要使用 `delete` 关键字进行释放。
// 对于静态分配的内存,我们不需要进行显式的释放操作。
}
void run() {
for (TaskMsg *curTsk = m_task; curTsk->run && curTsk->print; ++curTsk)
{
curTsk->print(curTsk->tskId, curTsk->str);
}
}
private:
TaskMsg *m_task;
};
int main()
{
Rpt rptObj;
rptObj.run();
return 0;
}
// 使用`vector`的版本比使用指针和数组的版本更加安全和易用。
// `vector` 类自动管理内存,避免了指针和数组使用中一些常见的内存管理问题(如内存泄漏、越界访问等)。
// `vector` 还提供了一些常用的方法和算法,简化了对元素的访问和处理。
// 此外,`vector` 支持动态大小调整,可以有效地避免了数组定义时大小不合适的问题。
// 使用了 `vector` 后,可以不需要数组的最后一个结束标志的元素,也不需要使用 num 来标记元素个数。
// 因为 `vector` 自带尺寸信息,并且提供了迭代器等方法可以方便地遍历元素。在遍历时,只需要使用 `for` 循环进行迭代即可。
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <string.h>
#include <vector>
typedef void (* cbPrint)(int id, const std::string & str);
typedef struct {
int tskId;
bool run;
cbPrint print;
std::string str;
} TaskMsg;
void Print(int id, const std::string & str)
{
std::cout << "info[" << id << "] " << str << std::endl;
}
std::vector<TaskMsg> tsk1 = {
{0, true, Print, "hello world 0"},\
{1, true, Print, "hello world 1"},\
{2, true, Print, "hello world 2"},\
{0, false, nullptr, ""}\
};
std::vector<TaskMsg> tsk2 = {
{0, true, Print, "hello c++ 0"},\
{1, true, Print, "hello c++ 1"},\
{0, false, nullptr, ""}\
};
class Rpt {
public:
#define TYPE 0
Rpt() {
// 使用容器`vector`来存储任务列表
m_task = (!TYPE) ? tsk1 : tsk2;
}
~Rpt() {}
void run() {
for (auto & task : m_task)
{
if (task.run && task.print)
{
task.print(task.tskId, task.str);
}
}
}
private:
std::vector<TaskMsg> m_task;
};
int main()
{
Rpt rptObj;
rptObj.run();
return 0;
}