C语言 | 指针 | 野指针 | 数组指针 | 指针数组 | 二级指针 | 函数指针 | 指针函数
文章目录
- 1.指针的定义
- 2.指针的加减运算
- 3.野指针
- 4.指针 & 数组 & 传参 & 字符数组
- 5.数组指针 & 指针数组
- 6.二级指针
- 7.指针函数 & 函数指针 & 回调函数
- 8.函数指针数组 & 指向函数指针数组的指针
1.指针的定义
指针是内存中一个最小单元的编号,也就是地址。平常口语中所说的指针,通常指的是指针变量,是用来存放内存地址的变量。指针的大小在32位平台是4个字节,在64位平台是8个字节。可以通过sizeof求得指针的大小。
#include <stdio.h>int main()
{int a = 5;char c = 'a';int* ptra = &a;char* ptrc = &c; printf("%ld\n",sizeof(ptra));printf("%ld\n",sizeof(ptrc));return 0;
}
我的系统64位的Ubuntu系统所以输出8。从上面的代码可以看出用 type + *
定义一个指针变量,而&
(取地址) 可以取得变量的地址。另外输出ptra
和ptrc
的结果都一样,难道它们的类型一样。不是的!ptra
的类型是int*
用来接收int
类型变量的地址,而ptrc
的类型是char*
用来接收char
类型变量的地址。对指针赋值的时候类型要匹配!
可以定义指针,那么如何取出指针的指向的内容呢,其实它和定义指针用的是同一个操作符 *
(解引用操作符或间接访问操作符)指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节),char* 指针的解引用就只能访问1个字节,int*指针的解引用就只能访问4个字节。
#include <stdio.h>int main()
{int a = 5;int* ptra = &a;printf("%d\n",*ptra);return 0;
}
2.指针的加减运算
C语言程序运行指针的整数加减,但是不能乘除!其实加减有时候就够头疼了,要是能乘除那不得了了!指针的加减会根据指针的类型决定了指针向前或者向后走一步有多大(距离),这时候能直观体现指针是有类型的。
#include <stdio.h>int main()
{int a = 5;char c = 'a';int* ptra = &a;char* ptrc = &c; printf("prta : before = %p : after = %p\n",ptra,ptra + 1);printf("prtc : before = %p : after = %p\n",ptrc,ptrc + 1);return 0;
}
输出结果:输出的结果是按照16进制来展示的。
prta : before = 0x7ffca65b0c74 : after = 0x7ffca65b0c78
prtc : before = 0x7ffca65b0c73 : after = 0x7ffca65b0c74
3.野指针
指针是很灵活的,但是有时候灵活就代表着容易出错,如果对指针操作不当那么很容易造成野指针。所谓的野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1、比如我对int*
类型的指针加一然后解引用访问,就会出错!指针越界访问!(int 只有4个字节大小的空间属于它) 通常越界访问是对数组经行操作,所以要注意数组下标访问是否越界。
int main()
{int a = 5;int* ptra = &a;printf("*prta : before = %d : after = %d\n",*ptra,*(ptra + 1));return 0;
}
输出:有些环境就程序直接就崩溃了,这里就输出了随机值。
*prta : before = 5 : after = 1747592652
2、指针未初始化
int main()
{int* ptra;printf("*prta : before = %d : after = %d\n",*ptra,*(ptra + 1));return 0;
}
3、指针指向的空间释放。对malloc动态申请的内存free后再去访问,也会造成野指针。
如何避免野指针的出现: 1.指针初始化、2.小心指针越界、3.指针指向空间释放即使置NULL、4.避免返回局部变量的地址、5.指针使用之前检查有效性。
上面的方法都是依靠程序员有良好的编码习惯,而人有时候会犯错,所以在C++中引入了智能指针来避免野指针。
4.指针 & 数组 & 传参 & 字符数组
数组名首元素的地址,它和&数组名输出的结果是一样但是它们代表并不是一个意思,因为指针是有类型的。
int main(int argc,char* argv[])
{int arr[5] = {1,2,3,4,5};printf("%p\n",arr);printf("%p\n",&arr);return 0;
}
输入结果
0x7ffebf362450
0x7ffebf362450
如果对数组名 + 1和&数组名 + 1会发现它们输出的内容是不一样的。
int main(int argc,char* argv[])
{int arr[5] = {1,2,3,4,5};printf("%p\n",arr);printf("%p\n",&arr);printf("%p\n",arr + 1);printf("%p\n",&arr + 1);return 0;
}
输出结果:
0x7ffd97b8f650
0x7ffd97b8f650
0x7ffd97b8f654
0x7ffd97b8f664
指针的类型决定了指针向前或者向后走一步有多大(距离)。数组名 + 1会跳过4个字节因为它的类型是 int*,而&数组名 + 1的类型是int (*)[5] 整形数组指针,跳过了20个字节。如果尝试这样赋值 int * p = &arr
,编译器会提醒你!
test.c:374:15: warning: initialization of ‘int *’ from incompatible pointer type ‘int (*)[5]’ [-Wincompatible-pointer-types]374 | int * p = &arr;
如果自动一个函数想要接收一个整形数组,不需要定义数组指针,只需要传递首元素的地址就能访问到整个数组的元素。可以有三种方式。
方式一:
void get(int* arr){}
方式二:这种方式需要和传入的数组的大小匹配,比较的麻烦,现实中定义函数的人也知道要定义多大。如果可以在函数中打印一下arr,发现就是一个指针的大小,而不是将整个数组传递。
void get(int arr[5])
{printf("%ld\n",sizeof(arr));
}
方式三:直接用方式一就好了。
void get(int arr[]){}
C语言没有像其他语言一样提供好用的String类型来处理字符串,它是通过char *指向 ""
引起的字符串常量,或者通过字符数组(栈),再或者存放到malloc开辟的动态内存空间中。不管怎么说以\0
标志结尾的C风格的字符串挺难用的。
那么char * (字符指针)和字符数组处理字符有什么区别呢:
int main()
{const char* str1 = "hello world";const char* str2 = "hello world";char str3[] = "hello world";char str4[] = "hello world";printf("str1 = %p : str2 = %p\n",str1,str2);printf("str3 = %p : str4 = %p\n",str3,str4);return 0;
}
输出结果
str1 = 0x55a2615af008 : str2 = 0x55a2615af008
str3 = 0x7fff95f06ca0 : str4 = 0x7fff95f06cac
str1 和 str2 指向了存放在常量区中的同一个"hello world"字符串的首元素的地址,而str3 和str4在栈区开辟了两块不同的内存存放Hello world。这样做虽然浪费了,一定的内存,但是字符数组中存放的内容可以修改,而char* 指向的字符串常量不能被修改,通常会加上一个const来修饰。另外,从键盘上输入字符就需要用到字符数组来接收。
int main()
{char str1[] = {'h','e','l','l','o'};char str2[] = {'h','e','l','l','o','\0'};char str3[] = "hello";printf("%ld\n",sizeof(str1));printf("%ld\n",sizeof(str2));printf("%ld\n",sizeof(str3));printf("%zd\n",strlen(str1));printf("%zd\n",strlen(str2));return 0
}
这里str1 定义并不是字符串,因为C字符串要以\0
结尾,所以strlen输出什么都是未定义的。这里注意区分sizeof 是计算内存的大小,而strlen是计算字符串的长度。
5.数组指针 & 指针数组
数组指针是指针,而指针数组是数组用来存放指针。它们的定义非常的相似。
数组指针: int (*p1)[5],p1是一个指针变量,指向的是一个大小为5个整型的数组。
指针数组: int *p2[5],p2是数组名,可以存放着5个int *
类型的指针。
这里定义数组指针需要括号,因为 [] 的优先级要高于 *
号的,加上()来保证p1先和*
结合。使用指针数组可以接收&一维数组名。也可以接收二维数组中的元素,注意二维数组中的元素是一维数组的地址。
void show(int(*p)[5],int cow,int col)
{for (int i = 0;i < cow ; i++) {for (int j = 0; j < col; j++) {printf("%d ",p[i][j]);}printf("\n");}
}int main()
{int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};show(a,2,5);return 0;
}
上面show函数打印了二维数组的值,数组通过 [] 访问更加清晰一些。在C++中通过重载 [] 也让容器在逻辑上顺序访问,哪怕它的底层空间不是连续的。这里的[] 和 * 是等价的。也可以用下面的方式,访问到二维数组的元素。
printf("%d ",*(*p + i) + j);
printf("%d ",*((*(p + i)) + j));
再来看一看,指针数组。来定义一个指针数组,并打印这些值。
int main(int argc,char* argv[])
{char* names[5] = {"张三","李四","王五","赵六","田七"};for(int i = 0;i < 5;i++){printf("%s\n",names[i]);}return 0;
}
上面的例子似乎没有什么挑战,那如果从键盘上输入五个人的名字,并输出呢,怎么做。就有一个关键点,指针数组中存放的是指针,如果从键盘上输入要用字符数组,然后赋值给指针。
int main(int argc,char* argv[])
{char* names[5]; for(int i = 0; i < 5;i++){char name[25] = {0};printf("请输入一个名字:");scanf("%s",name);names[i] = name;}for(int i = 0;i < 5;i++){printf("%s\n",names[i]);}return 0;
}
我们来使用指针数组完成一个有意思的东西,学过Linux的同学知道cat 命令是查看文本中的内容。我们自己编写一个程序也实现cat 命令完成的任务。没学过Linux也没有关系,知道了怎么编写命令,学的时候就不会被它唬住。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc,const char* argv[])
{if(argc <= 1){printf("Usage: %s <file1> [file2 ...]\n",argv[0]);return -1;}for(int i = 1;i < argc;i++){FILE* file = fopen(argv[i],"r");if(file == NULL){perror("文件不存在!\n");return -1;}char ch;while((ch = fgetc(file)) != EOF){fputc(ch,stdout);}fclose(file);}return 0;
}
通过gcc编译并添加到环境变量中
☁ day2024_11_14 [master] ⚡ gcc test.c[自己的文件名] -o easy_cat
☁ day2024_11_14 [master] ⚡ sudo mv easy_cat /usr/bin
☁ day2024_11_14 [master] ⚡ easy_cat test.c
6.二级指针
指针变量也是变量,存放指针变量的指针称为二级指针。
int main()
{int num = 5;int *p = #int **pp = &p;printf(" pp : %p &p : %p\n", pp,&p);printf("**pp : %d *p : %d\n",**pp,*p);return 0;
}
输出结果
pp : 0x7fff341ddbc8 &p : 0x7fff341ddbc8
**pp : 5 *p : 5
那么二级指针的用处是什么呢。函数的传参的方式有传值和传地址,着两种方式,传值传递参数是指的拷贝,如果想改变两个变量的地址,那么就需要通过指针传递。同样的道理,我想改变指针变量的内容就需要指针变量的地址,那就是二级指针。
交换两个int 变量的值:
void swap(int* a,int* b)
{int tmp = *a;*a = *b;*b = tmp;
}int main()
{int a = 5;int b = 10;printf("before a = %d,b= %d\n",a,b);swap(&a,&b);printf("after a = %d,b= %d\n",a,b);
}
交换两个int* 指针变量指向的值:
void swap(char** a,char** b)
{char* tmp = *a;*a = *b;*b = tmp;
}
当你想改变指针变量的指向的时候可以考虑使用二级指针,很多的数据结构中,二级指针都是很常见的。另外在巩固一下,如果要编写一个函数打印指针数组的值改怎么做呢。
void showName(char** names,int size)
{for(int i = 0;i < size;i++){printf("%s ",names[i]);}printf("\n");
}int main()
{char* names[5] = {"张三","李四","王五","赵六","田七"};showName(names,5);return 0;
}
为什么需要二级指针接收呢,可以这么理解:names[0]
类型为char*
而&names[0]
的类型为char**
。数组名就是首元素的地址,name = &names[0]
实参传递的是 char**
类型,showName
的形参类型也要用二级指针来接收。
7.指针函数 & 函数指针 & 回调函数
函数返回值类型是指针的函数称为指针函数,通常是返回堆空间的指针,避免返回栈上空间,出了作用域被销毁,访问一块销毁的空间。
void* malloc(int size);
char* strcpy(char* dest,char* src);
函数指针的本质是指针,用来存放函数的地址,而函数名就是函数在内存中首元素的地址。如何判断函数的类型。如定义一个add函数:
int add(int a,int b)
确认一个函数类型最快速的方法是,去掉函数名和函数的形参就是函数的类型。add 函数的类型是int (int,int)
。然后添加一个 (* p)
就可以定义一个函数指针了。
#include <stdio.h>int add(int a,int b)
{return a + b;
}int main()
{int (*p1)(int,int) = add;int (*p2)(int,int);p2 = add;printf("%d\n",p1(1,2));printf("%d\n",p2(1,3));return 0;
}
定义的函数指针可以在定义的时候赋初值,也可以先声明在赋值。使用函数指针像函数一样使用即可。既然像函数一样使用,那干嘛要麻烦的定义函数指针呢?定义函数指针可以让函数以参数的方式传递,让声明和实现分离,提高了代码的模块化程度。 这就像其他如Java语言中定义接口,通过接口来调用实现接口的类。
下面实现了加减,如果发生乘除取模(整除)等添加另外的功能函数的类型一样都可以通过calculate去调用。
#include <stdio.h>int add(int a,int b)
{return a + b;
}int sub(int a,int b)
{return a - b;
}int calculate(int cal(int,int),int a,int b)
{return cal(a,b);
}int main()
{printf("%d\n",calculate(add,5,10));printf("%d\n",calculate(sub,10,5));return 0;
}
回调函数callback,通常我们调用别人的函数API这个过程叫做call,而别人在他定义的函数中调用我们的函数这个行为就是callback。上面的例子中calculate 调用cal 这个动作就是callback。在C语言中qsort 就用到了callback 。
#include <stdio.h>
#include <stdlib.h>int int_cmp(const void* p1,const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = {1,6,2,3,8,9,3};int size = sizeof(arr) / sizeof(arr[0]);qsort(arr,size,sizeof(int),int_cmp);for(int i = 0;i < size;i++){printf("%d ",arr[i]);}printf("\n");return 0;
}
那qsort大概是怎么实现的呢?使用一个冒泡排序,实现对任意类型数据的排序。
C语言中使用void* 来接收任意类型,但是void* 不能加减运算,但是类型又是不确定的,所以只能转成最小类型的指针char* ,按照一个字节来操作数据。比较的时候,只需要两个确定元素的地址,而char* 一次只能跳过一个字节,所以要根据size来确定跳几个字节。交换的时候是按照一个一个字节,交换内容,要交换size次。
void _swap(void* p1,void* p2,int size)
{for(int i = 0;i < size;i++){char ch = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = ch;}
}void my_qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*))
{for(int i = 0; i < num - 1;i++){for(int j= 0;j < num -1 -i;j++){if(compar((char*)base + j * size,(char*)base + (j + 1) * size) > 0){ _swap((char*)base + (j* size),((char*)base + (j+ 1)* size),size);}}}
}int int_cmp(const void* p1,const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = {5,1,2,3,8,9,3};int size = sizeof(arr) / sizeof(arr[0]);my_qsort(arr,size,sizeof(int),int_cmp);for(int i = 0;i < size;i++){printf("%d ",arr[i]);}printf("\n");return 0;
}
8.函数指针数组 & 指向函数指针数组的指针
用来存放函数指针的数组。在上面的calculate例子中,可以使用函数指针数组来存放函数指针。
int add(int a,int b)
{return a + b;
}int sub(int a,int b)
{return a - b;
}int main()
{int (*p[2])(int,int) = {add,sub};for(int i = 0;i < 2;i++){printf("%d\n",p[i](10,5));}
}
当然还可以定义一个指向函数指针数组的指针
int (*(*pp)[2])(int,int) = &p;
相关文章:
C语言 | 指针 | 野指针 | 数组指针 | 指针数组 | 二级指针 | 函数指针 | 指针函数
文章目录 1.指针的定义2.指针的加减运算3.野指针4.指针 & 数组 & 传参 & 字符数组5.数组指针 & 指针数组6.二级指针7.指针函数 & 函数指针 & 回调函数8.函数指针数组 & 指向函数指针数组的指针 1.指针的定义 指针是内存中一个最小单元的编号&…...
mysql 的乐观锁和 mvcc 是一回事吗
MySQL 的乐观锁和 MVCC(多版本并发控制)是两个不同的概念,尽管它们都涉及到并发控制和数据的一致性,但在设计目的和实现方式上存在本质区别。 1. 乐观锁 概念 乐观锁是一种用于解决并发更新冲突的控制机制。它假设数据在大部分情况…...
redis的击穿和雪崩
Redis 是一个高性能的键值存储数据库,广泛用于缓存、会话管理等场景。然而,Redis 在高并发场景下可能会遇到一些问题,比如“击穿”和“雪崩”。下面详细解释这两个概念: 击穿(Hotspot) 击穿是指某个热点数…...

java中创建多线程的4种方式
目录 一、继承 Thread 类创建线程 步骤 示例代码 原理 二、实现 Runnable 接口创建线程 步骤 示例代码 原理 三、实现 Callable 接口创建线程 步骤 示例代码 原理 与Runnable接口相比的不同之处 四、使用线程池创建线程 步骤 示例代码(使用 Executo…...

MATLAB深度学习(二)——如何训练一个卷积神经网路
2.1 基本概念 从数学的角度看,机器学习的目标是建立输入和输出的函数关系,相当于 y F(x)的过程。F(x)就是我们所说的模型,对于使用者来说,这个模型就是一个黑箱,我们不知…...

删除k8s 或者docker运行失败的脚本
vi delete_exited_containers.sh#!/bin/bash# 列出所有停止的容器并存储到数组 list_exited_containers() {echo -e "\nStopped containers:"containers()# 获取停止的容器信息并存入数组while IFS read -r line; docontainers("$line")done < <(do…...
重置docker版本的octoprint管理员账号密码
我的情况是octoprint安装在HiNAS系统的机顶盒上,只有一个账号,但是忘记了用户名和密码。有两个选择: 可以试试先找回用户名,然后尝试你的常用密码。直接重置所有账号。 1.找回用户名: 使用使用 docker exec -it <…...
prometheus监控数据远程写入Kafka集群
文章目录 前言一、环境简介1.1 环境简介1.2 部署清单1.3 组件版本 二、部署步骤2.1 prometheus部署2.2 kafka集群部署2.3 prometheus-kafka-adapter部署 三、数据验证四、总结 前言 根据项目要求,需将prometheus监控数据存储到kafka中。前面为了图方便就搭建了单机…...

Excel使用-弹窗“此工作簿包含到一个或多个可能不安全的外部源的链接”的发生与处理
文章目录 前言一、探讨问题发生原因1.引入外部公式2.引入外部数据验证二、问题现象排查及解决1.排查公式2.排查数据验证3.特殊处理方式总结前言 作为一种常用的办公软件,Excel被大家所熟知。尽管使用了多年,有时候在使用Excel时候也会发生一些不太常见的现象,需要用心核查下…...
C++小白实习日记——Day 2 TSCNS怎么读取当前时间
和老板问了一下,今天就可以自己上手了: 用TSCNS写了一个cpp,运行出来老板说让我去看看另一个项目是怎么做的 用TSCNS和std库获取当前时间 #include <iostream> #include <iomanip> #include "tscns.h"using namespace std;TSCN…...

【Pythonr入门第二讲】你好,世界
"Hello, World!" 是一种传统的编程入门示例,通常是程序员学习一门新编程语言时编写的第一个程序。这个程序的目标非常简单:在屏幕上输出 "Hello, World!" 这个字符串。尽管它非常简单,但具有重要的象征意义和实际价值。 …...

3D Streaming 在线互动展示系统:NVIDIA RTX 4090 加速实时渲染行业数字化转型
随着科技的飞速发展,实时渲染正逐步成为游戏与实时交互领域的重要驱动力。与离线渲染不同,实时渲染需要极高的计算性能,对硬件设备尤其是GPU的性能要求极高。随着 RTX 4090 显卡的问世,其强大的算力和创新技术,为实时渲…...
Oracle 单机及 RAC 环境 db_files 参数修改
Oracle 数据库中 DB_FILES 定义了数据库中数据文件的个数,默认值为200,如果创建数据库文件时超过DB_FILES 定义的值就会报 ORA-00059 错误。 下面分别演示单机及 RAC 环境下修改 db_files 参数的操作步骤。 一、单机环境 1.查询当前参数值 SQL> sh…...

消息中间件分类
消息中间件(Message Middleware)是一种在分布式系统中实现跨平台、跨应用通信的软件架构。它基于消息传递机制,允许不同系统、不同编程语言的应用之间进行异步通信。 常见的消息中间件类型包括: 1. JMS(Java Message S…...
讯飞、阿里云、腾讯云:Android 语音合成服务对比选择
在 移动端 接入语音合成方面,讯飞和腾讯云等都是优秀的选择,但各有其特点和优势。咱们的需求是需要支持普通话/英语/法语三种语言,以下是对各个平台的详细比较: 一、讯飞语音合成介绍 与语音听写相反,语音合成是将一段…...
SpringBoot开发——整合AJ-Captcha实现安全高效的滑动验证码
文章目录 一、什么是AJ-Captcha二、项目配置1、Maven依赖配置2、滑动验证码的基本原理3、 后端实现3.1 生成滑动验证码图片代码解释3.2 校验滑块位置代码解释4、前端部分代码解释5、Redis 缓存滑动验证码信息5.1 Redis配置5.2使用Redis缓存验证码数据5.3 校验时从Redis获取总结…...
Spring Security 核心组件
Spring Security 是一个功能全面的安全框架,用于处理基于 Spring 应用程序的身份验证和授权。 它提供了开箱即用的支持,采用行业标准的做法和机制来保护你的应用。 无论你是开发简单的 Web 应用还是复杂的微服务架构,理解 Spring Security …...

聚焦 AUTO TECH 2025华南展:探索新能源汽车发展新趋势
随着“新四化”浪潮的推进,汽车行业正经历前所未有的变革。中国新能源汽车正逐渐走向世界。国内汽车制造巨头如比亚迪、吉利、奇瑞、长安等,已经将出口提升至核心战略地位。中国新能源汽车的发展,不仅推动了全球汽车产业的电动化转型…...

Python-简单病毒程序合集(一)
前言:简单又有趣的Python恶搞代码,往往能给我们枯燥无味的生活带来一点乐趣,激发我们对编程的最原始的热爱。那么话不多说,我们直接开始今天的编程之路。 编程思路:本次我们将会用到os,paltform,threading,ctypes,sys,…...

[STM32]从零开始的STM32 HAL库环境搭建
一、前言 之前在搭建STM32的标准库环境时就告诉过大家,开发STM32的方式主要有三种。一种是最原始但是效率最高的寄存器开发,另一种是效率仅次于寄存器难度相对较低的标准库开发,最后一种是最为简单但是程序效率最低的HAL库开发。如果对于初学…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...