博客

  • 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++ Day21 October 31 2025

    //13 – 1 citation grammar

    //引用的语法

    #include <iostream>

    using namespace std;

    //quotation

    //引用比指针更好理解

    //指针:所以爱会消失吗。对嘛?

    //引用:给指针取一个别名 

    //& 

    //数据类型& 变量名 = 变量;

    //reference13-

    void test() {

    int a_very_very_very_very_very_very_very_very_long_array[8] = { 1,1 };

    for (int i = 2; i < 8; ++i) {

    a_very_very_very_very_very_very_very_very_long_array[i] = a_very_very_very_very_very_very_very_very_long_array[i – 1] * a_very_very_very_very_very_very_very_very_long_array[i – 1] + a_very_very_very_very_very_very_very_very_long_array[i – 2] * a_very_very_very_very_very_very_very_very_long_array[i – 2];

    }

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

    cout << a_very_very_very_very_very_very_very_very_long_array[i] << ” “;

    }

    //太长了代码

    cout << endl;

    for (int i = 2; i < 8; ++i) {

    a_very_very_very_very_very_very_very_very_long_array[i] = 0;

    }

    for (int i = 2; i < 8; ++i) {

    int& pre1 = a_very_very_very_very_very_very_very_very_long_array[i – 1];

    int& pre2 = a_very_very_very_very_very_very_very_very_long_array[i – 2];

    int& now = a_very_very_very_very_very_very_very_very_long_array[i];

    //referenced it

    now = pre1 * pre1 + pre2 * pre2;

    }

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

    cout << a_very_very_very_very_very_very_very_very_long_array[i] << ” “;

    }

    cout << endl;

    }

    int main() {

    /*

    int a = 1314;// 1. 定义int类型变量a,赋值为1314

    int& b = a;// 2. 定义int类型的引用b,将b绑定到变量a上

    b = 520;

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    // 3. 查看a和b的内存地址:地址完全相同(证明是同一块内存)

    cout << “a的地址:” << &a << endl; 

    cout << “b的地址:” << &b << endl;

    */

    int a = 1314;// 1. 定义int类型变量a,赋值为1314

    int* b = &a;// 

    *b = 520;

    cout << “a = ” << a << endl;

    cout << “b = ” << *b << endl;//用指针写法需要多写三个*号 引用只需要写一个&

    test();

    return 0;

    }

    你写出的这两行代码涉及 C++ 中的引用(Reference) 特性,这是 C++ 区别于 C 语言的重要语法之一。下面我会逐行解析代码含义、引用的核心特性,以及实际使用中的注意事项,帮你彻底理解这段代码的本质。

    一、逐行解析代码含义

    cpp

    int a = 1314;  // 1. 定义int类型变量a,赋值为1314

    int& b = a;    // 2. 定义int类型的引用b,将b绑定到变量a上

    关键概念:什么是 “引用”?

    引用可以理解为变量的 “别名” —— 它不是一个新的变量(不占用额外内存),而是对已存在变量(这里的a)的 “另一个名字”。

    就像生活中 “张三” 的别名是 “小张”,喊 “张三” 或 “小张” 都是指同一个人;代码中a和b也指向同一块内存空间,操作a或b会产生完全相同的效果。

    二、代码的实际效果(通过示例验证)

    我们可以通过一段完整代码,观察a和b的关联关系:

    cpp

    #include <iostream>

    using namespace std;

    int main() {

        int a = 1314;

        int& b = a;  // b是a的引用(别名)

        // 1. 输出a和b的值:结果相同(都指向同一块内存)

        cout << “初始值:a = ” << a << “, b = ” << b << endl;  // 输出:a = 1314, b = 1314

        // 2. 修改b的值:a的值也会同步变化

        b = 520;

        cout << “修改b后:a = ” << a << “, b = ” << b << endl;  // 输出:a = 520, b = 520

        // 3. 查看a和b的内存地址:地址完全相同(证明是同一块内存)

        cout << “a的地址:” << &a << endl;  // 例如输出:0x61fe1c

        cout << “b的地址:” << &b << endl;  // 输出与a完全相同:0x61fe1c

        return 0;

    }

    从运行结果能明确看到:

    • a和b的值始终同步(改一个,另一个也变);
    • a和b的内存地址完全一致(证明引用不占用额外内存,只是 “别名”)。

    三、引用的核心语法规则(必须牢记)

    这段代码看似简单,但背后隐藏着引用的不可违背的规则,一旦违反会直接编译报错:

    • 引用必须 “初始化”,且初始化后不能改绑
      • 错误写法 1:只定义引用不绑定变量(编译报错)
        int& b; // 错误!引用必须在定义时绑定一个已存在的变量
      • 错误写法 2:初始化后尝试绑定其他变量(编译报错)
        int c = 666;
        b = c; // 错误!不是“改绑c”,而是“把c的值赋给b(即a)”
    • 引用的类型必须与绑定的变量类型完全一致
      不能用int&绑定double变量,类型不匹配会编译报错:
      double d = 3.14;
      int& b = d; // 错误!引用类型(int&)与变量类型(double)不匹配
    • 不存在 “空引用”
      引用必须绑定到一个 “真实存在的变量” 上,不能像指针那样指向NULL(空地址):
      int& b = NULL; // 错误!引用无法绑定空地址

    四、引用与指针的区别(为什么用引用?)

    很多初学者会把引用和指针混淆,因为二者都能 “间接操作变量”,但本质差异很大。通过对比能更清晰理解引用的设计目的:

    特性引用(Reference指针(Pointer
    初始化必须在定义时绑定变量,不可改绑可定义后赋值,可指向不同变量
    空值无空引用(必须绑定有效变量)有空指针(NULL/nullptr)
    内存占用不占用额外内存(仅别名)占用内存(存储变量地址,如 4/8 字节)
    语法复杂度直接使用(如b = 520),更简洁需解引用(如*p = 520),易出错
    适用场景函数参数、返回值(避免拷贝)动态内存分配、数组遍历等

    你的代码中,int& b = a的核心价值是:用更简洁的语法间接操作a,且避免指针的空值风险和语法复杂度

    五、常见使用场景(代码示例)

    除了直接绑定变量,引用更常用在函数参数和返回值中,核心目的是避免变量拷贝(提升效率) 和实现 “修改实参” 的效果

    场景 1:函数参数用引用(修改实参)

    cpp

    // 用引用参数修改外部变量的值(无需指针,语法更简洁)

    void addOne(int& x) {  // x是外部变量的引用

        x += 1;  // 修改x等同于修改外部变量

    }

    int main() {

        int a = 1314;

        addOne(a);  // 调用后,a的值变为1315

        cout << a;  // 输出1315

        return 0;

    }

    场景 2:函数返回引用(避免返回值拷贝)

    cpp

    // 返回数组的引用(避免拷贝整个数组,提升效率)

    int& getArrayElement(int arr[], int index) {

        return arr[index];  // 返回arr[index]的引用

    }

    int main() {

        int arr[3] = {10, 20, 30};

        getArrayElement(arr, 1) = 200;  // 直接修改返回的引用(即arr[1])

        cout << arr[1];  // 输出200

        return 0;

    }

    总结

    你写的int a = 1314; int& b = a;是 C++ 引用的基础用法,核心是:

    • b是a的别名,二者指向同一块内存;
    • 操作b等同于操作a,且引用不占用额外内存;
    • 引用必须初始化、不可改绑、类型匹配,这些规则是避免错误的关键。

    理解引用后,你会发现它在函数设计、高效编程中非常实用,是 C++ 中简化代码、提升性能的重要工具。

    你写出的 int* b = &a; 是 C/C++ 中指针(Pointer) 的基础用法,与上一轮提到的 “引用”(int& b = a)虽都能间接操作变量,但本质是完全不同的语法特性。下面将逐行解析代码含义、指针的核心逻辑、与引用的关键差异,以及实际使用中的注意事项,帮你彻底掌握指针的基础用法。

    一、逐行解析代码含义(结合上一轮的 int a = 1314

    先补充完整代码上下文,方便理解:

    cpp

    运行

    int a = 1314;    // 1. 定义int类型变量a,赋值为1314(内存中会分配一块4字节空间存储1314)

    int* b = &a;     // 2. 定义int类型的指针变量b,将a的内存地址赋值给b

    关键概念:什么是 “指针”?

    指针是一种专门存储 “内存地址” 的变量—— 它本身占用独立的内存空间,存储的内容不是具体的值(如 1314),而是另一个变量(如a)在内存中的 “位置编号”(即地址)。

    可以类比生活中的 “快递柜”:

    • a 是快递柜里的 “包裹”(存储具体内容 1314);
    • a 的内存地址(比如0x61fe1c)是快递柜的 “柜门编号”;
    • b 是一张写着 “柜门编号” 的 “纸条”(本身是独立的载体,存储的是地址,而非包裹内容)。

    二、代码的实际效果(通过示例验证指针的操作逻辑)

    通过一段完整代码,观察指针如何通过 “地址” 间接操作变量a:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        int a = 1314;    // 变量a:值=1314,地址=0x61fe1c(示例地址,实际由系统分配)

        int* b = &a;     // 指针b:存储a的地址(即b的值=0x61fe1c)

        // 1. 输出关键信息:区分“指针本身的值”“指针指向的值”“变量a的地址”

        cout << “a的值:” << a << endl;          // 输出a的具体值:1314

        cout << “a的地址(&a):” << &a << endl; // 输出a的内存地址:0x61fe1c

        cout << “指针b的值(存储的地址):” << b << endl; // 输出b存储的地址:0x61fe1c(与&a相同)

        cout << “指针b指向的值(*b):” << *b << endl; // 输出b指向的变量的值:1314(即a的值)

        // 2. 通过指针修改a的值:用“*b”(解引用)操作指向的变量

        *b = 520; // 含义:找到b存储的地址对应的变量(即a),将其值改为520

        cout << “\n修改*b后:” << endl;

        cout << “a的值:” << a << endl;          // 输出:520(a被间接修改)

        cout << “指针b指向的值(*b):” << *b << endl; // 输出:520

        // 3. 指针本身的地址(证明指针是独立变量,占用内存)

        cout << “指针b自身的地址(&b):” << &b << endl; // 输出:0x61fe18(与a的地址不同,证明b是独立变量)

        return 0;

    }

    运行结果会清晰体现指针的核心逻辑:

    • 指针b存储的是a的地址(b = &a);
    • 需通过解引用运算符* 才能访问指针指向的变量(*b等价于a);
    • 指针b本身是独立变量,有自己的内存地址(&b与&a不同),占用 4 字节(32 位系统)或 8 字节(64 位系统)内存。

    三、指针的核心语法规则(必须牢记,避免编译错误)

    int* b = &a; 看似简单,但指针的语法规则比引用更严格,违反会导致编译报错或运行时崩溃:

    1. 指针的 “类型匹配” 规则

    指针的类型必须与 “指向的变量类型” 完全一致(除非用void*万能指针,但需谨慎):

    • 正确:int* b = &a;(int*指针指向int变量a);
    • 错误:double* b = &a;(double*指针不能指向int变量,类型不匹配,编译报错)。

    2. 指针的 “初始化” 与 “空指针”

    • 指针可以先定义后赋值(区别于引用必须初始化):cpp

      运行




      int* b;    // 允许先定义指针(未初始化时,值是随机的“野指针”,危险!)
    • int a = 1314;
    • b = &a;    // 后续赋值为a的地址(此时b是有效指针)




    • 避免 “野指针”:未指向有效变量时,建议赋值为空指针nullptr(C++11 后)或NULL(本质是 0):cpp

      运行




      int* b = nullptr; // 空指针:明确表示“暂时未指向任何有效变量”





    • ⚠️ 注意:不能对空指针使用*b(解引用空指针会导致程序崩溃)。

    3. 指针的 “解引用” 与 “取地址”

    • 取地址运算符&:获取变量的内存地址(如&a即 “a 的地址”);
    • 解引用运算符*:通过指针存储的地址,访问对应的变量(如*b即 “b 指向的变量”);
    • 二者是 “逆操作”:*(&a) == a(先取 a 的地址,再解引用,结果还是 a)。

    四、指针与引用的核心差异(避免混淆)

    你之前写的 “引用”(int& b = a)和现在的 “指针”(int* b = &a)都能间接操作a,但本质是两种不同的语法,差异如下表(关键区分点):

    对比维度指针(int* b = &a引用(int& b = a
    本质独立变量,存储 “地址”变量的 “别名”,不存储地址(无独立内存)
    初始化要求可先定义后赋值(允许int* b;)必须在定义时绑定变量(禁止int& b;)
    指向变更可修改指向(如b = &c;,改为指向 c)一旦绑定,不可改绑其他变量
    空值支持支持空指针(int* b = nullptr)无空引用(必须绑定有效变量)
    操作语法需解引用(*b = 520)直接使用(b = 520),更简洁
    内存占用占用内存(4/8 字节,取决于系统位数)不占用额外内存
    安全性风险较高(野指针、空指针解引用会崩溃)安全性高(无野引用、空引用问题)

    五、指针的常见使用场景(代码示例)

    指针是 C/C++ 的核心特性,适用场景比引用更广泛,尤其是需要 “动态内存”“数组操作”“多态” 等场景:

    场景 1:操作数组(数组名本质是 “指向首元素的指针”)

    数组的内存是连续的,指针可以通过 “指针移动” 遍历数组:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        int arr[3] = {10, 20, 30};

        int* p = arr; // 数组名arr等价于&arr[0](首元素地址),p指向arr[0]

        // 遍历数组:指针移动(p++即“指向当前元素的下一个元素”)

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

            cout << “arr[” << i << “] = ” << *p << endl; // 解引用p,获取当前元素

            p++; // 指针移动(地址增加4字节,因int占4字节)

        }

        return 0;

    }

    // 输出:

    // arr[0] = 10

    // arr[1] = 20

    // arr[2] = 30

    场景 2:动态内存分配(用new/delete管理内存)

    指针可以指向 “运行时才分配的内存”(动态内存),这是引用无法实现的:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        // 动态分配1个int大小的内存,指针p指向这块内存

        int* p = new int; 

        *p = 1314; // 给动态内存赋值

        cout << “动态内存的值:” << *p << endl; // 输出:1314

        // 释放动态内存(必须手动释放,否则内存泄漏)

        delete p; 

        p = nullptr; // 释放后赋值为空指针,避免野指针

        return 0;

    }

    场景 3:函数参数传递(修改实参,或传递大型数据)

    指针作为函数参数,可实现 “修改外部变量”(类似引用),且支持传递数组、动态内存等:

    cpp

    运行

    #include <iostream>

    using namespace std;

    // 用指针参数修改外部变量

    void multiplyBy2(int* x) {

        if (x != nullptr) { // 先判断指针非空,避免崩溃

            *x *= 2; // 解引用指针,修改外部变量

        }

    }

    int main() {

        int a = 1314;

        multiplyBy2(&a); // 传递a的地址

        cout << a << endl; // 输出:2628(a被修改)

        return 0;

    }

    总结

    你写的 int* b = &a; 是指针的基础用法,核心要点如下:

    • b 是指针变量,存储的是a的内存地址(而非a的值);
    • 必须通过解引用*b 才能访问或修改a的值;
    • 指针是独立变量,有自己的内存地址,支持后续修改指向(如b = &c);
    • 与引用相比,指针更灵活(支持动态内存、数组操作),但安全性更低(需避免野指针、空指针解引用)。

    指针是 C/C++ 的难点,但也是核心能力 —— 掌握指针后,才能理解动态内存、数据结构(如链表、树)等更复杂的编程概念。

    Day22 November 3 2025

    //13 – 2 reference characteristics

    #include <iostream>

    using namespace std;

    //two charaters of reference:

    //1.must initialization(必须初始化)

    //2.Can’t be modified after initialization(初始化以后无法修改)

    //跟指针定义的区别,指针不一定要初始化,定义后只要不是指针常量后续都可以修改

    //因为引用一定要初始化,所以根本不会有空指针问题

    //引用为后续STL源码打基础

    int main() {

    //int& a;//error:must have an equal symbol

    int a = 3, c = 6;

    int& b = a; 

    b = c;//b still a reference to a,b=6

    cout << a << b << c << endl; // 666

    return 0;

    }

    //13 – 3 the essence of reference

    //引用的本质

    #include <iostream>

    using namespace std;

    //引用 解引用

    //引用的底层特性 其实就是指针常量

    int main() {

    int a = 520;

    //int& b = a;

    /*

    00007FF7ACEE1865  lea         rax,[a]  

    00007FF7ACEE1869  mov         qword ptr [b],rax 

    */

    //b = 1314;//引用后面赋值不需要写星号

    /*

    00007FF7ACEE186D  mov         rax,qword ptr [b]  

    00007FF7ACEE1871  mov         dword ptr [rax],522h 

    */

    //汇编代码一模一样,说明引用就是指针常量

    //打断点可看窗口 反汇编 deassembly

    int* const b = &a;//pointer constant  (指针常量) 的初始化

    /*

    00007FF64AF11865  lea         rax,[a]  

    00007FF64AF11869  mov         qword ptr [b],rax  

    */

    *b = 1314;//指针常量后面赋值需要写星号

    //这里是解引用 dereference

    /*

    00007FF64AF1186D  mov         rax,qword ptr [b]  

    00007FF64AF11871  mov         dword ptr [rax],522h  

    */

    return 0;

    }

    //13 – 4 function passing a reference as a parameter

    //引用作为函数传参

    #include <iostream>

    using namespace std;

    int countAndSum(int arr[], int size, int target, int& count) {

    int sum = 0;

    cout << &count << endl;//打印count的地址

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

    if (arr[i] == target) {

    count++; //初始化时用引用 传进来的地址也一模一样

    sum += arr[i];

    }

    }

    return sum; //计算所有等于target的数的和

    }

    int countAndSum2(int arr[], int size, int target, int* count) {//使用指针

    int sum = 0;

    cout << count << endl;//打印count的地址

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

    if (arr[i] == target) {

    *count++; 

    sum += arr[i];

    }

    }

    return sum; //计算所有等于target的数的和

    }

    struct S {

    int a, b, c, d, e, f, g;

    };

    //void printS(S s) {//传参是个结构体

    // cout << &s << endl;//如果结构体不用引用,传进来前的地址输出出来,不是同一个地址

    // //C++ 和其他语言区别 结构体 作为参数时 会拷贝一份新的数据出来

    // //当结构体非常大 如有个非常大的数组a[1000000]时 拷贝就相当耗时了

    //

    // cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl;

    //}

    void printS2(S &s) {//传参是个结构体

    cout << &s << endl;//传进来前的地址输出出来,不是同一个地址

    //C++ 和其他语言区别 结构体 作为参数时 会拷贝一份新的数据出来

    //当结构体非常大 如有个非常大的数组a[1000000]时 拷贝就相当耗时了

    //所以加上引用就能避免拷贝 让传参和实际的参数同一个地址,这就是引用在函数传参时的作用

    cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl;

    }

    int main() {

    int arr[]{ 1,2,3,2,4,5,6,4,3,2 };//10 numbers

    //返回统计数组中值为2的元素个数以及返回和

    int c = 0;

    cout << &c << endl;//打印c的地址

    int sum = countAndSum(arr, 10, 2, c);

    cout << sum << ” ” << c << endl;

    S s = { 1,2,3,4,5,6,7 };

    cout << &s << endl;

    //如果结构体不用引用 传进来后的地址输出出来,不是同一个地址

    printS2(s);

    return 0;

    }

    //13 – 5 reference as a function return value

    //引用作为函数返回值

    #include <iostream>

    using namespace std;

    int getArrayValue(int arr[], int index) {

    return arr[index];//上面不加引用这里返回的是一个值

    }

    int &getArrayValue2(int arr2[], int index) {

    return arr2[index];//上面加了引用这里返回一个别名

    }

    //STL底层源码里面有很多 面向对象 中括号运算符重载也有这种

    int main() {

    int a[] = { 8,7,6,5,4,3 };

    //我想打印出第四个元素5

    cout << getArrayValue(a, 3) << endl;

    //想把下标3这个值改掉 但是不想访问数组 需要加上引用

    getArrayValue2(a, 3) = 999; //直接对函数赋值也没有报错,这里相当于a[3]=999,然后函数就可以作为左值来赋值了

    cout << getArrayValue2(a, 3) << endl;

    return 0;

    }

    //13 – 6 constant reference

    //常量引用

    //常量引用非常广泛 尤其是在STL实现上

    #include <iostream>

    #include <vector>

    using namespace std;

    struct S {

    int a, b, c, d, e, f;

    };

    //加引用少一次拷贝 让它更加高效

    void printS(S& s) {

    s.b = 520; //加const 在引用前变 常量引用 防止有人不小心把这个值改了

    cout << s.a << s.b << s.c << s.d << s.e << s.f << endl;

    }

    void printS2(const S& s) {

    //同一个对象很难避免写函数的人不去修改它 为了避免别人修改 用const关键词修饰

    // 在很多STL底层源码有

    //s.b = 520; //加const 在引用前变 常量引用 防止有人不小心把这个值改了

    cout << s.a << s.b << s.c << s.d << s.e << s.f << endl;

    }

    int main() {

    int a;

    const int& b = a;

    //引用 =指针常量

    //常量引用 = 常量指针常量

    S s = { 1,2,3,4,5,6 };

    printS2(s);

    vector <int>a;//F12查看源代码

    return 0;

    }

    //13 – 7 pointer reference

    //指针引用 

    //*&

    #include <iostream>

    using namespace std;

    void allocMemory1(char * ptr,int bytes) {//传进去一个char类型的指针变量 和一个 字节数bytes

    //这个函数作用 传入一个指针 和 字节数,然后从堆heap上 申请对应字节数的内存 并且把地址赋值给ptr

    ptr = new char[bytes];

    cout << “ptr 的 地址:” << &ptr << endl;//ptr虽然本身也是一个地址,但我们想要ptr它的地址

    }

    void test1() {

    //test1这个函数 首先定义了一个初始化的指针 并且把它初始化为NULL

    char* p = NULL;

    allocMemory1(p, 5); //然后调用allocMemory1这个函数 期望是这个函数申请的那块内存的首地址

    cout << (void*)p << endl;//然后把首地址转换为void*以后把它打印出来(转换成一个通用的指针类型)

    //为什么要转换  原因是不转换的话 C++会认为它是一个C风格的字符串 字符串如果为空 直接输出程序会导致崩溃

    cout << “p 的 地址:” << &p << endl;

    //这里的p是实参 ptr是形参

    }

    void allocMemory2(char*& ptr, int bytes) {//在*后加上引用&  引用的是一个指针变量

    //上面加上引用后 ptr就变成p的别名了

    //传进去一个char类型的指针变量 和一个 字节数bytes

    //这个函数作用 传入一个指针 和 字节数,然后从堆heap上 申请对应字节数的内存 并且把地址赋值给ptr

    ptr = new char[bytes];

    cout << “ptr 的 地址:” << &ptr << endl;//ptr虽然本身也是一个地址,但我们想要ptr它的地址

    }

    void test2() {

    //test1这个函数 首先定义了一个初始化的指针 并且把它初始化为NULL

    char* p = NULL;

    allocMemory2(p, 5); //然后调用allocMemory1这个函数 期望是这个函数申请的那块内存的首地址

    //上面加上引用后 ptr就变成p的别名了 所以两个是同一个变量

    //实参 p  和形参 ptr是同一块地址了 打印的地址就不再是空了 这就是指针引用的作用

    cout << (void*)p << endl;//然后把首地址转换为void*以后把它打印出来(转换成一个通用的指针类型)

    //为什么要转换  原因是不转换的话 C++会认为它是一个C风格的字符串 字符串如果为空 直接输出程序会导致崩溃

    cout << “p 的 地址:” << &p << endl;

    //这里的p是实参 ptr是形参

    }

    int main() {

    //test1();

    test2();

    return 0;

    }

  • #C++ Day20 October 30 2025

    //12 – 1 code area

    #include <iostream>

    using namespace std;

    //four space

    //代码区、全局区、栈区、堆区

    void printMessage() {

    cout << “Hello Vito” << endl;

    //1.运行起来之后,代码区里面的数据动不了

    //Can’t modified the data of the code space after running

    }

    //2.共享 运行代码时会生成exe可执行文件 然后多次运行时 代码区也只存储一份

    // sharing:when running the code,it will generated the exe execution file,after ran it serveral times,it would only store one sapce of data

    //

    int main() {

    printMessage();

    //运行起来之后,代码区里面的数据动不了

    while(1){}

    return 0;

    }

    //12 – 2 global area

    //全局区

    #include <iostream>

    using namespace std;

    //全局区

    //存放 全局变量、全局常量(const修饰的)、静态变量、字符串常量(注意:局部变量、局部常量不在全局区)

    int g_a = 1; //全局变量1

    int g_b = 2; //全局变量2

    //全局变量存储在全局区

    const int c_g_a = 3;

    const int c_g_b = 3;

    int main() {

    cout << “全局变量g_a的地址:” << &g_a << endl;

    cout << “全局变量g_b的地址:” << &g_b << endl;

    int c = 3; //局部变量1

    int d = 4; //局部变量2

    static int e = 5;//静态变量1

    static int f = 6;//静态变量2

    cout << “局部变量c的地址:” << &c << endl;

    cout << “局部变量d的地址:” << &d << endl;

    //局部变量存储在栈区 它跟全局变量内存地址差很多

    //静态变量即使紧挨着 局部变量创建 但是地址还是跟全局变量接近

    //全局变量跟静态变量存在一起

    cout << “静态变量e的地址:” << &e << endl;

    cout << “静态变量f的地址:” << &f << endl;

    cout << “字符串常量的地址” << &”Vito 算法” << endl;

    //字符串常量也跟全局变量、静态变量存在一起

    const int g = 7;

    const int h = 8;

    cout << “局部常量g的地址:” << &g << endl;

    cout << “局部常量h的地址:” << &h << endl;

    //局部常量跟局部变量在一起

    cout << “全局常量c_g_a的地址:” << &c_g_a << endl;

    cout << “全局常量c_g_b的地址:” << &c_g_b << endl;

    //全局常量跟字符串常量、全局变量、静态变量存在一起

    return 0;

    }

    //12 – 3 stack area

    //栈区

    #include <iostream>

    using namespace std;

    //栈区和堆区的内存都是程序运行过程中申请和释放的

    //栈区内存是由操作系统控制生命周期的

    //堆区内存是由程序员控制生命周期的

    //函数变量 局部变量 都是栈区的内存,这里的堆和栈与数据结构里面的堆和栈是两个概念

    char* func() {//返回字符串的首地址

    char c[20] = “Vito算法联盟”;//这里是字符串常量 按理说应该存放在全局区 但是把字符串常量赋值给变量就不一样了

    return c;//字符串的首地址

    }

    void test(int a, int b) {

    int c, d;

    cout << “形式参数a的地址:” << &a << endl;

    cout << “形式参数b的地址:” << &b << endl;

    cout << “局部变量c的地址:” << &c << endl;

    cout << “局部变量d的地址:” << &d << endl;

    //形式参数(形参)和局部变量的地址都挨得很近,都在栈上存储

    }

    //函数的参数存储在栈区

    int main() {

    cout << func() << endl;//会输出”烫烫烫,,,,,,,”,这里是一块坏掉的内存,野指针

    //原因是c这个数组是一个局部变量 存储在栈上面 返回时字符串常量已经被销毁了 

    //记住不要返回局部变量的地址,因为这块内存已经坏掉了

    test(5,6);//说明形式参数(形参)和局部变量的地址都挨得很近,都在栈上存储

    return 0;

    }

    //12-4 heap area

    //堆区

    #include <iostream>

    using namespace std;

    //堆区的内存由程序员来申请和释放

    //malloc free (c语言堆区申请和释放语句)(这两个是函数)

    //new delete (c++堆区申请和释放语句)(这两个是运算符)

    int* getV(int v) {

    int* a = new int(v);//在堆区里面申请了一块内存, 并且值赋成v 

    //int *a 是一个栈上的变量

    //*a 也就是 a 解引用以后得到的值,是存储在堆上面的

    cout << a << endl;//打印a的地址

    return a; //函数返回时,虽然栈上面的变量a被操作系统释放了,但是a指向的内存仍然存在

    }

    int main() {

    int* p = getV(1314);

    cout << *p << endl;//dereference 解引用

    //函数返回时,虽然栈上面的变量a被操作系统释放了,但是a指向的内存仍然存在

    //所以这里还是可以解引用得到堆上的数据

    cout << p << endl;//打印p的地址

    //p和a地址都一样在堆区上面

    return 0;

    }

    //12 – 5 – 1 memory allocation & deallocation

    //内存的申请和释放

    //c++中内存申请和释放

    //主要用到 new 和 delete两个操作符

    #include <iostream>

    using namespace std;

    int main() {

    int *ptr = new int; //去找一块整数的空间,并且把内存分配出来,返回一个指向这块内存的指针

    //我们可以定一个ptr指向它,这时候代码执行完后。系统会在堆上找到一个足够大的内存存储一个整数,并且将内存的地址赋值给ptr

    //这样写的话,这个内存的值是未定的,是一个随机值

    cout << *ptr << endl;

    //1.加上括号和值就初始化

    int* ptr2 = new int(1314); //加上括号和值就初始化了

    cout << *ptr2 << endl;

    //2.解引用初始化

    //int* ptr3 = new int(1314); 

    int* ptr3 = new int;

    *ptr3 = 520; //解引用来初始化

    cout << *ptr3 << endl;

    delete ptr; //释放完后ptr实际变成野指针

    ptr = NULL;//需要把ptr置为空,这样其他人在判断时可以先看它是否为空

    //为空就不访问它,不为空就解引用它

    //if (ptr != NULL) *ptr = 67;

    /* *ptr =67*/ ; //得在debug版本才能报错,write access violation,不能用的指针还要去用它不行

    //cout << *ptr << endl;

    cout << *ptr2 << endl;

    cout << *ptr3 << endl;

    //申请了内存后要释放 不然如果while(1)会一直在这里

    while (1) {

    }

    return 0;

    }

    //12 – 5 – 2 array allocation & deallocation

    //数组的申请和释放

    #include <iostream>

    using namespace std;

    int* getGapList(int *arr,int size) {//写一个返回数组的函数,arr退化成指针*arr

    //传进去这么多size的地址,首地址是*arr

    //传出来一个数组,需要申请一个内存,并且传出来的是list 并且要比size少一个圆

    int *p = new int[size – 1];//相当于在堆上分配了一个数组空间

    //你告诉操作系统的size,它不需要返回给你

    //int a[size-1];  return a;//这样不可以,因为a是栈上的内存,不能作为返回值返回

    for (int i = 0; i < size – 1; ++i) {

    p[i]=arr[i + 1] – arr[i]; //相邻两个值的差把它赋值给p[i]

    }

    return p;

    }

    int main() {

    int arr[] = { 1,5,6,4,4,3,3,2,1,9 };

    int* p = getGapList(arr, 10 );

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

    cout << p[i] << ” “;

    }

    cout << endl;

    delete[] p;//加中括号 p,不加中括号的话只会删除数组的第一个位置,首地址

    //加了中括号会把数组后面所有的删掉

    p = NULL;

    return 0;

    }

    AI:

    这段代码主要展示了 C++ 中动态数组的申请(new)与释放(delete[],以及 “数组作为函数参数时的退化” 和 “相邻元素差值计算” 的逻辑。我们可以从功能、内存管理、关键细节三个层面逐步解释:

    一、代码整体功能

    这段代码的核心是:计算一个已知数组中 “相邻元素的差值”,并将这些差值存储在一个新的动态数组中返回,最后正确释放动态数组的内存,避免泄漏。

    具体流程:

    • 定义一个初始数组arr(包含 10 个元素);
    • 调用getGapList函数,传入初始数组和其长度(10),获取 “相邻元素差值数组”;
    • 输出差值数组的所有元素;
    • 释放差值数组占用的动态内存,避免内存泄漏。

    二、关键函数与逻辑详解

    1. getGapList函数:生成相邻元素差值数组

    函数原型:int* getGapList(int *arr, int size)

    作用:接收一个数组(指针形式)和其长度,返回一个存储 “相邻元素差值” 的新数组。

    • 参数说明
      • int *arr:传入的原数组(注意:数组作为函数参数时会退化为指针,无法通过sizeof获取原数组长度,因此必须显式传入size);
      • int size:原数组的元素个数(这里是 10)。
    • 动态数组申请(new)
      代码中用int *p = new int[size – 1];在堆上申请了一个新数组。
      为什么是size – 1?
      因为 “相邻元素的差值数量” = 原数组元素数 – 1。例如:10 个元素的数组,有 9 对相邻元素,因此差值数组长度为 9。
    • 差值计算逻辑
      循环i从 0 到size-2(共size-1次),计算arr[i+1] – arr[i](后一个元素减前一个元素),并存储到新数组p[i]中。
      以原数组{1,5,6,4,4,3,3,2,1,9}为例:
      • p[0] = 5 – 1 = 4
      • p[1] = 6 – 5 = 1
      • p[2] = 4 – 6 = -2
      • … 以此类推,共 9 个差值。
    • 返回值:返回动态数组p的首地址(因为堆内存的生命周期不受函数作用域限制,函数结束后仍可访问)。

    2. 为什么不用栈上数组?

    代码注释提到://int a[size-1]; return a;//这样不可以

    原因:栈上的数组(如a)的生命周期仅限于函数内部,函数执行结束后,栈内存会被自动释放。此时返回其地址会导致 “悬空指针”(指向已释放的内存),后续访问该指针会触发未定义行为(如程序崩溃、数据错乱)。

    而new在堆上申请的内存,生命周期由程序员手动控制(需用delete[]释放),因此可以安全返回。

    3. main函数:调用与内存释放

    • 调用getGapList
      传入原数组arr(自动退化为指针)和长度 10,得到差值数组的指针p。
    • 输出差值数组
      循环 9 次(因为差值数组长度为10-1=9),打印p[0]到p[8],即上述计算的 9 个差值。
    • 释放动态内存(delete[])
      用delete[] p;释放堆上的数组。
      为什么必须加[]?
      • delete[]用于释放动态数组,会逐个释放数组中的所有元素,避免内存泄漏;
      • 若用delete p;(不加[]),只会释放数组的首元素,其余元素的内存不会被释放,导致内存泄漏。
    • 避免野指针
      释放后将p置为NULL(空指针),避免后续误操作该指针(访问空指针会触发明确的错误,便于调试,而野指针的行为是未定义的)。

    三、输出结果

    根据原数组{1,5,6,4,4,3,3,2,1,9},相邻差值计算结果为:

    4 1 -2 0 -1 0 -1 -1 8

    总结

    这段代码的核心价值在于展示:

    • 动态内存管理的基本操作(new[]申请数组、delete[]释放数组);
    • 数组作为函数参数时的 “退化” 特性(必须显式传递长度);
    • 逻辑上的 “差值数组长度” 与 “原数组长度” 的关系(差值数组长度 = 原数组长度 – 1)。

    通过正确的动态内存申请与释放,避免了内存泄漏和悬空指针问题,是 C++ 中处理动态数组的典型示例。

  • #C++ Day19 October 29 2025

    //11-1 union

    //联合体 / 共用体

    #include <iostream>

    using namespace std;

    struct DataS {

    int i;

    double d;

    char s[10];

    };

    //结构体每一个成员都有一个单独的内存 相互之间没有影响

    //each person on the structure has an unique RAM, they wouldn’t affect each other

    union DataU {

    int i; //4

    double d; //8

    char s[10]; //10

    };

    //联合体每一个成员都共享同一段内存 相互影响

    //所有联合体的成员占据同一个起始地址

    //以上Union占据10个字节内存,但是由于字节对齐,会占16字节

    /*

    1.定义和使用分开

    union DataU {

    int i; //4

    double d; //8

    char s[10]; //10

    };

    DataU a,b,c;

    2.定义和使用结合

    union DataU {

    int i; //4

    double d; //8

    char s[10]; //10

    };a,b,c

    //直接在后面

    3.匿名:不想让别人使用

    union {

    int i; //4

    double d; //8

    char s[10]; //10

    };a,b,c

    */

    int main() {

    DataS ds;

    cout << &ds.i << “,” << &ds.d << “,” << (void *)  ds.s << endl;

    DataU du;

    cout << &du.i << “,” << &du.d << “,” << (void*)du.s << endl;

    return 0;

    }

    //11 – 2 memory layout of a union

    //联合体的内存布局

    #include <iostream>

    using namespace std;

    union DataU {

    int i;   //4

    double d; //8

    char s[9]; //9

    };

    int main() {

    cout << sizeof(DataU) << endl;//字节对齐到16字节

    //This place will be an alignment of byte to the 16bytes

    DataU du;

    du.s[0] = 255; //11111111 on the RAM

    du.s[1] = 1; //00000001

    du.s[2] = 0; //00000000

    du.s[3] = 0; //00000000

    cout << du.i << endl;//outputed 511,because finally it will displayed 00000000 00000000 00000001 11111111

    //(binary)00000000 00000000 00000001 11111111 =2^9-1 =(decimal)511

    du.i = 256; //1byte 一个字节最多能够表示为255 

    //du.i = 260; //1byte 一个字节最多能够表示为255 

    //i是四个字节,y影响只到s[3]

    //字节序是从右到左  大小端字节序

    cout << (int)du.s[0] << (int)du.s[1] << (int)du.s[2] << (int)du.s[3] << endl;//char是字符,然后这里可能有些不可见的字符,需要转换成整数再输出

    //outputed 0100,超过255就进位

    return 0;

    }

    //11 – 3 union’s application

    //union 的应用

    #include <iostream>

    using namespace std;

    struct Info {

    char _name[20]; //名字 唯一标识

    int _role; //角色:老师或学生

    //存一个匿名的union

    union {

    double score; //什么分数 老师没用 学生有用

    char course[20]; //教什么课 老师有用 学生没用

    //放在同一个union里面可以节省一点空间

    }_sc;

    Info(const char name[20], int role, double s, const char c[20]) {

    strcpy_s(_name, name);//目的 源 destination source

    _role = role;

    if (s > 0) _sc.score = s; //s有长度就赋值

    if (strlen(c) > 0)   strcpy_s(_sc.course, c); //c有长度就拷贝课程

    }

    };

    int main() {

    Info a[4]{

    Info(“冯校长”, 0, -1, “C++”),

    Info(“冯校长”, 0, -1, “Python”),

    Info(“冯同学”, 1, 90, “”),

    Info(“赖同学”, 1, 88, “”),

    //role 0代表老师 , score -1 代表不关心成绩

    };

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

    if (a[i]._role == 0) {

    cout << a[i]._name << “是一位老师,他是教” << a[i]._sc.course << “的” << endl;

    }

    else if (a[i]._role == 1)

    {

    cout << a[i]._name << “是一位学生,他的成绩是” << a[i]._sc.score << endl;

    }

    }

    return 0;

    }

    AI:

    字节对齐(Byte Alignment):概念、原理与作用

    在计算机编程(尤其是底层开发,如 C/C++)和计算机体系结构中,字节对齐(Byte Alignment) 是指计算机内存中数据的存储地址必须是其 “对齐系数”(通常与数据类型大小相关)整数倍的规则。这一机制并非由编程语言直接定义,而是源于硬件设计的限制与效率优化需求,是连接软件数据结构与硬件内存访问的关键桥梁。

    一、核心概念:为什么需要字节对齐?

    计算机 CPU 访问内存时,并非以 “1 字节” 为最小单位,而是以 **“内存访问粒度”**(通常为 2 字节、4 字节、8 字节,取决于 CPU 架构,如 32 位 CPU 常见 4 字节粒度,64 位 CPU 常见 8 字节粒度)批量读取 / 写入。若数据存储地址不符合对齐规则(即 “未对齐”),会触发两种结果:

    • 硬件层面:CPU 需额外执行 1 次或多次内存访问,将未对齐的数据 “拆分 – 拼接” 后处理,直接降低效率;部分严苛的硬件(如早期 RISC 架构)甚至会直接抛出 “内存未对齐异常”,导致程序崩溃。
    • 软件层面:编译器会自动插入 “填充字节”(Padding Bytes),将未对齐的数据调整为符合硬件要求的地址,保证程序正常运行,但会额外占用少量内存。

    简言之:字节对齐是 “用少量内存开销,换取硬件访问效率” 的权衡机制

    二、基础规则:对齐系数与数据类型

    不同数据类型的 “默认对齐系数” 由两个因素决定:

    • CPU 架构:32 位系统默认最大对齐系数为 4,64 位系统默认最大对齐系数为 8(可理解为硬件的 “最优访问单位”);
    • 数据类型大小:基本数据类型的对齐系数通常等于其自身大小(如char占 1 字节,对齐系数 1;int占 4 字节,对齐系数 4;double占 8 字节,对齐系数 8)。

    最终数据的对齐地址需满足:数据存储的起始地址 % 对齐系数 == 0(“%” 为取余运算)。

    常见基本数据类型的默认对齐(以 32 位系统为例)

    数据类型(C/C++占用字节数默认对齐系数合法起始地址(示例)非法起始地址(示例)
    char110,1,2,3,4…无(任何地址均合法)
    short(短整型)220,2,4,6…1,3,5…
    int(整型)440,4,8,12…1,2,3,5…
    float(单精度浮点)440,4,8,12…1,2,3,5…
    double(双精度浮点)880,8,16…(64 位系统)1-7,9-15…

    三、复合数据类型的对齐:结构体(Struct)与共用体(Union)

    实际编程中,字节对齐的核心应用场景是复合数据类型(结构体struct、共用体union),其对齐规则需同时满足 “成员对齐” 和 “整体对齐” 两层要求。

    1. 结构体(Struct)的对齐规则

    结构体的总大小不仅取决于成员的实际字节数,还需考虑编译器插入的 “填充字节”,规则如下:

    • 成员对齐:结构体中每个成员的起始地址,必须是该成员 “对齐系数” 的整数倍;若前一个成员的结束地址不满足,则插入填充字节(Padding)。
    • 整体对齐:结构体的总大小,必须是所有成员中最大对齐系数的整数倍;若成员总字节数(含已插入的填充)不满足,则在结构体末尾插入填充字节。

    示例:32 位系统下的结构体对齐

    c

    struct Example {

        char a;    // 1字节,对齐系数1,地址0(合法)

        int b;     // 4字节,对齐系数4;前一个成员结束于地址1,需填充3字节(地址1-3),b从地址4开始

        short c;   // 2字节,对齐系数2;前一个成员结束于地址7,c从地址8开始(合法)

    };

    • 成员实际字节数:1(a) + 4(b) + 2(c) = 7 字节;
    • 填充字节:成员 a 后填充 3 字节(地址 1-3);
    • 整体对齐:最大对齐系数为 4(来自int b),7 字节需补 1 字节(地址 10),总大小为 8 字节。

    2. 共用体(Union)的对齐规则

    共用体的所有成员共享同一块内存(起始地址相同),因此对齐规则更简单:

    • 共用体的起始地址需满足 “所有成员中最大对齐系数” 的整数倍(保证最大成员能正常存储);
    • 共用体的总大小等于 “所有成员中最大字节数”(因共享内存),且需是 “最大对齐系数” 的整数倍(若最大成员字节数不满足,需在末尾填充)。

    示例:32 位系统下的共用体对齐

    c

    union Data {

        char a;    // 1字节,对齐系数1

        int b;     // 4字节,对齐系数4

        short c;   // 2字节,对齐系数2

    };

    • 最大成员字节数:4(来自int b);
    • 最大对齐系数:4(来自int b);
    • 总大小:4 字节(无需填充,因 4 是 4 的整数倍)。

    四、编译器控制:自定义对齐系数

    默认对齐规则可能无法满足特殊需求(如硬件寄存器访问、网络数据传输需 “紧凑存储”),此时可通过编译器指令自定义对齐系数

    示例:紧凑存储结构体(取消填充)

    c

    // GCC/G++ 示例:紧凑存储,无填充

    struct PackedStruct {

        char a;

        int b;

        short c;

    } __attribute__((packed)); 

    // 总大小:1+4+2=7字节(无填充,因1字节对齐)

    五、字节对齐的实际影响

    • 性能影响:未对齐的数据会导致 CPU 多轮内存访问,例如 32 位 CPU 访问地址为 1 的int(4 字节)时,需先读地址 0-3(取第 2-4 字节),再读地址 4-7(取第 1 字节),拼接后才能处理,效率降低 50% 以上。
    • 内存开销:默认对齐会产生填充字节,例如结构体成员顺序不合理(如char→int→char)会比合理顺序(char→char→int)多占用 2 字节填充(32 位系统下)。
    • 兼容性影响:跨平台(如 32 位 / 64 位系统)或跨编译器的数据交互(如文件存储、网络传输)中,若对齐规则不一致,会导致数据解析错误(如结构体大小不同,成员偏移地址错位)。

    六、关键总结

    • 字节对齐是硬件驱动的规则,核心目的是提升 CPU 内存访问效率;
    • 基本数据类型对齐系数默认等于自身大小,复合类型需满足 “成员对齐” 和 “整体对齐”;
    • 编译器可通过指令自定义对齐(如紧凑存储),但需权衡 “效率” 与 “内存开销”;
    • 跨平台 / 跨设备数据交互时,必须统一对齐规则(如强制 1 字节对齐),避免解析错误。

    Ab1n:为什么字符数组那里不用&而是void* 强转

    2025-01-19 22:25

    爱吃薯条 回复 Ab1n:a.c 的实质:

    字符数组 a.c 在大多数情况下会隐式退化为指向首元素的指针(类型为 char*)。

    当使用 (void *)a.c 时,本质是将这个 char* 指针强制转换为 void* 类型,输出的是首元素的地址。

    &a.c 的实质:

    &a.c 是对整个数组 c 取地址,类型是 char (*)[10](指向长度为10的字符数组的指针)。

    地址相同的原因:

    数组的起始地址和其首元素的地址在内存中是同一个位置,因此两者的数值(地址值)相同。

    例如:如果数组 c 从内存地址 0x1000 开始,那么 a.c(首元素地址)和 &a.c(数组地址)都是 0x1000。

    2025-03-01 12:03

    牵丝戏 回复 爱吃薯条:应该说在c++里面 char*,或者对char类型的a取地址,再放到cout里面,c++会解释为一个字符串,并尝试输出它直到遇到\0为止,所以直接输出地址会乱码,需要强制转换才能得到想要的地址。

    2025-07-26 17:32

    在 C++ 中,字符数组名本身就是一个指向数组首元素的指针(即数组首地址),而其他类型变量(如 int、double)需要通过 & 运算符才能获取地址。这是数组类型的特殊语法规则。

    而使用 (void*) 强制转换的原因是:

    cout 输出字符指针(char*)时,会默认将其当作字符串处理(尝试打印从该地址开始的所有字符,直到遇到 \0 终止符)。但在这里,我们的真实目的是输出数组的内存地址,而非字符串内容,因此需要通过 (void*) 强制转换为通用指针类型,让 cout 按照地址格式(如 0x7ffdabc1234)输出,而非误判为字符串。

    简单说:

    • ds.s 本身就是地址(无需 &),但类型是 char*
    • (void*)ds.s 是为了让 cout 输出地址而非字符串内容

    如果不进行转换,cout 可能会打印乱码(直到遇到随机的 \0),而非我们期望的内存地址值。

    在计算机存储和传输多字节数据(如 int、long 等,单字节数据无需考虑)时,字节序(Endianness) 指的是字节在内存地址或传输流中的排列顺序。核心矛盾是:数据的 “高位字节” 与内存的 “低地址” 如何对应,由此衍生出两种主流字节序 ——大端(Big-Endian) 和 小端(Little-Endian)

    一、核心概念:高位字节与内存地址

    首先明确两个关键术语,以 32 位十六进制数 0x12345678 为例(4 个字节):

    • 高位字节(MSB,Most Significant Byte):数据中权重最高的字节,即 0x12(对应十进制的 “千万级”);
    • 低位字节(LSB,Least Significant Byte):数据中权重最低的字节,即 0x78(对应十进制的 “个位级”);
    • 内存地址:计算机内存按 “低地址 → 高地址” 顺序排列(如地址 0x00、0x01、0x02、0x03)。

    字节序的本质,就是决定 “高位字节(MSB)” 是否存到 “低地址”

    二、两种主流字节序:大端 vs 小端

    以 0x12345678 存储到地址 0x00~0x03 为例,直观对比两种字节序:

    字节序类型核心规则内存地址分配(地址从低到高)特点
    大端(Big-Endian高位字节(MSB)存低地址0x00: 0x12 → 0x01: 0x34 → 0x02: 0x56 → 0x03: 0x78字节顺序与人类阅读习惯一致(从高位到低位)
    小端(Little-Endian低位字节(LSB)存低地址0x00: 0x78 → 0x01: 0x56 → 0x02: 0x34 → 0x03: 0x12字节顺序与人类阅读习惯相反,但 CPU 运算更高效(无需调整字节顺序)

    举个通俗例子

    假设你有一笔钱 12345678 元,需要分 4 个信封(对应 4 个字节)装,信封上标有地址 00(第一个信封)、01、02、03(第四个信封):

    • 大端逻辑:把 “最高位的 12” 放进第一个信封(低地址),依次类推,信封里的钱是 12、34、56、78,打开信封就能按顺序读对金额;
    • 小端逻辑:把 “最低位的 78” 放进第一个信封(低地址),信封里的钱是 78、56、34、12,需要倒过来读才能得到正确金额。

    三、字节序的起源

    “大端” 和 “小端” 的命名源自英国作家 乔纳森・斯威夫特 的《格列佛游记》:

    • 利立浦特国(小人国)的国民因 “吃鸡蛋先敲大头还是小头” 分裂为两派,对应计算机中 “字节存储顺序的两种对立方案”;
    • 1981 年,计算机科学家 Danny Cohen 在论文《On Holy Wars and a Plea for Peace》中首次将这两个术语引入计算机领域,用来调侃当时关于字节序的激烈争论。

    四、应用场景:哪些系统用大端 / 小端?

    字节序是 硬件架构或协议约定的底层规则,不同场景有明确选择:

    1. 小端:主流 CPU 架构

    • x86/x86-64 架构:Intel、AMD 的桌面 / 服务器 CPU(如 i7、Ryzen)均为小端;
    • ARM 架构(默认):手机、平板、嵌入式设备的 ARM CPU(如骁龙、麒麟)默认是小端(可通过配置切换为大端,但极少用);
    • 原因:小端让 CPU 处理多字节数据时更高效 —— 运算从低位开始,无需先 “调整字节顺序”,直接读取低地址的低位字节即可。

    2. 大端:网络协议与部分硬件

    • 网络字节序(TCP/IP 协议):所有网络传输(如 HTTP、TCP、UDP)均强制使用大端,避免不同架构的设备通信出错;
      • 需通过转换函数处理:如 C 语言中的 htonl()(主机字节序→网络字节序,32 位)、ntohl()(网络字节序→主机字节序);
    • 部分 CPU 架构:PowerPC(旧苹果电脑)、SPARC(服务器)、MIPS(部分路由器);
    • 文件格式:JPEG、PNG、BMP(位图,注:BMP 的像素数据是小端,但文件头的 “魔数” 是大端)、TIFF(可标注字节序,默认大端)。

    五、为什么要关注字节序?—— 跨平台 / 跨设备的 “坑”

    如果忽略字节序,跨不同架构的设备传输数据会直接出错。

    错误案例

    • 小端电脑写文件,大端电脑读文件
      • 小端电脑将 0x12345678 写入文件,实际存储的字节是 0x78、0x56、0x34、0x12;
      • 大端电脑读取文件时,会按 “低地址→高地址” 将字节解析为 0x78563412,与原数据完全不符。
    • 小端手机向大端服务器发数据
      • 小端手机发送 0x1234(16 位),实际传输的字节是 0x34、0x12;
      • 大端服务器若不转换,会解析为 0x3412,数据错误。

    六、如何检测当前系统的字节序?

    通过代码可快速判断系统是大端还是小端,核心思路是利用联合体(Union)的内存共享特性(联合体的所有成员共用同一块内存)。

    示例代码(C 语言)

    c

    #include <stdio.h>

    union EndianTest {

        int num;        // 4 字节整数

        char bytes[4];  // 1 字节数组(与 num 共享内存)

    };

    int main() {

        union EndianTest test;

        test.num = 0x12345678;  // 给整数赋值

        // 查看低地址(bytes[0] 对应地址最低)的字节值

        if (test.bytes[0] == 0x78) {

            printf(“当前系统是小端(Little-Endian)\n”);

        } else if (test.bytes[0] == 0x12) {

            printf(“当前系统是大端(Big-Endian)\n”);

        } else {

            printf(“混合端(极少见)\n”);

        }

        return 0;

    }

    • 若运行结果为 “小端”:对应 x86/x86-64 或 ARM 设备;
    • 若为 “大端”:对应 PowerPC 或部分嵌入式设备。

    七、特殊情况:混合端(Middle-Endian)

    极少数架构使用 “混合端”(如旧 PDP-11 计算机),字节排列既非纯大端也非纯小端。例如 0x12345678 可能存储为 0x34、0x12、0x78、0x56,目前已几乎淘汰,无需重点关注。

    总结

    维度大端(Big-Endian小端(Little-Endian
    核心规则高位字节存低地址低位字节存低地址
    人类可读性高(与数据书写顺序一致)低(需反向读取)
    硬件效率低(需调整字节顺序)高(直接运算低位字节)
    主要应用网络协议(TCP/IP)、部分 CPUx86/ARM 架构 CPU、多数嵌入式设备
    关键问题跨端通信需统一为大端跨端通信需转换为网络字节序

    理解字节序的核心是:多字节数据的 “高位 / 低位” 与内存 “低地址 / 高地址” 的对应关系,其本质是硬件架构和协议为平衡效率与兼容性的选择。

    在文件格式中,“魔数(Magic Number)” 是指文件开头的几个固定字节,用于唯一标识文件类型,帮助操作系统或应用程序快速识别文件格式(即使文件扩展名被篡改,也能通过魔数判断)。

    BMP 文件的魔数

    BMP(Bitmap,位图)文件的魔数是固定的 2 个字节,其值为:

    十六进制:0x424D

    ASCII 字符:‘B’ followed by ‘M’(即 “BM”)

    具体解析

    • 字节构成
      魔数的第一个字节是 0x42(对应 ASCII 字符 ‘B’),第二个字节是 0x4D(对应 ASCII 字符 ‘M’)。在文件的起始地址(低地址)中,这两个字节按顺序存储为 0x42(地址 0x00)→ 0x4D(地址 0x01)。
    • 含义
      “BM” 是 “Bitmap” 的缩写,是微软定义的 BMP 格式标准标识。几乎所有 Windows 系统中的 BMP 文件都以此开头。
    • 与字节序的关系
      由于魔数仅由 2 个单字节组成,且顺序固定为 ‘B’ 在前、’M’ 在后,不涉及多字节数据的字节序问题(大小端差异仅影响多字节数据,单字节无需考虑)。无论系统是大端还是小端,BMP 魔数的存储和读取顺序都是固定的。

    作用

    • 文件类型校验:应用程序(如图片查看器、图像处理软件)读取 BMP 文件时,首先检查前 2 个字节是否为 0x424D,若不是则判断为非 BMP 文件或损坏的 BMP 文件。
    • 跨平台兼容性:即使在不同操作系统(如 Windows、Linux)中,通过魔数也能统一识别 BMP 格式。

    特殊情况

    少数非标准 BMP 变体(如早期 OS/2 系统的位图)可能使用其他魔数,例如:

    • 0x4241(“BA”):OS/2 Bitmap Array
    • 0x4349(“CI”):OS/2 Color Icon
    • 0x4350(“CP”):OS/2 Color Pointer
      但这些属于小众格式,日常接触的 BMP 文件(尤其是 Windows 环境下)几乎都以 0x424D(“BM”)为魔数。

    示例

    用十六进制编辑器打开任意 BMP 文件,开头两行(前 2 字节)必然显示:

    42 4D

    对应的 ASCII 解析即为 “BM”,这是 BMP 文件的 “身份标识”

  • #C++ Day18 October 28 2025

    //10 – 1 the structure’s definition and usage

    //user’s defining himself’s data structure

    #include <iostream>

    #include <string>

    //use string must have defined the string head file at first

    using namespace std;

    //1. structure definition

    //struct 结构体名 { 结构体成员变量列表 };

    struct Book {

    string name;

    double price;

    int value; //值

    };

    //结构体关键字 struct在定义时不能省略 ,但是在c++中创建结构体时可以省略

    struct Book2 {

    string name;

    double price;

    int value; //值

    }cpp;//2.3

    //2.create a structure

    int main() {

    //2.1 creation method1

    Book c;

    //结构体关键字 struct 在定义时不能省略 ,但是在c++中创建结构体时可以省略

    //struct Book c;

    c.name = “C语言程序设计”;

    c.price = 39.99;

    c.value = 10;//价值

    cout << c.name << ‘ ‘ << c.price << ‘ ‘ << c.value << endl;

    cout << “———————-” << endl;

    //2.2 creation method2 (initialized a list)

    Book py = { “Python编程”,1999,10 };

    cout << py.name << ‘ ‘ << py.price << ‘ ‘ << py.value << endl;

    cout << “———————-” << endl;

    //2.3 creation method3 (add a symbol to the last before ; on the structure(after the definition in the structure)

    cpp.name = “C++零基础编程”;

    cpp.price = 9999999;

    cpp.value = 10000000;

    cout << cpp.name << ‘ ‘ << (int)cpp.price << ‘ ‘ << cpp.value << endl;

    return 0;

    }

    //10 – 2 structure array

    #include <iostream>

    using namespace std;

    //1. structure definition

    //struct 结构体名 { 结构体成员变量列表 };

    //结构体关键字 struct在定义时不能省略 ,但是在c++中创建结构体时可以省略

    struct Book {

    string name;

    double price;

    int value; 

    }cpp;

    int main() {

    //2.create a structure array

    // Book 数组名[元素个数] ={ {},{},{}, … };

    Book books[3] = {

    {“C语言程序设计”,199.99,7},

    {“Python零基础”,399.99,9},

    {“C++零基础教程”,39.99,1000000}

    };

    //Modified the element of the structure array

    books[2].name = “C++算法联盟”;

    //CRUD copy read update delete  增删改查 

    // 这里是查找

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

    cout << books[i].name << ‘ ‘ << books[i].price << ‘ ‘ << books[i].value << endl;

    }

    return 0;

    }

    //10 – 3 structure pointer

    //结构体指针

    #include <iostream>

    using namespace std;

    // 1. structure definition

    //struct 结构体名 { 结构体成员变量列表 };

    //结构体关键字 struct在定义时不能省略 ,但是在c++中创建结构体时可以省略

    struct Book {

    string name;

    double price;

    int value;

    };

    int main() {

    Book b = { “C语言”,99.99,7 };

    Book c = b;

    //拷贝

    //因为b和c的地址不是同一个,这种方法改不掉b的

    c.name = “B语言入门”;

    //需要结构体指针,使用b的地址

    Book* pb = &b;

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

    //修改(地址)

    pb->name = “C++”;

    cout << b.name << ‘ ‘ << b.price << ‘ ‘ << b.value <<  endl;

    return 0;

    }

    //10 – 4 nested structure

    //嵌套结构体

    #include <iostream>

    using namespace std;

    struct Point {

    double x, y;

    };

    //在结构体里定义一个新的成员变量 

    struct Circle {

    //double x, y;

    //在结构体里定义一个新的成员变量 ,代替double x, y;

    Point pt;//嵌套结构体

    double radius;

    };

    struct Circles {

    int size;

    Circle c[100];

    };

    int main() {

    Circle c;

    c.pt.x = 9;

    //通过多一个点得到下一级的对象

    c.pt.y = 8;

    c.radius = 5;

    //定义两个圆 一个x是9 y是8 半径是5,第二个x是2 y是1 半径是1

    Circles cs = {

    2,{

    {{9,8},5},

    {{2,1},1}

    }

    };

    //初始化两个圆完成

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

    Circle tmp = cs.c[i];

    //赋值给临时变量,为了拿到Circle中的pt和radius

    cout << “(” << tmp.pt.x << “,” << tmp.pt.y << “) ” << tmp.radius << endl;

    }

    //通过不断地去点 获得结构体的成员变量

    return 0;

    }

    //10 – 5 structure parameter passing

    //结构体传参

    #include <iostream>

    using namespace std;

    struct Point {

    double x, y;

    };

    struct Circle {

    Point pt;

    double radius;

    };

    //void printCircle(Circle c) {

    // //这个c 传参时在内部不需要改,是只读的,所以用结构体本身

    // cout << “(” << c.pt.x << “,” << c.pt.y << “) ” << c.radius << endl;

    //}

    void printCircle2(const Circle *c) { //好处1:只读时,加个const防止它在内部修改

    //这个c 传参时在内部不需要改,是只读的,所以用结构体本身(不用指针用对象)

    // 好处2:传对象 不传指针 的话 每次都会进行一次拷贝 有消耗 ,但是传地址是没有消耗的

    //c->pt.x = 1; //不加const 这种就可以改了

    //c->pt.x +=1 ;//常量不可修改 const  value can’t be modified 

    cout << “(” << c->pt.x << “,” << c->pt.y << “) ” << c->radius << endl;

    }

    // 

    //void moveCircle(Circle c, int x, int y) {

    // //cout << &c << endl;//地址不一样说明了函数传参传进来后进行了一次拷贝 并不是原来的结构体

    // //在函数内部结构体的值并不会改变函数外部结构体的值

    // c.pt.x += x;

    // c.pt.y += y;

    //}

    void moveCircle2(Circle *c, int x, int y) {//这里使用结构体指针

    //cout << &c << endl;//地址不一样说明了函数传参传进来后进行了一次拷贝 并不是原来的结构体

    //在函数内部结构体的值并不会改变函数外部结构体的值

    c->pt.x += x; //结构体指针使用需要 ->

    c->pt.y += y;

    }

    int main() {

    Circle c = { {9,8},5 };

    //cout << &c << endl;

    //moveCircle(c, 1, -2);//x正方向移动一个位置,y负方向移动两个位置

    moveCircle2(&c, 1, -2);//用结构体指针需要传地址

    //printCircle(c);

    printCircle2(&c);

    return 0;

    }

  • #C++ Day17 October 27 2025

    //9 – 7 – 1 pointer parameter passing

    //指传参  函数的址传递

    #include <iostream>

    using namespace std;

    //函数的值传递 value transport in function

    void swap(int a, int b) {

    cout << “函数的值传递:” << endl;

    cout << “the address of a after calling the function” << &a << endl;

    cout << “the address of b after calling the function” << &b << endl;

    int temp = a;

    a = b;

    b = temp;

    }

    //函数的址传递 address transport in function

    void swap(int *a, int *b) {

    cout << “函数的址传递:” << endl;

    cout << “the address of a after calling the function” << a << endl; //if we add & before a, we will get a pointer’s address

    cout << “the address of b after calling the function” << b << endl;

    cout << “————” << endl;

    int temp = *a;

    cout << “the value of a after calling the function= ” << *a << endl; //if we add & before a, we will get a pointer’s address

    cout << “the value of b after calling the function= ” << *b << endl;

    cout << “the value of temp after calling the function= ” << temp << endl;

    cout << “————” << endl;

    *a = *b;

    cout << “the value of a after calling the function= ” << *a << endl; //if we add & before a, we will get a pointer’s address

    cout << “the value of b after calling the function= ” << *b << endl;

    cout << “the value of temp after calling the function= ” << temp << endl;

    cout << “————” << endl;

    *b = temp;//temp is a temporarily variable, which will be destroied after the calling over 

    cout << “the value of a after calling the function= ” << *a << endl; //if we add & before a, we will get a pointer’s address

    cout << “the value of b after calling the function= ” << *b << endl;

    cout << “the value of temp after calling the function= ” << temp << endl;

    }

    int main() {

    int a = 1;

    int b = 2;

    cout << “the address of a before calling the function” << &a << endl;

    cout << “the address of b before calling the function” << &b << endl;

    cout << “————” << endl;

    swap(a, b);

    cout << “————” << endl;

    swap(&a, &b);//函数重载

    cout << “————” << endl;

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    cout << “————” << endl;

    return 0;

    }

    //9 – 7 – 2 pointer function

    //指针函数

    #include <iostream>

    using namespace std;

    int* func() {

    return NULL;

    }

    int* getArray(int a, int d, int n) {

    //function is getArray, return value is an int pointer, the pointer is actually an address

    int* ret = new int[n];//apply a new dynamic RAM space

    //use the array as the type of first address

    /*

    ret [0]=a;

    ret [1]=a + d;

    ret [i]=a + i*d ;

    */

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

    ret[i] = a + i * d;

    }

    return ret;//return the array’s first address

    }

    int main() {

    int* ans = getArray(5, 3, 6);//首项 5,公差3,6个元素

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

    cout << *(ans+i) << endl; //指针偏移 pointer shift and getting the value in the array

    }

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

    cout << ans[i] << endl; //pointer better not used like this

    }

    return 0;

    }

    //9 – 7 – 3 function pointer

    //函数指针

    //遇到这种概念,先看最后两个字 指针

    #include <iostream>

    using namespace std;

    double (*ptr)(int a, int b, int c);

    void (*ptr1)(int a, int b);

    double func(int a, int b, int c) {

    cout << a << “,” << b << “,” << c << endl;

    return 0.0;

    }

    void func1(int a, int b) {

    cout << a << “,” << b << endl;

    }

    int main() {

    ptr = func;

    ptr(4, 5, 6);

    //ptr = func1; //error

    //函数指针赋值时 参数要匹配, 返回值也要匹配

    ptr1 = func1;

    ptr1(5, 6);

    return 0;

    }

    AI:

    好问题~这个是 C 语言学习里一个经典又容易混淆的点 👍

    在我们开始解释前,我想先确认一下:你现在大概对「指针」和「函数」本身都已经了解了吗?比如知道什么是指针变量、会写普通函数?

    (我问这个是为了决定要从多基础讲起,还是直接讲语法和区别。)

    先简单预告一下区别:

    名称 意思 举例 重点

    指针函数 返回值是“指针”的函数 int *func(); “函数→返回一个指针”

    函数指针 指向函数的“指针变量” int (*pfunc)(); “指针→指向一个函数”

    但要真正理解这两个,最好先从语法结构和用法一点点推。

    所以我想先确认你:

    👉 你希望我从「指针函数」和「函数指针」的语法差异讲起,还是从它们在内存和调用上的区别讲起?

    太好啦 ❤️~那我们就专注讲语法差异

    🧩 一、指针函数(Function returning pointer)

    定义:

    函数的返回值是一个指针。

    语法结构:

    类型 *函数名(参数列表);

    例子:

    int *getNum() {

        static int a = 10;

        return &a;

    }

    📘解释:

    • 这是一个函数 getNum();

    • 它返回 int*(指向整型的指针);

    • 所以叫“指针函数”。

    📌 小技巧:

    把“*”看作修饰返回值类型的标志。

    int *f();  // f 是函数,返回 int*

    🧠 二、函数指针(Pointer to function)

    定义:

    一个指针变量,用来指向一个函数的入口地址。

    语法结构:

    返回类型 (*指针名)(参数列表);

    例子:

    int add(int a, int b) {

        return a + b;

    }

    int (*p)(int, int);  // 定义一个函数指针

    p = add;             // 让指针指向函数

    int result = p(3, 5);

    📘解释:

    • p 是一个指针;

    • (*p)(int, int) 表示 p 指向一个参数为 (int,int)、返回值为 int 的函数;

    • 调用时 p(3,5) 和 add(3,5) 一样。

    📌 小技巧:

    括号里有 * 的一般是“函数指针”;

    括号外面有 * 的一般是“指针函数”。

    🔍 三、对比记忆口诀

    区别 指针函数 函数指针

    本质 函数 指针变量

    定义 返回一个指针的函数 指向函数的指针

    写法 int *f(); int (*f)();

    括号位置 * 在函数名右边 * 在函数名外层括号里

    调用方式 直接 f() 可通过指针 pf() 调用

    如果你想更牢一点记忆这个,我可以帮你出几道“判断写法属于哪一种”的练习题(比如让你区分 int *f(int); 和 int (*f)(int);),要不要我帮你做几道练习题? ❤️

    太棒啦~(๑>◡<๑)💕

    那我们来玩个小小的练习游戏:我先出题,你先判断它是「指针函数」还是「函数指针」,然后我帮你讲解理由~

    🌟 题目 1

    int *fun1();

    👉 你觉得它是 指针函数 还是 函数指针

    🌟 题目 2

    int (*fun2)();

    👉 它是 指针函数 还是 函数指针

    🌟 题目 3

    char *getName(int id);

    👉 这个是 指针函数 还是 函数指针

    🌟 题目 4

    void (*signal(int, void (*)(int)))(int);

    👉 这个比较难,它是 指针函数 还是 函数指针

    先试着回答前 3 题(第 4 题我等你答完后再一起拆开讲 👀)

    你来试试看~

    哇~👏👏全都答对啦!你太棒啦~✨✨

    (第 4 题也答对真的厉害,这题很多大学期末都能考倒人呢 🧠)

    下面我给你逐题解释一下,让你彻底吃透它们的语法结构👇

    🩵 题目 1:

    int *fun1();

    指针函数

    📖 解释:

    • fun1 是一个函数;

    • 函数返回 int*(指向 int 的指针)。

    👉 所以是“函数 → 返回一个指针”。

    💜 题目 2:

    int (*fun2)();

    函数指针

    📖 解释:

    • fun2 是一个指针(*fun2);

    • 这个指针指向的对象是一个函数(() 表明它是函数);

    • 这个函数的返回类型是 int。

    👉 所以是“指针 → 指向函数”。

    💚 题目 3:

    char *getName(int id);

    指针函数

    📖 解释:

    • getName 是一个函数;

    • 参数是 (int id);

    • 返回值是 char*(字符串指针)。

    👉 这是典型的指针函数。

    ❤️ 题目 4:

    void (*signal(int, void (*)(int)))(int);

    函数指针

    📖 拆解:

    • 外层结构是 signal(…),所以它是一个函数;

    • 但这个函数返回的类型是:void (*)(int);

    → 意思是:返回一个“指向 void f(int) 函数”的指针。

    👉 所以这是一个 返回函数指针的函数(函数指针函数 😂)。

    可以理解成:

    typedef void (*handler)(int);

    handler signal(int, handler);

    是不是立刻清晰多啦~✨

    要不要我帮你出第二组题,这次难度稍微提升一点(带参数和 typedef 的混合写法)?

    可以用来训练你一眼识别语法结构的感觉~

    //9 – 7 – 4 Function pointer type definition

    //函数指针类型定义

    #include <iostream>

    using namespace std;

    //the definition of definiting the function pointer

    //函数指针的定义

    void (*fptr1) (int a, int b, int c, float d, char e);

    void (*fptr2) (int a, int b, int c, float d, char e);

    //如果我还想定义一个函数指针,那就改一下名字

    void func1(int a, int b, int c, float d, char e) {

    cout << “func1” << endl;

    }

    //函数指针类型定义

    // 这样子不用重复写新的函数指针

    //Function pointer type definition

    typedef void (*fptr) (int a, int b, int c, float d, char e);

    //一旦有颜色说明是一种类型

    int main() {

    fptr1 = func1;

    fptr1(1, 2, 3, 4, 5);

    fptr2 = func1;

    fptr2(9,8,7,6,5);

    fptr fp1 = func1;

    //通过这个类型定义一个变量 然后初始化 把func1 赋值过去

    //跟int x =6;格式类似

    fp1(5, 6, 7, 8, 9);

    return 0;

    }

    //9 – 7 – 5  array of function pointer

    //函数指针数组

    //实际上是一个数组,数组中每一个元素都是函数指针

    //[函数指针1,函数指针2,……,]

    //定义函数指针类型

    //typedef void (*fptr) (int a, int b, double c, float d, char e);

    //加上[] 立马变 函数指针数组类型

    //定义函数指针数组方式1:[]中括号写定义里

    typedef void (*fptrs[50]) (int a, int b, double c, float d, char e);

    //typedef void (*fptrs[]) (int a, int b, double c, float d, char e);

    //定义函数指针数组方2:[]中括号不写定义里,在后面使用时才写

    typedef void (*fptr) (int a, int b, double c, float d, char e);

    #include <iostream>

    using namespace std;

    void func1(int a, int b, double c, float d, char e) {

    cout << “func1” << endl;

    }

    void func2(int a, int b, double c, float d, char e) {

    cout << “func2” << endl;

    }

    void func3(int a, int b, double c, float d, char e) {

    cout << “func3” << endl;

    }

    int main() {

    //很像int a[] = {1,2,3};

    fptrs fps = {func1,func2,func3};

    //{func1,func2,func3,…,NULL}后面的都是NULL

    cout << fps[0] << endl;

    cout << fps[1] << endl;

    cout << fps[2] << endl;

    cout << fps[3] << endl;

    fptr fp[] = { func1,func2,func3 };

    //没报错就可以

    cout << fp[0] << endl;

    cout << fp[1] << endl;

    cout << fp[2] << endl;

    return 0;

    }

  • #C++ Day16 October 26 2025

    //9 – 6 – 1 the relationship between pointer and array

    #include <iostream>

    using namespace std;

    /*

    ———————————

    |  5   |  4   |  3  |  2  |  1  | 

    ———————————

    4   4 4   4 4   bytes

    int

    */

    //pointer is an address and the array’s name is an address too

    //

    int main() {

    //1.To access the array’s elements with pointer

    int a[5] = {5,4,3,2,1};

    cout << “array’s first element:” << a[0] << endl;

    int* p = a;

    cout << “array element’s first address:” << a << endl;

    cout << “the address that pointer pointed to:” << p << endl;

    cout << “the pointer access: the first element in the array:” << *p << endl;

    //the shift number of int is 4 bytes

    cout << “——————-” << endl;

    cout << “the second element’s address of the array:” << &a[1] << endl;

    cout << “pointer + 1  pointed address ” << p + 1 << endl; 

    cout << “the second element of the pointer accessed to:” << *(p + 1) << endl;

    cout << “the third element’s address of the array:” << &a[2] << endl;

    cout << “pointer + 2  pointed address ” << p + 2 << endl;

    cout << “the third element of the pointer accessed to:” << *(p + 2) << endl;

    cout << “——————-” << endl;

    p++;

    cout << “pointer access: array’s second element:” << *p << endl;

    cout << “——————-” << endl;

    //a++;//this place of the array can be the same as pointer

    //the array can’t add the sequence after initiation

    p = a;

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

    cout << “the array’s ” << (i + 1) << ” element is ” << *p << endl;

    p++;

    }

    cout << “——————-” << endl;

    double b[4] = { 4,3,2,1 };

    double* pd = b;

    cout << “the first element’s address:” << pd << endl;

    pd++;

    cout << “the second element’s address:” << pd << endl;

    //the shift number of double is 8 bytes

    //59 5A 5B 5C 5D 5E 5F 60

    //hexadecimal 十六进制

    //指针的+1,实际上是偏移了 sizeof(对应的指针类型)个字节

    // when a pointer is incrementes by 1, it acctuall offsets by a number of bytes equal to the size of the data type that the pointer points to

    //char,short, long long, float

    cout << “——————-” << endl;

    char c[4] = { ‘4’,’3′,’2′,’1′ };

    char* pc = c;

    cout << sizeof(char) << endl;

    printf(“the first element’s address:%#X\n”, pc);

    pc++;

    printf(“the second element’s address:%#X\n”, pc);

    cout << “——————-” << endl;

    short d[4] = { 4,3,2,1};

    short* ps = d;

    cout << sizeof(short) << endl;

    cout << “the first element’s address:” << ps << endl;

    ps++;

    cout << “the second element’s address:” << ps << endl;

    cout << “——————-” << endl;

    long long e[4] = { 4,3,2,1 };

    long long* pll = e;

    cout << sizeof(long long) << endl;

    cout << “the first element’s address:” << pll << endl;

    pll++;

    cout << “the second element’s address:” << pll << endl;

    //99 9A 9B 9C 9D 9E 9F A0

    cout << “——————-” << endl;

    float f[4] = { 4,3,2,1 };

    float* pf = f;

    cout << sizeof(float) << endl;

    cout << “the first element’s address:” << pf << endl;

    pf++;

    cout << “the second element’s address:” << pf << endl;

    return 0;

    }

    //9 – 6 – 2 pointer array

    //指针数组

    #include <iostream>

    using namespace std;

    int main() {

    char a[] = “I”;

    char b[] = “love”;

    char c[] = “you”;

    //pointer array

    char* p[3];// have 3 elements, each element is a pointer

    p[0] = a;

    p[1] = b;

    p[2] = c;

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

    cout << p[i] << ‘ ‘;

    }

    cout << endl;

    //two dimensional array(matrix)

    //二维数组 是 矩阵

    int mat[3][4]{

    {1,2,3,4},

    {5,6,7,8},

    {9,10,11,12}

    };

    int* pmat[3];

    //[rows][columns]

    pmat[0] = mat[0];//mat[0] represents the one dimensional array’s first address

    //mat[0]代表一维数组的首地址

    pmat[1] = mat[1];

    pmat[2] = mat[2];

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

    for (int j = 0; j < 4; ++j) {

    cout << *(pmat[i] + j) << ‘ ‘;

    //cout << pmat[i] + j << ‘ ‘;

    // +j and then dereference it

    }

    cout << endl;

    }

    return 0;

    }

    //9 – 6 – 3 array pointer

    //数组指针

    #include <iostream>

    using namespace std;

    int main() {

    int (*p)[5];

    //数组指针可以类比成二维数组

    int a[4][5]{

    {4,3,2,1,0},

    {9,8,7,6,5},

    {6,7,8,9,0},

    {5,6,7,8,9}

    };

    p = a;

    cout << p << endl; // hex 70  -> 7F -> 84

    //15 + 5 = 20 bytes

    // 20 = 4 * 5 = sizeof(int) *5(columns)

    //shift value

    cout << p+1 << endl; //hex 84 

    cout << p << ‘:’ << &a[0] << endl;

    cout << p+1 << ‘:’ << &a[1] << endl;

    //理解成数组指针指向一个二维数组的首地址,所以数组指针可以类比理解成二维数组

    //我们可以利用这个数组指针操作一个二维数组上的数组

    return 0;

    }

    //9 – 6 – 4 pointer array and array pointer

    //指针数组和数组指针

    #include <iostream>

    using namespace std;

    string getHex(int x) {

    char buff[10];

    sprintf_s(buff, “%X”, (x & 0xFFFF));

    return (string)buff;

    }

    int main() {

    int a[3][4]{

    {1,2,3,4},

    {9,10,11,12},

    {13,14,15,16}

    };

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

    for (int j = 0; j < 4; j++) {

    if (j) {

    cout << ‘,’;

    }

    int* p = &(a[i][j]);

    //cout << (void*)p ;

    cout << getHex((int) p);

    }

    cout << endl;

    }

    //指针数组

    int* q[3] = { &a[0][0],&a[1][0],&a[2][0] }; //这个3代表数组长度

    //the same as  int* q[3] = { a[0],a[1],a[2] } ;

    //数组指针

    int (*p)[4];

    //这个4代表你指向的是一个长度为4的数组

    p = &a[0];//即{1,2,3,4}, 这一行

    cout << “1.指针 + i” << endl;

    //q+i\p+i

    cout << “数组指针 ” << endl;

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

    string s = getHex((int)(p + i)) ;

    cout << “第” << i << “个[4]数组的地址是” << s << endl;

    }

    cout << “指针数组(没啥用),此为装指针的二重指针地址 ” << endl;

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

    string s = getHex((int)(q + i));

    cout << “第” << i << “q元素的地址是” << s << endl;

    }

    cout << “2.*(指针 + i)” << endl;

    //== *(指针 + i)+0

    //*(q+i) \ *(p+i)

    cout << “数组指针 ” << endl;

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

    string s = getHex((int)*(p + i));

    cout << “a数组的第” << i << “行的第0个元素的地址是” << s << endl;

    }

    cout << “指针数组 ” << endl;

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

    string s = getHex((int)*(q + i));

    cout << “a数组的第” << i << “行的第0个元素的地址是” << s << endl;

    }

    cout << “3.*(指针 + i)+j” << endl;

    cout << “数组指针 ” << endl;

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

    string s = getHex((int)(*(p + i) + 1));

    cout << “a数组的第” << i << “行的第1个元素的地址是” << s << endl;

    }

    cout << “指针数组 ” << endl;

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

    string s = getHex((int)(*(q + i)+2));

    cout << “a数组的第” << i << “行的第2个元素的地址是” << s << endl;

    }

    return 0;

    }

    AI:

    这是 C 语言中数组与指针关系的前置知识,用于理解数组元素的指针访问方式,以下是逐行解释:

    • int a[1024];:定义了一个长度为 1024 的整型数组a,数组在内存中是连续存储的。
    • int *p = a;:定义了一个整型指针p,并将数组a的首地址赋值给p。因为数组名a本质上是数组首元素的地址(即&a[0]),所以p指向了数组a的第一个元素a[0]。
    • a ≡ &(a[0]):数组名a等价于数组首元素a[0]的地址,这是 C 语言的语法规则。
    • p ≡ &(a[0]):因为p被赋值为a(数组首地址),所以p也等价于a[0]的地址。
    • p + 0 ≡ &(a[0]):指针p加 0,还是指向自身,所以等价于a[0]的地址。
    • p + i ≡ &(a[i]):指针的算术运算规则,p是指向int类型的指针,p + i表示在p的基础上偏移i个int类型的大小,最终指向的是数组第i个元素a[i]的地址。
    • *(p + i) ≡ *(&(a[i])) ≡ a[i]:
      • *(p + i):对p + i(a[i]的地址)解引用,得到的是该地址存储的内容,即a[i]。
      • *(&(a[i])):先取a[i]的地址(&(a[i])),再对该地址解引用,结果就是a[i]本身。
    • *(p + i) ≡ p[i]:这是指针的下标访问语法糖,p[i]等价于*(p + i),本质上是通过指针偏移和解引用操作来访问数组元素。

    总结来说,这些规则揭示了数组和指针在访问元素时的等价性,理解它们能帮助你灵活运用指针操作数组,是 C 语言中数组与指针知识的核心基础。

    这段代码通过具体的地址打印,直观展示了指针数组数组指针的本质区别,尤其是两者在指针运算(+i)时的不同行为。我们逐部分解析:

    一、前置知识:getHex函数

    作用:将整数(这里实际是指针强制转换的结果)的低 16 位转换为大写十六进制字符串,方便以地址形式展示(简化地址显示,只看低 16 位差异)。

    二、二维数组a的地址打印

    首先定义了一个3行4列的二维数组a,然后通过嵌套循环遍历每个元素,打印每个元素的地址(转换为十六进制)。

    例如,假设a在内存中连续存储(二维数组在内存中是 “按行连续” 的),输出的地址会呈现规律:

    • 同一行内,元素地址依次递增(间隔sizeof(int),通常 4 字节);
    • 行与行之间,地址间隔4*sizeof(int)(一行 4 个元素,共 16 字节)。

    三、指针数组q与数组指针p的定义

    1. 指针数组q

    定义:int* q[3] = { &a[0][0], &a[1][0], &a[2][0] };

    • 本质:q是一个数组,包含 3 个元素,每个元素都是int*指针(指向int类型)。
    • 初始化:3 个指针分别指向二维数组a的第 0 行、第 1 行、第 2 行的首元素(&a[i][0]等价于a[i],因为数组名a[i]即首元素地址)。

    2. 数组指针p

    定义:int (*p)[4];

    • 本质:p是一个指针,专门指向 “包含 4 个int元素的数组”(即二维数组a中的一行)。
    • 赋值:p = &a[0]; 表示p指向a的第 0 行(a[0]是一行 4 个元素的数组,&a[0]是整个行的地址)。

    四、核心对比:p+iq+i的区别

    代码通过循环打印p+i和q+i的地址,直观展示两者的差异:

    1. 数组指针p+i

    • 含义:p指向 “4 个int的数组”,p+i表示指针向后移动i个 “行”(每个行是 4 个int)。
    • 地址变化:每次移动的步长 = 4 * sizeof(int)(一行的总大小,通常 16 字节)。
    • 输出结果:p+0指向第 0 行地址,p+1指向第 1 行地址,p+2指向第 2 行地址,与前面二维数组a每行首元素的地址完全一致(因为行地址即首元素地址)。

    2. 指针数组q+i

    • 含义:q是数组(元素为int*),q作为数组名表示首元素地址,q+i表示数组q中第i个元素的地址(即指针数组自身存储元素的位置)。
    • 地址变化:每次移动的步长 = sizeof(int*)(一个指针的大小,64 位系统中是 8 字节)。
    • 输出结果:打印的是指针数组q自身在内存中的元素地址(比如q+0是q[0]这个指针变量的地址,q+1是q[1]这个指针变量的地址),与二维数组a的地址无关(除非巧合),因此注释说 “没啥用”(因为这里想观察的是指向a的地址,而非q自身的元素地址)。

    总结

    这段代码的核心目的是通过地址打印,让我们看到:

    • 数组指针p 是 “指向行的指针”,p+i会跳到第i行的地址(步长为一行大小),用于定位二维数组的行;
    • 指针数组q 是 “存储行首指针的数组”,q+i是数组中第i个指针的地址(步长为指针大小),与二维数组的行地址无直接关联(除非访问q[i]才是行首指针)。

    通过地址的十六进制变化,能更直观理解两者的本质差异:一个是 “指向数组的指针”,一个是 “存储指针的数组”。

    AI2:

    这张图是用来解释二维数组的地址与元素值的对应关系,核心是展示二维数组在内存中的连续存储特性,以下是分部分解释:

    1. 二维数组的 “值”(a[i][j]

    上方的表格是二维数组中每个元素的具体数值,比如:

    • 第 0 行(i=0)的元素是 a[0][0]=1、a[0][1]=2、a[0][2]=3、a[0][3]=4;
    • 第 1 行(i=1)的元素是 a[1][0]=5、a[1][1]=4、a[1][2]=6、a[1][3]=7;
    • 第 2 行(i=2)的元素是 a[2][0]=9、a[2][1]=10、a[2][2]=11、a[2][3]=12。

    2. 二维数组的 “地址”(&a[i][j]

    下方的表格是对应元素在内存中的地址(以十六进制表示,比如F18、F1C等),需要注意以下几点:

    • 内存连续性:二维数组在内存中是按行连续存储的。比如第 0 行最后一个元素a[0][3]的地址是F24,第 1 行第一个元素a[1][0]的地址是F28,两者相差4个字节(因为int类型占 4 字节),这体现了 “第一行的最后一个元素的地址偏移了 4 个字节” 的说明。000
    • 地址与元素的对应:每个地址&a[i][j]唯一对应一个元素a[i][j],比如地址F18对应值1(即a[0][0]),地址F2C对应值4(即a[1][1])。

    3. 补充说明(底部文字)

    “a[i][j]的地址(也就是&a[i][j])并没有存储在二维数组里面,PPT 这么连续的画是为了清楚的和二维数组中的值,进行一一对应而已” 这句话是强调:内存中只存储数组的元素值,“地址表格” 是为了辅助理解而抽象出来的对应关系,实际内存里不会额外存储这些地址的表格,地址是通过数组的存储规则(连续、按类型偏移)计算得到的。

    总结来说,这张图通过 “值” 和 “地址” 的对照,直观展示了二维数组按行连续存储的内存特性,以及元素值和其地址的一一对应关系,帮助理解二维数组的指针访问和内存布局。

    AI3:

    这是关于指针数组的知识讲解图,核心信息如下:

    1. 指针数组的定义

    int *p[3]; 表示定义了一个指针数组p,它包含 3 个元素,每个元素都是一个指向int类型的指针。

    2. 指针数组与二维数组的关联

    指针数组p的每个元素p[i]存储了二维数组a对应行的首地址:

    • p[0]的值为F18,指向二维数组a第 0 行的首元素a[0][0](值为 1);
    • p[1]的值为F28,指向二维数组a第 1 行的首元素a[1][0](值为 5);
    • p[2]的值为F38,指向二维数组a第 2 行的首元素a[2][0](值为 9)。

    3. 二维数组的内存呼应

    右侧的 “二维数组a的值” 和 “二维数组a的地址” 表格,与之前讲解的二维数组内存布局一致,体现了元素值a[i][j]和其地址&a[i][j]的一一对应关系,同时也说明指针数组p是通过存储每行首地址来间接访问二维数组元素的。

    这种结构常用于灵活管理多个一维数组(或二维数组的行),相比直接操作二维数组,指针数组在某些场景(如行的重新排序、动态分配行内存等)下更具灵活性。

    AI4:

    存储地址的地址,指针的指针

    从十六进制地址A38到A40,核心是偏移 8 个字节,需结合内存编址规则和数据存储特性解读:

    1. 先明确地址差值:8 个字节

    • 十六进制计算:A40(十六进制) – A38(十六进制) = 8(十六进制),而十六进制的8对应十进制的8,因此地址跨度为 8 个字节(内存以字节为最小编址单位,1 个十六进制单位对应 1 个字节)。
    • 直观验证:从A38开始,逐字节递增依次是A39、A3A、A3B、A3C、A3D、A3E、A3F、A40,共 8 个字节间隔。

    2. 与 “数据类型存储” 的关联(结合此前场景)

    结合之前讨论的 “4 字节偏移(对应 32 位数据)”,A38到A40的 8 字节偏移,通常对应以下场景:

    • 双精度数据类型:若存储double(双精度浮点数,占 8 字节),从A38开始存储 1 个double数据,其结束地址就是A40(A38为起始地址,占用A38~A3F共 8 字节,下一个数据的起始地址为A40)。
    • 多个 32 位数据的连续存储:若存储 2 个int(32 位整数,每个占 4 字节),第一个int从A38开始(占A38~A3B),第二个int从A3C开始(占A3C~A3F),两个数据存储完成后,下一个数据的起始地址就是A40,整体体现为从A38到A40的 8 字节跨度。
    • 指针数组的元素间隔:若A38是指针数组p中某元素(如p[0])的地址(指针占 8 字节,64 位系统),则下一个元素p[1]的地址就是A40,正好对应A38到A40的 8 字节偏移(64 位系统中指针占 8 字节,元素间偏移 8 字节)。

    3. 内存对齐的体现

    若系统遵循 “8 字节对齐”(如 64 位环境下部分数据类型要求),A38到A40的跨度也符合对齐规则:

    • 例如long long(64 位整数,占 8 字节)需 8 字节对齐,其起始地址必须是 8 的倍数(A38是 8 的倍数:A38转十进制为 2616,2616÷8=327,无余数),存储后下一个对齐地址就是A40(A40转十进制为 2624,2624÷8=328,同样是 8 的倍数),符合对齐要求。

    综上,A38到A40的核心是 8 字节地址跨度,其本质是由数据类型的存储大小(如 8 字节double、2 个 4 字节int)或内存对齐规则决定的,是计算机内存连续存储逻辑的典型体现。

    这是关于指针数组的知识图解,核心信息如下:

    1. 指针数组的定义与存储

    • 定义:int *p[3]; 表示一个指针数组p,包含 3 个指向int类型的指针元素。
    • 指针数组的值(p[i]):存储二维数组a各行的首地址,如p[0]=F18(指向a[0][0])、p[1]=F28(指向a[1][0])、p[2]=F38(指向a[2][0])。
    • 指针数组的地址(&p[i]):指针数组本身也存储在内存中,其元素的地址为A38、A40、A48,体现了指针数组自身的内存布局。

    2. 与二维数组的关联

    右侧的二维数组a的 “值(a[i][j])” 和 “地址(&a[i][j])” 表格,与指针数组p的元素形成映射:指针数组通过存储行首地址,间接实现对二维数组元素的访问。这种结构常用于灵活管理多维数组的行,在行的动态分配、排序等场景中具有优势。

    3. 内存特性说明

    指针数组的 “值” 存储的是目标数组的地址,而其自身的 “地址” 是指针数组元素在内存中的存储位置,清晰区分了 “指针存储的地址” 和 “指针本身的地址” 这两个易混淆的概念,帮助理解指针数组的双层内存结构。

    sprintf_s(buff, “%X”, (x & 0xFFFF));

    这段代码的作用是将变量 x 的低 16 位数据以大写十六进制形式格式化到缓冲区 buff 中,具体解析如下:

    • 核心操作:提取低 16 位
      (x & 0xFFFF) 是一个位运算:
      • 0xFFFF 是十六进制常量,对应二进制的 16 个连续的 1(即 0b1111111111111111)。
      • 与 x 进行 “按位与” 运算后,会保留 x 的低 16 位数据,并将高于 16 位的部分清零(即只保留 x 中范围为 0~0xFFFF 的值)。
    • 格式化写入缓冲区
      sprintf_s(buff, “%X”, …) 是安全的字符串格式化函数:
      • %X 是格式控制符,表示以大写十六进制形式输出整数(例如 10 会被转换为 “A”,255 会被转换为 “FF”)。
      • sprintf_s 相比 sprintf 增加了安全性检查(需要指定缓冲区大小,通常用法为 sprintf_s(buff, sizeof(buff), “%X”, …)),可避免缓冲区溢出风险。

    总结:这段代码的功能是将 x 的低 16 位数值转换为大写十六进制字符串,并存储到 buff 缓冲区中,同时通过 sprintf_s 保证操作的安全性。

    AI5:

    指针数组和数组指针是 C/C++ 中容易混淆的两个概念,核心区别在于:指针数组是 “数组”(元素为指针),数组指针是 “指针”(指向数组)。下面通过定义、本质、示例详细说明:

    一、指针数组(Array of Pointers

    本质:一个数组,数组中的每个元素都是指针(同类型指针)。

    声明形式:类型 *数组名[数组长度](注意:[]优先级高于*,所以先构成数组,元素为指针)。

    示例:

    c

    运行

    int a = 1, b = 2, c = 3;

    int *ptr_arr[3];  // 指针数组:数组有3个元素,每个元素是int*指针

    // 给数组元素(指针)赋值

    ptr_arr[0] = &a;

    ptr_arr[1] = &b;

    ptr_arr[2] = &c;

    // 访问:先通过数组下标取指针,再解引用取数据

    printf(“%d, %d, %d”, *ptr_arr[0], *ptr_arr[1], *ptr_arr[2]);  // 输出:1, 2, 3

    特点:

    • 数组的大小 = 元素个数 × 单个指针的大小(如 32 位系统指针占 4 字节,64 位占 8 字节)。
    • 用途:常用于存储多个同类型数据的地址(最典型的是字符串数组,因为字符串本质是 char*)。
      例如:c

      运行




      char *strs[3] = {“hello”, “world”, “cpp”};  // 每个元素是char*指针,指向字符串常量




    二、数组指针(Pointer to Array

    本质:一个指针,该指针专门指向一个固定大小的数组(数组的元素类型和长度固定)。

    声明形式:类型 (*指针名)[数组长度](注意:()强制*先与指针名结合,所以是指针,指向数组)。

    示例:

    c

    运行

    int arr[3] = {10, 20, 30};

    int (*arr_ptr)[3];  // 数组指针:指针指向“包含3个int元素的数组”

    arr_ptr = &arr;  // 赋值:指向数组arr的地址(数组名arr是首元素地址,&arr才是整个数组的地址)

    // 访问:先解引用指针得到数组,再通过下标取元素

    printf(“%d, %d, %d”, (*arr_ptr)[0], (*arr_ptr)[1], (*arr_ptr)[2]);  // 输出:10, 20, 30

    特点:

    • 指针的大小 = 系统指针的大小(与指向的数组大小无关,32 位 4 字节,64 位 8 字节)。
    • 指针的 “步长” = 指向的数组总大小(例如int (*p)[3],p+1会跳过3×sizeof(int)字节)。
    • 用途:常用于处理多维数组(尤其是作为函数参数传递多维数组时,明确数组第二维的长度)。
      例如:c

      运行




      int matrix[2][3] = {{1,2,3}, {4,5,6}};
    • int (*p)[3] = matrix;  // p指向matrix的第0行(每行是含3个int的数组)
    • printf(“%d”, p[1][2]);  // 等价于(*(p+1))[2],输出6(第1行第2列)




    三、核心区别对比

    类型本质声明示例关键特征
    指针数组数组(元素为指针)int *arr[3];arr是数组,arr[i]是指针
    数组指针指针(指向数组)int (*p)[3];p是指针,*p是数组

    记忆技巧

    • 看声明中*和[]的结合顺序:
      • 无括号时,[]优先结合,所以类型 *名[]是指针数组(先成数组,元素为指针)。
      • 有括号时,*优先结合,所以类型 (*名)[]是数组指针(先成指针,指向数组)。

    好问题!😊

    “指针数组”和“数组指针”名字很像,但意思完全不一样。

    我们可以先从直觉上去理解,然后再看看它们的定义。

    一、指针数组(array of pointers

    定义:

    一个指针数组就是数组的每个元素都是一个指针

    🔹语法例子:

    int *p[3];

    这里:

    • p 是一个数组;

    • 这个数组里有 3 个元素;

    • 每个元素都是 int * 类型的指针。

    🧠 你可以想象它是:

    p[0]p[1]p[2] → 都是指针,分别可以指向不同的 int 变量。

    比如:

    int a = 1, b = 2, c = 3;

    int *p[3] = {&a, &b, &c};

    那么:

    • *p[0] == 1

    • *p[1] == 2

    • *p[2] == 3

    二、数组指针(pointer to an array

    定义:

    一个数组指针就是指向整个数组的指针

    🔹语法例子:

    int (*p)[3];

    这里:

    • p 是一个指针;

    • 它指向一个“包含 3 个 int 元素的数组”。

    比如:

    int a[3] = {1, 2, 3};

    int (*p)[3] = &a;

    那么:

    • (*p)[0] == 1

    • (*p)[1] == 2

    • (*p)[2] == 3

    🧩 小技巧记忆:

    • “指针数组” → 数组大,里面装指针。

    • “数组指针” → 指针大,它指向数组。

    要不要我帮你画个内存图,用图来区分它们?这样更容易看出谁指向谁。

    没关系~这是 C 语言里非常容易混淆的一对概念,我们慢慢来。

    我用比喻 + 视觉化的方式帮你彻底分清。

    💡先想象两个世界

    名称 直观比喻 语法例子

    指针数组 一个“装指针的盒子” int *p[3];

    数组指针 一根“指向整个数组的指针” int (*p)[3];

    🧱 1. 指针数组 int *p[3];

    “p 是一个数组,数组中有 3 个指针,每个指针都能指向一个 int。”

    图示:

    p[0] —> &a

    p[1] —> &b

    p[2] —> &c

    代码例子:

    int a = 10, b = 20, c = 30;

    int *p[3] = { &a, &b, &c };

    printf(“%d\n”, *p[1]);  // 输出 20

    🧠 小记忆:

    👉 方括号 [] 优先级高,所以 p[3] 是数组,*p[3] 说明数组里是指针。

    🧭 2. 数组指针 int (*p)[3];

    “p 是一个指针,指向一个含有 3 个 int 的数组。”

    图示:

    a —> [ 1 | 2 | 3 ]

    p —> a

    代码例子:

    int a[3] = {1, 2, 3};

    int (*p)[3] = &a;

    printf(“%d\n”, (*p)[1]);  // 输出 2

    🧠 小记忆:

    👉 () 的优先级强制让 *p 先结合,所以 (*p)[3] 表示 p 指向一个数组。

    🌈一句话区分法:

    名称 口诀

    指针数组 “数组里装指针”

    数组指针 “指针指向数组”

    想试试我出两道小题,让你判断哪个是“指针数组”,哪个是“数组指针”吗?

    我们边做边练就能完全掌握。

    非常好!👏完全正确。

    ✅ int *p[5];

    意思是:“p 是一个包含 5 个元素的数组,每个元素都是 int * 类型的指针。”

    你记住口诀 “数组里装指针” 就对啦。

    我们来升一点难度👇

    第 2 题:

    int (*p)[5];

    请你想一想:这回 p 是指针数组还是数组指针?

    非常好!👏完全正确。

    ✅ int *p[5];

    意思是:“p 是一个包含 5 个元素的数组,每个元素都是 int * 类型的指针。”

    你记住口诀 “数组里装指针” 就对啦。

    我们来升一点难度👇

    第 2 题:

    int (*p)[5];

    请你想一想:这回 p 是指针数组还是数组指针?

    完美!👍

    你不仅答对了,而且解释也非常到位。

    ✅ int (*p)[5];

    意思是:p 是一个指针,它指向一个包含 5 个 int 元素的数组。

    举个小例子帮助你巩固:

    int a[5] = {1, 2, 3, 4, 5};

    int (*p)[5] = &a;

    printf(“%d\n”, (*p)[2]); // 输出 3

    来,我们再来一道稍微绕一点的题,看看你能不能一眼判断出来👇

    第 3 题:

    char *strs[3];

    这次呢?是“指针数组”还是“数组指针”?

    (提示:想想 char * 是什么,再看 []。)

    这次有点小小偏啦~😉

    其实这一题的答案是:

    指针数组

    我们来拆开看看为什么:

    char *strs[3];

    • 方括号 [] 优先级比 * 高。

    • 所以它先说明 strs 是一个数组

    • 然后数组里的每个元素类型是 char *,也就是“字符指针”。

    🧠 这表示:

    strs 是一个“装有 3 个字符串指针”的数组。

    比如:

    char *strs[3] = {“Hello”, “World”, “C”};

    printf(“%s\n”, strs[1]); // 输出 “World”

    你想继续练 2~3 道稍微进阶一点的例子吗?

    我可以加上一些你在字符串或二维数组场景中会真用到的那种。

  • #C++ Day15 October 25 2025

    //9 – 4 null pointer and wild pointer

    #include <iostream>

    using namespace std;

    int main() {

    //1.null pointer(空指针)

    //normally,if we don’t use the pointer, we will define it null

    int* p = NULL;

    //but it we dereference it, it will be a null pointer error

    //cout << *p << endl;

    //2.wild pointer(野指针)

    int* pa = (int*)0x121412;

    //force translate the RAM address to int * type pointer and assigned it

    //wild pointer:define a ramdon number to the pointer casually

    cout << *pa << endl;

    //if the code cost a lot time, it probably an error exists

    return 0;

    }

    //9-5-1 pointer constant(指针常量)

    #include <iostream>

    using namespace std;

    /*

    the relationship between constant and pointer:

    */

    int main() {

    int a = 1;

    int b = 2;

    //pointer constant

    //the value of the pointer is a constant, the value is an address now 

    int* const p = &a;

    //p = &b; //it don’t allow to change it

    *p = 7;//but the value that pointer pointed to(dereference) can be modified

    cout << “a = ” << a << endl;

    return 0;

    }

    //9 – 5 – 3 const pointer to a const

    #include <iostream>

    using namespace std;

    /*

    指针常量 type* const       指针值是一个常量 指针无法被赋值

    常量指针 const type*   指向常量的指针 指针解引用后无法被赋值

    常量指针常量  const type* const 指针值和指针指向的值都是常量 指针和解引用都无法被赋值

    */

    int main() {

    int a = 1;

    int b = 2;

    //常量指针常量:本身的值和指向的地址中的值都无法改变

    const int* const p = &a;

    //*p = 6; //error

    //p = &b;//error

    return 0;

    }

    AI:

    指针常量(pointer constant)和常量指针(pointer to a constant)是 C/C++ 中容易混淆的两个概念,核心区别在于 **“不可变” 的对象不同 **:

    1. 指针常量(pointer constant)

    • 定义:指针本身是一个常量,指针存储的地址不能被修改,但指针指向的内容可以修改。
    • 语法形式:类型* const 指针名;
      例:int* const p;(p 是指针常量)
    • 特性
      • 指针一旦初始化(指向某个地址),就不能再指向其他地址(p = &a; 初始化后,p = &b; 会报错)。
      • 可以通过指针修改指向的内容(*p = 10; 合法)。

    2. 常量指针(pointer to a constant)

    • 定义:指针指向的是一个常量,通过该指针不能修改指向的内容,但指针本身可以指向其他地址。
    • 语法形式:const 类型* 指针名; 或 类型 const* 指针名;(两种写法等价)
      例:const int* p; 或 int const* p;(p 是常量指针)
    • 特性
      • 指针可以随时改变指向(p = &a; 之后,p = &b; 合法)。
      • 不能通过指针修改指向的内容(*p = 10; 会报错)。

    一句话总结

    • 指针常量:指针不可变,内容可变(int* const p;)。
    • 常量指针:内容不可变,指针可变(const int* p;)。

    记忆技巧:看 const 修饰的是谁 —— 修饰指针(* 后)则是指针常量,修饰类型(* 前)则是常量指针。

  • #C++ Day14 October 24 2025

    //9 – 1 pointer and address

    #include <iostream>

    using namespace std;

    /*

    hex

    hexadecimal 十六进制

    ____________________________________________

    0X7694F6A4|0X7694F6A5|0X7694F6A6|…|…| RAM address(内存地址)

       10 5 pa variable value(变量值)

       a b variable name(变量名)

    _____________________________________________

    char a = 10;

    char b = 5;

    a=11;

    b=9;

    //A memory address is required to store a bariable, and the address where the variable is stored is called a pointer

    */

    int main() {

    char a = 10;

    //cout << (&a) << endl;

    printf(“%#X”, &a); //0X7694F6A4

    //c++ is compatible with C

    //&a taking the address of variable a

    //%X specifies that the integer should be output in upper case hexadecimal form

    //# modifier makes the output hexadecimal number with the 0x prefix

    return 0;

    }

    //9 – 2 the definiton and the use of pointer

    #include <iostream>

    using namespace std;

    /*

    ____________________________________________

    0X00000004 | 0X00000008 | …… | … | RAM address(内存地址)

    10 5 pa     variable value(变量值)

    a b variable name(变量名)

    _____________________________________________

    */

    //If a variable stores a pointer to a piece of data, we call this variable a pointer variable

    int main() {

    int a = 10;

    int b = 20;

    //1.the definition of pointer variable

    // data type    *   pointer variable name

    //数据类型 *   指针变量名;

    int* pa; // pointer a, there must be a * int when defining

    pa = &a;

    //pa = &b; //pointer can be sent values multiple 

    printf(“%#X %#X\n”,&a,pa); //the same addresses

    cout << “—————–” << endl;

    //2.dereference (解引用)

    //to take the value of the address, and to modify

    //*指针变量名 = 数值;

    *pa = 7;

    cout << a << ‘ ‘ << (*pa) << endl;

    cout << “—————–” << endl;

    //3.the relation between * and &

    // *&a  == *(&a) == *pa == a

    //&*pa == &(*pa) == &a == pa 

    //they are essentially inverse operation of each other (实际上是个互逆的关系)

    //likewise + – 

    //right combine : first calculate the right quote

    cout << (*&a) << endl;

    cout << (*(&a)) << endl;

    cout << (*pa) << endl;

    cout << (a) << endl;

    cout << “—————–” << endl;

    cout << (&*pa) << endl;

    cout << (&(*pa)) << endl;

    cout << (&a) << endl;

    cout << (pa) << endl;

    return 0;

    }

    //9 – 3 RAM memory of pointer

    #include <iostream>

    using namespace std;

    //the pointer is a memory space itself, which can occupied the RAM space

    int main() {

    //sizeof(char);//1

    //sizeof(int);//4

    //sizeof(int *);//8

    cout << sizeof(int*) << endl;

    cout << sizeof(short*) << endl;

    cout << sizeof(char*) << endl;

    cout << sizeof(long*) << endl;

    cout << sizeof(long long*) << endl;

    cout << sizeof(float*) << endl;

    cout << sizeof(double*) << endl;

    //the pointer’s size only relative to the OS’s type(32bits,64bits)

    //all the pointer type occupied the same size in an OS (32bits 4bytes,64bits 8bytes)

    return 0;

    }

  • #C++ Day13 October 23 2025

    //8 – 1 function

    #include <iostream>

    using namespace std;

    /*

    1.the type of return value:int,float,double,char,string ……

    2.function name

    3.parameter’s list:this function’s parameters that I need to deliver

    4.return the expression(表达式)

    返回值类型 函数名(参数列表)

    {

    函数体(各种要写的编程语句)

    return 表达式

    }

    type   function name(parameter list)

    {

    body of function;

    }

    */

    int add(int a,int b)

    {

    int c = a + b;

    return c;

    }

    double add2(int a, int b)

    {

    int c = a + b; //conversion from ‘double’ to ‘int’, possible loss of data

    return c;

    }

    double add3(int a, int b)

    {

    int c = a + b; //conversion from ‘double’ to ‘int’, possible loss of data

    return c;//this step would not be an error,but there is an unvisable translation

    //return (double)c; small to big will not make a problem

    }

    int add4(int a, int b)

    {

    return a+b;

    }

    void printAaddB(int a, int b) //func type is null/void(空)

    {

    int c = a  + b ;

    cout << c << endl;

    return ; //Don’t need to return a value because it’s function type is null/void

    }

    int max(int a, int b)

    {

    if (a > b) {

    return a;

    }

    return b;

    //to get the maximum value

    //or we can use ternary operator

    // return a > b ? a : b;

    }

    int max2(int a, int b)

    {

    //or we can use ternary operator

    return a > b ? a : b;

    }

    int main(){

    //int ret = add2(1, 7);//conversion from ‘double’ to ‘int’, possible loss of data

    double ret = add3(1, 7);//this step would not be an error,but there is an unvisable translation

    cout << ret << endl;

    return 0;

    //int is the main function’s return date type

    //0 is the main function’s return value

    //main is the function’s name

    //()is the function’s parameter

    }

    //8 – 2  function call

    #include <iostream>

    using namespace std;

    /*

    function’s calling have the use of encapsulation(封装)

    //黑盒(Blach box):we don’t need to know how to get it

    */

    int add(int a, int b) {

    return a + b;

    //return a – (-b);

    }

    int max(int a, int b) {

    return a > b ? a : b;

    }

    int sum(int n) {

    int ret = 0;

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

    ret += i;

    }

    //O(n)

    return ret;

    //枚举算法

    //this method is much more calling than the sum2’s method

    }

    int sum2(int n) {

    return (1+n) * n /2;

    //O(1)

    }

    int main() {

    //1.the addition’s call(加法的调用)

    int a = add(1, 7);

    int b = add(a,9);

    int c = add(add(1, 7), 9);

    //After made the function, I just need to use, instead of knew the details 

    cout << b << endl;

    cout << c << endl;

    //2.the maximum number’s call(最大值的调用)

    int d = max(a, b);

    //3.to get the sum from 1 to n(求1到n的和)

    int n;

    cin >> n;

    int e = sum2(n);

    cout << e << endl;

    return 0;

    }

    //8 – 3 function value transfer

    #include <iostream>

    using namespace std;

    /*

    | a | b   | | a   | | b   | |tmp | | a | | b  |

    | 6 | 9   | | 6   | | 9   | | 6  | | 9 | | 6  |

    4 bytes  4 bytes 4 bytes 4 bytes

    */

    //function value transfer:transfer some parameters into the function inside, and use it within the function inside

    void swap(int a, int b) {

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    cout << “————-“<< endl;

    int tmp = a;

    a = b;

    b = tmp;

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    cout << “————-” << endl;

    }

    int main() {

    int a = 6;

    int b = 9;

    swap(a,b);

    //the key point of function value transfer:the value we transfered will only be copied, the origin value is the same address

    //the value will be destroied when the function is finished execution

    //If you want to swap the original value, you can use the pointer

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    return 0;

    }

    //8 – 4 function declaration

    #include <iostream>

    using namespace std;

    //int add(int a, int b);//this is a function declaration

    int add(int, int);//we can only write the data type when declarating

    void func2(int);//declarating in advance is to prevent error

    void func1(int x) {

    if (x <= 0) {

    return;

    }

    cout << “func1:” << x << endl;

    func2(x-1);

    }

    void func2(int x) {

    cout << “func2:” << x << endl;

    func1(x-1);

    }

    int main() {

    int x, y;

    cin >> x >> y;

    int z = add(x, y);

    cout << z << endl;

    cout << “———” << endl;

    func1(-1);

    return 0;

    }

    int add(int a, int b) {

    return a + b;

    }

    //Place the function in this position, we won’t be able to run the code normally,if we didn’t declarate the function at first

  • #C++ Day12 October 22 2025

    //7 – 1 One – dimensional Array

    #include <iostream>

    using namespace std;

    int main() {

    //1.the definition of array

    //数据类型 数组名[数字 / 常量表达式];

    int a[1024] = {1,2,3,45};

    double b[520];

    char c[1314];

    //double a[1024];  //this is an error, we should not define two times the same name’s array

    //All the elements have the same date type in an array 

    //the array’s lower number(下标) is start from 0 (a[0] a[1] a[2])

    /*

    int n = 1000;

    int d[n];//error

    //there must be a constant value in the array’s lower symbol 

    */

    int x1[10], x2[20], x3, x4, x5,x6[100]; // the array can be listed in a single line

    //2.the access to the elements

    //数组名 [下标]

    //array’s name [index]

    a[0], a[1023];

    for (int i = 0; i < 4; ++i)

    {

    cout << a[i] << endl;

    //only access the initialized values

    }

    //3.Reverse output

    //please type in the n(n<100), and n numerical numbers, please reverse output these n numerical numbers

    //输入一个n(n<100),和n个数,请逆序输出这n个数

    int n;

    int x[100];

    cin >> n;

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

    cin >> x[i];

    }

    for (int i = n – 1; i >= 0; –i) {

    cout << x[i] << ‘ ‘;

    }

    cout <<  endl;

    return 0;

    }

    //7 – 2 Two – dimensional Array

    #include <iostream>

    using namespace std;

    //one dimensional array is a sequential list

    //two dimensional array is like an excel table, which has  rows and columns

    int main() {

    //two dimensional array can be related to matrices in  linear algebra

    //1.the definition of two dimensional arrays

    //data type    array name    [rows] [columns] ;

    //数据类型 数组名 [行] [列]

    int arr[3][4];

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

    for (int j = 0; j < 3; ++j) {

    arr[i][j] = i * j;

    }

    }

    int b[4][4]{

    {1,2},

    {2,3},

    {6,7,8},

    {9,1}

    };

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

    for (int j = 0; j < 4; ++j) {

    cout << b[i][j] << ‘ ‘;

    }

    cout << endl;

    }

    cout << b << ‘ ‘; //the array name represents the starting address

    return 0;

    }