0
点赞
收藏
分享

微信扫一扫

2023-ICLR-Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning

霸姨 2023-11-01 阅读 39


文章目录


通过前面的介绍
C语言指针详解(一)超详细~
相信大家对指针的基本概念及用法有了初步的了解。

我们来回顾一下上次那个博客讲了什么吧~
1.指针就是变量,用于存放地址的,地址唯一标识的一块内存空间。
2.指针的大小分别是4/8个字节(32位平台/64位平台)
3.指针是有类型的,指针的类型决定了指针±整数的步长,以及指针解引用的权限有多大。
4.指针的运算。
那么这次博主给大家继续深入理解指针的其他高级用法吧
这是本次我们要讲解的知识点:


1.野指针

1.1 什么是野指针

1.2 造成野指针的原因有哪些呢

1.2.1造成野指针具体代码实例:

1.指针未被初始化

#include <stdio.h>
int main()
{
	int *p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	return 0;
}

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就是野指针
		*(p++) = i;
	}
	return 0;
}

3.指针指向的空间释放

int* test()//由于返回的是n的地址,因此函数返回的是int*类型
{
	int n = 100;//在test函数中创建了局部变量n,
	return &n;//当我们在中间的函数做了一些事情后,我们就返回n,把n的地址返回到指针变量p来接收
	
}
int main()
{
	int* p = test();//由于返回的是地址,所以我们拿指针变量p来接收
	printf("%d\n", *p);
	return 0;
}

1.3 如何避免野指针呢?

1.3.1如何对指针进行初始化?

初始化如下:

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

1.3.2如何才能小心指针越界?

1.3.3 指针变量不再使用时,如何及时置NULL,在指针使用之前检查有效性?


2.assert断言

2.1 什么是assert断言

2.2 如何使用assert断言呢?

在这里插入图片描述

assert(p != NULL);

2.3 使用assert有什么好处呢?

需要注意的是,assert()也是有缺点的。由于引入了额外的检查,会增加程序的运行时间。


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

3.1 学习指针的目的是什么?

经过一番思考后,我们可能会写出这个代码出来~

#include <stdio.h>
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

当我们运行此代码,结果如下:

在这里插入图片描述

因此我们得出以下结论:

那我们怎么解决呢?
在这里插入图片描述
我们得借助函数间传址调用来解决。

3.2 什么是传址调用?

3.3 怎么进行传址调用?

代码实现如下:

#include <stdio.h>
void Swap2(int*px, int*py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

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

4.数组名的理解

在上一次博客C语言指针详解(一)超详细~
我们曾写过两行代码:

int arr[10]={1,2,3,4,5,6,7,8,9,10};
int *p =&arr[0];

这里我们是使用&arr[0]的方式拿到了数组第一个元素的地址。但是数组名本来就是地址,不信我们可以拿VS编译器来测试一下。
在这里插入图片描述

因此我们可以得出这个结论:数组名是数组首元素(第一个元素)的地址
但是呢,有同学会有疑问,如果数组名是数组首元素的地址,那这个代码该怎么理解?

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

从下图,我们发现输出结果是40。
在这里插入图片描述
为什么不是4/8呢?如果数组是首元素的地址,按理说输出的应该是4/8才对。

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


#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);
	printf("&arr = %p\n", &arr);
	return 0;
}

发现这三个打印的结果都一样,会再次出现疑惑?
在这里插入图片描述
那接下来我来介绍他们之间的区别。

4.1 arr和&arr的区别

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

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

相信到这里大家应该搞清楚数组名的意义了吧。
除了有两个例外,其他的数组名都是数组首元素的地址。


5.二级指针

5.1 什么是二级指针

5.2 指针变量的地址存放在哪里呢?

在这里插入图片描述

另外,这里有个小细节需要大家注意的是,由于pa中的p左边的*代表pa是个指针变量,而前面的int代表pa是个int类型的指针变量。那同理,ppa中的p左边的 *代表ppa是个指针变量,而旁边还有一个 *。代表的是ppa是一个int *类型的指针变量。

5.3 对于二级指针的运算是怎么样的呢?

#include <stdio.h>
int main() {

	int a = 10;
	int* p = &a;//p是一级指针

	int** pp = &p;//pp是二级指针
	printf("%d\n", **pp);


	return 0;
}

VS运行结果如下所示:
在这里插入图片描述



6.指针数组

6.1 什么是指针数组呢?

并且指针数组的每个元素都是用来存放地址(指针)的。
如下图所示:
在这里插入图片描述



7.指针数组模拟二维数组

#include <stdio.h>
int main()
{
	int arr1[] = {1,2,3,4,5};
	int arr2[] = {2,3,4,5,6};
	int arr3[] = {3,4,5,6,7};
	//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = {arr1, arr2, arr3};
	int i = 0;
	int j = 0;
	for(i=0; i<3; i++)
	{
		for(j=0; j<5; j++)
		{
			printf("%d ", parr[i][j]);
			//parr[i][j]==*(*(parr+i)+j))
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述


** 好啦!今天博主就分享到这里**
在这里插入图片描述
** 如果觉得博主讲得不错的话。欢迎大家一键三连支持一下**

举报

相关推荐

0 条评论