1、指针的理解与定义
1.1 变量的访问方式
计算机中程序的运行都是在内存中进行的,变量也是在内存中分配的空间,且不同类型的变量占用不同大小的空间。那如何访问内存中变量存储的数据呢?有两种方式:直接访问
和间接访问
。直接访问,直接使用变量名进行的访问,以前的程序中都是采用这种方式。
1.2 内存地址与指针
为了能够有效的访问到内存的每个单元(即一个字节),就给内存单元进行了编号,这些编号被称为该内存单元的地址。因为每个内存单元都有地址,所以变量存储的数据也是有地址的。
通过地址能找到所需的变量单元,可以说,地址指向该变量单元,将地址形象化地称为“指针”。即:
-
变量
:命名的内存空间,用于存放各种类型的数据。
-
变量名
:变量名是给内存空间取的一个容易记忆的名字。 -
变量值
:在变量单元中存放的数据值。 -
变量的地址
:变量所使用的内存空间的地址,即指针
。 -
指针变量
:一个变量专门用来存放另一变量在内存中数据的地址 (即指针),则它称为“指针变量”。我们可以通过访问指针变量达到访问内存中另一个变量数据的目的。(有时为了阐述方便,将指针变量直接说成指针。) -
体会:指针就是内存地址,使用指针访问变量,就是直接对内存地址中的数据进行操作。
1.3 指针变量的定义
一般格式: 数据类型 *指针变量名 [=初始地址值];
-
数据类型是指针变量所指向变量数据类型。可以是 int、char、float 等基本类型,也可以是数组等构造类型。
-
字符
*
用于告知系统这里定义的是一个指针变量,通常跟在类型关键字的后面。比如, char * 表示一个指向字符的指针, float * 表示一个指向 float 类型的值的指针。此外,还有指向数组的指针、指向结构体的指针。
举例1:
int *p; //读作:指向int的指针”或简称“int指针”
这是一个指针变量,用于存储int型的整数在内存空间中数据的地址。
变形写法:
int* p;
int * p;
注意:
1、指针变量的名字是 p,不是*p。
2、指针变量中只能存放地址,不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。
举例2:同一行声明两个指针变量
// 正确
int * a, * b;
// 错误
int* a, b; //此时a是整数指针变量,而b是整数变量
举例3:一个指针指向的可能还是指针,这时就要用两个星号 ** 表示。(后面讲)
int **foo;
2、指针的运算
指针作为一种特殊的数据类型可以参与运算,但与其他数据类型不同的是,指针的运算都是针对内存中的地址来实现的。
2.1 取址运算符:&
取址运算符,使用“&
”符号来表示。作用:取出指定变量在内存中的地址
,其语法格式如下:
举例1:
int num = 10;
printf("num = %d\n", num); // 输出变量的值。 num = 10
printf("&num = %p\n", &num); // 输出变量的内存地址。&num = 00000050593ffbbc
说明:
1、在输出取址运算获得的地址时,需要使用“%p”作为格式输出符。
2、这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)。
举例2:将变量的地址赋值给指针变量
int num = 10;
int *p; //p为一个整型指针变量
p = #
举例3:
int d = 10;
int *e, *f;
e = &d;
f = e;
指针变量的赋值
1、指针变量中只能存放地址(指针),不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。
2、C语言中的地址包括位置信息(内存编号,或称纯地址)和它所指向的数据的类型信息,即它是“带类型的地址
”。所以,一个指针变量只能指向同一个类型的变量,不能抛开类型随意赋值。
-
char* 类型的指针是为了存放 char 类型变量的地址。
-
short* 类型的指针是为了存放 short 类型变量的地址。
-
int* 类型的指针是为了存放 int 类型变量的地址。
3、在没有对指针变量赋值时,指针变量的值是不确定的,可能系统会分配一个未知的地址,此时使用此指针变量可能会导致不可预料的后果甚至是系统崩溃。为了避免这个问题,通常给指针变量赋初始值为0(或NULL),并把值为0的指针变量称为空指针变量
。
举例4:通过指针变量修改指向的内存中的数据
int main() {
int num = 10, *ptr;
ptr = #
printf("%d\n",num);
scanf("%d", ptr); //等价于scanf("%d", &num);
printf("%d\n",num);
return 0;
}
2.2 取值运算符:*
在C语言中针对指针运算还提供了一个取值运算符,使用“*
”符号表示。其作用与&相反
,根据一个给定的内存地址取出该地址对应变量的值。也称为解引用符号
。
其中,“*
”不同于定义指针变量的符号,这里是运算符。“指针表达式”用于得到一个内存地址,与“*”结合以获得该内存地址对应变量的值。
举例1:
int main() {
int a = 2024;
int *p;
p = &a;
printf("%p\n",&a); //0000005cc43ff6d4
printf("%p\n",p); //0000005cc43ff6d4
printf("%d\n", *p); //2024
return 0;
}
举例2:
int main() {
int num = 10; //这里定义一个整型变量num
printf("num = %d\n", num); //输出变量num的值。输出:num = 10
printf("&num = %p\n", &num); //输出变量num的地址。输出:&num = 000000e6a11ffa1c
int *p = #
printf("%p\n",p); //000000e6a11ffa1c
printf("%d\n",*p);//10
printf("*&num = %d\n", *&num);//通过num地址读取num中的数据。输出:*&num = 10
return 0;
}
&
运算符与 *
运算符互为逆运算,下面的表达式总是成立:
int i = 5;
if (i == *(&i)) // 正确
举例3:通过指针变量修改指向内存地址位置上的值
int main() {
int num = 10;
int *p = #
*p = 20;
printf("num = %d\n",num); //num = 20
char ch = 'w';
char* pc = &ch;
*pc = 's';
printf("ch = %c\n", ch); //ch = 's'
return 0;
}
举例4:
定义指针变量 p1、p2,默认各自指向整数a、b,a、b从键盘输入。设计程序,使得 p1 指向其中的较大值,p2 指向其中的较小值 。
int main() {
int *p1, *p2, *p, a, b;
printf("请输入两个整数: ");
scanf("%d,%d", &a, &b);
p1 = &a;
p2 = &b;
if (a < b) {
p = p1;
p1 = p2;
p2 = p;
}
printf("输出p1、p2: ");
printf("%d,%d\n", *p1, *p2);
return 0;
}
举例5:已有代码如下:
int a = 10;
int *p;
p = &a;
请看问题:
问题1:&*p
的含义是什么?
-
“&”
和“*”
两个运算符的优先级别相同,但按自右而左方向运算。因此,&*p
与&a
相同,即变量a的地址。 -
如果有
p1 = &*p;
它的作用是将&a
(a的地址)赋给p1
,如果p1
原来指向 b,经过重新赋值后它已不再指向b了,而指向了a。
问题2:*&a
的含义是什么?
-
先进行
&a
运算,得a的地址,再进行*
运算。*&a
和*p
的作用是一样的,它们都等价于变量a。即*&a
与 a 等价。
2.3 指针的常用运算
指针本质上就是一个无符号整数,代表了内存地址。除了上面提到的取址运算外,指针还可以与整数加减、自增自减、同类指针相减运算等。但是规则并不是整数运算的规则。
2.3.1 指针与整数值的加减运算
格式:指针±整数
指针与整数值的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动)。指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。
通过此操作,可以快速定位你要的地址。
short *s;
s = (short *) 0x1234;
printf("%hx\n", s + 1); //0x1236 复习:%hx :十六进制 short int 类型
printf("%hx\n", s - 1); //0x1232
int *i;
i = (int *) 0x1234;
printf("%x\n", i + 1); //0x1238 复习:%x :十六进制整数
说明:s + 1 表示指针向内存地址的高位移动一个单位,而一个单位的 short 类型占据两个字节的宽度,所以相当于向高位移动两个字节。
再比如:变量a、b、c、d和e都是整型数据int类型,它们在内存中占据一块连续的存储区域。指针变量p指向变量a,也就是p的值是0xFF12
指针p+1并不是地址+1,而是指针p指向数组中的下一个数据。比如,int *p,p+1表示当前地址+4,指向下一个整型数据。
举例1:
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = &arr[1];
printf("p的地址为:%p,对应的值为%d\n", p, *p); //p1的地址为:000000df21bff6e4,对应的值为2
printf("p+1=的地址为:%p,对应的值为%d\n", p + 1, *(p + 1)); //p1+1=的地址为:000000df21bff6e8,对应的值为3
printf("p-1=的地址为:%p,对应的值为%d\n", p - 1, *(p - 1)); //p1-1=的地址为:000000df21bff6e0,对应的值为1
return 0;
}
注意:只有指向连续的同类型数据区域,指针加、减整数才有实际意义。
举例2:
对于长度是 N 的一维数组 a,当使用指针 p 指向其首元素后,即可通过指针 p 访问数组的各个元素。
其中:
-
a[0]
用*p
表示 -
a[1]
用*(p+1)
表示 -
a[i]
用*(p+i)
表示
遍历数组操作如下:
#include <stdio.h>
#define LENGTH 5
int main() {
int arr[LENGTH] = {10,20,30,40,50};
//方式1:传统直接访问的方式
for(int i = 0;i < LENGTH;i++){
printf("%d ",arr[i]);
}
printf("\n");
//方式2:使用指针访问
int *p = &arr[0];
for(int i = 0;i < LENGTH;i++){
printf("%d ",*(p+i));
}
return 0;
}
2.3.2 指针的自增、自减运算
指针类型变量也可以进行自增或自减运算,如下:
p++ 、 p-- 、 ++p 、--p
++和--在运算符章节已经讲过,这里针对指针的增加或减少指的是内存地址的向前或向后移动。
针对于数组来说,由于数组在内存中是连续分布的。
-
当对指针进行++时,指针会按照它指向的数据类型字节数大小增加,比如 int * 指针,每 ++ 一次, 就增加4个字节。
-
当对指针进行--时,指针会按照它指向的数据类型字节数大小减少,比如 int * 指针,每 -- 一次, 就减少4个字节。
int main() { int arr[5] = {1, 2, 3, 4, 5}; int *p1 = &arr[0]; int *p2 = &arr[3]; printf("p1的值为:%d\n", *p1); //1 printf("++p1的值为:%d\n", *(++p1)); //2 printf("p1的值为:%d\n", *p1); //2 printf("p1的地址为:%p\n", p1); //00000055c0bff704 printf("p1++的地址为:%p\n", ++p1); //00000055c0bff708 printf("p2的值为:%d\n", *p2); //4 printf("--p2的值为:%d\n", *(--p2)); //3 printf("p2的值为:%d\n", *p2); //3 return 0; }
举例2:请分析下面几种情况。
初始情况:
int a[5] = {10,20,30,40,50};
情况1:
int *p = a; //p开始时指向数组a的首元素 等同于 int *p = &a[0]; p++; //使p指向下一元素a[1] printf("%d\n",*p); //得到下一个元素a[1]的值,即20
情况2:
int *p = a; //p开始时指向数组a的首元素 printf("%d\n",*p++); //10 分析:由于++和*同优先级,结合方向自右而左,因此它等价于*(p++) printf("%d\n",*p); //20
拓展:
*(p++); //先取*p值,然后使p自增1 *(++p); //先使p自增1,再取*p
拓展:如果 p 当前指向 a 数组中第 i 个元素a[i],则:
*(p--) //相当于a[i--],先对p进行“*”运算,再使p自减 *(++p) //相当于a[++i],先使p自加,再进行“*”运算 *(--p) //相当于a[--i],先使p自减,再进行“*”运算
情况3:
int *p = &a[2]; //p开始时指向数组a的第3个元素 printf("%d\n",*(p--)); //30 p = &a[2]; printf("%d\n",*(++p)); //40 p = &a[2]; printf("%d\n",*(--p)); //20
情况3:
int *p = a; //p开始时指向数组a的首元素 printf("%d\n",++(*p)); //11 /* 分析:表示p所指向的元素值加1,如果p=a, 则相当于++a[0],若a[0]的值为10,则a[0]的值为11。 注意: 是元素a[0]的值加1,而不是指针p的值加1 */
2.3.3 同类指针相减运算
格式:指针 - 指针
相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位(注意:非字节数)
。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
返回的值属于 ptrdiff_t
类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件 stddef.h 里面。
举例1:
int main() {
short *ps1;
short *ps2;
ps1 = (short *) 0x1234;
ps2 = (short *) 0x1236;
ptrdiff_t dist = ps2 - ps1;
printf("%d\n", dist); // 1 相差2个字节正好存放1个 short 类型的值。
int *pi1;
int *pi2;
pi1 = (int *) 0x1234;
pi2 = (int *) 0x1244;
ptrdiff_t dist1 = pi2 - pi1;
printf("%d\n",dist1); //4 相差16个字节正好存放4个 int 类型的值。
return 0;
}