作者: supervito

  • #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;

    }

  • #C++ Day11 October 21 2025

    //6 – 2 – 1 while

    #include <iostream>

    #include <string>

    using namespace std;

    int main() {

    /*int count = 0;

    while (count < 1000) {

    cout << “I love you!” << endl;

    count += 1;

    }*/

    /*while (1) {

    cout << “I love you!” << endl;

    count += 1;

    //dead repeat

    }*/

    //string a;

    //while (cin >> a) {

    // cout << a+ ” I love you!” << endl;

    //}

    int a,b;

    while (cin >> a >> b) {

    cout << a+ b << endl;

    }

    return 0;

    }

    //6 – 2 – 2 do-while

    #include <iostream>

    using namespace std;

    int main() {

    /*

    do {

    } while (condition);

    */

    //if the condition is true, keep on execution

    /*int a = 0;

    do {

    cout << a << endl;

    a += 1;

    } while (a < 3);

    a = 0;

    while (a < 3) {

    cout << a << endl;

    a += 1;

    }*/

    int a = 3;

    do {

    cout << a << ” haha” << endl;

    a += 1;

    } while (a < 3); 

    //do-while will first execute the do case at least once,then continue to  judge the while’s conditon 

    cout << “—–” << endl;

    a = 3;

    //The while will judge the condition at first, if the condition is false, the sentence won’t be executed

    while (a < 3) {

    cout << a << ” gg” << endl;

    a += 1;

    }

    return 0;

    }

    AI:

    等差数列求和公式详解

    要理解等差数列的求和公式,首先需要明确等差数列的基本定义,再通过推导过程掌握公式的本质,最终学会根据已知条件选择合适的公式计算。

    一、等差数列的基本概念

    在等差数列中,有几个核心量需要先明确,后续公式均围绕这些量展开:

    • 首项:数列的第一个数,记为 ( a_1 )(例如数列 ( 2,5,8,11\cdots ) 中,( a_1=2 ));
    • 公差:相邻两项的差值(后项减前项),记为 ( d )(上述数列中,( d=5-2=3 ),且所有相邻项差值恒定);
    • 项数:数列中元素的个数,记为 ( n )(例如求“前5项和”时,( n=5 ));
    • 末项:数列的第 ( n ) 项,记为 ( a_n )(可由首项和公差推导:( a_n = a_1 + (n-1)d ),上述数列第5项为 ( a_5=2+(5-1)\times3=14 ));
    • 前( n )项和:数列前 ( n ) 个元素的总和,记为 ( S_n )(本文核心求算目标)。

    二、等差数列的两个核心求和公式

    等差数列求和公式有两种形式,分别适用于“已知末项”和“未知末项”的场景,本质是相通的(可相互推导)。

    公式1:已知首项、末项和项数(最直观的形式)

    公式表达式:

    [ S_n = \frac{n(a_1 + a_n)}{2} ]

    推导过程(倒序相加法,高斯求和的本质):

    以“求 ( 1+2+3+\cdots+100 ) 的和”为例,高斯的思路可推广到所有等差数列:

    1. 写出前 ( n ) 项和的正序:( S_n = a_1 + (a_1+d) + (a_1+2d) + \cdots + a_n );
    2. 写出前 ( n ) 项和的倒序:( S_n = a_n + (a_n-d) + (a_n-2d) + \cdots + a_1 );
    3. 将正序和倒序对应项相加,每一组的和均为 ( a_1 + a_n )(例如正序第1项+倒序第1项:( a_1+a_n );正序第2项+倒序第2项:( (a_1+d)+(a_n-d)=a_1+a_n ),以此类推);
    4. 总共有 ( n ) 组这样的和,因此:( 2S_n = n(a_1 + a_n) ),两边除以2即得公式1。

    适用场景:

    已知 ( a_1 )(首项)、( a_n )(末项)、( n )(项数),直接代入计算。

    示例:求等差数列 ( 3,7,11,15 ) 的前4项和。

    已知 ( a_1=3 ),( a_4=15 ),( n=4 ),代入公式:

    ( S_4 = \frac{4\times(3+15)}{2} = 4\times9 = 36 )(验证:( 3+7+11+15=36 ),结果正确)。

    公式2:已知首项、公差和项数(无需先求末项)

    公式表达式:

    [ S_n = na_1 + \frac{n(n-1)d}{2} \quad \text{或} \quad S_n = \frac{n\left[2a_1 + (n-1)d\right]}{2} ]

    推导过程(由公式1转化而来):

    因为等差数列的末项 ( a_n = a_1 + (n-1)d ),将其代入公式1:

    [ S_n = \frac{n\left[a_1 + a_1 + (n-1)d\right]}{2} = \frac{n\left[2a_1 + (n-1)d\right]}{2} ]

    展开后即可得到 ( S_n = na_1 + \frac{n(n-1)d}{2} )(两种形式等价,前者更便于分步计算)。

    适用场景:

    已知 ( a_1 )(首项)、( d )(公差)、( n )(项数),无需先求末项,直接代入计算。

    示例:求等差数列 ( 2,5,8,11\cdots ) 的前6项和。

    已知 ( a_1=2 ),( d=3 ),( n=6 ),代入公式:

    ( S_6 = 6\times2 + \frac{6\times(6-1)\times3}{2} = 12 + \frac{6\times5\times3}{2} = 12 + 45 = 57 )(验证:( 2+5+8+11+14+17=57 ),结果正确)。

    三、公式选择与注意事项

    1. 公式选择原则
      1. 若已知“末项 ( a_n )”,优先用公式1(计算更简洁);
      2. 若未知“末项 ( a_n )”,但已知“公差 ( d )”,用公式2(避免额外计算末项)。
    2. 关键易错点
      1. 项数 ( n ) 的确定:例如“求数列 ( 4,7,10\cdots,31 ) 的项数”,需用 ( a_n = a_1 + (n-1)d ) 反推:( 31 = 4 + (n-1)\times3 ),解得 ( n=10 )(注意:不是 ( (31-4)\div3=9 ),需加1);
      2. 公差 ( d ) 的符号:若数列为递减数列(如 ( 10,7,4,1\cdots )),公差 ( d=-3 ),代入公式时需保留负号,避免符号错误。

    四、总结

    等差数列求和的核心是“利用数列的对称性(倒序相加)”或“用首项、公差表示所有项”,两个公式的关系可总结为:

    [ \boxed{S_n = \frac{n(a_1 + a_n)}{2} \xleftrightarrow{\text{代入 } a_n = a_1 + (n-1)d} S_n = \frac{n\left[2a_1 + (n-1)d\right]}{2}} ]

    记忆时无需死记硬背,理解推导逻辑后,可根据已知条件灵活选择公式。

    //6 – 2 – 3 for

    #include <iostream>

    using namespace std;

    int main() {

    //a is initial condition

    //b is the stop step condition

    //c is the sentence that will be executed when each time we finished the {} sentence’s execution

    /*for (a;b ; c)

    {

    }*/

    /*int sum = 0;

    for (int i =1; i <= 5; ++i)

    {

    sum += i;

    }

    cout << sum << endl;*/

    /*int n;

    //method 1

    while (cin >> n) {

    int sum = 0;

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

    sum += i;

    }

    cout << sum << endl;

    }*/

    /*int n;

    //method 2

    while (cin >> n) {

    int sum = 0;

    int i = 1;

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

    sum += i;

    }

    cout << sum << endl;

    }*/

    /*

    int n;

    //method 3

    while (cin >> n) {

    int sum;

    int i;

    for (sum = 0, i = 1; i <= n; ++i) {

    sum += i;

    }

    cout << sum << endl;

    }

    */

    /*

    int n;

    //4.dead repeat

    while (cin >> n) {

    int sum;

    int i;

    for (sum = 0, i = 1;; ++i) {

    sum += i;

    }

    cout << sum << endl;

    }

    */

    int n;

    //4.dead repeat

    while (cin >> n) {

    int sum;

    int i;

    for (sum = 0, i = 1;i<=n; ) {

    sum += i;

    ++i;

    }

    cout << sum << endl;

    }

    //while repeat doesn’t have the initial step

    return 0;

    }

    //6-3-1 break

    #include <iostream>

    using namespace std;

    int main() {

    //1. the break in while;

    int cnt = 0;

    while (1) {

    cnt++;

    cout << “current number is ” << cnt << endl;

    if (cnt > 20) {

    break;

    //break is jump out this repeat

    }

    }

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

    //2. the break in for;

    cnt = 20;

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

    cout << “current number is ” << i << endl;

    if (i > cnt) {

    break;

    //break is jump out this repeat

    }

    //if this if sentence wrote in the for condition 2’s place, the code will be more complicative

    }

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

    //3. the break in switch case;

    int  a = 1;

    switch (a) {

    case 1:

    cout << “Vito” << endl;

    break; //if we aren’t all this break,the least included default will be outputed until met the break again

    case 2:

    cout << “Algorithm” << endl;

    default:

    cout << “Algorithm2” << endl;

    }

    return 0;

    }

    //6 – 3 – 2 continue

    #include <iostream>

    using namespace std;

    int main() {

    /*

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

    {

    if (i < 3) {

    continue;

    }

    cout << i << endl;

    }

    */

    /*

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

    {

    if (i < 3) {

    continue;

    //after the continue, the program will judege the for’s condition 2

    }

    cout << i << endl;

    ++i;

    //if we put the ++i on this place, this repetition will be the dead repetition, we must pay more attention when continue existed in the for sentence

    }

    */

    int a;

    while (cin >> a){

    if (a == 0) {

    continue;

    //continue is end up this time’s repetition

    }

    cout << a << endl;

    }

    return 0;

    }

  • #C++ Day10 October 16 2025

    Daily Sign in

    October 20 2025

    //6-1-1 if

    #include <iostream>

    using namespace std;

    int main() {

    /*if (express rule)

    rule1

    */

    int a = 43545435341;

    if (a % 2 == 1){

    cout << “a is an odd number” << endl;

    }

    int b;

    cin >> b;

    //==1 can be ignore, because if can recognize the 0(False) or the other numbers(True)

    if (b % 2 ) {

    cout << “b is an odd number” << endl;

    }

    if (b % 2) 

    cout << “b is an odd number” << endl;// if there is no {} symbol to select, there won’t execute the next line in this if express

    b = 3;// this one won’t be executed like the upper lineWW

    cout << “b is ” << b << endl;

    return 0;

    }

    //6-1-2 if else

    #include <iostream>

    using namespace std;

    int main() {

    /*

    if (exp1){

    rule1

    }

    else{

    rule2

    }

    */

    int x;

    cin >> x;

    string y = “add brick”;

    if (x & 1) {

    cout << “x is an odd number” << endl;

    y = y + ” JAVA”;

    }

    else {

    cout << “x is an even number” << endl;

    }

    cout << y << endl;

    return 0;

    }

    AI:

    这个问题抓得很准,位与判断奇数的核心就藏在二进制的最末位里!

    位与(&)能判断奇数,本质是利用了二进制数的末尾特征位与运算的规则。当一个数和数字 1 进行位与时,结果直接反映了这个数的奇偶性。

    1. 关键前提:二进制的 “奇偶密码”

    所有整数的二进制表示中,最后一位(最低位)直接决定了它是奇数还是偶数:

    • 奇数:二进制末尾一定是 1。比如 3(11)、5(101)、7(111)。
    • 偶数:二进制末尾一定是 0。比如 2(10)、4(100)、6(110)。

    这是因为二进制中,末尾位的权重是 \(2^0=1\),其他位的权重都是 2 的倍数(如 \(2^1=2\)、\(2^2=4\))。一个数的奇偶,本质就是看它是否包含 “1” 这个最小的奇数分量。

    2. 核心规则:位与(&)的运算逻辑

    位与运算的规则很简单:两个二进制位必须同时为 1,结果才是 1;否则结果为 0

    当我们用任意整数 n 和数字 1 进行位与时(即 n & 1),实际只关注 n 的二进制最后一位:

    • 若 n 是奇数(末尾为 1):1 & 1 = 1,最终结果为 1。
    • 若 n 是偶数(末尾为 0):0 & 1 = 0,最终结果为 0。

    3. 实例验证:一看就懂

    用具体数字举例,能更直观理解:

    十进制数(n二进制表示n & 1 的运算运算结果判断结论
    3(奇数)1111 & 01 = 011奇数
    4(偶数)100100 & 001 = 0000偶数
    7(奇数)111111 & 001 = 0011奇数
    8(偶数)10001000 & 0001 = 00000偶数

    要不要我帮你整理一份位运算判断奇偶的代码示例表?包含 Python、Java 等常见语言的具体写法,你可以直接参考使用。

    //6 – 1 – 3 else if

    #include <iostream>

    using namespace std;

    int main() {

    int  a;

    cin >> a;

    if (a==0) {

    cout << “Vito Algorithm” << endl;

    }

    //if (如果)

    //else if(否则如果)

    else if (a==1) {

    cout << “Vito1 Algorithm Legendary” << endl;

    }

    else if (a == 2) {

    cout << “Vito Algorithm Killer” << endl;

    }

    else if (a == 3) {

    cout << “Vito Algorithm Learner” << endl;

    }

    else{

    //the last else can be ignored

    cout << “Vito Another Algorithm” << endl;

    }

    return 0;

    }

    //6 – 1 – 4 conditional/ternary operator

    #include <iostream>

    using namespace std;

    int main() {

    int a = 3, b = 4;

    float c = 1.6;

    int x = -1;

    if (a > b) {

    x = a;

    }

    else {

    x = b;

    }

    //exp1? exp2:exp3;

    //if expression1 is true, then execute expression2, otherwise execute expresssion3

    x = (a > b ? a : b);

    x = (a > b) ? ( (a>c) ? a : c) : b; //ternary operator supports nest

    // the value type in ternary is depend on the values’ type

    cout << ((a > b) ? ((a > c) ? a : c) : c)<<endl;//1.6,output a float number

    cout << ((a < b) ? ((a > c) ? a : c) : c) << endl;//3,output an int number

    //If you think you can’t solve the priority problems, then you better add some quote symbols.

    return 0;

    }

    //6 – 1 – 5 switch case

    #include <iostream>

    using namespace std;

    int main() {

    int a = 0;

    cin >> a;

    /*

    if (a == 0) {

    cout << “Zero” << endl;

    }

    else if(a == 1) {

    cout << “One” << endl;

    }

    else if (a == 2) {

    cout << “Two” << endl;

    }

    else if (a == 3) {

    cout << “Three” << endl;

    }

    else{

    cout << “Beyond three or below zero” << endl;

    }

    */

    switch (a) {

    case 0:

    cout << “Zero” << endl;

    break; // If we didn’t add this break, then would execute next line until met the break, which is execute from up to down

    case 1:

    cout << “One” << endl;

    case 2:

    cout << “Two” << endl;

    break;

    case 3:

    cout << “Three” << endl;

    break;

    /*case 7.7:

    cout << “Three” << endl;

    break;*/

    case ‘4’:

    cout << “Four” << endl;

    break;

    //the switch expression must use int number or the ” char  to set the case, but the char ” will be recognized as the ascii codes

    case ‘9’-‘2’:

    //offset (偏移量),’9′ is 57 , ‘2’ is 50 , they are minus each other will be 7

    cout << “‘9’-‘2’ is 7” << endl;

    break;

    default:

    //If there weren’t meet the condition, if would jumped to the default case, which is equal to the if-else grammar’s else condition

    cout << “Beyond three or below zero” << endl;

    }

    return 0;

    }

  • #C++ Day9 October 15 2025

    //5 – 6 – 5 left shift operator

    #include <iostream>

    using namespace std;

    /*

    x << y    =  x * 2^y

    binary operator (二元运算符)

    */

    int main() {

    //1.positive number’s left shift

    int x = 0b11; //3

    x = (x << 1); // 6;0b110;left shift one bit, it will be multiple 2

    cout << x << endl;

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

    cout << (x<<4) << endl;//x*2^4

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

    //2.negative number’s left shift

    int y = -1;

    y = (y << 1);//-1*2^1

    cout << y << endl;

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

    //3.left shift negative’s bits

    int z = 64;

    z = (z << (-1));//it don’t allow the left shift’s right value is a negative number;despite some decoder can output it

    cout << z << endl;

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

    //4.left shift overflow

    int a = 64;

    a = (a << 10);//it don’t allow the left shift’s right value is a negative number;despite some decoder can output it

    cout << a << endl;

    //64=0b1000000

    //31 = 0b1…0000000000000000000000000000000000000

    //the int only can store 4bytes,so that the 1 will be cut out(截断)

    //left shift = *2 ;unstoppable *2 must be overflow

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

    return 0;

    }

    //5 – 6 – 6 right shift operator

    #include <iostream>

    using namespace std;

    /*

    x>>y right shift = x/2^y

    */

    int main() {

    //1.postive number’s right shift

    int a = 0b111; //7

    a = (a >> 1); // 7/2^1=3;cut out the rightest 1,and the remaining 0b11 will be 3

    cout << a << endl;

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

    //2.negative number’s right shift

    int  b = -1;

    cout << (-1 >> 1) << endl;//-1

    //11111111 11111111 11111111 11111111   this is -1-> right shift 1 bit–>//01111111 11111111 11111111 1111111  –> 11111111 11111111 11111111 1111111(finally it will be 1 on the symbol bit)

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

    //3.remove the lower k’s bits

    int c = 0b10000101; 

    cout << (c >> 7) << endl;//removed 0000101

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

    //4.retrive the lower k’s bits’ value

    int d = 0b101010101;

    //   1

    cout << ((d >> 4) & 1) << endl;//if the fourth bit is 1,after the bitwising,there will be output 1

    return 0;

    }

    AI:

    要理解 “11111111 11111111 11111111 11111111” 为何代表 – 1,核心在于计算机中整数的二进制表示规则—— 尤其是 “补码(Two’s Complement)” 编码方式。计算机为了统一处理正数和负数的加减运算,规定了带符号整数的存储格式,32 位(4 个字节,每个字节 8 位)带符号整数的表示逻辑如下:

    1. 先明确前提:这是 32 位 “带符号整数” 的二进制

    计算机中的整数分为两类:

    • 无符号整数(unsigned):仅表示非负数,所有二进制位都用于存储数值(范围:0 ~ 2³²-1);
    • 带符号整数(signed):需区分正负,规定最高位(第 31 位,从 0 开始计数)为符号位
      • 符号位 = 0 → 正数;
      • 符号位 = 1 → 负数。

    你给出的 “11111111 11111111 11111111 11111111” 是 32 位二进制,且最高位为 1,因此它是一个带符号负数,需用 “补码” 规则解析其实际值。

    2. 关键规则:负数用 “补码” 存储

    为了避免 “正 0” 和 “负 0” 的重复(如 8 位二进制中,00000000 和 10000000 都表示 0),同时让 “正数 + 负数” 的运算等同于普通二进制加法(无需额外处理符号),计算机规定:

    正数的原码、反码、补码完全相同;负数的补码 = 其 “绝对值的原码” 按位取反(反码) + 1

    反过来,若已知一个负数的补码,要计算其实际值,需执行逆操作

    负数的绝对值 = (补码 按位取反) + 1,再加上负号。

    3. 一步步计算:32 位全 1 补码对应的值

    对 “11111111 11111111 11111111 11111111” 执行逆操作:

    • 补码按位取反:将所有 1 变为 0,所有 0 变为 1。
      32 位全 1 的补码取反后,结果是 00000000 00000000 00000000 00000000(32 位全 0)。
    • 取反后 + 1:00000000…00000000 + 1 = 00000000 00000000 00000000 00000001(即十进制的 1)。
    • 加上负号:由于原补码的符号位为 1(表示负数),因此最终值为 -1

    4. 延伸验证:8 位带符号整数的例子

    若将范围缩小到 8 位(1 字节),“11111111” 同样是 – 1,逻辑完全一致:

    • 8 位全 1 的补码 → 符号位 = 1(负数);
    • 按位取反:00000000 → +1 后 = 00000001(十进制 1);
    • 最终值:-1。

    而 8 位带符号整数的范围是 -128 ~ +127(正是补码规则的结果),32 位带符号整数的范围则是 -2³¹ ~ +2³¹-1,其中 “11111111 11111111 11111111 11111111” 恰好是这个范围中的 “-1”。

    总结

    “11111111 11111111 11111111 11111111” 代表 – 1,本质是计算机用32 位带符号整数的补码规则存储负数的结果:

    符号位为 1(表示负数),其补码对应的绝对值为 1,因此最终值为 – 1。

  • #C++ Day8 October 14 2025

    //5 – 6 – 3 XOR Exclusive OR operator

    #include <iostream>

    using namespace std;

    /*

    ^ XOR :XOR from high to low,if two values are unequal, there will be 1

    */

    int main() {

    //1.the XOR’s definition

    int a = 0b1010;

    int b = 0b0110;

    //  ^ = 0b1100; //Dec = 12

    cout << (a ^ b) << endl;

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

    //2.Reverse the flag bit

    int c = 0b1000101;

    // 0b0001000;

    cout << c  << endl;

    cout << (c ^ 0b1000) << endl; // the first xor

    cout << ((c ^ 0b1000) ^ 0b1000) << endl;//xor again,xor is adaptive to the exchange and combine rules

    //two equal values,then xor will be full of the 0

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

    //3.exchange the values

    int d = 17;

    int e = 19;

    d = d ^ e;

    e = d ^ e; // = d’ ^ e = d ^ e ^ e = d ^ 0 = d

    d = d ^ e; // = d’ ^ d = d ^ e ^ d = d ^ d ^ e =0 ^ e = e, xor is adapt to the exchange rule

    cout << d << ‘ ‘ << e << endl;

    //3.1 any values xor with 0, it’s the same value ifself;

    //3.2 two same values xor, the output will be 0;

    //3.3 xor is adaptive to the exchange and the combine rule;

    //exclusive OR(XOR):not promote bit’s  binary add rule(不带进位的二进制加法)

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

    //4.occur the odd’s number(出现奇数次的数)

    int f = 3;//0b011

    int g = 4;//0b100

    int res = f ^ g ^ f; //=f^f^g=0^g=g

    cout << res << endl;

    //even’s number will finally become 0, and the odds will be left finally

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

    //5.encrypt

    int x = 1314;

    cout << “520” << x << endl;

    int y = (x ^ 3135); // 3135 is the key

    cout << “520” << y << endl; //5202333

    cout << “520” << (y ^ 3135) << endl; //decrypt

    return 0;

    }

    AI:

    好问题👍

    这个其实是理解 XOR(异或)运算 的核心所在。我们慢慢来,不急着看公式。先一步步想清楚概念。

    先回忆一下普通的 二进制加法:

    A B A+B(带进位)

    0 0 0

    0 1 1

    1 0 1

    1 1 10(进位1,结果0)

    看到最后一行了吗?

    只有当 A 和 B 都是 1 时,才会产生 进位。

    现在我们看 XOR 的真值表:

    A B A⊕B

    0 0 0

    0 1 1

    1 0 1

    1 1 0

    是不是发现——

    XOR 跟普通加法几乎一模一样,只是当 1+1 时,它不产生进位,而是直接输出 0。

    也就是说:

    XOR 就像是在做「不考虑进位」的二进制加法。

    我们换个角度想:

    在二进制加法里,其实结果有两个部分:

    1. 和(Sum):就是不带进位的部分 → A ⊕ B

    2. 进位(Carry):是产生进位的部分 → A & B

    所以:

    A + B = (A ⊕ B) + ((A & B) << 1)

    这就是计算机做加法的原理(位运算实现加法)。

    我想确认你理解到这里:

    你能解释一下「为什么当 A=1 且 B=1 时 XOR 的结果是 0 吗」?

    (提示:想想这时“1+1”的结果在二进制下是什么样的)

    AI2:

    对!你说对了 👏

    不过我们可以再把这句话拆开一点,让逻辑更清晰。

    A=1、B=1 时,普通加法得到 10(也就是结果位 0,进位 1)。

    而 XOR 只保留“结果位”,不管进位,所以它只取 0。

    换句话说:

    • 普通加法 = 结果位 + 进位

    • XOR = 只要结果位,不要进位

    所以我们说它是 “不带进位的二进制加法”

    来,我们检验一下你的理解:

    我给你两个二进制数:

    A = 1011  

    B = 1101

    你能试着算出 A ⊕ B 的结果吗?

    (一步一步来,不用急——我们只做 XOR,不带进位的)

    AI3:

    太棒了 🎯 完全正确!

    我们来一起验证一下思路:

       1011

    ⊕  1101

    =  0110

    逐位解释:

    A B A⊕B

    1 1 0

    0 1 1

    1 0 1

    1 1 0

    ✅ 结果:0110

    小结一下:

    • XOR(异或)相当于“不带进位的二进制加法”;

    • 如果要做完整的加法,就要加上 进位 = A & B 的那一部分。

    想让我带你一起推一推,怎么用 “XOR + AND + 移位” 来在 C 语言里实现一个不用 + 的加法器吗?

    AI4:

    很好~那我们一起来“拆解”加法器的思路吧。

    我们先从逻辑讲起,不直接写代码。

    🧩 第一步:回忆一下我们刚才说的公式

    普通二进制加法可以写成:

    Sum = A ⊕ B          // 不带进位的部分

    Carry = (A & B) << 1  // 进位,往左移一位

    但这个 Carry 可能又会跟 Sum 相加,还会继续产生新的进位。

    所以我们要 重复这个过程,直到进位变成 0。

    💡 第二步:我们可以用循环实现这个过程

    每一轮做的事:

    1. sum = a ⊕ b

    2. carry = (a & b) << 1

    3. 把 a = sum, b = carry

    4. 继续直到 carry == 0

    🔧 第三步:用 C 语言写出来

    int add(int a, int b) {

        while (b != 0) {

            int sum = a ^ b;       // 不带进位的和

            int carry = (a & b) << 1; // 计算进位

            a = sum;               // 更新a

            b = carry;             // 更新b

        }

        return a;

    }

    这个算法完全没用 + 或 -,却能实现加法。

    现在我想考考你 😏

    假如我们输入 add(5, 3),

    你能试着写出每一轮循环里 a、b、sum、carry 的变化吗?(我们可以先看前两轮)

    AI5:

    没关系~这个地方第一次接触确实有点绕,我们慢慢拆开。

    我们先换个思路:别看代码,先把**“加法的过程”**想象成两步循环发生的事。

    🧠 想象一下你要算 5 + 3

    先写成二进制:

    5 = 0101  

    3 = 0011

    第一步:XOR,不带进位加法

    我们先做 “不带进位”的加法(XOR):

    0101

    ⊕ 0011

    = 0110   ← 这就是 6

    但因为有些地方 “1+1” 了(会产生进位),我们还得算出进位

    第二步:AND,然后左移一位

    计算哪些位置产生进位:

    0101

    & 0011

    = 0001

    进位要往左移动一位(因为二进制加法中进位加到下一列):

    0001 << 1 = 0010

    这代表进位是 2。

    第三步:再加一次

    现在我们要把

    不带进位的结果 0110 (6)

    再加上进位 0010 (2)

    所以我们又回到第一步——再做一次 XOR + AND。

    看到这个循环了吗?

    每次做:

    1. 不带进位加法(XOR)

    2. 算出新的进位(AND << 1)

    3. 如果进位不为 0,就再来一轮。

    最后进位变成 0,结果就是最终的和。

    这样解释有点清楚了吗?

    要不要我帮你把上面的例子(5 + 3)一步步继续算完,让你看到循环怎么结束的?

    AI6:

    很好,我们就把刚才那个例子(5 + 3)一步一步完整推到底 💪

    🧩 起始值:

    a = 0101 (5)

    b = 0011 (3)

    🌀 第 1 轮循环

    1️⃣ 不带进位加法(XOR)

    0101

    ⊕ 0011

    = 0110   → sum = 6

    2️⃣ 计算进位(AND,然后左移)

    0101

    & 0011

    = 0001

    0001 << 1 = 0010   → carry = 2

    更新 a、b:

    a = 0110 (6)

    b = 0010 (2)

    🌀 第 2 轮循环

    1️⃣ XOR

    0110

    ⊕ 0010

    = 0100   → sum = 4

    2️⃣ AND + 左移

    0110

    & 0010 = 0010

    0010 << 1 = 0100   → carry = 4

    更新 a、b:

    a = 0100 (4)

    b = 0100 (4)

    🌀 第 3 轮循环

    1️⃣ XOR

    0100

    ⊕ 0100

    = 0000   → sum = 0

    2️⃣ AND + 左移

    0100

    & 0100 = 0100

    0100 << 1 = 1000   → carry = 8

    更新 a、b:

    a = 0000

    b = 1000

    🌀 第 4 轮循环

    1️⃣ XOR

    0000

    ⊕ 1000

    = 1000   → sum = 8

    2️⃣ AND + 左移

    0000

    & 1000 = 0000

    0000 << 1 = 0000   → carry = 0

    这时 b = 0,循环结束 ✅

    🎯 最终结果

    a = 1000(二进制)

    = 8(十进制)

    所以 add(5, 3) 的结果就是 8

    你能看到:

    • 每一轮 “不带进位加法” 用 XOR;

    • 每一轮 “进位” 用 AND 然后左移;

    • 不断重复,直到进位没了。

    要不要我帮你画一张小图,把这个「XOR + AND + 循环」的逻辑画成流程图?那样会更容易记。

    AI7:

    你的结论非常准确!**异或(XOR)运算确实是找到“数组中出现奇数次数字”的高效方法**,尤其适用于“只有1个或2个数字出现奇数次,其余数字均出现偶数次”的场景。其核心优势在于**时间复杂度O(n)、空间复杂度O(1)**(无需额外存储大量数据),远优于哈希表等需要额外空间的方法。

    要理解为什么XOR能做到这一点,首先需要掌握XOR运算的4条核心性质,这是整个逻辑的基础:

    ### 一、XOR运算的核心性质

    异或运算用符号“^”表示,运算规则是“**相同为0,不同为1**”(二进制层面),但从实用角度,更关键的是其推导出来的4条规律:

    1. **交换律**:`a ^ b = b ^ a`(顺序不影响结果)  

    2. **结合律**:`(a ^ b) ^ c = a ^ (b ^ c)`(分组不影响结果)  

    3. **自身异或为0**:`a ^ a = 0`(一个数和自己异或,结果为0)  

    4. **与0异或为自身**:`a ^ 0 = a`(一个数和0异或,结果还是它本身)  

    ### 二、场景1:只有1个数字出现奇数次,其余均为偶数次

    这是最基础的场景,也是XOR应用的核心。

    #### 原理推导:

    假设数组为`[a, b, c, a, b]`,其中`c`出现1次(奇数次),`a`和`b`各出现2次(偶数次)。  

    根据交换律和结合律,我们可以将“相同数字”优先分组异或:  

    `(a ^ a) ^ (b ^ b) ^ c`  

    根据“自身异或为0”,`a^a=0`,`b^b=0`,代入后得到:  

    `0 ^ 0 ^ c`  

    再根据“与0异或为自身”,`0^0=0`,最终结果为:  

    `0 ^ c = c`  

    可见,**所有出现偶数次的数字会通过异或“抵消”为0,最终剩下的就是出现奇数次的数字**。

    #### 实例演示:

    数组`[2, 3, 2, 4, 4, 3, 3]`,其中`3`出现3次(奇数次),其余数字各出现2次(偶数次)。  

    计算过程:  

    `2 ^ 3 ^ 2 ^ 4 ^ 4 ^ 3 ^ 3`  

    = `(2^2) ^ (3^3^3) ^ (4^4)`  

    = `0 ^ (0 ^ 3) ^ 0`  

    = `0 ^ 3 ^ 0`  

    = `3`  

    最终结果恰好是出现奇数次的`3`。

    ### 三、场景2:有2个数字出现奇数次,其余均为偶数次

    如果数组中有2个数字(设为`x`和`y`)出现奇数次,其余均为偶数次,XOR同样能解决,但需要多一步“分组拆分”。

    #### 原理推导:

    1. **第一步:得到x^y**  

       对整个数组异或,所有出现偶数次的数字会抵消为0,最终结果为:`0 ^ x ^ y = x ^ y`(记为`res = x ^ y`)。  

       由于`x ≠ y`(若x=y则出现次数为偶数,矛盾),`res`的二进制中**至少有1位是1**(这是关键:说明x和y在这一位上的二进制值不同)。

    2. **第二步:找到“区分位”,拆分数组**  

       找到`res`中任意一个为1的二进制位(比如最低位的1,记为`mask`)。  

       用`mask`对数组中所有数字进行“与运算”:  

       – 数字与`mask`的结果为1:放入组A(这类数字在“区分位”上为1)  

       – 数字与`mask`的结果为0:放入组B(这类数字在“区分位”上为0)  

       由于`mask`是`x^y`的为1的位,`x`和`y`会被分到不同的组(一个在A,一个在B),且**每组内只有1个数字出现奇数次**(其余均为偶数次)。

    3. **第三步:分别求x和y**  

       对组A所有数字异或,得到其中出现奇数次的数字(比如`x`);  

       对组B所有数字异或,得到另一个出现奇数次的数字(比如`y`)。

    #### 实例演示:

    数组`[1, 2, 3, 2, 3, 4, 4, 5]`,其中`1`和`5`各出现1次(奇数次),其余数字各出现2次(偶数次)。  

    1. 全数组异或:`1^2^3^2^3^4^4^5 = 1^5 = 4`(二进制`100`,即`res=4`)。  

    2. 找区分位:`res=4`的最低为1的位是第3位(二进制`100`),`mask=4`。  

    3. 分组:  

       – 组A(数字&4=1):`4`(4&4=4≠1?重新算:数组中数字是1(001)、2(010)、3(011)、4(100)、5(101)。mask=4(100),则:  

         – 1&4=0 → 组B  

         – 2&4=0 → 组B  

         – 3&4=0 → 组B  

         – 4&4=4 → 组A  

         – 5&4=4 → 组A  

       最终组A:`[4,4,5]`(5出现1次奇数次),组B:`[1,2,3,2,3]`(1出现1次奇数次)。  

    4. 分组异或:  

       – 组A异或:`4^4^5=5`  

       – 组B异或:`1^2^3^2^3=1`  

       得到两个出现奇数次的数字`1`和`5`。

    ### 四、注意事项

    XOR的这个用法有一个严格前提:**除了目标数字(1个或2个),其余所有数字必须出现偶数次**。如果有3个及以上数字出现奇数次,单纯用XOR无法直接找到所有目标(需要结合更复杂的逻辑,如哈希表统计次数)。

    综上,XOR利用其独特的运算性质,实现了“零额外空间找奇数次数字”的高效解法,是算法面试中非常经典的考点。

    AI8-1:

    很好 👍 那我们就从「为什么要有补码」讲起。

    在计算机里,加法和减法最好都能用“加法器”来完成,因为电路实现加法比减法简单。

    但问题是:

    如果我们用“原码”表示负数,比如:

    十进制 原码(二进制 8 位)

    +5 0000 0101

    -5 1000 0101

    你会发现:

    (+5) + (-5) = 0000 0101 + 1000 0101 = 1000 1010 → 这并不是 0!

    所以原码不能直接用于运算。

    于是就有了一个聪明的办法——补码(Two’s Complement)

    它的思路是:

    让“负数”变成一种特殊的正数形式,这样加法电路就能处理它。

    计算规则(以 8 位为例):

    1️⃣ 正数的补码 = 原码(不变)

    2️⃣ 负数的补码 = 先取反(把 1→0,0→1),再 +1

    例如:

    -5 的原码是 1000 0101

    → 取反:0111 1010

    → 加 1:0111 1011

    所以 -5 的补码是 1111 1011(注意最高位 1 表示负号)。

    当你用补码运算时:

    (+5) + (-5)

    = 0000 0101 + 1111 1011 = 1 0000 0000

    去掉溢出的最高位 → 0000 0000 ✅(结果正确!)

    我想让你试一下:

    👉 请你帮我算算,在 8 位系统下,-3 的补码是什么?

    (记得:先取反,再加 1)

    AI8-2:

    非常好 👏,你算得完全正确!

    我们来验证一下你的过程:

    • 原码(-3):1000 0011

    • 取反:0111 1100

    • 加 1:0111 1101

    → 所以补码是 1111 1101 ✅

    现在你已经理解了补码的生成方式。

    我们再往下一步走一点:

    你觉得用补码来表示负数,有什么好处呢?

    (提示:它跟加法、减法、电路设计的关系很大~)

    AI8-3:

    完全正确!👏👏

    你已经抓到补码最关键的两个优点了:

    1️⃣ 简化电路设计

    因为加法和减法都可以用同一个“加法器”完成。

    举个例子:

    想算 5 – 3,其实就是 5 + (-3)。

    如果 -3 用补码表示,就能直接丢进加法器里计算。

    2️⃣ 避免算错数(特别是符号位的问题)

    在原码或反码下,符号位要单独处理,容易出错;

    但补码让符号位“融入”了数值部分,整体运算更自然。

    来个小练习巩固一下?

    👉 如果是 8 位系统,1111 0110 这个补码代表的是十进制多少?

    (提示:你可以先判断它是正数还是负数,再反推出原码。)

    //5 – 6 – 4 reverse relied on each bit(bitwise NOT)

    #include <iostream>

    using namespace std;

    /*

    ~  reverse relied on each bit’s symbol(bitwise NOT)

    this is an unary operator(单目运算符)

    */

    int main() {

    //1.the unary operator’s (reverse relied on each bit’s symbol) definition

    int a = 0b00000000000000000000000000000001;

    int b = 0b11111111111111111111111111111110; //four 8 bits number

    cout << (~a) << endl; //-2

    cout << b << endl; //-2

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

    //2.Two’s Complement(补码)

    //if 0b00000000000000000000000000000000 =  0

    //and if 0b11111111111111111111111111111111 = -1

    //then minus 1  0b11111111111111111111111111111110 = -2

    int c = 0b0;

    cout << (~c) << endl; // 32 bits’ 1 will output -1

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

    //3.get the reverse number

    int d = 18;

    cout << (~d + 1) << endl;//-18

    //原码、反码、补码、移码

    //1’s ~ (bitwise NOT) is -2

    //0’s ~ (bitwise NOT) is -1

    return 0;

    }

    AI8-3:

    在计算机中,0b0(二进制0)按位取反后结果为-1,这是由**按位取反的运算规则**和**补码表示法**共同决定的,核心原因在于计算机用补码表示有符号整数。

    ### 具体分析步骤:

    #### 1. 明确“0b0”的二进制完整形式

    在计算机中,整数通常以固定位数(如32位、64位)的二进制存储。例如,32位系统中,`0b0`(十进制0)的二进制是**32个0**:  

    `00000000 00000000 00000000 00000000`  

    #### 2. 按位取反(~)的运算规则

    按位取反会将二进制的每一位都翻转(0变1,1变0)。因此,32位的0取反后,所有0都会变成1,结果是**32个1**:  

    `11111111 11111111 11111111 11111111`  

    #### 3. 补码中“全1”表示-1

    计算机中,有符号整数用**补码**表示,规则是:  

    – 正数的补码 = 原码(符号位为0,数值位为本身);  

    – 负数的补码 = 反码 + 1(反码是原码除符号位外取反)。  

    而“全1”的二进制(如32位的`11111111…1111`)恰好是-1的补码:  

    – 推导:假设求-1的32位补码  

      – 1的原码:`00000000…0001`  

      – 1的反码(除符号位外取反):`11111111…1110`(符号位为1,表示负数)  

      – 反码加1得补码:`11111111…1110 + 1 = 11111111…1111`  

    #### 4. 验证逻辑合理性

    从运算逻辑上看,-1的补码“全1”满足补码的核心特性:**负数加其绝对值等于0**。  

    例如:`-1 + 1 = 0`  

    – -1的补码:`11111111…1111`  

    – 1的补码:`00000000…0001`  

    – 相加结果:`11111111…1111 + 00000000…0001 = 100000000…0000`(溢出后截断为32位,结果为`00000000…0000`,即0),完全符合逻辑。  

    ### 结论

    0b0按位取反后得到“全1”的二进制,而在补码表示法中,“全1”恰好是-1的编码,因此结果为-1。这是计算机为了统一加减法运算(将减法转化为加法)而设计的补码规则导致的必然结果。

    AI9-1:

    为什么 这样 //3.get the reverse number

    int d = 18;

    cout << (~d + 1) << endl;//-18

    这段代码的输出结果为`-18`,核心原因是:**`~d + 1` 是计算机中求一个整数相反数(负数)的标准操作**,这是由补码的运算规则决定的。

    ### 具体分析步骤:

    #### 1. 明确 `d = 18` 的二进制补码

    在C++中,`int` 通常为32位有符号整数,用补码表示。  

    18的二进制原码(正数的补码与原码相同)为:  

    `00000000 00000000 00000000 00010010`  

    #### 2. 按位取反 `~d` 的结果

    `~` 是按位取反运算符,将二进制每一位翻转(0变1,1变0)。  

    对18的补码取反后得到:  

    `11111111 11111111 11111111 11101101`  

    #### 3. 加1操作 `~d + 1` 的结果

    取反后加1,得到:  

    `11111111 11111111 11111111 11101101 + 1 = 11111111 11111111 11111111 11101110`  

    #### 4. 该结果是 `-18` 的补码

    在补码规则中,负数的补码定义为“其绝对值的反码 + 1”。  

    – 18的绝对值是18,其原码为 `00000000…00010010`  

    – 反码为 `11111111…11101101`(除符号位外取反)  

    – 反码加1后正是 `11111111…11101110`,即 `-18` 的补码。  

    ### 结论

    `~d + 1` 本质是**通过补码运算求 `d` 的相反数**。对于任意整数 `d`,`~d + 1` 的结果恒等于 `-d`,因此当 `d = 18` 时,输出自然为 `-18`。这是计算机中利用补码统一加减法运算的典型应用。

    AI9-2:

    哦,你这里用的这一行代码,其实就是在用 补码的原理 来求负数。我们慢慢拆开来看:

    int d = 18;

    cout << (~d + 1) << endl;  // 输出 -18

    1️⃣ ~d 是什么?

    • ~ 是 按位取反(bitwise NOT)

    • 18 的二进制(32 位 int 为例):

    00000000 00000000 00000000 00010010

    • 取反后:

    11111111 11111111 11111111 11101101

    这就是 18 的反码

    2️⃣ +1 是为什么?

    • 在补码表示中,负数 = 取反 +1

    • 所以:

    ~18 + 1

    = 11111111 11111111 11111111 11101101 + 1

    = 11111111 11111111 11111111 11101110

    • 这就是 -18 的补码,所以输出就是 -18。

    🔑 核心理解

    • ~d + 1 就是 求 d 的补码负数

    • 换句话说,这种方式可以把一个正数变成负数

    如果你愿意,我可以画一张 正数 → 取反 → +1 → 负数补码 的流程图,让你一眼就看懂为什么会是 -18。

    AI10:

    好呀,我们把这些概念理一理,它们都是计算机表示有符号数的方法,但用法和原理不同。我们用 8 位为例子来讲。

    1️⃣原码(Sign-Magnitude

    规则:最高位是符号位,0 表示正,1 表示负,其余位表示数值大小。

    例子

    • +5 → 0000 0101

    • -5 → 1000 0101

    问题:加减法电路麻烦,需要特殊处理符号位。

    2️⃣反码(Ones’ Complement

    规则

    • 正数:与原码相同

    • 负数:原码符号位不变,其余位取反

    例子

    • +5 → 0000 0101

    • -5 → 1111 1010

    特点:减法可以用加法器做,但有“0 的两种表示”(0000 0000 和 1111 1111)。

    3️⃣补码(Two’s Complement

    规则

    • 正数:原码不变

    • 负数:取反 +1

    例子

    • +5 → 0000 0101

    • -5 → 1111 1011

    特点

    • 加减法都可以直接用加法器

    • 只有一种 0 表示

    • 最高位仍表示符号(1 负,0 正)

    4️⃣移码(Biased / Excess-K

    规则:把原码加上一个偏移值(bias),通常 bias = 2^(n-1),使所有数都是正数

    用途:浮点数指数部分常用移码

    例子(8 位,bias = 128):

    • 真值 0 → 1000 0000

    • 真值 -5 → 1000 0000 – 5 = 0111 1011

    • 真值 +5 → 1000 0000 + 5 = 1000 0101

    如果你愿意,我可以画一张图表把原码、反码、补码、移码对比起来,一眼就能看懂它们的关系。

    你想要我画吗?