【C语言】路漫漫其修远兮,深入[指针]正当下

一. 指针初步

1.概念定义

地址:我们在内存中开辟空间时,为了方便后续访问,每个数据有确切的地址。

指针:指向数据的地址,并将其地址储存在指针变量中。

 

2.基本运算符

• 取地址操作符(&)

%p是用于打印地址的格式。

2624ed0e7e0647e58112046a4c1270db.png

 

• 解引⽤操作符 (*)

*pa解引用a的数据。
82bb89c3c4624fa88bce55ebd3501fb0.png

 

3.指针变量的⼤⼩

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节。
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节。
• 指针变量的⼤⼩与类型⽆关,只要是指针类型的变量,在相同的平台下,⼤⼩都是相同。

 

 


二. 指针运算

1.指针+- 整数

5920e67fb36e468abd8cc24d7227dab1.png

数组在内存中是连续存放的,随着数组下标的增长,地址由高到低变化

 #include <stdio.h>int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++)
{printf("%d ", *(p+i));
}return 0;
}

 

注:p+1,即指针(地址增加一个int型字节大小[元素])。

 

2.指针 - 指针

指针 - 指针得到的是指针之间相差的元素个数。

#include <stdio.h>
int my_strlen(char *s)
{char *p = s;while(*p != '\0' )p++;return p-s;
}
int main()
{printf("%d\n", my_strlen("abc"));return 0;
}

 

3.指针的关系运算

#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

通过指针访问数组内元素,while循环遍历数组每一个数据。

三. 野指针

1.野指针成因

    • 指针未初始化

#include <stdio.h>
int main()
{ int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

    • 指针越界访问

#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}

    • 指针指向的空间释放

#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();printf("%d\n", *p);return 0;
}

局部变量n作用域在test函数,当程序运行至主函数时,n的空间被内存收回。

2.如何规避野指针

• 指针初始化

如果明确知道指针指向哪⾥就直接赋值地址,但不确定指针应该指向哪⾥,可以给指针赋值NULL。NULL 是C语⾔中定义的⼀个标识符常量,值是0(0也是地址),这个地址是⽆法使⽤的,读写时会报错。

7370f67819cc4cffb6dfb407f5d69b50.png 

#include <stdio.h>
int main()
{int num = 10;int*p1 = &num;int*p2 = NULL;return 0;
}

• 小心指针越界

 

程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

• 指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性  

int main()
{int arr[10] = {1,2,3,4,5,67,7,8,9,10};int *p = &arr[0];for(i=0; i<10; i++){*(p++) = i;}//此时p已经越界了,可以把p置为NULLp = NULL;//下次使⽤的时候,判断p不为NULL的时候再使⽤//...p = &arr[0];//重新让p获得地址if(p != NULL) //判断{//...}return 0;
}

• 避免返回局部变量的地址

如造成野指针的第3个例⼦,不要返回局部变量的地址。

四. assert断言

1.assert基本概念

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。  

 23b03baf29ec48b7bc976dfbf70144b8.png

上述代码在程序运⾏到这⼀⾏语句时,检查变量 p 是否等于 NULL 。若不等于 NULL ,程序继续运⾏,否则终⽌运⾏,并且显示报错信息提⽰。 assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零, assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

2.assert启动关闭

d3e8bc5bee684b1c961d1f9f085c9e50.png

assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。
然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语 句。 assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样成开
发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响⽤⼾使⽤时程序的效率。

开启>> 

#define NDEBUG
# include <assert.h>
int main()
{int var= 8;assert (var==1);system("pause");return 0;
}

关闭>> 

#define NDEBUG
# include <assert.h>
int main()
{int var= 8;assert (var==1);system("pause");return 0;
}

 
五. 指针的传值调用与传址调用 

例如:写⼀个函数,交换两个整型变量的值,分别采用传值调用与传址调用这两种方法

1.传值调用

#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

 1ac2889ba9b34856bde67c15c2159129.png

显然,在这里,传值调用并没有将a,b的值进行交换。

2.传址调用

#include <stdio.h>
void Swap2(int*px, int*py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

2dd6cc344f964e749b8b6d26a15dcea4.png

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。顾名思义, 形参是实参的一份临时拷贝

 如果要发生交换的效果,形参就应该接收main函数中的实参的地址所以……

因此我们可以在main函数中将a和b的地址(通过指针变量)传递给Swap函数,Swap 函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

六. 数组 + 指针(深入解析)

      1. 数组名的理解

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

             sizeof(数组名):表示整个数组,计算的是整个数组的大小,单位是字节。

 

             &数组名:也表示整个数组,取出的是整个数组的地址。

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("arr = %p\n", arr);return 0;
}

 0b9a07470157400fa70c6d6ad7a51504.png

 &arr[0]即首元素地址,arr(数组名),在debug x86环境下,两地址相同。

注:对于“[ ]”来说,只是一个操作符,比如arr[i]即*(arr+i)

        因此,可以换成*(i+arr),加法的交换律。

  •    arr[ i ] == *(p+i)== *(arr)+i

      main函数中将arr作为参数传到函数,传的只是arr的首元素地址。

      2. 使⽤指针访问数组

#include <stdio.h>
int main()
{int arr[10] = {0};//输⼊int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);//输⼊int* p = arr;for(i=0; i<sz; i++){scanf("%d", p+i);//scanf("%d", arr+i);//也可以这样写}//输出for(i=0; i<sz; i++){printf("%d ", *(p+i));}return 0;
}

       观上,数组名(arr)作为数组首元素地址,在这里,我们可以直接将arr赋值给p(指针变量),我们可以通过数组下标访问数组的元素,也可以通过指针变量解引用“*(p+i)”进行访问。

      3. ⼀维数组传参的本质

#include<stdiio.h>
void test(int arr[])
{int sz2 = sizeof(arr)/sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sz1 = sizeof(arr)/sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}

 73ad2a403fbf44189bbbe4b32194aab0.png

     从结果来看,sz1得到了arr数组中元素个数,然而sz2却打印错误,究其原因,也就是说本质上数组传参传递的是数组⾸元素的地址,test函数中形参得到的应该是数组第一个元素。

    tips: ⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

      4. 冒泡排序

              • 代码实现

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{int i = 0;for(i=0; i<sz-1; i++){int flag = 1;//假设这⼀趟已经有序了int j = 0;for(j=0; j<sz-i-1; j++){if(arr[j] > arr[j+1]){flag = 0;//发⽣交换就说明,⽆序int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}if(flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了break;}
}
int main()
{int arr[] = {3,1,7,5,8,9,0,2,4,6};int sz = sizeof(arr)/sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for(i=0; i<sz; i++){printf("%d ", arr[i]);}return 0;
}

        如上,对10个数字进行冒泡排序,第一个“3”,与1,7,5,8……进行比较,采取相邻两位进行比较法,循环往复。加入flag进行优化,提高程序运行效率。

      5. ⼆级指针

指针变量也是变量,是变量就有地址,二级指针就用于存放指针变量的地址。
*ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20;
*ppa = &b;//等价于 pa = &b;

           **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

 

      6. 指针数组

指针数组是指针还是数组?
我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
8b49b624ae794568bda9435cef72f030.png

 

      7. 指针数组模拟⼆维数组

#include <stdio.h>
int main()
{int arr1[] = {1,2,3,4,5};int arr2[] = {2,3,4,5,6};int arr3[] = {3,4,5,6,7};//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中int* parr[3] = {arr1, arr2, arr3};int i = 0;int j = 0;for(i=0; i<3; i++){for(j=0; j<5; j++){printf("%d ", parr[i][j]);}
printf("\n");}return 0;
}

d759913205ff46a29f89f0f378e7ed45.png 

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。

 

      8. const引入

              • const定义

       const是C++中的一个关键字,表示常量,即表示变量的值是固定不变的。const可以用于变量、函数参数、函数返回值、类成员函数等。在变量中使用const,可以定义一个常量,该变量的值不能被修改。

 429c1dcadca34ca2bd324ab05a0e9e01.png

            • const语法

6ddddc49575142999f6c97525fe7ef8d.png

 

 

七. 一系列的指针变量 

      1. 字符指针变量

              在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;

int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}//还有⼀种使⽤⽅式如下:
int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?printf("%s\n", pstr);return 0;
}

      2. 数组指针变量

             1. 数组指针概念

• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
数组指针变量     int (*p)[10];
解释:p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。

              2. 数组指针初始化 

int arr[10] = {0};
&arr;//得到的就是数组的地址
int(*p)[10] = &arr;

      3. ⼆维数组传参的本质

#include <stdio.h>
void test(int a[3][5], int r, int c){int i = 0;int j = 0;
比特就业课主页:https://m.cctalk.com/inst/s9yewhfr 比特就业课for(i=0; i<r; i++)
比特就业课主页:https://m.cctalk.com/inst/s9yewhfr{for(j=0; j<c; j++){printf("%d ", a[i][j]);}printf("\n");}
}int main(){
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}
⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

 50a1a81df50246c686d4a4f77f6b89c6.png

第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址。tips:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

 

      4. 函数指针变量

            1. 函数指针变量

                       • 创建函数指针变量

#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
//输出结果如下:
test: 005913CA
&test: 005913CA
打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。

         • 使用函数指针变量 

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:
void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{return x+y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

        • 解析函数指针变量 

int      (*pf3) (int x, int y)|         |     ------------ |         |           ||         |     pf3指向函数的参数类型和个数的交代|    函数指针变量名pf3指向函数的返回类型int (*) (int x, int y) 

2. typedef 关键字

 

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
//⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef unsigned int uint;
//将unsigned int 重命名为uint//如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;//但是对于数组指针和函数指针稍微有点区别:
//⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边//函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
//那么要简化代码2,可以这样写:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

      5. 函数指针数组

 

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组, ⽐如:
int *arr[10];
//数组的每个元素是int*

 把函数的地址存到⼀个数组中,这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针

 

      6. 转移表

函数指针数组的⽤途:转移表

• 举例:计算器的⼀般实现:

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

• 使⽤函数指针数组的实现: 

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a*b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf( "请选择:" );scanf("%d", &input);if ((input <= 4 && input >= 1)){printf( "输⼊操作数:" );scanf( "%d %d", &x, &y);ret = (*p[input])(x, y);printf( "ret = %d\n", ret);}else if(input == 0){printf("退出计算器\n");}else{printf( "输⼊有误\n" ); }
}while (input);49 return 0;}

八. qsort函数深入解析

1. qsort定义

1d77ccea21214a2e9a3d76e0876c6f08.png

2. qsort排序 

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
#include<string.h>struct Stu {char name[20];int age;
};//比较两个字符串大小(按名字排序)
int cmp_stu_1(const void* e1, const void* e2) {return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//strcmp返回值是小于0,等于0,大于0 。// 比较的是对应字符Ascall码大小 
}//按照年龄大小进行比较(按年龄排序)
int cmp_stu_2(const void* p1, const void* p2) {return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}void print_arr(struct Stu* s, int sz) {for (int i = 0; i < sz; i++) {printf("%s %d\n", (s + i)->name, (s + i)->age);}printf("\n");
}void test() {struct Stu arr[3] = { {"张三",19},{"李四",21},{"王五",18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_1);qsort(arr, sz, sizeof(arr[0]), cmp_stu_2);print_arr(arr, sz);
}int main() {test();return 0;
}

3. qsort模拟实现 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>//交换函数
void Swap(char* buff1, char* buff2, size_t width)//char*一次仅访问一个字节,width:字节个数
{for (int k = 0; k < width; k++) {char tmp = *buff1;*buff1 = *buff2;*buff2 = tmp;buff1++;buff2++;}
}//排序函数
void bubble_sort(void* base, size_t sz, size_t width, int(*int_cmp)(const void* p1, const void* p2))){              //初始函数指针/数组  , 数组元素个数  , 每个元素的大小  ,  相邻两个元素的指针        for (int i = 0; i < sz - 1; i++) {for (int j = 0; j < sz - 1 - i; j++) {if (int_cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {//将前后两个数据进行交换Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}//比较函数
int int_cmp(const void* p1, const void* p2) {return *(int*)p1 - *(int*)p2;
}//打印函数
//void print_arr(const void* p, size_t sz) 
void print_arr(int arr[], size_t sz) {for (int i = 0; i < sz; i++) {//printf("%d ", *((int*)(p + i)));printf("%d ", arr[i]);}
}//主函数
int main() {int arr[] = { 5,6,4,7,3,8,2,9,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);print_arr(arr, sz);return 0;
}

九. sizeof与strlen

 • sizeof

sizeof是单目操作符,不是函数,只关注内存类型,即空间大小。

 

tips:sizeof中如果有表达式,表达式不参与计算

because-> sizeof操作时在编译时,而表达式进行时在程序运行时进行操作

088eeb72a37b416cb454f616c5217888.png 

 • strlen

 

主要关心‘/0‘,参数是地址

仅用于求字符串长度,计算“\0”之前字符的个数,当遇见'\0'时,结束。

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:  

 size_t strlen ( const char * str );

      统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。

strlen 函数会⼀直向后 \0 字符,直到找到为⽌,所以可能存在越界查找。
#include <stdio.h>
int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));printf("%d\n", sizeof(arr1));printf("%d\n", sizeof(arr2));return 0;
}

 

 • 两者比较 

7db7ef2065504f02bfa9edcd8f064561.png 

cf2d2f9ff0d64f81893671aadc8cb46a.gif 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【强化学习入门】基于DDPG的强化学习控制器设计

最近在看控制领域研究热门–强化学习相关的东西&#xff0c;跟着matlab官方强化学习教程一边看一边学&#xff0c;感觉入门门槛略高&#xff0c;需要补很多机器学习相关的知识&#xff0c;高数概率论那些&#xff0c;摸索了个把月感觉现在只大概会用&#xff0c;原理啥的还没搞…

进口家装水管十大品牌哪家好,弗锐德为您推荐进口家装水管领先十大品牌

水管作为家装隐蔽工程之一&#xff0c;选对一款优质的水管是至关重要的&#xff0c;毕竟好的水管能够保证家庭后续几十年的用水安全和健康。今天&#xff0c;小编就和大家说说进口家装水管十大品牌哪家好&#xff1f; 目前国内进口家装水管具有知名度和消费者认可的品牌有&…

自制一个3D打印的移动终端——T3rminal

T3rminal是我过去几个月一直在努力开发的一个CyberDeck&#xff0c;并希望将其开源。 我从不同设备如Decktility、YARH和其他项目中获得了灵感。 你可以在我的Github上协助并关注该项目&#xff1a;https://github.com/crazycaleb2008/T3rminal/tree/main/3D%20Models 材料 …

Edge的使用心得和深度探索-Sider: ChatGPT 侧边栏

作为一款备受欢迎的网络浏览器&#xff0c;Microsoft Edge在用户体验和功能方面都有着诸多优势。在长期的使用中&#xff0c;我总结出了三条使用心得&#xff0c;同时也发现了三个能够极大提高效率的功能。让我们一起深度探索Edge的潜力吧&#xff01; 使用心得&#xff1a; 界…

Spring 常用的注入方式有什么?

Spring 是一个非常流行的 Java 开发框架&#xff0c;它提供了多种依赖注入&#xff08;Dependency Injection&#xff09;的方式&#xff0c;使得开发者可以轻松地管理应用程序中的组件依赖关系。在 Spring 中&#xff0c;常用的注入方式主要包括构造器注入、Setter 方法注入、…

【测试报告】星光日册

⭐ 作者&#xff1a;Jwenen &#x1f331; 作者主页&#xff1a;Jwenen的个人主页 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 测试报告 1. 项目介绍2. 测试用例框架3. 自动化测试源码 1. 项目介绍 “星光日册”项目实现了用…

继续SQL

主知识点六&#xff1a;having 聚合前的筛选用where&#xff0c;聚合后的筛选用having Having和where的区别是&#xff1a;运行顺序和对象不用 Having是在group by聚合后的基础上进行筛选。 ● 【例题27*】&#xff08;运行原理&#xff09;查询总人口数至少为3亿的大洲和…

vxe-table 区域选取、复制粘贴功能,的基本使用

vxe-table区域选取、复制粘贴功能&#xff0c;的基本使用&#xff08;注&#xff1a;该功能仅支持企业版&#xff0c;这里仅供部分演示&#xff09; 1.鼠标区域选择功能&#xff1a; 参数说明&#xff1a; mouse-config.area 是否开启鼠标单元格区域选取 <template>&l…

详解基于 RAG 的 txt2sql 全过程

前文 本文使用通义千问大模型和 ChromaDB 向量数据库来实现一个完整的 text2sql 的项目&#xff0c;并基于实际的业务进行效果的展示。 准备 在进行项目之前需要准备下面主要的内容&#xff1a; python 环境通义千问 qwen-max 模型的 api-keyChromaDB 向量数据库acge_text_…

Linux-笔记 uboot修改设备树

1. FDT介绍 扁平设备树&#xff08;Flattened Device Tree&#xff0c;FDT&#xff09;&#xff0c;也叫平坦设备树&#xff0c;是设备树的一种二进制表示形式&#xff0c;提高了在嵌入式系统中的传输和解析效率&#xff1b; 2. 在U-Boot中使用FDT 2.1. 进入U-Boot 开发板上…

OpenCV 入门(四)—— 车牌号识别

OpenCV 入门系列&#xff1a; OpenCV 入门&#xff08;一&#xff09;—— OpenCV 基础 OpenCV 入门&#xff08;二&#xff09;—— 车牌定位 OpenCV 入门&#xff08;三&#xff09;—— 车牌筛选 OpenCV 入门&#xff08;四&#xff09;—— 车牌号识别 OpenCV 入门&#xf…

【论文阅读】VMamba: Visual State Space Model

VMamba:视觉状态空间模型 code&#xff1a;https://github.com/MzeroMiko/VMamba Background ​ CNN拥有线性复杂度因而可以处理高分辨率的图像&#xff0c;而ViT在拟合能力方面超过了CNN&#xff0c;但ViT是二次复杂度&#xff0c;在处理高分辨率图像时计算开销较大。ViT通过…

Wappalyzer指纹识别下载安装使用教程,图文教程(超详细)

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

Windows平台通过MobaXterm远程登录安装在VMware上的Linux系统(CentOS)

MobaXterm是一个功能强大的远程计算工具&#xff0c;它提供了一个综合的远程终端和图形化的X11服务器。MobaXterm旨在简化远程计算任务&#xff0c;提供了许多有用的功能&#xff0c;使远程访问和管理远程服务器变得更加方便&#xff0c;它提供了一个强大的终端模拟器&#xff…

钉钉开放平台创建企业内部H5微应用或者小程序

前言&#xff1a; 在当今企业数字化转型的浪潮中&#xff0c;创建企业内部H5微应用或小程序已成为提升工作效率和促进内部沟通的重要举措。发话不多说本文将介绍如何利用钉钉平台快速创建这些应用&#xff0c;让企业内部的工作更加便捷高效。 步骤 1.在浏览器打开链接…

【解决】Android APK文件安装时 已包含数字签名相同APP问题

引言 在开发Android程序过程中&#xff0c;编译好的APK文件&#xff0c;安装至Android手机时&#xff0c;有时会报 包含数字签名相同的APP 然后无法安装的问题&#xff0c;这可能是之前安装过同签名的APP&#xff0c;但是如果不知道哪个是&#xff0c;无法有效卸载&#xff0c;…

ESD静电问题 | 更换接口芯片

【转发微信公众号&#xff1a;EMC容冠电磁】

VMare Workstation安装ubuntu虚拟机异常问题处理

安装方法 ubuntu官网下载插件 异常处理 开启时报错"unable to proceed without a log file" 遇到此问题的都有一个共同点&#xff0c;工作目录路径上都带了数字&#xff0c;比如"Ubuntu 64位 01"&#xff0c;解决方法为&#xff1a; 选中"Ubuntu 64位…

用得助全媒体呼叫中心,让AI落到实处帮品牌做营销

怎么让人工智能落到实处的帮助到我们&#xff1f;我们今天来讲讲中关村科金得助全媒体呼叫中心是怎么让AI帮品牌。 这次聊的案例是知名的护肤品牌&#xff0c;该品牌在中国功能性护肤品市场占有率达到20.5%&#xff0c;这么高的市场占有率客户的咨询量也是非常庞大的&#xff0…

C++ 继承篇

面向对象语言的三大特性&#xff1a;封装&#xff0c;继承和多态 根据目前学到的知识&#xff0c;对于封装的理解&#xff0c;大致有两层&#xff1a; 将数据和方法封装&#xff0c;不想让外面看到用private/protected修饰&#xff0c;想让外面看到用public修饰类型的行为不满…