C语言指针进阶(1)

很多小伙伴在学习完指针后,就会觉得指针是个用来存储地址的变量、指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。指针作为C语言的精髓,自然不会就这么简单啦。下面就一起看一下指针的进阶。篇幅有点长,请小伙伴耐心看完哟。

目录

字符指针

指针数组

 数组指针

数组指针的定义

&数组名VS数组名 

数组指针的使用


字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* 。一般都是下面这样使用:

int main()
{
  char ch = 'w';
  char *pc = &ch;
  *pc = 'w';
  return 0;
}

但是字符指针不仅仅可以指向单个字符,还可以指向字符串。另一种使用方法:

int main()
{
  char* pstr = "helloworld.";
  printf("%s\n", pstr);
  return 0;
}

代码 char* pstr = "helloworld."; 特别容易让小伙伴误以为是把字符串"helloworld." 存储字符指针 pstr变量里了,但是其本质是把字符串"helloworld."首字符h的地址放到了pstr中。我们可以来看看打印单个字符,看看pstr里面是什么。

如果是要打印整个字符串就是用printf("%s\n", pstr);和数组倒是挺像的,但这种方式和数组本质的区别就是char arr[] = "helloworld.";是把整个字符串都放在arr数组中。那就会有小伙伴有疑问了,把整个字符串放在数组自然是能打印字符串来,那为什么字符指针中就放一个字符串首字符的地址也能打印出完整的字符串呢?

因为%s是从首地址开始一个一个字符的输出,直到遇见了'\0'就停止,而字符串"helloworld."这种形式就是会默认在末尾加上'\0',所以也就能打印整个字符串了。

下面一起来做一道题目:

#include <stdio.h>
int main()
{
  char str1[] = "helloworld.";
  char str2[] = "helloworld.";
  char *str3 = "helloworld.";
  char *str4 = "helloworld.";
  if(str1 ==str2)
    printf("str1 and str2 are same\n");
  else
    printf("str1 and str2 are not same\n");
  
  if(str3 ==str4)
    printf("str3 and str4 are same\n");
  else
    printf("str3 and str4 are not same\n");
  
  return 0;
}

结果会打印什么呢 ?

不知道小伙伴有没有答对 。为什么是这个答案,先来看str1和str2的比较,两个数组中存放了两个相同的字符串,但是创建数组时都是要开辟新的地址空间,而str1和str2是两个数组的数组名,数组名代表的是数组首元素地址,由于两块地址空间不同,所以str1和str2不相同。

再来看str3和str4,是两个字符指针,这里需要注意的是str3和str4指向的是同一个常量字符串,而str3和str4也是字符串常量指针,字符串常量指针的正确写法应该是const char* str3 = "helloworld.";和const char* str4 = "helloworld.";,如果有小伙伴的编译器报错了,就在前面加上const。

因为指针是指向一个常量,平常我们都是拿指针来指向变量。既然是常量字符串的内容就不能改变了,C/C++会把常量字符串存储到常量区,即全局(静态)变量区,当几个指针。指向同一个常量字符串的时候,他们实际会指向同一块地址空间,所以str3和str4相同。

指针数组

指针数组是指针还是数组?答案是数组,是存放指针的数组。数组我们已经知道整形数组,字符数组。

int arr1[5];
char arr2[6];

那指针数组是怎样的?

int* arr3[5];

arr3是一个数组,有五个元素,每个元素是一个整形指针。

 下面的代码就用指针数组来模拟了一个类似的二维数组。

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

    return 0;
}

为什么是说模拟一个类似的二维数组呢?因为这个看起来和二维数组是一样的,但是其本质的区别是二维数组中每个元素的地址都是连续的,而指针数组模拟的二维数组并不是每个元素的地址都是连续的。前面说了创建数组时都是要开辟新的地址空间,模拟的二维数组是由有四个数组组成的,又怎么会每个元素的地址都是连续的呢。

通过上图能发现每个一维数组的元素地址是连续相差4个字节,但是一维数组之间的元素是不连续的,达到二维数组的效果只是把3个一维数组的首地址都存放在arr数组中,使用指针来操作数组。

 数组指针

数组指针的定义

数组指针是指针?还是数组?答案是指针。我们知道整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数据的指针。那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];

答案是int(*p)[10]; ,因为p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。                                                         这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。 

double* a[5]; 

这是一个指针数组,那么它的数组指针怎么写呢?

答案是double* (*p)[5] = &a;,首先写出(*p)确保是指针,然后是(*p)[5]表示指向一个元素为5个的数组,最后是double* (*p)[5]因为数组的元素类型是double*,表示指针变量p指向一个元素为5个double*类型的数组。

&数组名VS数组名 

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?我们知道arr是数组名,数组名表示数组首元素的地址。那&arr数组名到底是啥?
我们看一段代码

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

 打印的结果是两个地址完全一样,但是这两个的意义真的是一样吗?

我们再看一段代码:

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

    printf("p2 = %p\n", p2);
    printf("p2+1 = %p\n", p2 + 1);
    return 0;
}

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。实际上: &arr 表示的是数组的地址,而不是数组首元素的地址,arr 表示的是数组首元素的地址。数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的地址相差40个字节。数组首元素的地址+1,就是跳到下一个元素的地址,而两个相邻元素的地址相差4个字节。

数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print_arr2(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; 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} };
	print_arr1(arr, 3, 5);
	
	print_arr2(arr, 3, 5);
	return 0;
}

在把arr传到print_arr2中数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收。而p+i相当于找到了第i行的地址,然后解引用*(p+i)相当于找到了第i行的数组名 arr[i],*(p+i)+j相当于找到了第i行下标为j的地址,然后解引用*(*(p+i)+j))相当于找到了第i行下标为j的元素arr[i][j]。这里使用一个一级指针来接收二维数组。

有的小伙伴就会疑惑,这样写这么麻烦还不如直接用print_arr1中一个二维数组接受方便。那我们为什么还要这样写呢?其中很大一部分要考虑的是内存,用一个二维数组来接收,要在内存中开辟一个局部变量如果这个二维数组是一个arr[100][100]或者更大的数组,那么非常容易栈溢出,就是栈空间被用完了。而用一个数组指针来接收只需要开辟一个4字节或8字节(看系统是32位还是64位)的局部指针变量,这样就能节约很多的内存空间。

其实*(*(p+i)+j)也可以直接写成p[i][j];,p[i]就相当于*(p+i),总的来说算不上麻烦。

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

 int arr[5];数组,表示的是有5个整型元素的数组。

int *parr1[10];数组,表示的是有10个整型指针元素的数组。

int (*parr2)[10];指针,表示的是一个指针变量指向一个有10个整型元素的数组。

int (*parr3[10])[5];数组,表示的是有10个数组指针元素的数组,每个数组指针能够指向一个有5个整型元素的数组。

这是C语言指针进阶的第一部分,要是有什么错误的地方和有不理解的地方欢迎在评论区评论,如果感兴趣的小伙伴可以继续观看后续部分。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值