指针(冬四周——冬六周)
1.地址
任何一个变量一定具备以下两个属性:地址和值。
变量的值可以改变,变量的地址不可改变。
变量的值会占用内存空间,变量的地址不占用内存空间。
理解
若x的地址是1000,则x的指针式1000,1000指向x,指针和地址等价。
当变量跨越多个地址时,其整体地址等价为最小的地址。
若一个地址指向多个变量,需要引入指针的类型
小端存储原则:如对于y这样宽度大于1个字节的变量存放到内存中时,先存储低8位(一个字节),再存储高8位。例如,若short int a=0x09,则a占用两个地址,第一个地址存放0x09,第二个地址存放0x00
char x=0x78;
short int y=0x5678;
long int z=0x12345678;
2.指针运算
赋值
char x=0x78;
char *p;
p是一个指针变量,它指向一个char,这里的*可以理解成一个向左的箭头←
定义多个指针时,*不能共享,只属于离他最近的变量
int *p,*q; //q类型为int类型指针
int *p,q; //q类型为int
变量p的类型是char *,表示p指向的对象的类型是char
p = &x;
此时p指向x,p只能接受一个char类型变量的地址的赋值
把一个变量的地址赋值给指针变量时,该变量在此之前必须已经定义。也可以用初始化了的指针变量给另一个指针变量作初始值。
char a,b;
char *p;
p=&a;
b=*p;
char a,b;
char *p;
p=&b;
*p=a;
可以把p理解为←p,所以 p表示p指向的对象,两段代码都把a赋值给b
char a[]="ABC";
char *p;
p=&a[0];
printf("p=%p,*p=%c\n",p,*p);
%p用来输入或输出指针或地址的值,输出地址时实际为%X(16进制)的格式
赋值时类型不匹配
long int a=0x12345678;
unsigned char *p;
p=&a;
指针类型和数据类型不匹配,程序会报错,必须进行强制类型转换。最好不要省略unsigned,否则可能会进行扩充。
指针类型小于指向变量类型
unsigned long int a=0x12345678;
unsigned char *p;
p=(unsigned char *)&a;
本例中指针类型小于指向变量类型,强制类型转换时实际上只有a变量的低8位(char类型2字节)的地址赋值给p,所以p指针实际指向的值是0x78。
指针类型大于指向变量类型
本例中指针类型大于数据类型,转换时直接把4个char类型数组元素变成一个long类型元素赋值给p,因此p指向0x12345678。
对p++的理解
数组中p+1并不代表一定地址加了1,因为一个变量可能占用多个内存地址。p+1的本质是跳到下一个元素
若p指向第0个元素,那么p+i指向第i个同类型的元素
计算公式如下:
p+i= (int)p + i*sizeof(*p);
指针减法运算
int *p,*q;
q-p表示这两个指针指向的对象之间相差几个元素
计算公式如下:
q-p=((int)q-(int)p)/sizeof(*p);
q、p 指针相减的意义实际上是计算两个指针差了几个“单位”的距离,指针相减并不是将其值(也就是地址)相减,如果这是预期行为,那么应该使用以下两种写法:
printf("%d", (int)q - (int)p);
printf("%d", (char*)q - (char*)p); // 转为 char* 类型指针,单位就是一个字节,和地址相减效果相同
与指针的比较
可以做大小比较的逻辑运算
指针合法运算
(1)p+i 指针加减一个整数
(2)q-p 指针减法
(3)q>p 指针比较
指针的宽度
sizeof(指针类型)
在64位VC以及PTA内的gcc中=8
sizeof (char *)==sizeof(short *);
所有指针类型的宽度都是一致的
3.与指针相关三个概念
野指针
char a,b;
char *p;
*p = a;
b=*p;
p没有先赋值,称为野指针,要避免这种情况发生,否则可能会覆盖其他变量。
引用*p前,先确保对p本身赋值
空指针
p=0(NULL)时,称p是空指针,空指针即是0地址
常量NULL在stdio.h中被定义,其值为0
在C语言中,0地址对应的内存单元不能存放任何对象
当p=0时,p所指向的对象不存在
通用指针
void *p;
上述定义的指针为通用指针,即指向任意类型的指针
void表示无类型,它不能用来定义一个变量
void函数类型表示没有返回变量
void可以放在函数的形参内,表示该函数没有参数,调用时不要写参数,直接写f()
void *p;
long int a=0x12345678;
char *q;
p=&a;
q=p;
上述代码中:
(1)p起到了万能容器的作用,它可以接受任何类型指针的赋值。
(2)p起到了万能赋值者的作用,它可以赋值给任何类型的指针变量。
任何时候都不能引用*p,因为p所指向的对象没有类型。
4.指针作为函数的参数
建议详细阅读书P195
地址传递方式
p赋值为变量a的地址,*p表示p指向的变量,为a
*p+=1,a也会加1
地址传递的方式可以在两个不同的函数内部改变一个变量的值,传入数组时相当于地址传递
值传递
两个变量a虽然同名,但是属于两个不同函数,互不影响,在f里面对a++不影响main函数的a
5.一维数组与指针的关系
数组作为函数的参数的本质是地址传递
int f(int a[],int n);
int f(int *p,int n);
int *a是int a[]的本质
f(a,3);
等价于f(&a[0],3);
实参a[]的本质是a[0]的地址(&a[0]),一维数组a和a[0]的地址存在等价关系
int *p是int p[]的本质。当p为指针变量时,p[i]表示指向该指针p指向的第i个元素,p[0]是p当前指向的元素
p[i]等价于*(p+i),用一维数组做函数的参数是地址传递
数组是一个指针常量,不是变量,不能赋值或作类似a++的运算,地址不能改变
如果使用数组名作为函数参数,那么它会被立即转换为指针。因此C语言会自动把作为参数的数组声明转换为相应的指针声明。
6.数组指针
int a[3][4]=
{
{0,1,2,3},
{10,11,12,13},
{20,21,22,23}
};
int *p; //也可以写成int (*p)[4];
int *q;
p=&a[0];
q=&a[0][0];
数组指针也可以定义位int (*p)[4]
p指向a[0]这个一维数组,他的地址为a[0] [0]的地址,故p==q
p是数组指针,它指向了一个一维数组
数组指针的类型可表示为int (*p)[10],这是因为[]优先级高,这个类型可以理解为p是一个指向int [10]数组类型的指针。
数组指针运算
p+i表示p所指向的第i个元素,为a[i]
p[i]表示*(p+i),为a[i],所以p[i] [j]的值为a[i] [j]
(*(p+i))[j] == a[i] [j] //注意[]优先级比 *高,所以要有括号
*(p[i]+j) == a[i] [j]
*(*(p+i) + j )==a[i] [j]
例子
- ① * (p[2]+1)= * (a[2]+1) = * (&a[2] [0]+1) = a[2] [1] = 21
- ② * ( *(p+2)+3)= * (a[2]+3) = * (&a[2] [0]+3)=a[2] [3] = 23
二维数组作为函数参数本质
int main()
{
int a[3][4]=
{
{0,1,2,3},
{10,11,12,13},
{20,21,22,23}
};
f(a,3,4);
}
void f(int p[][4],int r,int c)
{
p[r-1][c-1]*=-1;
}
形参p[] [4]等价于int (*p)[4]
实参a等价于&a[0],二维数组的名字等价于该数组首行的地址
传递方式也是地址传递,传递后p指向a[0]
7.指针数组
int a[3]={10,20,30};
int *r[3];
r[0]=&a[0];
r[1]=&a[1];
r[2]=&a[2];
[]优先级较高,r[]表示r为数组,前面星号表示数组存放的变量类型为指针
8.指针的指针
int a;
int *q=&a;
int **t;
t=&q;
t指向指针q,指针q指向变量a
要获得变量a,可以用**t
一个例子:
int a[2][3]=
{
{0,1,2},
{10,11,12}
}
int y;
y = **a;
y = **&a[0] = *a[0] = *&a[0][0] = a[0][0] = 0
9.指针和字符串的关系
char *p="XYZ";
p指向该字符数组首元素的地址,等价于char *p = &"XYZ"[0],先存放"XYZ",再赋值指针p
字符数组的本质也是首元素的地址,或指向首元素的指针
puts()本质
void puts(char *p)
{
while (*p!='\0')
{
putchar(*p);
p++;
}
putchar('\n');
}
char a[100]="ABC";
puts(a+1);
数组a的本质是&a[0],a+1指向a[1],故该程序输出BC
10.数组与指针变量的相同点和不同点
char a[100]="ABC";
char *p=a; //或char *p=&a[0];
相同点:
(1)p[i]==a[i]
(2)*(p+i)= * (a+i)
不同点:
(1)sizeof (a) != sizeof (p)
此处的a必须理解成数组a,不能理解成&a[0]
(2)赋值
a= "xyz"; 语法错误,这里的a就是&a[0]即它是一个常数,不能被赋值
p="xyz"; 语法正确。
注意:作为数组时,a是一个指针常量,一直代表a[0]的地址,所以既不能对a直接赋值,也不能对a进行++的操作
(3)取地址
只有对象(容器)可以取地址,对象包括变量、数组、函数。所以&a[0]不能取地址,即a在取地址时不能理解为&a[0]
char (*u)[100]=&a; //此处a是数组a,而不是&a[0]
char **t=&p;
11.函数指针
函数名可以理解成函数的地址
int (*p)(int,int)=add; //指向add函数的指针
#include <stdio.h>
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a/b;
}
int find_index(char operator) /* 根据operator返回其在a中的下标 */
{ /* '+' '-' '*' '/'的下标分别为0、1、2、3 */
char a[] = "+-*/";
int i;
for(i=0; i<sizeof(a)-1; i++)
{
if(a[i] == operator)
return i;
}
return -1;
}
int main()
{
int (*pf[4])(int a, int b) = /* pf是指针数组, 每个元素均为函数指针; 把*pf[4]替换成X, */
{ /* 可以发现X是一个函数, 故pf[i]指向一个函数, pf[i]就是函数指针 */
add, sub, mul, div /* 函数名可以理解成函数的地址, 例如这里的add代表add()函数的地址 */
}; /* 所谓函数的地址是指该函数经过编译后生成的机器码的首字节的地址 */
int x, y, z;
char operator;
scanf("%d %c %d", &x, &operator, &y);
z = (*pf[find_index(operator)])(x, y);
/* pf[0]就是add()函数的地址, *pf[0]就是add()函数本身,
故(*pf[0])(x, y)相当于add(x, y);
函数指针数组的意义在于我们可以通过它实现多分支结构,
要是不用函数指针数组, 就需要通过以下多分支结构来实现
对x、y的加减乘除:
if(operator == '+')
z = add(x, y);
else if(operator == '-')
z = sub(x, y);
else if(operator == '*')
z = mul(x, y);
else if(operator == '/')
z = div(x, y);
*/
printf("%d %c %d = %d\n", x, operator, y, z);
return 0;
}