目录
1.指针定义
1.1指针定义
指针:地址的别名,本身存放着所对应的变量的地址。通过对指针的解引用(*指针名),可以找到该地址存放的变量。
int a=5,b=0;
int *p=&a; //一级指针定义,以变量a的地址对其进行初始化
int *q=&b;
int **t=&p; //二级指针定义,在一级指针基础上,
//以指针变量p的地址对其进行初始化
//p==&a;*p==a;
//*t==p;**t==*p==a;
1) 可通过 p=&b,对指针p所存放的地址,也就是p的指向进行修改。此时,*p==0。
2) 二级指针也可以通过t=&q(指向另一个一级指针)、*t=&b(改变它所指向的一级指针(p)(*s==p)的指向)改变指向。二级指针需解两次引用(**t)。
int a = 5, b = 0;
int* p = &a;
int* q = &b;
int** t = &p;
printf("**t = %d,*q = %d\n", **t, *q);
t = &q;
*t = &a;
printf("**t = %d,*q = %d\n", **t, *q);
1.2'*'的含义
1)指针的定义:类型* 指针变量名
2)解引用:*指针变量名
对一级指针解一次引用,可以直接访问其指向的变量,即*p==a。
对二级指针解一次引用,可以找到其指向的一级指针,即*s==p;对二级指针解两次引用,可以找到其一级指针指向的变量,即**s==*p==a。
3)乘法:数据*数据
1.3'&'的含义
1)取地址:指针变量其本身存放的是其指向变量的地址,所以用int* p = &a;进行指针初始化,使用p = &b;进行指针指向修改的操作。
2)按位与运算
1.4'*'与'&'的联用,'*&'、'&*'是否相同
p==&a;p存放的变量a的地址,所以打印出来p为一长串数字,就是在我本地运行中·变量a的存放地址。
1)*&a可以理解为*(&a),&a为取变量a的地址(等价于p),*&a就等价于*p,对p解引用可以访问的p所指向的变量即a(*p==a),所以*&a等价于a。
2)*&p可以理解为*(&p),&p为取指针变量p的地址(等价于s),*&p就等价于*s,对二级指针s解一次引用就找到了s所指向的一级指针p(*s==p),所以*&p等价于p。
3)*&p可以理解为&(*p),*p为对一级指针p解引用,可以访问的p所指向的变量即a(*p==a),&*p就等价于&a,&a为取变量a的地址(等价于p),所以&*p等价于p。
综上,*&就等价于&*,且*与&互相抵消。
1.5'[ ]'的解引用功能
对于数组名arr,只有当其与数组arr的定义在同一作用域,使用sizeof(arr)时,arr代表整个数组的大小。其他时候arr为数组的起始地址,也是数组首元素的地址。我们通常用arr[i](i为数组元素下标)来访问数组元素。当我们用指针指向数组首地址后(int *p=arr),就可以使用*(p+i)来访问数组元素。
int arr[] = { 1,2,3,4,5,6 };
int* p = arr;
int len = sizeof(arr) / sizeof(arr[0]);//数组长度
for (int i = 0; i < len; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
}
printf("\n");
for (int i = 0; i < len; i++) {
printf("arr[%d] = %d\n", i, p[i]);
}
但是,我们发现使用p[i]也可以访问数组元素,且*(p+i)等价于p[i]。所以,[]也具有解引用功能。
2.指针大小
指针大小与指针类型(如int*,char*,....)、指针级别(一级,二级,...)无关。只和操作系统(OS)有关。
在x86(32 bit)系统上,指针大小为4字节。在x64(64 bit)系统上,指针大小为8字节。
3.可以用指针进行的操作
3.1访问变量
int a = 10, b = 20;
int* p = &a;
printf("*p = %d\n", *p); //1
*p = 5; //2
printf("a = %d\n", a);
p = &b; //3
printf("*p = %d\n", *p);
1)通过指针解引用,访问指针所指向的变量。*p==a==10。
2)通过对指针解引用进行赋值修改,达到修改指针所指向的变量的目的。*p = 5等价于a = 5。
3)通过对指针所存地址进行修改,改变指针的指向。p = &b,意为将p原本所存的值,即a的地址,修改为b的地址。再对p进行解引用就访问到了变量b的值。
3.2函数传参
若想要达到无需返回值,使形参的改变,影响实参的值,就必须要:
1)将实参地址作为指针传入。
2)对指针解引用,访问到其存放地址所指向的变量值,再进行想要的操作。
如使用无返回值交换函数Swap,实现a、b值的交换。
void Swap(int* p, int* q) {
int temp = *p;
*p = *q;
*q = temp;
}
int main() {
int a = 10, b = 20;
Swap(&a, &b);
printf("a = %d,b = %d\n", a, b);
}
在主函数中,调用Swap函数,将a、b地址作为实参传入,相当于以a、b地址分别对指针p、q 进行初始化(int* p = &a;int* q = &b)。
1)temp = *p;如图中箭头1,通过*p将a的值赋值给temp。
2)*p = *q;如图中箭头2,通过*q寻址到变量b,获取到b的值,再通过*p寻址到变量a,将a的值赋值为b的值(*p = *q等价于a = b)。
3)*q = temp;如图中箭头3,通过*q寻址到变量b,将b的值赋值为temp,即a的值。
以上即为通过对指针形参的操作,影响了实参的值,达到了无需返回值的目的。
4.类型对指针变量起到的两个作用
4.1解析存储单元的大小
在同一操作系统上,虽然各类指针的大小相同,但其所解析的存储单元的大小因指针类型而不同。如char* 的解析单元大小为1字节,int*的解析单元大小为4字节,double*的解析单元大小为8字节等。
以一维数组为例。因为Windows系统中采用小端数据存储模式(即低位数据存放在低地址位置),并且数组元素地址在内存中由低到高,且连续。所以数组int arr[]={1,2,3,4};在内存中的表示形式如下:(数据以十六进制表示,如1-->0x0000 0001)
int arr[] = { 1,2,3,4 };
int* p = arr;
printf("%d\n", *p);
printf("%d\n", *(p + 1));
若使p指向数组首地址,使用*p就可以访问数组的首元素(即*p==arr[0]),且*(p+1)== arr[1]。如下图所示:
此时,*p 等价于arr[0],*(p+1) 等价于arr[1]。
int arr[] = { 1,2,3,4 };
int* p = arr;
char* q = (char*)p; //指针强转
printf("%d\n", *q);
printf("%d\n", *(q + 1));
printf("%d\n", *(q + 12));
若将int*型指针p强制转换为char*型指针,则其解析存储单元能力也从4字节变为1字节。
此时,*q还等于1,但*(q+1) 等于0,*(q+4)才等于2。
综上,我们可以推导出:*p读取了0x100到0x103共4字节地址上的数据,*(p+1)读取了0x104到0x107共4字节地址上的数据,*q读取了0x100上1字节地址上的数据,*(q+1)读取了0x101上1字节地址上的数据,*(q+4)读取了0x104上1字节地址上的数据。所以int*类型的指针解析存储单元能力为4字节,char*类型的指针解析存储单元的能力为1字节。
4.2指针变量加整型的能力
同样以一维数组int arr[]={1,2,3,4};为例,使int*型指针p指向其首地址。则p+1指向了下一个单元格的地址。将p,p+1所指向的地址分别打印出:
int arr[] = { 1,2,3,4 };
int* p = arr;
printf("p = %x\n", p); //p存放的地址,以十六进制打印处
printf("p + 1 =%x\n", p + 1);
printf("p + 2 = %x\n", p + 2);
我们可以发现地址之间相差4字节大小。所以int*型指针加整型1时会向后(高地址)偏移4字节位置,加整型2时会向后(高地址)偏移8字节位置,所以int*型指针加整型n时会向后偏移4*n个字节位置。又因为int型数据大小为4字节,所以int*型指针加整型n时会向后偏移sizeof(int)*n个字节位置。
由此,我们思考char*型指针加整型n后会不会也向后偏移n*sizeof(char)个字节位置?
验证:
char arr[] = { 1,2,3,4 };
char* p = arr;
printf("p = %x\n", p);
printf("p + 1 = %x\n", p + 1);
printf("p + 2 = %x\n", p + 2);
由此,我们发现char*型指针加整型n确实会向后偏移n*1(即n*sizeof(char))字节位置。
综上,我们可以得出结论:类型* 型指针,其加整型的能力为,地址向后偏移sizeof(类型)*整型个字节。