一、指针的概念
在间接访问中通过另一变量中存储的地址能够找到所需变量,可以认为改地址指向目标变量。
变量的指针就是变量的地址,指针的类型就是地址类型,存放指针的另一变量就是指针类型的变量(简称指针变量)
(此处需要注意的是,定义指针变量时,必须指明它所指向的变量的类型)
指针就是间接访问的一种形式
直接访问:直接通过变量名访问变量
间接访问:将a的地址储存在pa中,访问实现从pa中取出a的地址,再按照地址进行访问
int main()
{
int a = 10;
int* pa = &a;
printf("%d", a);
printf("\n%d", *pa);
}
输出结果:
10
10
如图所示,打印该变量与打印指向该变量的指针所显示的数值是相等的。
二、指针变量
1.取值符号*与取址符号&
&、单目运算符,规定&只能取内存中变量的地址
*、单目运算符,联合指针组合使用,获取地址所指向变量的量,通过指针间接访问变量。
2,指针变量的定义
基本格式 数据类型*指针变量名【=&变量名】
基类型名:该指针变量所指向变量的类型名
【=&变量名】:可选项,将该变量名对应的地址作为初始值赋给所定义的指针变量
指针变量指向的类型由基类型名确定,基类型名间接确定了存储数据的字节数与存储形式。
指针类型决定了指针进行解引用操作时,能访问的空间大小
如:
int*p;*p能访问4个字节
char*p;*p能访问1个字节
double*p;*p能访问8个字节
(存储地址的时候所存储的地址是相同的,但是在解引用操作的时候有差距)
3,指针的运算
(1)当指针变量指向数组的时候,指针变量加减一个整数m,表示指针变量向前(或向后)移动m个元素(不是m个字节)
指针变量每每增减1,地址字节数的增减量d等于基类型字节数
(2)同类型指针可以进行相减,得到二者相差的元素个数
(3)同类型指针进行比较,设有空指针NULL,不指向任何存储单元,可以赋给任何类型的指针变量,也可以进行比较
4,指针变量与数组
先将数组名赋给指针,通过增减运算使它指向不同的元素,数组元素中的“[]”为变址运算符
数组名可以不用取址符号,因为数组名本身就是数组在内存中存放的首地址
数组名表示数组首元素地址
&数组名表示整个数组的地址
int main()
{
int a = 10;
int arr[10];
int* p = arr;
printf("%p\n", arr);
printf("%p\n", &arr);
}
输出结果:
000000FA0D74F898
000000FA0D74F898
此时显示出来地址相同,但进行运算的时候是有区别的
此时对他们进行指针的增减运算
int main()
{
int a = 10;
int arr[10];
int* p = arr;
printf("%p\n", arr);
printf("%p\n", arr + 1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
}
输出结果:
000000BF5BEFFC48
000000BF5BEFFC4C
000000BF5BEFFC48
000000BF5BEFFC4C
000000BF5BEFFC48
000000BF5BEFFC70
由此可见arr与&arr[0]所取到的地址是相同的,对其进行加减运算的时候所呈现的结果也相同,而&arr所代表的是整个数组的地址,只不过是以首元素的地址代表整个元素的地址,所以对他进行+1的时候,所呈现的结果是+40
由上可知,地址+1是对应在地址字节数相应增加,而arr定义的时候确定为int类型,所以地址在进行运算的时候字节数+4,但是在&arr时,定义arr是确定arr中有10个元素,arr+1的时候直接跳过数组中的所有元素,所以一次性跳过了4*10个字节。
&arr与arr算是指针变量中的特例,&arr代表整个数组的地址,而arr代表首元素的地址。
同时,上文提到的[]在指针中为变址运算符
由图可知,图中的*(p+4)是可以等价于p[4]的,在变址运算符中,p[j]=*(p+j)
其中p[j]被称为指针变量的下标法,而*(p+j)是指针法。
int main()
{
int a = 10;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
printf("%d\n", *(p + 4));
printf("%d\n", p[4]);
}
输出结果:
5
5
5、野指针
提到指针的正规用法就不得不说一下“违规操作”了
(1)指针变量没有被赋予地址,指针指向的位置是随机的,没有进行初始化,指针地址默认为随机值(越界到不能管理的范围内,就被称为野指针)
(2)指针越界访问
此时的指针就出现了明显的越界,arr中一共10个元素,但是在打印的时候访问到了arr所申请内存之外的内存部分,此时会出现错误
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
printf("%d\n", p[11]);
}
输出结果:
-858993460
此时的指针就是越界指针,访问的过程也就是越界访问。
(3)指针指向空间已经被释放
此时虽然正常返回了a的值,但实际上却是一个错误用法,在函数中创建了a这个变量,但实际上a这个变量是一个临时变量,因为调用了函数test ,a才因此被创建,所以当函数结束的时候,a也会随之销毁,就算是此处成功打印出了a的值,但是实际上,该用法并不正确,当使用的时候,编译器会出现警告,当下方打印的时候,实际上属于a的那一部分内存已经销毁了,所以指针p的指向没有意义。(函数内部的变量基本都为临时变量,用指针比较容易出现野指针)
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d\n", *p);
}
输出结果:10
此时,a的内存已经被释放,但仍想让此指针合法存在,应将其定义为NULL
如果想规避野指针的情况,可以对其先进行检测
利用语句
if(p!=NULL)
只有不为NULL才能正常进行
6、指针数组
每个数组元素为指针类型变量,该数组为指针数组
int main()
{
int a = 1, b = 3, c = 4, d = 5,i;
int* arr[4] = { &a,&b,&c,&d };
for (i = 3; i >= 0; i--)
{
printf("%d\n", arr[i]);
printf("%d\n", *arr[i]);
}
}
输出结果:
0000008B218FF684
5
0000008B218FF664
4
0000008B218FF644
3
0000008B218FF624
1
如图所示,可以用数组存储地址,也可以对其进行打印,如图中int*arr的数组就是指针数组。
指针数组的使用却并不为了单纯的存放变量的地址
可以利用指针数组打印出各个存放在指针数组中的数组的元素,存储在指针数组中的依旧是数组的首元素地址。
int main()
{
int j, i;
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };
int* p [3]= { arr1,arr2,arr3 };
for (j = 0; j < 3; j++)
{
for (i = 0; i < 4; i++)
{
printf(" %d", *(p[j] + i));
}
printf("\n");
}
}
输出结果:
1 2 3 4
2 3 4 5
3 4 5 6
7、二级指针变量
基本格式:数据类型**指针变量名称=所赋予的地址
定义一个指向指针的二级指针变量,可以用其访问整型指针数组元素所指向的整型数据。
8、字符指针变量
顾名思义,字符指针变量就是指向字符的指针,而在下图的代码中,该指针指向的是字符串的首元素地址,但是在打印的时候却可以通过p中的首元素地址将字符串全部打印。这是一种用法。在此处进行打印的时候,已经将字符串整个视为一个常量。
但是同时,不能通过字符指针变量对字符串进行更改,因为在这个地方已经将字符串视为一个常量,不能通过指针对常量进行修改,这样是错误的用法。
int main()
{
char arr[] = "abcdef";
char* p = arr;
printf("%s", p);
}
也可以使用上文中提到的数组地址的特殊用法:
int main()
{
char arr[] = "abcdef";
char* p = arr;
printf("%s\n",&arr);
printf("%s",p);
}
二者所打印出来都是字符串的全部内容。前者是因为&arr代表了整个数组的位置,后者则是因为在此处已经将字符串视为了一个常量。
int main()
{
char arr[] = "abcdef";
char* p = arr;
printf("%c\n",*p);
}
输出结果:a
需要明确的是,这个时候指针所指向的元素仍旧是字符串中的首元素,此时单独对p取值打印所显示结果依旧是a。
对于区分常量字符串仍需要注意
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (arr1 == arr2)
printf("yes");
else
printf("no");
}
输出结果:no
此处arr代表的首元素的地址,对于两个arr来说,虽然存储内容相同,但是其所申请的地址并不相同,在此处if判断语句比较的是二者的首个元素地址,所以此处的结果是不相等。
而字符指针则大不相同 。
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";
if (p1 == p2)
printf("yes");
else
printf("no");
}
输出结果:yes
在这两个指针中,abcdef相对于指针而言就是常量,是p1与p2去对abcdef进行取用,而不是将abcdef这个字符串存储到p1与p2所申请的内存中,所以p1与p2在实质上指向的是同一个地址,就是常量abcdef的地址,所以在这个地方p1=p2。