本篇是我针对指针板块认为比较重要的一些点做的一个梳理总结
如有错误和补充欢迎dd
如下图所示,指针p-1、p、p+2就是指针,其实就是一个变量,而它的值是所指元素在内存中的地址。
指针的长度跟CPU的位数相等。
当CPU是32位时,不管这个指针的指向类型是什么,指针长度都为32bit,也就是4个字节。同理得到CPU是64位时,指针长度就是8字节。
所以,sizeof(指针)都是固定值,不会随类型的改变而改变。
和数组一样,对于指针来说初始化同样重要 。
在指针的初始化中,“=”的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但int *p = 0除外,该语句表示指针为空)。
举个例子:
int *t; //未初始化的指针 *t = 10; //nonooo
这是错误的写法。实际上第二行是指将10存入了t指向的位置,但t未被初始化,它的值就是一个随机值(因为创建指针时,计算机只分配了存储指针本身的内存,而没有分配存储数据的内存),所以计算机根本不知道要将10存在哪里。
来一串代码测试下就能明白个七七八八了:
#include <stdio.h> int data[2] = { 100, 200 }; int moredata[2] = { 300, 400}; int main () { int *p1, *p2, *p3; p1 = p2 =data; p3 = moredata; printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1,*p2,*p3); printf(" *p1++ = %d, *++p2 = %d, (*p3)++ = %d\n",*p1++,*++p2,(*p3)++); printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1,*p2,*p3); return 0; }输出结果为:
第一行为初始值,指针名表示数组的首元素;
再来看到第二行打印中,*p1++和(*p3)++的值都与原始值一样,即说明*优先级比++运算符的优先级高;
最后来到第三行打印值,发现只有(*p3)++改变了数组元素的值,剩余两个分别把p1和p2的值指向了数组的下一个元素,造成这种差异是因为++的对象不同。
对“p”++表示递增至p的下一个元素,而对“*p”++则表示对*p的值+1。
前提是求差的两指针需要是同一数组中的不同元素。两指针相减可以求出两指针之间的距离,同时其差值单位与数组单位相同。
假设“ yr ”是一个数组,即:
yr == &yr[ 0 ]
两者等价,都是常量,可直接将其赋值给指针变量。
若设置一个 *a,那么你可以:a = yr,即将数组yr首元素的地址赋给了指针。
同时注意对于一个指针来说(以*a为例):
a 表示该指针指向元素的地址 &a 表示该指针自身的地址
ar[ i ] == *( ar+i ) //相同的值
&ar[ i ] == ar + i //相同的地址
注意:*( yr + 1 ) 不等同于 * yr + 1,前者表示yr第二个元素的值,后者表示yr第一个元素的值加1。
我们以二维数组(数组的数组)举例
int (*a) [2]
int a [ ] [2]
两者等价,表示a是一个指向2个int类型值的指针,即a[][2]
一般声明一个指向多维数组的指针时,只能省略最左边方括号中的值,因为它只用于表示这是个指针,而其他方括号则用于描述指针所指向数据对象的类型。
我感觉多少有点套中套的意思。举个例子就能理解了:
a[0] == &a[0][0](地址)
**a == *&a[0][0](值)
相当于一个包含关系,a里的一层是a[0],然后a[0]里又有一层是a[0][0]。
总结:二维中a是地址的地址,必须要*两次才能得到原始值。
方便理解的测试代码:
输出结果为:
a[0][0] = 2 *a[0] = 2 **a = 2 a[2][1] = 3 *(*(a+2)+1) = 3
看到没看到没,a[0][0]、a[0]和*a的值是一样的!刚开始是多少会有点晕头转向的,但只要理解了嵌套逻辑和包含关系,就能够理清思路了。
然后我们再来分析下最后那一行看起来略微复杂的*(*(a+2)+1),咱给它拆开看看:
a 二维数组首元素地址
a+2 二维数组第三个元素(一维)的地址
*(a+2) 二维数组第三个元素的首元素(一个类型值)的地址
*(a+2)+1 二维数组第三个元素的第二个元素的地址
*(*(a+2)+1) 二维数组中第三个元素([2])的第二个元素([1])的值
(注意两层*才能得到二维数组的值哦)
说了那么多,其实*(*(a+2)+1)与a[2][1]是一样的,只不过一个指针表示法一个数组表示法。
用的话就乖乖用数组表示法吧,简单易懂,直接明了。
{ int thing[ 10 ][ 6 ]; zippo( 10 ,6 , thing ); } //主函数体内调用函数 void zippo( int n, int m, int ar[ n ][ m] ) //ar是一个指向数组(含m个int类型值) 的指针 { int temp [ n ][ m]; //temp是一个n*m的int型数组 temp[ 0 ][ 0 ] = 2; //设置temp的第一个元素为2 ar[ 0 ][ 0 ] = 2; //设置thing[ 0 ][ 0 ]为2 }
刚开始看了可能会暴起:这明明就是数组!它怎么又成指针了!!
(多少呢有点混淆是吧)这就要熟悉数组和指针之间声明的联系了。如上,当调用zippo函数时,ar就成为了指向thing[ 0 ]的指针,而temp则被创建为10*6的数组。
实际上ar和thing都是指向thing[ 0 ][ 0 ]的指针,所以ar[ 0 ][ 0 ]和thing[ 0 ][ 0 ]访问的数据位置是相同的。
int *ar[ ] | 指针数组 |
int (*ar)[ ] | 数组指针 |
- 指针数组:实际上是个数组,而数组中的元素都为一个个的指针。(ar是一个含有n个*int的数组)
- 数组指针:实际上是个指针,而它指向一个数组。(ar是一个指向大小为n个整形的数组的指针)
那咱试试思考写一个数组:
数组有5个元素每个元素是一个指针,且指针指向一个有3个整形元素的数组。
应该就写成int ( *arr[5]) [3] ,对着了没
使用指针作为参数,可实现两种功能:
(1)可以读取上一层函数中的变量的值*p
(2)可以修改上一层函数中变量中的值*p(普通参数无法实现)或者实现两数的交换。
#include<stdio.h> void test(int* p) { printf("内层a:%d \n",*p);//读取上一层参数的值 *p=1;//修改上一层参数的值 } int main() { int a=0; test(&a); printf("外层a:%d \n",a); return 0; }
输出结果为:
总之,传指针就相当于传地址,那传值和传地址又有什么区别呢?
传值相当于传递一个拷贝,占用空间,且复制元素占用时间;
而传地址只需传一次地址就可以了,可以直接访问,效率高。因此对于变量所占空间大的情况,适合传地址,也就是传指针。
指针大法好。