深入了解C语言指针(2)

深入了解指针(2)

1.数组名的理解

先前我们说过数组名其实就是数组的首元素地址

我们现在就来验证一下 这个说法到底有多少可信度

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	printf("%p\n", arr);
	printf("%p\n", p);
	printf("%p\n", &arr[0]);
	// 运行后 我们发现地址都是一样的
	return 0;
}
从这个结果看 数组名其实就是数字的首元素地址的说法是正确的

那我们再来看一种情况

	printf("%d\n",sizeof(arr)); // 40

如果数组名其实就是数字的首元素地址的话 这里应该是4或者8 但是是40说明了这个说法还是有一点问题的

还要第二种情况

&数组名 这里的地址是整个数组的地址

	printf("%p\n", arr);
	printf("%p\n", &arr);
	printf("%p\n", &arr[0]);

这三个打印的其实都是数组首元素的地址

	// 在这种情况下 区别就出来了
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("arr + 1 = %p\n", &arr + 1);
	printf("&arr[0] + 1 = %p\n", &arr[0] + 1);
	printf("arr + 1 = %p\n", arr + 1);

&arr + 1 这个实际上会跳过40个字节

来一张图片更加清晰直观

image-20240322000207970

总结:(数组名的两个例外)

只有两种情况下的数组名不代表数组的首元素地址

  1. sizeof(数组名) 这里放的数组名代表的是整个数组,计算的是整个数字的大小,单位是字节
  2. &数组名 这里的数组名也是代表整个数组,取出的是 整个数组的地址

注意!

我们之前说过 每个指针都有类型 代表着它们的步长 也就是一次+1跳过多少个字节

&数组名 + 1 一次跳过了40个字节 那这是什么类型呢

这其实是一个特殊类型———— 数组指针类型

2.使用指针访问数组

在之前的学习中,我们已经知道了指针访问数组的方法

现在再来重温加深一下

int main()
{
	// 通过指针来对数组进行输入和输出
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[1]);
	int* p = arr;  // 指针类型要和数组类型是一个类型 
	 输入
	//for (int i = 0; i < sz; i++)
	//{
	//	scanf("%d", p + i); // p + i 等价于 &arr[i]
	//}
	 输出
	//for (int i = 0; i < sz; i++)
	//{
	//	printf("%d ", *p);
	//	p++;  // 要注意这里的p执行完之后已经越界访问了
	//}
	// 还有一种写法
	for (int i = 0; i < sz; i++)
	{
		/*printf("%d ", *(p+i));*/ // 这里的p执行完后还是指向首元素地址
		// 我们可以思考一下  arr就是首元素地址  也就是说p代表的地址和arr是一样的
		// 那么*(p+i)和 *(arr+i) 其实就是等价的  
		// 而*(arr+i) 和  arr[i] 又是等价的
		// 那么有没有一种写法是 p[i]呢
		printf("%d ", p[i]);  // 我们发现是可以的
	}

	return 0;
}

**实际上在编译器当中 (p+i)和 (arr+i) 和 arr[i] 和 p[i]的写法是完全等价的

*编译器会将arr[i] 转化成这种方式 (arr+i) 去计算

其实还有更加奇葩的写法

*(arr + i) == *(i + arr) == i[arr] // 这种写法并不建议

其实就是说 [] ----- 操作符

x[y]的x和y对于这个[]操作符来说其实就是两个操作数 顺序调换一下并不会有什么影响

3.一维数组传参的本质

void test(int arr[]) // 本质上接受的是指针
{
	int sz2 = sizeof(arr) / sizeof(arr[0]); // 2
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]); // 10
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

由于 传过去的是arr 本质上是首元素的地址 在64位的环境下地址的大小是8个字节 因此8/4 = 2

而sz1 的arr代表的就是整个数组的大小 就是40 / 4 = 10

4.冒泡排序

排序算法其实很多

  1. 冒泡排序
  2. 选择排序
  3. 出入排序
  4. 希尔排序
  5. 快速排序

这里我们学习一个冒泡排序

// 冒泡排序
void bubble_sort(int arr[],int sz)
{
	int flag = 0;
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1; j++)
		{
			//if (arr[j] > arr[j + 1])
			//{
			//	int tmp = arr[j];
			//	arr[j] = arr[j + 1];
			//	arr[j + 1] = tmp;
			//	flag = 1;
			//}
			// 也可以改成指针的形式
			if (*(arr + j) > *(arr + j + 1))
			{
				int tmp = *(arr+j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}
void Print(int* p, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
		
	}
}
int main()
{
	int arr[] = { 1,3,2,4,6,7,5,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	Print(arr, sz);

	return 0;
}

5.二级指针

我们都知道指针可以指向一个地址 并把这个地址存起来 但是指针变量存下一个地址的同时也需要开辟自己的内存空间 ,那么这个指针变量也有自己的地址 那么当另一个指针指向指针变量的地址时这个指针就叫二级指针

image-20240322124122110

int a = 10;
int* p = &a;
int* * pp  = &p;

pp就是2级指针 放着p这个指针变量的地址

int * p 的第一个 * 代表p是指针变量 int 代表指针指向的是一个int类型的变量 代表着p这个指针变量的步长

int** pp 最右边的 * 代表了pp是一个指针变量 int*代表了 指向的对象是一个int *类型的

来看一个实际应用

int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	**pp = 100;
	printf("%d\n", a); // 100
	return 0;
}

*pp代表解引用pp这个指针变量 找到的是p 在解引用一次 就找到了a

6.指针数组

我们知道数组有各种类型

  1. 有整形数组 int arr[10] 存放整形
  2. 有字符数组 char arr 存放字符

那么指针数组就是专门用来存放指针的数组

int* arr[5]

arr 是数组名称 [5]是数组元素个数 int * 表示数组内部存放的是指向整形的指针的类型

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

我们直接来看一个案例

//指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	// 通过指针数组来储存一维数组 模拟实现二维数组
	int* arr[3] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

因为每个数组名都是各自首元素的地址 因此我们就可以实现上面这个代码

,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
// 通过指针数组来储存一维数组 模拟实现二维数组
int* arr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf(“%d”, arr[i][j]);
}
printf(“\n”);
}
return 0;
}


因为每个数组名都是各自首元素的地址  因此我们就可以实现上面这个代码

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C 语言中的指针可以说是一个比较难理解的概念,因为它涉及到了计算机内存的操作。如果你不熟悉内存的工作原理,指针的概念可能会比较抽象和难以理解。 此外,指针的概念需要对数据类型、地址、内存分配等方面有一定的了解,而这些知识对于初学者来说可能是比较困难的。 同时,指针的错误使用也很容易导致程序崩溃,因此需要十分小心。 总的来说,C 语言中的指针是一个需要深入理解和练习的概念,但是它是 C 语言编程中很重要的一部分,并且对于提高编程能力有很大的帮助。 ### 回答2: C语言指针之所以被认为难学,是因为它相对于其他编程语言来说更加底层且概念较为抽象。以下是几个可能的原因: 1. 指针与内存管理:C语言中,指针与内存密切相关。指针可以直接访问内存地址,但也需要手动进行内存的分配和释放,否则可能会导致内存泄漏或者悬空指针等问题。 2. 指针算术:C语言允许对指针进行算术操作,如指针加减、指针递增、递减等。这要求学习者掌握指针和内存地址之间的关系,并且需谨慎处理指针算术,以确保不发生内存越界等错误。 3. 多级指针C语言支持多级指针,允许指针指向指针。这增加了学习的难度,尤其是对于初学者来说,理解多级指针的概念和应用可能会比较困难。 4. 字符串处理:在C语言中,字符串是以字符数组的形式存储和处理的。使用指针可以更有效地操作字符串,然而对于初学者来说,指针与字符串的关系可能需要花费更多的时间去理解和掌握。 虽然C语言指针难度较高,但掌握好指针的概念和使用方法对于理解底层编程、内存管理以及某些高级语言的底层原理都具有重要意义。通过坚持学习、阅读指针相关的资料和教程,并进行实践,可以逐渐掌握好C语言指针。 ### 回答3: C语言中的指针是许多初学者感到困惑的一个概念,因为它与其他高级语言的概念不同且较为抽象。以下是指针难以学习的几个原因: 第一,指针是一种直接访问内存地址的方式。这意味着我们需要理解计算机内存的工作原理,并学会如何在程序中正确地使用这些内存地址。这对于没有计算机底层知识的人来说可能会很复杂。 第二,指针有许多复杂的语法规则。例如,我们需要使用星号(*)来声明指针变量,并使用地址运算符(&)来获取变量的地址。此外,我们还需要理解指针的类型匹配规则以及指针算术运算的规则。这些规则对于初学者来说可能会很令人困惑。 第三,指针容易出现错误。由于指针直接访问内存地址,因此在使用指针时出现错误可能导致程序崩溃或产生未定义的行为。例如,当指针没有正确初始化或者指针指向的内存已被释放时,我们可能会遇到段错误或野指针等问题。 第四,指针的概念相对抽象。指针是一种间接引用的方式,通过指针我们可以访问其他变量的值或者修改其他变量的值。这种间接性可能会增加学习的难度,尤其是对于没有编程经验的人来说。 总之,C语言指针之所以难以学习,是因为它需要理解底层的内存访问机制、掌握复杂的语法规则、注意错误的出现以及理解抽象的概念。然而,一旦掌握了指针的概念和用法,它可以成为编写高效、灵活的程序的重要工具。因此,努力学习和理解指针是编程学习的关键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值