C语言 -- 深入理解指针(一)

C语言 -- 深入理解指针(一)

  • 1.内存和地址
    • 1.1 内存
    • 1.2 究竟该如何理解编址
  • 2. 指针变量和地址
    • 2.1 取地址操作符(&)​
    • 2.2 指针变量和解引用操作符(*)​​
      • 2.2.1 指针变量
      • 2.2.2 如何拆解指针类型
      • 2.2.3 解引用操作符​
    • 2.3 指针变量的大小
  • 3. 指针变量类型的意义
    • 3.1 指针的解引用
    • 3.2 指针+-整数
    • 3.3 void* 指针
  • 4. const修饰指针
    • 4.1 const修饰变量
  • 5. 指针运算
    • 5.1 指针+- 整数​
    • 5.2 指针- 指针
    • 5.3 指针的关系运算​
  • 6. 野指针
    • 6.1 野指针成因​
    • 6.2 如何规避野指针​
      • 6.2.1 指针初始化​
      • 6.2.2 小心指针越界
      • 6.2.3 指针变量不再使用时,及时置NULL,指针使用之前检查有效性​
      • 6.2.4 避免返回局部变量的地址
  • 7. assert断言​
  • 8. 指针的使用和传址调用
    • 8.1 strlen的模拟实现​
    • 8.2 传值调用和传址调用​

1.内存和地址

1.1 内存

  • 定义 :在计算机的组成结构中有一个很重要的部分是存储器。它是用来存储程序和数据的部件。
  • 我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?

1.其实也是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节。一个字节里面能放八个比特。每个内存单元也都有一个编号,有了这个内存单元的编号,CPU就可以快速找到一个内存空间。
2.在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。
3.所以我们可以理解为:内存单元的编号 == 地址 == 指针​

关于对内存有着更进一步的了解,我们可以看图:如下
在这里插入图片描述
计算机中常见的单位(补充):
⼀个比特位可以存储⼀个2进制的位1或者0
在这里插入图片描述

1.2 究竟该如何理解编址

  • CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很多,需要给宿舍编号一样)。
  • 计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。
  • 首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。
  • 但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。

请看下图
在这里插入图片描述
假设CPU要想去内存中拿数据(值)时,怎么拿呢?
先通过控制总线向内存中读一个数据,读的数据地址在内存哪里呢?

  • 地址总线会给出信息,如果在32位机器上就会有32根地址总线,每一根地址线上都会有一个电信号,高电频或低电频这样的一个电信号,我们把这样的电信号转化成数字信号的时候就是1 或者 0,每根地址线上都给这样的信号话,那32根地址线上就会产生一个二进制序列来作为地址,因为有32根地址总线 所以就能产生2^32种二进制序列,每一种都代表一个地址。

我们要去读一个数据时,地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

2. 指针变量和地址

2.1 取地址操作符(&)​

首先我们先看下面代码:
在这里插入图片描述
其中每个字节都有地址,我们如何能得到a的地址呢?
这里就得学习一个操作符(&)-取地址操作符
在看如下代码:

在这里插入图片描述

打印出的a地址是以 16进制的形式输出的。展开的形式不以二进制形式展示,因为二进制位太长了,观察起来不方便。

1.&a取出的是a所占4个字节中地址较小的字节的地址。
2.虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

2.2 指针变量和解引用操作符(*)​​

2.2.1 指针变量

  • 那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。

如下:

在这里插入图片描述

  1. pa 是指针变量 - 存放地址 - 地址又被称为指针,指针变量是用来存放指针的
  2. 指针 和 指针变量的区别
    2.1 指针就是地址,指针变量就是一个变量 -->存放地址
  3. 口头语:说指针一般就是指针变量

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

2.2.2 如何拆解指针类型

我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?​
解析下面代码:

在这里插入图片描述

  • int * 是 指针变量pa的类型
  • 这里的 * 说明pa是指针变量
  • 最前面的int是在说明pa指向的对象是int类型的

如下图:

在这里插入图片描述
那如果有一个char类型的变量ch,ch的地址,要放在什么类型的指针变量中呢?

char ch = ‘w’;
pc = &ch;//pc 的类型怎么写呢?
答案是 : char * pc = &ch;

2.2.3 解引用操作符​

  • 我们将地址保存起来,未来是要使用的,那怎么使用呢?
  • 在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品。
  • C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须学习一个操作符叫解引用操作符(*)。​

在这里插入图片描述

1 .* pa 的意思就是通过pa中存放的地址,找到指向的空间,
2. * pa其实就是变量a;所以*pa = 0,这个操作符是把a改成了0.​

有同学肯定在想,这里如果目的就是把a改成0的话,写成 a = 0; 不就完了,为啥非要使用指针呢?​
其实这里是把a的修改交给了pa来操作,这样对a的修改,就多了一种的途径,写代码就会更加灵活,后期慢慢就能理解了。

2.3 指针变量的大小

  • 指针变量是用来专门存放地址的,大小取决于一个地址的存放需要多大空间。
  • 在32位机器上,地址线是32根,地址的二进制序列就是32bit位,要把这个地址存起来,就需要4个字节才能存储。
  • 同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要8个字节的空间。

在这里插入图片描述

在这里插入图片描述
结论

  1. 32位平台下地址是32个bit位,指针变量大小是4个字节
  2. 64位平台下地址是64个bit位,指针变量大小是8个字节​
  3. 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

3. 指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的,我们接下来继续学习。

3.1 指针的解引用

对比,下面2段代码,主要在调试时观察内存的变化
在这里插入图片描述

  • 调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。

结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

3.2 指针±整数

下面再看一段代码,观察地址的变化。
在这里插入图片描述
运行结果:

在这里插入图片描述

  • 我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。

结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。

3.3 void* 指针

  • 在指针类型中有一种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的±整数和解引用的运算。​

举例:
在这里插入图片描述

  • 在上面的代码中,将一个int类型的变量的地址赋值给一个char星类型的指针变量。编译器给出了一个警告(如下图),是因为类型不兼容。而使用void*类型就不会有这样的问题。

使用void*类型的指针接收地址:

在这里插入图片描述

这里我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算

  • 那么 void* 类型的指针到底有什么用呢?
  • *一般 void 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。**使得一个函数来处理多种类型的数据。

4. const修饰指针

4.1 const修饰变量

  • 变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量。
  • 但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用。

在这里插入图片描述

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就行修改,就不符合语法规则,就报错,致使没法直接修改n。
但是如果我们绕过n,使用n的地址,去修改n就能做到了,虽然这样做是在打破语法规则。
在这里插入图片描述

我们可以看到这里一个确实修改了,但是我们还是要思考一下,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?
首先我们要明白:

1.p里面存放的是地址(a的地址)
2. p是变量,由自己的地址
3.*p是p指向的空间

  • const修饰指针变量的时候放的位置不同,作用不同
  • const 可以放在 * 的左边
  • const 也可以放在 * 的右边
  • const int * p (限制*p)
  • int const * p (限制*p)
  • int * const p (限制 p)

(const限制*p) 意思是:不能通过p来修改p指向的空间内容,但是指针变量本身的内容可变。
(const 限制 p) 意思是:p变量不能被修改了,没办法在指向其他变量了,但是指针指向的内容,可以通过指针改变。

图如下:

在这里插入图片描述
在这里插入图片描述

5. 指针运算

指针的基本运算有三种,分别是:

  • 指针± 整数
  • 指针-指针​
  • 指针的关系运算

5.1 指针± 整数​

因为数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的所有元素
在这里插入图片描述
例子:

在这里插入图片描述

5.2 指针- 指针

在这里插入图片描述

1.指针-指针的绝对值是指针和指针之间的元素个数
2. 指针-指针运算的前提条件是:两个指针指向同一块空间

指针-指针 的应用

在这里插入图片描述

5.3 指针的关系运算​

在这里插入图片描述

6. 野指针

  • 概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)​

6.1 野指针成因​

1. 指针未初始化
在这里插入图片描述

全局变量如果不初始化,变量的值默认为0.
静态变量不初始化,值也默认为0;

2. 指针越界访问

在这里插入图片描述

3. 指针指向的空间释放

在这里插入图片描述

  • 局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束。也就是变量被销毁。

6.2 如何规避野指针​

6.2.1 指针初始化​

如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL.​NULL 是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

在这里插入图片描述

6.2.2 小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

6.2.3 指针变量不再使用时,及时置NULL,指针使用之前检查有效性​

在这里插入图片描述

6.2.4 避免返回局部变量的地址

如造成野指针的第3个例子,不要返回局部变量的地址。

7. assert断言​

  • assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

assert(p != NULL);

  • 上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。

下面代码测试

#include <stdio.h>
#include <assert.h>
int main()
{
	int a = 10;
	int* p = &a;
	//....
	//....
	p = NULL;
	int b = 5;
	assert(p != NULL);
	//assert(b == 5);

	printf("%d\n", *p);
	return 0;
}

运行结果:
在这里插入图片描述

  • assert() 宏接受一个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
  • assert() 的使用对程序员是非常友好的,使用 assert() 有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义一个宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

测试代码如下:

#include <stdio.h>
//#define NDEBUG   没有定义宏时,代码结果会报错
#include <assert.h>
int main()
{
	int a = 10;
	int* p = &a;
	//....
	//....
	//p = NULL;
	int b = 20;
	assert(p != NULL);
	assert(b == 5); //断言b == 5,但实际上我们初始化b =20;
					//不符合b ==5 ,报错

	printf("%d\n", *p);
	return 0;
}

运行结果:
在这里插入图片描述
测试代码2:

在这里插入图片描述

  • 然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。如果程序又出现问题,可以移除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启用了 assert() 语句。

assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
一般我们可以在 Debug 中使用,在 Release 版本中选择禁用 assert 就行,在 VS 这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使用时程序的效率。

8. 指针的使用和传址调用

8.1 strlen的模拟实现​

  • 库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。​
  • 函数原型如下:

size_t strlen ( const char * str );

  • 参数str接收一个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回长度。​如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停止。

在这里插入图片描述

8.2 传值调用和传址调用​

  • 传值调用:在函数调用的时候传的是数值
  • 传址调用:在函数调用的时候传的是地址

下面是一个传值调用的例子

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	//调用函数
	int ret = Add(a, b); //传值调用
	printf("%d\n", ret);

	return 0;
}
  • 学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
  • 例如:写一个函数,交换两个整型变量的值
    一番思考后,我们可能写出这样的代码:

在这里插入图片描述

我们发现其实没产生交换的效果,这是为什么呢?
调试一下,试试呢?

  • 我们发现在main函数内部,创建了a和b,a的地址是0x00cffa58,b的地址是0x00cffa4c,在调用Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是0x00cff974,y的地址是0x00cff978,x和y确实接收到了a和b的值,过x的地址和a的地址不一样,y的地址和b的地址不一样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值,只有x和y的值交换了,自然不会影响a和b,当Swap1函数调用结束后回到main函数,a和b的没法交换。Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种叫传值调用。

结论:实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参。

  • 所以Swap1是失败的了。​
  • 那怎么办呢?
  • 那就用传址调用写个Swap2函数试试
  • 我们现在要解决的就是当调用Swap2函数的时候,Swap2函数内部操作的就是main函数中的a和b,直接将a和b的值交换了。那么就可以使用指针了,在main函数中将a和b的地址传递给Swap2函数,Swap2函数里边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

代码如下:

在这里插入图片描述

  • 我们可以看到实现成Swap2的方式,顺利完成了任务,这里调用Swap2函数的时候是将变量的地址传递给了函数,这种函数调用方式叫:传址调用。

传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。

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

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

相关文章

Vue2和Vue3的区别Vue3的组合式API

一、Vue2和Vue3的区别 1、创建方式的不同&#xff1a; &#xff08;1&#xff09;、vue2:是一个构造函数&#xff0c;通过该构造函数创建一个Vue实例 new Vue({})&#xff08;2&#xff09;、Vue3:是一个对象。并通过该对象的createApp()方法&#xff0c;创建一个vue实例。 Vue…

【React】React18 Hooks之useState

目录 useState案例1&#xff08;直接修改状态&#xff09;案例2&#xff08;函数式更新&#xff09;案例3&#xff08;受控表单绑定&#xff09;注意事项1&#xff1a;set函数不会改变正在运行的代码的状态注意事项2&#xff1a;set函数自动批量处理注意事项3&#xff1a;在下次…

C++ 多态篇

文章目录 1. 多态的概念和实现1.1 概念1.2 实现1.2.1 协变1.2.2 析构函数1.2.3 子类虚函数不加virtual 2. C11 final和override3.1 final3.2 override 3. 函数重载、重写与隐藏4. 多态的原理5. 抽象类6.单继承和多继承的虚表6.1 单继承6.2 多继承 7. 菱形继承的虚表(了解)7.1 菱…

springboot三层架构详细讲解

目录 springBoot三层架构0.简介1.各层架构1.1 Controller层1.2 Service层1.3 ServiceImpl1.4 Mapper1.5 Entity1.6 Mapper.xml 2.各层之间的联系2.1 Controller 与 Service2.2 Service 与 ServiceImpl2.3 Service 与 Mapper2.4 Mapper 与 Mapper.xml2.5 Service 与 Entity2.6 C…

论文阅读--Simple Baselines for Image Restoration

这篇文章是 2022 ECCV 的一篇文章&#xff0c;是旷视科技的一篇文章&#xff0c;针对图像恢复任务各种网络结构进行了梳理&#xff0c;最后总结出一种非常简单却高效的网络结构&#xff0c;这个网络结构甚至不需要非线性激活函数。 文章一开始就提到&#xff0c;虽然在图像复原…

【MYSQL】事务隔离级别以及InnerDB底层实现

事务隔离级别 读未提交&#xff08;Read Uncommitted&#xff09; 允许事务读取其他事务未提交的数据&#xff0c;可能会导致脏读。 读已提交&#xff08;Read Committed&#xff09; 一个事务只能看见已经提交的事务所做的更改&#xff0c;可以避免脏读&#xff0c;但可能…

【C++:默认成员函数初始化列表】

构造函数 特点 没有返回值支持函数重载对象实例化时&#xff0c;编译器自动调用作用不是构造&#xff0c;而是初始化函数名与类名相同无参函数和全缺省的函数&#xff0c;不用传参就能调用的函数叫做默认构造函数 构造函数是一个特殊的成员函数 注&#xff1a;无参构造函数在实…

星辰宇宙动态页面vue版,超好看的前端页面。附源码与应用教程(若依)

本代码的html版本&#xff0c;来源自“山羊の前端小窝”作者&#xff0c;我对此进行了vue版本转换以及相关应用。特此与大家一起分享~ 1、直接上效果图&#xff1a; 带文字版&#xff1a;文字呼吸式缩放。 纯净版&#xff1a; 默认展示效果&#xff1a; 缩放与旋转后&#xf…

简单的手动实现spring中的自动装配案例

简简单单的实现一个spring中的自动装配和容器管理的小骚操作。 1&#xff0c;创建AutoSetBean.java 使用injectBeans静态方法&#xff0c;可以扫描指定包下的所有带MyInject注解的字段&#xff0c;如果在beans的Map中存在这个字段的实例化类&#xff0c;则执行装配。 import…

【UE5.1 角色练习】13-枪械射击——拿出与收起武器

目录 效果 步骤 一、安装射击武器 二、拿武器和收武器 效果 步骤 一、安装射击武器 1. 在虚幻商城中将“FPS Weapon Bundle”添加到工程中&#xff0c;由于我们使用的是5.1版本&#xff0c;我们可以先将该资产放入UE4工程中&#xff0c;然后迁移到5.1版本的工程 2. 打开角…

alphazero学习

AlphaGoZero是AlphaGo算法的升级版本。不需要像训练AlphaGo那样&#xff0c;不需要用人类棋局这些先验知识训练&#xff0c;用MCTS自我博弈产生实时动态产生训练样本。用MCTS来创建训练集&#xff0c;然后训练nnet建模的策略网络和价值网络。就是用MCTSPlayer产生的数据来训练和…

Spring与Quartz整合

Quartz框架是一个轻量级的任务调度框架&#xff0c;它提供了许多内置的功能&#xff0c;包括&#xff1a;支持作业的调度、集群调度、持久化、任务持久化、任务依赖、优先级、并发控制、失败重试等。同时也支持自定义作业类型和触发器类型。与Spring整合步骤如下&#xff1a; …

深入解析.[datastore@cyberfear.com].mkp勒索病毒:威胁与防范

引言 在数字化时代&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒&#xff08;Ransomware&#xff09;作为一种极具破坏性的恶意软件&#xff0c;严重威胁着个人用户和企业机构的数据安全。.[ datastorecyberfear.com].mkp勒索病毒便是这一领域中的一颗“毒瘤”&am…

2.5 C#视觉程序开发实例1----IO_Manager实现脉冲输出控制

2.5 C#视觉程序开发实例1----IO_Manager实现脉冲输出控制 1 目标效果视频 目标效果展示 IO_Manager 2 信号输出流程说明 为了防止线程不同步导致输出信号没有被输出&#xff0c; 尽量使用一个输出队列来进行输出的管理 3 IO_Manager中添加内容 3.0 添加两个类 1 Out_Sta…

【课程总结】Day13(下):人脸识别和MTCNN模型

前言 在上一章课程【课程总结】Day13(上):使用YOLO进行目标检测,我们了解到目标检测有两种策略,一种是以YOLO为代表的策略:特征提取→切片→分类回归;另外一种是以MTCNN为代表的策略:先图像切片→特征提取→分类和回归。因此,本章内容将深入了解MTCNN模型,包括:MTC…

Windows 11文件资源管理器选项卡的4个高级用法,肯定有你喜欢的

作为一个每天使用文件资源管理器来管理我的工作流程的人,选项卡帮助我为处于不同完成阶段的工作创建了不同的文件夹。以下是我使用选项卡提高工作效率的最佳技巧。 打开和关闭选项卡 假设你的计算机上安装了Windows 11的最新更新,请按Ctrl+E打开文件资源管理器。在我发现“…

STM32智能家居掌上屏实战:从WiFi连接到MQTT通信,打造你的家庭物联网网关

摘要: 本文深入探讨一种基于STM32的智能家居掌上屏设计方案&#xff0c;详细阐述其硬件架构、软件设计以及通信协议等关键技术细节。该方案利用WiFi构建局域网&#xff0c;实现与各类传感器、执行器的便捷交互&#xff0c;并通过TFT彩屏提供直观的控制和数据展示&#xff0c;旨…

五、保存数据到Excel、sqlite(爬虫及数据可视化)

五、保存数据到Excel、sqlite&#xff08;爬虫及数据可视化&#xff09; 1&#xff0c;保存数据到excel1.1 保存九九乘法表到excel&#xff08;1&#xff09;代码testXwlt.py&#xff08;2&#xff09;excel保存结果 1.2 爬取电影详情并保存到excel&#xff08;1&#xff09;代…

大模型周报|15 篇必读的大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.谷歌推出风格感知拖放新方法 Magic Insert 来自谷歌的研究团队提出了 Magic Insert&#xff0c;用于以物理上可信的方式将用户提供的图像中的对象拖放到不同风格的目标图像中&#xff0c;同时与目标图像的风格相匹…

基于CLIP特征的多模态大模型中的视觉短板问题

【论文极速读】 基于CLIP特征的多模态大模型中的视觉短板问题 FesianXu 20240706 at Tencent WeChat search team 前言 今天读到篇CVPR 24’的论文 [1]&#xff0c;讨论了常见的多模态大模型&#xff08;大多都基于CLIP语义特征&#xff0c;以下简称为MLLM&#xff09;中的视觉…