C语言底层学习(2.指针与数组的关系与应用)(超详细)

  • 🌈🌈🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
  • 🔥🔥🔥专栏:《C语言入门知识点》、《C语言底层》
  • 💪💪💪格言:今天多敲一行代码,明天少吃一份苦头

前言:

随着《C语言入门知识点》指针abc篇的结束,相信大家已经对指针有一个大概的了解了,在指针c篇我们谈到了数组其实有两种表示法(指针表示和数组表示),那么大家会不会好奇,指针和数组到底是什么关系呢?
这篇文章就带着大家来了解指针和数组的关系与应用,快来围观~


插入问题:

本篇全部代码运行结果都是以代码块的内容输出的,大家觉得是以代码块内容输出看着舒服还是原来图片的形式看着舒服,欢迎评论区留言

文章目录

    • 前言:
    • 插入问题:
    • 正文:
      • [1. 数组名的理解](#1. 数组名的理解)
      • [2. 用指针访问数组(数组的指针表示法)](#2. 用指针访问数组(数组的指针表示法))
      • [3. 一维数组传参的本质](#3. 一维数组传参的本质)
      • [4. 冒泡排序法](#4. 冒泡排序法)
      • [5. 二级指针](#5. 二级指针)
      • [6. 指针数组](#6. 指针数组)
      • [7. 指针数组模拟二维数组](#7. 指针数组模拟二维数组)

正文:

1. 数组名的理解

还记得我们在指针c篇和大家说过(没看过的可以直接打开《指针c篇》),数组名其实就是数组第一个元素的地址,但是大家可能见过这样的代码:

c 复制代码
#include <stdio.h>

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int* p = &arr[0];
	printf("%zu\n",sizeof(p));
	printf("%zu\n", sizeof(arr));
	return 0;
}

如果按照arr这个数组名是第一个元素的地址的话,那两个输出应该是一摸一样的 8 (x64环境下)或者 4(x86环境下),实际结果是:

c 复制代码
//64位环境下
8
40

这是为什么呢?

其实数组名等价于第一个元素地址有两个特殊情况

    1. sizeof(【数组名】):就是上面这种情况,当sizeof()里面加数组名时,会返回整个数组的字节数
    1. &数组名:当&加数组名时,返回的是整个数组的地址

那第一种情况我们刚才已经见到了,再来看一下第二种情况呗

c 复制代码
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("&arr     = %p\n", &arr);
	printf("&arr[0]  = %p\n", &arr[0]);
	printf("arr      = %p\n", arr);
	return 0;
}

运行结果:

c 复制代码
&arr     = 000000D4948FF5F8
&arr[0]  = 000000D4948FF5F8
arr      = 000000D4948FF5F8

为什么会一摸一样呢?数组的地址其实也是第一个元素的地址,那为什么要区分呢?

c 复制代码
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("&arr       = %p\n", &arr);
	printf("&arr[0]    = %p\n", &arr[0]);
	printf("arr        = %p\n", arr);
	printf("&arr+1     = %p\n", &arr+1);
	printf("&arr[0]+1  = %p\n", &arr[0]+1);
	printf("arr+1      = %p\n", arr+1);
	return 0;
}

运行结果:

c 复制代码
&arr       = 000000313936F808
&arr[0]    = 000000313936F808
arr        = 000000313936F808
&arr+1     = 000000313936F830
&arr[0]+1  = 000000313936F80C
arr+1      = 000000313936F80C

逐个分析一下:

  • &arr+1:用十六进制刚好是加了40个字节,证明它是整个数组的地址
  • arr+1和&arr[0]+1:都是加了4个字节,证明是它们是第一个元素的地址
  • 也就是说除了两个例外,其余情况下,数组名和第一个元素地址是完全等价的

2. 用指针访问数组(数组的指针表示法)

完全了解数组名以后,我们就可以理所当然地用指针表示法了

比如说我们建立一个数组

clike 复制代码
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	//可以用指针访问数组了
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0;i < sz;i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

运行结果:

clike 复制代码
1 2 3 4 5
1 2 3 4 5

可以看到,printf("%d ", arr[i]);printf("%d ", *(p + i));完全是等价的。

也就是说:arr[i]*(arr+i)完全一致

3. 一维数组传参的本质

传参,即将参数传递给函数
那么什么情况下传参能够正确地解决问题呢?

比如说,现在我们来写一个函数,在函数内部计算出数组元素的个数

clike 复制代码
void Count(int arr[]);

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//十个元素
	Count(arr);
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	return 0;
}

void Count(int arr[])
{
	int num = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", num);
}

运行结果:

clike 复制代码
//64位环境下
2
10
//32位环境下
1
10

在函数外计算出来的值是10,而在函数内计算出来的值就是1或者2.

首先说明一点:在 C 语言中,对指针使用 sizeof 运算符,得到的是指针变量本身所占用的内存字节数

也就是说,32位环境下输出的是4,64位环境下输出的是8

下面我们来探讨一下为什么函数内部出现的是1或2,函数内部的int num = sizeof(arr) / sizeof(arr[0]);sizeof(arr)得到的8或者是4,也就是说这里的arr其实是一个指针

  • 那我们也就能得到结论了,其实数组传参传的是地址

什么地址?

clike 复制代码
void Count(int arr[]);

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//十个元素
	Count(arr);
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	return 0;
}

void Count(int arr[])
{
	int num = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", arr);
	printf("%d\n", arr + 1);
	printf("%d\n", num);
}

加两句printf以后运行:

clike 复制代码
13630548
13630552
1
10

能看的出来,数组传参传的是首个元素的地址

  • 结论:一维数组传参传的是首个元素的地址.

4. 冒泡排序法

所谓冒泡,就是一个和一个比,比如说第一个数,我们把它当作一个泡泡,然后这个泡泡根据自身的大小和其他数比较。假如说是升序排序,就是大的往后面换。两两比较,两两交换。

clike 复制代码
void bubble_sort(int arr[], int sz);

int main()
{
	int arr[] = { 0,4,5,8,6,7,5,8,4,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);//升序排序
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

void bubble_sort(int arr[], int sz)
{
	int i, j = 0;
	for (i = 0;i < sz-1;i++)//循环9次,除最后一位,每一位循环一次
	{
		for (j = 0;j < sz-i-1;j++)//开始7次,每次减1
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = 0;
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
}

运行结果:

clike 复制代码
0 4 4 4 5 5 6 7 8 8

以上是冒泡排序法的代码。我们发现他大概要循环252次,可是假如说现在一个数组:
int arr[10] = {0,1,2,3,4,5,6,7,8,9}

整个数组有必要循环这么多次吗?

所以我们可以改进一下这个代码:

clike 复制代码
void bubble_sort(int arr[], int sz);

int main()
{
	int arr[] = { 0,4,5,8,6,7,5,8,4,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);//升序排序
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

void bubble_sort(int arr[], int sz)
{
	int i, j = 0;
	for (i = 0;i < sz-1;i++)//循环9次,除最后一位,每一位循环一次
	{
		int flag = 1;//放一个标志,假定已经有序了
		for (j = 0;j < sz-i-1;j++)//开始7次,每次减1
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 0;//如果这么多次里面有交换,就反驳假定
				int temp = 0;
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
		if (flag)//如果假定没有被反驳,证明确实有序,退出循环
		{
			break;
		}
	}
}

这样子的话,假如第一次循环中发现根本没有交换,那就证明不需要排序,直接退出。只循环63次

5. 二级指针

我们知道指针是一个变量,既然是变量的话,就会存在该变量的地址,那么指针的地址称为什么呢?就是二级指针.

二级指针字母表示?和一级指针一样吗?

clike 复制代码
int main()
{
	int a = 0;
	int* pa = &a;
	int** ppa = &pa;
	printf("%d\n", a);
	printf("%p\n", pa);
	printf("%p\n", ppa);
}

运行结果:

clike 复制代码
0
00F7F794
00F7F788

二级指针有什么特殊的运算吗?

*ppa

clike 复制代码
int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa

clike 复制代码
**ppa = 30;
//等价于*pa = 30; 
//等价于a = 30; 

6. 指针数组

同理,指针是一个变量,既然是变量的话就能存放在数组里变成指针数组

例如说:

clike 复制代码
int main()
{
	int arr[5] = { 0,1,2,3,4 };
	int* arr_p[5] = { &arr[0],&arr[1],&arr[2],&arr[3],&arr[4] };
}

就定义了一个指针数组

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

理论上可以用指针数组模拟出二维数组:

clike 复制代码
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]);
		}
		printf("\n");
	}
	return 0;
}

运行结果:

clike 复制代码
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。

上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。

  • 本节完...
相关推荐
东木君_2 小时前
RK3588:MIPI底层驱动学习——入门第三篇(IIC与V4L2如何共存?)
学习
eqwaak02 小时前
Python Pillow库详解:图像处理的瑞士军刀
开发语言·图像处理·python·语言模型·pillow
柯南二号2 小时前
【Java后端】《Spring Boot Starter 原理详解》博客
java·开发语言·spring boot
歪歪1003 小时前
如何在SQLite中实现事务处理?
java·开发语言·jvm·数据库·sql·sqlite
祐言QAQ3 小时前
(超详细,于25年更新版) VMware 虚拟机安装以及Linux系统—CentOS 7 部署教程
linux·运维·服务器·c语言·物联网·计算机网络·centos
珍宝商店3 小时前
优雅的 async/await 错误处理模式指南
开发语言·前端·javascript
Ziyoung3 小时前
【探究】C语言-类型转换问题
c语言
数据知道3 小时前
Go基础:Go语言能用到的常用时间处理
开发语言·后端·golang·go语言
风已经起了3 小时前
FPGA学习笔记——图像处理之对比度调节(直方图均衡化)
图像处理·笔记·学习·fpga开发·fpga