指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解

Posted 小赵小赵福星高照~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解相关的知识,希望对你有一定的参考价值。

指针的进阶


指针初阶的基础知识:

1、指针就是地址,地址就是指针,指针存在变量里叫指针变

2、指针的大小是4(32位平台)/8个字节(64位平台)

3、指针类型的意义:决定指针±整数的步长,指针解引用访问的字节

4、指针的运算

本人初阶指针的总结:C语言指针初阶

字符指针

int main()
{
   // char ch='q';
   // char *pc=&ch;
    //一个指针是可以指向一个字符串的,这个字符串是常量字符串
    char *ps="hello world";
    //ps本质上把字符串首字符'h'的地址存进去了
    char arr[]="hello world";
    //和ps不相同,arr是数组,ps是变量,arr是吧hello world都存进数组里,而ps只存了首字符的地址
    printf("%c\\n",*ps);//h
    //两种定义的名字都能打印字符串,都是首字符地址
    printf("%s\\n",ps);
    printf("%s\\n",arr);
    return 0;
}

注意:

ps本质上把字符串首字符’h’的地址存进去了

arr和ps不相同,arr是数组,ps是变量,arr是把hello world所有字符都存进数组里,而ps只存了首字符的地址

我们来看一道笔试题

#include <stdio.h>
int main()
{
  char str1[] = "hello worid.";
  char str2[] = "hello world.";
  char *str3 = "hello world.";
  char *str4 = "hello world.";
  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;
}

这个代码结果会打印什么呢?

image-20210524124044351

解读:

数组名是首元素的地址,数组str1和str2创建的空间不同,首元素地址就不相同,所以str1和str2肯定不相等

str3和str4是指针变量,str3和str4存放的是hello world字符串的首字符的地址,hello world这个字符串叫做常量字符串 ,是不能修改的,我们看一下我们如果要改的话的测试结果

image-20210524125420987

这里代码直接挂掉了,我们进行调试后发现会进行报错:

image-20210524125537269

所以常量字符串是不能修改的

str3和str4中的常量字符串是不能被修改的,两份相同的内容,而且不能被修改,是没有必要存两份的,所以这样的str3和str4在内存中只会存一份,所以常量字符串hello world只有一份,把h的地址存在str3中,把h的地址也存在str4中,str3中存的地址和str4中存的地址一样,所以str3等于str4。

下面我们来看另外一个知识点:

指针数组

存放指针的数组,它本质上是个数组,数组中存放的是地址(指针)。

int main()
{
    //int *arr[3];//arr是存放3个整形指针的数组
    int a=10;
    int b=20;
    int c=30;
    int *arr[3]={&a,&b,&c};
    int i=0;
    for(i=0;i<3;i++)
    {
        printf("%d ",*(arr[i]);
    }
    return 0;
}

这样的写法可行,是没有什么错误的,但是不太好,没什么应用场景,我们也不经常这样使用指针数组,我们来看下面这种写法:

int main()
{
	int a[5]={1,2,3,4,5};
	int b[5]={2,3,4,5,6};
	int c[5]={3,4,5,6,7};

	int *arr[3]={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]);
            //arr[i]==*(arr+i)  
            //arr[i][j]==*(arr[i]+j)
            //[j]==*(+j)
        }
        printf("\\n");
    }
    return 0;
}

这个写法将a,b,c三个数组名放在了指针数组中,而数组名是首元素地址,拿到了首元素地址,我们就可以访问数组当中的元素,而这种写法是我们经常使用的。

下面我们试着来辨别以下声明是什么:

int *arr1[10];

arr先和[]结合,说明它是个数组,然后去掉arr1[10]发现数组的每个元素为int*类型。所以它是存放整形指针的数组

char *arr2[4];

存放一级字符指针的数组,arr先和[]结合,说明它是个数组,然后去掉arr2[4]发现数组的每个元素为char*类型。

char **arr3[10];

存放二级字符指针的数组,arr先和[]结合,说明它是个数组,然后去掉arr3[10]发现数组的每个元素为char**类型。

指针数组就讲到这里,下面我们来看数组指针。

数组指针

数组指针是数组还是指针呢?答案是指针。

整形指针是指向整形的指针

int a = 10;
int* pa = &a;

字符指针是指向字符的指针

char ch = 'w';
char* pc = &ch;

顾名思义,数组指针就是指向数组的指针

接下来我们看看是如何定义数组指针的呢?我们来看下面的代码:

int main()
{
    double* d[5];//指针数组
    double* (*pd)[5]= &d;//pd就是一个数组指针
    int arr[10] = { 1,2,3,4,5 };
    int (*parr)[10] = &arr;//取出的是数组的地址
     //parr 就是一个数组指针 - 其实存放的是数组的地址
    //arr;//arr-数组名是首元素的地址- arr[0]的地址
    return 0;
}

我们在定义数组指针时,需要注意[]的优先级比*高,所以我们需要使用()强制先于*结合。

&数组名与数组名

我们首先看以下代码:

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

image-20210524153555061

打印上面代码后发现他们两个值一样,但是这两个的意义是不一样的,&arr是整个数组的地址,arr是数组首元素地址

那么&arr和arr区别怎么体现呢?以下代码就体现了他们的区别:

int main()
{
    int arr[10]={0};
    int *p1=arr;
    int (*p2)[10] = &arr;
    printf("%p\\n",arr);
    printf("%p\\n",&arr);   
    return 0;
}

我们用指针p1,p2存放这两个地址时,int *p1=arr;int (*p2)[10];

p1是个整形指针就可以了,而p2要是个数组指针,因为他存放的是&arr,是整个数组的地址。

既然他们的类型不一样,加减整数的步幅就不一样了

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

image-20210524155056391

我们能够发现p1加1的步幅是4个字节,p2加1的步幅却是40个字节

说明数组名和&数组名还是有差别的。

我们经常说数组名是数组首元素的地址

但是有两个例外:

1.sizeof(数组名) - 数组名表示整个数组,计算的是整个数组的大小

2.&数组名 - 数组名表示整个数组,取出的是整个数组的地址

数组指针的使用

利用数组指针访问一维数组

int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int *p=arr;//这个方便
    int (*pa)[10]=&arr;//不方便
    int i=0;
    for(i=0;i<10;i++)
    {
        *((*pa)+i);//*pa拿到arr
    }
    
    return 0;
}

我们也可以通过数组指针来访问一维数组的指针,但是不方便,将问题复杂化了。我们完全可以利用整形指针和[]下标引用操作符来访问,所以我们一般一维数组不用数组指针,数组指针的使用一般用于二维指针,接下来我们看一下二维数组是如何访问的。

传统的方式访问二维数组

void print1(int arr[3][5],int r,int c)
{
    int i=0;
    int j=0;
    for(i=0;i<r;i++)
    {
        for(j=0;j<c;j++)
        {
            printf("%d ",arr[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}};
    print1(arr,3,5);
    print2(arr,3,5);//arr数组名,表示数组首元素的地址
    return 0;
}

利用数组指针来访问二维数组

//p是一个数组指针---指向一维数组的指针
void print2(int(*p)[5],int r,int c)
{
    int i=0;
    int j=0;
    for(i=0;i<r;i++)
    {
        for(j=0;j<c;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);//arr数组名,表示数组首元素的地址
    return 0;
}

我们需要注意:

二维数组的数组名表示首元素的地址

二维数组的首元素是:第一行!

这里我们将第一行的地址传过去了,需要用一个指向一维数组的指针来接收

printf("%d ",*(*(p+i)+j));

这句代码的解读:

p是一个指向一维数组的指针,他存的是整个数组的地址,p+i跳过的是整个数组,p+0指向二维数组第一行arr[0]的整个一维数组的地址&arr[0],p+1指向二维数组第二行arr[1]的整个一维数组的地址,p+2指向二维数组第三行arr[2]的整个一维数组的地址,对p+i解引用找到的是第i行的数组名,数组名就是首元素地址,*(p+i)+j拿到的是第i行第j个元素的地址,在对它解引用就拿到了元素。

下面我们来辨别几个声明:

int arr[5];//整形数组
int *parr1[10];//指针数组
int (*parr2)[10];//数组指针,数组的元素为int类型,10个元素
int (*parr3[10])[5];//指针数组,每个元素为数组指针

注释是答案,我们重点来看一下最后一个怎么辨别:

image-20210524174018183

parr3首先和[]结合,说明它是个数组,将圈起来红色部分去掉,剩下的就是数组元素的类型,例如int arr[10];去掉arr[10],剩下的int就是数组元素类型

所以首先parr3是有10个元素的数组,数组的每一个元素又是数组指针。每个数组指针能够指向一个数组,数组5个元素,元素类型为int

数组参数、指针参数

一维数组传参

我们一维数组传参,函数参数类型可以写成什么呢?

int main()
{
    int arr[10]={0};
    int *arr2[20]={0};//arr2是存放整形指针的数组
    test(arr);
}

首先我们知道一维数组的数组名是首元素的地址,我们传过去的是首元素的地址,是arr[0]的地址,我们可以用哪些类型的参数来接收呢?

void test(int arr[10])
{}

传过来的是数组,当然可以用数组接收

void test(int arr[])//参数部分写成数组,数组元素可省略
{}

传过来的是数组,当然可以用数组接收,参数部分写成数组,数组元素可省略,操作系统会自行计算

void test(int *arr)//参数部分写成指针
{}

参数部分写成指针,数组名是首元素的地址,当然可以用一个指针来接收

我们再来看看指针数组传参,函数参数类型该如何写?

int main()
{
    int *arr2[20]={0};//arr2是存放整形指针的数组
    test2(arr2);//数组名表示首元素地址,而首元素又是int*,所以用int**接收
}

我们依次来看以下的传参方式可行不可行:

void test2(int *arr[20])//指针数组
{}

可以,我们传过来的是指针数组,所以可以直接用指针数组来接收

void test2(int **arr)//数组名表示首元素地址,而首元素又是指针,所以要用一个二维指针来接收
{}

可以,我们知道数组名表示首元素地址,而数组里面存放的是指针,首元素是一个指针,所以我们可以用一个二维指针来接收

二维数组传参

我们二维数组传参,函数参数类型可以写成什么呢?

int main()
{
    int arr[3][5]={0};
    test(arr);//传过去的是首元素的地址,首元素是arr[0],是第一行一维数组的地址&arr[0]
    return 0;
}

首先我们知道二维数组的数组名是首元素的地址,我们传过去的是首元素的地址,是第一行一维数组的地址&arr[0],我们可以用哪些类型的参数来接收呢?

我们来看以下的函数形参的类型是否可行:

void test(int arr[3][5])//可以,传过来的是二维数组,当然可以用二维数组接收
{}

可以,传过来的是二维数组,当然可以用二维数组接收

void test(int arr[][])//不行,不能省略列
{}

不行,二维数组不能省略列

void test(int arr[][5])//可以,可以省略行
{}

可以,二维数组可以省略行

void test(int *arr)//不可以,传过来的是一个一维数组的地址,要用数组指针来接收
{}

不可以,传过来的是一个一维数组的地址,要用数组指针来接收

void test(int* arr[5])//更不行,这是一个指针数组
{}

更不行,这是一个指针数组

void test(int (*arr)[5])//可以,指向一个5个元素的数组的指针
{}

可以,指向一个5个元素的数组的指针

一级指针传参

void print(int *ptr,int sz)//用一级指针接收
{
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d ",*(ptr+i));
    }
}
int main()
{
    int arr[10]={1,2,3,4,5,6,7,

以上是关于指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解的主要内容,如果未能解决你的问题,请参考以下文章

自定义类型的这些知识你知道吗?C语言超硬核结构体枚举联合体画图+文字详细讲解

✨三万字制作,关于C语言,你必须知道的这些知识点(高阶篇)✨

嵌入式进阶必看!7个硬核的 C 语言要点

硬核知识C语言文件操作!文件的打开和关闭!

你是否还记得c语言的这些文件操作?

C语言进阶5——指针的进阶