【C语言】(指针系列2)指针运算+指针与数组的关系+二级指针+指针数组+《剑指offer面试题》

news2024/9/20 9:46:05

前言:开始之前先感谢一位大佬,清风~徐~来-CSDN博客,由于是时间久远,博主指针的系列忘的差不多了,所以有些部分借鉴了该播主的,有些地方如果解释的不到位,请翻看这位大佬的,感谢大家!!!!!!

目录

一、指针运算

1.1指针+-整数

1.2.指针-指针

1.3.指针的关系运算

二、野指针

一.野指针成因

1.指针未初始化

 2.指针越界访问

3.指针指向的空间释放

  三、规避野指针

1.小心指针越界

2.避免返回局部变量的地址

3.指针变量不再使用是置为NULL,使用时检查其有效性

assert断言

四、指针与数组的关系

1.数组名

2.使用指针访问数组

五、字符指针

1.字符指针隐藏秘密

2.常量字符串

《剑指offer》笔试题

六、二级指针

七、指针数组

用指针数组模拟二维数组

结尾祝福语


一、指针运算

1.1指针+-整数

指针是一个存放地址的变量,这些我们都知道,但是对于一个指针来说,他的运算是怎么样的?我们可以看看。我们都知道数组在内存中是连续存放的,只要知道首地址,我们就可以知道后面几个元素的地址。

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

我们观察发现

  • p存放的是arr[0]的地址,p+1跳过4个字节,也就是1个整形,所以p+1指向整形元素arr[1]
     
  • p一次访问4个字节,也就是一个整形,得到arr[0]
     
  • 同理*(p+1)得到arr[1],按照指针的方法就可以打印数组所有的元素
     
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址虽然数值一样,但还是有区别的)
     
  • 除此之外,任何地方使用数组名,数组名都表示首元素的地址。
     

1.2.指针-指针

大指针 - 小指针得到的是指针之间元素的个数,仅限于它们是同一块空间 还有小指针 - 大指针得到的就是负数

#include <stdio.h>
//指针 - 指针
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = &arr[0];
	int* p2 = &arr[6];
	printf("%d\n", p1 - p2);//-6
	printf("%d\n", p2 - p1);//6
	return 0;
}
  • 数组再内存中是连续存放的,且是由低地址向高地址存放的。

1.3.指针的关系运算

指针还能够比较大小,指针本质是地址,地址以16进制显现出来的,所以本质就是比较两个数的大小

	while (p < arr + sz) //指针的大小比较 
	{
		printf("%d ", *p);//打印数组所有的元素
		p++;
	}

!!这还有一个需要注意的点是:

二、野指针

野指针就是指向的位置是不可知的(危险的,未知的,没有明确限制的),是非常危险的

一.野指针成因

1.指针未初始化

//1.指针未初始化
#include <stdio.h>
int main()
{
	int*p//局部指针变量未初始化,没有明确的指向,默认值为随机值
*p=20;//!!!非法访问了,p成了野指针
//随机将p指向的对象改变是非常危险的。
	return 0;
}

 2.指针越界访问

//2.指针越界访问
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
//当指针的指向的范围超出了arr的范围,这就是越界了,也算非法访问
//我们没有权利访问和修改超出的空间

		*(p++)=i;
	}
	return 0;
}

3.指针指向的空间释放

//3.返回局部变量的地址(生命周期结束后使用)
int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
/*p要存a的地址,函数返回的时候已经把a还给操作系统了,p没有权限访问这块空间,所以p是野指针
但是内存里的这块空间还在,只是不属于当前程序,没有使用的权限*/

	printf("%d\n", *p);//通过非法的地址,如果这块空间没有被使用(覆盖),还能找到10,但是不属于我们。
	return 0;
}

  三、规避野指针

如果知道这块指针指向的哪里就直接将这块地址赋值给指针,如果不知道指针只想哪里,就赋值给NULL

NULL是C语言当中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错

#include<stdio.h>
int main()
{
int num=10;
int*p1=&num;
int*p2=NULL;
return 0;
}

1.小心指针越界

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

2.避免返回局部变量的地址

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

3.指针变量不再使用是置为NULL,使用时检查其有效性

  • 当一个指针变量指向一块区域时,我们可以通过指针访问这块区域,但是我们如果后期不再使用时,我们置为NULL,这样就不用害怕成野指针了,为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使用前要判断指针是不是NULL
  • 我们可以把野指针想象成野狗,野狗放任不管是非常危险的,所以我们可以找⼀棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起来
  • 不过野狗即使拴起来我们也要绕着走,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使用,如果不是我们再去使用。
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p++) = i;
	}
	//此时p已经越界了,可以把p置为NULL 
	p = NULL;
	//下次使⽤的时候,判断p不为NULL的时候再使⽤ 
	//...

	p = &arr[0];//重新让p获得地址 
	if (p != NULL) //判断 
	{
		//...
	}
	return 0;
}

assert断言

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

 assert(p!=NULL);

验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示

四、指针与数组的关系

1.数组名

大多数人认为,数组名只不过是一个代号罢了,没有什么实际的意义,没什么大用,如果你怎么想,那就大错特错了,当初祖师爷在设计的时候,将数组名设计了一个特殊的角色---------数组的首地址 !!!!

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr = %p\n", arr);
	return 0;
}

运行可以发现:两个的地址是完全一样的,所以数组名就是数组的首地址

我们再来看一段代码,看一下arr的大小,

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));
	return 0;
}

这不紧让我们引发了思考:我们微微皱眉,arr既然是元素的首地址,应该是指针变量呀,返回的是应该是(4/8)呀,为什么会返回40哪?

这是因为:

arr是元素的首地址是对的,但是有两个意外:

  • sizeof(数组名)表示sizeof函数如果后面的参数是数组名,表示的是整个数组,取出来的是整个数组,计算时算出来的是整个数组的字节数
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址虽然数值一样,但还是有区别的)

  • 除此之外,任何地方使用数组名,数组名都表示首元素的地址。

再度思考:那么数组的地址,数组名与数组首元素的地址这三种又存在什么关系呢?以下的代码将使你清晰理解这三者的联系:

分析代码

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

 

  • &arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过⼀个元素,也就是4个字节,而每个字节都有对应的地址,且地址相差1,所以它们的地址就相差4。
     
  • &arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组,就是40个字节,地址相差(0x26),到这里大家应该搞清楚数组名的意义了吧。

2.使用指针访问数组

  有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针访问数组元素了,如下代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
int*p=arr;
for(i=0;

	return 0;
}

将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]是等价于*(p+i)。
同理arr[i]应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
还可以这么写*(i+arr),以及这么写i[arr],是不是很奇妙啊,了解一下就行了,不推荐这么写。

思考为什么可以使用指针来访问数组呢?
总结:

  • 数组在内存是一块连续的空间,存放的是相同类型的元素。
     
  • 指针变量是一个变量,是存放地址的变量,数组和指针不是一回事,但是可以利用指针来访问数组,指针进行不断地+1,解引用可以很方便地遍历数组,取出数组的内容。

  我们发现在函数内部是没有正确获得数组的元素个数,这又是为什么呢?你也许会想,指针怎么这么…(此处省略一万字),要尝试先接受它,以后学习多了自然都解释地清了。

  • 这就要学习数组传参的本质了,上个小节我们学习了:数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。
  • 那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
  • 那形参为什么可以写成数组的形式呢?这是因为C语言考虑到了学者的感受,在学习数组的时候,如果一来就传地址,形参用指针变量来接收,学者会非常地疑惑的。所以说C语言并不是这么冷若冰霜的。


总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

五、字符指针

1.字符指针隐藏秘密

  在指针的类型中我们知道有⼀种指针类型为字符指针 char* ,存放的是字符的地址,比如:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}

还有一种

#include<stdio.h>
int main()
{
	const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

代码 const char* pstr = “hello bit.”;特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是不妨考虑一下指针存放的是地址,怎么可能会存放字符串呢?

其实本质是把常量字符串 hello bit. 首字符(h)的地址放到了指针变量pstr中

2.常量字符串

常量字符串,字面意思,就是该字符串不能被修改,接下来看一个代码:

#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	char* p1 = arr;
	*p1 = 'b';
	printf("%s\n", arr);

	char* p2= "abcdef";
	*p2 = 'b';
	printf("%s\n", p2);
	return 0;
}

可以发现指针p1指向的空间可以修改,而修改指针p2指向的空间则报错:写入访问权限冲突。这是因为p2是常量字符串,它还有更重要的特点,接下来带我慢慢为你分析一二,请看以下的笔试题。

《剑指offer》笔试题

《剑指offer》:中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";

	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是两个字符数组,储存的字符为"hello world",是两个不同的空间,str1和str2表示的是首地址,由于是两个不同的空间,所以str1和str2不相等。

str3和str4是被const修饰的字符指针,都是指向“hello world”字符串的首地址的,所以str3和str4是相等的

总结:

str1和str2是两个数组,数组的操作方式是将右边常量字符串的内容拷贝进来,所以他们是两个空间,只是内容相同,所以str1 != str2。
而str3和str4是两个指针, 编译器在处理的时候,会将相同的常量字符串做成同一个地址,所以,str3和str指向的是同一个常量字符串,所以str3 == str4。

六、二级指针

问题:我们知道指针是存放元素地址的变量,但是指针的地址我们可以存放吗?

可以的,指针的地址可以用另一个不同的指针变量来存放,我们一般将这样的指针叫做二级指针

	int a = 10;
	int* p = &a;
	int** ppa = &p;
	int* l = 0;
	
	*p = 20;
	printf("%d\n", a);
	l = *ppa;
		printf("%p\n", l);
		printf("%p\n", p);
	**ppa = 30;
	printf("%d\n", a);

	return 0;

运行发现,二级指针往往需要进行两层解引用,我们用一层解引用发现,一级解引用二级指针的结果和指针变量p所在的地址是相同的,所以表明了,二级指针存放的是一级指针的地址

七、指针数组

思考一下:指针数组是指针还是数组?好好思考这个问题,有助于跟后面的学习区分开仔细想想:

  • 整型数组是存放整形的数组,字符数组是存放字符类型的数组。
  • 那么指针数组一定是存放指针的数组。指针数组的每一个元素都是用来存放地址的。指针数组的元素是地址,而每一个地址都可以指向一块区域。

所以,我们可以先来看一道题

用指针数组模拟二维数组

#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* parr[] = { arr1,arr2,arr3 };
	int i = 0, j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j< 10; j++)
		{
			printf("%d", parr[i][j]);
		}
		printf("\n");
	}
}

 

parr是数组名,表示首元素的地址,也就是数组的地址,这就牵扯到了数组指针,数组指针又是什么呢?

...................................

........................

...................

结尾祝福语

风带来故事的种子,时间使之发芽,本章就到这里,博主会尽快更新下一章!!!!感谢大家支持!!!

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

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

相关文章

C++ char*和char[] 可能指向的内存区域详解(附实验)

C char* 指向的内存区域详解 写在前面c内存结构简介指针常量和常量指针简介情况一&#xff1a;char* 指向栈区内容情况二&#xff1a;char* 指向堆区内容情况三&#xff1a;char* 指向常量区内容情况四&#xff1a;char* 指向静态区内容情况五&#xff1a;char* 指向全局区内容…

Scratch游戏-史诗忍者7免费下载

小虎鲸Scratch资源站-免费少儿编程Scratch作品源码,素材,教程分享网站! 作品描述&#xff1a; 在Scratch版本的《史诗忍者7》中&#xff0c;你需要穿越关卡&#xff0c;击败敌人并收集33个水果。通过灵活的操作和精准的攻击&#xff0c;逐步闯过重重难关。游戏中提供了丰富的技…

【GESP】C++一级练习BCQM3005,基本输出语句printf

一道基础练习题&#xff0c;练习基本输出语句printf。 BCQM3005 题目要求 描述 输出表达式1234∗5678的结果。 输入 无 输出 1234∗56787006652 输入样例 无 输出样例 1234 * 5678 7006652 全文详见个人独立博客&#xff1a;https://www.coderli.com/gesp-1-bcqm3005/ 【…

使用 SuperCraft AI 设计书橱模型的指南

在现代家居设计中&#xff0c;书橱不仅是存放书籍的地方&#xff0c;更是展示个人品味和风格的重要家具。借助 SuperCraft AI&#xff0c;你可以轻松设计出独一无二的书橱。以下是详细的步骤指南&#xff0c;帮助你从零开始设计一个理想的书橱。 1. 创建项目 首先&#xff0c…

【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)

目录 一、插入排序 算法思想 二、插入排序 算法步骤 四、复杂度分析 时间复杂度&#xff1a;O(n^2) 空间复杂度&#xff1a;O(1) 稳定性&#xff1a;稳定算法 五、应用场景 &#x1f493; 博客主页&#xff1a;C-SDN花园GGbond ⏩ 文章专栏&#xff1a;探索数据结构…

node卸载流程

步骤&#xff1a; 1.开始中搜素”命令提示符“&#xff0c;并将其以”管理员身份运行“ 在弹出的框中输入cmd&#xff0c;并确认进入”命令提示符“ 2.在里面通过npm config list查看node相关文件路径&#xff0c; 并找到config from与prefix &#xff0c;后面对应的路径&…

element-plus的菜单组件el-menu

菜单是几乎是每个管理系统的软件系统中不可或缺的&#xff0c;element-plus提供的菜单组件可以快速完成大部分的菜单的需求开发&#xff0c; 该组件内置和vue-router的集成&#xff0c;使用起来很方便。 主要组件如下 el-menu 顶级菜单组件 主要属性 mode:决定菜单的展示模式…

visual studio给项目增加eigen库 手把手教程

Eigen是一个开源的C库&#xff0c;主要用来支持线性代数&#xff0c;矩阵和矢量运算&#xff0c;数值分析及其相关的算法。Eigen 除了C标准库以外&#xff0c;不需要任何其他的依赖包。Eigen库3.4.0版本的下载地址为&#xff1a; https://gitlab.com/libeigen/eigen/-/archive/…

qt-creator-10.0.2之后版本的jom.exe编译速度慢下来了

1、Qt的IDE一直在升级&#xff0c;qt-creator的新版本下载地址 https://download.qt.io/official_releases/qtcreator/ 2、本人一直用的是qt-creator-10.0.2版本&#xff0c;官网历史仓库可以下载安装包qt-creator-opensource-windows-x86_64-10.0.2.exe https://download.qt…

清华大佬自曝:接到了省烟草局的offer,我就拒掉了华为!结果华为立马给我申请了特殊涨薪,总包70w是烟草的2倍,这可如何是好?

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

Java设计模式—面向对象设计原则(四) ----->接口隔离原则(ISP) (完整详解,附有代码+案例)

文章目录 3.4 接口隔离原则(ISP)3.4.1 概述3.4.2 案列 3.4 接口隔离原则(ISP) 接口隔离原则&#xff1a;Interface Segregation Principle&#xff0c;简称ISP 3.4.1 概述 客户端测试类不应该被迫依赖于它不使用的方法&#xff1b;一个类对另一个类的依赖应该建立在最小的接…

我国常见电压等级有哪些?来试试电压版2048让你轻松牢记

作为电气工程专业的小姜同学&#xff0c;平时喜欢敲点代码&#xff0c;前一阵做了一个电气特色的2048小游戏。既能缓解学习压力&#xff0c;又能让大家在玩之中把我国的电压等级牢记于心。 项目预览 功能描述 游戏方法与2048相同&#xff0c;根据我国标准的电压等级&#xff…

基于SpringBoot的人事管理系统【附源码】

基于SpringBoot的人事管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统功能结构设计 4.3数据库设计 4.3.1数据库E-R图设计 4.3.2 数据库表结构设计 5 系统实现 5.1管理员功能介绍 5.1.1管理员登…

【Leetcode:1184. 公交站间的距离 + 模拟】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

PDF转JPG,奋斗汪的必备技能,你掌握了吗?

现在大家都用电脑手机处理文件&#xff0c;PDF和JPG是最常见的两种。PDF文件方便打印和分享&#xff0c;而JPG图片小巧清晰&#xff0c;适合在手机上看和发给别人。有时候&#xff0c;我们需要把PDF文件变成JPG图片&#xff0c;比如想把教材或报告变成图片&#xff0c;方便在手…

F28335 时钟及控制系统

1 F28335 系统时钟来源 1.1 振荡器OSC与锁相环PLL 时钟信号对于DSP来说是非常重要的,它为DSP工作提供一个稳定的机器周期从而使系统能够正常运行。时钟系统犹如人的心脏,一旦有问题整个系统就崩溃。DSP 属于数字信号处理器, 它正常工作也必须为其提供时钟信号。那么这个时钟…

树(森林)的定义和画图

目录 代码实现 “双亲表示法”顺序存储 “孩子表示法”链式存储 树的孩子表示法存储 v.s. 图的邻接表存储 v.s. 散列表的拉链法 v.s. 基数排序 “孩子兄弟表示法”链式存储 画图表示 “双亲表示法” 1.树 2.森林 “孩子表示法” 1.树 2.森林 “孩子兄弟表示法” …

SPI学习笔记

SPI SPI是一种同步串行通信接口规范&#xff0c;它允许一个主设备与一个或多个从设备进行全双工通信。SPI用于短距离通信&#xff0c;主要应用于嵌入式系统。 SPI通信过程 1.初始化&#xff1a;SPI主机首先将SS或CS线拉低&#xff0c;以选择特定的从设备并开始通信。 2.数据…

AI健身体能测试之基于paddlehub实现引体向上计数个数统计

【引体向上计数】 本项目使用PaddleHub中的骨骼检测模型human_pose_estimation_resnet50_mpii&#xff0c;进行人体运动分析&#xff0c;实现对引体向上的自动计数。 1. 项目介绍 人体运动分析是近几年许多领域研究的热点问题。在学科的交叉研究上&#xff0c;人体运动分析涉…

【电机仿真】全速域观测器零速旋转方波高频注入+低速+高速滑模观测器三段式启动

【电机仿真】全速域观测器零速脉振方波高频注入低速高速滑模观测器三段式启动 文章目录 前言一、零速——HFI方波注入1.HFI观测器2.静止坐标系——旋转高频电压信号3.PLL1锁相环4.PLL2锁相环 二、零速到中高速——HFI切SMO1.函数定义2.函数内部 三、中高速——SMO观测器1.SMO观…