C语言——指针进阶(含例题及详细代码分析)

news2024/11/25 16:19:42

文章目录

    • 导语:
    • 思维导图:
    • 1.字符指针
    • 2.指针数组
    • 3.数组指针
      • 3.1 数组指针的定义
      • 3.2 &数组名和数组名
      • 3.3 数组指针的使用
    • 4.数组参数、指针参数
      • 4.1 一维数组传参
      • 4.2 二维数组传参
      • 4.3 一级指针传参
      • 4.4 二级指针传参
    • 5.函数指针
    • 6.函数指针数组
    • 7.指向函数指针数组的指针
    • 8.回调函数
    • 结语

导语:

之前有一篇文章给大家讲解了指针的概念(链接:指针的疑难杂症):

1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

那本篇文章,讲解深层次的指针概念。

思维导图:

在这里插入图片描述

1.字符指针

字符指针的一般用法:

int main()
{
	char ch = 'q';
	char* pc = &ch;
	//*pc = 'q'
	return 0;
}

还一种使用方法:

int main()
{
	char* p = "abcdef";
	return 0;
}

注意,在这里不是"abcdef"赋给了p,"abcdef"是占7个字节(包含\0),而我们知道,指针变量是4个字节,那p肯定是放不下的。

在这里,"abcdef"是一个常量字符串,当常量字符串出现在表达式里面的时候,其实这个字符串的值是首元素的地址。

在这里插入图片描述
既然刚刚说了"abcdef"是常量字符串,常量是不能修改的,那么上面的这种写法就是不够标准,应该改为:

int main()
{
	const char* p = "abcdef";
	return 0;
}

有了这个知识,我们来看一道例题:

#include <stdio.h>
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

输出:
在这里插入图片描述

分析:
str1和str2是两个数组,各自独立占一块空间;
str3和str4是指针,指向的是一个常量字符串,常量字符串不可被修改,在内存中只会开辟一个空间;
那么在比较的时候,str1和str2比较的是首元素地址,因为是2块空间,所以肯定不一样;str3和str4比较的是首元素地址,指向的是同一块空间。

在这里插入图片描述

2.指针数组

指针数组是存放指针的数组

int main()
{
	int* arr1[10]; //整型数组  存放整型的数组
	char* arr2[10];//字符数组  存放字符的数组
	
	char ch1[] = "qwe";
	char ch2[] = "asd";
	char ch3[] = "zxc";
	char* arr3[3] = { ch1,ch2,ch3 }; //指针数组  存放指针的数组
	//指针数组的访问
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr3[i]);
	}
	return 0;
}

在这里插入图片描述

3.数组指针

3.1 数组指针的定义

数组指针是数组?还是指针?
答案是:指针

int main()
{
	int* pi; //整型指针 -- 存放整型地址的指针 -- 指向整型的指针
	char* pc;//字符指针 -- 存放字符地址的指针 -- 指向字符的指针

	int arr[10];
	int(*pa)[10] = &arr;//数组指针 -- 存放数组地址的指针 -- 指向数组的指针
	return 0;
}

在这里,是需要区分这两端代码p1和p2到底代表着什么?

int main()
{
	int* p1[10];
	int(*p2)[10];
	return 0;
}

首先要知道,[ ] 优先级是要高于 * 号
int* p1[10] ,p1优先和数组结合,那么此时p1就是一个数组,里面存放的内容都是指针类型,所以p1是一个数组,里面存放的内容是指针的地址,叫指针数组。
int(*p2)[10],在这里 *号优先和p2结合,那么p2此时就是一个指针变量,指向的是一个大小为10的整型数组,所以p2是一个指针,指向一个数组,叫数组指针。

3.2 &数组名和数组名

我们知道,数组名就是数组首元素的地址,那么&数组名又是什么意思呢?
在这里插入图片描述
通过编译器,我们可以看出数组名和&数组名指向的都是首元素的地址,那他们的意义是一样的吗?
在这里插入图片描述

将他们的地址都+1,我们发现arr+1是跳过4个字节,而   ~  &arr+1是跳过40个字节

那么我们就可以得出结论:
数组名是数组首元素的地址。
&数组名是整个数组的地址。
数组首元素地址虽然和整个数组的地址从值的角度来看,虽然是一样的,但是意义不相同。

这里还一点就是指针类型决定了指针+1到底偏移的是几,这里数组名和&数组名的类型是不一样的。
在这里插入图片描述

数组名的类型是 int*,首元素的地址也是int*
而&数组名类型是int(*)[10]

3.3 数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* pa = arr;
	int(*p)[10] = &arr;
	int i = 0;
	//一般访问
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(pa + i));
	}
	printf("\n");
	//数组指针使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);
	}
	return 0;
}

但我们发现在这里使用数组指针特别的别扭,有点脱裤子放屁的感觉,很鸡肋。其实数组指针在一维数组运用比较少,在二维数组的运用会多一点

int main()
{
	int arr[3][4] = { {1,2,3,4 },{2,3,4,5 },{3,4,5,6} };
	int(*p)[4] = &arr;
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", (*(p+i))[j]);
			//printf("%d ", p[i][j]);  写出这种形式也行
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述
既然了解了指针数组和数组指针的概念,那我们再来看看下面这段代码的意思:

int main()
{
	int arr[5]; //整型数组 -- 元素是5个
	int* parr1[10];//指针数组 -- 可存放10个整型指针
	int(*parr2)[10];//数组指针 -- 指向一个数组 -- 该数组元素为10个,每个元素为整型
	
	//[]的优先级高于*,使用parr3先和[]结合
	//parr3是数组,数组有10个元素
	//数组的每个元素是int(*)[5]的数组指针类型
	int(*parr3[10])[5];
	return 0;
}

在这里插入图片描述

4.数组参数、指针参数

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

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//形参部分写出数组,可以不指定大小(√)
{}
void test(int arr[10])//形参部分是数组接收,10个元素,与传递过来的数组一致(√)
{}
void test(int* arr)//传过来的是数组名,数组名是首元素地址,用整型指针接收(√)
{}
void test2(int* arr[20])//形参与传递过来的保持一致(√)
{}
void test2(int** arr)//传的是数组名,首元素地址,传过来的类型是int*一级指针,用二级指针接收(√)
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr); //数组传参,传的数组名,也是首元素的地址
	test2(arr2);//数组传参,传的数组名,也是首元素的地址
	return 0;
}

4.2 二维数组传参

//数组形式
void test(int arr[3][5])//形参与传递过来的参数保持一致,标准写法 (√)
{}
void test(int arr[][])//二维数组行可以省略,但列不能省略,该处都省略了 (×)
{}
void test(int arr[][5])//二维数组可以省略,但列不能省略 (√)
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
//指针形式
void test(int* arr)//形参写成了整型一维数组的接收方式 (×)
{}
void test(int* arr[5])//形参是指针数组,是数组,但不是二维数组;能存指针,但不是指针 (×)
{}
void test(int(*arr)[5])//形参是数组指针,数组5个元素,每个元素是int类型(√)
{}
void test(int** arr)// 传过来的是首行的地址,而二级指针是接收一级指针的地址 (×)
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);//传的是二维数组
}

4.3 一级指针传参

void print(int* p, int sz)//一级指针传递,一级指针接收,没什么高科技
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

那我们想一想,当一个函数的参数部分为一级指针的时候,函数能接收什么参数呢?

void test(int* p)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int arr[5] = { 1,2,3,4,5 };
	test(&a);//整型变量的地址
	test(p);//一级指针
	test(arr);//一维数组的数组名
	return 0;
}

4.4 二级指针传参

void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);//二级指针
	test(&p);//一级指针的地址
	return 0;
}

那我们再思考,当一个函数的参数部分为二级指针的时候,函数又能接收什么参数呢?

void test(int** p)
{}
int main()
{
	int** ptr;
	int* pp;
	int* arr[5];
	int arr[3][3];
	test(ptr);//二级指针
	test(&pp);//一级指针的地址
	test(arr);//指针数组的数组名
	return 0;
}

5.函数指针

在上面我们讲了,数组指针是指向数组的指针;那么函数指针顾名思义就是指向函数的指针
函数还有地址吗?我们通过编译器,看看是否能将函数的地址取出:
在这里插入图片描述
我们发现确实可以拿到函数的地址,那么函数的地址是否存起来呢?答案是肯定的,是地址,我们就可以存起来,语法规则如下:

int Add(int x,int y)
{

	return x + y;
}
int main()
{
	int(*pf)(int, int) = &Add;
	//pf是存放一个函数地址的变量 -- 函数指针
	
	//int(*pf)(int, int) = Add;
	//&函数名和函数名都代表函数的地址,这里 没 有 区 别!
	return 0;
}

函数指针与数组指针的书写十分类似

数组指针书写:
int arr[10];
int(*pa)[10] = &arr;
函数指针的书写:
int(*pf)(int, int) = &Add;

在这里插入图片描述
既然能拿到函数的地址,也能将函数的地址存起来,那是否能通过地址调用这块函数呢?答案也是肯定的。
请添加图片描述
通过调试,可以观察到确实可以通过pf指针去调用这个函数。
另外,既然&函数名和函数名一样,都代表着函数的地址,那么其实我们的写法可以更加简便一点:

int Add(int x, int y)
{

	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;
	//Add赋给pf,那pf就和Add其实是一回事
	//int ret = Add(1,2); //原先调用Add函数的写法
	//那么pf也可以这样写
	int ret = pf(1, 2);
	printf("%d\n", ret);
	return 0;
}

注意:(*pf)这里的 * 可以不写,但是如果要写上,一定要加上括号(*pf)

阅读(坑X)两段有趣的代码:

//代码1
(*(void (*)())0)();

在这里插入图片描述

该代码是一次函数调用;
首先将0先强制类型转换成一个函数指针类型,把0当成某个函数的地址,那个函数没有参数,返回类型为void;
然后*解引用调用函数,函数是无参的,所以最后面直接写括号,没有参数。

//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述

该代码是一次函数声明;
声明的函数名字叫signal;
signal有2个参数,一个是int类型,一个是函数指针类型,该函数指针指向的函数参数为int,返回类型为void;
signal函数的返回类型是一个函数指针,该函数指针指向的函数参数为int,返回类型为void。

其实在这里,代码还是十分的难以理解,我们可以将其简化:

//将函数指针类型改名为pf_t
typedef void(*pf_t)(int);
//signal一个参数为int,另一个为pf_t,返回类型也是pf_t
pf_t signal(int, pf_t);

6.函数指针数组

函数指针数组,见名知意,是存放函数指针的数组,既然了解了函数指针的概念,那么这个写法,在函数指针的基础上再改造一下:

void test1()
{}
void test2()
{}
void test3()
{}
int main()
{
	//函数指针
	void(*pf1)() = &test1;
	void(*pf2)() = &test2;
	void(*pf3)() = &test3;
	//函数指针数组
	//pf先与[]结合,说明是一个数组,该数组里面存放的内容是函数指针类型
	void(*pf[3])() = { pf1,pf2,pf3 };//存放了3个函数的地址
	return 0;
}

函数指针数组的用途:转移表
我们在这里模拟一个简易计算器的程序,这是一般写法:

void menu()
{
	printf("*********************\n");
	printf("*   1.add   2.sub   *\n");
	printf("*   3.mul   4.div   *\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()
{
	menu();
	int input = 0;
	int a = 0;
	int b = 0;
	do
	{
		printf("请选择:");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			printf("请输入两个操作数:");
			scanf("%d %d", &a,&b);
			printf("%d\n", add(a, b));
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			printf("%d\n", sub(a, b));
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			printf("%d\n", mul(a, b));
			break;
		case 4:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			printf("%d\n", div(a, b));
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

这个写法其实是十分的冗余,我们了解函数指针数组之后,就可以简化很多:

void menu()
{
	printf("*********************\n");
	printf("*   1.add   2.sub   *\n");
	printf("*   3.mul   4.div   *\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()
{
	menu();
	int input = 0;
	int a = 0;
	int b = 0;
	//将函数的地址全部放入函数指针中去
	int(*pf[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			int ret = pf[input](a,b);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误,重新输入\n");
		}
	} while (input);
	return 0;
}

在这里插入图片描述

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

看到这个是不是感觉有点套娃的感觉,如果不是很好理解,其实先可以巧记,我们就看最后两个字是数组还是指针,指向函数指针数组的指针,它是一个指针。

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

将这段代码拆分就会更好理解一点,这里要一步一步的写,这样不容易出错,思路也会更清晰一点。
在这里插入图片描述

8.回调函数

前面的函数指针、函数指针数组的内容,其实基本上会运用于回调函数的实现。

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

还是以简易计算器为例:
在这里插入图片描述
红框里面的代码,相似度非常高,逻辑也是十分的类似,那么其实可以将其封装为一个函数:

void menu()
{
	printf("*********************\n");
	printf("*   1.add   2.sub   *\n");
	printf("*   3.mul   4.div   *\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(*pf)(int, int))
{
	int a = 0;
	int b = 0;
	printf("输入2个操作数:");
	scanf("%d %d", &a, &b);
	printf("%d", pf(a, b));
}
int main()
{
	menu();
	int input = 0;
	do
	{
		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;
}

回调逻辑:
在这里插入图片描述
在这里确实并没有直接去调用add函数,而是把函数的地址传递给另一个函数,然后在这个函数内部通过函数指针去调用add函数。
换言之,回调函数是一个中介,现实生活中,通过找中介办事,这样自己会轻松很多,但同时会被赚差价。那么运用到这里,就是代码的通用性更加高,但是调来调去,运行速度,自然会降低。

结语

   ~~   指针是C语言的重点内容,不要想的太复杂,也不要不敢面对,指针它就是个地址,没什么高科技,将内容梳理一下,采用零敲牛皮糖战术,一点一点的消化。
   ~~   那么本期方向就到这里,有帮助的话,三连支持一下,谢谢,再见!

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

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

相关文章

Django自定义认证系统原理及源码分析解读

疑问 Django在如何自定义用户登录认证系统的时候&#xff0c;大家都会里面立马说 自定义一个 或者多个backend&#xff0c;比如通过账号密码、邮箱密码&#xff0c;邮箱验证码、手机号短信验证码等等。 然后设置 在settings中配置一个 AUTHENTICATION_BACKENDS就行。 但是为什…

【尚硅谷】Java数据结构与算法笔记11 - 树结构的实际应用

文章目录一、堆排序1.1 堆排序基本介绍1.2 堆排序的基本思想1.3 堆排序步骤图解1.4 堆排序思路总结1.5 堆排序代码实现二、赫夫曼树2.1 基本介绍2.2 重要概念1.3 赫夫曼树构建思路图解1.4 赫夫曼树代码实现三、赫夫曼编码3.1 基本介绍3.2 原理剖析3.3 实践&#xff1a;数据压缩…

java运算符2023010

运算符&#xff1a; Java语言中的运算符可分为如下几种。 ➢ 算术运算符 ➢ 赋值运算符&#xff0c;/—/各种和等号组合的都是赋值运算符&#xff0c;赋值表达式是有值的&#xff0c;赋值表达式的值就是右边 被赋的值。例如String str2str表达式的值就是str。因此&#xff0c;赋…

Java技能树-操作符(一)-练习篇

算术运算符 执行完下面的代码&#xff0c;变量b的值是&#xff1a; java int a 1; int b a; 答案是&#xff1a;D 在后,先赋值再运算 自动递增和递减 下面代码执行后的结果是&#xff1a; int a 0; a a; int b 0; b b; System.out.println("a " a); S…

Numpy的轴及numpy数组转置换轴

Numpy的轴 import numpy as np 数组np.array([[[1,2],[4,5],[7,8]],[[8,9],[11,12],[14,15]],[[10,11],[13,14],[16,17]],[[19,20],[22,23],[25,26]]]) print(数组.shape) # 返回 (4, 3, 2)最内层一对 [ ] 可以代表一个1维数组 加粗的一对 [ ] 里面有3个一维数组&#xff0c;也…

Layout布局(element ui)

Layout布局嘚吧嘚gutter示例发现el-row行内容居中默认局左上角水平居中垂直居中水平垂直居中嘚吧嘚 其实layout布局的使用在element官网上都有相关描述&#xff0c;也有相关示例&#xff0c;很容易快速上手。但是在实际使用的过程还是发现一些问题&#xff0c;于是做了一些学习…

递归(基础)

目录 一、递归的定义 1、什么时候会用到递归的方法 1. 定义是递归的 2. 数据结构是递归的 3. 问题的解法是递归的 2、应用递归的原则 3、递归调用顺序问题 1. 首先递归的过程可以总结为以下几点&#xff1a; 2. 递归工作栈​​​​​​​ 二、 递归和非递归的转化 …

Allegro如何快速把Class高亮成不同的颜色操作指导

Allegro如何快速把Class高亮成不同的颜色操作指导 在做PCB设计的时候,高亮Class组是一个非常频繁的操作,Allegro支持快速的将Class高亮成不同的颜色,并且还可以形成一个列表,如下图 具体操作如下 选择File选择Change Editor

select ( ) for update 锁行还是锁表?

select &#xff08; &#xff09; for update 锁行还是锁表&#xff1f; 一、验证 创建SQL表 //id为主键 //name 为唯一索引 CREATE TABLE user (id INT ( 11 ) NOT NULL AUTO_INCREMENT,name VARCHAR ( 255 ) DEFAULT NULL,age INT ( 11 ) DEFAULT NULL,code VARCHAR ( …

SpringCloud微服务项目实战 - 6.延迟任务

我没有失约&#xff0c;我与春风共至&#xff0c;我与夏蝉共鸣&#xff0c;我与秋叶共舞&#xff0c;我与凛冬共至&#xff0c;唯独你背道而行&#xff01; 系列文章目录 项目搭建App登录及网关App文章自媒体平台&#xff08;博主后台&#xff09;内容审核(自动)延迟任务 - 精…

JVM快速入门学习笔记(一)

参考&#xff1a; https://blog.csdn.net/m0_38075425/article/details/81627349 www.kuangstudy.com JVM 常问面试题 请你谈谈你对JVM的理解&#xff1f; java—>class---->Java8虚拟机和之前的变化更新&#xff1f;什么是OOM 内存溢出什么是栈溢出StackOverFlowErr…

Matplotlab绘制散点图小节

前言现有一堆数据&#xff0c;是散点坐标形式&#xff0c;现在需要将它们绘制成散点图&#xff0c;并解决了关于Matplotlib绘图不能显示汉字的问题。读取数据数据格式如下图。第一行为一个数字&#xff0c;表示当前文件共有多少行数据。 第二行开始为真正的数据&#xff0c;各数…

如何冻结Excel中的行

在Excel中有一个冻结行的功能。在冻结行的帮助下,我们可以固定我们选择的窗格或行,以超出特定的限制工作表。 可以从“视图”菜单选项卡的“窗口”部分的“冻结窗格”下拉列表中访问“冻结行”。首先,要冻结列,请选择要冻结的列或将光标放在该列的任何位置,然后从列表中选…

vue 使用hook 对 chekbox 做简单的逻辑抽离,一个核心多套模板

现在的组件库都会包含些相同的基础组件&#xff0c;功能大差不差&#xff0c;只是不同UI规范下的具体实现。这些基础组件基本能满足大部分的开发需求。 但世上无银弹&#xff0c;有时我们需要对组件做细微的调整可能是功能上的&#xff0c;可能是UI上的&#xff0c;例如 tab切换…

JavaWeb基础(三) Request和Response详解

JavaWeb基础(三) Request和Response详解 1&#xff0c;Request和Response的概述 Request是请求对象&#xff0c;Response是响应对象。 此时&#xff0c;我们就需要思考一个问题request和response这两个参数的作用是什么? request: 获取请求数据 浏览器会发送HTTP请求到后台…

跨站脚本攻击漏洞(XSS)-基础篇

数据来源 跨站脚本攻击 1、什么是跨站脚本攻击? 跨站脚本( Cross-site Scripting)攻击&#xff0c;攻击者通过网站注入点注入客户端可执行解析的 payload&#xff08;脚本代码&#xff09;&#xff0c;当用户访问网页时&#xff0c;恶意 payload自动加载并执行&#xff0c;…

索引(index)

索引&#xff08;index&#xff09; 1、什么是索引&#xff1a; 索引是在数据库表的字段上添加的&#xff0c;是为了提高查询效率存在的一种机制。一张表的一个字段可以添加一个索引&#xff0c;当然多个字段联合起来也可以添加索引&#xff0c;索引相当于一本书的目录&#xf…

Spring Boot 3 步完成日志脱敏,简单实用!

在我们写代码的时候&#xff0c;会书写许多日志代码&#xff0c;但是有些敏感数据是需要进行安全脱敏处理的。 对于日志脱敏的方式有很多&#xff0c;常见的有&#xff1a; 使用conversionRule标签&#xff0c;继承MessageConverter 书写一个脱敏工具类&#xff0c;在打印日志…

springboot+mybatisplus实现分页

在日常开发中&#xff0c;多记录的列表查询可能会遇到分页处理的场景&#xff0c;在springboot项目中传统是引入mybatis组件进行持久化&#xff0c;然后通过pagehelper组件进行分页实现。下面体验一下在springboot项目中引入mybatisplus组件&#xff0c;通过其自带分页插件实现…

cpu简述--指令集架构

很多初级开发者其实都对cpu了解不多&#xff0c;个人兴趣原因想要了解一下cpu的相关知识&#xff0c;所以开几篇文章记录一下吧。 2002年8月10日,中国科学院计算技术研究所的青年科学家胡伟武带领研制组,研制出我国首枚拥有自主知识产权的通用高性能微处理芯片——“龙芯一…