C语言重点难点精讲C语言指针

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言重点难点精讲C语言指针相关的知识,希望对你有一定的参考价值。

指针是C语言中的一大重点,有关指针的理解一些基础性使用不在本篇文章介绍范围内,读者可以阅读站内其它博主的文章,写得都比较棒,本文主要是针对指针中的一些重点,难点做一些总结,以及最重要的是和数组的关系

一:指针入门

如果读者能够较深刻理解下面语句的含义,那么对于指针就算是达到入门的标准了

//第一组
int a=10;
int* p=&a;
//第二组
p=10;
int* q=p;
//第三组
*p=10;
int b=*p;
  • 第一组:定义一个整形变量a,并赋值10,然后定义一个指针变量p,用a的地址初始化
  • 第二组:把10赋值给指针变量p,此时指针p指向一个地址为10的地址,然后把p的内容同样赋值给一个同样类型的指针变量q
  • 第三组:*p表示解引用,将p所指向的空间里的内容改为10,然后赋值给变量b

二:数组入门

(1)数组的内存空间布局

数组是整体申请空间的,然后将地址最低的空间,作为a[0]元素


这就是为什么我们访问数组或者用指针访问数组时采用的是++,本质就是其元素排布时按照地址增大方向排布

(2)区分&arr[0]和&arr

首先我们需要搞清楚指针+1究竟是什么含义,看如下代码

int main()
{
	char* c = NULL;
	short* s = NULL;
	int* i = NULL;
	double* d = NULL;

	printf("%d\\n", c);
	printf("%d\\n\\n", c + 1);

	printf("%d\\n", s);
	printf("%d\\n\\n", s + 1);

	printf("%d\\n", i);
	printf("%d\\n\\n", i + 1);

	printf("%d\\n", d);
	printf("%d\\n\\n", d + 1);

}

其运行结果如下

因此,指针+1实际加上的是该指针类型的大小

回到正题

int main()
{
	char arr[10] = { 0 };
	printf("%p\\n", &arr[0]);
	printf("%p\\n", &arr[0]+1);

	printf("%p\\n", &arr);
	printf("%p\\n", &arr+1);
}
  • &arrsizeof(arr)arr表示整个数组,
  • &arr[0]:表示数组首元素的地址,因为[]的优先级更高
  • &arr[0]+1:由于是char类型的数组,所以+1
  • &arr+1:这是数组的地址,可以理解横跨整个数组

还有,当数组名做右值时,代表数组首元素的地址,本质等价于&arr[0];但是,数组名不可以充当左值,能够充当左值的,必须是有空间且可被修改的

三:指针和数组的关系

指针和数组没有关系,他们是完全不同的两套规范,只不过在操作上有交集

(1)以指针的形式访问和以数组形式访问

C语言中保存字符串中有两种形式,如下

int main()
{
	char* str = "abcdef";//str指针在栈上保存,“abcdef”在字符常量区,不可修改
	char arr[] = "abcdef";//整个数组都在栈上保存,可以被修改
}

1:分别以指针和以下标的方式访问指针

int len = strlen(str);
for (int i = 0; i < len; i++)
{
	printf("%c ", *(str + i));//以指针方式访问
	printf("%c ", str[i]);//以数组方式访问
}
printf("\\n");

1:分别以指针和以下标的方式访问数组

int len = strlen(arr);
for (int i = 0; i < len; i++)
{
	printf("%c ", *(arr+i));//以指针方式访问
	printf("%c ",arr[i]);//以数组方式访问
}
printf("\\n");

指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性,但注意它们不是同一个东西

(2)为什么C语言要这样设计?

我们知道数组传参时可以采用这种方式

void Show(int arr[], int num)
{
	for (int i = 0; i < num; i++) {
		printf("%d ", arr[i]);
	}
	printf("\\n");
}

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int num = sizeof(arr) / sizeof(arr[0]);
	Show(arr, num);
}

其中的sizeof(arr)/sizeof(arr[0])语句不能放入在函数内进行,否则将会只打印一个元素,相信初学者在这里也犯过很多错误。

指针和数组互通的好处之一就在这里,因为如果不互通,那么在传参时对于数组这样庞大的东西就会浪费非常多的额外空间,所以干脆在传参时将其降维成指针,直接让指针访问即可

那么既然这样,形参中的int arr其实就可以写作int* arr

void Show(int* arr, int num)
{
	for (int i = 0; i < num; i++) {
		printf("%d ", arr[i]);
	}
	printf("\\n");
}

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int num = sizeof(arr) / sizeof(arr[0]);
	Show(arr, num);
}

因此:所有的数组在传参时都会降维成指针——指向其内部类型的指针,一维数组当然就是对应数据类型的指针,二维数组就是一维数组的数组指针

四:指针数组和数组指针

指针数组: 它是一个数组,里面的元素均是指针,即int* p1[10]

数组指针: 它是一个指针,指向了一个数组,即int (*p2)[10]

  • []的优先级大于“*”
  • int* p1[10]可以理解为int* [10] p1
  • int (*p2)[10]可以理解为int[10]* p2
  • int (*p[4])[5][4]是一个数组,这个数组存放了四个数组指针p,分别指向含有5个int类型的数组

数组指针赋值时一定要注意

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

五:多维数组和多级指针

(1)二维数组

需要注意多维数组其内存空间布局仍然是线性连续递增的

int main()
{
	char c[4][3] = { 0 };
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("&a[%d][%d]:%p\\n", i, j, &c[i][j]);
		}
	}
}


就二维数组而言,我们认为:二维也可以看做一位数组,只不过其内部元素仍然是一维数组

因此下面这三种地址虽然值是相同的但是其含义完全不同

char c[4][3] = { 0 };
printf("%p\\n", &c);//这个“二维”数组的地址
printf("%p\\n", c);//“二维”数组中第一个元素,也即第一个数组的地址
printf("%p\\n", &c[0][0]);//“二维”数组第一个元素的第一个元素的地址


那么相应的+1操作也有各自的含义

char c[4][3] = { 0 };
printf("%p\\n", &c);
printf("%p\\n", &c+1);//直接越过12个元素
printf("\\n");
printf("%p\\n", c);
printf("%p\\n", c+1);//越过“1”个元素,也即1维数组,也即3个char
printf("\\n");
printf("%p\\n", &c[0][0]);
printf("%p\\n", &c[0][0]+1);//越过1个char

下面的练习题可以帮助你很好理解上面的概念

int a[3][4] = {0};

//求整个数组的大小
printf("%d\\n",sizeof(a));//(3×4)×4=48
//求单个元素的小
printf("%d\\n",sizeof(a[0][0]))//1×4=4
//二维数组第一个“元素”,即第一个一维数组的大小
printf("%d\\n",sizeof(a[0]))//4×4=16
//当sizeof()内部只含有一个数组名时表示整个数组,当内部数组名参与运算时表示单个元素
//因此这里就是第一个元素也即第一个数组的首元素地址进行偏移,仍然是一个指针
printf("%d\\n",sizeof(a[0]+1))//4
//第一个元素也即第一个数组的首元素偏移至第一个数组的第二个元素进行解引用,也即a[0][1]
printf("%d\\n",sizeof(*(a[0]+1)))//a[0][1],4
//表示该二维数组的第一个数组的地址,然后偏移至第二个数组的地址处,注意仍然是地址
printf("%d\\n",sizeof(a+1));//4
//这是第二个数组的地址,对齐解引用相当于第二个数组的数组名
printf("%d\\n",sizeof(*(a+1)));//4×4=16
//a[0]表示第一个数组,&a[0]第一个数组的地址,然后+1,偏移值第二个数组的地址处
printf("%d\\n",sizeof(&a[0]+1));//4
//同上,它是第二个数组的地址,然后解引用就是第二个数组的大小
printf("%d\\n",sizeof(*(&a[0]+1))); //4×4=16
//相当于*(a+0),于是表示第一个数组,然后解引用就是第一个数组的大小
printf("%d\\n",sizeof(*a)); //4×4=16
//第四个一维数组
printf("%d\\n",sizeof(a[3])); //16


其实以下写法是等价的

  • a[2]等价于*(a+2);
  • a[2][3]等价于*(*(a+2)+3)

(2)二级指针

准确理解以下例子的含义即可

int main()
{
int a = 10;
int* p = &a;
int** pp = &p;

p = 100;//将p指针的指向更改为100
*p = 100;//将p指针指向的空间也即变量a的内容改为100
pp = 100;//pp之前指向了一级指针,现在更改为指向100
*p = 100;//pp保存的是p的地址,因此现在相当于将p的指向改为了100
**pp = 100;//两次解引用就是修改变量a的内容100

}

以上是关于C语言重点难点精讲C语言指针的主要内容,如果未能解决你的问题,请参考以下文章

C语言重点难点精讲关键字精讲

C语言重点难点精讲C语言预处理

C语言重点难点精讲C语言内存管理

C语言重点难点精讲C语言中的重要符号

C语言重点难点精讲第一部分关键字:第一节-关键字分类细讲

漫谈C语言重点难点