指针进阶详解(一)

一.指针回顾
1,指针就是个变量,用来存放地址,地址唯一标识一块内存空间
2.指针的大小是固定的4/8字节(32位平台/64位平台)

#include<stdio.h>
void test(int a[])
{
    
    
	printf("%d",sizeof(a)/sizeof(a[0]);
int main()
{
    
    
	int a[10]={
    
    0};
	test(a);
}

//若为32位平台,运行结果为1;64位则为2

3.指针是有类型的,指针的类型决定了指针的±整数的步长,指针解引用时的权限
4.指针的运算
二.指针进阶
1.字符指针

三种表示方法

#include<stdio.h>
int main()
{
    
    
	char ch='w';
	char* pc=&ch;
	return 0;
}
#include<stdio.h>
int main()
{
    
    
	char arr[]="abcdef";
	char* pc=arr;
	printf("%s\n",arr);
	printf("%s\n",pc);
}
#include<stdio.h>
int main()
{
    
    
	const char* p="abcdef"; //"abcdef"为常量字符串,注意不能修改
	printf("%c\n",*p);
	printf("%s\n:,p);	
}

接下来看一道题

#include<stdio.h>
int main()
{
    
    
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	if (arr1 == arr2)
	{
    
    
		printf("hehe\n");
	}
	else
	{
    
    
		printf("haha\n");
	}
	if (p1 == p2)
	{
    
    
		printf("hehe\n");
	}
	else
	{
    
    
		printf("haha\n");
	}
	// 打印结果 haha
	//		   hehe	

用指针方式表示地址相同是因为字符串存储在内存的常量区(其内容不能被修改),因此p1和p2里都存的是字符串常量区的地址
而用数组表示则是将存储在常量区的字符串拷贝到栈区的数组中(栈区的数据是可以被改变的),因此这两个数组的地址是不相同的
可能有的同学不太了解内存四区,可以看下这篇文章更好的理解
内存四区模型(栈,堆,代码,全局)

2.指针数组
指针数组是数组,用来存放指针

#include<stdio.h>
int main()
{
    
    
	int arr1[] = {
    
     1,2,3,4,5 };
	int arr2[] = {
    
     2,3,4,5,6 };
	int arr3[] = {
    
     3,4,5,6,7 };
	int* parr[] = {
    
     arr1,arr2,arr3 };
	// []的优先级要高于 *号,parr先与[]结合,说明parr是个数组,里面存的元素类型是int *类型
	int i = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < 5; j++)
		{
    
    
			printf("%d ", *(parr[i] + j));
		}
	}
	printf("\n");
}
//打印结果  1 2 3 4 5
//         2 3 4 5 6
//         3 4 5 6 7

parr[i]分别对应三个指针变量,它们分别存放的是arr1,arr2,arr3首元素的地址
parr[i]+j则表示数组里每个元素的地址,解引用后得到其内容

3.数组指针
数组指针是指针,是指向数组的指针

int arr[10]={
    
    0};
int(*p)[10]=&arr;

解释:p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整数的数组,所以p是一个指针,指向一个数组,叫数组指针
这里要注意,[]的优先级要高于 *号,所以必须要加上括号来保证p先和 *结合.

接下来我们看一下容易混淆的问题:
&数组名vs数组名

对于下面的数组

int arr[10];

arr和&arr分别是什么呢?
我们知道arr是数组名,数组名表示数组首元素的地址
那么&arr到底是什么呢?

我们来看下面一段代码

#include<stdio.h>
int main()
{
    
    
	int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
}

由结果我们可以看到,数组名和&数组名打印的地址是一样的.
难道两个是一样的吗?

我们再看一段代码

#include<stdio.h>
int main()
{
    
    
	int arr[10] = {
    
     0 };
	printf("arr=%p\n", arr);
	printf("&arr=%p\n", &arr);
	printf("arr+1=%p\n", arr + 1);
	printf("&arr+1=%p\n", &arr + 1);
}

根据上面的代码我们可以发现,虽然&arr和arr值是一样的,但意义并不相同
实际上,&arr表示的是数组的地址,并非数组首元素的地址(这里要仔细理解一下)
数组的地址加1,跳过整个数组的大小,所以&arr+1和&arr的差值为40.

4.数组指针的使用

来看这样一段代码

#include<stdio.h>
int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", (*p)[i]); // *p==arr
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", *(*p + i));
	}
	printf("\n");
}
// 1 2 3 4 5 6 7 8 9 10
// 1 2 3 4 5 6 7 8 9 10

这里关键是要理解*p==arr ,我们对一个数组的地址进行解引用相当于拿到了数组名

#include<stdio.h>
void print2(int(*p)[5], int x, int y)
{
    
    
	int i = 0;
	for (i = 0; i < x; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < y; j++)
		{
    
    
			printf("%d ", p[i][j]);
			printf("%d ", *(p[i] + j));
			printf("%d ", *(*(p + i) + j));
			printf("%d ", (*(p + i))[j]);
		}
		printf("\n");
	}
}
int main()
{
    
    
	int arr[3][5] = {
    
     {
    
    1,2,3,4,5},{
    
    2,3,4,5,6},{
    
    3,4,5,6,7} };
	print2(arr, 3, 5);
}

我们知道,数组名是数组首元素的地址,那么二维数组名在传参时该用什么类型指针呢?

其实我们可以将这个二维数组想象成一个一维数组,将每一行当成一个元素,那么我所写的数组就有三个元素,每一个元素是一个一维数组,第一个元素的地址即是一个int[5]类型的一维数组的地址。所以我们在传参的时候用数组指针来接收数组的地址.

以上四种打印方式都是等价的,第一种很好理解,下面我们看下面三种三种方式

*(p[i]+j)

p是第一行一维数组的地址,p[i]则是将第(i+1)行的数组地址解引用,上面已经提到对数组地址解引用相当于拿到了数组名,数组名又是每行数组首元素的地址,因此p[i]+j就是每个元素的地址,再次解引用得到内容.

*(*(p+i)+j)

p是第一行一维数组的地址,前面提到,数组地址加1,跳过整个数组大小,则(p+i)表示每行一维数组的地址,解引用后*(p+i)则是每行一维数组的数组名,即每行一维数组首元素地址,*(p+i)+j则表示每个元素地址,解引用后得到其内容

通过以上两个对比,我们不难发现:
p[i]等价于*(p+i)

(*(p + i))[j]

*(p + i)表示每行一维数组首元素的地址, 则此方式也可得到二维数组中的每个元素.

猜你喜欢

转载自blog.csdn.net/DR5200/article/details/112299601