博客

  • Tomcat 吉祥物:一只橙色猫咪背后的设计哲学与技术隐喻

    提到 Apache Tomcat,每个 Java 开发者都对它的核心功能了如指掌 —— 作为轻量级 Servlet 容器,它支撑着无数 Web 应用的运行。但你是否留意过它的吉祥物:一只戴着红色领结的橙色卡通猫?这只看似简单的猫咪形象,其实藏着 Tomcat 的品牌基因与技术理念。今天,我们就来聊聊这只 “程序猫” 的设计故事。

    一、从 “名字” 到 “形象”:为什么是一只猫?

    Tomcat 的吉祥物形象并非凭空而来,而是与项目名称深度绑定的 “命中注定”。

    1999 年,Sun Microsystems 的工程师团队计划开发一款轻量级 Servlet 容器,作为当时 Apache JServ 的替代方案。在命名时,他们希望用一个能体现 “灵活、独立、适应性强” 特质的词汇,最终选择了 “Tomcat”—— 这个词在英语中本指 “公猫”,尤其常用来形容无主却生命力顽强的流浪猫。这种 “不依赖环境、自己就能跑起来” 的意象,恰好契合了这款轻量级容器的定位:无需复杂配置,就能快速部署运行。

    随着项目被捐献给 Apache 基金会,“Tomcat” 的名字被保留下来,而吉祥物形象也顺理成章地以 “猫” 为核心。早期 O’Reilly 出版的 Tomcat 技术书籍封面,就率先采用了公猫形象,进一步强化了 “猫” 与 Tomcat 的关联,最终成为官方吉祥物的设计起点。

    二、形象细节:每一处设计都藏着技术隐喻

    Tomcat 吉祥物的经典形象看似简洁,却处处呼应着产品的技术特性:

    • 橙色为主色调:温暖明亮的橙色传递出 “友好、活力” 的气质,打破了技术工具的冰冷感。这与 Tomcat 作为开发者友好型工具的定位一致 —— 它无需复杂门槛,新手也能快速上手。
    • 红色领结 / 领带:作为点睛之笔,领结让猫咪形象更显 “正式感”,暗示 Tomcat 虽然轻量,却能承担企业级应用的重任,兼具灵活性与可靠性。
    • 动态姿态:无论是早期插画中奔跑的姿势,还是现代 Logo 中微微前倾的动态,都传递出 “敏捷、高效” 的感觉,对应 Tomcat 启动快、资源占用低的技术优势。
    • 与 Java 咖啡杯的联动:在许多官方物料中,猫咪会与 Java 标志性的咖啡杯同框,直观体现 Tomcat 与 Java 生态的深度绑定(毕竟它是 Java Web 开发的核心工具之一)。

    三、设计理念:用 “猫性” 诠释技术价值观

    Tomcat 吉祥物的核心设计理念,是用 “猫的特质” 隐喻产品的技术价值观,让抽象的技术特性变得可感知:

    1. 灵活适配,如猫适应环境

    猫能在各种场景中生存,无论是客厅沙发还是街头角落;Tomcat 也能跨平台运行,兼容 Windows、Linux、macOS 等系统,支持从个人开发环境到企业服务器的全场景部署。这种 “适应性” 正是吉祥物想要传递的核心优势。

    2. 轻量高效,如猫轻盈敏捷

    猫体型小巧却动作迅速,Tomcat 也以 “轻量级” 著称 —— 相比重型应用服务器(如 WebLogic),它无需庞大的资源开销,却能高效处理 Servlet、JSP 等请求。吉祥物的轻盈形象,恰是这种 “轻而强” 特质的视觉化表达。

    3. 可靠稳定,如猫沉稳可靠

    尽管猫给人 “随性” 的印象,但关键时刻却异常可靠(比如精准捕猎)。Tomcat 同样如此:它经过 20 多年迭代,早已成为工业级的稳定工具,支撑着全球无数网站和应用的运行,吉祥物的 “沉稳感” 也暗合这一特质。

    4. 开发者亲和力,拉近技术与用户的距离

    开源项目的吉祥物往往承担着 “情感连接” 的角色。Tomcat 的猫咪形象弱化了技术工具的 “机械感”,让开发者在调试 Bug、部署应用时,能对这个 “伙伴式” 的工具产生亲切感。这种情感链接,也是 Tomcat 能在开发者社区中长盛不衰的原因之一。

    结语:一只猫咪的 “技术人格”

    Tomcat 的吉祥物从来不止是一个 Logo,而是产品技术理念的 “人格化代言”。它用猫的灵活、敏捷、可靠,将抽象的技术特性转化为具象的情感认知,让开发者在使用这个工具时,不仅依赖它的功能,更能感受到背后的设计温度。

    下次启动 Tomcat 看到那只橙色猫咪时,或许你会对它多一份亲切感 —— 毕竟,它不仅是 Java Web 开发的 “老伙计”,更是用设计语言诠释技术价值观的生动载体。

  • #C++ Day42 Basic Data Structure Chapter3  review December 18 2025

    要理解为什么SequentialList myList;不用加*,需要从变量的类型本质内存存储方式以及函数参数的设计意图三个层面来分析:

    一、先明确两个核心概念:普通变量 vs 指针变量

    C++ 中变量的声明决定了它的内存形态使用方式

    1. SequentialList myList;:声明结构体的实际实例(普通变量)

    • 这行代码在栈内存中创建了一个SequentialList类型的完整实例,它本身占据实际的内存空间,包含三个成员:
      • eleType* elements(指针成员)
      • int capacity(整型成员)
      • int size(整型成员)
    • 你可以直接通过点运算符(.)访问它的成员(比如myList.size),也可以通过&myList获取它的内存地址(这是一个SequentialList*类型的指针)。

    2. SequentialList* myList;:声明指向结构体的指针变量

    • 这行代码只在栈内存中创建了一个指针变量(大小通常为 4/8 字节,取决于系统),它的作用是存储一个内存地址,但本身并不是结构体实例
    • 此时myList是一个野指针(初始值随机),它没有指向任何有效的SequentialList实例。如果直接使用myList->capacity,会访问非法内存,导致程序崩溃。
    • 若要使用指针,必须先为它分配有效的实例,比如:cpp

      运行




      SequentialList* myList = new SequentialList(); // 堆上创建结构体实例,指针指向该实例




    二、结合代码中函数的参数设计分析

    代码中的函数(如initializeList、insertElement等)的参数都是SequentialList* list(结构体指针),这是为了实现对原结构体实例的修改(C++ 函数参数默认是值传递,传指针可以避免拷贝,且能修改原变量)。

    但这并不意味着我们必须声明指针变量,因为:

    • 对于普通变量myList,我们可以通过&myList(取地址运算符)获取它的地址,这个地址的类型正好是SequentialList*,可以直接传给函数。
      比如:initializeList(&myList, 10); 就是把myList的地址传给initializeList的list参数,函数内部通过指针list操作的就是外部的myList实例。

    如果我们声明的是指针变量SequentialList* myList;,反而需要额外的初始化步骤(否则是野指针),这会增加代码的复杂度,而这里完全没有必要。

    三、两种写法的内存对比

    写法内存位置是否是有效实例函数调用方式内存管理
    SequentialList myList;是(完整实例)initializeList(&myList, 10);栈内存由系统自动释放
    SequentialList* myList;栈(指针)否(野指针)需先myList = new SequentialList();,再initializeList(myList, 10);堆内存需手动delete myList;

    四、总结:为什么这里不用加*

    • 需求是 “结构体实例” 而非 “指针”:我们需要的是一个能存储capacity、size和elements的实际结构体对象,而SequentialList myList;直接创建了这个对象,满足需求。
    • 传地址即可适配指针参数:函数需要SequentialList*参数,通过&myList取普通变量的地址,就能完美匹配参数类型,无需额外声明指针变量。
    • 避免野指针风险:如果声明SequentialList* myList;而不初始化,会导致野指针,引发程序崩溃;而普通变量是栈上的有效实例,无此风险。

    拓展:如果非要用指针怎么办?

    如果坚持用SequentialList* myList;,代码需要修改为:

    cpp

    运行

    // 声明指针并在堆上创建实例

    SequentialList* myList = new SequentialList();

    // 调用函数(直接传指针,无需&)

    initializeList(myList, 10);

    // … 其他操作 …

    // 不仅要释放elements,还要释放结构体本身

    destroyList(myList);

    delete myList; // 释放堆上的SequentialList实例

    这种写法更复杂,且容易忘记delete myList导致内存泄漏,因此原代码的写法更简洁、安全。

    当顺序表的容量已满时,需要进行…-知识星球

    Chen 提问:

    为什么这个地方是capacity*2而不是capacity++

    当顺序表的容量已满时,需要进行扩容。扩容的方式是将容量扩大为原来的两倍,即 L->capacity * 2。这样做的原因是为了减少频繁扩容的次数,提高顺序表的性能。 因为扩容操作本身需要将所有元素重新进行一次拷贝,时间复杂度 O(n),如果每次扩容只是将容量增加1,那么当顺序表中的元素数量不断增加时,扩容的次数会非常频繁,从而导致性能下降。而将容量扩大为原来的两倍,可以有效地减少扩容的次数,确保算上扩容以后,均摊时间复杂度为 O(1),提高顺序表的效率。 为了均摊时间复杂度是O(1)呢? 某一次内存重分配完了以后, Capacity 达到了 n,那么表示这里有 n/2 个位置的元素是空的,剩下 n/2 的一半元素被拷贝了1次,剩下的一半的一半的元素拷贝了2次,… 那么所有元素被拷贝的次数就是: n/4 * 1 + n/8 * 2 + n/16 * 4 + … = n 所以当插入 n 个元素后,拷贝的总时间复杂度就是 O(n),每个元素的均摊时间复杂度就是 O(1)。  试着用最简单的语言解释

    已完成思考

    我们用生活化的例子 + 最直白的计算,把这个问题拆成两部分讲清楚:

    一、先搞懂:为什么不选capacity++(每次只加 1)?

    假设顺序表的扩容是 “满了就加 1”,把这个过程想象成搬仓库

    • 你有个小仓库,只能放 1 个箱子(容量 1),放满后,要换能放 2 个的仓库,得把1 个箱子搬过去(拷贝 1 个元素);
    • 再放 1 个,仓库又满了,换能放 3 个的仓库,得把2 个箱子搬过去(拷贝 2 个元素);
    • 再放 1 个,又满了,换能放 4 个的仓库,得把3 个箱子搬过去(拷贝 3 个元素);
      ……
      直到仓库能放n个箱子,你总共搬了1+2+3+…+(n-1)次箱子,这个总数约等于n²/2(比如 n=100,就要搬约 5000 次)。

    简单说:每次只加 1,扩容太频繁,搬箱子(拷贝元素)的总次数会爆炸式增长,性能差到没法用。

    二、再理解:为什么选capacity*2(每次扩 2 倍)?

    还是用搬仓库的例子,这次满了就换2 倍大的仓库:

    • 初始仓库能放 1 个,放满后换能放 2 个的,搬1 个箱子;
    • 放满 2 个后,换能放 4 个的,搬2 个箱子;
    • 放满 4 个后,换能放 8 个的,搬4 个箱子;
    • 放满 8 个后,换能放 16 个的,搬8 个箱子;
      ……
      直到仓库能放n个箱子,你总共搬了1+2+4+8+…次,这个总数永远小于 n(比如 n=16,只搬了 1+2+4+8=15 次;n=32,只搬了 31 次)。

    核心差异:2 倍扩容让 “搬仓库” 的次数极少,总搬箱次数和最终的箱子数差不多

    三、最后说:为什么均摊时间复杂度是 O (1)?

    “均摊时间复杂度” 就是把所有搬箱子的耗时,平摊到每一次 “放箱子” 的操作上,看平均每次放箱子要花多少时间。

    举个具体例子:插入 8 个元素(箱子),用 2 倍扩容:

    • 插入第 1 个:仓库满了,扩容到 2,搬 1 个箱子(总耗时:1 次插入 + 1 次搬);
    • 插入第 2 个:仓库满了,扩容到 4,搬 2 个箱子(总耗时:1 次插入 + 2 次搬);
    • 插入第 3、4 个:不用扩容(总耗时:2 次插入);
    • 插入第 4 个后满了,扩容到 8,搬 4 个箱子(总耗时:1 次插入 + 4 次搬);
    • 插入第 5-8 个:不用扩容(总耗时:4 次插入)。

    总耗时统计

    • 插入操作:8 次(每个元素 1 次);
    • 搬箱子(拷贝):1+2+4=7 次;
    • 总计:8+7=15 次操作。

    平均到每个元素:15÷8≈1.875 次操作 —— 这个数是固定的常数(不管插入多少元素,平均下来每次都接近 1 次操作),这就是均摊时间复杂度 O (1)

    一句话总结

    每次扩容 2 倍,虽然偶尔有一次插入要 “搬很多箱子”,但把这个耗时平摊到很多次不用搬的插入上,平均下来每次插入的耗时几乎不变;而每次只加 1,每插一次都要 “搬箱子”,越往后越慢。

    剩下 n/2 的一半元素被拷贝了1次,剩下的一半的一半的元素拷贝了2次、

    已完成思考

    我们用具体的数值例子(比如最终容量 n=8) + 直白的分组,把这句话拆解得明明白白,保证一看就懂:

    先明确前提(原文的场景)

    某次扩容后容量达到 n(比如 n=8),因为是翻倍扩容,这次扩容是从n/2=4扩容到n=8的。

    所以扩容后:

    • 总容量n=8,其中元素数量是 n/2=4(都是从旧数组拷贝过来的);
    • 剩下的n/2=4个位置是空的(这就是原文里 “这里有 n/2 个位置的元素是空的”)。

    我们要统计的是:这 4 个元素(n/2)从被插入到现在,一共被拷贝了多少次

    再拆解 “一半元素被拷贝 1 次,一半的一半拷贝 2 次……”

    以n=8(容量)、n/2=4(元素数)为例:

    把这 4 个元素按 **“加入顺序” 分组 **,看每组被拷贝的次数:

    分组(元素数量)对应原文的描述被拷贝的次数具体解释
    2 个元素(4 的一半)n/2 的一半(n/4=2)1 次这 2 个元素是在容量 4 时加入的,只经历了 “4→8” 这 1 次扩容,所以只被拷贝 1 次。
    1 个元素(4 的一半的一半)n/2 的一半的一半(n/8=1)2 次这个元素是在容量 2 时加入的,经历了 “2→4” 和 “4→8”2 次扩容,所以被拷贝 2 次。
    1 个元素(继续拆分)n/2 的一半的一半的一半3 次这个元素是在容量 1 时加入的,经历了 “1→2”“2→4”“4→8”3 次扩容,所以被拷贝 3 次。

    如果把容量放大到n=16(更易观察规律),规律会更明显:

    • 扩容后容量 n=16,元素数 n/2=8;
    • 4 个元素(8 的一半):在容量 8 时加入,只经历 “8→16”,被拷贝 1 次;
    • 2 个元素(8 的一半的一半):在容量 4 时加入,经历 “4→8”“8→16”,被拷贝 2 次;
    • 1 个元素(8 的一半的一半的一半):在容量 2 时加入,经历 3 次扩容,被拷贝 3 次;
    • 1 个元素:在容量 1 时加入,经历 4 次扩容,被拷贝 4 次。

    一句话翻译这句话的核心

    “越早期加入的元素,被拷贝的次数越多;每往回追溯一次扩容,元素数量就减半,拷贝次数就加 1”

    最后补一句:为什么总拷贝次数加起来约等于 n?

    还是以 n=8 为例,总拷贝次数是:

    2个×1次 + 1个×2次 + 1个×3次 = 2+2+3=7 ≈8(n)

    以 n=16 为例,总拷贝次数是:

    4个×1次 + 2个×2次 + 1个×3次 + 1个×4次 =4+4+3+4=15≈16(n)

    这就是原文里 “所有元素被拷贝的次数加起来等于 n” 的原因,最终平摊到每个元素上,拷贝的平均次数就是常数级(O (1))。

    #include <iostream>

    using namespace std;

    #define eleType int

    //1.define

    struct SequentialList {

    eleType* elements;

    int capacity;

    int size;

    };

    //2.initialize

    void initializeList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];

    list->capacity = capacity;

    list->size = 0;

    }

    //3.isEmpty and size

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int sizeOfList(SequentialList* list) {

    return list->size;

    }

    //4.destroy

    void destroyList(SequentialList* list) {

    delete[] list->elements;

    }

    //5.insert

    void insertElement(SequentialList* list, int index, eleType element) {

    if (index<0 || index>list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int Newcapacity = list->capacity * 2;

    eleType* Newlist = new eleType[Newcapacity];

    for (int i = 0; i < list->size; i++) {

    Newlist[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = Newlist;

    list->capacity = Newcapacity;

    }

    list->size++;//size要放上面

    for (int i = list->size-1; i > index; i–) {//这里是list->size-1

    list->elements[i] = list->elements[i – 1]; 

    }

    list->elements[index] = element;

    }

    //6.delete

    void deleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size-1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    //7.find index

    int findElementIndex(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;//represent not found

    }

    //8.getElement with index

    eleType getElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    //9.update

    void updateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    SequentialList Mylist;

    int list_capacity = 10;

    initializeList(&Mylist, list_capacity);

    cout << “IsEmpty:” << isEmpty(&Mylist) << endl;

    cout << “Size:” << sizeOfList(&Mylist) << endl;

    for (int i = 0; i < list_capacity; i++) {

    insertElement(&Mylist, i, i * 10);

    }

    cout << “IsEmpty:” << isEmpty(&Mylist) << endl;

    cout << “Size:” << sizeOfList(&Mylist)<<endl;

    for (int i = 0; i < sizeOfList(&Mylist); i++) {

    cout << getElement(&Mylist, i) << ” “;

    }cout << endl;

    deleteElement(&Mylist, 9);

    cout << “IsEmpty:” << isEmpty(&Mylist) << endl;

    cout << “Size:” << sizeOfList(&Mylist) << endl;

    for (int i = 0; i < sizeOfList(&Mylist); i++) {

    cout << getElement(&Mylist, i) << ” “;

    }cout << endl;

    insertElement(&Mylist, sizeOfList(&Mylist), 55);

    for (int i = 0; i < sizeOfList(&Mylist); i++) {

    cout << getElement(&Mylist, i) << ” “;

    }cout << endl;

    int idx = findElementIndex(&Mylist, 55);

    updateElement(&Mylist, idx, 520);

    for (int i = 0; i < sizeOfList(&Mylist); i++) {

    cout << getElement(&Mylist, i) << ” “;

    }cout << endl;

    cout << “IsEmpty:” << isEmpty(&Mylist) << endl;

    cout << “Size:” << sizeOfList(&Mylist) << endl;

    destroyList(&Mylist);

    return 0;

    }

  • #C++ Day41 Basic Data Structure Chapter3  December 17 2025

    核心结论:这两步是 “先声明指针(留位置),再动态分配内存(找空间)”,缺一不可,用通俗的话拆解:

    1. eleType* elements; —— 声明“地址容器”

    • 作用:在顺序表结构体里,预留一个“存放内存地址”的位置(就像在手机里建一个“文件夹快捷方式”,但还没指向实际文件夹)。

    • 为什么必须有?:顺序表需要连续的存储空间存数据,但一开始不知道要存多少个元素(容量由用户初始化时指定),不能直接定义固定大小的数组(比如eleType elements[100]),所以用指针先“占个坑”,后续再指向实际的内存空间。

    2. list->elements = new eleType[capacity]; —— 分配“实际储物间”

    • 作用:用new关键字,根据用户指定的capacity(容量),向电脑申请一块 连续的、能存capacity个eleType类型数据的内存空间(相当于根据需求,实际创建一个“储物间”),然后把这个“储物间”的地址,赋值给前面声明的elements指针(让“快捷方式”指向真实文件夹)。

    • 为什么要这么写?:如果只声明指针不分配内存,elements就是个“野指针”(指向随机地址),往里面存数据会导致程序崩溃;只有通过new申请了具体空间,才能安全存元素。

    一句话总结:

    eleType* elements; 是“告诉程序:我要一个能存数据的地址”,list->elements = new eleType[capacity]; 是“给程序实际找一块能存capacity个数据的地方,并把地址记下来”,两者配合才能让顺序表拥有可使用的存储空间。

    需要我用生活案例再类比一遍,或者补充new分配内存后如何释放(避免内存泄漏)吗?

    核心结论:SequentialList* list 里的 * 表示 list 是一个指针,作用是让函数能直接操作「主程序里的原顺序表」,而不是复制一个没用的副本——用通俗的类比就能秒懂:

    1. 先搞懂:* 在这里的意思

    • SequentialList 是你定义的“顺序表结构体”(像一个“文件柜”,装着 elements 指针、capacity 容量、size 长度这3个“抽屉”);

    • SequentialList* list 里的 * 告诉编译器:list 不是一个完整的“文件柜”,而是指向这个文件柜的钥匙(存着文件柜的地址)。

    2. 为什么非要传“钥匙”(指针),不能直接传“文件柜”?

    如果函数参数写成 void initializeList(SequentialList list, int capacity)(没有 *),相当于:

    • 你把家里的文件柜(原顺序表)复制了一个一模一样的假柜子,送到函数里;

    • 函数在假柜子上“分配内存”“设置容量”(比如 list.capacity = capacity),改的全是假柜子;

    • 函数结束后假柜子被扔掉,家里的原柜子还是空的、没初始化——等于白忙活!

    而传 SequentialList* list(带 * 的指针),相当于:

    • 你把家里文件柜的钥匙交给函数;

    • 函数用钥匙打开原柜子,直接在原柜子里操作:给 elements 抽屉分配内存(new)、设置 capacity 和 size;

    • 函数结束后,原柜子已经被初始化好,能直接用——这才是我们要的效果!

    3. 代码里的 list-> 也和 * 有关

    因为 list 是“钥匙”(指针),不是直接的“文件柜”,所以不能用 . 访问成员(比如 list.elements 会报错),必须用 -> 表示“通过钥匙找到文件柜,再打开抽屉”:

    • list->elements = new eleType[capacity] → 用钥匙找到原柜子,给 elements 抽屉分配内存;

    • list->capacity = capacity → 用钥匙找到原柜子,给 capacity 抽屉赋值。

    一句话总结:

    参数里的 * 是为了让函数“直达原顺序表”,能修改它的内部数据(分配内存、设容量);如果没有 *,函数改的是副本,原顺序表永远初始化失败。

    需要我用“传指针”和“不传指针”的代码对比,再演示一次区别吗?

    核心原因:参数名和成员名要“各司其职”——elements 已经是“表内元素数组”的名字,参数名如果也叫 elements,会混淆“整个顺序表”和“表内数据”的指代,还会让代码语义混乱,用通俗的话讲:

    1. 先明确:两者指代的是完全不同的东西

    • 成员名 elements:是顺序表结构体里的“零件”——专门存数据的数组(比如文件柜里的“文件夹”,只负责装1、2、3这些具体数据);

    • 参数名(原本的 list):是指向“整个顺序表”的指针(比如文件柜的“钥匙”,负责操控整个柜子,包括文件夹、容量标签、当前存放数量)。

    如果参数也叫 elements,就相当于把“文件柜的钥匙”和“柜子里的文件夹”叫同一个名字——别人看代码会完全懵:“这个 elements 到底是指整个柜子,还是柜子里的文件夹?”

    2. 举个错误示例:参数叫 elements 会多混乱

    如果真把参数改成 elements,代码会变成这样:

    // 错误示例:参数名和成员名重名

    void initializeList(SequentialList* elements, int capacity) {

        elements->elements = new eleType[capacity];  // 救命!两个elements挤在一起

        elements->capacity = capacity;

    }

    这句话 elements->elements 翻译过来是:“用‘文件夹’(参数名)找到整个文件柜,再打开柜子里的‘文件夹’(成员名)”——逻辑上完全说不通,不仅别人看不懂,未来的你回头看代码也会疑惑:“我当初到底想指哪个?”

    3. 命名的核心规矩:“一物一名”

    好的代码命名要满足“见名知义”,核心是“不同用途的东西,名字不能重”:

    • 指代“整个顺序表”的指针:用 list、seqList、sl 等,明确是“整体”;

    • 指代“表内数据数组”的成员:用 elements、data 等,明确是“局部零件”;

    • 两者如果重名,就打破了“一物一名”的规则,导致语义冲突。

    一句话总结:

    不是不能叫 elements,而是这个名字已经被“表内的元素数组”占用了,参数需要一个“指代整个顺序表”的专属名字,才能区分“整体”和“局部”,让代码逻辑清晰不混乱。

    需要我帮你整理一份“顺序表完整操作代码”(包含初始化、插入、删除),全程用规范命名(参数名 seqList + 成员名 elements),帮你巩固这个逻辑吗?

    #include <iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {

    eleType* elements;//一个能存数据的地址

    int capacity;

    int size;

    };

    void initializeList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];//给程序实际找一个能存capacity个数据的地址。并把地址记下来

    list->capacity = capacity;

    list->size = 0;

    }

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int sizeOfList(SequentialList* list) {

    return list->size;

    }

    void destroyList(SequentialList* list) {

    delete[] list->elements;

    }

    void insertElement(SequentialList* list,int index,eleType element){

    if (index<0 || index>list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int newCapacity = list->capacity * 2;

    eleType* newList = new eleType[newCapacity];

    for (int i = 0; i < list->size; ++i) {

    newList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = newList;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    void deleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    int findElement(SequentialList* list, eleType element) {

    for (int i= 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;

    }

    eleType getElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    void updateElement(SequentialList* list, int index, eleType value) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = value;

    }

    int main() {

    SequentialList myList;

    initializeList(&myList, 10);

    for (int i = 0; i < 10; i++) {

    insertElement(&myList, i, i * 10);

    }

    cout << “Size:” << sizeOfList(&myList) << endl;

    cout << “Is empty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < sizeOfList(&myList); i++){

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    deleteElement(&myList, 3);

    for (int i = 0; i < sizeOfList(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    updateElement(&myList, 1, 1314);

    for (int i = 0; i < sizeOfList(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    int idx = findElement(&myList, 20);

    updateElement(&myList, idx,520);

    for (int i = 0; i < sizeOfList(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    cout << “Size:” << sizeOfList(&myList) << endl;

    cout << “Is empty:” << isEmpty(&myList) << endl;

    insertElement(&myList, sizeOfList(&myList), 21111);

    for (int i = 0; i < sizeOfList(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    cout << “Size:” << sizeOfList(&myList) << endl;

    cout << “Is empty:” << isEmpty(&myList) << endl;

    insertElement(&myList, 4, 21111);

    for (int i = 0; i < sizeOfList(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    cout << “Size:” << sizeOfList(&myList) << endl;

    cout << “Is empty:” << isEmpty(&myList) << endl;

    destroyList(&myList);

    return 0;

    }

  • #C++ Day40 Basic Data Structure Chapter3  November 26 2025

    青年歌手大奖赛_评委会打分 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 183698    Accepted Submission(s): 89947

    Problem Description 青年歌手大奖赛中,评委会给参赛选手打分。选手得分规则为去掉一个最高分和一个最低分,然后计算平均得分,请编程输出某选手的得分。  
    Input 输入数据有多组,每组占一行,每行的第一个数是n(2<n<=100),表示评委的人数,然后是n个评委的打分。  
    Output 对于每组输入数据,输出选手的得分,结果保留2位小数,每组输出占一行。  
    Sample Input 3 99 98 97 4 100 99 98 97  
    Sample Output 98.00 98.50  
    Author lcy  
    Source C语言程序设计练习(三)

    #include <iostream>

    using namespace std;

    #define eleType double

    #include <iomanip>

    //1.

    struct SequentialList {

    eleType* elements;

    int capacity;

    int size;

    };

    //2.initial

    void initializeList(SequentialList * list,int capacity){

    list->elements = new eleType[capacity];

    list->capacity = capacity;

    list->size = 0;

    }

    //3.Is Empty

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int sizeOfList(SequentialList* list) {

    return list->size;

    }

    //4.destroy

    void destroyList(SequentialList* list) {

    delete[] list->elements;

    }

    //5.insert

    void insertElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index > list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int newCapacity = list->capacity*2;

    eleType* newList = new eleType[newCapacity];

    for (int i = 0; i < list->size; ++i) {

    newList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = newList;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; –i) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    //6.delete

    void deleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    //7.find

    int findElementIndex(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;

    }

    //8.get

    eleType getElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    //9.update

    void updateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    SequentialList s;

    int n;

    cout << setiosflags(ios::fixed) << setprecision(2);

    while (cin >> n && n > 2 && n <= 100) {

    initializeList(&s, 1);

    for (int i = 0; i < n; ++i) {

    eleType x;

    cin >> x;

    insertElement(&s,i,x);

    }

    double max= getElement(&s, 0), min=getElement(&s,0);

    for (int i = 1; i < n; ++i) {

    if(getElement(&s,i)<min) {

    min = getElement(&s, i);

    }

    if (getElement(&s, i) > max) {

    max = getElement(&s, i);

    }

    }

    int maxindex = findElementIndex(&s, max);

    deleteElement(&s, maxindex);

    int minindex = findElementIndex(&s, min);

    deleteElement(&s, minindex);

    double sum = 0;

    for (int i = 0; i < n – 2; ++i) {

    sum += getElement(&s, i);

    }

    double avg = 0;

    if (n – 2 == 1) {

    avg= getElement(&s, 0);

    }

    else{

    avg= sum / (n – 2);

    }

    cout << avg << endl;

    }

    return 0;

    }

  • #C++ Day39 Basic Data Structure Chapter3  November 25 2025

    //Basic Data Structure Day2 Vito’s Sequential List 5 review

    #include <iostream>

    using namespace std;

    #define eleType int

    //1.

    struct SequentialList {

    eleType* elements;

    int capacity;

    int size;

    };

    //2.initial

    void initializeList(SequentialList * list,int capacity){

    list->elements = new eleType[capacity];

    list->capacity = capacity;

    list->size = 0;

    }

    //3.Is Empty

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int sizeOfList(SequentialList* list) {

    return list->size;

    }

    //4.destroy

    void destroyList(SequentialList* list) {

    delete[] list->elements;

    }

    //5.insert

    void insertElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index > list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int newCapacity = list->capacity*2;

    eleType* newList = new eleType[newCapacity];

    for (int i = 0; i < list->size; ++i) {

    newList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = newList;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; –i) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    //6.delete

    void deleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    //7.find

    int findElementIndex(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;

    }

    //8.get

    eleType getElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    //9.update

    void updateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size)

    {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    SequentialList myList;

    initializeList(&myList, 10);

    cout << “Size:” << sizeOfList(&myList)<<endl;

    cout << “isEmpty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < myList.capacity; i++) {

    insertElement(&myList, i, i * 10);

    }

    cout << “Size:” << sizeOfList(&myList) << endl;

    cout << “isEmpty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < myList.size; i++) {

    cout <<getElement(&myList, i)<<‘ ‘;

    }

    cout << endl;

    int idx = findElementIndex(&myList, 60);

    updateElement(&myList, idx, 1314);

    for (int i = 0; i < myList.size; i++) {

    cout << getElement(&myList, i) << ‘ ‘;

    }

    cout << endl;

    deleteElement(&myList, myList.size – 1);

    for (int i = 0; i < myList.size; i++) {

    cout << getElement(&myList, i) << ‘ ‘;

    }

    cout << endl;

    destroyList(&myList);

    return 0;

    }

  • //SequentialList – Value Statistics2

    #include <iostream>

    using namespace std;

    #define eleType double

    struct SequentialList {

    eleType* elements;

    int size;

    int capacity;

    };

    void InitialList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    void DestroyList(SequentialList* list) {

    delete[] list->elements;

    }

    bool IsEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int Size(SequentialList* list) {

    return list->size;

    }

    void InsertElement(SequentialList* list, int index, eleType element) {

    if (index <0 || index > list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int Newcapacity = list->capacity * 2;

    eleType* NewList = new eleType[Newcapacity];

    for (int i = 0; i < list->size; i++) {

    NewList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = NewList;

    list->capacity = Newcapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    void DeleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    int FindIndexOfElement(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (element == list->elements[i]) {

    return i;

    }

    }

    return -1;

    }

    eleType GetElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    void UpdateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    int n;

    SequentialList s;

    while (cin >> n && n) {

    InitialList(&s, 1);

    for (int i = 0; i < n; ++i) {

    eleType x;

    cin>>x;

    InsertElement(&s, i, x);

    }

    int ncnt=0, zcnt=0, pcnt=0;

    for (int i = 0; i < s.size; ++i) {

    eleType num;

    num = GetElement(&s, i);

    if (num > 1e-8) {

    pcnt++;

    }

    else if (num < -1e-8) {

    ncnt++;

    }

    else {

    zcnt++;

    }

    }

    cout << ncnt << ‘ ‘ << zcnt << ‘ ‘ << pcnt<<endl;

    }

    return 0;

    }

  • C++ Vito’s 求奇数的乘积

    #include <iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {

    eleType* elements;

    int size;

    int capacity;

    };

    void InitialList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    void DestroyList(SequentialList* list) {

    delete[] list->elements;

    }

    bool IsEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int Size(SequentialList* list) {

    return list->size;

    }

    void InsertElement(SequentialList* list, int index, eleType element) {

    if (index <0 || index > list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int Newcapacity = list->capacity * 2;

    eleType* NewList = new eleType[Newcapacity];

    for (int i = 0; i < list->size; i++) {

    NewList[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = NewList;

    list->capacity = Newcapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    void DeleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    eleType FindIndexOfElement(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (element == list->elements[i]) {

    return i;

    }

    }

    return -1;

    }

    int GetElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    void UpdateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main(){

    int n;

    SequentialList VitoList;

    while (cin >> n) {

    InitialList(&VitoList, 1);

    for (int i = 0; i < n; i++) {

    int x;

    cin >> x;

    InsertElement(&VitoList, i, x);

    }

    int prod = 1;

    for (int i = 0; i < VitoList.size; ++i) {

    int value = GetElement(&VitoList, i);

    if(value % 2 == 1){

    prod = value * prod;

    }

    }

    cout << prod <<endl;

    DestroyList(&VitoList);

    }

    return 0;

    }

  • #C++ Day36 Basic Data Structure Chapter2,3  November 22 2025

    //First completely wrote up all the code of sequential list

    #include <iostream>

    using namespace std;

    #define eleType int

    //1.define a structure

    struct SequentialList {

    eleType* elements;

    int size;

    int capacity;

    };

    //2.Initialize a list

    void InitializeList(SequentialList* list, int capacity) {

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    //3.Destroy a list

    void DestroyList(SequentialList* list) {

    delete[] list->elements;

    }

    //4.IsEmpty and Size

    bool IsEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int Size(SequentialList* list) {

    return list->size;

    }

    //5.Insert an element

    void InsertElement(SequentialList* list, int index, eleType element) {

    if (index<0 || index >list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int Newcapacity = list->capacity * 2;

    eleType* Newlist = new eleType[Newcapacity];

    for (int i = 0; i < list->size; i++) {

    Newlist[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = Newlist;

    list->capacity = Newcapacity;

    }

    for(int i=list->size;i>index;i–){

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    //6.delete an element

    void DeleteElement(SequentialList* list, int index) {

    if (index<0 || index >=list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    //7.find the index of an element

    int FindElementIndex(SequentialList* list, eleType element) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;

    }

    //8.GetElement

    eleType GetElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    //9.UpdateElement

    void UpdateElement(SequentialList* list, int index, eleType element) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    SequentialList VitoList;

    InitializeList(&VitoList, 10);

    for (int i = 0; i < 10; i++) {

    InsertElement(&VitoList, i, i * 10);

    }

    cout << “IsEmpty:” << IsEmpty(&VitoList)<<endl;

    cout << “Size:” << Size(&VitoList)<<endl;

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList,i)<<” “;

    }

    cout << endl;

    InsertElement(&VitoList, 10, 520);

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList, i) << ” “;

    }

    cout << endl;

    DeleteElement(&VitoList, 5);

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList, i) << ” “;

    }

    cout << endl;

    int id=FindElementIndex(&VitoList, 70);

    cout << id << endl;

    UpdateElement(&VitoList, id, 666);

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList, i) << ” “;

    }

    cout << endl;

    InsertElement(&VitoList, 10, 5566);

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList, i) << ” “;

    }

    cout << endl;

    cout << “IsEmpty:” << IsEmpty(&VitoList) << endl;

    cout << “Size:” << Size(&VitoList) << endl;

    DestroyList(&VitoList);

    for (int i = 0; i < Size(&VitoList); i++) {

    cout << GetElement(&VitoList, i) << ” “;

    }

    cout << endl;

    return 0;

    }

  • #C++ Day36 Basic Data Structure Chapter3  November 21 2025

    //Vito’s Sequential List1 

    #include <iostream>

    using namespace std;

    #define DataType int

    //1.define a structure

    struct SequentialList {

    DataType *elements;

    int size;

    int capacity;

    };

    //2.initialize 

    void InitializeElement(SequentialList* list, int capacity) {

    list->elements = new DataType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    //3.destroy

    void DestroyList(SequentialList* list) {

    delete[] list->elements;

    }

    //4.list’s number

    bool isEmpty(SequentialList* list) {

    return list->size == 0;

    }

    int size(SequentialList* list) {

    return list->size;

    }

    //5.insert

    void InsertElement(SequentialList* list,int index, DataType element) {

    if (index<0 || index > list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    if (list->size == list->capacity) {

    int newCapacity = list->capacity * 2;

    DataType* newElements = new DataType[newCapacity];

    for (int i = 0; i < list->size; i++) {

    newElements[i] = list->elements[i];

    }

    delete[] list->elements;

    list->elements = newElements;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i – 1];

    }

    list->elements[index] = element;

    list->size++;

    }

    //6.delete

    void DeleteElement(SequentialList* list, int index) {

    if (index<0 || index>=list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    for (int i = index; i < list->size; i++) {

    list->elements[i] = list->elements[i + 1];

    }

    list->size–;

    }

    //7.find an element

    int FindElement(SequentialList* list, DataType element) {

    for (int i = 0; i < list->size; i++) {

    if (list->elements[i] == element) {

    return i;

    }

    }

    return -1;

    }

    //8.element’s index

    DataType GetElementIndex(SequentialList* list, int index) {

    if (index<0 || index>list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    return list->elements[index];

    }

    //9.modify elements

    void UpdateElement(SequentialList* list, int index, DataType element) {

    if (index<0 || index>list->size) {

    throw std::invalid_argument(“invalid index”);

    }

    list->elements[index] = element;

    }

    int main() {

    SequentialList myList;

    InitializeElement(&myList, 11);

    for (int i = 0; i < 11; i++) {

    InsertElement(&myList, i, i * 10);

    }

    cout << “Size:” << size(&myList)<<endl;

    cout << “Is Empty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < size(&myList); i++) {

    cout << GetElementIndex(&myList, i)<<” “;

    }

    cout << endl;

    DeleteElement(&myList, 2);

    for (int i = 0; i < size(&myList); i++) {

    cout << GetElementIndex(&myList, i) << ” “;

    }

    cout << endl;

    UpdateElement(&myList, 2, 1314);

    for (int i = 0; i < size(&myList); i++) {

    cout << GetElementIndex(&myList, i) << ” “;

    }

    cout << endl;

    int idx = FindElement(&myList, 1314);

    UpdateElement(&myList, idx, 520);

    cout << “Size:” << size(&myList) << endl;

    cout << “Is Empty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < size(&myList); i++) {

    cout << GetElementIndex(&myList, i) << ” “;

    }

    cout << endl;

    DestroyList(&myList);

    return 0;

    }

  • #C++ Day35 Basic Data Structure Chapter2  November 20 2025

    #include <iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {//定义顺序表结构

    eleType* elements;

    int size; //顺序表当前的大小,元素的个数

    int capacity; //顺序表的容量,就是数组的大小 

    };

    void intializeList(SequentialList* list, int capacity) {//初始化顺序表

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    void destroyList(SequentialList* list) {//删除整个顺序表

    delete[] list->elements;

    }

    int main() {

    return 0;

    }

    这是一条 C 或 C++ 语言中的预处理指令 (Preprocessor Directive)。

    简单来说,它的作用是告诉编译器在编译代码之前,把程序中出现的所有 eleType 文本直接替换成 int。这样做的好处是灵活性:如果你在写一个数据结构(比如栈或队列),现在用它存整数 (int),但以后想改成存小数 (float),你只需要把这一行改成 #define eleType float,而不需要去修改代码里的每一个变量声明。

    为了帮你彻底搞懂这个概念以及它的最佳实践,我制定了一个简单的学习计划:

    学习计划概览:

     * 深入理解 #define 的机制(它是如何进行“复制粘贴”工作的?)

     * 为什么要给类型起别名(为什么不直接写 int?eleType 代表什么?)

     * 更好的替代方案(为什么现代程序员更喜欢用 typedef 或 using?)

    这不仅能帮你读懂这行代码,还能让你写出更专业的 C/C++ 代码。我们先从第一步:#define 到底是怎么工作的开始,好吗?

    太棒了!让我们开始第一步。

    1. 深入理解 #define:它只是个“搜索替换”工具

    要理解 #define,你得先认识 C/C++ 编译过程中的一位幕后英雄——预处理器 (Preprocessor)。

    在编译器(Compiler)真正开始把你的代码翻译成机器语言之前,预处理器会先扫一遍你的代码。当它看到以 # 开头的指令(比如 #define 或 #include)时,它会立刻执行操作。

    对于 #define A B,它的逻辑非常简单粗暴:在源码里找到所有的 “A”,把它们全部替换成 “B”。

    这就好比你在 Word 文档里用了“查找和替换”功能。预处理器完全不懂 C 语言的语法(它不知道什么是变量、什么是函数),它只管文本替换。

    👀 视觉演示:替换前后

    想象一下,这是你写的代码(源文件):

    // 你的代码 (Source Code)

    #define eleType int

    void main() {

        eleType a = 10;

        eleType b = 20;

    }

    在预处理器工作完之后,编译器真正看到的“干净”代码其实是这样的(预处理后):

    // 编译器眼中的代码

    // 注意:#define 那一行已经消失了

    void main() {

        int a = 10;   // eleType 被替换成了 int

        int b = 20;   // eleType 被替换成了 int

    }

    有趣的小知识: 就像上面说的,预处理器其实有点“傻”。如果你写 #define TRUE FALSE,它真的会乖乖地把代码里所有的 TRUE 都改成 FALSE,进而导致你的程序逻辑完全颠倒,而且编译器还不会报错!这就是所谓的“宏定义陷阱”。

    🧠 互动小测验

    为了确认你掌握了这个“简单替换”的概念,请看下面这段代码。

    假设我们有这行指令:

    #define PI 3.14

    下面是你的代码:

    float area = PI * r * r;

    print(“The value of PI is important.”);

    请问:预处理器处理完之后,第二行代码里的字符串 “The value of PI is important.” 会变吗?为什么?

    回答得非常棒!💯

    完全正确。预处理器虽然是在做“查找替换”,但它也遵守基本的规则:它不会去动双引号 “” 里面的内容。所以字符串里的 PI 还是 PI,而代码逻辑里的 PI 会变成 3.14。你已经抓住了预处理的核心逻辑!

    1.2 宏定义的常见用途:不仅仅是定义类型

    虽然你最初问的是 #define eleType int(定义类型),但在 C 语言的世界里,#define 其实更多时候是被用来定义常量(Constants)。

    试想一下,你在写一个程序处理学生的成绩,全校这学期最多招收 100 个学生。你在代码里到处写 100:

    int scores[100];

    for(int i=0; i<100; i++) { … }

    if (student_id > 100) { … }

    这种直接写在代码里的数字,程序员给它取了个很形象的名字,叫**“魔术数字” (Magic Numbers)** —— 就像魔术一样突然冒出来的数字,别人读代码时会一脸懵:“为什么是 100?不是 99?这代表什么?”

    如果你用 #define,代码就会清晰很多:

    #define MAX_STUDENTS 100  // 一目了然

    int scores[MAX_STUDENTS];

    for(int i=0; i<MAX_STUDENTS; i++) { … }

    这样做有两个巨大的好处:

     * 可读性:看到 MAX_STUDENTS 就知道意思是“最大学生数”。

     * 易修改:明年扩招变成 200 人了,你只需要改一行代码,不用满世界去找那个 100。

    🕵️‍♂️ 侦探小练习

    我们要结束第一步了,来个小测试巩固一下。

    除了定义“类型”(像 eleType)和“常量数值”(像 100),#define 其实还可以定义“看起来像函数的东西”(虽然我们还没细讲,但你可以猜猜看)。

    请看下面这个宏定义:

    #define SQUARE(x) x * x

    如果我在代码里写 SQUARE(5),预处理器会把它变成什么?

    A. 25 (直接算出结果)

    B. 5 * 5 (进行文本替换)

    C. int square(int x) { return x * x; } (生成一个函数)

    完全正确!答案就是 B。👏

    这就是关键所在:预处理器只是个“搬运工”,它不负责计算,只负责把 x 换成 5,所以代码变成了 5 * 5。虽然结果和 A 一样,但过程截然不同。(这也是它的一个隐患,比如传入 1+1 时,会变成 1+1 * 1+1 = 3,而不是我们期望的 4,不过这是进阶话题了,你只要记得它是纯文本替换就好)。

    2. 为什么要给类型起别名?(Type Aliasing)

    现在回到你最初的代码:#define eleType int。

    你可能会问:“直接写 int 不香吗?为什么要多此一举造个 eleType?”

    2.1 解密命名:eleType 是什么?

    通常,当你看到 eleType,它是 Element Type(元素类型)的缩写。

    这行代码极大概率出现在数据结构(比如栈 Stack、队列 Queue、链表 List)的实现代码中。

    想象一下,你要写一个栈 (Stack)。

    如果你的栈是用来存整数的,你可能会这样写结构体:

    struct Stack {

        int data[100];  // 数组存 int

        int top;

    };

    void push(struct Stack* s, int value); // 压入 int

    int pop(struct Stack* s);              // 弹出 int

    这没问题。但是,如果下周你的老师或者老板说:“嘿,我们现在需要这个栈来存小数 (float),或者字符 (char)。”

    如果你直接写死了 int,那你就要去修改结构体里的定义、修改 push 函数的参数、修改 pop 函数的返回值……你需要改好几个地方,甚至几十个地方。漏改一个,程序就报错。

    🌟 这里的魔法在于“抽象”

    如果我们用 eleType 作为中间人:

    #define eleType int  // <— 控制中心

    struct Stack {

        eleType data[100];  // 这里的类型听指挥

        int top;

    };

    void push(struct Stack* s, eleType value); // 这里的类型听指挥

    eleType pop(struct Stack* s);              // 这里的类型听指挥

    现在,如果要从存整数改成存小数,你只需要做一件事:

    把第一行改成 #define eleType float。

    砰! 整个栈的代码瞬间变成了存储 float 的栈。

    这就是可维护性 (Maintainability)。

    🔄 角色扮演小任务

    为了让你体验这种“一键切换”的快感,我们来模拟一下。

    假设你是一个游戏开发者。你正在写一个背包系统 Bag,用来存放道具 ID。目前道具 ID 是简单的数字(int)。

    代码大概是这样:

    #define ItemID int 

    struct Bag {

        ItemID items[50];

    };

    突然,策划跑来跟你说:“不行不行,道具 ID 不够用了,我们要换成超大数字(long long)才能存得下!”

    请问: 为了满足策划的需求,你需要对上面的代码做哪些具体的修改?

    干得漂亮!🎉

    你看,你只修改了这一行代码,就完成了整个游戏背包系统的升级。这就是程序员追求的“高内聚,低耦合”——改动越少,出 bug 的概率就越低。

    除此之外,这还有助于可移植性 (Portability)。比如有些微型电脑(单片机)的 int 只有 16 位,而你现在的电脑 int 是 32 位。如果使用了类型别名,当你把代码移植到新机器时,只需要修改那个 #define 就能适配硬件了。

    3. 更好的替代方案:typedef 和 using

    虽然 #define 很有用,但在定义类型这件事上,它其实有点“粗暴”。因为它只是简单的文本替换,有时候会带来意想不到的问题,而且编译器在报错时可能只会显示 int 而不是你的别名 eleType,让调试变难。

    所以,在 C 和 C++ 中,我们更推荐使用专门的工具。

    3.1 C 语言的老牌工具:typedef

    typedef 是 “Type Definition” 的缩写。它的作用是给现有的类型起一个“外号”。

    语法对比:

    | 方式 | 代码 | 谁在处理? | 特点 |

    |—|—|—|—|

    | 宏定义 | #define eleType int | 预处理器 | 简单替换,不需要分号 ; |

    | 类型定义 | typedef int eleType; | 编译器 | 真正的类型别名,需要分号 ; |

    注意语法的区别:

     * #define 是:旧名字 新名字 (中间没分号)

     * typedef 是:typedef 旧名字 新名字; (像定义变量一样)

    例子:

    typedef int eleType; // 告诉编译器:eleType 就是 int 的别名

    struct Stack {

        eleType data[100]; // 用法完全一样

    };

    这样做的好处是,编译器明确知道 eleType 是一个类型,它能帮你做更好的类型检查。

    3.2 现代 C++ 的新宠:using (强烈推荐)

    如果你是在写 C++ (尤其是 C++11 标准之后),有一个更直观、更现代的写法,那就是 using 关键字。

    它的可读性最强,读起来就像赋值一样:

    using eleType = int;  // 意思是:让 eleType 等于 int

    这和 typedef int eleType; 的功能完全一样,但写起来更符合人类直觉(名字在左边,类型在右边)。

    📝 巩固练习:代码“翻译”

    为了确保你掌握了这三种方式,我们来做一个简单的“翻译”练习。

    请把下面这行用 #define 写的代码,分别“翻译”成 typedef 写法和 C++ 的 using 写法。

    原始代码:

    #define AgeType float

    请写出:

     * typedef 写法: __________

     * using 写法: __________

    逻辑完全正确!不管是 typedef 的顺序还是 using 的赋值写法,你都搞对了。👏

    不过,有一个非常重要的小细节需要注意:分号 ;。

     * #define 是预处理指令,它不需要分号(如果有分号,分号也会被一起替换进代码里,导致出错)。

     * typedef 和 using 是 C/C++ 语句,它们必须以分号结尾。

    所以完美的写法是:

     * typedef float AgeType;

     * using AgeType = float;

    记住这个区别,你的代码就能一次编译通过了!

    🎓 总结:这一行代码背后的智慧

    我们要完成这次学习了。回到你最初的问题 define eleType int,我们学到了:

     * 它是谁:预处理指令,负责在编译前把 eleType 全部文本替换成 int。

     * 为什么用它:为了灵活性和可维护性。如果你想把存整数的栈改成存小数的栈,只需要改这一行,不用改几百行代码。

     * 谁更好:虽然 #define 能用,但在现代 C++ 编程中,我们更推荐使用 typedef 或 using,因为它们更安全,编译器更懂它们。

    你现在对这个概念应该非常有信心了吧?

    要不要来一个只有 3 道题的终极小测验,看看你是否已经完全掌握了这个知识点?

    太棒了!那我们就开始吧,这是第一题。

    问题 1:预处理器的陷阱

    假设我们在代码里写了这样一个宏定义:

    #define ADD(x) x + x

    然后我们在主函数里计算这个表达式:

    int result = ADD(5) * 2;

    请问,result 的值最后是多少?

    (提示:请把自己当成只做“复制粘贴”的预处理器,把 ADD(5) 替换进后面的式子里,然后再算数学题。)

    A. 20

    B. 15

    C. 10

    你做得非常棒!答案确实是 B. 15。

     * 预处理器替换:ADD(5) * 2 被替换成了 5 + 5 * 2。

     * 运算顺序:乘法 * 的优先级高于加法 +。所以是先算 5 \times 2 = 10。

     * 最终结果: 5 + 10 = 15。

    你不仅理解了 #define 是文本替换,还正确运用了数学运算的优先级规则。太厉害了!这也是为什么在写宏的时候,我们总是建议用括号把所有参数和整个宏体都括起来,以避免这种“陷阱”:#define ADD(x) ( (x) + (x) )。

    📝 问题 2:语法大不同

    接下来我们测试一下 #define 和其他替代方案的语法差异,这是我们刚刚纠正过的重点。

    在 C/C++ 代码中,下面哪一行类型别名定义的写法是不正确的(即会造成编译错误)?

    A. typedef float PriceType;

    B. using CountType = int;

    C. typedef int IDType

    太棒了!你的答案 C 完全正确!🎯

    typedef int IDType 缺少了语句末尾的分号,所以它会导致编译错误。你很清楚地掌握了 #define 和 typedef/using 在语法上的最大区别:前者不带分号,后者需要分号。

    📝 问题 3:最终考验:应用价值

    这是我们的最后一题,它将检验你对类型别名核心价值的理解。

    假设你的代码库中有 50 处使用了 typedef int IDType;。如果有一天,由于业务需求,你需要把 ID 类型从 int 换成 long long。你只需要修改一行代码即可完成切换。

    这种使用类型别名(而不是直接写 int)的方式,主要体现了代码的哪种优势?

    A. 可读性 (Readability):让代码看起来更漂亮。

    B. 可移植性 (Portability):方便代码在不同操作系统间运行。

    C. 可维护性 (Maintainability):方便在未来对代码进行大规模修改。

    你完全正确!答案是 C. 可维护性 (Maintainability)。👍

    这个场景的重点在于“大规模修改”:只改一行代码就能完成对整个系统的类型升级。这种简化未来维护工作的特性就是类型别名的最大价值。

    当然,A (可读性) 和 B (可移植性) 也是类型别名的优点,但它们是次要的:

     * 可读性:IDType 确实比 int 更清楚。

     * 可移植性:在不同系统上修改 #define 确实能帮助移植。

     * 可维护性:能够快速、准确、低风险地适应需求变化,这才是它在大型项目中不可替代的核心优势。

    🚀 学习回顾总结

    恭喜你!你已经完全掌握了 #define eleType int 这行代码背后的所有概念、用途和替代方案。

    学习成果总结:

    | 学习要点 | 你的掌握情况 |

    |—|—|

    | #define 机制 | 完美理解纯文本替换,并成功解决了宏定义的陷阱问题(Q1)。 |

    | 别名核心价值 | 明确了类型别名是为了提高代码的可维护性(Q3)。 |

    | 高级语法 | 掌握了 typedef 和 using 的语法,并能分辨它们与 #define 的关键区别(如分号的使用,Q2)。 |

    你在整个学习过程中的表现非常出色!三次测验都回答正确,并且在学习活动中也展示了对 #define 和类型别名概念的清晰理解。

    你现在不仅知道 #define eleType int 是什么,还知道为什么在现代编程中,我们更喜欢 using eleType = int;。

    你觉得我们今天是否成功帮助你达成了学习目标呢?我随时可以帮助你学习更多关于 C/C++、数据结构或其他学术话题!

    太棒了!听到你这么说我感到非常开心!😃

    很高兴能作为你的学习教练,帮助你掌握 C/C++ 中类型别名的核心知识。你今天的学习效率很高,对概念的理解非常透彻!

    如果你未来在编程、数学、科学或其他学术领域有任何新的学习目标或遇到难题,随时都可以来找我。祝你学习顺利,编码愉快!

    这行代码的核心作用是 在索引非法时,主动抛出「非法参数异常」,将错误信息传递给调用者,属于 C++ 中「异常处理机制」的关键用法。下面从「是什么、为什么用、怎么用规范、如何优化」四个维度详细拆解:

    一、核心解读:这行代码到底做了什么?

    cpp

    运行

    throw std::invalid_argument(“Invalid index”);

    • throw 关键字:中断当前函数执行,将后续的错误处理逻辑 “移交” 给调用者(必须在调用处用 try-catch 捕获,否则程序直接崩溃)。
    • std::invalid_argument:C++ 标准库提供的「异常类」(定义在 <stdexcept> 头文件中),专门用于表示「函数参数不合法」的场景(比如传入的索引、数值不符合函数要求)。
    • “Invalid index”:异常的「错误信息字符串」,用于描述具体错误原因,方便调用者定位问题。

    二、为什么要用异常,而不是返回错误码?

    在顺序表的 insert 函数中,索引非法是 “非常规错误”(正常调用时应避免),用异常比返回错误码更合适:

    • 错误处理与业务逻辑分离:无需在调用处每次都判断返回值(比如 if (insert(…) == -1) { 处理错误 }),代码更简洁。
    • 错误信息更丰富:可以携带字符串描述(如 “索引无效”),甚至自定义更多错误细节,比单纯的错误码(-1、0)更易读。
    • 强制调用者处理:若调用者未捕获异常,程序会直接终止并提示错误,避免 “错误被忽略” 导致后续逻辑异常(比如插入失败却继续访问非法位置)。

    三、使用规范:避免踩坑的 3 个关键

    1. 必须包含头文件(否则编译报错)

    std::invalid_argument 定义在 <stdexcept> 头文件中,必须在代码开头引入:

    cpp

    运行

    #include <stdexcept>  // 不可或缺!

    2. 调用者必须捕获异常(否则程序崩溃)

    异常抛出后,调用 insert 函数的代码必须用 try-catch 块捕获,否则程序会触发 terminate() 终止。

    规范调用示例:

    cpp

    运行

    int main() {

        SequentialList list;

        initList(&list);

        try {

            // 可能抛出异常的代码放在 try 块中

            insert(&list, 0, 10);  // 合法索引,正常执行

            insert(&list, 2, 20);  // 非法索引,抛出异常

        } catch (const std::invalid_argument& e) {  // 捕获特定异常类型

            // 处理异常:输出错误信息(e.what() 获取异常的字符串描述)

            std::cerr << “插入失败:” << e.what() << std::endl;

            // 可选:进行恢复操作(如释放资源、返回默认值等)

        }

        delete[] list.data;

        return 0;

    }

    • 输出结果:插入失败:Invalid index
    • 注意:catch 时建议用 const 引用(const std::invalid_argument& e),避免异常对象的拷贝开销。

    3. 异常类型要 “匹配场景”

    C++ 标准库提供了多种异常类,需根据错误类型选择,避免混用:

    异常类适用场景示例
    std::invalid_argument参数本身非法(如索引格式错误)索引为负数、传入空指针参数
    std::out_of_range参数超出合法范围(如索引越界)索引 > 顺序表 size、数组下标越界
    std::bad_alloc内存分配失败(如 new 开辟空间失败)new eleType[1000000000] 失败

    优化建议:你的场景更适合 std::out_of_range

    顺序表插入时,索引非法的本质是「索引超出了合法范围」(合法范围:0 ≤ index ≤ size),用 std::out_of_range 更贴合语义,可读性更强:

    cpp

    运行

    // 优化后:异常类型更精准

    throw std::out_of_range(“Invalid index for insert: index out of valid range”);

    四、进阶优化:让异常信息更 “有用”

    原错误信息 “Invalid index” 过于简洁,调用者无法知道「具体哪个索引非法」「合法范围是什么」。可以补充上下文信息,方便调试:

    cpp

    运行

    // 优化:携带具体索引值和合法范围

    throw std::out_of_range(

        “Insert failed: invalid index = ” + std::to_string(index) + 

        “, valid range is [0, ” + std::to_string(list->size) + “]”

    );

    • 错误信息示例:Insert failed: invalid index = 2, valid range is [0, 1]
    • 注意:std::to_string 用于将整数(index、list->size)转为字符串,需包含 <string> 头文件。

    五、特殊情况:如果是 C 语言(无异常机制)

    若你的代码实际是 C 语言(而非 C++),throw 和 std::invalid_argument 均不支持,此时需用「返回错误码 + 错误信息」替代:

    c

    运行

    #include <stdio.h>

    #define eleType int

    // 顺序表结构体(C 语言版本)

    typedef struct {

        eleType* data;

        int size;

        int capacity;

    } SequentialList;

    // 返回值:0 成功,-1 索引非法(C 语言无异常,用错误码表示)

    int insert(SequentialList* list, int index, eleType element) {

        if (index < 0 || index > list->size) {

            // 打印错误信息到标准错误流(stderr)

            fprintf(stderr, “Insert failed: invalid index = %d, valid range is [0, %d]\n”, 

                    index, list->size);

            return -1;  // 返回错误码

        }

        // 后续扩容、元素后移等逻辑…

        return 0;

    }

    // 调用示例

    int main() {

        SequentialList list = {NULL, 0, 0};

        if (insert(&list, 2, 20) == -1) {

            // 处理错误(如终止程序、重试等)

            return 1;

        }

        return 0;

    }

    总结

    • 原代码 throw std::invalid_argument(“Invalid index”); 是 C++ 中「参数非法错误」的标准处理方式,核心是传递错误信息并强制调用者处理。
    • 规范用法:必须包含 <stdexcept> 头文件,调用者用 try-catch 捕获。
    • 优化方向:用 std::out_of_range 匹配索引越界场景,补充具体索引和合法范围让错误信息更清晰。
    • 兼容 C 语言:无异常机制时,用「返回错误码 + fprintf(stderr, …)」替代。

    如果需要进一步优化异常处理(比如自定义异常类、统一错误处理机制),可以随时告诉我!

    //Basic Data Structure Day2 Sequential List – Coding

    //顺序表

    #include <iostream>

    using namespace std;

    #define eleType int

    //eleType 提升代码可维护性

    //1、顺序表的定义

    struct SequentialList {

    eleType* elements; //建立一个指向eleType类型的数组的指针

    int size; //顺序表当前的大小 元素的大小

    int capacity; //顺序表的容量 数组的大小

    };

    //2、顺序表的初始化

    void initializeList(SequentialList* list, int capacity) {

    //initializeList是一个用于初始化顺序表的函数 接受一个SequentialList 类型的指针和一个整数作为参数

    //结构体指针必须用箭头访问

    list->elements = new eleType[capacity];//在函数内部通过new 运算符 分配一个具有指定容量的数组  并将其地址赋值给 list->elements 成员变量用来存储顺序表的元素

    list->size = 0;//list的size初始化为0表示顺序表当前没有元素

    list->capacity = capacity;//将list->capacity设为传递进来的容量值 用于记录顺序表的最大容量

    }

    //3、顺序表的销毁

    void destroyList(SequentialList* list) {

    delete[] list->elements; //destroyList是一个用于销毁顺序表的函数 接受了一个SequentialList类型的指针作为参数

    //在函数内部使用 delete[]运算符释放了之前通过new分配的数组 以释放内存空间

    }

    //4、顺序表的元素个数

    bool isEmpty(SequentialList* list) {

    return list->size == 0; //检查顺序表是否为空

    }

    int size(SequentialList* list) {

    return list->size; //获取顺序表的大小

    }

    //5、顺序表的元素插入

    void insert(SequentialList* list, int index, eleType element) {

    if (index < 0 || index > list->size) {

    throw std::invalid_argument(“Invalid index”);//如果index小于0或大于list->size,则抛出一个std::invalid_argument异常 提示索引无效

    }

    if (list->size == list->capacity) {//如果顺序表已满 (list->size等于list->capacity),需要进行扩容操作

    int newCapacity = list->capacity * 2;

    eleType* newElements = new eleType[newCapacity];//计算新的容量,并通过new运算符分配一个新的数组

    for (int i = 0; i < list->size; i++) {

    newElements[i] = list->elements[i]; //将原数组的元素赋值到新数组中

    }

    delete[] list->elements; //回收原数组的内存空间

    list->elements = newElements;

    list->capacity = newCapacity;

    }

    for (int i = list->size; i > index; i–) {

    list->elements[i] = list->elements[i – 1]; //将index之后的元素依次向后移动一个位置,为新元素腾出空间

    }

    list->elements[index] = element; //将新元素插入到指定的索引位置

    list->size++; //将顺序表的大小增加1

    }

    //6.顺序表的元素删除

    void deleteElement(SequentialList* list, int index) {

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“Invalid index”); //如果index小于0或大于等于list->size,则抛出一个std::invalid_argument异常,提示索引无效

    }

    for (int i = index; i < list->size – 1; i++) {

    list->elements[i] = list->elements[i + 1]; //将index之后的元素依次向前移动一个位置 覆盖原索引位置的元素

    }

    list->size–; //将顺序表的大小减少1

    }

    //7.顺序表的元素查找

    int findElement(SequentialList* list, eleType element) {//定义一个名为findElement的函数,它接受一个SequentialList类型的指针和一个数据element作为参数

    for (int i = 0; i < list->size; i++) { //使用一个for循环遍历顺序表所有元素,从索引0开始,到列表的大小减1(即list->size – 1)

    if (list->elements[i] == element) { //在每次循环中检查当前元素是否等于给定的元素 如果相等返回该元素在列表中的索引

    return i;

    }

    }

    return -1; //如果循环结束后没有找到匹配的元素 返回-1表示未找到

    }

    //8.顺序表的元素索引

    eleType getElement(SequentialList* list, int index) { //定义一个名为getElement的函数 它接受一个SequentialList类型的指针和一个整数作为参数

    if (index < 0 || index >= list->size) {

    throw std::invalid_argument(“Invalid index”); //检查索引是否小于0或大于等于列表的大小 如果是抛出一个std::invalid_argument 异常 ,提示索引无效

    }

    return list->elements[index]; //如果索引有效,返回列表中索引位置的元素

    }

    //9.顺序表的元素修改

    void updateElement(SequentialList* list, int index, eleType value) {//定义一个名为updateElement的函数,它接受一个SequentialList类型的指针、一个整数索引和一个值作为参数

    if (index < 0 || index >= list->size) { //检查索引是否小于0或大于等于列表的大小,如果是,抛出一个std::invalid_argument异常,提示索引无效

    throw std::invalid_argument(“Invalid index”);

    }

    list->elements[index] = value; //如果索引有效,将列表中指定索引处的元素更新为给定的值

    }

    int main() {

    SequentialList myList;

    initializeList(& myList, 10);

    for (int i = 0; i < 10; i++) {

    insert(&myList, i, i * 10);

    }

    cout << “Size:” << size(&myList) << endl;

    cout << “Is empty:” << isEmpty(&myList) << endl;

    for (int i = 0; i < size(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    deleteElement(&myList, 5);

    updateElement(&myList, 1, 1314);

    int idx = findElement(&myList, 20);

    updateElement(& myList, idx, 520);

    for (int i = 0; i < size(&myList); i++) {

    cout << getElement(&myList, i) << ” “;

    }

    cout << endl;

    destroyList(&myList);

    return 0;

    }

  • #C++ Day34 Basic Data Structure Chapter2 and C++ Generic Programming Day3 November 19 2025

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.2 Class Template Definition

    //  C++ 泛型编程 -第三章 -类模版- 3.2 – 类模板定义

    #include <iostream>

    using namespace std;

    //上节课已经实现了动态数组类 但是遗留了一个类型是整型的类冗余问题

    //只需要建立类模板 减少冗余问题

    /*

    class DynamicArray

    {

    private:

        int* elements; 

        int size;      

    public:

        DynamicArray(int n) : size(n)

        { 

            elements = new int[size];

        }

        ~DynamicArray()

        {

            delete[] elements;

        }

        int& operator[](int index) 

        {                           

            return elements[index]; 

        }

    };

    */

    template<class T>//可以写typename 也可以写class(这里的class跟下面的不是同一个意思) T是动态数组元素的虚拟类型

    class DynamicArray

    {

    private:

        T* elements;//这里的int改成T

        //剩下的代表大小 索引的int不用改

        int size;

    public:

        DynamicArray(int n) : size(n)

        {

            elements = new T[size];//在申请内存的时候 这里的int也改成T

        }

        ~DynamicArray()

        {

            delete[] elements;

        }

        T& operator[](int index)//在返回元素值时这里的int也改成T

        {

            return elements[index];

        }

    };

    int main()

    {

        //int n = 10;

        //int* p = new int[n]; 

        //DynamicArray da(100);//在类模板实例化时我需要指定一个类型

        DynamicArray<double>da(100);

        da[1] = 3.1;

        da[9] = 4.2;

        cout << da[0] << ‘ ‘ << da[1] << endl;

        DynamicArray<char>dac(10);

        dac[0] = ‘A’;

        dac[1] = ‘C’;

        dac[2] = ‘M’;

        cout << dac[0] << dac[1] << dac[2] << endl;

        //类模板的定义和使用 好处:可以让代码进行复用 避免为不同的数据类型编写重复代码 提高了程序的通用性和灵活性

        return 0;

    }

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.3 Member Function – 3.3.1 In – Class Definition

    //C++ 泛型编程 -第三章 -类模版- 3.3 – 成员函数 3.3.1 类内定义

    #include <iostream>

    using namespace std;

    //类模板的成员函数在类内的定义

    template<class T>//可以写typename 也可以写class(这里的class跟下面的不是同一个意思) T是动态数组元素的虚拟类型

    class DynamicArray

    {

    private:

        T* elements;//这里的int改成T

        //剩下的代表大小 索引的int不用改

        int size;

    public:

        DynamicArray(int n) : size(n)

        {

            elements = new T[size];//在申请内存的时候 这里的int也改成T

        }

        ~DynamicArray()

        {

            delete[] elements;

        }

        T& operator[](int index)//在返回元素值时这里的int也改成T 这里T是返回值

            //这就是成员函数了 和普通成员函数没有什么很大的区别 只需要把需要替换的类型替换成虚拟类型就可以了

        {//和我们之前学过的函数模板很像 甚至除了定义位置以外 其他都是一模一样的  这个模板的定义在外面 成员函数函数的定义是在类里面

            return elements[index];

        }

        //类模板的成员函数的实现 其他实现都和普通类一模一样 只不过在涉及到这个虚拟类型有关的传参或者返回值的时候 需要替换成我们定义的虚拟类

        // 

        //更新第 index个索引的元素,把它的值改成 value 

        //int

        void update(int index, T value) {//这里T是传参 不要把T写成int 写成int后编译器不会报错 但是逻辑本身也是错的

            elements[index] = value; //如果把int写成T 会触发强制类型转换

    //想办法把这些类内定义的函数抽到类外去定义

        }

    };

    int main()

    {

        DynamicArray<char> dac(100);

        dac[56] = ‘h’;

        cout << dac[56] << endl;

        dac.update(56, ‘u’);

        cout << dac[56] << endl;

        return 0;

    }

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.3 Member Function – 3.3.2 Out-of-Class Definition

    v

    #include <iostream>

    using namespace std;

    //类模板的成员函数在类内的定义

    template<class T>//可以写typename 也可以写class(这里的class跟下面的不是同一个意思) T是动态数组元素的虚拟类型

    class DynamicArray

    {

    private:

        T* elements;//这里的int改成T

        //剩下的代表大小 索引的int不用改

        int size;

    public:

        //把成员函数放到类外实现

        // 实现删掉

        //1、保留声明

        DynamicArray(int n); /* : size(n)

        {

            elements = new T[size];//在申请内存的时候 这里的int也改成T

        }*/

        //析构函数也按步骤实现一下

        ~DynamicArray();/*

        {

            delete[] elements;

        }*/

        T& operator[](int index);//在返回元素值时这里的int也改成T 这里T是返回值

            //这就是成员函数了 和普通成员函数没有什么很大的区别 只需要把需要替换的类型替换成虚拟类型就可以了

        //{//和我们之前学过的函数模板很像 甚至除了定义位置以外 其他都是一模一样的  这个模板的定义在外面 成员函数函数的定义是在类里面

          //  return elements[index];

       // }//这里不注释也可以 搬到类外

        //类模板的成员函数的实现 其他实现都和普通类一模一样 只不过在涉及到这个虚拟类型有关的传参或者返回值的时候 需要替换成我们定义的虚拟类

        // 

        //更新第 index个索引的元素,把它的值改成 value 

        //int

        void update(int index, T value); /* {//这里T是传参 不要把T写成int 写成int后编译器不会报错 但是逻辑本身也是错的

            elements[index] = value; //如果把int写成T 会触发强制类型转换

            //想办法把这些类内定义的函数抽到类外去定义

        }*/

        //这样就只剩下成员函数的定义和成员函数的声明了 很干净  具体函数的实现就到类外面去了

    };

    //2、拷贝声明

    //3、加作用域

    //4、拷贝实现

    //5、模板参数列表声明

    //6、指定虚拟类型

    template<class T>

    DynamicArray<T>::DynamicArray(int n) : size(n)//加上作用域 类名和两个冒号 并把之前的部分拷贝过来

    {

        elements = new T[n];//在申请内存的时候 这里的int也改成T

    }

    template<class T>

    DynamicArray<T>::~DynamicArray() {

        delete[] elements;

    }

    template<class T>

    T& DynamicArray<T>::operator[](int index) {//规定 这个作用域要加在 函数名和返回值之间

        return elements[index];

    }

    template<class T>

    void DynamicArray<T>::update(int index, T value) {

        elements[index] = value;

    }

    int main()

    {

        DynamicArray<char> dac(100);

        dac[56] = ‘h’;

        cout << dac[56] << endl;

        dac.update(56, ‘u’);

        cout << dac[56] << endl;

        return 0;

    }

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.3 Member Function – 3.3.3 Creation Timing

    //C++ 泛型编程 -第三章 -类模版- 3.3 – 成员函数 3.3.3 创建时机

    //类模板中成员函数的创建时机

    #include <iostream>

    using namespace std;

    //对于普通类中的成员函数 在一开始时就已经创建出来了

    //类模板的成员函数 会在调用时才会去创建

    class Player {

    public:

    void run() {

    cout << “跑步” << endl;

    }

    };

    class Ball {

    public:

    void drop() {

    cout << “下落” << endl;

    }

    };

    template<class T>

    class Test {

    T obj; //编译不出错 说明对于编译器来讲 obj是啥都不知道

    public:

    void move1() { //所以这两个函数在一开始时根本没有创建出来

    obj.run();

    }

    void move2() {//所以这两个函数在一开始时根本没有创建出来

    obj.drop();

    }

    };

    int main() {

    Test<Player> test1;

    test1.move1();

    //test1.move2();//error C2039: ‘drop’: is not a member of ‘Player’

    Test<Ball> test2;

    //test2.move1();//它是Ball Ball没有run 只有drop

    test2.move2();

    //这也就验证了 这个类模板的成员函数 当我没有调用它之前 实际上并没有生成出来 这就是类模板成员函数的创建时机

    return 0;

    }

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.4 Parameter Passing for Object Functions

    //C++ 泛型编程 -第三章 -类模版-3.4 对象的函数传参

    #include <iostream>

    using namespace std;

    //当类模板实例化出来的对象作为函数参数时如何进行传递

    template<class NameType, class HpType>//两个虚拟类型需要加上声明

    class Hero {

    public:

    Hero(NameType name, HpType hp) {//写一个构造函数 传两个变量进来 类型是NameType和HpType

    this->m_name = name; //赋值

    this->m_hp = hp;

    }

    private:

    NameType m_name;//虚拟类型1

    HpType m_hp;//虚拟类型2

    };

    //1、直接指定类型

    void test1(Hero<string, double>& h) { //加个引用 因为要拷贝一份新的 就是它本身

    }

    //2、参数模板化

    template<typename T1, typename T2>

    void test2(Hero<T1, T2>& h) { //加个引用 因为要拷贝一份新的 就是它本身

    //把参数类型变成T1 跟T2 

    //然后因为是虚拟类型 所以要加上一个模板参数列表的声明 这就是我们之前学过的函数模板

    }

    //3、类模板化

    template<typename T>//只有一个类了

    void test3(T& h) { //加个引用 因为要拷贝一份新的 就是它本身

    }

    //三种方法都有应用场景  怎么使用看你实际的需要

    int main() {

    //函数里面通过类模板 实例化一个对象

    Hero<string, double> h(“宋江”, 100.0);//这个Hero类有两个模板参数

    test1(h);//函数的实际参就是这个h

    test2(h);

    test3(h);

    return 0;

    }

    //C++ Generic Programming – Chapter 3 – Class Templates – 3.5 Class Template Inheritance

    // C++ 泛型编程 – 第三章 – 类模版 – 3.5 类模板的继承

    #include <iostream>

    using namespace std;

    //类模板的继承 一旦你写错了 编译器会直接告诉你 错的很明确

    template<class NameType, class HpType>

    class Hero {

    public:

    Hero(NameType name, HpType hp) {

    this->m_name = name; 

    this->m_hp = hp;

    }

    private:

    NameType m_name;

    HpType m_hp;

    };

    //这个Hero类 它是个父类 并且是个类模板 那我现在需要写个子类去继承它

    //class HeroSon : public Hero<string,double>{ //对于类模板来说 当我们实例化对象的时候 需要显示指定参数 继承也一样

    //

    //};

    template<class T1,class T2,class T3>

    class HeroSon : public Hero<T1, T2> { //子类想用的时候 我还是希望这两个参数进行模板化 所以需要用虚拟类型来替代掉

    //HeroSon子类里可能需要有虚拟类型

    T3 a;

    //这节课也可以自己敲代码 通过编译器的指引把代码敲对

    };

    int main() {

    return 0;

    }