当前位置: 首页 > news >正文

C语言:深入了解指针1

内存和地址

1. 酒店房间类比内存和地址

场景描述

把计算机的内存想象成一家酒店,每个房间就是一个内存单元,每个房间都有一个唯一的房间号,这个房间号就相当于内存地址。房间里可以存放客人的行李等物品,这些物品就好比存储在内存中的数据。

对应到 C 语言代码
#include <stdio.h>int main() 
{int num = 10;int *ptr = &num;printf("变量 num 存储的内容(相当于房间里的物品): %d\n", num);printf("变量 num 的地址(相当于房间号): %p\n", (void *)&num);printf("指针 ptr 存储的地址(相当于记录了房间号的纸条): %p\n", (void *)ptr);printf("通过指针 ptr 访问存储的内容(相当于根据纸条找到房间并查看里面的物品): %d\n", *ptr);return 0;
}
解释
  • 房间(内存单元):房间里放着一个行李箱,行李箱上写着 “10”,代表变量 num 的值。
  • 房间号(内存地址)&num 就是获取 num 变量所在的内存地址,就如同获取酒店房间的房间号(例如 621)。
  • 记录房间号的纸条(指针):指针 ptr 存储了 num 的地址,类似于一张记录了房间号的纸条。我们可以通过这张纸条(指针)找到对应的房间(内存单元)并查看或修改里面的物品(数据)。

  • 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如何⾼效的管理呢?
  • 其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
    计算机中常⻅的单位(补充):⼀个⽐特位可以存储⼀个2进制的位1或者0
    计算机中的单位:
    bit - 比特位       
    Byte -字节       1 Byte = 8  bit
    KB   -千字节      1 KB = 1024 Byte
    MB   - 兆字节     1 MB = 1024 KB
    GB   - 吉字节     1 GB = 1024 MB
    TB   - 太字节     1 TB = 1024 GB
    ....         ..............
    其中,每个内存单元,相当于⼀个酒店房间,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐酒店里面放的行李(规定)一样,每个行李是⼀个⽐特位。每个内存单元也都有⼀个编号(这个编号就相当于酒店房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫: 指针
                                        内存单元的编号 == 地址 == 指针

2. 指针变量和地址

     2.1 取地址操作符(&)和解引⽤操作符(*)
int main()
{int a = 4;&a;// & -- 取地址操作符,拿到变量a的地址 printf("%p\n", &a);//%p是专门打印地址int* pa = &a;//pa是一个变量,这个变量pa是用来存放地址(指针)的,pa叫指针变量*pa = 200; * -- 解引用操作符(间接访问操作符)// int* 是pa的类型   ,pa是指针变量的名字// *表示pa是指针变量//int表示pa指向的变量a的类型是intreturn 0;
} 

3. 指针变量的⼤⼩

//                          32位平台下地址是32个bit位(即4个字节)
//                          64位平台下地址是64个bit位(即8个字节)
//     注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
int main()
{  //在64位平台下地址是64个bit位(即8个字节)printf("%zd\n", sizeof(char*));  //8printf("%zd\n", sizeof(int*));   //8printf("%zd\n", sizeof(short*)); //8printf("%zd\n", sizeof(double*));//8return 0;}

3.1 指针的解引⽤

/* 调试我们可以看到,代码1 会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。*///代码1:
int main()
{int a = 0x12435644;//十六机进制每二位为一个字节12 43 56 44int* pc = &a;*pc = 0;//每次访问四个字节,所以一次性改了四个字节return 0;
}
//代码2:
int main()
{int a = 0x12435644;char* pc = &a;*pc = 0;//每次访问一个字节,所以一次性改了一个字节return 0;
}

3.2 指针+-整数

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
int main()
{int a = 20;int* pa = &a;char* pc = &a;printf("  &a = %p\n", &a);       //&a = 00BAFC28printf("  pa = %p\n", pa);       //pa = 00BAFC28printf("  pc = %p\n", pc);       //pc = 00BAFC28printf("&a+1 = %p\n", &a + 1); //&a+1 = 00BAFC2C    跟&a相差4个字节(多)printf("pa+1 = %p\n", pa + 1); //pa+1 = 00BAFC2C    跟pa相差4个字节(多)printf("pc+1 = %p\n", pc + 1); //pc+1 = 00BAFC29    跟pc相差1个字节(多)printf("&a-1 = %p\n", &a-1);   //&a-1 = 00BAFC24    跟&a相差4个字节(少)printf("pa-1 = %p\n", pa-1);   //pa-1 = 00BAFC24    跟&a相差4个字节(少)printf("pc-1 = %p\n", pc-1);   //pc-1 = 00BAFC27    跟&a相差1个字节(少)return 0;
}
                 结论 :指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

                int* pa;             pa+1  -->  +1*sizeof(int)
                                        pa+n  -->  +n*sizeof(int)
 

               char* pa;           pa+1  -->  +1*sizeof(char)
                                        pa+n  -->   +n*sizeof(char)

总结

看了这么多了是不是还是有一点不懂?那我在这里跟你们举个例子:

int a =20;

int* pa = &a;

pa ,*pa ,&pa这三个有什么区别?

 

1. pa

含义

pa 是一个指针变量,它存储的是另一个变量的内存地址。在上述代码中,pa 存储的是变量 a 的内存地址,即 pa 指向变量 a

用法
  • 作为地址传递:可以将 pa 作为参数传递给函数,让函数能够通过这个地址访问或修改所指向的变量。例如:
    #include <stdio.h>void modifyValue(int *ptr) //传递过来的是地址所以必须得要指针来接
    {*ptr = 100;
    }int main() {int a = 20;int b = 100int *pa = &a;pa = &b;//pa换了一个地址,这时pa存储的就是b的地址modifyValue(pa);//pa本身就是地址所以不用’&‘printf("a 的值: %d\n", a);  // 输出 100return 0;
    }

    2. *pa

  • 含义

    * 是解引用运算符,*pa 表示访问 pa 所指向的内存地址中存储的值。在上述代码中,*pa 访问的就是变量 a 的值。

    用法
  • 读取所指变量的值:可以通过 *pa 获取 pa 所指向变量的值。例如:
    #include <stdio.h>int main(){int a = 20;int *pa = &a;printf("*pa 的值: %d\n", *pa);  // 输出 20return 0;
    }
  • 修改所指变量的值:可以对 *pa 进行赋值操作,从而修改 pa 所指向变量的值。例如:
    #include <stdio.h>int main() {int a = 20;int *pa = &a;*pa = 50;printf("a 的值: %d\n", a);  // 输出 50return 0;
    }

    3. &pa

    含义

    & 是取地址运算符,&pa 表示获取指针变量 pa 本身自己的内存地址。也就是说,&pa 是指向指针 pa 的指针。

    用法
  • 二级指针:可以将 &pa 赋值给一个二级指针(指向指针的指针)。例如:
    #include <stdio.h>int main(){int a = 20;int *pa = &a;int **ppa = &pa;printf("通过二级指针访问 a 的值: %d\n", **ppa);  // 输出 20return 0;
    }

    在上述代码中,ppa 是一个二级指针,它存储的是指针 pa 的地址,通过两次解引用 **ppa 可以访问到变量 a 的值。

     

    综上所述,pa 是指针,存储的是地址;*pa 是解引用操作,用于访问指针所指向的值;&pa 是取指针本身的地址,可用于创建二级指针等操作。 

3.3 void* 指针

void* 指针是一种可以指向任何类型数据的指针。它不指定所指向数据的具体类型,只是表示一个内存地址。例如,可以定义一个  void* 指针如下:
void* ptr;

 

特点
  • 通用性:可以指向任意类型的数据,包括基本数据类型(如 intfloat 等)和自定义数据类型(如结构体、联合体等)。这使得 void* 指针在一些通用的数据处理函数或数据结构中非常有用,能够实现对不同类型数据的统一操作。
  • 类型无关性:由于 void* 指针不指定具体的数据类型,所以在使用它进行指针运算或访问所指向的数据时,需要进行显式的类型转换。因为编译器无法根据 void* 指针确定所指向数据的大小和类型,所以不能直接通过 void* 指针进行解引用操作来访问数据。
常见用途
  • 函数参数:在函数参数中使用 void* 指针,可以使函数能够接受任意类型的数据指针作为参数,从而实现函数的通用性。例如,C 标准库中的 memcpy 函数就是使用 void* 指针来实现对任意类型数据的内存复制操作,其函数原型为 void* memcpy(void* dest, const void* src, size_t n);,可以将 src 指向的内存区域中的 n 个字节复制到 dest 指向的内存区域中。
  • 数据结构:在一些数据结构的实现中,void* 指针可以用来存储不同类型的数据。例如,链表节点可以使用 void* 指针来存储节点的数据,这样链表就可以存储任意类型的数据。
注意事项
  • 类型转换:在使用 void* 指针访问数据或进行指针运算时,必须进行显式的类型转换,将 void* 指针转换为正确的数据类型指针,否则可能会导致错误的访问或运算结果。
  • 内存管理:由于 void* 指针可以指向任意类型的数据,在进行内存分配和释放时要特别小心,确保正确地分配和释放内存,避免内存泄漏或悬空指针等问题。

下面是一个简单的示例代码,演示了 void* 指针的基本用法:

#include <stdio.h>
#include <stdlib.h>// 函数接受void*指针作为参数,并根据传入的类型进行相应的操作
void printData(void* data, char type) 
{if (type == 'i') 
{// 将void*指针转换为int*指针,然后解引用输出int* intPtr = (int*)data;printf("Integer value: %d\n", *intPtr);}else if (type == 'f') 
{// 将void*指针转换为float*指针,然后解引用输出float* floatPtr = (float*)data;printf("Float value: %f\n", *floatPtr);}
}int main(){int intValue = 10;float floatValue = 3.14f;// 使用void*指针指向int类型数据void* voidPtr1 = &intValue;printData(voidPtr1, 'i');//i:代表 int// 使用void*指针指向float类型数据void* voidPtr2 = &floatValue;printData(voidPtr2, 'f');//f:代表 floatreturn 0;
}

在上述代码中,printData 函数接受一个 void* 指针和一个字符类型的参数 type,根据 type 的值将 void* 指针转换为相应类型的指针,并输出数据。在 main 函数中,分别使用 void* 指针指向一个 int 类型数据和一个 float 类型数据,并调用 printData 函数进行输出。

4. const 修饰指针(被修饰的变量,并不是变成常量,而是本质还是变量,只是不能被修改罢了

指向常量的指针(const type* 或 type const*

  • 定义及特点:这种指针指向的内容是常量,不能通过该指针去修改所指向的数据,但指针本身可以指向其他地址。例如:

const int* p;

int const* p; // 与上面的定义等价

  • 示例
    #include <stdio.h>int main() {int num = 10;const int* p = &num;//因为*p被修饰限制了,但p并没有// *p = 20;  // 错误,不能通过指向常量的指针修改数据int anotherNum = 30;p = &anotherNum;  // 正确,指针本身可以指向其他地址return 0;
    }

    常量指针(type* const

  • 定义及特点:常量指针本身的值是一个常量,即它所指向的地址不能被改变,但可以通过该指针去修改所指向的数据。例如:

 int* const p;

这里定义了一个 int 类型的常量指针 p,在初始化后,p 就只能指向它初始化时所指向的那个地址,不能再指向其他地址,但可以通过 p 来修改其所指向的 int 类型数据的值。 

#include <stdio.h>int main() 
{int num1 = 10;int num2 = 20;int* const p = &num1;*p = 30;  // 正确,可以通过常量指针修改所指向的数据// p = &num2;  // 错误,常量指针不能改变指向return 0;
}

指向常量的常量指针(const type* const

  • 定义及特点:这种指针既不能改变指针本身所指向的地址,也不能通过该指针去修改所指向的数据。例如:

const int* const ptr;

  • 示例
    #include <stdio.h>int main() 
    {int num = 10;const int* const ptr = &num;//这里p和p*都被限制// *p = 20;  // 错误,不能通过指针修改数据// p = &anotherNum;  // 错误,指针不能改变指向return 0;
    }

    5. 指针运算

指针的基本运算有三种,分别是:
  • 指针+- 整数
  • 指针-指针
  • 指针的关系运算

    1. 指针 ± 整数

    运算规则:当一个指针加上或减去一个整数时,实际上是在内存中向前或向后移动了若干个所指向数据类型大小的位置。移动的字节数等于整数乘以指针所指向数据类型的字节数。

    示例代码

    #include <stdio.h>int main() 
    {int arr[5] = {1, 2, 3, 4, 5};int *ptr = arr;  // 指针ptr指向数组arr的首元素// 指针加整数int *ptr1 = ptr + 2;  // 指针向后移动2个int类型的位置printf("ptr1指向的值: %d\n", *ptr1);  // 输出3// 指针减整数int *ptr2 = ptr1 - 1;  // 指针向前移动1个int类型的位置printf("ptr2指向的值: %d\n", *ptr2);  // 输出2return 0;
    }

    代码解释:在上述代码中,int 类型通常占 4 个字节。ptr + 2 表示指针向后移动 2 * sizeof(int) 个字节,即指向数组中的第 3 个元素;ptr1 - 1 表示指针向前移动 1 * sizeof(int) 个字节,即指向数组中的第 2 个元素。

    2. 指针 - 指针(相加其实没有实际用途,比较少)


    运算规则只有当两个指针指向同一个数组中的元素时,才能进行指针相减运算。指针相减的结果是两个指针之间相隔的元素个数,其类型为 ptrdiff_t(在 <stddef.h> 头文件中定义)。 示例代码

    #include <stdio.h>
    #include <stddef.h>int main() 
    {int arr[5] = {1, 2, 3, 4, 5};int *ptr1 = &arr[0];int *ptr2 = &arr[3];ptrdiff_t diff = ptr2 - ptr1;  // 计算两个指针之间相隔的元素个数printf("ptr2和ptr1之间相隔的元素个数: %td\n", diff);  // 输出3return 0;
    }

    代码解释ptr2 指向数组的第 4 个元素,ptr1 指向数组的第 1 个元素,它们之间相隔 3 个元素,所以 ptr2 - ptr1 的结果为 3。

    3. 指针的关系运算


    运算规则:指针的关系运算包括 <><=>=== 和 != 等。这些运算通常用于比较两个指针是否指向同一个位置,或者判断一个指针是否在另一个指针之前或之后。同样,只有当两个指针指向同一个数组中的元素时,这些关系运算才有意义。

    示例代码

    #include <stdio.h>int main() 
    {int arr[5] = {1, 2, 3, 4, 5};int *ptr1 = &arr[0];int *ptr2 = &arr[3];if (ptr1 < ptr2) {printf("ptr1在ptr2之前\n");} else {printf("ptr1不在ptr2之前\n");}if (ptr1 != ptr2) {printf("ptr1和ptr2不指向同一个位置\n");}else {printf("ptr1和ptr2指向同一个位置\n");}return 0;
    }

    代码解释:在上述代码中,由于 ptr1 指向数组的第 1 个元素,ptr2 指向数组的第 4 个元素,所以 ptr1 < ptr2 条件成立,ptr1 != ptr2 条件也成立。

    6. 野指针

    定义

    野指针是指指向一个无效的、未分配或者已经释放的内存地址的指针。这种指针没有明确指向一个合法的内存区域,对其进行解引用操作(即访问其所指向的内存内容)会产生不可预期的结果。

    产生原因

    1. 指针未初始化

    当定义一个指针变量但没有对其进行初始化时,该指针会包含一个随机的内存地址,这个地址可能并不指向任何有效的内存区域,从而成为野指针。

    #include <stdio.h>int main(){int *ptr;  // 定义指针但未初始化// *ptr = 10;  // 错误:对野指针进行解引用操作return 0;
    }
    2. 指针所指向的内存被释放后未置为 NULL

    当使用 malloc()calloc() 或 realloc() 等函数动态分配内存,并使用指针指向这块内存后,如果后续使用 free() 函数释放了该内存,但没有将指针置为 NULL,那么这个指针就会变成悬空指针也被称为野指针。

    #include <stdio.h>
    #include <stdlib.h>int main() 
    {int *ptr = (int *)malloc(sizeof(int));//申请动态内存if (ptr == NULL){return 1;}*ptr = 10;free(ptr);  // 释放内存// ptr 现在是野指针   // *ptr = 20;  // 错误:对野指针进行解引用操作return 0;
    }
    3. 指针越界访问

    当指针指向数组元素时,如果对指针进行了超出数组范围的操作,使得指针指向了数组之外的内存区域,那么该指针就可能成为野指针。

    #include <stdio.h>int main() 
    {int arr[5] = {1, 2, 3, 4, 5};int *ptr = arr;ptr = ptr + 10;  // 指针越界,指向了数组之外的内存区域// *ptr = 10;  // 错误:对野指针进行解引用操作return 0;
    }
    4.局部变量超出作用域
     

    当一个指针指向一个局部变量时,如果这个局部变量所在的函数执行完毕,局部变量的生命周期结束,其占用的内存会被释放。但此时指针仍然保留着该局部变量的地址,就会成为悬空指针。

    #include <stdio.h>int* add(int x,int y) 
    {int z = x + y;return &z;  // 返回局部变量的地址
    }int main() 
    {int a = 3;int b = 5;int *ptr = add(a,b);// 此时ptr成为悬空指针,因为z已经超出作用域// *ptr = 20;  // 错误:对悬空指针进行解引用操作return 0;
    }
     

    危害

    程序崩溃:对野指针进行解引用操作时,可能会访问到操作系统保护的内存区域,从而导致程序崩溃。

    数据损坏:野指针可能指向了其他程序或数据结构正在使用的内存区域,对其进行写操作会破坏这些数据,导致程序出现不可预期的错误。

    避免方法

    1. 初始化指针

    在定义指针变量时,尽量对其进行初始化。如果暂时不知道要指向哪里,可以将其初始化为 NULL

    #include <stdio.h>int main() 
    {int *ptr = NULL;  // 初始化指针为NULLint num = 10;ptr = &num;  // 让指针指向有效的内存地址*ptr = 20;printf("%d\n", *ptr);return 0;
    }
    2. 释放内存后将指针置为 NULL
     

    在使用 free() 函数释放动态分配的内存后,及时将指针置为 NULL,避免再次使用该指针时产生错误。

    #include <stdio.h>
    #include <stdlib.h>int main() 
    {int *ptr = (int *)malloc(sizeof(int));//申请动态空间if (ptr == NULL) {return 1;}*ptr = 10;free(ptr);ptr = NULL;  // 将指针置为NULL// 此时再使用ptr就可以先判断是否为NULLif (ptr != NULL) {*ptr = 20;}return 0;
    }
    3. 避免指针越界访问
     

    在使用指针访问数组元素时,要确保指针不会超出数组的有效范围。可以通过合理的边界检查来避免指针越界。

    #include <stdio.h>int main() 
    {int arr[5] = {1, 2, 3, 4, 5};int *ptr = arr;for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i));  // 确保指针在数组有效范围内}return 0;
    }

    7. assert 断⾔

    assert 是一个宏,定义在  <assert.h> 头文件中。其原型如下:
    void assert(int expression);

    这里的 expression 是一个需要检查的条件表达式,如果该表达式的值为 0(即条件为假),则 assert 会触发断言失败,程序会终止运行;如果表达式的值为非 0(即条件为真),则程序会继续正常执行。

    使用方法

    以下是一个简单的使用 assert 的示例代码:

    #include <stdio.h>
    #include <assert.h>// 一个简单的除法函数
    int divide(int a, int b) 
    {// 使用assert检查除数是否为0assert(b != 0);return a / b;
    }int main()
    {int result1 = divide(10, 2);printf("10 / 2 = %d\n", result1);// 这会触发断言失败int result2 = divide(10, 0);printf("10 / 0 = %d\n", result2);return 0;
    }

    在上述代码中,divide 函数使用 assert 检查除数 b 是否为 0。当调用 divide(10, 2) 时,条件 b != 0 为真,程序会正常计算并输出结果;而当调用 divide(10, 0) 时,条件 b != 0 为假,assert 会触发断言失败,程序会终止运行,并输出类似下面的错误信息:

    Assertion failed: b != 0, file test.c, line 6

 

该信息会指出断言失败的条件、所在的文件名和行号,方便开发者定位问题。

工作原理

在程序编译时,assert 宏会被展开为一段代码。当程序运行到 assert 语句时,会先计算传入的条件表达式的值。如果值为 0,则会调用 abort() 函数终止程序,并输出包含断言条件、文件名和行号的错误信息;如果值为非 0,则程序会继续正常执行。

禁用 assert

在发布版本的程序中,为了避免 assert 带来的性能开销,可以通过定义 NDEBUG 宏来禁用 assert。例如:

#define NDEBUG#include <stdio.h>
#include <assert.h>int main() {int num = 10;assert(num == 20);  // 由于NDEBUG被定义,此断言不会生效printf("Program continues...\n");return 0;
}

在上述代码中,由于在包含 <assert.h> 头文件之前定义了 NDEBUG 宏assert 宏会被展开为空代码,不会进行条件检查,从而提高程序的运行效率。

优缺点

优点
  • 方便调试assert 可以在程序运行时自动检查条件,当条件不满足时会立即终止程序并给出详细的错误信息,帮助开发者快速定位和解决问题。
  • 代码简洁:使用 assert 可以在代码中简洁地表达对程序状态的预期,使代码更易读和维护。
缺点
  • 性能开销:在程序的每个 assert 语句处都需要计算条件表达式的值,这会带来一定的性能开销,尤其是在条件检查比较复杂或者程序中有大量 assert 语句时。
  • 仅用于调试:由于 assert 可以通过定义 NDEBUG 宏来禁用,因此它只能用于调试阶段,不能用于处理程序运行时可能出现的错误(如用户输入错误、文件打开失败等),这些错误需要使用其他机制(如错误码、异常处理等)来处理。

8. 指针的使⽤和传址调⽤

    8.1 strlen的模拟实现

库函数strlen的功能是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数。
函数原型如下:
size_t strlen ( const char * str );
  • size_t 是无符号整数类型,通常用于表示对象的大小或长度。
  • const char *str 表示传入的是一个指向常量字符的指针,这意味着函数内部不会修改字
实现思路
    参数str接收⼀个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回⻓度。 如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停⽌。
#include <stdio.h>// 模拟实现strlen函数
size_t my_strlen(const char *str) 
{const char *ptr = str;  // 定义一个指针ptr指向字符串的首地址while (*ptr != '\0') // 当指针指向的字符不是字符串结束符时{  ptr++;  // 指针向后移动一位}return ptr - str;  // 计算指针ptr和str之间的差值,即为字符串的长度
}int main() 
{char str[] = "Hello, World!";size_t length = my_strlen(str); //调用自定义的my_strlen函数计算字符串的长度,数组名就是地址printf("字符串 \"%s\" 的长度是: %zu\n", str, length);return 0;
}

函数实现

const char *ptr = str;

while ( *ptr !=  '\0' ) {

   ptr++;

}  

return ptr - str;

 

  • 首先,定义一个指针 ptr 并将其初始化为 str,即指向字符串的首地址。
  • 然后,使用 while 循环遍历字符串,当 ptr 指向的字符不是字符串结束符 '\0' 时,将 ptr 向后移动一位。
  • 最后,计算 ptr 和 str 之间的差值,即为字符串的长度。

传址调用:

size_t length = my_strlen(str);

  • 在 main 函数中,将字符串 str 的首地址传递给 my_strlen 函数,这就是传址调用。通过传址调用,函数可以直接访问和操作原始字符串,而不是创建字符串的副本。

相关文章:

C语言:深入了解指针1

内存和地址 1. 酒店房间类比内存和地址 场景描述 把计算机的内存想象成一家酒店&#xff0c;每个房间就是一个内存单元&#xff0c;每个房间都有一个唯一的房间号&#xff0c;这个房间号就相当于内存地址。房间里可以存放客人的行李等物品&#xff0c;这些物品就好比存储在内…...

【AI】探索自然语言处理(NLP):从基础到前沿技术及代码实践

Hi &#xff01; 云边有个稻草人-CSDN博客 必须有为成功付出代价的决心&#xff0c;然后想办法付出这个代价。 目录 引言 1. 什么是自然语言处理&#xff08;NLP&#xff09;&#xff1f; 2. NLP的基础技术 2.1 词袋模型&#xff08;Bag-of-Words&#xff0c;BoW&#xff…...

游戏引擎 Unity - Unity 下载与安装

Unity Unity 首次发布于 2005 年&#xff0c;属于 Unity Technologies Unity 使用的开发技术有&#xff1a;C# Unity 的适用平台&#xff1a;PC、主机、移动设备、VR / AR、Web 等 Unity 的适用领域&#xff1a;开发中等画质中小型项目 Unity 适合初学者或需要快速上手的开…...

文本复制兼容方案最佳实现落地。

文章目录 一、navigator.clipboard.writeText二、方案落地总结 一、navigator.clipboard.writeText navigator.clipboard.writeText 是一个Web API&#xff0c;它允许网页脚本将文本数据写入用户的系统剪贴板。这个API是异步的&#xff0c;并且设计用于提高安全性和用户体验&a…...

LabVIEW如何高频采集温度数据?

在LabVIEW中进行高频温度数据采集时&#xff0c;选择合适的传感器&#xff08;如热电偶或热电阻&#xff09;和采集硬件是关键。下面是一些建议&#xff0c;帮助实现高效的温度数据采集&#xff1a; 1. 传感器选择&#xff1a; 热电偶&#xff08;Thermocouple&#xff09;&am…...

AI智慧社区--人脸识别

前端 人脸的采集按钮&#xff1a; 首先对于选中未认证的居民记录&#xff0c;进行人脸采集 前端的按钮 <el-form-item><el-button v-has"sys:person:info" type"info" icon"el-icon-camera" :disabled"ids.length < 0" …...

C++11—右值引用

目录 简介 左值和右值 左值 右值 右值引用 生命周期 引用折叠 实际应用 移动语义 移动构造函数 移动赋值运算符 完美转发 简介 之前我们曾学习过引用叫左值引用&#xff0c;但那是C98的&#xff0c;在C11中新增了一种引用叫右值引用。右值引用主要用于支持移动语…...

Workbench 中的热源仿真

探索使用自定义工具对移动热源进行建模及其在不同行业中的应用。 了解热源动力学 对移动热源进行建模为各种工业过程和应用提供了有价值的见解。激光加热和材料加工使用许多激光束来加热、焊接或切割材料。尽管在某些情况下&#xff0c;热源 &#xff08;q&#xff09; 不是通…...

Windows11 不依赖docker搭建 deepseek-R1 1.5B版本(附 Open WebUi搭建方式)

零、前言 过年这几天发现 DeepSeek 非常火&#xff0c;试用了一下发现确实不错。与豆包、kimi、perplexity 这些相比完全不是一个次元的存在&#xff0c;特别是用ta写文章的时候体验非常好。所以试着自己搭一个环境。 一、安装 Ollama和DeepSeek-R1 我的安装方式很简单&#xf…...

Error: Expected a mutable image

你的函数用了不支持的图片格式比如我的人脸检测&#xff0c;本来要RGB565我却用JPEG所以报错...

【4Day创客实践入门教程】Day2 探秘微控制器——单片机与MicroPython初步

Day2 探秘微控制器——单片机与MicroPython初步 目录 Day2 探秘微控制器——单片机与MicroPython初步MicroPython语言基础开始基础语法注释与输出变量模块与函数 单片机基础后记 Day0 创想启程——课程与项目预览Day1 工具箱构建——开发环境的构建Day2 探秘微控制器——单片机…...

代码随想录算法训练营Day51 | 101.孤岛的总面积、102.沉没孤岛、103.水流问题、104.建造最大岛屿

文章目录 101.孤岛的总面积思路与重点 102.沉没孤岛思路与重点 103.水流问题思路与重点 104.建造最大岛屿思路与重点 101.孤岛的总面积 题目链接&#xff1a;101.孤岛的总面积讲解链接&#xff1a;代码随想录状态&#xff1a;直接看题解了。 思路与重点 nextx或者nexty越界了…...

网络基础

协议 协议就是约定 网络协议是协议中的一种 协议分层 协议本身也是软件&#xff0c;在设计上为了更好的模块化&#xff0c;解耦合&#xff0c;也是设计成为层状结构的 两个视角&#xff1a; 小白&#xff1a;同层协议&#xff0c;直接通信 工程师&#xff1a;同层协议&…...

利用Spring Batch简化企业级批处理应用开发

1. 引言 1.1 批处理的重要性 在现代企业系统中,批处理任务用于处理大量数据,如报表生成、数据迁移、日终结算等。这些任务通常不需要实时响应,但需要高效、可靠地完成。批处理可以显著提高系统性能,减少实时系统的负载,并确保数据的完整性和一致性。 1.2 Spring Batch简…...

Python - pyautogui库 模拟鼠标和键盘执行GUI任务

安装库&#xff1a; pip install pyautogui 导入库&#xff1a;import pyautogui 获取屏幕尺寸&#xff1a; s_width, s_height pyautogui.size() 获取鼠标当前位置&#xff1a; x, y pyautogui.position() 移动鼠标到指定位置&#xff08;可以先使用用上一个函数调试获取当…...

UE求职Demo开发日志#19 给物品找图标,实现装备增加属性,背包栏UI显示装备

1 将用到的图标找好&#xff0c;放一起 DataTable里对应好图标 测试一下能正确获取&#xff1a; 2 装备增强属性思路 给FMyItemInfo添加一个枚举变量记录类型&#xff08;物品&#xff0c;道具&#xff0c;装备&#xff0c;饰品&#xff0c;武器&#xff09;--> 扩展DataT…...

【PyQt】lambda函数,实现动态传递参数

为什么需要 lambda&#xff1f; 在 PyQt5 中&#xff0c;clicked 信号默认会传递一个布尔值&#xff08;表示按钮是否被选中&#xff09;。如果我们希望将按钮的文本内容传递给槽函数&#xff0c;需要通过 lambda 函数显式传递参数。 这样可以实现将按钮内容传递给槽函数&…...

Unity 2D实战小游戏开发跳跳鸟 - 跳跳鸟碰撞障碍物逻辑

在有了之前创建的可移动障碍物之后,就可以开始进行跳跳鸟碰撞到障碍物后死亡的逻辑,死亡后会产生一个对应的效果。 跳跳鸟碰撞逻辑 创建Obstacle Tag 首先跳跳鸟在碰撞到障碍物时,我们需要判定碰撞到的是障碍物,可以给障碍物的Prefab预制体添加一个Tag为Obstacle,添加步…...

LeetCode:121.买卖股票的最佳时机1

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;121.买卖股票的最佳时机1 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票…...

DeepSeek-R1 论文. Reinforcement Learning 通过强化学习激励大型语言模型的推理能力

论文链接&#xff1a; [2501.12948] DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 实在太长&#xff0c;自行扔到 Model 里&#xff0c;去翻译去提问吧。 工作原理&#xff1a; 主要技术&#xff0c;就是训练出一些专有用途小模型&…...

SQL索引优化_提高系统响应速度的秘诀

1. 引言 1.1 索引的重要性 在数据库管理系统中,索引是提升查询性能的关键工具。合理的索引设计可以显著减少查询时间,提高系统的响应速度,从而改善用户体验。然而,不恰当的索引使用可能会适得其反,增加写操作的开销并导致性能下降。 1.2 文章目标与结构 本文旨在介绍如…...

MoonBit 编译器(留档学习)

MoonBit 编译器 MoonBit 是一个用户友好&#xff0c;构建快&#xff0c;产出质量高的编程语言。 MoonBit | Documentation | Tour | Core This is the source code repository for MoonBit, a programming language that is user-friendly, builds fast, and produces high q…...

基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)

酒店管理小程序目录 目录 基于微信小程序的酒店管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 (1) 用户信息管理 (2) 酒店管理员管理 (3) 房间信息管理 2、小程序序会员模块的实现 &#xff08;1&#xff09;系统首页 &#xff…...

重生之我在异世界学编程之C语言:深入指针篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文&#xff08;1&#xff09;内置数…...

Unity Shader Graph 2D - 跳动的火焰

在游戏中&#xff0c;火焰是一种常见的特效。通常来讲火焰特效通过粒子系统的方式实现的相对较多&#xff0c;本文将通过Shader Graph的方式来实现一种不同的火焰效果。 那么怎么实现呢 首先创建一个名为Fire的Shader Graph文件&#xff0c;然后创建一个名为M_Fire的材质球。 …...

CoRAG 来自微软与人大的创新RAG框架技术

微软与人大合作开发的CoRAG(Chain-of-Retrieval Augmented Generation)是一种创新的检索增强生成(RAG)框架,旨在通过模拟人类思考方式来提升大语言模型(LLM)在复杂问题上的推理和回答能力。以下是对CoRAG的深度介绍: 1. CoRAG的核心理念 CoRAG的核心思想是通过动态调…...

MySQL的GROUP BY与COUNT()函数的使用问题

在MySQL中&#xff0c;GROUP BY和 COUNT()函数是数据聚合查询中非常重要的工具。正确使用它们可以有效地统计和分析数据。然而&#xff0c;不当的使用可能会导致查询结果不准确或性能低下。本文将详细讨论 GROUP BY和 COUNT()函数的使用方法及常见问题&#xff0c;并提供相应的…...

DS常识问答:人民币升值贬值是什么回事

好的&#xff01;我用一个简单的比喻来解释&#xff0c;就像用苹果换橘子一样&#xff1a; 1. **汇率是什么&#xff1f;** 假设你有人民币&#xff0c;别人有美元。汇率就是你们交换的“价格”。比如今天1美元能换7块人民币&#xff0c;明天可能变成7.5块或者6.5块&…...

docker直接运行arm下的docker

运行环境是树莓派A 处理器是 arm32v6 安装了docker&#xff0c;运行lamp 编译安装php的时候发现要按天来算&#xff0c;于是用电脑vm下的Ubuntu系统运行arm的docker 然后打包到a直接导入运行就可以了 第一种方法 sudo apt install qemu-user-static 导入直接运行就可以了…...

机器学习--概览

一、机器学习基础概念 1. 定义 机器学习&#xff08;Machine Learning, ML&#xff09;&#xff1a;通过算法让计算机从数据中自动学习规律&#xff0c;并利用学习到的模型进行预测或决策&#xff0c;而无需显式编程。 2. 与编程的区别 传统编程机器学习输入&#xff1a;规…...

人机交互系统实验三 多通道用户界面

实验目的和要求 1)了解常见的多通道用户界面 2)查找资料&#xff0c;熟悉一种多通道用户界面并写出综述 实验环境 Windows10 实验内容与过程 (一) 实验内容: 要求上网查找资料&#xff0c;熟悉一种多通道用户界面并写出综述&#xff0c;可以是眼动跟踪、手势识别、 三维…...

Python(Pandas)数据分析学习

1.Pandas基本构成 引入Pandas import pandas as pd 1.Series 行 对应Excel中的一行数据&#xff0c;一维数据 定义Series # 第一个参数是具体数据 # 第二个参数的对应的索引下标 # 第三个参数的行名称 data pd.Series([1,2,3,4,5], index[a,b,c,d,e], namedata) print(d…...

JavaScript系列(53)--内存管理与垃圾回收机制详解

JavaScript内存管理与垃圾回收机制详解 &#x1f9f9; 今天&#xff0c;让我们深入探讨JavaScript的内存管理与垃圾回收机制。理解这些机制对于编写高性能、无内存泄漏的JavaScript应用至关重要。 内存管理基础概念 &#x1f31f; &#x1f4a1; 小知识&#xff1a;JavaScrip…...

每日一题——用两个栈实现队列

用两个栈实现队列 题目描述数据范围示例 代码实现1. 代码思路push 操作&#xff1a;pop 操作&#xff1a; 2. 代码实现3. 代码解析4. 时间复杂度与空间复杂度 总结 题目描述 用两个栈来实现一个队列&#xff0c;使用 n 个元素来完成 n 次在队列尾部插入整数(push)和 n 次在队列…...

DeepSeek与OpenAI:谁是AI领域的更优选择?

在人工智能领域&#xff0c;DeepSeek和OpenAI是两个备受瞩目的玩家。尽管OpenAI凭借其强大的GPT系列模型在全球范围内获得了广泛的认可&#xff0c;但DeepSeek凭借其独特的技术优势和创新理念&#xff0c;正在逐渐成为许多企业和开发者的新选择。本文将对DeepSeek和OpenAI进行详…...

【爬虫】JS逆向解决某药的商品价格加密

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…...

deepseek v3 搭建个人知识库

目录 deepseek-r1本地部署&#xff0c;这个比较好&#xff0c;推荐 Chatbox连接ollama服务 知乎教程&#xff0c;需要注册&#xff1a; deepseek-r1本地部署&#xff0c;这个比较好&#xff0c;推荐 公司数据不泄露&#xff0c;DeepSeek R1本地化部署web端访问个人知识库搭建…...

ETCD集群证书生成

安装cfssl工具配置CA证书请求文件创建CA证书创建CA证书策略配置etcd证书请求文件生成etcd证书 继续上一篇文章《负载均衡器高可用部署》下面介绍一下etcd证书生成配置。其中涉及到的ip地址和证书基本信息请替换成你自己的信息。 安装cfssl工具 下载cfssl安装包 https://github…...

【软件测试项目实战】淘宝网订单管理功能

一、测试功能模块分析 选择淘宝网订单管理功能进行测试&#xff0c;核心子功能包含&#xff1a; 订单创建&#xff08;商品结算、地址选择&#xff09;订单状态变更&#xff08;待付款、已付款、已发货、已收货、退款中&#xff09;订单修改&#xff08;地址修改、商品数量修…...

扩散模型(一)

在生成领域&#xff0c;迄今为止有几个主流的模型&#xff0c;分别是 GAN, VAE&#xff0c;Flow 以及 Diffusion 模型。 GAN&#xff1a;GAN 的学习机制是对抗性学习&#xff0c;通过生成器和判别器的对抗博弈来进行学习&#xff0c;这种竞争机制促使生成器不断提升生成能力&a…...

EF Core与ASP.NET Core的集成

目录 分层项目中EF Core的用法 数据库的配置 数据库迁移 步骤汇总 注意&#xff1a; 批量注册上下文 分层项目中EF Core的用法 创建一个.NET类库项目BooksEFCore&#xff0c;放实体等类。NuGet&#xff1a;Microsoft.EntityFrameworkCore.RelationalBooksEFCore中增加实…...

深入解析“legit”的地道用法——从俚语到正式表达:Sam Altman用来形容DeepSeek: legit invigorating(真的令人振奋)

深入解析“legit”的地道用法——从俚语到正式表达 一、引言 在社交媒体、科技圈甚至日常对话中&#xff0c;我们经常会看到或听到“legit”这个词。比如最近 Sam Altman 在 X&#xff08;原 Twitter&#xff09;上发的一条帖子中写道&#xff1a; we will obviously deliver …...

玩转ChatGPT:DeepSeek测评(科研思路梳理)

一、写在前面 DeepSeek-R1出圈了&#xff0c;把OpenAI的o3-mini模型都提前逼上线了&#xff08;还免费使用&#xff09;。 都号称擅长深度推理&#xff0c;那么对于科研牛马的帮助有多大呢&#xff1f; 我连夜试一试。 二、科研思路梳理 有时候我们牛马们做了一堆结果以后&…...

实验9 JSP访问数据库(二)

实验9 JSP访问数据库&#xff08;二&#xff09; 目的&#xff1a; 1、熟悉JDBC的数据库访问模式。 2、掌握预处理语句的使用 实验要求&#xff1a; 1、使用Tomcat作为Web服务器 2、通过JDBC访问数据库&#xff0c;实现增删改查功能的实现 3、要求提交实验报告&#xff0c;将代…...

CMake项目编译与开源项目目录结构

Cmake 使用简单方便&#xff0c;可以跨平台构建项目编译环境&#xff0c;尤其比直接写makefile简单&#xff0c;可以通过简单的Cmake生成负责的Makefile文件。 如果没有使用cmake进行编译&#xff0c;需要如下命令&#xff1a;&#xff08;以muduo库echo服务器为例&#xff09;…...

PyCharm中使用Ollama安装和应用Deepseek R1模型:完整指南

引言 人工智能和大型语言模型正在改变我们与技术交互的方式。Deepseek R1是一个强大的AI模型,而Ollama则是一个让我们能够轻松在本地运行这些模型的工具。本文将指导您如何使用Ollama安装Deepseek R1模型,并在PyCharm中创建一个简单的聊天应用。 © ivwdcwso (ID: u0121…...

编程AI深度实战:大模型知识一文打尽

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…...

012-51单片机CLD1602显示万年历+闹钟+农历+整点报时

1. 硬件设计 硬件是我自己设计的一个通用的51单片机开发平台&#xff0c;可以根据需要自行焊接模块&#xff0c;这是用立创EDA画的一个双层PCB板&#xff0c;所以模块都是插针式&#xff0c;不是表贴的。电路原理图在文末的链接里&#xff0c;PCB图暂时不选择开源。 B站上传的…...

基于springboot+vue的哈利波特书影音互动科普网站

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

MySQL5.5升级到MySQL5.7

【卸载原来的MySQL】 cmd打开命令提示符窗口&#xff08;管理员身份&#xff09;net stop mysql&#xff08;先停止MySQL服务&#xff09; 3.卸载 切换到原来5.5版本的bin目录&#xff0c;输入mysqld remove卸载服务 测试mysql -V查看Mysql版本还是5.5 查看了环境变量里的…...