[C语言]指针进阶详解

指针是C语言的精髓所以内容可能会比较多,需要我们认真学习


目录

1、字符指针

2、指针数组

3、数组指针

3.1数组指针的定义

3.2&数组名vs数组名

3.3数组指针的使用 

4、数组传参和指针传参

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5、函数指针

6、函数指针数组

7、指向函数指针数组的指针

8、回调函数

8.1回调函数定义

8.2qsort库函数的用法

1、字符指针

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

一般使用:

int main()

{

  char ch='w';

  char* pc=&ch;//*表示pc是指针,char表示pc指向的对象ch他的类型是char

  return 0;

}

#include<stdio.h>
int main()
{
  char* pc="abcdef";
  printf("%s\n",pc);
  return 0;
}

看这样一个代码的运行结果:

 这里需要注意pc里面放的并不是整个字符串,而是a的地址,非要放在pc(4字节)中是放不下的,而打印的是字符串(%s),我们只要知道首字符a的地址就能向后访问得到整个字符串的地址在char* 前面加上const会更加好一些。看这样一道题:

#include<stdio.h>
int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	if (p1 == p2)
		printf("p1==p2\n");
	else
		printf("p1!=p2");
	if (arr1 == arr2)
		printf("arr1==arr2");
	else
		printf("arr1!=arr2");
	return 0;
}

 这说明p1和p2指向同一个字符a,字符串abcdef是常量字符串,放在内存中的只读内存区,不能改变它的值,没有必要存在多份,只在内存中存一份,所以p1和p2都指向a的地址,p1就等于p2.而arr1和arr2不相等,是因为arr1[ ]和arr2[ ]是两个独立的数组,,每一个都在内存中开辟了一份独立的空间,所以地址肯定是不相同的。

2、指针数组

顾名思义指针数组就是存放指针的数组(本质是一个数组)

int* arr1[10];//整型指针的数组

char* arr2[10];//一级字符指针的数组

char **arr[5];//二级字符指针的数组 

#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 2,3,4,5 };
	int arr3[] = { 3,4,5,6 };
	int* arr[3] = { arr1,arr2,arr3 };//每个数组的首元素地址,每个元素的类型是int*

	int i = 0;//代表arr的每个元素的下标
	for (i = 0; i < 3; i++)
	{
		int j = 0;//j代表的是arr1、arr2、arr3中每个元素的下标
		for (j = 0; j < 4; j++)
		{
			printf("%d ", *(arr[i] + j));//arr[i]代表各个数组的数组名,即每个数组的首元素地址,加上j并解引用就拿到了所有元素
            //因为*(p+i)等价于arr[i],所以有可以写成这样的形式
            //printf("%d ",*(*(arr+i)+j));或者
            //printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

用了一个指针数组把三个一维数组关联起来了,模拟实现二维数组,但本质上并不是二维数组,因为这三个数组在内存中并不是连续存放的但二维数组在内存中是连续存放的。

3、数组指针

3.1数组指针的定义

数组指针的本质是指针。

整型指针:int* p;能够指向整形数据的指针

浮点型指针:float* p;能够指向浮点型数据的指针

所以说数组指针就是能够指向数组的指针。比如:

int (*p)[10];(*p)代表p是指针,指向的是整型数组中的10个元素,每个元素是int类型。

注意:[ ]的优先级要高于*号,因此必须加上()来保证p先和*相结合。

3.2&数组名vs数组名

对于下面的数组:

int arr[10];

arr和&arr的区别:

我们知道arr是数组名,数组名表示数组首元素的地址。那么&arr表示的是什么?

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}
//数组名通常表示的是数组首元素的地址,但有两个例外:
//1.sizeof(数组名),这里的数组名表示的是整个数组
//2.&数组名,这里的数组名依然表示的是整个数组,所以&arr取出的是整个数组的地址

这里我们会发现运行结果显示,三个地址是完全相同的,但是&arr的步长和arr的步长是不同的,arr+1(类型是int*)跳过4个字节,但&arr+1跳过28(16进制)字节,即40字节。那么应该怎么存放整个数组的地址呢?

存放数组首元素的地址:int *p=arr;

存放整个数组的地址:int (*p)[10];(数组指针用来存放整个数组的地址)。它的类型为int (*)[10]。

3.3数组指针的使用 

#include<stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(*p + i));//因为p指向的是整个数组,(不包含数组大小),*p表示的是整个数组的内容(数组名)即首元素地址
		//也可以这样说:*p表示从p所指向的内存地址开始的数组内存的布局。这通常被理解为该数组本身,实际上它只是一个别名或引用,指向已经存在的数组
		//简单理解就是如果int* p=arr,那*p就是找到arr的内容,如果int (*p)arr[10]=&arr,则*p就是找到*&arr即arr(数组名)
	}
	return 0;
}

这样用我们会感到相当别扭,很不舒服,所以很不建议这样用! 建议大家用在二维数组上。

#include<stdio.h>
void print1(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");
	}
}
void print2(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));//p相当于是第一行的地址,解引用相当于首元素地址
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
	print1(arr, 3, 5);
	print2(arr, 3, 5);//传过去的是二维数组的首元素地址,那二维数组的首元素地址是什么呢?
	//其实是二维数组的第一行,所以传过去的其实是数组内容为5个整型的一维数组的地址
	return 0;
}

int (*parr[10])[5];parr先和[5]配对说明是个数组数组的类型是int (*)[5]是个数组指针。所以parr就是存放数组指针的数组 。

4、数组传参和指针传参

写代码时难免会把数组或指针传给函数,那么函数的参数该如何设计呢?

4.1一维数组传参

#include<stdio.h>
void test1(int arr1[]) {

}
void test1(int arr1[10]) {

}
void test1(int* arr1) {

}
void test2(int *arr2[10]) { 

} //10可以省略
void test2(int** arr2) {

}
int main()
{
    int arr1[10] = { 0 };
    int* arr2[10] = { 0 };
    test1(arr1);
    test2(arr2);
    return 0;
}

4.2二维数组传参

#include<stdio.h>
void test(int arr[3][5]) {

}
void test(int arr[][5]) {

}//二维数组传参,函数的形参设计只能省略行,因为对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素
void test(int (*p)[5]) {

}//二维数组传参传的是第一行元素的地址,需要拿一个数组指针才能接收
int main()
{
    int arr[3][5] = {0};
    test(arr);
    return 0;
}

4.3一级指针传参

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

4.4二级指针传参

#include<stdio.h>
void print(int** ptr)
{
    printf("%d\n", **ptr);
}
int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    print(&p);
    return 0;
}

当函数的参数为二级指针的时候,可以接收什么参数?

可以说指向一级指针的变量,也可以是指向一级指针的数组,二级指针变量本身

5、函数指针

指向函数的指针就叫做函数指针,用来存放函数的地址

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	return 0;
}

 由此可见打印出来的值就是Add函数的地址。对于函数来说,&函数名和函数名都是函数的地址

那怎么用指针把函数地址存起来呢?

拿此例子来说:int (*pf)(int,int)=&Add;

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	int ret = (*pf)(2, 3);//解引用就相当于找到了这个函数,*是可以省略的
	printf("%d\n", ret);
	return 0;
}

 int main()
{
    (*(void(*) () )0)();//把0强制转换成函数指针类型(我们可以认为0就是一个地址),解引用调用这个函数,但什么参数都没有传,所以本质上是一次函数调用,调用的是0作为地址处的函数

    void(* signal(int,void(*)(int) ) )(int);

    //signal与括号先结合是函数名,其中void(*)(int)是函数指针类型,去掉

    signal(int,void(*)(int) )我们会发现剩下部分也是返回也是一个函数返回类型。也就是说这个代码是一次函数声明。声明的signal函数第一个参数的类型是int,第二个参数的类型是函数指针。该函数指针指向的函数参数是int,返回类型是void;而signal函数的返回类型也是一个函数指针,该函数指针指向的参数是int,返回类型也是void。
    return 0;
}

函数指针的用途:(写一个计算器能够实现简单的加法、减法、除法、乘法)

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

6、函数指针数组

把存放函数的地址存到一个数组中,那么这个数组就叫做函数指针数组,如何定义函数指针数组?

int add(int x, int y)
{
    return x + y;
}
int sub(int x, int y)
{
    return x - y;
}
int mul(int x, int y)
{
    return x * y;
}
int div(int x, int y)
{
    return x / y;
}

int main( )

{

    int (*arr[4])(int,int)={add,sub,mul,div};//arr先于[4]结合表明本质是个数组,数组每个元素的类型为int (*)(int,int)即函数指针类型

    int i = 0;
   for (i = 0; i < 4; i++)
   {
      int ret = arr[i](8, 4);
      printf("%d ", ret);
    }

    return 0;

}

有什么用途呢?以上面模拟计算机为例,我们还可以对代码进行简化,并且增加计算机功能时也相对比较容易,极大简化了修改功能时的工作量。

#include<stdio.h>
void menu()
{
	printf("*******************\n");
	printf("****1.add 2.sub****\n");
	printf("****3.mul 4.div****\n");
	printf("****   0.exit  ****\n");
    printf("*******************\n");
}
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
int main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
    //转移表
	int (*pfarr[5])(int, int) = { 0,add,sub,mul,div };//之所以第一个元素放0,是为了,与为了调用时和菜单选项对应起来,比如输入下标为1是调用的是add函数
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出程序\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>\n");
			scanf("%d%d", &x, &y);
			ret = pfarr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误请重新选择\n");
		}
	} while (input);
	return 0;
}

这个代码现在就变得清爽了许多,非常的简洁。 

7、指向函数指针数组的指针

指向函数指针数组的指针本质上就是一个指针,指针指向一个数组,数组里面的元素都是函数指针

int main()
{
    int (*pfarr[5])(int, int) = { 0,add,sub,mul,div };
    int (*(*ppfarr)[5])(int,int) = &pfarr;
    return 0;
}

但用的相对并不多,以后会详细介绍 

8、回调函数

8.1回调函数定义

回调函数就是通过函数指针调用的函数。如果把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方式直接调用,而是在特定的事件或条件发生时有另外的一方的调用的,用于对该事件或条件进行响应。

8.2qsort库函数的用法

qsort是C标准库<stdlib.h>中的一个函数,用于对数组进行快速排序。它接受一个指向要排序的数组的指针、数组中元素的数量、每个元素的大小(字节为单位)以及一个比较函数作为参数。

//qsort的声明

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

base:指向要排序的数组的第一个元素的指针(数据的起始位置)

nmemb:数组中元素的数量

size:数组中每个元素的大小

compar:一个指向比较函数的指针,该函数用于确定元素的排序顺序 

比较函数应该接受两个只想要比较的元素的指针,并返回一个整型,表示他们的相对顺序。如果第一个元素排在第二个元素之前返回负数(第一个数小于第二个数),反之返回整数,相等则返回0.

qsort具体要怎么用呢?现在我们对整型数组arr[10]={10,9,8,7,6,5,4,3,2,1}排成升序之前我们学过冒泡排序的思想,现在我们换种方法来实现

void print(int arr[],int sz)//打印数组内容
{
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
//比较两个整型元素,e1指向一个整数,e2也指向一个整数
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);//强制类型转换成int*类型
}//如果我们像排成降序只需要把e1改成e2,把e2改成e1,逻辑相反
int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
	return 0;
}
//void* 是无具体类型的指针,不能解引用操作,也不能加减整数

qsort不仅仅可以排序整型数据也可以排结构体数据,也可以排字符数据

我们来看对结构体数据排序的实例:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct Stu
{
	char name[20];
	int age;

};
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//e1是指针变量
	//strcmp函数如果第一个字符串比第二个大返回正数,小于返回负数,否则返回0
}
int main()
{
	struct Stu s[] = { {"张三",15},{"李四",16},{"王五",18}};
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("name=%s age=%d\n",(s+i)->name,(s+i)->age);//s虽然本身不是指针,是数组名,但会"退化为"指向第一个元素的指针。这是为了与期望接受指针的函数(qsort)兼容
		//也可以用s[i].name,s[i].age的形式来访问结构体成员
	}
	return 0;
}
}

 

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Docker学习】docker run的端口映射-p和-P选项

docker run的端口映射选项分为-p&#xff08;小写&#xff0c;全称--publish&#xff09;&#xff0c;-P&#xff08;大写&#xff0c;全称--publish-all&#xff09;&#xff0c;之前认为只有改变容器发布给宿主机的默认端口号才会进行-p的设置&#xff0c;而不改变默认端口号…

现代信号处理8_递归的最小二乘(CSDN_20240505)

递归的最小二乘大约出现在50年前。递归&#xff0c;就是在已经算出的结果的基础下&#xff0c;当新的数据到来时&#xff0c;不需要再对数据进行一次完整的运算&#xff0c;而是在已有结果的基础上做一些简单的调整&#xff0c;就能得到新的结果。使用递归的好处&#xff1a; …

面试中算法(使用栈实现队列)

使用栈来模拟一个队列&#xff0c;要求实现队列的两个基本操作:入队、出队。 栈的特点&#xff1a;先入后出&#xff0c;出入元素都是在同一端&#xff08;栈顶&#xff09;。 队列的特点&#xff1a;先入先出&#xff0c;出入元素是在两端&#xff08;队头和队尾)。 分析&…

ICode国际青少年编程竞赛- Python-1级训练场-for循环与变量

ICode国际青少年编程竞赛- Python-1级训练场-for循环与变量 1、 a 1 for i in range(4):Spaceship.step(a)Dev.step(2)Dev.step(-2)a a 12、 a 1 for i in range(4):Spaceship.step(a)Dev.step(3)Dev.step(-3)a a 13、 a 1 for i in range(4):Dev.turnLeft()Dev.step(…

【机器学习】Ctrl-Adapter:视频生成领域的革新者

Ctrl-Adapter&#xff1a;视频生成领域的革新者 一、ControlNets的挑战与Ctrl-Adapter的应运而生二、Ctrl-Adapter的技术原理与实现三、Ctrl-Adapter的应用实例与性能表现四、Ctrl-Adapter的意义与未来展望 随着人工智能技术的飞速发展&#xff0c;图像与视频生成领域正经历着前…

【电源专题】拿人体的循环系统与板级电源做个比较

一般人可能会觉得电源大概是电子设备里面比较容易搞定的门类。因为,只要线路没有接错,指示灯(如果有)能亮,电源都能工作。从这个方面说,好像是很容易。但是通过多年的经验和经历的坑,发现电源其实是一个很麻烦的东西,稍微有一点不完美就会有大问题出现。 如果将人体也当…

基于Springboot的水产养殖系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的水产养殖系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

ue引擎游戏开发笔记(30)——对角色移动进行优化:实现人物转向

1.需求分析&#xff1a; 当前我们只实现了通过控制器可使角色进行前后左右的移动&#xff0c;但角色移动时与动画不匹配&#xff0c;并不会进行转向&#xff0c;实现角色随移动转向。 2.操作实现&#xff1a; 1思路&#xff1a;利用反转换函数inverse transform direction获取…

GitHub Desktop安装与使用教程

GitHub Desktop 是GitHub公司推出的一款桌面应用程序&#xff0c;旨在帮助开发人员更轻松使用GitHub。它提供了一个直观的用户界面&#xff0c;允许用户通过图形化界面来执行常见的 Git 操作&#xff0c;如克隆仓库、创建分支、提交更改、合并代码等。 GitHub Desktop 的设计使…

mac自定义快捷键打开系统应用

最终效果是达成altg直接打开浏览器&#xff0c;解放双手、再也不需要移动鼠标双击打开应用啦&#xff01;&#xff01;&#xff01;&#xff5e; 1.commandspace输入自动操作 2.选择快速操作 3.选择使用工具、运行appleScrpit 4.输入打开浏览器代码 tell application "G…

Day31:单元测试、项目监控、项目部署、项目总结、常见面试题

单元测试 保证独立性。 Assert&#xff1a;断言&#xff0c;一般用来比较是否相等&#xff0c;比如 Assert.assertEquals 在JUnit测试框架中&#xff0c;BeforeClass&#xff0c;Before&#xff0c;After和AfterClass是四个常用的注解&#xff0c;它们的作用如下&#xff1a; …

FFmpeg学习记录(四)——SDL音视频渲染实战

1.SDL使用的基本步骤 SDL Init/sDL _Quit()SDL_CreateWindow()/SDL_DestoryWindow()SDL CreateRender() SDL_Windows *windows NULL;SDL_Init(SDL_INIT_VIDEO);window SDL_CreateWindow("SDL2 Windows",200,200, 640,480,SDL_WINDOW_SHOWN);if(!window) {printf(&…

手撕netty源码(四)- ServerBootstrap是如何监听事件的

文章目录 前言一、OP_ACCEPT事件注册1.1 bind 完成之后监听OP_ACCEPT1.2 register0注册完成之后监听OP_ACCEPT 二、事件处理在这里插入图片描述 三、总结 前言 文档中的图片如果不清晰可以直接在线看processOn processOn文档跳转 接上一篇&#xff1a;手撕netty源码&#xff0…

基于Springboot的校园疫情防控系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

力扣每日一题112:路径总和

题目 简单 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是…

FilterListener详解

文章目录 MVC模式和三层架构MVC模式三层架构MVC和三层架构 JavaWeb的三大组件Filter概述快速入门过滤器API介绍过滤器开发步骤配置过滤器俩种方式修改idea的过滤器模板 使用细节生命周期拦截路径过滤器链 案例统一解决全站乱码问题登录权限校验验 ServletContextServletContext…

多器官和多模态图像的通用异常检测模型-不受特定模型约束

文章目录 A Model-Agnostic Framework for Universal Anomaly Detection of Multi-organ and Multi-modal Images摘要方法实验结果 A Model-Agnostic Framework for Universal Anomaly Detection of Multi-organ and Multi-modal Images 摘要 背景与挑战&#xff1a;深度学习在…

Android Binder机制

一.简介 Binder是什么&#xff1f; Android系统中&#xff0c;涉及到多进程间的通信底层都是依赖于Binder IPC机制。 例如当进程A中的Activity要向进程B中的Service通信&#xff0c;这便需要依赖于Binder IPC。不仅于 此&#xff0c;整个Android系统架构中&#xff0c;大量采…

520表白代码

一、以下代码用html及css编写 代码用记事本打开可直接使用 二、效果如下 代码如下&#xff0c;以下代码复制记事本里面&#xff0c;文件名称后缀改成.html格式&#xff0c;即可运行 名称可在记事本里进行更改 文件名称更改后&#xff0c;文件会变成如下图所示的样式 <ht…

Initialize failed: invalid dom.

项目场景&#xff1a; 在vue中使用Echarts出现的错误 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 例如&#xff1a;在vue中使用Echarts出现的错误 ERROR Initialize failed: invalid dom.at Module.init (webpack-internal:///./node_modules/echarts…