指针高级
数组指针(也称行指针)
定义:
int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。
指针数组
定义:
int *p[n];
- []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。
- 这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;
- 因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
二级指针
- 指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double
*、char * 等。 - 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。
假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:
将这种关系转换为C语言代码:
int a =100;
int *p1 = &a;
int **p2 = &p1;
int a=100;
int *p1 =&a;
int **p2= &p1;
获取指针指向的数据时,一级指针加一个*,二级指针加两个*,三级指针加三个*,以此类推,请看代码:
#include <stdio.h>
int main()
{
int a =100;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
printf("%d, %d,%d, %d\n", a, *p1, **p2, ***p3);
printf("&p2 = %#X, p3 = %#X\n", &p2, p3);
printf("&p1 = %#X, p2 = %#X, *p3 = %#X\n", &p1, p2, *p3);
printf(" &a= %#X, p1 = %#X, *p2 = %#X, **p3 = %#X\n", &a, p1, *p2, **p3);
return 0;
}
运行结果:
100, 100, 100, 100
&p2 = 0X28FF3C, p3 = 0X28FF3C
&p1 = 0X28FF40, p2 = 0X28FF40, *p3 =0X28FF40
&a = 0X28FF44, p1 = 0X28FF44, *p2 =0X28FF44, **p3 = 0X28FF44
以三级指针 p3 为例来分析上面的代码。
*p3等价于((p3))。p3得到的是 p2 的值,也即 p1 的地址;(p3) 得到的是 p1 的值,也即a 的地址;经过三次“取值”操作后,((*p3)) 得到的才是 a 的值。
假设 a、p1、p2、p3 的地址分别是 0X00A0、0X1000、0X2000、0X3000,它们之间的关系可以用下图来描述:
【实例】使用指针遍历二维数组。
#include <stdio.h>
int main()
{
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0; i<3; i++)
{
for(j=0; j<4; j++)
printf("%2d ",*(*(p+i)+j));
printf("\n");
}
return 0;
}
运行结果:
0 1 2 3
4 5 6 7
8 9 10 11
指针数组和二维数组指针的区别
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:
- 指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
结构体指针
程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的指针也可以指向一个结构体,定义的形式一般为:
struct 结构体名 *变量名;
注意:
-
结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&,所以给pstu 赋值只能写作:
struct stu *pstu = &stu1;
而不能写作:
struct stu *pstu = stu1;
【示例】结构体指针的使用。
#include <stdio.h>
struct stu
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
};
int main()
{
struct stu stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", (*pstu).name, (*pstu).num, (*pstu).age, (*pstu).group,(*pstu).score);
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", pstu->name, pstu->num, pstu->age, pstu->group, pstu->score);
return 0;
}
运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。
【示例】计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。
#include <stdio.h>
struct stu
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
};
struct stu stus[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
void average(struct stu *ps, int len);
int main()
{
int len = sizeof(stus) / sizeof(struct stu);
average(stus, len);
return 0;
}
void average(struct stu *ps, int len)
{
int i, num_140 = 0;
float average, sum = 0;
for(i=0; i<len; i++)
{
sum += (ps + i) -> score;
if((ps + i)->score < 140)
num_140++;
}
printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5,num_140);
}
运行结果:
sum=707.50
average=141.50
num_140=2首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。
无类型指针
无类型指针指的是void *这种指针,表示可以指向任何数据类型。
比如
int n = 3;
int *p = NULL; //说明指针p此时空闲,没有指向任何有意义的内存空间
void *gp = &n; //无类型指针gp指向整型变量n
p = (int *)gp; //把无类型指针转换为整型指针
printf("%d\n", *p);
结果输出3,说明无类型指针可以转换成任何数据类型的指针。
常量指针 和 指针常量
我们先总结一下 常量指针 和 指针常量 的区别
-
(指针)和 const(常量)谁在前先读谁 ;
-
*象征着地址,const象征着内容;
-
谁在前面谁就不允许改变。
int a =3; int b = 1; int c = 2; int const *p1 = &b;//const 在前,定义为常量指针 int *const p2 = &c;//*在前,定义为指针常量
常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。
p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
p2= &a是错误的,而*p2 = a 是正确的。