能看懂文字就能明白系列
C语言笔记传送门
个人主页
:古德猫宁-
前面有篇文章简单的介绍了指针及其一些简单的运算和野指针,指针提供了一种以符号形式使用地址的方法。因为计算机的指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。尤其是指针能有效地处理数组。数组表示法其实是在变相地使用指针。
数组名是首元素的地址
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
这⾥我们使用 &arr[0] 的方式拿到了数组第⼀个元素的地址并赋值给指针变量p,但是其实数组名本来就是地址,⽽且是数组⾸元素的地址,我们来做个测试。
#include
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);
return 0;
}
按照上面的说法,大家觉得结果是否一样
我们发现数组名和数组⾸元素的地址打印出的结果⼀模⼀样,数组名就是数组⾸元素(第⼀个元素)的地址。
数组名如果是数组⾸元素的地址,那下⾯的代码怎么理解呢?
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
return 0;
}
运行结果:
按照上面所说,数组名是数组首元素(第一个元素)地址,那运行的结果应该是4/8才对,怎么变成40个字节了。
其实数组名就是数组首元素(第一个元素)地址是对的,但是有两个例外:
除此之外,任何地方使用数组名,数组名都表示首元素的地址。
那各位再看看下面的代码
#include
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;
}
运行结果:
我丢,三个的结果一样,那arr和&arr有什么区别呢?
不要急,请接着往下看
#include
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;
}
这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素(这里是int类型,所以是4个字节)。
但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的。
下面的等式就体现了C语言的灵活性:
//arr + 2 == &arr[2];相同的地址
//*(arr+2)==arr[2]相同的值
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。
从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义arr[n]的意思是*(arr+n)。可以认为*(arr+n)的意思是“到内存的arr位置,然后移动n个单元,检索储存在那里的值”。
特别注意的是:
不要混淆*(arr+2)和*arr+2。解引用运算符(*)的优先级高于+,所以*arr+2相当于(*arr)+2
//*(arr + 2);arr第三个元素的值
//*arr + 2;arr第一个元素的值再加2
明白了数组和指针的关系,我们来练习一下
#include
int main()
{
int arr[10] = { 0 };
//输⼊
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//输⼊
int* p = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
//scanf("%d", arr+i);//也可以这样写
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", p[i]);
}
return 0;
}
运行结果:
将*(p+i)换成p[i]也是能够正常打印的,本质上p[i] 是等价于 *(p+i)。
同理arr[i] 应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引用来访问的
假设要编写一个处理数组的函数,该函数返回数组的元素之和,那应该如何调用该函数呢?
total=sum(arr);我们可能是这样调用函数的
那么,该函数的原型是什么呢?前面说过,数组名是首元素的地址,所以实际参数arr是一个存储int类型值的地址,应该把它赋值给一个指针形式参数,即该形参是一个指向int的指针。
int sum(int* arr);//对应的函数原型
sum()从该参数获得了该数组首元素的地址,知道要在该位置上找出一个整数。
注意,该参数并未包含数组元素个数的信息。
我们有两种方法让函数获得这一信息:
第一种方法是,在函数代码中写上固定的数字大小
int sum(int* arr)
{
int total = 0;
for (int i = 0;i < 10;i++)
{
total += arr[i];//arr[i]和*(arr+i)相同
}
return total;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int ret = sum(arr);
printf("%d", ret);
return 0;
}
既能用指针表示数组名,也可以用数组名表示指针。
该函数定义有限制,只能计算10个int类型的元素。
另一个比较灵活的方法是把数组大小作为第二个参数:
int sum(int* arr,int n)
{
int total = 0;
for (int i = 0;i < n;i++)
{
total += arr[i];
}
return total;
}
int main()
{
int n = 0;
scanf("%d", &n);
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int ret = sum(arr,n);
printf("%d", ret);
return 0;
}
这里,第一个形参告诉函数该数组的地址和数据类型,第二个形参告诉函数该数组中元素的个数