【C语言】深入理解指针(进阶篇)

news2024/10/2 17:17:03

一、数组名的理解

数组名就是地址,而且是数组首元素的地址。

任务:运行以下代码,看数组名是否是地址。

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

输出结果:

运行以上代码,我们发现数组名和数组首元素的地址打印的结果一模一样,数组名就是数组首元素(第一个元素)的地址

疑问:数组名如果就是首元素的地址,那么下面的代码证明理解?

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

 输出结果:40,如果arr是数组首元素的地址,那么输出的应该是4/8才对。

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名):sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名:这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素的地址。

要了解arr和&arr有什么区别,请看以下代码:

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

	printf("arr       = %p\n", arr);
	printf("arr+1     = %p\n", arr+1);

	printf("&arr       = %p\n", &arr);
	printf("&arr+1     = %p\n", &arr+1);
	return 0;
}

 输出结果:

这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,这是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素。

都是&arr和&arr+1相差40个字节,这里是因为&arr是整个数组的地址,+1操作就是跳过整个数组的。

到这里大家应该搞清楚数组名的意义了吧。

数组名是数组首元素的地址,但是有两个例外( sizeof(arr)和&arr )。

二、使用指针访问数组

有了前面的知识支持,再结合数组的特点,我们可以很方便的使用指针访问数组了。

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	//输入
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);//p+i 是地址
	}
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));//*(p+1) 解引用操作符(*),是数值
	}
	return 0;
}

 这个代码搞明白了,我们再试一下,如果我们再分析一下,数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的。那我们可以使用arr[i]访问数组元素,那p[i]是否也可以访问数组呢?

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 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;
}

在第17行的地方,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]等价于*(p+i)

因为数组名arr和p是等价的,所以arr[i]等价于*(arr+i)。数组元素的访问在编译器的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

三、一维数组传参的本质

我们知道数组是可以传递给函数的,这里我们讨论一下数组传参的本质。

首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给一个函数后,在函数内部求数组的元素个数吗?

#include <stdio.h>
void test(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

 输出结果:

 在函数内部求数组的元素个数,输出的结果是1,并没有正确获得元素个数。

这就要学习数组传参的本质了,第一节数组名的理解,我们知道:数组名就是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说数组传参本质上传递的是数组首元素的地址

 所以函数形参的部分理论上应该是用指针变量来接收首元素的地址。那么在函数内部写sizeof(arr)计算的是一个地址的大小(单位字节),而不是数组的大小(单位字节)。正是因为函数的参数部分本质是指针,所以在函数内部是没有办法求数组元素个数的

#include <stdio.h>
void test2(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
void test3(int *p)
{
	int sz3 = sizeof(p) / sizeof(p[0]);
	printf("sz3 = %d\n", sz3);
}
int main()
{
	int arr[10] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test2(arr);
	test3(arr);
	return 0;
}

 总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

四、冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

方法一:

#include <stdio.h>
bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz-1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j]>arr[j+1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[10] = { 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]);
	}
}

方法二:优化

#include <stdio.h>

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[10] = { 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]);
	}
}

五、二级指针

5.1 指针的定义

一级指针:是一个指针变量,指向一个普通变量,并保存该普通变量的地址

二级指针:是一个指针变量,指向一个一级指针,并保存该一级指针的地址

 二级指针是一个指向指针的指针变量。它存储了一个指针的地址,该指针又指向另一个变量的地址。

二级指针画图

 5.2 引入二级指针

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int* pa = &a;
	int** ppa = &pa;

	//一次解引用*ppa,此时类型int*
	*ppa = &b;
	//二次解引用**ppa,此时类型int
	**ppa = 200;

	return 0;
}

逻辑关系如下:

a是一个int类型的变量,一级指针pa指向a,并保存a的地址。

二级指针变量ppa指向一级指针pa,并保存pa的地址

二级指针ppa解引用操作:

  • 一次解引用:*ppa的类型变成 int*(代表一级指针pa)间接改变了pa的指向,从a的地址变成了b的地址。
  • 二次解引用:ppa的类型变成了 int (代表变量b),此时ppa = 200;(等价于b=200)。

(1)下面举个例子:

#include <stdio.h>
int main()
{
	//普通变量
	int a1 = 1;
	int a2 = 1;
	int a3 = 1;

	//一级指针
	int* p1 = &a1;
	int* p2 = &a2;
	int* p3 = &a3;

	//二级指针
	int** s = &p1;
}

 (假设a1,a2.a3空间连续,p1,p2,p3空间连续)逻辑图如下:

表达式移动字节数/值的变化类型
s+1sizeof(int*)*1 =  4*1  =  4int**
*s+1sizeof(int)*1  =  4*1  =  4int*
**s+1a1+1  =  1+1  =  2int

分析:

  • s+1 表示二级指针s指向了p2,移动的字节数需要根据指向的数据的空间大小进行计算sizeof(int*)*1 = 4字节,此时s+1还是二级指针,所以类型是int*。
  • *s+1 先对s进行一次解引用为*s,相当于操控一级指针p1,然后*s+1,相当于p1指向了a2的地址,所以移动sizeof(int)*1 = 4字节,此时的类型为int*。
  • **s+1 先对s进行二次解引用为**s,相当于操控变量a1,然后a1加1,所以a1=2;a1的类型是int。

总结:在对二级指针变量s的移动时,s都会将已经保存的一级指针的类型进行解析步长(s+sizeof(p)*n);而一级指针*s(相当于p一级指针变量)会以保存的变量的类型进行解析步长(*s+sizeof(a)*n)。

 六、指针数组

指针数组是多个指针变量,以数组的形式存储在内存中,数组中的每个元素都是一个地址(指针),占有多个指针存储空间。指针数组即存放指针的数组

指针数组的声明方式为:数据类型 *数组名[数组长度]。

 6.1 指针数组模拟二维数组

#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;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for(j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
		return 0;
	}
}

parr数组的画图演示

 parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。

上述代码模拟出的二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的

七、数组指针变量

7.1 数组指针变量是什么

上一节我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。

数组指针是指针变量,是存放数组的地址,能够指向数组的指针

任务: int (*p)[5] = { },如何理解?

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

 这里()和[ ]优先级相同,根据结合律,从左到右运算

()里是*p,p先和*结合,先定义了指针,说明p是一个指针变量,然后指向的是一个大小为5个整数的数组。所以p是一个指针,指向一个数组,叫数组指针。

int (*p)[5]的画图

 7.2 数组指针变量怎么初始化

数组指针变量是依赖存放数组地址的,那么怎么获取数组的地址呢?这就要用到&数组名

 如果要存放数组的地址,就得存放在数组指针变量中,如下:

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

 我们调试可以看到&arr和p的类型是完全一致的。

数组指针类型解析:

int (*p) [10] = &arr;
|       |      |
|       |      |
|       |      p指向数组的元素个数
|        p是数组指针变量名
p指向的数组的元素类型
 

 7.3 二维数组传参的本质

有了数组指针的理解,我们来讲解一下二维数组传参的本质。

过去我们有一个二维数组,徐亚传参给一个函数的时候,我们是这样写的:

#include <stdio.h>
void test(int arr[3][5],int r,int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[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;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他写法吗?

这里我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是一维数组。

arr数组

 所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是int[5],所以第一行的地址的类型就是数组指针类型iny(*)[5]。那就意味着二维数组传参本质上也是传递地址,传递的是第一行这个一维数组的地址。那么形参也是可以写成指针形式的。如下:

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p+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;
}

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

八、函数指针变量

8.1 函数指针变量的创建

函数指针即定义一个指向函数的指针变量。

 定义格式如下:

int (*p)(int x, int  y);  //注意:这里的括号不能掉

 这个函数的类型是有两个整型参数,返回值是整型。

思考:函数是否有地址?

让我们看看以下代码:

#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}

输出结果如下:

 观察发现,确实打印出来了地址,所以函数是有地址的,函数名技术函数的地址,当然也可以通过&函数名的方法获取函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针变量非常类似。如下:

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)     //pf3函数指针变量的类型

 8.2 函数指针变量的使用

通过函数指针调用指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;

	printf("%d\n", (*pf)(2, 3));
	printf("%d\n", pf(3,5));
	return 0;
}

输出结果:

九、函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组。如下:

int *arr[10];
//数组的每个元素是int*

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

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

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

十、转移表

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

举例:计算机的一般实现

#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);
	return 0;
}

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

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

相关文章

PMP-项目管理十大知识领域学习和理解

项目管理十大知识领域&#xff1a; 整合、范围、进度、成本、 质量、资源、沟通、风险、采购、干系人 十大知识领域的理解 十大知识领域裁剪情况&#xff1a; 十大知识领域: 硬技能和软技能

flink 总结

flink 流式api checkpoint state 状态分类 Managed State 和 Raw State Managed State Flink 自己管理&#xff0c;支持多种数据结构 Raw State 用户自己管理&#xff0c; 只支持byte Managed Staste 分为 Keyed State 和 operator State Managed State 只能在Keyed Str…

沁恒CH32V307VCT6开发板记录---kalrry

沁恒CH32V307VCT6开发板记录---kalrry 一、官网申请开发板二、环境准备图一 一、官网申请开发板 1.沁恒官网 二、环境准备 1.数据手册&#xff1a;CH32V307DS0.PDF&#xff0c;CH32FV2x_V3xRM.PDF 2.CH32V307评估板说明及参考应用例程&#xff1a;CH32V307EVT_ZIP 3.集成开…

Ubuntu18.04 下使用 Pybind11实现 C++ 调用 Python 函数和类的示例

Pybind11 是一个轻量级的库&#xff0c;它提供了在 C 中无缝集成 Python 代码的能力。使用 Pybind11&#xff0c;你可以很容易地从 C 调用 Python 代码&#xff0c;反之亦然。下面我将通过一个简单的例子来展示如何在 Ubuntu 系统上使用 Pybind11 从 C 调用 Python 接口。 安装…

上位机图像处理和嵌入式模块部署(qmacvisual二维码识别)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果说条形码在商品上使用比较多的话&#xff0c;那么二维码识别是一个更加使用频繁的场合。为什么使用这样频繁&#xff0c;我想很多一部分原因来…

Java开发:对象间复制属性,方法归纳

在Java开发中&#xff0c;对象间复制属性是一项常见的任务&#xff0c;特别是在处理层&#xff08;如控制器层&#xff09;与服务层或数据传输对象&#xff08;DTOs&#xff09;之间的数据转换时。有多种方法可以实现User对象到UserDTO对象的属性复制&#xff0c;下面列举了几种…

Ubuntu 安装谷歌拼音输入法

一、Fcitx 安装 在Ubuntu 下&#xff0c;谷歌拼音输入法是基于Fcitx输入法的。所以&#xff0c;首先需要安装Fcitx。一般来说&#xff0c;Ubuntu最新版中都默认安装了Fcitx&#xff0c;但是为了确保一下&#xff0c;我们可以在系统终端中运行如下命令&#xff1a; sudo apt ins…

Matlab|【EI复现】电动汽车集群并网的分布式鲁棒优化调度模型

目录 1 内容简介 2 关键知识点 2.1 三类电动汽车模型 3 程序结果 4 下载链接 1 内容简介 电动汽车的数据模型种类繁多&#xff0c;但是用到比较高阶数学方法的并不多&#xff0c;本次分享的程序是下图所示的文章。 采用分布鲁棒优化模型&#xff0c;用到鲁棒对等转换&…

latex绘图中\begin{figure}[htbp]中的htbp什么意思

在LaTeX中&#xff0c;\begin{figure}[htbp] 用来开始一个图形环境&#xff0c;其中 [htbp] 是一个位置参数&#xff0c;用来指导LaTeX如何放置这个图形。 具体来说&#xff0c;[htbp] 中的每个字母代表一个放置选项&#xff1a; h&#xff1a;代表“here”&#xff0c;意味着…

vscode 配置 c 语言 问题解决

1.VS code调试时显示Unable to start debugging.The value of miDebuggerPath is invalid。 解决方法: VS code调试时显示Unable to start debugging.The value of miDebuggerPath is invalid_unable to start debugging. the value of midebugger-CSDN博客 2.VSCode运行C终端…

数据库系统原理实验报告1 | E-R图设计

整理自博主2021级专业课《数据库系统原理》自己完成的实验报告。 目录 一、实验目的 二、实验内容 1、某个学校有若干个系 2、某工厂生产若干产品 3、某学校的田径运动会中设置了各类比赛 4、自己调查一个需要提供开发数据库应用系统的单位 三、实验结果总结 四、实验结…

分布式之SpringCloud

一、SpringCloud 1、SpringCloud是什么 Spring Cloud是一系列框架的有序集合&#xff0c;这些框架为我们提供了分布式系统构建工具。 2、SpringCloud包含那些项目 项目项目名称服务注册于发现Alibaba Nacos、Netflix Eureka、Apache Zookper分布式配置中心Alibaba Nacos、S…

架构面试题汇总:网络协议34问(七)

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 网络协议是实现各种设备和应用程序之间顺畅通信的基石。无论是构建分布式系统、开发Web应用&#xff0c;还是进行网络通信&#x…

前端vite+vue3——可视化页面性能耗时指标(fmp、fp)

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐可视化fmp、fp指标&#x1f496; MutationObserver 计算 dom的变化&#x1f496; 使用条形图展示 fmp、fp时间 ⭐项目代码⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于 前端vitevue3——可视化页面性能耗时…

微信小程序开发系列(二十五)·wxml语法·条件渲染wx:if, wx:elif, wx:else 属性组以及hidden 属性的使用

目录 1. 使用 wx:if、wx:elif、wx:else 属性组 2. 使用 hidden 属性 条件渲染主要用来控制页面结构的展示和隐藏,在微信小程序中实现条件渲染有两种方式: 1. 使用 wx:if, wx:elif, wx:else 属性组 2. 使用 hidden 属性 wx:if 和 hidden 二者的区别&#xff1a; 1. wx…

VMware 安装 MS-DOS 6.22

VMware 安装 MS-DOS 6.22 0. 准备工作创建虚拟机下载安装包装入安装软盘 1. 安装界面2. 创建分区3. 格式化分区4. 安装参考资料 提醒&#xff1a;如果你是为了 int 13 来的&#xff0c;我试了没用 0. 准备工作 创建虚拟机 先创建一个 DOS 虚拟机右健我们创建的DOS 虚拟机 >…

15.Webpack进阶使用

一.Webpack 自动生成 html 文件 1. 插件 html-webpack-plugin 作用&#xff1a; 在 Webpack 打包时生成 html 文件&#xff0c;并引入其他打包后的资源 2. 步骤&#xff1a; 下载 html-webpack-plugin 本地软件包到项目中 npm i html-webpack-plugin --save-dev配置 webpack…

DataGrip 2023:让数据库开发变得更简单、更高效 mac/win

JetBrains DataGrip 2023是一款功能强大的数据库IDE&#xff0c;专为数据库开发和管理而设计。通过DataGrip&#xff0c;您可以连接到各种关系型数据库管理系统(RDBMS)&#xff0c;并使用其提供的一组工具来查询、管理、编辑和开发数据库。 DataGrip 2023软件获取 DataGrip 2…

TimescaleDB 集群安装部署

文章目录 1.安装环境2.安装TimescaleDB2.1. 安装PostgreSQL2.2.安装Timescaledb插件2.3.配置TimescaleDB 3.集群配置4.创建数据库5.测试 开源中间件 # TimescaleDBhttps://iothub.org.cn/docs/middleware/ https://iothub.org.cn/docs/middleware/timescale/timescale-deploy-…

SPI读取TLE5012B编码器角度数据_ACM32G103

买到一个带编码器的电机&#xff0c;卖家留出来读取编码器数据的接口有SPI 具体的原理图如下&#xff1a; P2标注的是SPI的接口&#xff0c;其中MISO和MOSI是硬件连接到一起的 使用ACM32G103的硬件SPI2去读取其角度数据 原理大概如下&#xff1a; 1、先发送读取数据的指令&…