【C语言】深入理解指针
目录
1.字符指针
2.指针数组
3.数组指针
4.数组传参与指针传参
一维数组传参
二维数组传参
一级指针传参
二级指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针(了解即可)
8.回调函数
回调函数的应用:库函数qsort
模拟实现库函数qsort
1.字符指针
允许用字符串来初始化字符指针
char*p="abcdef"这个语句是正确的,他表示把后面字符串首元素地址放到指针变量p里面去。其中abcdef是一个常量字符串
看下面这个代码

刚上来的两句是创建了两个字符数组,并用hello bit来初始化他们,这两个数组的创建必然要申请两块不同的内存空间,而打印的条件是str1==str2,而数组名是数组首元素地址,所以不满足if的条件,因此会打印str1 and str2 are not same。
然后是用常量字符串初始化字符指针的操作,对于常量字符串hello bit来讲,他存放在内存的代码段,他的地址是固定的,用他的地址来初始化str3和str4,因此str3和str4里面存放的都是常量字符串hello bit的首元素地址,因此打印的是str3 and str4 are same。
2.指针数组
指针数组顾名思义就是用来存放指针的数组,本质上还是一个数组,用来存放一组类型相同的指针。
比如这样一个代码

他们在内存中存放的形式如图

如果拿到了arr[0],也就是arr数组的第一个元素,里面存的是arr1,他指向了arr1数组,也就是说arr[0]就是arr1,arr1[0]表示arr1数组的首元素,又因为arr1就是arr[0]所以arr[0][0]就是arr1的首元素,这看起来像一个二维数组,同理arr[1][0]就是arr2数组的首元素。
虽然形式上跟二维数组一样,但是指针数组跟二维数组还是有很大区别的,二维数组在创建的时候在内存中被分配的空间是连续的,而这个指针数组再创建的时候就是存了三个指针,这个指针指向了三个数组,这三个数组并不一定连续。也就是说对于一个三行五列的二维数组(假设数组名为arr)来说,arr[0][4],arr[1][0]的地址是连续的,但是对于上面的指针数组来说arr[0][4]是arr1的第五个元素,而arr[1][0]是arr2的首元素,由于arr1和arr2在向内存申请空间的时候都是随机申请的,大概率不连续,因此这个指针数组arr的arr[0][4]和arr[1][0]也是大概率不连续的。
3.数组指针
类比一下我们熟悉的其他类型指针,比如int *p,*代表p是一个指针,int代表p指向的数据是int类型的,数组指针也类似,比如int *p[10],*代表p是一个指针,去掉*p之后剩下的int [10]是其指向数据的类型,这是一个数组类型。因此p就叫做数组指针变量,里面存放的是一个数组的地址。p的类型我们写作int(*)[10]。
在对数组指针变量进行初始化的时候,应该写成 int(*p)[10]=&arr;
可以这样理解,首先得有个指针变量p,然后他前面得有个*表示他是一个指针,此时如果没有(),p就会和[]结合,因此我们加上括号,让p和*结合代表他是一个指针。指向的元素是int [10]类型的,存放的内容是arr的地址。同时印证了这个数组指针的类型是int(*)[10],因为去掉指针变量的名字,就是他的类型。
数组指针的使用
先来看一个不合适的使用方法

说我们要打印这个数组的每个元素,这里代码使用了数组指针的方式,首先&arr代表取出整个数组的地址(arr在一般情况下表示数组首元素地址,但是有两个例外,一个是arr单独放在sizeof内部,另一个数&arr,这两种情况下arr代表的是整个数组),既然是一个数组的地址,我们就要放在数组指针里面,因此我们创建了一个int(*)[10]类型的指针变量p,用来存放arr的地址,然后我们要打印arr中的每个元素,肯定要对p进行解引用,*p拿到了arr(可以理解成&和*抵消了),要想打印arr中的每个元素,我们现在知道了arr的首元素地址(因为这里拿到arr是通过解引用,并不属于上面的两种特殊情况,因此这里arr就是数组首元素地址,是int*类型的),想要打印,直接就*(arr+i)即可。也就是说打印的时候应该*((*p)+i),非常的别扭。因为我们想要打印整个数组的内容,直接使用一个int*类型的指针即可,这里却使用了数组指针显然是不合理的,那么数组指针到底应该怎么用?
这里直接给出答案:数组指针在访问二维数组的时候比较方便。
假如有这样一个二维数组arr

当然这只是为了方便理解才画成这样,实际上因为二维数组中每个元素在内存中是连续存放的,他们应该排成一条线。
二维数组可以理解成里面有三个一维数组,也就是说二维数组是一维数组的数组。而二维数组的数组名也代表首元素的地址,首元素又是一个一维数组,因此二维数组的数组名是一个一维数组的地址,我们如果想要把他存起来,应该使用一个数组指针。那么下面这个代码就可以实现二维数组元素的打印

因为arr包含三个一维数组,每个一维数组是四个元素,因此arr的数组名也就是首元素地址是一个int [4]类型的数组的地址,类型是int(*)[4],因此在形参接收的时候应该写int(*p)[4]。
首先p是int(*)[4]类型的数组指针,+1就会跳过一个int [4]类型的数组,p里面存放的是arr首元素的地址,也就是第一个一维数组的地址,因此*p就拿到了第一个数组,相当于拿到了第一个数组的数组名,也就是说*p是int*类型的,+1就会跳过一个int类型。
*p+i用来访问所有行,*((*p+i)+j)就能访问所有元素了,或者说(*p+i)[j]也可以,他们是等效的。
当然也可以直接形参直接使用二维数组来接收

实际上形参写成数组形式只是语法允许,让我们能够直观的知道接收的是一个数组,本质上地址就是要用指针来接收,即使写成第二种写法,编译器在编译的时候也会编译成我们第一种写法,在为形参开辟空间的时候也是为数组指针开辟一块空间。
4.数组传参与指针传参
一维数组传参
下面哪些传参方式是正确的?


先看test函数的传参,实参是arr,也就是一个int类型数组的数组名,表示首元素的地址,应该用一个int*类型的指针来接收,因此形参写成int*arr是对的,又因为C语言语法允许数组传参的时候用数组接收,因此int arr[]和int arr[10]作为形参也是可以的,因为实参传的只是一个地址,形参部分是不会真正去创建一个数组的,但是语法规定可以用数组来作为形参接收一个数组名,我们甚至可以把形参写成int arr[1000],也是对的,编译器都会按照形参是int *类型的来理解。
再来看test2的传参,arr2是一个指针数组,他的数组名是这个指针数组首元素的地址,也就是一个指针的地址,即二级指针,实参传了二级指针,形参要拿一个二级指针变量来接收,因此形参应该是int **类型的,当然语法允许用和实参代表的数组同类型的数组作为形参来接收,但是本质上形参还是一个指针。
因此上面所有传参均正确。
二维数组传参

test函数实参是一个二维数组的数组名,他代表二维数组arr首元素的地址,又因为二维数组arr是由3个一维数组构成的,每个一维数组又有五个int类型的元素,因此arr的首元素是第一个一维数组的地址,存放他应该用一个数组指针,因此实参应该写成int(*arr)[5],表示arr是一个指针,指向的数组类型是int[5]类型的数组,也就是我们所谓的二维数组里面的一维数组,当然数组名作为实参传递,语法上允许用与arr代表的数组类型相同的数组来作为形参接收,这是为了方便理解,但是系统是不会为形参这个数组去开辟内存空间的,编译的时候仍然会按照数组指针的形式开辟空间。
二维数组在创建的时候可以省略行但是不能省略列,因此正确的形参创建形式有int arr[3][5],int arr[][5],int(*arr)[5]。
注:除去数组名单独放在sizeof括号里还有&数组名这两种情况,二维数组的数组名一定是一个数组指针。
一级指针传参
对于指针传参来说,如果实参是一个一级指针,形参就用同类型的指针来接收即可,二级指针同理,这非常的简单,因此对于指针传参,我们来讨论一下如果我们知道了形参是一个一级指针,实参可能传递的是什么。

可以接收
1.char a=0; test2(&a) 一个char类型变量的地址
2.char ch="abc"; test2(ch);一个char[]类型数组的数组名
3.char a=0;char* p=&a; test2(p);一个char*类型的指针变量
二级指针传参

如果形参已经确定是char**p,可能接收的是什么实参?
可以接收
1.char a=0;char *p=&a;test(&p);一个char*类型指针的地址
2.char a=0;char *p=&a;int**pp=&p;test(pp);一个二级指针
3.char* arr[10];test(arr);一个char*[10]类型数组的数组名
注:数组传参,形参可以用数组形式接收,也可以用指针形式接收,但是指针传参,形参指针写成指针形式。
5.函数指针
函数指针顾名思义就是指向函数的指针,里面存放的是一个函数的地址
来看一段代码


居然是一样的,这说明函数名和&函数名代表的意思都是函数的地址
既然函数有地址,我们就可以把他存到指针变量里面去,这个指针变量就是函数指针,在初始化的时候我们写作void (*p)(),也就是函数的返回值类型以及形参类型决定了他的函数指针是什么样的类型。比如有一个函数
int add(int x,int y),用来存放他的函数指针在初始化的时候就写作
int(*p)(int,int),去掉指针的名字,就是他的类型,因此这个函数指针的类型就是int(*)(int,int)
再比如有一个函数void test(charpc,int arr[10]),初始化他的函数指针就写作void (*p)(char*,int [10]),p的类型就是void(*)(char*,int [10]),10可以省略,也可以写成int *
有了函数指针,我们就可以对他进行解引用来调用函数。如果pf里存放了函数add的地址,那么add(3,5)和(*p)(3,5)就是等效的。实际上*都多余了,因为我们函数名和&函数名其实是一回事,我们可以测试一下


因此我们既然把&add赋给了pf,那就相当于把add赋给了pf,也就是说pf就是add,因此我们直接pf(3,5)也是可以正常调用add函数的
来看两个有趣的代码

首先我们看到最后有个小括号,这肯定代表函数调用,然后左边最外层的括号应该就是就是为了分割开,这样我们就从内部的0开始入手,0前面有一个括号,括号里面是一个类型,这表示把0强制类型转换成函数指针的类型,那么0作为函数指针就代表了一个地址,再对他进行解引用就能调用0地址处的函数。之所以最右边调用的小括号里面啥也没有,是因为这个函数不需要任何参数。这个代码应用场景可能就是我们知道了0地址处有一个函数,想要调用他。

这个代码看起来无从下手,因为他好像也并没有函数调用,也没有强制类型转换,如果我提前告诉你,这是一个函数名为signal函数的声明,你有没有头绪了呢?
解释一下,我们就从signal开始入手,因为这肯定是一个函数名,那么他后面的括号就是他的参数类型,分别是int和void(*)(int),也即一个int类型和一个函数指针类型,那么知道了函数名,函数的参数类型,把他们都去掉,不就是他的返回类型吗?故signal的返回类型是void(*)(int),他的返回值也是函数指针类型。因此上面这段代码是signal的声明。
这个代码之所以难以理解,就是因为这个函数指针的类型写起来非常具有迷惑性,如果把这个函数指针类型进行类型重定义成一个简单的类型,这个代码阅读起来就会非常的轻松。那首先来介绍一下typedef的使用方法,如果是把unsigned int类型重命名为uint,就写作typedef unsigned int uint,如果是把int 类型重命名为ptr_t,就写作typedef int ptr_t
但是如果要把函数指针 int(*)(int)重命名为pint,就不能写tepedef int(*)(int) pint,而应该写成typedef int(*pint)(int)
要把函数指针void(*)(int)重命名为pf_t,就写作typedef void(*pf_t)(int),这样就可以把上面的代码简化为pf_t signal(int,pf_t),这样读起来就舒服多了,一眼就看出这是一个函数的声明。
6.函数指针数组
顾名思义就是这个数组中的每个元素都是函数指针类型。
假如我们要写一个计算器,于是我们就写了下面四个函数

我们发现这些函数的参数和返回值都已相同类型的,那我们就可以把这些函数放到一个数组里面去,怎么放函数?只能放他的函数名吧,那我们又说函数名其实是函数的地址,因此我们放的其实是一些函数的地址,也就是函数指针,当然要用一个函数指针数组来存放,那么如何初始化这个数组?类比int arr[4]={0};这表示arr数组里面能放4个int类型的元素
那么我们想要一个能够放四个int(*)(int,int)类型变量的数组,就初始化成int(*)(int,int) arr[4]是不是?这样理解可以,但是语法规定我们要把arr[4]写到*的旁边,也就是说我们应该这样初始化函数指针数组
注:指针变量在创建的时候,指针变量一定是在*的旁边的。
int(*p)(int ,int),*表示p是个指针,去掉*和p就是指针指向的元素类型是
int(int,int),说明指向的元素是函数类型,单独去掉变量名就是p的类型,也就是说p是int(*)(int,int)类型。
int(*arr[4])(int,int),那么对于简单的计算器这个代码的实现,就可以这样写(计算器只针对整数)

7.指向函数指针数组的指针(了解即可)
假如arr是一个函数指针数组,我们现在&arr,放到指针p里面,则p就是指向函数指针数组的指针,他的类型是什么呢?
首先来看函数指针数组的类型应该怎么写。int(*[4])(int,int),是函数指针数组的类型,现在p要指向他,应该写成int(*(*p)[4])(int,int)=&arr,其中p旁边的那个*表示p是一个指针,去掉p,就是p的类型,所以p的类型是int(**arr[4])(int,int),类型去掉p和他旁边的*,就是p指向的元素的类型,因此p指向的元素类型是int(*[4])(int,int),也即一个函数指针类型。
8.回调函数
函数指针有一个非常重要的用途就是实现回调函数。回调函数就是通过函数指针调用的函数。比如前面的计算器,除了使用函数指针数组来实现,还可以使用函数指针来实现。

其中calc函数就通过函数指针回调了加减乘除的函数,此时的add,sub,mul,div就是回调函数,通过函数指针调用谁,谁就是回调函数
回调函数的应用:库函数qsort
qsort的头文件是stdlib.h

他的四个参数分别是要进行排序的数组base的首地址,base数组的元素个数,每个元素的大小,以及一个函数指针,这个函数指针指向了一个函数,这个函数的参数是两个void*类型的指针,返回类型是int,要求这个函数能够比较参数(这个函数的参数是两个指针)指向的两个元素的大小,规定如果elem1指向的元素比elem2指向的元素大,那这个函数就返回一个大于零的数,反之就返回一个小于零的数,如果elem1和elem2指向的元素一样大,就返回0。
void*类型的指针可以接收任何类型的地址,但是不能直接解引用,由于我们不知道未来要比较的两个函数是什么类型的,那就只能写成void*类型,就可以接收任何类型了,在解引用之前强制类型转换即可。
qsort默认是以升序排列的(如果要降序排列,只需要比较大小的函数返回值上用相反的逻辑即可,比如elem1指向的元素如果比elem2大,就返回一个小于零的数),并且可以排列任何类型的数组。来看一个例子

模拟实现库函数qsort
下面我们将使用回调函数模拟实现qsort,由于目前没有学习快速排序,因此使用冒泡排序代替。

模拟实现,我们就把参数设置成和库函数qsort一样,最后一个函数指针指向的是一个能比较两个元素大小的函数,需要使用者自己编写。
我们排序使用的是冒泡排序的思想,就是比较相邻的两个元素,如果前面比后面大,就交换,那现在的问题是当时冒泡排序是针对整形数组的,比较就是直接base[j]和base[j+1]进行比较即可,但是现在我们想要这个my_sort类型能够对任意类型的数据进行排序,就不能简单的写成base[j]和base[j+1],因为我们连base的类型都不知道,当然也不知道+1会跳过几个字节,也就不知道+1是不是拿到了和base[j]相邻的元素。那么我们干脆一个字节一个字节的操作,因为不管什么类型,大小至少也是一个字节,那如何操作一个字节?很简单,使用char类型的指针即可。因此不管base代表什么类型的数组,我们都先把base强制类型转换成char*类型,那么base[j]其实就是(char*)base+j*size,与他相邻的元素就是(char*)base+(j+1)*size。
这样我们就拿到了要比较的两个元素,但是这两个元素如何比较?直接使用大于号显然是不合理的,这时候就用到了我们的第四个参数,cmp函数,我们需要自己写一个能够比较两个元素大小的函数,并且规定如果前者大,就返回一个大于0的数,反之就返回一个小于0的数,如果二者相等,就返回0。
通过判断cmp函数的返回值,我们就知道了需不需要交换这两个元素。那问题又来了,如何交换?直接创建临时变量吗?那临时变量是什么类型的呢?但是要交换的数据是什么类型,他们最小也有一个字节,那么我们直接一个字节一个字节的交换就行,什么时候才算交换结束呢?当然是交换完size个字节后,就结束了。如图是swap函数的交换

这样我们的my_sort函数就可以正常使用了,比如我们要排序的是一个整形数组,我们就要去写一个能够比较两个整形数据的函数,比如这样写

如果我们要比较两个结构体类型的数据呢?
假如结构体类型是这样的

如何比较两个struct stu类型变量的大小?
由于上面的结构体类型含有两个成员变量,那么要比较struct sut类型变量的大小,就可以按照name的大小来进行比较,也可以按照age的大小进行比较,当然如果按照name的大小来进行比较,就不能直接相减了,因为字符串的比较是用库函数strcmp,这个库函数的返回值逻辑与我们的cmp函数一致,也是前者大就返回大于零的数,后者大就返回小于零的数,一样大就返回0,因此我们如果按照name的大小来比较的话,直接返回strcmp的值就可以,如图

注:由于空指针是无法进行解引用操作的,因此我们在写cmp函数的时候都需要进行强制类型转换,要比较的是什么类型的,就强制类型转换成对应的指针。
相关文章:
【C语言】深入理解指针
目录 1.字符指针 2.指针数组 3.数组指针 4.数组传参与指针传参 一维数组传参 二维数组传参 一级指针传参 二级指针传参 5.函数指针 6.函数指针数组 7.指向函数指针数组的指针(了解即可) 8.回调函数 回调函数的应用:库函数qsort …...
Excel——有效性、二级菜单联动
一、录入规范数据 1.手动输入序列录入有效性信息 选择需要录入有效性的所有单元格 选择【数据】——【有效性】——【有效性】 在【允许】输入的值之间选择【序列】 在【序列】输入框中输入想要选择的值,中间用逗号(必须是英文逗号)隔开 。…...
计算机网络总结
1. 网络分层 网络上进行协议分层的好处 ①分层之后层次之间的耦合程度比较低,上层协议不必了解下层的细节,下层也不必了解上层的细节; ②方便的对某一层的协议进行替换; 真实网络的协议分层 OSI 七层网络模型(教科书&…...
初识文件包含漏洞
目录 什么是文件包含漏洞? 文件包含的环境要求 常见的文件包含函数 PHP伪协议 file://协议 php://协议 php://filter php://input zip://、bzip2://、zlib://协议 zip:// bzip2:// zlib:// data://协议 文件包含漏洞演示 案例1:php://inp…...
AR特效自研AI算法技术解决方案
在当今这个高速发展的数字化时代,增强现实(AR)技术已经成为企业创新和市场竞争的重要手段。美摄科技凭借对AI技术的深厚积累,为企业提供了一套创新的AR特效自研AI算法技术解决方案,旨在满足企业在AR领域的多元化需求。…...
牛客2024年除夕娱乐赛(题解)
比赛地址 : 牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A 看题面然后猜!!! #include<bits/stdc.h> using namespace std; int main(){cout << "原神启动" << endl; } B 也是看题面然后猜 : #include<bits/stdc.…...
5 scala的函数式编程简介
与Java一样,Scala 也是使用 Lambda 表达式实现函数式变成的。 1 遍历 除了使用 for 可以对数组、List、Set 进行遍历外,也可以使用 foreach 函数式编程进行遍历,使代码更为简洁。 foreach 的方法签名为: foreach(f: (A) > …...
陪护系统|陪护小程序提升长者护理服务质量的关键
在如今逐渐老龄化的社会中,老年人对更好的护理服务需求不断增加。科技的进步使得陪护小程序系统源码成为提供优质服务的重要途径之一。本文将从运营角度探讨如何优化陪护小程序系统源码,提升长者护理服务的质量。 首先,我们需要对软件的设计和…...
C++算法之双指针、BFS和图论
一、双指针 1.AcWing 1238.日志统计 分析思路 前一区间和后一区间有大部分是存在重复的 我们要做的就是利用这部分 来缩短我们查询的时间 并且在使用双指针时要注意对所有的博客记录按时间从小到大先排好顺序 因为在有序的区间内才能使用双指针记录两个区间相差 相当于把一个…...
【大厂AI课学习笔记】1.5 AI技术领域(3)自然语言处理
今天来梳理自然语言处理的相关内容。 自然语言处理:定义、关键技术、技术发展、应用场景与商业化成功 一、自然语言处理的定义 自然语言处理(NLP)是人工智能(AI)领域的一个重要分支,它研究的是如何让计算…...
【数字电子技术课程设计】多功能数字电子钟的设计
目录 摘要 1 设计任务要求 2 设计方案及论证 2.1 任务分析 2.1.1 晶体振荡器电路 2.1.2 分频器电路 2.1.3 时间计数器电路 2.1.4 译码驱动电路 2.1.5 校时电路 2.1.6 整点报时/闹钟电路 2.2 方案比较 2.3 系统结构设计 2.4 具体电路设计 3 电路仿真测试及结…...
【新书推荐】7.3 for语句
本节必须掌握的知识点: 示例二十四 代码分析 汇编解析 for循环嵌套语句 示例二十五 7.3.1 示例二十四 ■for语句语法形式: for(表达式1;表达式2;表达式3) { 语句块; } ●语法解析: 第一步:执行表达式1,表达式1…...
爬山算法优化遗传算法优化极限学习机的多分类预测,p-ga-elm多分类预测
目录 背影 极限学习机 爬山算法优化遗传算法优化极限学习机的多分类预测,p-ga-elm多分类预测 主要参数 MATLAB代码 效果图 结果分析 展望 完整代码下载链接:爬山算法优化遗传算法优化极限学习机的多分类预测,p-ga-elm多分类预测(代码完整,数据)资源-CSDN文库 https://d…...
挑战杯 opencv 图像识别 指纹识别 - python
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于机器视觉的指纹识别系统 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:3分创新点:4分 该项目较为新颖,适…...
【Docker】了解Docker Desktop桌面应用程序,TA是如何管理和运行Docker容器(2)
欢迎来到《小5讲堂》,大家好,我是全栈小5。 这是《Docker容器》系列文章,每篇文章将以博主理解的角度展开讲解, 特别是针对知识点的概念进行叙说,大部分文章将会对这些概念进行实际例子验证,以此达到加深对…...
PHP、Python、Java 和 Go语言对比
PHP、Python、Java 和 Go 都是流行的编程语言,每种语言都有其独特的优势和适用场景。下面是对这些语言的一些基本对比: 一:PHP 适用场景:主要用于Web开发,特别是服务器端脚本。 特点:语法简单易懂&#…...
算法题目题单+题解——图论
简介 本文为自己做的一部分图论题目,作为题单列出,持续更新。 题单由题目链接和题解两部分组成,题解部分提供简洁题意,代码仓库:Kaiser-Yang/OJProblems。 对于同一个一级标题下的题目,题目难度尽可能做…...
车载测试中:如何处理 bug
一:Jira 提交 bug 包含那些内容 二:如何处理现上 bug 三:车载相关的 bug 如何定位 四:遇到 bug ,复现不出来怎么办 五:bug 的处理流程 一:Jira 提交 bug 包含那些内容二:如何处理现上…...
亲测解决vscode的debug用不了、点了没反应
这个问题在小虎登录vscode同步了设置后出现,原因是launch文件被修改或删除。解决方法是重新添加launch。 坏境配置 win11 + vscode 解决方法 Ctrl + shift + P,搜索debug添加配置: 选择python debugger。 结果生成了一个文件在当前路径: launch内容: {// Use Int…...
立足智能存取解决方案|HEGERLS智能托盘四向车储存制动能量 实现能源回收
对于商业配送和工业生产的企业而言,如何能高效率、低成本进行低分拣、运输、码垛、入库,用以提升仓库空间的利用效率,是现在大多企业急需要解决的行业痛点。对此,为了解决上述痛点,近年来,物流仓储集成商、…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
