C/C++多级指针与多维数组

使用指针访问数组

指针类型的加减运算可以使指针内保存的首地址移动。
指针类型加n后。首地址向后移动 n * 步长 字节。
指针类型减n后。首地址向前移动 n * 步长 字节。
步长为指针所指向的类型所占空间大小。
例如:

int *p = (int *)100;
  • p + 1,结果为首地址向后移动sizeof(int)字节,即104。
  • p - 1,结果为首地址向前移动sizeof(int)字节,即96。

因此,指针加减运算对于访问在内存中连续排布的数据对象非常方便。
而数组这种数据对象,每个元素在内存中一定是连续排布的。下面,我们来探究怎样使用指针访问数组。

使用第一个元素获取数组首地址

既然数组元素在内存中的存储可以保证是连续的,那么第一个元素的首地址,就是整个数组的首地址。

#include <stdio.h>
int main()
{int arr[5] = { 111, 222, 333, 444, 555 };int* p = &arr[0];printf("%d\n", *p); // 第1个元素printf("%d\n", *(p + 1)); // 第2个元素printf("%d\n", *(p + 2)); // 第3个元素printf("%d\n", *(p + 3)); // 第4个元素printf("%d\n", *(p + 4)); // 第5个元素return 0;
}

我们可以使用取地址运算符&,获取第一个元素的首地址和空间大小,即获取一个 int * 类型的指针。
通过取值运算符*,可以使用指针中的首地址和空间大小访问或修改目标数据对象。

表达式 p + 1 必须先被括号包裹,再使用取值运算符*。

这是因为取值运算符*的优先级高于算术运算符。
我们需要先让首地址移动,再进行取值操作。
若不使用括号,*p会先被取值,之后值再被加1。

  • 不使用括号:
    • *p的值为111,*p + 1的结果为112。
  • 使用括号:
    • (p + 1) 使得首地址移动到第二个元素, *(p + 1) 得到结果为222。

使用数组名获取数组首地址

#include <stdio.h>
int main()
{int arr[5] = { 111, 222, 333, 444, 555 };int* p = arr;printf("sizeof arr = %d\n", sizeof(arr));printf("sizeof p = %d\n", sizeof(p));printf("sizeof arr + 1 = %d\n", sizeof(arr + 1));return 0;
}

使用32位进行编译

sizeof arr = 20
sizeof p = 4
sizeof arr + 1 = 4

arr 的大小为20。
p 的大小为4。
arr + 1 的大小为4。
p 是一个指针大小为4是理所当然的。
但是 arr 的大小为20,那么arr应该不是一个指针类型才对,但是它却又可以成功赋值给 int * 。
而 arr + 1 的大小却又为4。

类型为“以T为元素的数组arr”与“指向T的指针p”的关系。

当数组名arr出现在一个表达式当中,数组名arr将会被转换为指向数组第一个元素的指针。但是,这个规则有两个例外:

  • 对数组名arr使用sizeof时。
  • 对数组名arr使用&时。

也就是说,数组名arr的类型其实是 int [5] ,因此 sizeof(arr) 的结果才会是20。
数组名arr出现在表达式int* p = arr中,会被转换为指向数组第一个元素的指针,即 int [5] 转为 int * 类型。之后进行赋值运算。
arr + 1 也是一个表达式,数组名 arr 被转换为 int * 类型,进行加法运算后,仍然为 int * 类型。

使用指针访问数组等价于下标访问

现在我们学会了访问数组元素的两种办法:

  • 数组名[下标]
  • *(数组名 + 偏移量)

其中,偏移量就是指针指向的地址与数组首地址之间相差几个元素。
事实上,这两种形式是等价的。
中括号 [] ,被称作下标运算符,它的优先级高于一切其他运算符。通常的形式为:

A[k]

而表达式运算时,最终会将下标运算符展开为:

*(A + k)

测试一下

#include <stdio.h>
int main()
{int arr[5] = { 111, 222, 333, 444, 555 };printf("arr[2] = %d\n", arr[2]);printf("2[arr] = %d\n", 2[arr]);printf("*(arr + 2) = %d\n", *(arr + 2));return 0;
}
  • arr[2] 展开为 *(arr + 2) 。
  • 2[arr] 展开为 *(2 + arr) 。

因此,使用指针访问数组等价于下标访问。

指针作为参数传递

形参与实参相互独立

#include <stdio.h>
void swap(int x, int y)
{// 打印x,y的首地址printf("&x= %u\n", &x);printf("&y= %u\n", &y);int temp = x;x = y;y = temp;
}
int main()
{int a, b;int temp;a = 1;b = 2;// 打印a,b的首地址printf("&a= %u\n", &a);printf("&b= %u\n", &b);// 交换a,b变量swap(a, b);return 0;
}

分析过程

{% gallery::::one %}


{% endgallery %}

将指针作为参数传递

#include <stdio.h>
void swap(int* x, int* y)
{int temp = *x;*x = *y;*y = temp;
}
int main()
{int a, b;int temp;a = 1;b = 2;printf("a=%d b=%d\n", a, b);// 交换a,b变量swap(&a, &b);printf("a=%d b=%d\n", a, b);return 0;
}

不是交换指针x,y的值,而是交换目标数据对象a,b的值。所以,需要在指针前使用取值运算符*

为何在使用 scanf 函数时,需要对变量先取地址再传入参数

int n;
scanf("%d", &n);

scanf 会从读取从键盘的输入,转换后存储到变量n当中。被调函数 scanf 无法直接修改在主调函数中的变量n。因此,我们将变量n的指针传入 scanf 函数。通过指针使得被调函数间接地修改主调函数中的变量

指针不仅仅是首地址

再次强调,指针内保存的不仅仅是目标数据对象首地址,指针的类型也非常重要。
要在内存中找到一个数据对象,需要有以下两个信息。

  • 数据对象的首地址。
  • 数据对象占用存储空间大小。

指针的值保存着数据对象首地址,指针类型对应着目标数据对象的类型,用于标记目标数据对象的空间大小和指针运算时的步长。

仅有首地址的指针类型void *

由于指针类型定死了指针所指向的数据类型。为了让函数可以交换更多的数据类型,我们仅需要指针类型中保存的首地址,目标数据大小通过额外的参数传入。

  • 不同指针类型不能相互赋值,相互赋值后会造成目标数据对象类型的改变,无法通过编译。
  • void* 类型为特例,它可以接受任意指针类型的赋值,也可以赋值给任意类型的指针。
void swap(void* x, void* y, int size)
{// 指针转为char *,单个字节操作内存char* pX = (char*)x;char* pY = (char*)y;char temp;for (int i = 0; i < size; i++){temp = pX[i];pX[i] = pY[i];pY[i] = temp;}
}

由于 void _ 不能取值和加减,所以我们将其转换为 char _ 。 char * 可以提供单个单个操作内存的能力。
在C语言中 void *类型不但可以接受任意类型的指针,也可以自动转换为任意类型的指针。
但在C++中,规则稍微严格了一点, void * 仅能接受任意类型的指针,不能自动转换为其他类型的指针。为了保证代码的兼容性,我们将 void * 强制转为 char * ,避免在C++中编译出错。

char *pX = (char *)x;
char *pY = (char *)y;

多级指针与指针数组

int * 的指针的类型为 int **

int **p; // 正确
int**p; // 正确
int* *p; // 正确
int * *p; // 正确
int * * p; // 正确

二级指针为例

#include <stdio.h>
int main()
{int n = 123;int* pn = &n;int** pnn = &pn;printf("**pnn = %d\n", **pnn);return 0;
}

取地址过程

  • 对n使用取地址运算符,获得n的指针pn,类型为 int * 。
  • 对pn使用取地址运算符,获得pn的指针pnn,类型为 int ** 。

取值过程

  • 对pnn使用取值运算符,将 int ** 还原为 int * 。
  • 对_pnn使用取值运算符,将 int _ 还原为 int 。即,还原为n。

指针数组

p ,指向 pToArr 的第一个元素,类型为 int ** 。
*p ,指向 arr1 的第一个元素,类型为 int * 。
*p + j ,指向 arr1 中的第j个元素,类型为 int * 。
*(*p + j) ,为 arr1 中的第j个元素。

多维数组名与指针

数组指针的移动

#include <stdio.h>
int main()
{int b[5][10] ={{0,1,2,3,4,5,6,7,8,9},{10,11,12,13,14,15,16,17,18,19},{20,21,22,23,24,25,26,27,28,29},{30,31,32,33,34,35,36,37,38,39},{40,41,42,43,44,45,46,47,48,49},};int(*pInt10)[10] = b;   // int[5][10]转为int (*)[10]int(*pInt) = *pInt10;   // *pInt10从int[10]转换为int *printf("pInt[0]=%d\n", pInt[0]);    // 等价于*(pInt + 0)printf("pInt[1]=%d\n", pInt[1]);    // 等价于*(pInt + 1)printf("pInt[2]=%d\n", pInt[2]);    // 等价于*(pInt + 2)printf("pInt[3]=%d\n", pInt[3]);    // 等价于*(pInt + 3)printf("pInt[4]=%d\n", pInt[4]);    // 等价于*(pInt + 4)printf("pInt[5]=%d\n", pInt[5]);    // 等价于*(pInt + 5)printf("pInt[6]=%d\n", pInt[6]);    // 等价于*(pInt + 6)printf("pInt[7]=%d\n", pInt[7]);    // 等价于*(pInt + 7)printf("pInt[8]=%d\n", pInt[8]);    // 等价于*(pInt + 8)printf("pInt[9]=%d\n", pInt[9]);    // 等价于*(pInt + 9)printf("pInt[10]=%d\n", pInt[10]);  // 等价于*(pInt + 10)return 0;
}

另一种结果相同的表达

#include <stdio.h>
int main()
{int b[5][10] ={{0,1,2,3,4,5,6,7,8,9},{10,11,12,13,14,15,16,17,18,19},{20,21,22,23,24,25,26,27,28,29},{30,31,32,33,34,35,36,37,38,39},{40,41,42,43,44,45,46,47,48,49},};int(*pInt10)[10] = b;   // int[5][10]转为int (*)[10]printf("*pInt10[0]=%d\n", (*pInt10)[0]);    // pInt10先从int(*)[10]转为int *,再通过下标访问printf("*pInt10[1]=%d\n", (*pInt10)[1]);printf("*pInt10[2]=%d\n", (*pInt10)[2]);printf("*pInt10[3]=%d\n", (*pInt10)[3]);printf("*pInt10[4]=%d\n", (*pInt10)[4]);printf("*pInt10[5]=%d\n", (*pInt10)[5]);printf("*pInt10[6]=%d\n", (*pInt10)[6]);printf("*pInt10[7]=%d\n", (*pInt10)[7]);printf("*pInt10[8]=%d\n", (*pInt10)[8]);printf("*pInt10[9]=%d\n", (*pInt10)[9]);printf("*pInt10[10]=%d", (*pInt10)[10]);return 0;
}

输出结果

*pInt10[0]=0
*pInt10[1]=1
*pInt10[2]=2
*pInt10[3]=3
*pInt10[4]=4
*pInt10[5]=5
*pInt10[6]=6
*pInt10[7]=7
*pInt10[8]=8
*pInt10[9]=9
*pInt10[10]=10

由于下标运算符[]的优先级比取值运算符*的优先级高。我们想要pInt10先从int (*)[10]转为int *,再通过下标访问。所以,需要用括号让取值先进行。

如果数组名B出现在表达式中,会从int[5][10]转为int (*)[10]
*B又可以看作*(B + 0),所以*B等价于B[0]

对数组取地址

当数组名arr出现在一个表达式当中,数组名arr将会被转换为指向数组首元素的指针。但是,这个规则有两个例外:

  • 对数组名arr使用sizeof时。
  • 对数组名arr使用&时。

现在开始讨论第二个例外。

int arr[10];
&arr;

arr的类型为int[10],而对数组使用取地址运算符&。触发第二个例外,数组不会进行类型转换,而是直接取地址

  • int取地址为int (*)类型的指针。
  • int[10]取地址为int (*)[10]类型的指针。
#include <stdio.h>
int main()
{int arr[10];int(*pInt10)[10] = &arr;printf("pInt10=%u\n", pInt10);printf("pInt10+1=%u\n", pInt10 + 1);return 0;
}

pInt10是类型int (*)[10]类型的数组指针,步长为40。

pInt10=7601480
pInt10+1=7601520

如果再对pInt10取地址呢?

  • int[10]类型的数组取地址为int (*)[10]类型的数组指针,它指向int[10]的数组。
  • int (*)[10]类型的数组指针取地址为int (**)[10]的二级指针,它指向int(*)[10]的指针。

注意&&arr是不对的,&arr确实可以获得一个指针。但是,这个指针是一个临时数据对象,应当将其赋值给变量才能保存它的值。

指针与三维数组示例

#include <stdio.h>
int main()
{int S[2][5][10] = {{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},{20, 21, 22, 23, 24, 25, 26, 27, 28, 29},{30, 31, 32, 33, 34, 35, 36, 37, 38, 39},{40, 41, 42, 43, 44, 45, 46, 47, 48, 49}},{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},{20, 21, 22, 23, 24, 25, 26, 27, 28, 29},{30, 31, 32, 33, 34, 35, 36, 37, 38, 39},{40, 41, 42, 43, 44, 45, 46, 47, 48, 49}}};// 访问元素S[1][2][3]printf("S[1][2][3] = %d", *(*(*(S + 1) + 2) + 3));return 0;
}

分析

S + 1 类型为 int(*)[5][10] 的指针。
*(S + 1) 类型为 int[5][10] 的数组。
*(S + 1) + 2 类型为 int(*)[10] 的指针。
*(*(S + 1) + 2) 类型为 int[10] 的数组。
*(*(S + 1) + 2) + 3 类型为 int(*) 的指针。
*(*(*(S + 1) + 2) + 3) 类型为 int 的整型。

{% gallery::::one %}






{% endgallery %}

也可以故意将表达式结果赋值给一个无法转换的变量。让报错信息告诉我们表达式结果具体的类型

验证


指针的大小为4,整型的大小为4。
int[5][10] 数组的大小为200。
int[10] 数组的大小为40。

对数组取地址

int[2][5][10] 取地址为 int (*)[2][5][10] 类型的指针。

多级指针应用

从函数中返回指针

return关键词可以从被调函数中返回一个值到主调函数。
现在我们尝试让它返回一个指针到主调函数中。

#include <stdio.h>
int* func()
{int n = 100;return &n;
}
int main()
{int* p = func();printf("%d\n", *p);return 0;
}

我们在函数 func 中定义了变量n。接着return &n;取得变量n的指针,并返回到main函数。
main函数收到返回值后赋值给p,并使用指针p来访问变量n。

这个程序看似正确,并且可以通过编译。但是,却存在潜在问题。
这是因为函数结束后,函数内部的变量也会被回收。所以,变量 n 已经失效了。再去访问它有可能正常,也有可能得到一些无意义的值或者引发错误。
这样设计的原因是因为函数与函数之间的变量是独立的,即使是同一个函数多次运行,这些变量也是独立的。在函数返回后,函数内的变量没有继续存在的意义了。所以,函数内的变量将被回收,回收后的内存空间将给接下来运行的函数使用。
如果不想让变量被回收,那么可以在变量前加上关键词**static**

int* func()
{static int n = 100; // 关键词static让变量n不被回收n++; // 变量n自增return &n;
}
#include <stdio.h>
int main()
{int* p = func();printf("%d\n", *p);func();printf("%d\n", *p);func();printf("%d\n", *p);func();printf("%d\n", *p);func();printf("%d\n", *p);return 0;
}

现在函数 func 结束后,变量n不会被回收了。并且,重复调用func函数,使用的是同一个地址上的变量n
因此,我们只需获取一次变量n的地址,即可观察到变量n每次调用函数都被自增。

从函数中返回多个变量

将指针的指针,也就是二级指针作为参数传入函数。即可让被调函数“返回”多个指针。

void func(int** a, int** b)
{static int x = 100;static int y = 200;*a = &x;*b = &y;
}
#include <stdio.h>
int main()
{// 两个指针,初始化为空int* a = NULL;int* b = NULL;func(&a, &b); // 将指针的指针传入被调函数if (a != NULL && b != NULL)printf("a=%d b=%d\n", *a, *b);return 0;
}

在main函数中,声明两个指针并把它们初始化为 NULL
**NULL**** 是一个由 **#define NULL 0** 定义的符号常量。**
将指针初始化为NULL,也就是将指针内保存的地址设置为0。
让指针初始化为零是一个非常好的编码习惯。
一般结合指针判空,即 if (a != NULL && b != NULL) ,来判断指针是不是有一个正确的指向了。
调用函数 func 后,两个指针均被修改为有效指针,即非0。
我们通过判断指针是不是非零来确定函数 func 已经给指针赋值了。
若指针仍然为0,则说明函数 func 并未给指针赋值,不可以使用没有明确指向的指针。
函数 func 内部, &x 、 &y 取得变量 x 、 y 的指针,类型为 int *
在被调函数内,为了修改主调函数中的变量,先对二级指针 a、b 取值,将 int ** 转换为 int * ,再赋
值一个 int 给它。
类似于使用一级指针作为参数时,先对一级指针 a、b 取值,将 int * 转换为 int ,再赋值一个 int 给它。

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

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

相关文章

Vue 2.0的源码构建

Vue.js 源码是基于 Rollup 构建的&#xff0c;它的构建相关配置都在 scripts 目录下。 1. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json 文件&#xff0c;它是对项目的描述文件&#xff0c;它的内容实际上是一个标准的 JSON 对象。 我们通常会配置 script …

Autox.js和Auto.js4.1.1手机编辑器不好用我自己写了一个编辑器

功能有 撤销 重做 格式化 跳转关键词 下面展示一些 内联代码片。 "ui"; ui.layout( <drawer id"drawer"><vertical><appbar><toolbar id"toolbar"title""h"20"/></appbar><horizontal b…

get_cli_args函数

CLI是"Command Line Interface"的缩写&#xff0c;中文意为"命令行界面"。它是一种与计算机进行交互的方式&#xff0c;用户通过键盘输入文本命令来执行特定的任务&#xff0c;而不是通过图形用户界面&#xff08;GUI&#xff09;进行操作。在命令行界面中…

P1734 最大约数和

P1734 最大约数和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 预处理出小于等于S的数的约数和&#xff0c;将一个数的值作为体积&#xff0c;这个数的约数和作为价值&#xff0c;之后01背包模板。 void solve() {int n; cin>>n;vector<array<int,2>> a…

Linux环境搭建(tomcat,jdk,mysql下载)

是否具备环境&#xff08;前端node&#xff0c;后端环境jdk&#xff09;安装jdk,配置环境变量 JDK下载 - 编程宝库 (codebaoku.com) 进入opt目录 把下好的安装包拖到我们的工具中 把解压包解压 解压完成&#xff0c;可以删除解压包 复制解压文件的目录&#xff0c;配置环境变量…

【opencv】debug报错HEAP CORRUPTION DETECTED

运行至第一句涉及矩阵运算的代码&#xff08;如cv::multiply&#xff09;时报错 HEAP CORRUPTION DETECTED: after Normal block (#45034) at 0x000001BDC586F0E0. CRT detected that the application wrote to memory after end of heap buffer.release下不会报错&#xff0…

vue3使用西瓜播放器播放flv、hls、mp4视频

vue3使用西瓜播放器播放flv、hls、mp4视频 安装相关的插件 npm install xgplayer npminstall xgplayer-flv npm install xgplayer-hls npm install xgplayer-mp4 组件封装 <template><div :id"${playerId}" /> </template> <script setup la…

PDF控件Spire.PDF for .NET【转换】演示:自定义宽度、高度将 PDF 转 SVG

我们在上一篇文章中演示了如何将 PDF 页面转换为 SVG 文件格式。本指南向您展示如何使用最新版本的 Spire.PDF 以及 C# 和 VB.NET 指定输出文件的宽度和高度。 Spire.Doc 是一款专门对 Word 文档进行操作的 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻…

MyBatis 快速入门

MyBatis 快速入门 前言什么是 MyBatis简介核心特性使用示例配置文件Mapper 接口SQL 映射文件使用 MyBatis 如果大家对以上的导读很懵怎么办&#xff01;没关系 往下阅读&#xff01; 1. MyBatis 介绍1.1. 什么是MyBatis1.2. 持久层1.3. 框架1.4. JDBC 弊端1.5.…

如何解决网站被攻击的问题:企业网络攻防的关键路径

在当今数字化时代&#xff0c;企业面临着不断升级的网络威胁&#xff0c;网站遭受攻击的风险也与日俱增。解决网站被攻击的问题对企业发展至关重要&#xff0c;不仅关系到企业的信息安全&#xff0c;也直接影响到企业的声誉和利益。从企业发展的角度出发&#xff0c;我们将探讨…

科技云报道:全球勒索攻击创历史新高,如何建立网络安全的防线?

科技云报道原创。 最简单的方式&#xff0c;往往是最有效的&#xff0c;勒索软件攻击就属于这类。 近两年&#xff0c;随着人类社会加速向数字世界进化&#xff0c;勒索软件攻击成为网络安全最为严重的威胁之一。今年以来&#xff0c;勒索软件攻击在全球范围内呈现快速上升态…

开源和闭源的优劣势比较

开源与闭源软件之争一直是技术领域一个备受关注的话题&#xff0c;而在近期特斯拉CEO马斯克的表态中&#xff0c;关于开源的讨论更是引发了广泛的关注。以下是一些关于开源和闭源的优劣势以及对未来大模型发展的一些见解&#xff1a; 开源软件的优势&#xff1a; 创新与合作&a…

HDCTF2023 - Reverse方向全WP

文章目录 [HDCTF 2023]easy_re[HDCTF 2023]easy_asm[HDCTF 2023]fake_game[HDCTF 2023]enc[HDCTF 2023]double_code[HDCTF 2023]买了些什么呢[HDCTF2023]basketball [HDCTF 2023]easy_re UPX壳&#xff0c;脱壳 一个base64编码。 [HDCTF 2023]easy_asm ida打开后可以看到xor 10…

【FPGA】IP核

一.IP核是什么 IP&#xff1a;知识产权&#xff0c;半导体产业中&#xff1a;在ASIC和FPGA中定义为预先设计好的电路功能模块。 在使用的时候其他用户可以直接调用IP核心。 二. 为什么要是有IP核 提高开发效率&#xff0c;减小设计和调试的时间&#xff0c;加速开发进程&am…

go 判断两棵树内容是否一致

goroutine http://127.0.0.1:3999/concurrency/8 question 使用 go 判断 两个 树 存放的 序列 是否 相同, 如果 相同 他们 被称为 equivalent_tree tree struct type Tree struct {Left *TreeValue intRight *Tree }由于 递归的 写法 比较简单, 此处 使用循环的 形式 来实…

Rabin加解密算法(python3)

Rabin加解密算法 详细代码如下&#xff1a; # 空空 # dahouzi.cn import random from sympy import isprimedef decrypt_rabin(c, p, q):"""解密 Rabin 密文Args:c (int): 密文p (int): 素数 pq (int): 素数 qReturns:tuple: 解密结果 M1, M2, M3, M4"&q…

数据库管理工具,你可以用Navicat,但我选DBeaver!

大家好&#xff0c;我是豆小匠。数据库GUI工具哪家强&#xff0c;众人遥指Navicat。 可是Navicat老贵了。 如果公司有正版授权的还好&#xff0c;如果没有正版授权&#xff0c;还不给你用盗版&#xff0c;那才叫绝绝子。 好了&#xff0c;主角登场&#xff0c;DBeaver&#x…

Protege简单教程(安装启动建模推理)

Protege 是一个用于本体编辑和OWL (Web Ontology Language) 开发的开源工具。下面是一个简单的 Protege 使用教程。 1. 下载和安装 Protege 首先&#xff0c;需要下载 Protege。可以从官方网站 Protege 下载最新版本。 2. 启动 Protege 安装完成后&#xff0c;启动 Protege…

基于安卓android微信小程序美容理发店预约系统app

项目介绍 为美容院设计一个系统以减少员工的工作量就成为了想法的初始状态。紧接着对美容院进行进一步的调查发现我的想法已然落后。基本上每个美容院都以有了自己的信息系统&#xff0c;并且做的已经较完善了。 在这时我突然想到&#xff0c;现在关注美容养生的人越来越多&am…

智能井盖传感器建设信息化时代智慧城市

近年来随着信息技术的快速发展和城市化进程的加速推进&#xff0c;智慧城市的概念逐渐成为现实。作为智慧城市生命线建设中的重要组成部分&#xff0c;智能井盖传感器的应用正在为城市的可持续发展和居民的生活质量提供新的解决方案。 智能井盖传感器能够实时监测井盖状态&…