【C语言】指针篇(指针数组,数组指针,函数指针,一级、二级指针)

news2024/12/29 10:23:22

文章目录

  • 一、指针基础
    • 1.什么是指针
    • 2.指针的定义和初始化
    • 3.指针的解引用
    • 4.野指针和空指针
    • 5.指针的类型
    • 6.指针的大小
    • 7.指针的运算
    • 8.指针和数组
    • 9.指针和字符串
    • 10.二级指针
  • 二、指针数组和数组指针
    • 1.指针数组
    • 2.数组指针
    • 3.练习
  • 三、数组传参和指针传参
    • 1.一维数组传参
    • 2.二维数组传参
    • 3.一级指针传参
    • 4.二级指针传参
  • 四、函数指针
  • 五、函数指针数组
  • 六、指向函数指针数组的指针
  • 七、回调函数

一、指针基础

1.什么是指针

C语言中指针是一种数据类型,指针是存放数据的内存单元地址。

指针是内存中一个最小单元(1个字节)的编号,也就是地址
平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个
变量就是指针变量。

后面提到的指针均是指 指针变量。

总结:指针就是地址。

2.指针的定义和初始化

语法格式:数据类型 *指针变量名[=初始地址值]。

int a = 10;
int *pa = &a;

指针变量除了可以存放变量的地址外,还可以存放其他数据的地址,比如可以存放数组和函数的地址,后面都会讲解。

指针变量的初始化,除了可以是已定义变量的地址,也可以是已初始化的同类型的指针变量,也可以是NULL(空指针)。在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL 值是一个良好的编程习惯。

int *p = NULL;

3.指针的解引用

取地址操作符&和解引用操作符*是搭配使用的。

int a = 10;
int* pa = &a;
printf("%d\n", *pa);

将a的地址通过&存放到指针变量pa中,再对pa解引用找到地址中的内容:a中存放的数据10

4.野指针和空指针

空指针:指针赋值为NULL,不指向任何空间。

对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何东西,因为对一个NULL指针解引用是一个非法的操作,所以在解引用之前,必须确保它不是一个NULL指针。

野指针:

野指针就是指针指向的位置是不可知的。

野指针是学习C语言中比较常犯的一个错误,野指针分为三种:

1.指针未初始化就使用

int *p;//局部变量指针未初始化,默认为随机值
*p = 10;

2.指针越界访问

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

3.指针指向的空间释放后未置空

指针指向的空间释放后指向的就是无效内存,这时指针应立即置NULL,不然会成为野指针。
具体内容等后面动态内存开辟的时候总结。

规避野指针:

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址 (当被调函数结束后 ,栈区中局部变量的内存空间被释放)
  5. 指针使用之前检查有效性 (是否为NULL)

5.指针的类型

指针的类型与指针指向的类型是不一样的,一定要区分清楚!

从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。
把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

例如:

int* ptr; //指针的类型是int*	 指针所指向的类型是int
char* ptr;//指针的类型是char*  指针所指向的类型是char
int** ptr;//指针的类型是int**  指针所指向的类型是int*

指针的类型决定了指针向前或者向后走一步有多大距离

在这里插入图片描述

char* 类型的指针存放 char 类型变量的地址,每走一步跳过一个char类型即1个字节。
short* 类型的指针存放 short 类型变量的地址,每走一步跳过一个short类型即2个字节。
int* 类型的指针存放 int 类型变量的地址,每走一步跳过一个int类型即4个字节。

6.指针的大小

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平和低电平(1或者0),那么32根地址线产生就会产生2^32 次方个地址,内存中是以字节为单位的,也就是会有 2^32 个字节。2^32 Byte = 2^22 KB = 2^12 MB = 4GB 的空间进行编址;同样64位机器中可以编址8GB空间。

所以32位平台上,一个指针变量的大小就是4字节;64位平台上是8字节。

7.指针的运算

指针 + - 整数: 上面分析指针的类型中提过,指针的类型决定了指针+1或者-1移动的距离就是指针所指向类型的大小。比如:int* 类型的指针+1 则往后走了4个字节,char* 类型指针-2 则往前移动了2个字节。

指针 - 指针:两个指针相减代表指针之间所经历的元素(由参与运算的指针类型决定)个数
例如写一个求字符串长度的函数:

int my_strlen(char *s)
{
	char *p = s;
	while(*p != '\0' )
	{
		p++;
	}
	return p-s;
}

8.指针和数组

前面讲到过,指针的类型决定了指针向前或者向后走一步有多大距离,比如:char* 类型的指针存放 char 类型变量的地址,每走一步跳过一个char类型即1个字节;int* 类型的指针存放 int 类型变量的地址,每走一步跳过一个int类型即1个字节。 所以,我们可以也通过指针来访问数组

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//数组首元素的地址存放p中
	for (int i = 0; i < 10; i++)
	{
		//printf("%d ", arr[i]);或者printf("%d ", *(arr + i));
		printf("%d ", *(p + i));//printf("%d ", p[i]);也是正确的
	}
	return 0;
}

上述中四种写法都是正确的,在数组篇我们了解到,除了&(数组名)和sizeof(数组名)这两种特殊情况外,其他情况下数组名表示首元素的地址指针变量p也表示数组首元素的地址,所以这四种写法本质上都是一样的。

9.指针和字符串

char* ps = "abcdef";
printf("%s\n", ps);
char s[] = "abcdef";
char* ps = s;
printf("%s\n", ps);

注:并不是将字符串赋予指针变量,而是将存放字符串的连续内存单元的首地址赋予指针变量。

看下面这道例题:

在这里插入图片描述

p1和p2指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当
几个指针指向同一个字符串的时候,他们实际会指向同一块内存。所以p1和p2相等。
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块(数组存放在栈区),所以s1与s2的地址不同。

可修改性

char* ps = "hello";
p[1] = 'a';//错误

char str[] = "hello";
str[0] = 'w';//正确

因为ps指向的是一个字符串常量,字符串字面量通常存储在程序的只读内存区域(如常量区),因此不能修改。而str是一个字符数组,是存储在栈上的,可以被修改。

10.二级指针

指针变量存放其他数据的地址,而指针变量的地址是存放在二级指针中。

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

	**ppa = 25;
	printf("%d\n", a);
	printf("%d\n", *pa);
	printf("%d\n", **ppa);
	return 0;
}

**ppa = *(*ppa) = *pa = a
:二级指针指向的类型是指针类型,所以二级指针+1等于加上4字节(32位)或者8字节(64位)

二、指针数组和数组指针

1.指针数组

指针数组是数组,数组指针是指针,只需要看末尾名词确定类型。

指针数组的定义

指针数组就是存放指针的数组,本质上是数组。

例如:

int* arr[5];

arr是一个数组,数组中有5个元素,每个元素的类型是int*整型指针。

首先,通过前面操作符篇我们知道[]的优先级比* 高,所以arr先与[]结合,确定是一个数组,数组中每个元素是都是指向int类型的指针,所以是指针数组。

指针数组的使用

#include<stdio.h>
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8 };
	int* p[3] = { arr[0], arr[1], arr[2] };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", p[i][j]);//printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
	return 0;
}

p首先是一个数组,数组中的3个元素是int*类型,将二维数组的每一行元素的首地址赋给p中各元素初始化,p的用法相当于一个二维数组。

在这里插入图片描述

2.数组指针

数组指针的定义

数组指针就是指向数组的指针,本质上是指针。

例如:

int (*arr)[5];

由于[]的结合性比*高,所以加上()使arr先与*结合,表示arr是一个指针,这个指针指向的类型是int[5],即指向一个长度为5的整型数组,所以是数组指针。

数组指针的使用

一维数组

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3 };
	int (*p)[10] = &arr;
	for(int i = 0; i < 10; i++)
		printf("%d ", (*p)[i]);
	return 0;
}

*p相当于*(&arr) = arr 数组首元素的地址 或者由定义可得*p的类型是int[10],即数组

二维数组

#include<stdio.h>
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8 };
	int(*p)[5] = arr;
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", p[i][j]);//printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
	return 0;
}

p首先是一个指针,指向一个整型数组,这个数组中有5个int类型的数据,所以p+1是跳过这个数组即跳过5个int类型的大小,而通过前面数组篇我们知道二维数组在内存中是连续存储的,也就是说二维数组的下一行首元素的地址是与上一行末尾元素的地址相邻的。所以数组指针p可以用来访问二维数组。
在这里插入图片描述

3.练习

指针数组和数组指针已经了解过了,我们来做几个练习:

指针数组

int* p1[10];

p1是一个数组,数组中有10个元素,每个元素的类型是int*

数组指针

int(*p2)[10];

p2是一个指向数组的指针,指向的数组中有10个元素,每个元素的类型是int

数组指针数组

int(*p3[10])[5];

p3先与[10]结合是一个数组,数组中有10个元素,每个元素的类型是int(*)[5]
p3是一个存放数组指针的数组

指针数组指针

int* (*p)[5];

p4先与*结合是一个指针,指向一个数组,数组中有5个元素,每个元素的类型是int*,所以这个数组是指针数组。
p4是一个指向指针数组的指针。

如何判断指针的类型呢?

指针的类型中提到过,现在又多加了指针数组和数组指针的判断

把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型;
把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

int* ptr; //指针的类型是int*	 指针所指向的类型是int
char* ptr;//指针的类型是char*  指针所指向的类型是char
int** ptr;//指针的类型是int**  指针所指向的类型是int*
int(*ptr)[10];//指针的类型是int(*)[10] 指针所指向的的类型是int[10]
int*(*ptr)[5];//指针的类型是int*(*)[5] 指针所指向的的类型是int*[5]

例如:
将arr强制类型转换成与p相同类型

int(*p)[10] = (int(*)[10])arr;

三、数组传参和指针传参

数组篇虽然写过,但在这里再强调一遍加深巩固。
通常情况下,我们所说的数组名都是数组首元素的地址,但有两种特殊情况:

1.sizeof(数组名),计算的是整个数组的大小,此处的数组名表示整个数组。
2.&数组名,取出的是整个数组的地址。

1.一维数组传参

#include <stdio.h>
void test(int a[])//√
{}			//数组传参时可以不写元素个数
void test(int a[10])//√
{}			//正常传参
void test(int* a)//√
{}			//数组传参传的是数组首元素的地址,指针就是地址,接收正常
void test(int (*a)[10])//√
{}		//数组指针,指向有10个int元素的数组,符合arr条件,可以接收
void test2(int* a[20])//√
{}			//正常传参
void test2(int** a)//√
{}//arr2是指针数组,所指数组中的元素类型是指针,数组名又是首元素地址,指针的地址用二级指针接收

int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

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);
}

所以,二维数组的传参只有三种方式:行列都不省略,只省略列数,用数组指针。

注:数组地址与数组首元素的地址不一样! 数组地址解引用才能得到数组首元素的地址

3.一级指针传参

#include <stdio.h>
void Print(int *p, int sz)
{
	for(int 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 sz = sizeof(arr)/sizeof(arr[0]);
	int *p = arr;//p指向数组首元素的地址
	Print(p, sz);//一级指针p,传给函数
	return 0;
}

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

#include <stdio.h>
void test(int* p)
{}
int main()
{
	int a = 10;
	test(&a);//变量的地址

	int* p = &a;
	test(p);//一级指针

	int arr[5];
	test(arr);//数组首元素的地址

	return 0;
}

4.二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	test(ppa);//传二级指针
	test(&pa);//传一级指针的地址
	return 0;
}

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

#include <stdio.h>
void test(int** ptr)
{}
int main()
{
	int* p;
	test(&p);//一级指针的地址

	int** pp;
	test(pp);//二级指针

	int* arr[10];//指针数组,指向的数组的元素是int*指针类型
	test(arr);//传的是数组首元素的地址即int*指针类型的地址,可以用二级指针接收

	return 0;
}

四、函数指针

函数指针就是指向函数的指针变量。
函数指针可以像一般函数一样,用于调用函数、传递参数。

在这里插入图片描述

函数指针的使用

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;//int(*pf)(int, int) = &Add;
	int ret = pf(3, 5);//int ret = (*pf)(3, 5);两种写法都可以
	printf("%d\n", ret);
	return 0;
}

注:&函数名和函数名都表示函数的地址

嘿嘿,来看两段有趣的代码

第一段代码来自《C陷阱与缺陷》

	(*(void (*)())0)();
分析:void(*)() 表示一个函数指针类型,所指函数的参数为空即没有参数,返回类型是void(void(*)())00进行强制类型转换,转换成一个函数指针类型,0被当成一个函数的地址;
	(*(void (*)())0)() 调用0地址处的函数,该函数的返回值是void,无参

第二段代码:

	void (* signal(int, void(*)(int)))(int);
分析:这是一次函数的声明,声明的函数名叫signal;
	 signal函数的参数有两个,第一个是int类型,第二个是void(*)(int)函数指针类型
	 该指针指向的函数的参数类型是int,返回类型是void;
	 
	 把signal(int, void(*)(int))去掉再看,signal函数的返回类型是一个void(*)(int)函数指针
	 该指针指向的函数的参数类型是int,返回类型是void

不难发现signal函数的返回类型第二个参数类型相同,都是void(*)(int)类型
所以可以给void(*)(int)类型重命名来简化代码;

typedef void(*)(int) pf_t;//错误写法

这样写符合逻辑,但不符合语法,应当这样写

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

这样就更好理解了:声明一个叫signal的函数,该函数的第一个参数类型是int,第二个参数类型是pf_t,返回类型也是pf_t;而pf_t是一个void(*)(int)函数指针类型,该指针指向的函数的参数是int类型,返回类型是void

五、函数指针数组

把函数的地址存到一个数组中,那这个数组就叫函数指针数组,函数指针数组中存放的是函数的地址。

比如说写一个加减乘除的计算器:

#include <stdio.h>
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 (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };//函数指针数组 存放函数地址
	int x = 0, y = 0, ret = 0;
	while (1)
	{
		printf("***1.Add  2.Sub***\n");
		printf("***3.Mul  4.Div***\n");
		printf("***   0.exit   ***\n");
		printf("请选择功能:->");
		int input = 0;
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:->");
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else if(input == 0)
		{
			printf("已退出\n");
			break;
		}
		else
		{
			printf("输入错误,请重新选择\n");
		}
	}
	return 0;
}

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

指向函数指针数组的指针是一个指针,这个指针指向一个数组,数组的元素都是函数指针

#include <stdio.h>
void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pf
	void (*pf)(const char*) = test;
	//函数指针的数组pfArr
	void (*pfArr[5])(const char* str);
	pfArr[0] = test;
	//指向函数指针数组pfArr的指针ppfArr
	void (*(*ppfArr)[5])(const char*) = &pfArr;
	return 0;
}

七、回调函数

回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

注: 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

简单回调函数示例:

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
void print_hehe(void(*pf)())
{
	if (1)
		pf();
}
int main()
{
	//test();
	print_hehe(test);
	return 0;
}

练习: 使用回调函数,模拟实现qsort函数(采用冒泡的方式)
库函数qsort的使用方法具体请查阅参考手册,这里简单示范下:

#include <stdio.h>
int int_cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

int main()
{
	int a[] = { 1,2,5,6,9,3,8,4,7,0 };
	int len = sizeof(a) / sizeof(a[0]);
	qsort(a, len, sizeof(a[0]), int_cmp);
	for (int i = 0; i < len; i++)
		printf("%d ", a[i]);
	return 0;
}

接下来利用回调函数模拟实现qsort函数(万能通用适用于各种类型排序):

#include<stdio.h>
void Swap(char* buf1, char* buf2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//整型排序
int int_cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//被排序元素什么类型就用什么指针
}

//指针数组排序
int pa_cmp(const void* e1, const void* e2)
{
	return strcmp(*(char**)e1, *(char**)e2);
}

//改进qsort 排序任意类型的数组
void bubble_sort(void* base, int len, int width, int (*cmp)(const void* e1, const void* e2))
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
		}
	}
}

int main()
{
	char* pa[] = { "abce", "bcde", "abcd"};
	int len = sizeof(pa) / sizeof(pa[0]);
	bubble_sort(pa, len, sizeof(pa[0]), pa_cmp);
	for (int i = 0; i < len; i++)
		printf("%s ", pa[i]);
	return 0;
}

注:内存中最小存储单元是字节,char类型是1字节大小,所以可以将各种类型的数据拆分成单个字节,通过单个字节地进行调整,可以满足各种类型的排序,实现qsort功能。

总结完毕,指针的内容就到这里,感谢大家的观看。

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

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

相关文章

开源区块链系统/技术 总结(欢迎补充,最新)

1. FISCO BCOS FISCO BCOS 2.0 技术文档 — FISCO BCOS 2.0 v2.9.0 文档https://fisco-bcos-documentation.readthedocs.io/ 2. ChainMaker&#xff08;长安链&#xff09; 文档导航 — chainmaker-docs v2.3.2 documentationhttps://docs.chainmaker.org.cn/v2.3.2/html/in…

你们是如何保证消息不丢失的?

1、什么是死信 在 RabbitMQ 中充当主角的就是消息&#xff0c;在不同场景下&#xff0c;消息会有不同地表现。 死信就是消息在特定场景下的一种表现形式&#xff0c;这些场景包括&#xff1a; 1. 消息被拒绝访问&#xff0c;即 RabbitMQ返回 basicNack 的信号时 或者拒绝basi…

CKA 基础操作教程(五)

Kubernetes Ingress 理论学习 Ingress 提供从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源所定义的规则来控制。 Ingress 资源示例&#xff1a; apiVersion: networking.k8s.io/v1 # 指定 Kubernetes 中使用的 API 版本 kind: Ingress # 指定对象…

【日常记录】【JS】填充数组的三种方案

文章目录 1、for 循环填充2、new Array、fill、map 三者配合填充3、Array.from 填充数组参考链接 一般在开发中需要生成一个数组&#xff0c;用于测试等其他情况&#xff0c;以下介绍三种常见方案 1、for 循环填充 如果需要对这个数组的内容做一些特殊处理&#xff0c;写起来就…

Mysql底层原理七:InnoDB 行记录

1.行格式 1.1 Compact行格式 1.1.1 示意图 1.1.2 准备一下 1&#xff09;建表 mysql> CREATE TABLE record_format_demo (-> c1 VARCHAR(10),-> c2 VARCHAR(10) NOT NULL,-> c3 CHAR(10),-> c4 VARCHAR(10)-> ) CHARSETascii ROW_FORMATCOM…

企业网络安全运营能力的多维度评价及优化策略

网络安全是企业面临的一个日益重要的问题&#xff0c;安全运营能力的强弱直接关系到企业的健康可持续发展和综合竞争力的提升。为推动企业网络安全工作的标准化建设&#xff0c;提升企业的网络安全运营能力&#xff0c;本文从安全建设、安全应对和安全效果三个角度出发&#xf…

【迅为iTOP-4412-linux 系统制作(4)】ADB 或者 TF 卡烧写测试

准备工作 编译生成的内核镜像uImage 和设备树 dtb 文件“exynos4412-itop-elite.dtb”已经可以使用了。 把编译生成的uimage和dtb文件。拷贝fastboot工具。官方的u-boot-iTOP-4412.bin 也拷贝到 platform-tools 文件夹目录内。system.img 也拷贝到 platform-tools 文件夹目录…

阿里通义千问开源 320 亿参数模型;文字和音频自动翻译成手语Hand Talk拉近人与人的距离

✨ 1: Qwen1.5-32B Qwen1.5-32B是Qwen1.5系列中性能与效率兼顾的最新语言模型&#xff0c;内存占用低&#xff0c;运行速度快。 Qwen1.5-32B是Qwen1.5语言模型系列的最新成员&#xff0c;这个模型是基于先进的技术研发的&#xff0c;旨在提供一种既高效又经济的AI语言理解和生…

JS--demo2录入学生信息

实现学生信息录取。 效果图: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><meta http-equiv"X-U…

景联文科技:为AI大模型提供高质海量训练数据

在全球AI浪潮的推动下&#xff0c;大量训练数据已成为AI算法模型发展和演进中的关键一环。 艾瑞咨询数据显示&#xff0c;包括数据采集、数据处理&#xff08;标注&#xff09;、数据存储、数据挖掘等模块在内的AI基础数据服务市场&#xff0c;将在未来数年内持续增长。 预计到…

【LeetCode题解】2009. 使数组连续的最少操作数

文章目录 [2009. 使数组连续的最少操作数](https://leetcode.cn/problems/minimum-number-of-operations-to-make-array-continuous/)思路&#xff1a;一、排序去重滑动窗口代码&#xff1a; 2009. 使数组连续的最少操作数 思路&#xff1a;一、排序去重滑动窗口 1.对数组进行…

SpringBoot + Dobbo + nacos

SpringBoot Dobbo nacos 一、nacos https://nacos.io/zh-cn/docs/quick-start.html 1、下载安装包 https://github.com/alibaba/nacos/releases/下载后在主目录下&#xff0c;创建一个logs的文件夹&#xff1a;用来存日志 2、启动nacos 在bin目录下打开cmd运行启动命令&a…

[StartingPoint][Tier2]Oopsie

Task 1 With what kind of tool can intercept web traffic? (哪种工具可以拦截web数据包) proxy Task 2 What is the path to the directory on the webserver that returns a login page? (路径到返回登录页面的 Web 服务器目录是什么&#xff1f;) /cdn-cgi/login Tas…

用vue.js写案例——ToDoList待办事项 (步骤和全码解析)

目录 一.准备工作 二.编写各个组件的页面结构 三.实现初始任务列表的渲染 四.新增任务 五.删除任务 六.展示未完成条数 七.切换状态-筛选数据 八.待办事项&#xff08;全&#xff09;代码 一.准备工作 在开发“ToDoList”案例之前&#xff0c;需要先完成一些准备工作&a…

MySQL-单行函数:数值函数、字符串函数、日期和时间函数、流程控制函数、加密与解密函数、MySQL信息函数、其他函数、单行函数练习

1.数值函数 1.1 基本的操作 SELECT ABS(-123),ABS(32),SIGN(-23),SIGN(43),PI(),CEIL(32.32),CEILING(-43.23),FLOOR(32.32), FLOOR(-43.23),MOD(12,5),12 MOD 5,12 % 5 FROM DUAL;1.2 取随机数 SELECT RAND(),RAND(),RAND(10),RAND(10),RAND(-1),RAND(-1) FROM DUAL;1.3 四…

计算机网络——40各个层次的安全性

各个层次的安全性 安全电子邮件 Alice需要发送机密的报文m给Bob Alice 产生随机的对称秘钥&#xff0c; K s K_s Ks​使用 K s K_s Ks​对报文进行加密&#xff08;为了效率&#xff09;对 K s K_s Ks​使用Bob的公钥进行加密发送 K s ( m ) K_s(m) Ks​(m)和 K B ( K S ) K…

如何使用群晖Synology Drive结合cpolar内网穿透实现同步Obsidian笔记文件

文章目录 一、简介软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步1 安装并设置Synology Drive套件2 局域网内同步文件测试 三、内网穿透群晖Synology Drive&#xff0c;实现异地多端同步Windows 安装 Cpolar步骤&#…

视觉大模型--DAB-deter的深入理解

原理大家参考这篇文章&#xff0c;我主要是根据自己的理解和整个流程图以及代码进行对应&#xff0c;这样更有利于深入理解&#xff1a; 下图是解码器结构图&#xff0c;编码器没动和deter一样的 这张图片基本上说清了模型的结构和传递过程&#xff0c;红色代表切断梯度反向传…

线程池实践篇

文章目录 配置线程池参数定义参数实体bean配置线程池使用 配置线程池参数 定时任务线程池基础参数 # 定时任务线程池基础参数 task:pool:corePoolSize: 5 # 核心线程数maxPoolSize: 20 # 设置最大线程数keepAliveSeconds: 300 # 设置线程活跃时间&#xff0c;单位秒queueCapa…

如何快速开启一个项目-ApiHug - API design Copilot

ApiHug101-001开启篇 &#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin |…