一、指针的概念

在间接访问中通过另一变量中存储的地址能够找到所需变量,可以认为改地址指向目标变量。

变量的指针就是变量的地址,指针的类型就是地址类型,存放指针的另一变量就是指针类型的变量(简称指针变量)

(此处需要注意的是,定义指针变量时,必须指明它所指向的变量的类型)

指针就是间接访问的一种形式

直接访问:直接通过变量名访问变量

间接访问:将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。