数组和指针

news2024/11/20 6:36:47

文章目录

  • 指针和数组
    • 指针
        • 为什么要有指针
        • 如何理解编址
        • 指针的内存布局
        • 指针解引用
        • 如何将数值存储到指定的内存地址
          • 栈随机化
    • 数组
        • 数组的内存布局
        • &a[0] VS &a
          • 步长问题
        • 指针和数组无关
          • C为何要这样设计
    • 指针数组和数组指针
          • 数组元素个数是否是数组类型的一部分?是
          • 地址的强制转化
    • 多维数组和多级指针
      • 二维数组
      • 二级指针
    • 数组传参和指针参数
        • 二维数组传参
          • 为什么第二个维度之后不能省略大小呢?
    • 函数指针
          • 为什么函数也有地址?
          • 函数地址
        • `(*(void (*)())0)()`

指针和数组

指针

变量:

定义一个变量,本质就是在内存中根据类型开辟空间.有了空间就必须具有地址来标识空间,方便CPU进行寻址,有了空间就可以把数据保存起来.

  • 指针就是地址,地址本质就是数据,数据可以被保存在变量空间里面.
  • 指针变量:是存放指针数据的,空间是左值,内容是右值.

所以,当指针变量取右值的时候是等价于指针的.

指针变量是操作指针(地址)的抓手,更加的方便相比于直接使用指针字面值进行操作.

#include<stdio.h>
int main()
{
    //定义整形变量
	int a = 10;	//使用的是a的空间;左值
	int b = a;  //使用的是a的内容:右值,a==10

	int* p = &a;	//指针变量本质是变量,然后里面保存的是地址(指针)值
	p = (int*)0x1234;//p变量的空间:左值
	int* q = p;		 //p变量的内容:右值->0x1234

	(int*)0x123456;//指针<->地址
	*((int*)0x123456) = 10;//通过指针是可以直接访问的

	return 0;
}

对指针解引用,得到的是指针所指向的目标.

指针变量也是变量,他也有地址,也可以用变量存储起来,就是二级指针变量.

为什么要有指针

如果没有定位的能力,只能通过遍历的方式寻找目标内容,提高查找效率.

32位机器下能够识别多大的物理内存.

image-20230508122909905

既然CPU寻址按照字节寻址,但是内存又很大,所以,内存可以看做众多字节的集合.

image-20230508123306126

其中,每个内存字节空间,相当于一个学生宿舍,字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位。

每间宿舍都有门牌号就等价于每个字节空间对应的地址,即该空间对应的指针。

那么,为何要存在指针呢?为了CPU寻址的效率。

如何理解编址

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。

但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。

而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。

不过,我们今天关心一组线,叫做地址总线。
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很多,需要给宿舍编号一样)

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

钢琴 吉他 上面没有写上“都瑞咪发嗦啦”这样的信息,但演奏者照样能够准确找到每一个琴弦的每一个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且所有的演奏者都知道。本质是一种约定出来的共识!

硬件编址也是如此,我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2中含义,2根线就能表示4中含义,依次类推。32根地址线,就能表示2^32中含义,每一种含义都代表一个地址。

地址信息被下达给内存,在内存内部,就可以找到改地址对应的数据,将数据在通过数据总线传入CPU内寄存器.

指针的内存布局

一个整形,有4个字节,那么应该有4个地址!

  • 那么&a取了哪一个地址?首地址
  • 那么如何全部访问这4个字节呢?通过首地址,根据类型向后访问4字节的空间

所谓的强制类型转化,就是起始地址不变,改变的是向后访问字节的个数.

指针解引用

对指针解引用,就是指针指向的目标。所以*p,就是a .至于是空间还是内容要根据左值还是右值来区分.

int main()
{
    int a=0;
    int * p=&a;
    *p=10;
}

*p使用的是左值还是右值?是右值,直接访问指针变量里面的右值.

image-20230508125000022

如何将数值存储到指定的内存地址

知道了指针的本质就是地址,地址就是数据,那么我们可以直接通过地址数据对变量进行访问吗?
题外话:大部分技术书,一定是落后于行业的。这本书也是,目前主流的编译器和操作系统,为了安全,已经有了很多内存
保护的机制。我们目前的win和Linux都有栈随机化这样的机制来方式黑客对用户数据地址进行预测。当然,还有其他的栈保护机制,比如“金丝雀”技术之类的。
经过试验,目前vs2013和Centos7上,使用C语言定义的局部变量,在每次运行的时候,地址都是不同的。经过试验发现,定义全局变量,每次更改代码,地址也会发生变化。所以这个实验没法正确做出来,但是程序崩溃,也能说明问题。
题外话:个人认为,书中这部分内容,是非常好的理解案例.
栈随机化

栈开辟空间是变化的,函数栈中的变量的地址也是会发生变化的.全局变量不会轻易发生变换,但是当前后顺序或者新增减少变量时,地址也是会变化的.

image-20230508144117374

数组

数组的内存布局

image-20230508151434274

在main函数中定义变量,在栈空间开辟空间,地址是从高地址向低地址方向开辟的.独立的变量地址是递减的.

image-20230508151228674

在开辟空间的时候,不应该被认为是一个个独立的元素,应该是整体开辟空间,整体释放.

所以数组中的元素地址依次是向着增大的方向分布的.所以访问时永远是i++的.

image-20230508151121423

&a[0] VS &a

步长问题

对指针+1其实是+上其指针所指向类型的大小.

int main()
{
	char* c = NULL;
	short* s = NULL;
	int* i = NULL;
	double* d = NULL;

	int a = 10;
	double* b = (double*)&a;

    //二级及往上都是4字节
	double** pb = NULL;
	double*********** pb1 = NULL;

	printf("%d\n", c);
	printf("%d\n\n", c + 1);

	printf("%d\n", s);
	printf("%d\n\n", s + 1);

	printf("%d\n", i);
	printf("%d\n\n", i + 1);

	printf("%d\n", d);
	printf("%d\n\n", d + 1);

	printf("%d\n", b);
	printf("%d\n\n", b + 1);

	printf("%d\n", pb);
	printf("%d\n\n", pb + 1);

	printf("%d\n", pb1);
	printf("%d\n\n", pb1 + 1);
	system("pause");
	return 0;
}

image-20230508152534192

数组名使用时代表整个数组:

  1. &arr:数组的地址
  2. sizeof(arr):整个数组

[]优先级>&

image-20230508152711458

&arr[0] 和 &arr虽然地址数字一样大,但是类型意义完全不同 .因为首元素的地址和数组整体开辟的获取到的最小的地址是重叠的.

  • 数组名做右值的时候代表的是数组收元素的地址.

image-20230508155743042

  • 数组是不支持整体赋值的,支持整体初始化,不能作为左值来使用.只能使用[]指定位置赋值.

指针和数组无关

访问元素上方式相似.

int main()
{
	char* str1 = "hello yuanwei";
	const char* str2 = "hello yuanwei";
	//存放于字符常量区,不可被修改,属于系统层面的保护
	//const属于编译器提醒保护
	//str1[1] = 'H';//fatal 系统级别保护

	char buffer[] = "hello world";
	//存放于栈区,用户有权进行修改内容

	int len = strlen(str1);
	for (int i = 0; i < len; i++)
	{
		printf("%c ",*(str1+i));//指针方式打印
		printf("%c ",str1[i]);//数组方式打印
	}
	int len2 = strlen(buffer);
	for (int i = 0; i < len2; i++)
	{
		printf("%c ", *(buffer + i));//指针方式打印
		printf("%c ", buffer[i]);//数组方式打印
	}
	return 0;
}
C为何要这样设计
  1. 数组传参,降维成指针.

image-20230509134104699

  1. 为什么要降维?

如果不降维,就会发生全拷贝,函数调用效率降低.所以会降维,但是:

  • 传参时是否生成临时拷贝?指针变量还是变量,仍然是拷贝4字节的指针.

image-20230509134616416

arr是代表数组首元素,传参时发生降维为指针类型(首元素地址值),形参根据类型开辟空间大小,空间初始化(实例化)时将传来的指针数据填充进形参的空间.这个形参就是临时变量,是那个一份拷贝.

  1. 降维成啥?

所有的数组传参,都会降维成指针,降维为指向其内部元素类型的指针.

  1. 在C语言中,任何函数调用只要有形参实例化,必定形成临时拷贝.

  2. 形参中的一维数组[]中的数字可以忽略.

  3. C面向过程语言,遇到啥解决啥,函数是核心,定义和调用函数传参,数组,数组传参,为了效率降维.

  4. 假设指针和数组访问元素方式不通用,在函数中由于降维是指针,只能用指针的方式访问,在主函数中只能用数组的方式访问,程序员需要不断地在不同的代码片段处进行习惯的切换,增加错误的概率.

  5. 为了让减少出错的概率,所以指针和数组访问元素的方式设置为通用.

指针数组和数组指针

typedef int* p1_t[5];
typedef int(* p2_t)[5];

int main()
{
	int* p1[5];//指针数组,数组内部可以放任何类型(内置类型+自定义类型)
	int(*p2)[5];//数组指针,指针可以指向任何合法的类型变量

	p1_t p1_[5];
	p2_t p2_[5];
	system("pause");
	return 0;
}
数组元素个数是否是数组类型的一部分?是
int main()
{
	int arr[10];
	//arr是int* 类型代表的是首元素地址
	//int(*p)[10]=arr;//warning C4047: “初始化”:“int (*)[10]”与“int *”的间接级别不同
	int(*p)[10] = &arr;//&arr代表的是数组地址

	int(*p1)[11] = &arr;//warning C4048: “int (*)[11]”和“int (*)[10]”数组的下标不同
	//所以数组元素个数也是作为数组的一部分,一旦两个数组的元素个数不同,也不认为两个数组是相同的.
	printf("%d",sizeof(p));//4
	system("pause");
	return 0;
}
地址的强制转化

所谓的强制类型转化,本质是改变看待数据的方式,数据本身不会发生任何变化.

image-20230510093415956

根据目标类型改变看待数据的方式.

image-20230510094122467

image-20230510100038219

  • 例一
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	printf("%d\n",sizeof(struct Test));//20->0x14

	printf("%p\n", p + 0x1);			//0x100014
	printf("%p\n", (unsigned long)p + 0x1);//0x100001 以unsigned long类型看待这个数据,就是数字相加
	printf("%p\n", (unsigned int*)p + 0x1);//0x100004

	printf("%d\n",sizeof(unsigned long));//4
	system("pause");
	return 0;
}
  • 例二

image-20230510101807748

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x\n", ptr1[-1], *ptr2);
	system("pause");
	return 0;
}

多维数组和多级指针

理解链:
数组的定义是:具有相同数据元素类型的集合,特征是,数组中可以保存任意类型。
那么数组中可以保存数组吗?答案是可以!
在理解上,我们甚至可以理解所有的数组都可以当成"一维数组"!(这样理解的好处,我们后面就说)
就二维数组来说,我们认为二维数组,可以被看做“一维数组”,只不过内部“元素”也是一维数组
那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体便也是线性连续且递增的,这也就解释了,上述地址为何是连续的。
在强调一遍,我们认为:二维数组可以被看做内部元素是一维数组的一维数组.

image-20230510111310769

证明数组线性的概念:二维数组的遍历.

int main()
{
	char c[4][3] = {
		{1,2,3},
		{4,5,6},
		{7,8,9},
		{-1,-2,-3}
	};
	char* p = (char*)c;
	for (int i = 0; i < 4 * 3; i++)
	{
		printf("%d\n", *(p + i));
	}
}

二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a)); //48
	printf("%d\n", sizeof(a[0][0])); //4
	printf("%d\n", sizeof(a[0])); //16
	printf("%d\n", sizeof(a[0] + 1)); //第一个元素,数组中的第二个int的地址,4
	printf("%d\n", sizeof(*(a[0] + 1))); //a[0][1],4
	printf("%d\n", sizeof(a + 1)); //a代表首元素地址(指针),+1 代表第二个元素的地址(指针),4
	printf("%d\n", sizeof(*(a + 1))); //第二个元素,4*4=16
	printf("%d\n", sizeof(&a[0] + 1)); //第二个元素的地址(指针),4
	printf("%d\n", sizeof(*(&a[0] + 1))); //第二个元素,4*4=16
	printf("%d\n", sizeof(*a)); //数组第一个元素,4*4=16
	printf("%d\n", sizeof(a[3])); //16
	return 0;
}
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("a_ptr=%p,p_ptr=%p\n", &a[4][2], &p[4][2]);
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

	//指针相减,代表指针之间所经历的元素的个数
}

image-20230511174859863

二级指针

  • 指针变量也是变量,变量就有地址,地址就可以放在另一个变量中存储.
  • 指针解引用得到的就是指针所指向的目标.

image-20230511175347748

数组传参和指针参数

  • 数组传参时一定要穿数组名,就是首元素的地址.(降维原因)
  • 形参实例化时发生一次临时拷贝,C语言中只要传参就会生成临时拷贝.
  • 一维数组传参时,元素个数是被忽略的.

一级指针传参也要发生值拷贝,要对指针传参指向一块空间,需要传址传参

void GetStr(char** pp)//二级指针接收
//void GetStr(char* pp)//pp是这个函数栈帧的临时变量,出作用域会被销毁
{
	*pp = malloc(sizeof(char) * NUM);
	if (NULL != *pp) {
		strcpy(*pp, "hello");
	}
	else {
		//do nothing!
	}
}
int main()
{
	char* p = NULL;
	GetStr(&p);//传址传参
    //GetStr(p);//传值传参
	printf("%s\n", p);//hello
    //printf("%s\n", p);//null,函数中的修改是在pp新空间进行的,pp被销毁了还,p空间没有任何变化
	system("pause");
	return 0;
}

二维数组传参

  • 所有数组都可以被看为一维数组.
  • 所有数组传参都要降维,降维成为指针.指向其内部元素类型的指针.
//void print1(int (*a)[6])		//1
//void print(int a[5][6])
//void print(int a[][6])//一维数组的维度大小是可以被省略的,二维数组第一个维度也可以被省略
void print(int a[][6],int num)//标准写法
{

}

//void print2(int(*a)[6][7][8][9])
void print2(int(*a)[][7][8][9])
//void print2(int(*a)[][][8][9])//error
{

}
int main()
{
	int a[5][6] = {0};
	int b[5][6][7][8][9] = {0};
	print(a,5);
	print1(a);//a是首元素地址,就是一个数组的地址,所以接收为1
	print2(b);
	return 0;
}
为什么第二个维度之后不能省略大小呢?

之前我们证明过数组大小也是数组类型的一部分,

如果我们省略了第二个之后的下标,指针类型不明确.

//void print(int(*p)[],int num)//warning C4048: “int (*)[0]”和“int [5][6]”数组的下标不同
void print(int(*p)[6],int num)
{
}
int main()
{
	int a[5][6] = {0};
	print(a,5);
	return 0;
}

函数指针

为什么函数也有地址?

函数是代码的一部分,程序运行的时候也要加载进内存,以供CPU后续寻址访问代码也有地址.

image-20230512094523667

函数地址

众多编译器中,函数地址就是他代码块的起始地址.但是在vs中经历了一次jmp跳转.也就是说&fun得到的是jmp的地址,jmp再跳转到函数起始地址.

image-20230512100055442

函数有地址,是数据就可以被变量保存=>函数指针类型

void fun()
{
	printf("hello yuanwei \n");
}
int main()
{
	void (*p)() = fun;
	(*p)();//p是左值,解引用得到地址值
	p();//p直接取右值地址直接调用
}

(*(void (*)())0)()

image-20230512102742960

int main()
{
	void(*p)();//函数指针
	void(*p[10])();//函数指针 数组
	void(*(*p)[10])();//函数指针 数组 指针
	void(*(*p[5])[10])();//函数指针 数组 指针 数组
	return 0;
}

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

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

相关文章

国赛推荐方向!Prompt工程设计大赛

近期&#xff0c;大型语言模型&#xff08; LLM&#xff0c;Large Language Model &#xff09;的魅力&#xff0c;让大家惊艳于 AI 的天花板还能继续抬升。具备“涌现能力”的 LLM &#xff0c;许多能力都将被解锁&#xff0c;带来意想不到的精彩礼物。对于用户来说&#xff0…

maven学习总结

生命周期 每个生命周期的各个环节都是由各种插件完成&#xff01;&#xff01;&#xff01;Maven有三个相互独立的生命周期&#xff08;Maven的这三个生命周期不能看成一个整体&#xff09;&#xff01;&#xff01;&#xff01; 我们在开发中描述的项目的生命周期&#xff0…

Java之阻塞队列和消息队列

目录 一.上节复习 1.什么是单列模式 2.饿汉模式 3.懒汉模式 二.阻塞队列 1.什么是阻塞队列 三.消息队列 1.什么是消息队列 2.消息队列的作用 1.解耦 2.削峰填谷 3.异步 四.JDK中的阻塞队列 1.常见的阻塞队列 2.向阻塞队列中放入元素---put() 3.向阻塞队列中拿出元…

python url拼接的方法

Python的 url是一个常用的文件链接&#xff0c;一个文件包含多个 url&#xff0c;在很多网站中&#xff0c;我们都需要拼接多个 url。 在网上我们经常可以看到关于 Python拼接的方法介绍&#xff0c;但是很多都是非常不完整的&#xff0c;今天我们就来了解一下&#xff0c;比较…

晨控CK-FR208-EIP与欧姆龙PLC工业Ethernet/IP协议通讯指南

CK-FR208-EIP 是一款支持标准工业 Ethernet/IP 协议的多通道工业 RFID 读写器&#xff0c;读卡器 工作频率为 13.56MHZ&#xff0c;支持对 I-CODE 2、I-CODE SLI 等符合 ISO15693 国际标准协议格式标签的读写。 读卡器同时支持标准工业通讯协议 Ethernet/IP&#xff0c;方便用…

Linux使用rsync同步文件

1.rsync的概念 rsync&#xff0c;remote synchronize顾名思义就知道它是一款实现远程同步功能的软件&#xff0c;它在同步文件的同时&#xff0c;可以保持原来文件的权限、时间、软硬链接等附加信息。 2.查看rsync 查看服务器端rsync版本 rsync --version rsync命令选项 -…

从GFS到GPT,AI Infra的激荡20年

导读 最近AIGC和LLM的浪潮层层迭起&#xff0c;大有把AI行业过去十年画的饼&#xff0c;一夜之间完全变现的势头。而AI Infra&#xff08;构建AI所需的基础设施&#xff09;&#xff0c;也成了讨论的焦点之一。大众对AI Infra的关注点&#xff0c;往往放在AI算力上——比如A100…

创作纪念日|我在CSDN的第365天(内含粉丝福利)

创作纪念日 大家好&#xff0c;我是陈橘又青&#xff0c;最近因为一直在备考&#xff0c;所以没怎么更新博客&#xff0c;今天起来和往常一样看了一眼私信&#xff0c;发现了一条来自CSDN官方的私信。 打开一看&#xff0c;原来是创作一周年的通知&#xff0c;回想起来&#…

Python数据分析:NumPy、Pandas和Matplotlib的使用和实践

在现代数据分析领域中&#xff0c;Python已成为最受欢迎的编程语言之一。Python通过庞大的社区和出色的库支持&#xff0c;成为了数据科学家和分析师的首选语言。在Python的库中&#xff0c;NumPy、Pandas和Matplotlib是三个最为重要的库&#xff0c;它们分别用于处理数值数组、…

基于密度的无线传感器网络聚类算法的博弈分析(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 提高能源效率是无线传感器网络面临的关键挑战之一&#xff0c;无线传感器网络日益普遍。由于节点&#xff08;传感器&#xff…

服务高可用保障:服务限流,Nginx实现服务限流

一、前言 1.1什么是限流&#xff1f; 限流存在于高可用服务中。 用于高可用的保护手段&#xff0c;主要包括&#xff1a;缓存&#xff0c;降级&#xff0c;限流 限流&#xff1a;只允许指定的事件进入系统&#xff0c;超过的部分将被拒绝服务&#xff0c;排队或者降级处理。 …

【零基础学web前端】html学习,表格标签,列表标签,表单标签(form和input),无语义标签div与span

前言: 大家好,我是良辰丫,今天我们就开始进入前端知识的学习&#x1f49e;&#x1f49e; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;零基础学web前端 &#x1f34e;励志语句&#xff1a;生活也许会让我们遍体鳞伤&#xff0c;但最终这些伤…

组织学图像弱监督腺体分割的在线简易示例挖掘

文章目录 Online Easy Example Mining for Weakly-Supervised Gland Segmentation from Histology Images摘要本文方法分割 实验结果 Online Easy Example Mining for Weakly-Supervised Gland Segmentation from Histology Images 摘要 背景 开发AI辅助的组织学图像腺体分割方…

DNDC模型在土地利用变化、未来气候变化下的建模方法及温室气体时空动态模拟

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。“十四五”时期&#xff0c;我国生态文明建设进入了以降碳为重点战略方向、推动减污降碳协同增效、促进经济社会发展全面绿色转型、实现生态环境质量改…

除氟树脂,除氟树脂用啥再生,离子交换除氟,矿井水除氟

氟化物选择吸附树脂 Tulsimer CH-87 是一款去除水溶液中氟离子的专用的凝胶型选择性离子交换树脂。它是具有氟化物选择性官能团的交联聚苯乙烯共聚物架构的树脂。 去除氟离子的能力可以达到 1ppm 以下的水平。中性至碱性的PH范围内有较好的工作效率&#xff0c;并且很容易再生…

2023年苹果企业开发者证书申请流程

第一步&#xff1a;注册apple ID&#xff0c;注意&#xff0c;要使用公司官网域名相关的企业邮箱账号注册&#xff0c;前提是公司要有企业邮箱&#xff0c;开通企业邮箱可用163代理的&#xff0c;也可以自己搭建。 第二步&#xff1a;在移动设备上登录该apple ID&#xff0c;并…

.Net中间件的概念---杨中科笔记

什么是中间件&#xff1f; 中间件是ASP.NET Core的核心组件&#xff0c;MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件。 中间件组成一个管道&#xff0c;整个ASP.NET Core的执行过程就是HTTP请求和响应按照中间件组装的顺序在中间件之间流转的过程。开发人员可…

一种KV存储的GC优化实践

作者&#xff1a;vivo 互联网服务器团队- Yuan Jian Wei 从内部需求出发&#xff0c;我们基于TiKV设计了一款兼容Redis的KV存储。基于TiKV的数据存储机制&#xff0c;对于窗口数据的处理以及过期数据的GC问题却成为一个难题。本文希望基于从KV存储的设计开始讲解&#xff0c;到…

MySQL 高级(进阶) SQL 语句三 存储过程

1.1 什么是存储过程 存储过程是一组为了完成特定功能的SQL语句集合。 存储过程在使用过程中是将常用或者复杂的工作预先使用SQL语句写好并用一个指定的名称存储起来&#xff0c;这个过程经编译和优化后存储在数据库服务器中。当需要使用该存储过程时&#xff0c;只需要调用它…

中国物种物候和地面物候数据获取方法

物候学是研究自然界的植物&#xff08;包括农作物&#xff09;、动物和环境条件&#xff08;气候、水文、土壤条件&#xff09;的周期变化之间相互关系的科学。它的目的是认识自然季节现象变化的规律&#xff0c;以服务于农业生产和科学研究。 [3-4] 物候既可指生物的周期性…