当前位置: 首页 > news >正文

C语言:指针(2)

1. 数组名的理解

在前面我们知道,当数组名单独使用的时候代表的数组中首元素的地址,也就是说,下面的两句代码是等效的:

int arr[10] = { 0 };
int *p1 = arr;
int *p2 = &arr[0];

我们可以尝试将地址打印出来检测是否正确:

int main()
{int arr[10] = { 0 };int *p1 = arr;int *p2 = &arr[0];printf("p1 = %p\np2 = %p\n",p1,p2);return 0;
}
p1 = 000000381DDFFC5C 
p2 = 000000381DDFFC5C

我们发现,两组打印结果相同,证明上面的两句代码的效果是一样的。

但是,我们又会想到,之前在计算数组的大小以及数组中元素个数时,我们会将代码写成如下形式:

int main()
{int arr[10] = { 0 };int a = sizeof(arr);int b = sizeof(arr)/ sizeof(arr[0]);printf("a = %d b = %d\n",a,b);
}

我们发现,a 输出值是40,可是如果 arr 代表的是首元素的地址的话,这里的 a 值结果应该是4或8才对,那这又是什么原因呢?

其实,这是因为,大部分情况下,数组名都是代表了数组中首元素的地址,但是有两种情况例外:

1. sizeof(数组名):当在sizeof()单独放入数组名的话,这个时候,数组名表示整个数组,计算出的是整个数组的大小,单位是字节。

2.&数组名:当取地址操作符与数组名一起使用时,数组名代表的是整个数组,取出的地址计算整个数组的地址(整个数组的地址与首元素的地址是有区别的)。

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

为了体现不同,我们可以使用下面的代码尝试一下:

int main()
{int arr[10] = { 0 };printf("&arr[0]     = %p\n",&arr[0]);printf("&arr        = %p\n",&arr);printf("arr         = %p\n",arr);printf("&arr[0] + 1 = %p\n",&arr[0] + 1);printf("&arr    + 1 = %p\n",&arr + 1);printf("arr     + 1 = %p\n",arr + 1);return 0;
}
&arr[0]     = 00000039D51FF650
&arr        = 00000039D51FF650
arr         = 00000039D51FF650
&arr[0] + 1 = 00000039D51FF654
&arr    + 1 = 00000039D51FF678
arr     + 1 = 00000039D51FF654

我们可以发现,上面三行代码的打印的结果完全相同,证明arr,&arr[0],&arr所指向的都是一个地址,但是当我们让指针与整数进行加法运算后,我们发现,arr和&arr[0]加上1后都只跳过了4个字节,而&arr则跳过了40个字节。

这是因为,arr与arr[0]都是首元素的地址,所以+1只能跳过一个元素,即4个字节。而&arr是数组的地址,所以+1后就是跳过一个数组,在这里就跳过了40个字节。

2. 使用指针访问数组

了解了上面的知识后,我们就可以根据数组的特点,用指针来访问元素:

int main()
{int i;int arr[10] = { 0 };int *p = arr;for(i = 0;i < 10;i++)*(p + i) = i;for(i = 0;i < 10;i++)printf("%d ",*(p + i));return 0;
}
0 1 2 3 4 5 6 7 8 9 

搞清楚这里代码后,我们知道,arr其实就是指针,并且还可以赋值给p,我们可以用p来访问数组中的元素,那p与arr就是等效的。我们可以用arr[i]来访问数组元素,那是不是也可以用p[i]来访问数组元素呢?

int main()
{int i;int arr[10] = { 0 };int *p = arr;for(i = 0;i < 10;i++)p[i] = i;for(i = 0;i < 10;i++)printf("%d ",p[i]);return 0;
}
0 1 2 3 4 5 6 7 8 9 

我们发现,将*(p + i)换为p[i]后,代码也能正常运行,说明p[i] 与 *(p+i)是等效的。那么arr[i]也等效于 *(arr+i),编译器在访问数组元素的时候也是将其转换为首元素地址+偏移量的形式来获得地址,然后解引用来访问的。

3. 一维数组传参的本质

我们知道,数组也是可以作为参数传递给函数的。在之前,我们在计算数组元素个数的时候都是在函数外部进行的,那如果我们将数组作为参数传递给函数,能否在函数内部正确计算元素个数?

void test(int arr[])
{int sz2 = sizeof(arr)/ sizeof(arr[0]);printf("sz2 = %d\n",sz2);
}
int main()
{int arr[10] = { 0 };int sz1 = sizeof(arr)/ sizeof(arr[0]);printf("sz1 = %d\n",sz1);test(arr);return 0;
}
sz1 = 10
sz2 = 2

我们发现,函数并未得到正确的结果,这是因为数组名是首元素的地址,所以函数在传参时会将数组名作为指针处理,在计算时,将arr作为首元素的地址进行计算,因此得出结果为2(64位环境下指针大小为8字节)。

所以说,数组传参本质上传递的是首元素的地址。

4. 冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较

如果我们要将数组排序为降序的形式,当靠前的元素小于靠后的元素时,我们就交换两个元素的值。而很明显一轮只能保证最小的数字被移到最后,所以我们要进行多轮。当数组中一共有n个元素时,我们就只需要进行n - 1轮就可以完成排序。而我们每一轮结束就可以确定一个数字的位置,下一轮所需要遍历的元素个数就减少一个,所以每一轮需要遍历的元素个数是与轮次有关的,因此,第i轮只需要遍历n-1-i个元素。另外,我们可以创建一个变量flag,用来确定排序是否已经完成,避免多余的运算

因此,我们可以得到如下的代码:

void Bubble(int* arr,int sz)
{int i,j,count = 0,temp;for(i = 0;i < sz - 1;i++){for(j = 0;j < sz - 1 - i;j++){if(*(arr + j) < *(arr + j + 1)){temp = *(arr + j);*(arr + j) = *(arr + j + 1);*(arr + j + 1) = temp;count++;}if(count == 0)break;}}
}

我们可以进行测试:

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

结果符合预期,代码正常运行。

5. 二级指针

我们知道,指针变量也是变量,而变量就需要地址来存储,那么指针变量存储在哪里呢?

这里我们就需要学习二级指针

用代码可以表示为:

int a = 0;
int *pa = &a;
int **ppa = &p1;

那么,根据指针的知识来类推,对pa进行解引用操作得到的是a,那对ppa进行解引用操作得到的就是pa。所以,如果我们希望通过ppa来得到a的话就需要进行两次解引用操作,即:

**ppa = a;
//*(*ppa) = a;
//*pa = a

6. 指针数组

类比一下整型数组中存放整型数据,那么指针数组中存放的就是指针。

而指针又有类型的区别,所以类比一下整型数组的定义语法,我们定义整型指针数组时,可以写成这个样子:

int* arr[10];

其中,arr是数组名,10是数组大小,而int* 是数组中元素的类型。

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

上面我们知道,指针数组中存放的都是指针,而指针又可以指向一个地址。那么我们是不是可以根据这个特点来用指针数组模拟二维数组呢?

int main()
{int arr1[4] = {0,1,2,3};int arr2[4] = {1,2,3,4};int arr3[4] = {2,3,4,5};int* p[3] = {arr1,arr2,arr3};int i,j;for(i = 0;i < 3;i++){for(j = 0;j < 4;j++){printf("%d ",p[i][j]);}printf("\n");}return 0;
}
0 1 2 3 
1 2 3 4 
2 3 4 5 

数组名就是首元素地址,我们将三个数组的数组名放入指针数组p中,通过p[i]来访问数组的地址,再加上[j]来访问数组中的元素,通过解引用操作得到对应的元素的值。这样,我们就通过指针数组模拟了二维数组的效果。

但是实际上,这并不等同于二维数组。因为二位数组在内存中是连续的,而在这里,我们只是将不同的数组的地址放在了一起,进行访问,而在内存中,这些数组并没有连续储存在一起。

http://www.dtcms.com/a/324243.html

相关文章:

  • Gin vs Beego vs Echo:三大主流 Go Web 框架深度对比
  • 前端开发中的常见问题与实战解决方案​
  • JS数组排序算法
  • scanpy单细胞转录组python教程(三):单样本数据分析之数据标准化、特征选择、细胞周期计算、回归等
  • 2025.8.10总结
  • 学生成绩管理系统的 SQL 表设计与多表查询实战
  • 部署一个免费开源的博客系统
  • 库的制作和原理
  • 双亲委派机制是什么?
  • 大模型工具集成四层架构:识别、协议、执行与实现
  • reinterpret_cast and static cast
  • Lua的数组、迭代器、table、模块
  • Elasticsearch 搜索模板(Search Templates)把“可配置查询”装进 Mustache
  • 从MySQL到大数据平台:基于Spark的离线分析实战指南
  • 重学React(四):状态管理二
  • Spark执行计划与UI分析
  • 【软考中级网络工程师】知识点之 DCC 深度剖析
  • 系统架构设计师备考之架构设计高级知识
  • 企业高性能web服务器——Nginx
  • App Trace 功能详解 (开发者视角)
  • IDEA 如何导入系统设置
  • 从0到1学LangChain之Agent代理:解锁大模型应用新姿势
  • 【机器学习深度学习】Embedding 模型详解:从基础原理到实际应用场景
  • Xstream反序列化,fastjson,jcakson靶场复现
  • 刑法视野下的虚拟财产属性争议:法律风险与市场潜力解析
  • ThinkPHP8学习篇(二):路由
  • Day39--动态规划--198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III
  • Code Exercising Day 10 of “Code Ideas Record“:StackQueue part02
  • MVCC和日志
  • 国内外主流大模型深度体验与横向评测:技术、场景与未来展望