总结一句话:指针就是地址
指针的格式:
数据类型 *指针变量名 = 初始地址值;
数据类型是指针变量所指向变量数据类型。可以是 int、char、float 等基本类型,也可以是数组等构造类型。字符
*用于告知系统这里定义的是一个指针变量,通常跟在类型关键字的后面。比如, char * 表示一个指向字符的指针, float * 表示一个指向 float 类型的指针。此外,还有指向数组的指针、指向结构体的指针。1
int *p; //读作:指向int类型的指针”或简称“int指针”
这是一个指针变量,用于存储int型的整数在内存空间中数据的地址。
注意:
1、指针变量的名字是 p,不是 *p。
2、指针变量中只能存放地址,不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。
取址运算符:&
作用:
取出指定变量在内存中的地址语法格式:&变量;(p=&a)
指针=&变量(只是表示这个指针指向这个变量的地址,变相说明这个地址存储的内容,也给了这个指针)
举例:
1
2
3int num = 10;
printf("num = %d\n", num); // 输出变量的值。 num = 10
printf("&num = %p\n", &num); // 输出变量的内存地址。&num = 00000050593ffbbc说明:
1、在输出取址运算获得的地址时,需要使用“%p”作为格式输出符。
2、这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)。
将变量的地址赋值给指针变量:
1
2
3int num = 10;
int *p; //p为一个整型指针变量
p = #1
2
3int a,*p;
p=&a;
以后p也就代表着&a;
指针变量的赋值:
指针变量中只能存放地址(指针),不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。
C语言中的地址包括位置信息(内存编号,或称纯地址)和它所指向的数据的类型信息,即它是“
带类型的地址”。所以,一个指针变量只能指向同一个类型的变量,不能抛开类型随意赋值。- char* 类型的指针是为了存放 char 类型变量的地址。
 - short* 类型的指针是为了存放 short 类型变量的地址。
 - int* 类型的指针是为了存放 int 类型变量的地址。
 
在没有对指针变量赋值时,指针变量的值是不确定的,可能系统会分配一个未知的地址,此时使用此指针变量可能会导致不可预料的后果甚至是系统崩溃。为了避免这个问题,通常给指针变量
赋初始值为0(或NULL),并把值为0的指针变量称为空指针变量。(野指针)1
2
3
4
5
6
7
8int main() {
int num = 10, *ptr;
ptr = #
printf("%d\n",num);
scanf("%d", ptr); //等价于scanf("%d", &num);
printf("%d\n",num);
return 0;
}
取值运算符:*(就是解引用):
作用:根据一个给定的内存地址取出该地址对应变量的值
语法格式:* 指针表达式;(*p=5)
“* 指针=指针指定的变量的值”(前面加上*表示这个指针等于这个变量的值)
“
*”不同于定义指针变量的符号,这里是运算符。“指针表达式”用于得到一个内存地址,与“*”结合以获得该内存地址对应变量的值。1
2
3int a=5,*p;
p=&a;
printf("%d\n",*p);//等价于*(&a) 先得到地址,再进行求值
举例:
1
2
3
4
5
6
7
8
9
10
11
12int 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;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14int 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;
}通过指针变量修改指向内存地址位置上的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14int 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;
}
& 运算符与 * 运算符:
&运算符与*运算符互为逆运算1
2int i = 5;
if (i == *(&i)) // 正确&*p的含义:“&”和“*”两个运算符的优先级别相同,但按自右而左方向运算。因此,&*p与&a相同,即变量a的地址。- 如果有
p1 = &*p;它的作用是将&a(a的地址)赋给p1,如果p1原来指向 b,经过重新赋值后它已不再指向b了,而指向了a。 
*&a的含义:- 先进行
&a运算,得a的地址,再进行*运算。*&a和*p的作用是一样的,它们都等价于变量a。即*&a与 a 等价。 
- 先进行
 
指针与数组:(解释)
C语言规定:数组的名字是数组的首地址,即第0个元素的地址。如&a[0]
**注意:***p和a[5],p和a不同的是:p是指针变量,a是个常量,所以可以用等号给p赋值,但是不能用等号给a赋值
如:p=&a[0]正确;a=&b[0];错误
对于长度是 N 的一维数组 a,当使用指针 p 指向其首元素后,即可通过指针 p 访问数组的各个元素
&数组名[某数] 相当于 数组名+某数
数组名[某数] 相当于 *(数组名+某数)
1
2
3
4
5
6
7
8int a[5],*p;
p=a;//数组的首地址给p
p+1也就等与a[1]的地址
*(p+1)也就等于a[1]的值
a[0]用 *p 表示
a[1]用*(p+1)表示
a[i]用*(p+i)表示
指针的运算:
指针本质上就是一个无符号整数,代表了内存地址。除了上面提到的取址运算外,指针还可以与整数加减、自增自减、同类指针相减运算等。
指针与整数值的加减运算:
格式:指针±整数
指针与整数值的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动)。指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。
比如:变量a、b、c、d和e都是整型数据int类型,它们在内存中占据一块连续的存储区域。指针变量p指向变量a,也就是p的值是0xFF12,则:

说明:指针p+1并不是地址+1,而是指针p指向数组中的下一个数据。比如,int *p,p+1表示当前地址+4,指向下一个整型数据。
指针的自增、自减运算:
- 针对指针的增加或减少指的是内存地址的向前或向后移动
 - 当对指针进行
++时,指针会按照它指向的数据类型字节数大小增加,比如 int * 指针,每++一次, 就增加4个字节。 - 当对指针进行
--时,指针会按照它指向的数据类型字节数大小减少,比如 int * 指针,每--一次, 就减少4个字节。 
同类指针相减运算:(相加是非法的)
格式:指针 - 指针
相同类型的指针允许进行减法运算,返回它们之间的距离,即
相隔多少个数据单位(注意:非字节数)。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。1
2
3
4
5
6
7
8int main() {
short *ps1;
short *ps2;
ps1 = (short *) 0x1234;
ps2 = (short *) 0x1236;
ptrdiff_t dist = ps2 - ps1;
printf("%d\n", dist); // 1 相差2个字节正好存放1个 short 类型的值。两个指针相减,通常两个指针都是指向同一数组中的元素才有意义。结果是两个地址之差除以数组元素的长度。不相干的两个变量的地址,通常没有做减法的必要。

指针间的比较运算:
指针之间的比较运算,比如 ==、!= 、<、 <= 、 >、 >=。比较的是各自的内存****地址的大小,返回值是整数 1 (true)或 0 (false)。
1
2
3
4
5
6
7
8int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
printf("%d\n",p1 > p2); //0
printf("%d\n",p1 < p2); //1
printf("%d\n",p1 == p2); //0
printf("%d\n",p1 != p2); //1
野指针:
野指针:就是指针指向的位置是不可知(
随机性,不正确,没有明确限制的)。形成野指针的原因:
指针使用前未初始化:
指针变量在定义时如果未初始化,
其值是随机的,此时操作指针就是去访问一个不确定的地址,所以结果是不可知的。此时p就为野指针。1
2
3
4
5
6int main() {
int *p;
printf("%d\n",*p);//错误 未初始化 直接访问
return 0;
}
指针越界访问:
当i=10时,此时
*p访问的内存空间不在数组有效范围内,此时*p就属于非法访问内存空间,p为野指针。
指针指向已释放的空间
野指针的避免:
- 定义指针的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。即
 - int *p = NULL;
 - 赋为 NULL 值的指针被称为
空指针,NULL 指针是一个定义在标准库 <stdio.h>中的值为零的常量 #define NULL 0 
二级指针:(多重指针)
指针的指针:即指针的地址,先定义一个指针变量,指针变量本身也有自己的地址,从而拿另一个指针来存储这个指针的地址。简单来说,
二级指针就是一个指针变量存储的值是另外一个指针变量的地址。通俗来说,二级指针就是指向指针的指针。一个指针p1记录一个变量的地址。由于指针p1也是变量,自然也有地址,那么p1变量的地址可以用另一个指针p2来记录。则p2就称为
二级指针。格式:数据类型 **指针名; 如:int **p;

- 进而推理,会有int ***pppa = &ppa; 等情况,但这些情况一般不会遇到。
 
指针与数组:(专题)
- “
*“,称为解引用符号,其作用与&相反。 - “
*“,后面只能跟指针变量(即地址),”&“后面跟的是普通变量(包括指针变量)。 
一维数组与指针:
可以用一个指针变量指向一个数组元素。

如果指针变量p的初值为
&a[0],则:p+i和a+i就是数组元素a[i]的地址。或者说,它们指向a数组序号为i的元素。*(p+i)或*(a+i)是p+i或a+i所指向的数组元素的值,即a[i]的值。
指向数组元素的**
指针变量**也可以带下标,如p[i]。p[i]被处理成*(p+i),如果p是指向一个整型数组元素a[0],则p[i]代表a[i]。但是必须弄清楚p的当前值是什么:如果当前p指向a[3],则p[2]并不代表a[2],而是a[3+2],即a[5]。&数组名:
1
2
3int arr[5];
printf("%p\n", arr); //000000000034fa50
printf("%p\n", &arr); //000000000034fa50虽然输出结果是一样的,但是arr表示arr[0]元素的地址,而&arr则表示的是arr数组的地址
二维数组与指针:
数组名[数1][数2] 等价于 *(数组名[数1]+数2)
如:a[1][2]==*(a[1]+2)
&数组名[数1][数2] 等价于 数组名[数1]+数2
如:&a[1][2]==a[1]+2
获取数组元素值的三种表示形式:
- 1) 
a[i][j]下标法 - 2) 
*(a[i]+j)用一维数组名 - 3) (
(a+i)+j)用二维数组名 
- 1) 
 设有一个二维数组 a 定义为:
1
2
3int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};二维数组 a,可视为三个一维数组:a[0]、a[1]、a[2];而每个一维数组又是分别由 4 个元素组成。首先,理解如下的操作:
1
2
3
4
5printf("%d\n",a[0][0]); //二维数组中元素a[0][0]的值
printf("%p\n",&a[0][0]); //二维数组中元素a[0][0]的值对应的地址
printf("%p\n",a[0]); //二维数组中a[0][0]的地址
printf("%p\n",a); //二维数组中a[0]的地址
printf("%p\n",&a); //二维数组a的地址

总结:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18&a:二维数组a的地址
a: 二维数组中a[0]的地址
a[0]:二维数组中a[0][0]的地址
讨论:a[0][0]相关的
a[0][0]的地址:&a[0][0],a[0],*a,
a[0][0]的值: a[0][0],*(a[0]),**a,
讨论:a[1]相关的
a[1]的地址:&a[1],a + 1
讨论:a[1][0]相关的
a[1][0]的地址:&a[1][0],a[1],*(a+1)
a[1][0]的值:a[1][0],*a[1],*(*(a+1))
讨论:a[1][2]相关的
a[1][2]的地址:&a[1][2],a[1]+2,*(a+1)+2
a[1][2]的值:a[1][2],*(a[1]+2),*(*(a+1)+2)注意:如果 a 是二维数组,则 a[i]代表一个数组名, a[i]并不占内存单元,也不能存放 a 数组元素值。它只是一个地址。所以:a、a+i、a[i]、(a+i)、(a+i)+j、a[i]+j 都是地址。
使用指针变量访问:
设 p 是指针变量,若p 指向数组首元素,即
p = a[0]p+j 将指向 a[0] 数组中的元素
a[0][j]。对于二维数组
a[M][N]来讲,由于 a[0]、a[1]、… 、a[M-1]等各行数组在内存中是依次连续存储,则对于 a 数组中的任一元素a[i][j]:- 地址表示:
p+i*N+j - 值表示:
*(p+i*N+j)、p[i*N+j] - 数组在内存当中是连续存放的,不论是几维数组。所以在公式二维数组当中 i*N 就是把前面的有几行,一行有几个元素都算上,再加上本行的第 j 个元素
 
- 地址表示:
 举例:
1
2
3
4
5
6int b[4][3] = {{10, 20, 30},
{40, 50, 60},
{70, 80, 90},
{100, 110, 120}};
int *p = b[0];- 则:元素 
b[1][2]对应的地址/指针、元素值为: 
1
2
3printf("b[1][2]对应的地址/指针为:%p\n",p+1*3+2);
printf("b[1][2]对应的值为:%d\n",*(p+1*3+2));
printf("b[1][2]对应的值为:%d\n",p[1*3+2]);- 则:元素 
 
数组指针:(是个指针)
- 当指针变量里存放一个数组的首地址时,此指针变量称为指向数组的指针变量,简称
数组指针。 - 二维数组a中。a+1指向下一个元素。即下一个一维数组,即下一行
 - 整型指针: int * pint; 能够指向整型数据的指针。
 - 浮点型指针: float * pf; 能够指向浮点型数据的指针。
 - 数组指针:int (*p)[5] 能够指向数组的指针。
 
指针数组:(是个数组)
数组是用来存放一系列相同类型的数据,当然数组也可以用来存放指针,这种用来
存放指针的数组被称为指针数组,它要求存放在数组中指针的数据类型必须一致。指针数组:是一个数组,其中的每个元素都是一个指针。也就是说,数组中的每个元素存储的是某个变量的地址。
格式:数据类型 *指针数组名[大小];
举例:int *arr[5];
arr是一个数组,有5个元素,每个元素是一个整型指针,需要使用下标来区分。


字符指针:
一个字符串,可以使用
一维字符数组表示,也可以使用字符指针来表示。字符数组由若干个元素组成,每个元素放一个字符
字符指针变量中存放的是地址(字符串/字符数组的首地址),绝不是将字符串放到字符指针变量中。
举例:
1
2char str[] = "hello tom"; //使用字符数组
char * pStr = "hello tom"; //使用字符指针图示:

两种方式对比:
数组名是一个常量,在定义好数组以后,不可以给数组重新赋值为一个新的数组,但是可以通过角标的方式获取或修改指定索引位置上的元素值。
指针是一个变量。可以多次重新赋值
对已声明好的字符数组,只能一一对各个元素赋值,不能用以下错误方法对字符数组赋值,而对字符指针变量,采用如下方式赋值是可以的。
1
2
3
4
5char str[14];
str[0] = 'i'; //正确
str = "hello Tom"; //错误
char * pStr = "hel";
pStr = "hello tom"; //正确一个字符数组,因为它有确定的内存地址,所以字符数组名是一个
常量。而定义一个字符指针变量时,它在指向某个确定的字符串数据的情况下,也可以多次重新赋值。
字符串数组和字符串指针表示:
如果一个数组的每个成员都是一个字符串,则构成了字符串数组。字符串数组有两种表示方式:
① 二维字符数组;②字符指针数组。方式一:使用二维字符数组
1
char fruit[][7]={"Apple","Orange","Grape","Pear","Peach"}; //上一章5.6节举例4
1
2
3
4
5
6
7
8
9char weekdays[7][10] = { //行数7也可以省略
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};- 字符串数组,一共包含7个字符串,所以第一维的长度是7。其中,最长的字符串的长度是10(含结尾的终止符 \0 ),所以第二维的长度统一设为10。
 - 数组的第二维,长度统一定为10,有点浪费空间,因为大多数成员的长度都小于10。解决方法就是把数组的第二维,从字符数组改成字符指针。所以说使用字符指针数组比使用二维字符数组可读性更好
 
方式二:字符指针数组
1
2
3
4
5
6
7
8
9char* weekdays[7] = { //7也可以省略
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};- 上面的字符串数组,其实是一个一维数组,成员就是7个字符指针,每个指针指向一个字符串(字符数组)。
 - 类似的:定义表示颜色的指针数组 colors,存储“red”、“yellow”、“blue”、“white”、“black” 5 种颜色。
 
1
char *colors[5] = {"red", "yellow", "blue", "white", "black"};
两种方式的遍历是一样的:
1
2
3for (int i = 0; i < 7; i++) {
printf("%s\n", weekdays[i]);
}
指向固定长度数组的指针变量:
- 定义格式:(*标识符)[一维数组元素个数];
 - 例如:定义一个指针变量 p,它指向包含有 4 个元素的一维数组。
 
- int (*p)[4];
 
说明:p先和*结合,说明p是一个指针变量,指向一个大小为4的整型数组。
注意:此时定义的是一个指针变量,并非是一个指针数组。(*p 必须放在括弧内,否则就变成了定义指针数组。)
由于 p 是指向有 4 个整型元素的一维数组的指针变量,因此,p+1 是将地址值加上 4*4,即指向下一个一维数组。
举例:
1
2
3
4
5int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int (*q)[4];
q = a;
 q 为二维数组第 0 行首地址,与 a 相同;
 q+1 为二维数组第 1 行首地址,与 a+1相同;
 q+2 为二维数组第 2 行首地址,与 a+2相同;
 *(q+i)为二维数组第 i 行第 0 列元素的地址,与*(a+i)相同;
 *(q+i)+j 为二维数组第 i 行第 j 列元素的地址,与*(a+i)+j 相同;
 *(*(q+i)+j) 为二维数组第 i 行第 j 列元素值,与*(*(a+i)+j)相同,即 a[i][j]。
容易混淆的概念:
- 指针数组:
- 定义:是个数组,有若干个相同类型的指针构成的集合
 - int *p[5],表示数组p有5个int *类型的指针变量构成,分别是p[0]~p[4]
 
 - 数组指针:
- 定义:是个指针,指向一个数组,加1跳一个数组
 - int (*p)[5],p是一个指针,p是一个数组指针,p+1表示指向下个数组跳5个整形
 
 - 指针的指针:(二级指针)
- int **p;p是指针的指针
 - int *q;
 - p=&q;用p来存储指针q的地址