数据结构与算法学习笔记五--串(C++)

目录

前言

一、定义

二、串的表示和实现

1.定长顺序存储表示

1.定义

2.串拼接

3.求子串

4.完整代码

2.堆分配存储表示

1.定义

2.求串长

3.串比较

4.清空s串,释放空间

5.串拼接

6.求子串

7.完整代码

3.串的块链存储表示

1.定义

2.生成串

3.生成串

4.完整代码


前言

        这篇文章主要记录下串的相关知识。

一、定义

        串即字符串,是由零个或者多个字符组成的有限序列,是数据元素为单个字符的特殊线性表。

        串长:串中字符的个数(n>=0)。n=0的时候称为空串。

        空格(白)串:由一个或者多个空格符组成的串。

        子串:串S中任意个连续的字符序列叫做S的子串;S叫做主串。

        子串位置:子串的第一个字符在主串中的序号。

        字符位置:字符在串中的序号。

        串相等:串长度相等,且对应位置上字符相等。

二、串的表示和实现

        串有三种机内表示方法,分别为定长顺序存储表示、对分配存储表示、串的块链存储表示。

        在了解串的几种表示之前,我们先复习下C++中的字符数组,以便您更好的理解代码,当然如果您已经非常熟悉这块的知识,可以直接看下面的实现。

        字符数组是用来存放字符数据的数组。

        字符数组的声明方式为:

char 数组名[常量表达式];

        例如:

char c[11] = {'I','','a','m','','C','h','i','n','a'}

        如果我们使用字符数组表示字符串的时候,需要在最后加一个'\0'表示字符串结束的标志。

1.定长顺序存储表示

        类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用定长数组如下描述之。

1.定义

// ----- 串的定长顺序存储表示 -----

#define Maxstrlen 255

typedef unsigned char SString[Maxstrlen + 1];

        定义好串之后,我们可以实现串的常用操作,例如拼接,求子串等操作

2.串拼接

       拼接串的时候,我们要考虑下两个字符串的长度和是否超过我们定义的串的长度,超过之后我们要做截取处理。

//字符串拼接
void concat(String &result, String s1, String s2) {
    if (length(s1)+length(s2) <= Maxstrlen) {
        //不需要截断
        int i = 0;
        while (s1[i] != '\0') {
            result[i] = s1[i];
            i++;
        }
        int j = 0;
        while (s2[j] != '\0') {
            result[i] = s2[j];
            i++;
            j++;
        }
        result[i] = '\0';
    }else if( length(s1) < Maxstrlen ){//截断S2
        //不需要截断
        int i = 0;
        while (s1[i] != '\0') {
            result[i] = s1[i];
            i++;
        }
        int j = 0;
        for (; i+j <= Maxstrlen; j++) {
            result[i+j] = s2[j];
            cout<<"j="<<j<<endl;
        }
        result[Maxstrlen] = '\0';
    }else{
        //不需要截断
        for (int i = 0; i< Maxstrlen; i++) {
            result[i] = s1[i];
        }
        result[Maxstrlen] = '\0';
    }
}

3.求子串

//求子串
bool substring(String &sub, String str, int pos, int len) {
    int strLen = length(str);
    if (pos < 0 || pos > strLen || len < 0 || len > strLen - pos + 1) {
        return false;
    }
    for (int i = 0; i < len; ++i) {
        sub[i] = str[pos + i];
    }
    sub[len] = '\0';
    return true;
}

4.完整代码

#include <iostream>
using namespace std;

#define Maxstrlen 8
typedef unsigned char String[Maxstrlen + 1];

void initString(String &str) {
    str[0] = '\0';
}

int length(String str) {
    int len = 0;
    while (str[len] != '\0') {
        len++;
    }
    return len;
}
//字符串拼接
void concat(String &result, String s1, String s2) {
    if (length(s1)+length(s2) <= Maxstrlen) {
        //不需要截断
        int i = 0;
        while (s1[i] != '\0') {
            result[i] = s1[i];
            i++;
        }
        int j = 0;
        while (s2[j] != '\0') {
            result[i] = s2[j];
            i++;
            j++;
        }
        result[i] = '\0';
    }else if( length(s1) < Maxstrlen ){//截断S2
        //不需要截断
        int i = 0;
        while (s1[i] != '\0') {
            result[i] = s1[i];
            i++;
        }
        int j = 0;
        for (; i+j <= Maxstrlen; j++) {
            result[i+j] = s2[j];
            cout<<"j="<<j<<endl;
        }
        result[Maxstrlen] = '\0';
    }else{
        //不需要截断
        for (int i = 0; i< Maxstrlen; i++) {
            result[i] = s1[i];
        }
        result[Maxstrlen] = '\0';
    }
}
//求子串
bool substring(String &sub, String str, int pos, int len) {
    int strLen = length(str);
    if (pos < 0 || pos > strLen || len < 0 || len > strLen - pos + 1) {
        return false;
    }
    for (int i = 0; i < len; ++i) {
        sub[i] = str[pos + i];
    }
    sub[len] = '\0';
    return true;
}
//插入
bool insertString(String &str, int pos, String insertStr) {
    int strLen = length(str);
    int insertLen = length(insertStr);
    if (pos < 0 || pos > strLen || strLen + insertLen > Maxstrlen) {
        return false;
    }
    for (int i = strLen; i >= pos; --i) {
        str[i + insertLen] = str[i];
    }
    for (int i = 0; i < insertLen; ++i) {
        str[pos + i] = insertStr[i];
    }
    str[strLen + insertLen] = '\0';
    return true;
}
//删除
bool deleteString(String &str, int pos, int len) {
    int strLen = length(str);
    if (pos < 0 || pos > strLen || len < 0 || len > strLen - pos + 1) {
        return false;
    }
    for (int i = pos + len; i <= strLen; ++i) {
        str[i - len] = str[i];
    }
    return true;
}

int main() {
    String str, sub, result;
    initString(str);

    // 测试初始化和长度计算
    cout << "字符串str长度: " << length(str) << endl;

    // 测试串的拼接
    String s1 = "Hello", s2 = "Worlda";
    concat(result, s1, s2);
    cout << "拼接之后的字符串: " << result << endl;

    // 测试子串的提取
    int pos = 2, len = 3;
    if (substring(sub, result, pos, len)) {
        cout << "从下标2开始的子字符串 " << pos << " 长度 " << len << ": " << sub << endl;
    } else {
        cout << "非法位置" << endl;
    }

    // 测试插入操作
    String insertStr = " C++";
    if (insertString(result, pos, insertStr)) {
        cout << "插入之后的字符串: " << result << endl;
    } else {
        cout << "插入失败." << endl;
    }

    // 测试删除操作
    len = 5;
    if (deleteString(result, pos, len)) {
        cout << "字符串删除: " << result << endl;
    } else {
        cout << "字符串删除失败." << endl;
    }

    return 0;
}

2.堆分配存储表示

        在上述的使用定长存储表示串的时候,我们会发现再串的拼接、插入、置换等操作中有可能出现长度超过我们设定的最大长度。为了克服上述的弊端,我们可以不限定串长的最大长度,也就是动态分配串值的存储空间。

1.定义

        这种存储表示的特点是,仍以一组地址连续的存储单元存放串值字符序列,但它们的存储空间是在程序执行过程中动态分配而得。我们可以使用堆内存给串分配存储空间,调用C语言的malloc()和free()来管理。

typedef struct {
    char* ch;       // 若是非空串,则按照串长分配存储区,否则ch为NULL
    int length;     // 串长度
} HString;

2.求串长

        返回串的长度即可。

//求表长
int length(HString &str){
    return str.length;
}

3.串比较

// 字符串比较
int compare(const HString& s1, const HString& s2) {
    int i = 0;
    while (s1.ch[i] != '\0' && s2.ch[i] != '\0') {
        if (s1.ch[i] != s2.ch[i]) {
            return s1.ch[i] - s2.ch[i];
        }
        i++;
    }
    return s1.ch[i] - s2.ch[i];
}

4.清空s串,释放空间

        删除s占用的内存空间,串长置空。

// 释放内存
void destroyString(HString& str) {
    if (str.ch) {
        delete[] str.ch;
    }
    str.length = 0;
}

5.串拼接

// 字符串拼接
bool concat(HString& s1, const HString& s2) {
    // 计算拼接后的长度
    int newLength = s1.length + s2.length;
    char* newStr = new char[newLength + 1];

    // 将s1的字符复制到新串中
    for (int i = 0; i < s1.length; ++i) {
        newStr[i] = s1.ch[i];
    }

    // 将s2的字符复制到新串中
    for (int i = 0; i < s2.length; ++i) {
        newStr[s1.length + i] = s2.ch[i];
    }

    newStr[newLength] = '\0';

    // 释放原来的内存空间
    delete[] s1.ch;

    // 更新s1的指针和长度
    s1.ch = newStr;
    s1.length = newLength;

    return true;
}

6.求子串

// 截取子串
void subString(HString& str, int pos, int len) {
    // 判断参数合法性
    if (pos < 1 || pos > str.length || len < 0 || pos + len - 1 > str.length) {
        cout << "截取位置或长度非法" << endl;
        return;
    }

    // 释放原有的内存空间
    delete[] str.ch;

    // 更新指针和长度
    str.ch = new char[len + 1];
    str.length = len;

    // 复制子串到新的内存空间
    for (int i = 0; i < len; ++i) {
        str.ch[i] = str.ch[pos - 1 + i];
    }
    str.ch[len] = '\0';
}

7.完整代码

#include <iostream>

using namespace std;

typedef struct {
    char* ch;       // 若是非空串,则按照串长分配存储区,否则ch为NULL
    int length;     // 串长度
} HString;

// 初始化
void initString(HString& str, const char* initial) {
    if (initial) {
        int len = 0;
        const char* p = initial;
        while (*p != '\0') {
            len++;
            p++;
        }

        str.length = len;
        str.ch = new char[len + 1];

        for (int i = 0; i < len; ++i) {
            str.ch[i] = initial[i];
        }
        str.ch[len] = '\0';
    } else {
        str.length = 0;
        str.ch = nullptr;
    }
}
// 为字符串赋值为常量字符串chars
void assignStr(HString& str, const char* chars) {
    if (str.ch) {
        delete[] str.ch; // 释放原有的空间
    }
    int len = 0;
    const char* p = chars;
    while (*p != '\0') {
        len++;
        p++;
    }

    str.length = len;
    str.ch = new char[len + 1];

    for (int i = 0; i < len; ++i) {
        str.ch[i] = chars[i];
    }
    str.ch[len] = '\0';
}

//求表长
int length(HString &str){
    return str.length;
}
// 字符串比较
int compare(const HString& s1, const HString& s2) {
    int i = 0;
    while (s1.ch[i] != '\0' && s2.ch[i] != '\0') {
        if (s1.ch[i] != s2.ch[i]) {
            return s1.ch[i] - s2.ch[i];
        }
        i++;
    }
    return s1.ch[i] - s2.ch[i];
}

// 字符串拼接
bool concat(HString& s1, const HString& s2) {
    // 计算拼接后的长度
    int newLength = s1.length + s2.length;
    char* newStr = new char[newLength + 1];

    // 将s1的字符复制到新串中
    for (int i = 0; i < s1.length; ++i) {
        newStr[i] = s1.ch[i];
    }

    // 将s2的字符复制到新串中
    for (int i = 0; i < s2.length; ++i) {
        newStr[s1.length + i] = s2.ch[i];
    }

    newStr[newLength] = '\0';

    // 释放原来的内存空间
    delete[] s1.ch;

    // 更新s1的指针和长度
    s1.ch = newStr;
    s1.length = newLength;

    return true;
}
// 截取子串
void subString(HString& str, int pos, int len) {
    // 判断参数合法性
    if (pos < 1 || pos > str.length || len < 0 || pos + len - 1 > str.length) {
        cout << "截取位置或长度非法" << endl;
        return;
    }

    // 释放原有的内存空间
    delete[] str.ch;

    // 更新指针和长度
    str.ch = new char[len + 1];
    str.length = len;

    // 复制子串到新的内存空间
    for (int i = 0; i < len; ++i) {
        str.ch[i] = str.ch[pos - 1 + i];
    }
    str.ch[len] = '\0';
}

// 释放内存
void destroyString(HString& str) {
    if (str.ch) {
        delete[] str.ch;
    }
    str.length = 0;
}
// 插入
bool insertString(HString& s, int pos, const HString& T) {
    // 插入位置非法
    if (pos < 1 || pos > s.length + 1) {
        cout << "插入位置非法" << endl;
        return false;
    }

    // 计算插入后新串的长度
    int newLength = s.length + T.length;
    char* newStr = new char[newLength + 1];

    // 将s的前pos-1个字符复制到新串中
    for (int i = 0; i < pos - 1; ++i) {
        newStr[i] = s.ch[i];
    }

    // 将串T复制到新串中
    for (int i = 0; i < T.length; ++i) {
        newStr[pos - 1 + i] = T.ch[i];
    }

    // 将s中pos位置后的字符复制到新串中
    for (int i = pos - 1; i < s.length; ++i) {
        newStr[T.length + i] = s.ch[i];
    }

    newStr[newLength] = '\0';

    // 释放原来的内存空间
    delete[] s.ch;

    // 更新s的指针和长度
    s.ch = newStr;
    s.length = newLength;

    return true;
}

// 打印字符串
void printString(const HString& str) {
    cout << str.ch << endl;
}

int main() {
    HString s, T;
    initString(s, "Hello ");
    initString(T, "world!");

    cout << "插入前:";
    printString(s);

    insertString(s, 6, T); // 在位置6插入字符串T
    cout << "插入后:";
    printString(s);

    // 测试赋值函数
    char chars[] = "Welcome";
    assignStr(s, chars);
    cout << "赋值后:";
    printString(s);

    // 测试字符串比较
    HString s1, s2;
    initString(s1, "abc");
    initString(s2, "abd");
    cout << "字符串比较结果:" << compare(s1, s2) << endl;

    // 测试字符串拼接
    concat(s1, s2);
    cout << "字符串拼接结果:";
    printString(s1);

    // 测试子串截取
    subString(s1, 2, 2);
    cout << "截取子串结果:";
    printString(s1);

    // 释放内存
    destroyString(s);
    destroyString(T);
    destroyString(s1);
    destroyString(s2);

    return 0;
}

3.串的块链存储表示

        串的块链存储表示可以通过链表来实现,每个节点表示一个字符,串中的字符按顺序连接在一起。

1.定义

// 定义节点结构体
struct CharNode {
    char data;          // 数据域,存储字符
    CharNode* next;     // 指针域,指向下一个节点
};

// 定义串结构体
struct HString {
    CharNode* head;     // 头指针,指向串的第一个节点
    int length;         // 串的长度
};

2.生成串

        生成串的时候,串的头结点置空,长度为0。

// 初始化串
void initString(HString& str) {
    str.head = nullptr;
    str.length = 0;
}

3.生成串

// 生成串
void assignStr(HString& str, const char* chars) {
    initString(str); // 清空原有串

    const char* p = chars;
    CharNode* prev = nullptr; // 前一个节点指针
    while (*p != '\0') {
        CharNode* newNode = new CharNode; // 创建新节点
        newNode->data = *p;
        newNode->next = nullptr;

        if (!str.head) {
            str.head = newNode; // 如果头指针为空,将新节点设置为头节点
        } else {
            prev->next = newNode; // 否则将新节点链接到上一个节点
        }
        prev = newNode; // 更新前一个节点指针
        str.length++; // 更新串长度
        p++;
    }
}

4.完整代码

#include <iostream>

using namespace std;

// 定义节点结构体
struct CharNode {
    char data;          // 数据域,存储字符
    CharNode* next;     // 指针域,指向下一个节点
};

// 定义串结构体
struct HString {
    CharNode* head;     // 头指针,指向串的第一个节点
    int length;         // 串的长度
};

// 初始化串
void initString(HString& str) {
    str.head = nullptr;
    str.length = 0;
}

// 生成串
void assignStr(HString& str, const char* chars) {
    initString(str); // 清空原有串

    const char* p = chars;
    CharNode* prev = nullptr; // 前一个节点指针
    while (*p != '\0') {
        CharNode* newNode = new CharNode; // 创建新节点
        newNode->data = *p;
        newNode->next = nullptr;

        if (!str.head) {
            str.head = newNode; // 如果头指针为空,将新节点设置为头节点
        } else {
            prev->next = newNode; // 否则将新节点链接到上一个节点
        }
        prev = newNode; // 更新前一个节点指针
        str.length++; // 更新串长度
        p++;
    }
}

// 打印串
void printString(const HString& str) {
    CharNode* p = str.head;
    while (p) {
        cout << p->data;
        p = p->next;
    }
    cout << endl;
}

// 释放内存
void destroyString(HString& str) {
    CharNode* p = str.head;
    while (p) {
        CharNode* temp = p;
        p = p->next;
        delete temp; // 释放节点内存
    }
    str.head = nullptr;
    str.length = 0;
}

int main() {
    HString str;
    assignStr(str, "Hello, world!");

    cout << "打印串: ";
    printString(str);

    destroyString(str);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/572533.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

爬虫学习笔记-数美验证

测试网址&#xff1a;智能验证码体验_图片验证码_数美科技数美科技智能验证码在线体验&#xff0c;智能识别风险用户级别&#xff0c;自行切换智能验证码难度及类型&#xff0c;提供滑动、拼图、点选、数字、动态等多种智能验证码服务&#xff0c;精准拦截机器行为。https://ww…

SOLIDWORKS Composer如何使用3D工具实现更真实的动画效果

当我们使用SOLIDWORKS composer创建动画时&#xff0c;往往会涉及到产品的安装与拆解&#xff0c;现实生活中我们在拆卸组装产品的时候&#xff0c;我们往往需要一些工具的协助&#xff0c;比如扳手、螺丝刀等等&#xff0c;那么我们如何在虚拟动画中也将这一过程以逼真的形式展…

新建云仓库

1.GitHub新建云仓库&#xff1a; LICENSE:开源许可证&#xff1b;README.md:仓库说明文件&#xff1b;开源项目&#xff1b;cocoaPodsName.podspec: CocoaPods项目的属性描述文件。 2.Coding新建云仓库&#xff1a; 备注&#xff1a; Coding新建项目&#xff1a;

STM32,复位和时钟控制

外部时钟 HSE 以后需要用到什么就这样直接拿去配就行了

左叶子之和(力扣404)

解题思路:用后序遍历找左孩子&#xff0c;需要注意的是左叶子需要通过其父节点来判断其是不是左叶子 具体代码&#xff1a; class Solution { public: int sumOfLeftLeaves(TreeNode * root) { if(rootNULL)return 0; if(root->leftNULL&&root->rightNULL)ret…

纳米尺度下的单粒子追踪,厦门大学方宁团队用 AI 奏响「细胞里的摇滚」

在微观世界里&#xff0c;每一个细胞都是一个繁忙的城市&#xff0c;而分子们则是这个城市中的居民。想象一下&#xff0c;如果我们能够追踪这些居民的每一个动作&#xff0c;或许便能够揭开生命奥秘的一角。这就是科学家们在活细胞中进行 3D 单粒子跟踪 (single particle trac…

lua整合redis

文章目录 lua基础只适合lua连接操作redis1.下载lua依赖2.导包,连接3.常用的命令1.set,get,push命令 2.自增管道命令命令集合4.使用redis操作lua1.实现秒杀功能synchronized关键字 分布式锁 lua 基础只适合 1.编译 -- 编译 luac a.lua -- 运行 lua a.lua2.命名规范 -- 多行注…

数字化转型成功的企业到底是什么样的?

数字化转型成功的企业通常具备以下特征&#xff1a; 1、以客户为中心的业务模式&#xff1a;成功的数字化转型企业将客户放在业务模式的核心位置&#xff0c;通过提供个性化的服务和产品来满足客户需求。这种以客户为中心的模式能够带来更好的客户体验和忠诚度。 2、强大的数…

Power BI 如何解决月份排序错误/乱序问题(自定义顺序/正确排序)

问题描述 在创建图标时&#xff0c;月份标签没有按照正确的顺序排列。 解决方案&#xff1a; Sort by Column 单击选中排序错误的列&#xff08;MMM&#xff09;根据列排序 (sort by Column)选择需要根据哪一列排序。 这里选择的是Month列&#xff0c;这一列有月份的序号1-…

Golang——GMP原理与调度

一. Golang调度器的由来 1.1 单进程时代不需要调度器 我们知道&#xff0c;一切软件都跑在操作系统上&#xff0c;真正用来干活(计算)的是CPU。早期的操作系统每一个程序就是一个进程&#xff0c;直到一个程序运行完&#xff0c;才能进行下一个进程&#xff0c;就是"单进程…

CSS盒子模型与常见问题

CSS盒子模型 显示模式转换显示模式 盒子模型边框线内边距padding 多值写法 尺寸计算与内减法模式外边距 清除默认样式元素溢出外边距问题合并现象塌陷现象 行内元素 – 内外边距问题 显示模式 显示模式&#xff1a;标签&#xff08;元素&#xff09;的显示方式作用&#xff1a;…

C++中的数制转换工具

一、引言 在编程和日常计算中&#xff0c;我们经常需要在不同的数制之间进行转换。二进制、十进制和十六进制是最常用的数制。二进制是计算机内部处理数据的基础&#xff0c;十进制是我们日常生活中最常用的数制&#xff0c;而十六进制则在编程和硬件相关领域中广泛使用。 二…

不要摆摊,不要开早餐店,原因如下

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我最近开通了视频号会员专区嘛&#xff0c;专区有个问答功能可以提问&#xff0c;有个会员问了我问题&#xff0c;其中一条问答分享给大家&#xff1a; 松哥&#xff0c;突然想去兼职&#xff0c;早上卖点杂粮煎饼果…

小塔 | 时尚领域RFID应用,别人早你一步!

优衣库&#xff0c;作为知名服装品牌零售商&#xff0c;近年来在数字化转型的道路上取得了显著的成果。其中&#xff0c;RFID技术的应用成为了优衣库提升运营效率、优化客户体验以及实现精准营销的重要工具。 RFID助力时尚门店品牌升级 优衣库深知RFID技术的潜力&#xff0c;将…

web--跨域,cors,jsonp,域名接管劫持

同源策略 可以放在csrf cosp 解决同源策略 它会将会从xiaodi这个网站中去获取资源&#xff0c;然后发送给localhost这个网站 就获取到了资源 jsonp 就是这个网站的回调信息有个人的数据 就看callback有没有回调信息 域名接管 当右边两个域名过期&#xff0c;就可以注册它的域名…

Git 安装及配置教程(Windows)【安装】

文章目录 一、 下载1. 官网下载2. 其它渠道 二、 安装三、 配置四、 更新 软件 / 环境安装及配置目录 一、 下载 1. 官网下载 安装地址&#xff1a;https://git-scm.com/download 2. 其它渠道 &#xff08;1&#xff09;百度网盘&#xff08;2.44.0 版本&#xff09; 链接…

使用FPGA实现逐级进位加法器

介绍 逐级进位加法器就是将上一位的输出作为下一位的进位输入&#xff0c;依次这样相加。下面以一个8位逐级进位加法器给大家展示。 我增加了电路结构&#xff0c;应该很容易理解吧。 下面我也列举了一位加法器&#xff0c;可以看下。 电路结构 设计文件 1位加法器 librar…

【Python爬虫】爬取淘宝商品数据——新手教程

大数据时代&#xff0c; 数据收集不仅是科学研究的基石&#xff0c; 更是企业决策的关键。 然而&#xff0c;如何高效地收集数据 成了摆在我们面前的一项重要任务。 本文将为你揭示&#xff0c; 一系列实时数据采集方法&#xff0c; 助你在信息洪流中&#xff0c; 找到…

SpringCloud基础 Consul的引入

前言 首先是为什么引入consul这个组件 我们知道微服务分为很多个模块,这里模块中相互调用,我使用硬编码的模式是不好的 比如微服务模块需要更新的时候,我们使用硬编码的方式可能需要修改很多个地方 但是使用consul之后,就引入了注册中心,我们只需要将对应的服务注册为节点 这样…

重生奇迹MU召唤术师攻略(重生奇迹MU召唤技能)

1、召唤术师&#xff0c;重生奇迹MU的强力职业之一。跟格斗家一样&#xff0c;需要RMB购买资格证才能够使用的一个职业&#xff0c;召唤术师可以说是对于玩家将职业玩法的一种总结性职业&#xff0c;这个职业虽然叫做召唤术师&#xff0c;但是整个重生奇迹MU里唯一能够召唤宝宝…