指针进阶(万字深层次指针解析)

news2024/11/24 8:53:19

❤️ 作者简介 :对纯音乐情有独钟的阿甘 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识,对纯音乐有独特的喜爱
📗 日后方向 : 偏向于CPP开发以及大数据方向,如果你也感兴趣的话欢迎关注博主,期待更新


指针进阶

  • 1.字符指针
    • 2. 指针数组
    • 3.数组指针
    • 3.2 &数组名VS数组名
    • 3.3数组指针的使用
  • 4. 数组参数、指针参数
    • 4.1 一维数组传参
    • 4.2 二维数组传参
    • 4.3 一级指针传参
    • 4.4二级指针传参
  • 5.函数指针
    • 5.1函数指针的定义
    • 5.2函数指针的使用
  • 6.函数指针数组
  • 7. 指向函数指针数组的指针
  • 8.回调函数
    • 8.1实现计算器(简)
    • 8.2 qsort函数:
        • 一.无类型指针void*
          • 1.整形排序
          • 2.结构体名字大小排序
          • 3.结构体年龄排序
        • 二.源码

内存会划分为一个个的内存单元,每个内存单元都有有个独立的编号——编号也称为地址
地址在C语言中也称为指针
指针(地址)需要存储起来—存储到变量中,这个变量就叫做指针变量
请添加图片描述
接下来让我们进入今天的正题

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
让我们通过代码演示来理解:
在这里插入图片描述

请添加图片描述
代码 const char* p = “abcdef”;
特别容易让我们以为是把字符串abcdef放到字符指针 p里了,但是/本质是把字符串 abcdef 首字符的地址放到了pstr中。

接下来让我们看一道面试题:
请添加图片描述这个代码到底会输出什么呢?
让我们来看一下输出结果:
请添加图片描述
为什么是这个答案呢?

我们看到程序,str1和str2存储的两个字符串,我们可以注意到,我们使用数组名的时候,他们应该是数组首元素的地址,str1和str2是不同的常量,需要在内存中开辟不同的空间,所以两个数组首元素的地址不同,也就是str1和str2不同

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会
开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

2. 指针数组

让我们来看看下面这段代码分别是什么意思:

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

在这里就运用了多级指针的知识
我们一般的整形数组应该是:

int arr[10];

类似于这样的,arr中存储的是是个整形
那我们在将int变成int*,那么数组中存储的是什么呢?
我们可以看到,类型为int,数组里面存储的是int,那么我们现在是int*,那数组里面必然存储的是int*的内容,也就是整形指针的数组

char *arr2[4]与其类似,可以自行理解一下
我们来看到char **arr3[5],这个时候有人就会问了,一个*是指针,那两个呢?


我们int*类型中存储的是int类型的地址,也就是我们int*存储的是一块地址,其地址上是int类型,我们解引用的结果是地址上的int内容,而我们int**存储的也是一块地址,他是int*的地址,我们解引用也就得到的是int*的内容,是int的地址

如果听不懂的话,我们采用另一个方式来描述:
指针可以当做一个指向另一个,我们int*就相当于我们定义了一个类型int*,它指向的内容是int类型,而(*)只是用来表明它是一个一维指针
我们的二维指针**同理,定义为int** 类型时,**表示其为二维指针,指向的是int/*类型


这个时候我们来解释:
char **arr3[5];//二级字符指针的数组
这个数组就是二级字符指针的数组,里面存放的都是二级字符指针


3.数组指针

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

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


3.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = {0};
	//%p是地址类型
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

我们来看输出结果:
在这里插入图片描述
可见数组名和&数组名打印的地址是一样的。


难道两个是一样的吗?

我们再看一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("arr+1 = %p\n", arr+1);
	printf("&arr+1= %p\n", &arr+1);
	return 0;
}

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

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

总结:
数组名是数组首元素的地址
有两个例外:
1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节

2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址
除此之外,所有的地方和数组名都是数组首元素的地址


3.3数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
在这里插入图片描述

来看输出结果:
在这里插入图片描述


这里我们要了解一个概念:
请添加图片描述
也就是说,我们的二维数组传入的是一个一维数组的地址
那我们就应该用一个数组指针来接收


我们接下来用数组指针:
在这里插入图片描述

输出结果:
在这里插入图片描述
***!](https://img-blog.csdnimg.cn/fa74c9618e594d948c87a20e8a56e454.png)

总:
数组指针,是指针,是指向数组的指针
指针数组,是数组,是存放指针的数组


4. 数组参数、指针参数

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


4.1 一维数组传参

我们通过一系列例子来感受:

#include <stdio.h>
void test(int arr[])//ok?
{}
arr为整形数组,这个可行,省略了长度也可以
void test(int arr[10])//ok?
{}
arr微整形数组,长度未省略,可行
void test(int *arr)//ok?
{}
传参为arr数组名,为首元素地址,也是指针,可行
void test2(int *arr[20])//ok?
{}
这里的参数为指针数组,类型匹配,可行
void test2(int **arr)//ok?
{}
原数组为指针数组,数组名传参为二级指针,类型匹配可行
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

总结:
一维数组传参,函数参数可以为一维指针,一维数组
一维指针数组传参,函数参数可以为二维指针,一维指针数组


4.2 二维数组传参

void test(int arr[3][5])//ok?
{}
可行,参数就是二维数组
void test(int arr[][])//ok?
{}
不可行,二维数组行可以省略,列不可省略
void test(int arr[][5])//ok?
{}
可行
总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。
void test(int *arr)//ok?
{}
不可行,二维数组传参为首一维数组的地址,一维指针与其不兼容
void test(int* arr[5])//ok?
{}
这是一维指针数组,不可行
void test(int (*arr)[5])//ok?
{}
加了括号,这为指针数组且列标对得上,可行
void test(int **arr)//ok?
{}
二维指针不可以,应该为数组指针,指向的是数组
int main()
{
	int arr[3][5] = {0};
	test(arr);
}


4.3 一级指针传参

我们先来看一段代码:


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

这是一个简单的打印函数,它的实参是一个指针和一个int类型的变量,他的形参也是一个同类型的指针接收。
另外我们要知道其实它还可以用数组接收
当一个函数的参数部分是一级指针时,函数可以接收什么类型的参数?
举个栗子:(其他类型的同理)

void test(char* p)
{}

这个函数可以接收什么类型的参数?
1.一维字符数组名
2.一个字符变量的地址(&ch(ch是字符变量))
3.char*的指针变量


4.4二级指针传参

先来看一个二级指针传参数的例子:

void test(int** pstr)
{
	printf("%d\n", **pstr);
}
 
int main()
{
	int n = 5;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

这是一个二级指针传参,p是一级指针变量(存的是变量的地址),pp是二级指针变量(存的是一级指针变量的地址)
test(pp)和test(&p)是一样的
二级指针传参其实还可以是指针数组

当一个函数的参数部分是二级指针时,函数可以接收什么类型的参数?
比如:

void test(char** p)
{}

这个函数可以接收什么类型的参数?

1.取地址一级指针变量(&p (p是一级指针变量))

2.二级指针变量

3.数组指针


5.函数指针

我们都知道了int、char、数组等等很多指针类型,我们还有一个函数指针,平常很难碰到,今天我们来讲一讲

5.1函数指针的定义

我们知道数组指针是指向数组的指针,那函数指针是不是指向函数的指针呢?
让我们来看看到底是不是

数组指针里面存的是数组的地址,类比一下,函数指针里面存的是不是函数的地址呢?这里你肯定会好奇函数也有地址吗?其实是有的。

让我们来看一段代码:

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

我们来输出一下:
在这里插入图片描述
我们发现它确实是有地址的,而且我们可以发现函数名是函数的地址,&函数名也是函数的地址
接下来我们再看一段代码

int Add(int x, int y)
{
	return x + y;
}
 
int main()
{
	int x = 3;
	int y = 5;
	int(*pf)(int, int) = &Add;
	return 0;
}

我们这里定义的

int ( *pf )( int , int )=&Add

pf就是我们的函数指针名,它与*结合成(*pf)这就是说明其为指针,前面的int就是函数add的返回值类型,后面括号以及里面的两个int也就是add函数的两个参数

由此,我们不难得出函数指针定义方式
type (*变量名)(形式参数,形式参数,…)=&(函数名)
在这里插入图片描述
我们通过这个代码会发现,诶,三个输出的地址全部都是一样的?
没错,这也证实了我们的函数指针确确实实指向的是函数


5.2函数指针的使用

我们清楚了函数指针的定义,接下来让我们看看它是如何使用的:
我们和上面一样,采用代码的形式理解:

我们想使用Add函数来实现两个数的加减:
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int x = 3;
	int y = 5;
	int(*pf)(int, int) = &Add;
	我们普通的做法就是:
	printf("普通做法:%d\n", Add(x, y));
	如今我们定义了函数指针,我们来试着使用一下
	我们一般使用指针的时候采取的是解引用的方式:
	例如
	int a = 5;
	int* p = &a;
	我们想用指针来修改a的值:
	*p = 4;
	printf("我们用指针修改常量的值:%d\n", a);
	我们通过函数指针是不是也是这样呢?
	printf("我们用指针的做法:%d", (*pf)(x, y));
	答案是肯定的我们对pf前面加*号,表示解引用
	注意:这里的*和上面定义的*可不一样
	上面定义的*是表示pf为指针,下面的*是用来给pf解引用
	return 0;
}

注释为了好看没有加注释符,如果需要测试需要将注释一一加上注释符(//)哦
接下来我们来看看,这到底有没有报错:
在这里插入图片描述
成功输出了

6.函数指针数组

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

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

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

我们先来定义四个函数
比如计算器加减乘除四个函数
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* ass[] = { 0 };
	那我们定义函数指针数组呢?
	我们就应该这样写:
	int(*p[5])(int x, int y) = {add,sub,mul,div};
	之前我们一个函数指针我们是这样的
	int(*p)(int x,int y) = add;
	我们用变量p来接收一个函数add
	现在我们将变量p变成p[]。自然而然就对应的是函数数组
	由此就可以看到函数指针数组的定义为
	type(*数组名[])(参数,参数)={函数名、函数名、}
}

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

指向函数指针数组的指针是一个 指针,指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?
让我们看几个例子:

函数指针:int(*pf)(int,int)
函数指针数组:int(*pfarr[4])(int,int)
我们当初思考二维指针的时候,我们在一维指针前面
加上一个*号就表示其为二维指针
我们的指向函数指针数组的指针是不是也一样的呢?
答案是肯定的
int(*(*p)[4]))(int,int)=&pfarr;//函数指针数组的地址
p就是指向函数指针数组的指针
可以看到我们就是在函数指针数组前
加了一个*号表示其为函数指针数组的指针

8.回调函数

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


8.1实现计算器(简)

我们想通过代码实现简单的计算器(实现加减乘除基本运算)
这是我们正常代码:

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

void menu()
{
	printf("***************************\n");
	printf("*****  1.add  2.sub  ******\n");
	printf("*****  3.mul  4.div  ******\n");
	printf("*****  0.exit        ******\n");
	printf("***************************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		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;
}

我们会发现我们在swich—case语句中,有很多重复冗余的部分,每次进入语句都会需要写输入语句以及函数调用,使得我们的代码量大大增加,我们能否省略这些冗余的部分呢?
接下来我们采用回调函数,相关操作实现

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

void menu()
{
	printf("***************************\n");
	printf("*****  1.add  2.sub  ******\n");
	printf("*****  3.mul  4.div  ******\n");
	printf("*****  0.exit        ******\n");
	printf("***************************\n");
}

void Calc(int (*pf)(int, int)) //回调函数-->指向函数指针
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);   //我们通过调用pf,用pf接受了传进来的函数地址,也就是调用传进来的函数
	printf("ret = %d\n", ret);
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 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;
}

我们可以看到
我们可以通过函数指针省略非常大一部分的代码量,实现我们想要的操作
接下来我们介绍一个函数:


8.2 qsort函数:

qsort:q就是quick,sort就是排序,也就是快速排序
看一下www.cplusplus.com 里面的函数定义:
在这里插入图片描述

void* base —>指向了需要排序的数组的第一个元素
size_t num —>排序的元素个数
size_t size —>一个元素的大小,单位是字节
int (*cmp)(const void*,const void*)
函数指针类型 这个函数指针指向的函数,能够比较base指向数组的两个元素的大小
这个函数通常叫排序函数(需要自己实现)


一.无类型指针void*

注:
void* 指针 --无具体类型的指针
void* 类型的指针可以接受任意类型的地址
这种类型的指针是不能直接解引用操作的
也不能直接进行之指针运算的
在上面的函数里只是代表了所有的函数类型
当我们在函数中参数使用void*指针来接收的时候,虽然能接收任意类型的参数指针,但是接收后类型依然是void*,我们在函数内部使用的时候我们需要对其进行强制转换,转换成我们需要的参数类型进行比较才可以

下面我们就用三个栗子来了解qsort以及void*的用法:(分别是用qsort排序整型,用qsort排结构体名字和年龄)


1.整形排序
int cmp_int(const void* arr1, const void* arr2)
{
	return (*(int*)arr1 - *(int*)arr2);
}
 
void print(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
 
int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
	return 0;
}

在这里插入图片描述


2.结构体名字大小排序
struct Stu
{
	char name[20];
	int age;
};
 
int cmp_name(const void* str1, const void* str2)
{
	return strcmp(((struct Stu*)str1)->name, ((struct Stu*)str2)->name);
}
 
void print(struct Stu* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s  %d\n", (arr+i)->name, (arr+i)->age);
	}
}
 
int main()
{
	struct Stu arr[] = { {"李华",32},{"小黑",8},{"花花",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_name);
	print(arr, sz);
	return 0;
}

在这里插入图片描述


3.结构体年龄排序
struct Stu
{
	char name[20];
	int age;
};
 
int cmp_age(const void* arr1, const void* arr2)
{
	return ((struct Stu*)arr1)->age - ((struct Stu*)arr2)->age;
}
 
void print(struct Stu* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s  %d\n", (arr+i)->name, (arr+i)->age);
	}
}
 
int main()
{
	struct Stu arr[] = { {"李华",32},{"小黑",8},{"花花",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_age);
	print(arr, sz);
	return 0;
}

在这里插入图片描述


void*指针的用法也是接下来实现所有类型qsort的一个必要条件

知道了qsort函数接下来我们看代码:
这是我们简单调用qsort函数

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
	return (*( int *)p1 - *(int *) p2);
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
	for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf( "%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

我们会发现,普通的qsort函数只能进行整形的排序
我们使用回调函数模拟实现qsort操作
实现任何类型的排序

二.源码

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
	return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{
	int i = 0;
	for (i = 0; i< size; i++)
	{
		char tmp = *((char *)p1 + i);  
		*(( char *)p1 + i) = *((char *) p2 + i);
		*(( char *)p2 + i) = tmp;
	}
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
	int i = 0;
	int j = 0;
	for (i = 0; i< count - 1; i++)
	{
		for (j = 0; j<count-i-1; j++)
		{
			if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
			{
				_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
			}
		}
	}
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
	int i = 0;
	bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
	for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf( "%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

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

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

相关文章

Java正则表达式,不定期更新

Java正则表达式 1. 匹配数字&#xff08;包含负数、小数&#xff09;2. 匹配不是纯数字和纯字母且需要8位以上的密码3. 密码&#xff1a;字母、数字、符号&#xff08;_-*.,!#符号可自定义&#xff09;三选二4. 密码&#xff1a;必须包含大写、小写、数字、符号&#xff08;_-*…

车道线检测|利用边缘检测的原理对车道线图片进行识别

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 这两个都是博主在学习Linux操作系统过程中的记录&#xff0c;希望对大家的学习有帮助&#xff01; 操作系统Operating Syshttps://blog.csdn.net/yu_cblog/category_12165502.html?spm1001.2014.3001.5482Linux S…

工程监测振弦采集仪的解决方案案例解释

振弦采集仪是一种用于测量结构物的振动状态和应力变化的高精度仪器&#xff0c;广泛应用于建筑、桥梁、隧道、地铁等工程领域。以下是一些常见的解决方案案例分析&#xff1a; 基础监测方案&#xff1a;对于大型建筑或桥梁工程&#xff0c;需要对基础进行实时监测。使用振弦采集…

System类 BigInterger BigDecimal

System类 常用方法和案例 exit&#xff1a; 退出当前程序 System.out.println("zhang"); // 0表示一个正常退出的状态 System.exit(0); System.out.println("cheng");System.arraycopy&#xff1a; 复制数组元素&#xff0c;比较适合底层的调用&#xf…

基于linux下的高并发服务器开发(第二章)- 2.2 进程状态转换

01 / 进程的状态 &#xff08;1&#xff09;三态模型 进程状态分为三个基本状态&#xff0c;即就绪态&#xff0c;运行态&#xff0c;阻塞态 &#xff08;2&#xff09;五态模型 在五态模型中&#xff0c;进程分为新建态&#xff0c;就绪态&#xff0c;运行态&#xff0c;阻…

mongodb练习---增删改查

环境&#xff1a; 1. 创建一个数据库 名字grade 2. 数据库中创建一个集合名字 class 3. 集合中插入若干数据 文档格式如下 &#xff5b;name:zhang,age&#xff1b;10,sex:m,hobby:[a,b,c]&#xff5d; hobby: draw sing dance basketball football pingpong compu…

Java8实战-总结2

Java8实战-总结2 基础知识方法和Lambda传递代码&#xff1a;一个例子从传递方法到Lambda 基础知识 方法和Lambda Scala和Groovy等语言的实践已经证明&#xff0c;让方法等概念作为一等值可以扩充程序员的工具库&#xff0c;从而让编程变得更容易。一旦程序员熟悉了这个强大的…

理解LLM中的ReAct

large language models (LLMs)大语言模型在语义理解和交互式决策方面有着不错的表现。ReAct在一次交互中循环使用推理和行动两个操作解决复杂问题&#xff0c;推理即利用模型自身语义理解能力&#xff0c;行动则利用模型以外的能力&#xff08;如计算、搜索最新消息&#xff0c…

OpenCv之滤波器

目录 一、卷积 二、方盒滤波与均值滤波 三、高斯滤波 四、中值滤波 五、双边滤波 一、卷积 图像卷积就是卷积核在图像上按行华东遍历像素时不断的相乘求和的过程 相关知识点: 步长:就是卷积核在图像上移动的步幅.(为充分扫描图片&#xff0c;步长一般为1)padding:指在图片…

跨服务器跨库数据联合查询

今天群里有人问多个数据源, 可否显示在一个dbgrid, 我感觉是可以的 应该有两种办法 1,如果你两个服务器上都是用的mssqlserver, 那比较好办的, 如果不同数据库,如一个mssql,一个oracle。 则需要ssms方式创建。 通过SSMS查看,如果Microsoft OLE DB Provider for …

docekr-compose搭建redis集群(三主三从)

硬件&#xff1a;三台主机 172.50.2.40 172.50.2.41 172.50.2.42 需求&#xff1a;不想让它随机分配主从关系。想指定主从关系&#xff0c;如下&#xff1a; 主节点&#xff1a;172.50.2.40:6379&#xff0c;从节点172.50.2.41:6378 主节点&#xff1a;172.50.2.41:6379&…

C波段可调谐激光器控制软件系统

花了两周时间&#xff0c;利用下班时间&#xff0c;设计了一个ITLA可调谐激光器控制系统&#xff0c;从硬件到软件。下面这个图片整套硬件系统&#xff0c;软件硬件都自己设计&#xff0c;可以定制&#xff0c;做到单片机问题也不大。相当于一套光源了 这是软件使用的界面&…

PyTorch中的torch.nn.Linear函数解析

torch.nn是包含了构筑神经网络结构基本元素的包&#xff0c;在这个包中&#xff0c;可以找到任意的神经网络层。这些神经网络层都是nn.Module这个大类的子类。torch.nn.Linear就是神经网络中的线性层&#xff0c;可以实现形如yXweight^Tb的加和功能。 nn.Linear()&#xff1a;…

Linux网络---网络预备

文章目录 计算机网络背景计算机网络协议网络传输基本流程 网络中的地址管理 一、计算机网络背景 独立模式: 计算机之间相互独立; 网络互联: 多台计算机连接在一起, 完成数据共享; 局域网LAN: 计算机数量更多了, 通过交换机和路由器连接在一起 广域网WAN: 将远隔千里的计算机…

基于Javaweb实现ATM机系统开发实战(十二)用户转账功能实现

还是老规矩&#xff0c;先看前端传来怎样的参数&#xff1a; <% page language"java" contentType"text/html; charsetUTF-8" pageEncoding"UTF-8"%> <% taglib prefix"c" uri"http://java.sun.com/jsp/jstl/core"…

本地安装Linux虚拟机(超详细)

本文已收录于专栏 《运维》 目录 安装前准备虚拟机软件Linux镜像 安装过程中创建虚拟机安装linux系统 安装后测试 安装前准备 虚拟机软件 需要下载一个虚拟机软件&#xff0c;比如VirtualBox或VMware Workstation。这些软件可以创建和管理虚拟机。 这是VMware的官网&#xff1…

ceph集群(一)

ceph 一、存储基础分布式存储&#xff08;软件定义的存储 SDS&#xff09; 二、Ceph 简介Ceph 优势Ceph 架构Ceph 核心组件OSD 存储后端Ceph 数据的存储过程Ceph 集群部署 三、基于 ceph-deploy 部署 Ceph 集群实验Ceph 生产环境推荐&#xff1a;Ceph 环境规划部署 Ceph 集群 一…

Linux环境下,通过Docker搭建及配置RabbitMQ

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Linux环境下&#xff0c;通过Docker搭建及配置RabbitMQ ⏱️ 创作时间…

SpringBoot--整合FreeMarker--使用/实例

原文网址&#xff1a;SpringBoot--整合FreeMarker--使用/实例_IT利刃出鞘的博客-CSDN博客 简介 本文介绍SpringBoot如何使用FreeMarker。 配置文件 application.yml spring:#模板引擎 freemarkerfreemarker:# 模板后缀suffix: .ftl# 是否启用模板缓存cache: false# 模板编…

二、MySQL启动和客户端连接

一、启动 方法一&#xff1a; 1、winR&#xff0c;输入services.msc&#xff0c;按回车 2、找到MySQL&#xff0c;右键-启动/停止 MySQL安装后&#xff0c;默认已启动。 方法二、 1、winR&#xff0c;输入cmd&#xff0c;打开命令行 2、输入启动与停止命令 二、客户端连接 …