对进阶指针的追忆

news2024/11/14 16:16:26

目录

思维导图

指针前言

一:字符指针

二:指针数组

三:数组指针

四:数组参数 && 指针参数

五:函数指针

六:函数指针数组

七:函数指针数组的指针

八:回调函数


 思维导图:

指针前言:

学过C语言的各位“铁汁们”,想必都知道,C语言的“灵魂”所在就是指针啦!

指针其实就是用来存放某个东西的“地址”(也可以理解为:指针是打开一个房间门的一把钥匙)!注意这是形象化的理解哈,站在计算机的角度理解就是:一串一串的数字。计算机的内存被划分无数个的小单元,每一个小单元(可以理解为一个房间)都对应一个唯一的地址标识,换言之就是指针(地址),通过指针就可以进入此房间,进行合理的一些操作。

1)关于地址的由来:(对于32为平台而言)地址是由32个 0 或者 1组成的一串二进制序列

2)指针大小是固定的:4个字节(32为平台下)或者是8个字节(64为平台下)

3)指针的类型决定了指针在 加上或者减去一个整数的时候,他解引用时候的权限

4)不管是一级指针还是二级指针亦或是高级指针,他们都是指针(注意哈:咱可不要站在门缝里看指针,把指针看扁了),是指针就是一个变量,都是用来存放地址的。

5)指针的运算:指针-指针的绝对值得到的是元素的个数

1.字符指针

可以借助类比的思想,对于整形指针想必我们应不陌生吧。

整形指针:用来存放整形数据的指针

那么顾名思义,字符指针就是用来存放字符的数据类型的指针呗

那接下来,话不多说,举几个栗子就可以 get 到了 。

1)

2)

显然是不可能滴!

指针p的大小是4个字节(32位平台上)而字符串“hello world”所占大小是11个字节。

此时只是把首字符串h 的地址放在p指针里面了,通过记住此时字符h 的地址,以便后续的操作。

那接下来小试牛刀一把,以加强对此知识点的理解。

对于当前的程序,输出的结果是啥???

分析:

数组名表示数组首元素的地址,数组名 arr1和 数组名 arr2 是2个完全不一样的数组,所以 输出: arr1 and  arr2  are not same

虽然这两个数组的首元素都是字符 h ,但是他们所表示的含义是不一样的。

对于常量字符串 hello ,是存放在代码区域的(只能指向读的操作),其中字符指针str1  ,str2 都是指向这个常量字符串的,站在计算机内存角度,相同的代码没有必要存放2份,所以输出:str1 and str2 are same

运行结果如下:

2.指针数组
2.1 定义

依然借助类比思想,通过对整形指针数组的理解,开头知道

指针数组:是存放指针的数组

数组:存储一组相同类型数据的集合

2.2 使用

3.数组指针
3.1定义

数组指针:首先他是指针,是指向数组的指针

3.2使用

1)

在这里,想必有不少的初学者对于数组指针  和  指针数组 这两个分不清吧!

其实只要把主语定位准确就好

数组指针旨在强调指针,只不过是指向数组的指针罢了

指针数组旨在强调数组,只不过是用来存放指针的数组罢了

对于以下2种写法,各位看看哪一个是数组指针呢

 第46行:这是指针数组;去掉p之后就是数组的类型,这个数组有3个元素。每个元素是int* 类型

第47行:是一个数组指针;去掉p之后,就是指针p的类型,指向一个数组的地址,这个数组有3个元素,每个元素都是int

2)数组指针具体场景的使用:二维数组

想必大家对这个二维数组的访问,应该感到并不陌生吧,甚至说再熟悉不过了。双层for循环,通过控制二维数组的行和列来便利访问当前数组罢了。 

接下来看看借助数组指针如何来遍历

 

二维数组名的理解:

1)他依然表示数组首元素的地址;只不过是表示第一行元素的地址(换言之就是:以数组指针的形式表示的)

2)可以把二维数组看作是由一个个一维数组组成的元素

3.3 数组名 与 &数组名的不同

数组名:代表数组首元素的地址;但是有2 个特殊情况。

第一个:sizeof(数组名):求的是整个数组的地大小(单位是字节)

第二个:&数组名,取出的是整个数组的地址(注意只不过在打印的时候是从当前首元素地址开始打印,但二者含义是不一样是)。

结合下面的栗子,有助于更好的理解!

4.数组传参和指针传参
4.1一维数组传参

思考:实参传递  arr,  对应的形参应该以啥形式来接收???

第85行:形参以数组的形式接收,支持(注意:数组可以不指定大小

第87行:形参以数组的形式接收只不过相比较上面指定了数组的大小,支持

第89行:形参以指针的形式接收,支持;数组名就是表示数组首元素的地址嘛

第91行:形参以指针数组的形式接收,不支持

 注意:

当实参以数组名的形式传参的时候,形参要是写成数组的形式,可以指定数组大小也可以不指定数组的大小;因为此时虽然是以数组的形式传参,但是其实底层是以指针的方式来接收的

4.2二维数组传参

此时arr:是一个二维数组的数组名

第99行:当实参传递数组名,和对应的列数时候,形参也是以同样方式接受的,是支持的

第101行:未指定二维数组的列,不支持

第103行:支持

第105:不支持,形参此时是一个一级整形指针而实参是一个数组指针

第107:不支持,形参是一个二级指针,而实参是一个数组指针

第109行:支持,形参实参类型都是:数组指针

 注意:

对于二维数组而言:在传参的时候 ,行可以省,但是二维数组的列不能省二维数组名表示数组首元素的地址(也就是第一行元素的地址)

4.3一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(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 *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

 分析:实参传递的是一个整形指针;

当形参的参数是指针的时候,对应的实参可以是什么类型???

1)指针

2)一个普通的变量

3)一维数组名

4)常量字符串

4.4二级指针传参

当形参是二级指针的时候,对应的实参可以传那些类型???

1)二级指针

2)二维数组名

3)存放一级指针的指针的地址

5.函数指针

还是老问题,借助类比的思想:

数组指针:是存放数组的指针

函数指针:就是存放函数的指针

5.1 对&函数名与函数名的理解

通过对结果的分析可知:&函数名和 函数名所代表的含义一样都表示当前函数的地址

注意这里是不同于 &数组名 和 数组名的含义

 有了对函数名的理解以及,接下来看一下下面2行代码所表示的含义吧!!!

 分析:

第一行代码:调用0地址处的函数

第二行代码:是signal 函数的声明

5.2 typedef 关键字的使用 

1)typedef 作用:对类型进行重新命名

比如说:unsigned char 这个数据类型名字过长,使用typedef 可以对unsigned char 进行重新命名为 :un_c(也就是 说,给unsigned char 起一个别名,只不过还是同一个数据类型,只不过称呼不一样啦)

2)举例

但是若是对函数指针进行重命名的话,稍许有点不太一样

6.函数指针数组

要想弄清楚函数指针数组,那就必须对函数 以及函数指针掌握透彻。

函数指针是啥???

函数名 和 

 &函数名 又是啥 ???

顾名思义:函数指针数组:是用来存放函数指针的一个数组,数组的元素是一个个函数指针

6.1函数指针数组的使用

 

 写一个计算器的程序:

普通版本:

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, y, ret = 0;
	do
	{
		Menu();
		printf("输入您的选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			switch (input)
			{
			case 1:
				ret = add(x, y);
				printf("ret = %d\n", ret);

				break;
			case 2:
				ret = sub(x, y);
				printf("ret = %d\n", ret);

				break;
			case 3:
				ret = mul(x, y);
				printf("ret = %d\n", ret);

				break;
			case 4:
				ret = div(x, y);
				printf("ret = %d\n", ret);

				break;
			}
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入错误,重新输入\n");
		}
	} while (input);
	return 0;
}

分析:随着用户需求的不断更改,对计算器的更能也需要不断完善,比如实现 2个数据的 按位与,按位或等等。这将会致使case 语句越来越冗余,而且复用率极低,不是一个很好的代码。 

借助函数指针的版本:

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,ret = 0;
	int x, y;
	do
	{
		Menu();
		int (*p[5]) (int, int) = { NULL,add,sub,mul,div };
		printf("输入您的选择:>");
		scanf("%d", &input);

		if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = p[input](x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
			printf("输入错误,重新输入\n");
	} while (input);
	return 0;
}

1)之所以把函数指针数组大小设置为5而不是4:就是为了保持与case 语句的一致。

2)函数名:表示函数的地址 

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

还是老问题:函数指针数组的指针 是 指向函数指针数组的 指针

8.回调函数
8.1回调函数定义

首先回调函数是借助一个函数指针来调用的函数。当把函数的地址作为参数传递给一个指针,来间接调用要想调用的函数,此时这个被调用的函数就是回调函数

8.2回调函数应用

1)还是借助计算器实现的那个程序进行解释:一个计算器的功能不止一个(可以进行加法运算,减法运算,乘法运算,除法运算,按位与……)。那有没有一个函数可以实现多种功能:不仅可以进行加法运算,还可以进行减法运算,乘法运算…… 这就是回调函数该发挥作用啦!

初始版本的计算器代码:

其实不难发现:对于圈出部分,过于冗余了,可复用性并不高

 借助回调函数实现的计算器代码:

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 Calc(int (*p)(int, int ))
{
	int x, y;
	printf("请输入2个操作数:>");
	scanf("%d %d", &x, &y);

	return p(x, y);
}
int main()
{
	int input;
	int ret = 0;
	
	do
	{
		Menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			
			ret =Calc(Add);//此时被间接调用的Add函数就成为回调函数
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);

			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);

			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误,重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

使用回调函数模拟实现qsort()(注意是基于冒泡思想实现的qsort函数)

1)冒泡核心思想:两两相邻元素进行交换(满足一定条件的)

2)qsort( )函数的初步使用

使用此函数对int 类型数据进行排序(注意qsort函数默认是以升序来排列的)

对于compare()这个函数,需要注意以下几点:

第一:此函数返回类型是int  类型

第二:此函数的参数类型必须是const void *

第三:对于void*类型的指针是不能直接进行解引用的,需要先进行类型强转,再进行解引用 

qsort()可以用来排序任意类型的数据

对int 类型数据排序

int cmp(void* p1, void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

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

对结构体类型数据排序:

typedef struct student
{
	int age;
	char name[20];
	char sex;
}stu;int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
	return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
int main()
{
	stu arr[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(stu), Cmp_name);
}
8.3 基于冒泡函数思想模拟qsort() 实现

完整代码:

int cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
typedef struct student
{
	int age;
	char name[20];
	char sex;
}stu;
int Cmp_age(const void* p1,const void* p2)
{
	return (((stu*)p1)->age -( (stu*)p2)->age);// 注意 (stu*)p2->age错误的
}
int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
	return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
void Swap(char* p1, char* p2, int size)
{
	//注意是一个字节一个字节交换
	for (int i = 0; i < size; ++i)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		++p1;
		++p2;
	}
}
void Qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
	for (int i = 0; i < num - 1; ++i)
	{
		for (int j = 0; j < num - i - 1; ++j)
		{
			//相邻2元素进行比较
			//注意这里必须强转成char*类型指针
			if (cmp(((char*)base + j * size) ,((char*)base + (j + 1) * size) )  > 0) //注意这里条件必须注明是>0 还是<0
			{
				//交换
				Swap(((char*)base + j * size), ((char*)base + (j + 1) * size), size);
			}
		}
	}
}
int main()
{
	int arr[10] = { 7,4,1,2,5,8,9,6,3,10 };
	Qsort(arr, 10, sizeof(arr[0]), cmp);
	stu arr1[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'} };
	//Qsort(arr1, 3, sizeof(stu), Cmp_name);
	Qsort(arr1, 3, sizeof(stu), Cmp_age);//注意最后一个函数指针对应的函数需要使用者自己实现
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 结语:

首先非常感谢各位老铁可以看到最后,想必收获满满吧!(有疑惑也是正常的,毕竟知识量太大了,俺也是整了不止一时半会才写完这篇博客的)对于指针这部分的学习,是真的及其重要!

对于后期数据结构以及C嘎嘎的学习,起着至关重要的作用,为了检验大家对指针这一知识点的学习,大家可以看看关于指针和数组相关面试的题型,链接在此,各位自取哈~~~

 https://blog.csdn.net/X_do_myself/article/details/134366577

今日的share到此就结束了,希望各位友友们可以支持一下~~~

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

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

相关文章

MySql 数据库 (基础) - 下载安装

MySQL数据库 简单介绍 数据库 数据存储的仓库数据库管理系统 操作和管理数据库的大型软件SQL 操作关系型数据库的变成语言&#xff0c;是一套标准 版本 MySQL官方提供了两种不同的版本&#xff1a; 社区版 免费&#xff0c;MySQL不提供任何的技术支持商业版 收费&#xff0c…

暑期备考美国数学竞赛AMC8和AMC10:吃透1850道真题和知识点

距离接下来的AMC8、AMC10美国数学竞赛还有几个月的时间&#xff0c;实践证明&#xff0c;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c;…

谷粒商城实战笔记-29~34-前端基础 - ES6

文章目录 零&#xff0c;安装Live Server插件一&#xff0c;创建前端工程1&#xff0c;创建工程2&#xff0c;在工程ES6中创建一个html文件 二&#xff0c;ES6 简介1&#xff0c;ES6 的历史 三&#xff0c;前端基础ES61&#xff0c;let 和 const1.1&#xff0c;let1.1.1 严格的…

JavaScript青少年简明教程:开发工具与运行环境

JavaScript青少年简明教程&#xff1a;开发工具与运行环境 JavaScript是一种基于对象和事件驱动且具有安全性能的脚本语言。使用它和HTML结合可以开发出交互式的Web页面。 脚本语言是为了缩短传统的编写-编译-链接-运行过程而创建的计算机编程语言。脚本通常是解释执行而非编…

text prompt如何超过77个词

【深度学习】sdwebui的token_counter,update_token_counter,如何超出77个token的限制?对提示词加权的底层实现_prompt中token权重-CSDN博客文章浏览阅读1.6k次,点赞26次,收藏36次。文章探讨了如何在StableDiffusionProcessing中处理超过77个token的提示,涉及token_counte…

Is Temperature the Creativity Parameter of Large Language Models?阅读笔记

最近有小伙伴来问LLM的参数该如何设计&#xff0c;废话不多说来看看paper吧。首先&#xff0c;常见的可以进行调参的几个值有temperature&#xff0c;top-p和top-k。今天这篇文章是关于temperature的。 原文链接&#xff1a;https://arxiv.org/abs/2405.00492 temperature如果…

基于双向长短期记忆 BiLSTM 实现股票单变量时间序列预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

SpringBoot新手快速入门系列教程十一:基于Docker Compose部署一个最简单分布式服务项目

我的教程都是亲自测试可行才发布的&#xff0c;如果有任何问题欢迎留言或者来群里我每天都会解答。 如果您还对于Docker或者Docker Compose不甚了解&#xff0c;可以劳烦移步到我之前的教程&#xff1a; SpringBoot新手快速入门系列教程九&#xff1a;基于docker容器&#xff…

python | setup.py里有什么?

setup.py里有什么&#xff1f; 文章目录 setup.py里有什么&#xff1f;C/C扩展总结gcc/g的编译参数&#xff1a;Windows Visual StudioCmakesetup.py C/C扩展模块 为什么需要分发打包&#xff1f;Distutils一个简单的例子通用的 Python 术语 使用 Setuptools 构建和分发软件包源…

高铁站客运枢纽IPTV电视系统-盐城高铁站西广场IP电视系统应用浅析

高铁站客运枢纽IPTV电视系统-盐城高铁站西广场IP电视系统应用浅析 由北京海特伟业科技有限公司任洪卓于2024年7月9日发布 随着科技的飞速发展&#xff0c;特别是“互联网”战略的深入推进&#xff0c;高铁站客运枢纽的信息化建设成为提升服务质量、增强乘客体验的重要手段。盐…

开发业务(2)——wordpress使用基础教程

外贸领域里面wordpress是比较通用的框架。由于多年的发展&#xff0c;性能和插件非常强大&#xff0c;包括支持各种企业站&#xff08;很多人已经设计了各种风格&#xff0c;只需要你将对应主题风格安装即可&#xff0c;当然也有付费的&#xff09;。这导致其内部生态非常强大&…

【C++题解】1231 - 考试成绩的分布情况

问题&#xff1a;1231 - 考试成绩的分布情况 类型&#xff1a;数组基础 题目描述&#xff1a; 期末考试结束&#xff0c;小明的语文老师想知道&#xff0c;这次考试的成绩分布情况&#xff0c;主要计算如下几个数据&#xff1a;平均分、≥ 平均分的总人数、 < 平均分的总人…

linux进程周边知识——内核对硬件的管理——计算机世界的管理

前言&#xff1a;本节主要讲解内核也就是操作系统对于硬件的管理&#xff0c; 本节内容同样为进程的周边知识。 主要是关于软件方面&#xff0c; 和我的上一篇——冯诺依曼体系结构可以说是兄弟文章&#xff0c; 这篇文章主要是关于硬件方面。 两篇文章都是为学习进程做准备。但…

论文翻译 | LEAST-TO-MOST: 从最少到最多的提示使大型语言模型中的复杂推理成为可能

摘要 思维链提示&#xff08;Chain-of-thought prompting&#xff09;在多种自然语言推理任务上展现了卓越的性能。然而&#xff0c;在需要解决的问题比提示中展示的示例更难的任务上&#xff0c;它的表现往往不佳。为了克服从简单到困难的泛化挑战&#xff0c;我们提出了一种新…

Spring系统学习 - Spring事务的概念

提到事务&#xff0c;这个我们应该比较熟悉了&#xff0c;在数据库学习的过程中&#xff0c;我们或多或少接触过了事务&#xff0c;当然你可能没有用到&#xff0c;也可能用到了&#xff0c;这篇博客我们将围绕Spring的相关事务的概念进行&#xff0c;了解Spring中的事务和事务…

【开源 Mac 工具推荐之 1】gibMacOS:方便快捷的 macOS 完整包下载 Shell 工具

简介 gibMacOS 是由 GitHub 开发者 corpnewt 编写的一款 Shell 工具。它采用 Python 编程语言&#xff0c;可以让用户打开后在纯文本页面中轻松选择并下载来源于 Apple 官方的 macOS 完整安装包。 Repo 地址&#xff1a;https://github.com/corpnewt/gibMacOS &#xff08;其…

《机器学习中的数学》:探索算法背后的数学奥秘

在当今这个数据驱动的时代&#xff0c;机器学习已经成为科技发展的关键力量。然而&#xff0c;很多人可能不知道&#xff0c;机器学习的高效运作离不开背后的数学基础。今天&#xff0c;我要向大家推荐一本关于机器学习数学基础的书籍——《机器学习中的数学》。这本书不仅详细…

【Python】下载与安装

目录 一、 下载安装Python 1. 配置环境变量 2. 检查是否配置成功 一、 下载安装Python 在我上传的资源可以免费下载&#xff01;&#xff01;&#xff01; https://download.csdn.net/download/m0_67830223/89536665?spm1001.2014.3001.5501https://download.csdn.net/dow…

cpp随笔——如何实现一个简单的进程守护模块

前言 在前面我们已经实现过调度模块和进程的心跳模块了&#xff0c;而今天我们要讲的是作为服务端后台服务程序运行三个通用模块的最后一项:进程的守护模块&#xff0c;在开始讲解进程的守护模块之前我们首先来看究竟什么是进程的守护模块&#xff1a; 守护模块&#xff08;有…

torch之从.datasets.CIFAR10解压出训练与测试图片 (附带网盘链接)

前言 从官网上下载的是长这个样子的 想看图片&#xff0c;咋办咧&#xff0c;看下面代码 import torch import torchvision import numpy as np import os import cv2 batch_size 50transform_predict torchvision.transforms.Compose([torchvision.transforms.ToTensor(),…