一、什么是指针
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
指针也就是内存地址,指针变量是用来存放内存地址的变量。
每个变量都会占据一定空间,存在一个地址,&运算符可以访问地址。
eg:
int a = 3;
int *p = &a;
printf("a的地址为:%p \n",p);
//结果为:a的地址为:0x7ffeeaae08d8
- 地址:按字节为单位进行编号;
- 指针:就是地址
- 指针变量:用来保存地址的变量;
- 指针的目标变量:指针指向的变量;
二、指针的一般形式
<存储类型><数据类型>
* <
指针变量名>
存储类型
:
存储类型和指针变量自身有关,和指针的目标变量无关
;
数据类型
:
由指针的目标变量来确定
;
指针变量名
:
保存目标变量的地址
;
eg
:
int
var
;
int *
p
;
//
一个可以指向整型地址的指针变量
;
p
= &
var
;
// p
指向了
var
p
--->
指针变量
&
var
-->
指针
var
-->
指针的目标变量
;
(
在不影响理解的情况下,地址、指针、指针变量统称为指针
)
- &: 在一个变量的基础上取地址可以得到变量的地址;
- *:在地址的基础上取*可以得到这个地址上的内容;
- & * 互为逆运算
三、指针的初始化
<存储类型><数据类型>
* <
指针变量名>
= <
地址量
>
;
eg
:
int
var
=
10
;
int *
p
= &
var
;
&
var
==
p
== &
(
*
p
)
var
== *
p
== *
(
&
var
)
&
p
: 表示指针变量自身的地址,和目标变量无关
;
四、指针的大小
指针所占的字节数和操作系统有关,和数据类型没有关系
;
32
位 指针变量占
4
字节;
64
位 指针变量占
8
字节;
五、空指针和野指针
- 空指针:是指指针变量的内容为零的状态; int *p = NULL;
- 野指针:一个没有固定指向的指针;要避免野指针的出现;
六、指针的运算
算术运算
+ - ++ --
p + 1 --> 表示指针变量向地址值大的方向偏移一个单位
p++
++p
p-1 --> 表示指针变量向地址值小的方向偏移一个单位
p--
--p
p+q //error
p - q --> 表示两个指针之间相差几个单位
指针偏移的实际地址量:
p +/- n * (sizeof(数据类型))
关系运算
> < == != >= <=
在比较的时候要保证指针的类型要一样;
赋值运算
1.将一个变量的地址赋值给一个具有相同类型的指针变量;
eg:
char ch;
char *p;
p = &ch;
2.将一个指针变量赋值给一个具有相同类型的指针;
char *p = &ch;
char *q = p; //p 和 q 都指向ch
3.将一个数组的首地址赋值给一个具有相同类型的指针变量;
int arr[5] = {0};
int *p = arr; //p指向了数组的首地址
七、指针和一维数组、字符串的关系
一维数组
int arr[32] = {0};
int *p = arr;
//数组第i个元素的地址
p + i == &arr[i] == arr + i
//数组的第i个元素
arr[i] == *(p+i) == *(&arr[i]) == *(&p[i]) == p[i] == *(arr+i)
//arr++ // error
p++ //表示p指向第二个元素的地址;
二维数组
eg:
用一级指针访问二维数组;
二维数组的理解:
int arr[2][3];
//可以看成是一个由arr[0] 和 arr[1] 两个一维数组组成;
arr[0] == *(arr + 0) //数组名 +下标 表示每个元素的地址
arr[1] == *(arr + 0)
arr[0] 是第一个一维数组:
元素: 数组名[下标] *(数组名 + 下标)
arr[0][0]==*(arr[0] + 0)== *(*(arr + 0)+0)==(*
(arr+0))[0]
arr[0][1]==*(arr[0]+1)==*(*(arr+0)+1)==(*(arr+0))[1];
arr[0][2]==*(arr[0]+2)== *(*(arr+0)+2)==(*(arr+0))
[2];
arr[1] 是第二个一维数组;
总结:
arr[i][j]:
== *(arr[i]+j) == *(*(arr+i)+j) == (*(arr+i))[j];
字符串
1.当一个指针要指向字符串的时候,字符串应该有一个存储空间来保存,
将字符串的首地址赋值给指针变量;
eg:
char buf[] = "hello world";
char *p = buf;
eg:
char buf[32] = {0};
char *p = buf;
// strcpy(p, "hello world");
scanf("%s", p);
//将字符串存储到p指向的buf空间中;
eg:
/******* error ***************
char *p;
scanf("%s", p);
//相当于将输入的字符串存储到一片没有申请的空间中
//应该让p先指向一片空间
puts(p);
*/
char buf[32] = {0};
char *p = buf;
scanf("%s", p);
2.一个指针变量初始化为字符串常量时,
用指针来访问字符串的时候只允许读不允许修改;
eg:
const char *p = "hello";
//char *p; p = "hello";
*p = 'H'; //error
//相当于指针p指向字符串常量"hello"的首地址, *p == 'h';
//这个字符串保存在静态数据区的常量区,不允许修改只允许读
八、二级指针、行指针
一个存储指针的指针;
eg
:
int
var
=
10
;
int *
p
= &
var
;
int **
q
= &
p
;
q
== &
p
;
*
q
==
p
== &
var
;
**
q
== *
p
==
var
;
int
var1
;
*
q
= &
var1
;
通过指针修改目标的值
(
p
的值
)
二维数组名叫做行地址,二维数组名加一表示偏移一行的元素;
注意:
二维数组不能用二级指针指向,数据类型不一样;
二维数组可以被行指针指向;
行指针的一般形式:
<存储类型> <数据类型> (*指针变量名)[列数];
eg:
int arr[3][2] = {0};
int (*p)[2] = arr;
第i行的第j列的元素:
arr[i][j] == *(arr[i]+j) == *(*(arr+i)+j)
== (*(arr+i))[j]
== p[i][j] == *(p[i]+j) == *(*(p+i) + j)
== (*(p+i))[j]
九、指针数组
指针数组本质是数组,由若干个相同类型的指针变量组成的集合;
一般形式:
<存储类型> <数据类型> * <指针数组名>[数组的元素个数];
eg:
int arr[3][2];
int *parr[3] = {arr[0], arr[1], arr[2]}
每个元素 arr[i][j]:
*(*(parr + i) + j)
parr: 数组名,地址常量;
parr[i] 表示每个指针变量,占8字节;
指针数组的大小:
8 * 数组元素个数; //64位
访问指针数组可以用二级指针来访问;
eg:
int **pp = parr;
arr[i][j] == *(*(parr + i) + j) = *(*(pp + i) + j);
十、const和void指针
const int var;
const 修饰变量,让变量常量化;
const 修饰的局部变量可以通过指针间接修改;
eg1:
1.不能通过指针修改目标的值,但可以修改指针的指向;
int var = 10, var2 = 20;
const int *p = &var; //int const *p = &var;
p = &var2; //true
// *p = 100; //false
2. 不能修改指针的指向,但可以通过指针修改目标的值;
int var = 10, var2 = 20;
int * const p = &var; //必须初始化
//p = &var2; //false
*p = 100;
3.既不能修改指针的指向,也不能通过指针修改目标的值;
int var = 10, var2 = 20;
//int const * const p = &var;
const int * const p = &var;
//p = &var2; //false
// *p = 100; //false
一般形式为:
void *<指针变量名称> ;
对于void型的指针变量,实际使用时,一般需通过强制类型转换才能使void型指针变量得到具体变量或数组地址。在没有强制类型转换之前,void型指针变量不能进行任何指针的算术运算。