关于C语言指针、数组和函数的相关内容

目录

一、字符指针

二、指针数组

三、数组指针

 四、数组传参

一维数组传参

二级数组传参

 五、指针传参

一级指针传参

二级指针传参


一、字符指针

首先,我们要知道指针的概念,指针就是用来储存内存块所在的地址(关于内存块的地址是如何分布的,可以看我另一条博客)。下面举例说明:

int main()
{
    int i = 0;//定义i为整型,想内存申请4个字节的空间(内存块)
    int *p = &i;//取地址符&,&i取出i所在内存块的地址,由指针p接收
    return 0;
}

 上面定义的指针p的类型为int(整型),但是我们知道,在c语言中,类型由7种,下面举例说明

int main()
{
    char *p;//字符型指针
    short *p;//短整形指针
    int *p;//整型指针
    long *p;//长整型指针
    long long *p;//长整型指针
    float *p;//单浮点型指针
    double *p;//双浮点型指针
    return 0;
}

下面,我们讲字符指针,其他类型指针则类似,依旧是我们的举例说明,毕竟举例更加直观嘛

int main()
{
    char i = 'w';//将字符w由字符型变量i接收,变量i就在内存空间中开辟一个内存块
    char *p =&i;//将i所在内存块的地址由字符型指针接收
}
//一般来说,指针的类型应该和它接收地址的那个变量类型相同,如上所示
//原因是因为通过解引用符 * 的时候,可以改变i的值

下面,我们来看一段代码,看看它的运行结果是不是和你想的一样了?

int main()
{
    char *p = "hello hello";
    char arr[] = "hello hello";
    printf("%s\n",p);
    printf("%s\n",arr);
    return 0;
}

下面是运行的结果:

hello hello
hello hello

其实char *p = " hello hello ";本质上是将“hello hello”这个字符串的首字符地址储存在了指针p中,类似于数组名放到指针里面,都是首元素地址,所以打印的时候,可以完整的打印出来


看到这里,你肯定对字符指针有所了解,下面我们看这一段代码,看看运行的结果是否和你想的一样。

int main()
{
    char str1[] = "hello hello";
    char str2[] = "hello hello";
    char *str3 = "hello hello";
    char *str4 = "hello hello";
    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("str1 and str2 are not same\n");
    }
    return 0;
}

下面是运行的结果

str1 and str2 are not same
str3 and str4 are same

先来解释第一个,原因是数组 str1[ ] 和 str2[ ] 分别向空间申请了内存块进行字符串的存储,所以占用了不同的内存块,所以每个内存块的地址不同,而数组名 str1 和 str2 代表的是首元素地址,所以地址不相同。

要按照这么说,第二个打印出来应该和第一个一样啊。其实不然,我们当把字符串放在数组里面的时候,是一个字符一个字符的放在对应的内存块的位置,所以每个字符是可以通过相应的代码修改的,所以要放在不同的内存块中。

       but!!!我们上面说的用指针接收字符串常量的时候,是把首字符的地址传给了指针,而且,最重要的是字符串常量不能被修改!!!所以这个字符串常量只需要申请一块内存块即可,所以指针str3和str3里面存放的地址相同。


二、指针数组

指针数组,简单来说就是一个存放指针的数组,所以它还是一个数组,即数组里面存放的都是指针。

int *arr[10];//存放整型指针的数组
char *arr[3];//存放字符型指针的数组
char **arr[8];//存放二级字符指针的数组

依旧来举例

int main()
{
    int a[5] = {0,1,2,3,4,5};
    int b[] = {2,4,6};
    int c[] = {3,6,8};
    int* arr[3] = {a,b,c};
    return 0;
}

 

我来解释一下,如果所示,三个整型数组有不同的内存块,内存块的地址不相同, 而指针数组里面放的是数组名a、b、c,而数组名代表的是首元素的地址,而这个地址所指向的数组首元素是整型,所以这个指针数组的类型即 int* 。

这里肯定有人会问,咦~,这个指针数组里面明明放的是数组名,数组名是代表的首元素地址,这里不是指针啊。我们来这么理解,指针是用来干嘛的,是不是用来接收地址的,所以指针代表的就是地址,可以这么说指针即地址,地址即指针。


当指针数组遇到加法的时候,是否和指针加法一样了?我们来看下一个代码 ,是否结果和一想的一致了?

int main()
{
    int i = 0;
    int a[5] = {0,1,2,3,4,5};
    int b[] = {2,4,6};
    int c[] = {3,6,8};
    int* arr[]={a,b,c};
    for( i=0 ; i<5 ; i++ )
    {
        printf( "%d " , *(arr[0]+i) );
    }
    return 0;
}

运行结果是: 

0 1 2 3 4 5 

首先 arr[0] 指的是指针数组的第一个元素int* a,上面我们说了这个是数组 a[ 5 ] 首元素的地址,那么通过从加0到加4,就可以依次获得数组 a[ 5 ] 里的每个元素地址,再通过解引用符 * ,就可以获得数组 a[ 5 ] 的每个元素,进而打印出来。


三、数组指针

首先,我们来看它的定义,数组指针是指针,还是数组?

答案是:指针!!!

我们已经熟悉的:整型指针:int* p是指向整型数据的指针。单浮点指针:float* t是指向单浮点数据的指针。

那么同理,我i们可以得到数组指针,就是指向数组的指针

我们老规矩,直接上代码解释

 

上面关于数组指针的写法已经很明确了,如果还有老铁不太清楚。那么就一句话:数组指针就是存放整个数组地址指针,本质上任然是指针,用来存放地址。

还有了,就是关于数组指针的类型如何写

int arr[10] = {0};

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

那么这个数组指针p的类型就是 int (*)[10] ,没错,就是将数组指针p去掉就是了。


 数组指针的加法又是怎么样的了,上代码解释:

 从上面的代码可以看出 &arr 和 arr 的地址是一样的,但是为什么一个加了4,一个加了40了?

我们说,虽然取出的地址一样,但是意义却不一样。&arr 取出来的地址是整个数组的地址,

arr 取出来的是数组首元素的地址,所以前者是跳过10个整型的字节(因为这里数组里面是10个整型),后者一个是跳过一个整型的字节。


我们来看下一段代码,看看结果如何

#include<stdio.h>

void print(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,0,0),(4,5,6,0,0),(7,8,9,0,0)};
    print(arr,3,5);
    return 0;
}

下面是运行的结果:

1 2 3 0 0
4 5 6 0 0
7 8 9 0 0

这里有三个点需要解释

第一:我们知道数组名代表的是首元素地址,一维数组我们很清楚,但是二维数组的数组名代表的是首元素地址就是第一行的地址,即用 &arr[0] 表示第一行的地址,同理,第二行和第三行的地址就是 &arr[1] 和 &arr[2] ,是不是感觉和一维数组很像,没错,因为二维数组其实本质上就是几个一维数组的组合,如本代码这个二维数组可以看成由3个一维数组组成 。

第二:在我们自定义的函数中,形参数组指针 int (*p)[5] 就是用来接收二维数组第一行的地址,因为函数调用的实参是直接给的二维数组名 arr 。

第三:来解释一下 *( *( p + i ) + j ),初看一下很懵,这种样式的,我们需要从里往外看,首先p是数组指针,接受的是来自二维数组第一行的地址,即 &arr[0] 。

       此时 i = 0,*p 就是指向的第一行的数组名,即 arr[0] ,注意!!!这是一个一维数组的数组名,所以代表的是首元素地址,再通过加 j ,就能得到这个一维数组里面元素的地址,最后整体解引用,就得到这个一维数组的所有元素。然后进入 i =1 的下一次循环。

       此时 i =1,( p+1) 就是指向二维数组第二行的地址,再解引用 *( p+1) 就是指向的第二行的数组名,即 arr[1] ,同上,这是一个一维数组的数组名,所以代表的是首元素地址,再同过加 j 和解引用就能得到二维数组第二行的元素。然后进入 i =1 的下一次循环。

       i = 2 同上,所以打印的结果如上。


指针数组和数组指针,当这两个结合的时候,会是什么样的了?

指针数组:int*  arr[3],存放指针的数组

数组指针:int  (*p)[5],存放数组地址的指针,该指针可以指向一个数组,数组的5个元素,         每个元素的类型是 int 。

int  (*parr[10])[5],这里的 parr 就是一个存放数组指针数组,该数组可以存放10个数组指针每个数组指针指向一个数组,数组由5个元素每个元素的类型是 int

       我们可以这么看: int  (*)[5],这是一个数组指针的类型,parr[10]是一个数组,加起来parr就是一个存放数组指针的数组。


四、数组传参

在写代码的时候,难免会把数组传递给函数,那么函数的参数应该如何设计了?

一维数组传参

#include<stdio.h>

void test1(int arr[]) //第一种
{}
void test1(int arr[10]) //第二种
{}
void test1(int* arr) //第三种
{}
void test2(int* arr[]) //第四种
{}
void test2(int* arr[20]) //第五种
{}
void test2(int** arr) //第六种
{}

int main()
{
    int arr1[10]={0};
    int* arr2[20]={0};
    test1(arr1);
    test2(arr2)
    return 0;
}

如上代码所示,在一维数组传参时,以上函数的形参设计都对。

前三种是对整型数组进行传参,第一种和第二种可以看成用一个形参整型数组接收数组,形参数组的数组元素可写可不写,但是实际传递过去的整型数组首元素地址,第三种就是将实际的首元素地址接收,用指针进行接收。

后三种是对指针数组进行传参,第四种和第五种可以看成用一个形参指针数组接收数组,形参数组的数组元素可写可不写,但是实际传递过去的指针数组首元素地址,而指针数组的首元素本身是一个一级指针,而一级指针必须用二级指针接收,所以用 int** arr 。


二级数组传参

#include<stdio.h>

void test1(int arr[][4])
{}
void test1(int arr[3][5])
{}
void test1(int (*arr)[5]);
{}

int main()
{
    int arr[3][5]={0};
    test(arr);
    return 0;
}

 对于二维整型数组,上面有三种,我觉得第一和第二种不需要解释了,但是在省略形参整型数组的元素的时候,列绝对不能省略,行可省可不省。

第三种,arr 是二维数组的数组名,代表的是二维数组第一行的地址,即一个一维数组的地址,所以我们需要用一个数组指针来接收,而第一行的数组元素个数是5,所以第三如代码所示。


五、指针传参

在写代码的时候,难免会把指针传递给函数,那么函数的参数应该如何设计了?

一级指针传参

 一级指针传参很简单,函数的形参就用一个指针接收这个指针即可。

但是,这里有一个需要特别注意的点:就是传递过去的指针和函数形参的指针必须是相同级别,即一级指针用一级指针接收,二级指针用二级指针接收。如果实参是一级指针,形参是二级指针,那么这个形参接收的内容就是一级指针本身的地址,就不是一级指针所指向的那个数据的地址。


我们来思考一个问题,当一个函数参数为一级指针的时候,函数能接收什么实参? 

如上图所示,形参一级指针可以接受一级指针和地址。 


二级指针传参

 一级指针传参很简单,函数的形参就用一个指针接收这个指针即可。依旧和上面一样,需要注意指针级别的问题。


我们又来思考一个问题,当一个函数参数为二级指针的时候,函数能接收什么实参?

#include<stdio.h>

void test(int** p)
{}

int main()
{
    int a = 1;
    int* p1 = &a;
    int** p2 = &p1;
    test(p2); //第一种
    test(&p1); //第二种
    int* arr[10] = {0};
    test(arr); //第三种
    return 0;
}

当函数形参为二级指针的时候,函数的实参又三种。

第一种:直接将二级指针传递过去。

第二种:将一级指针的地址传递过去,这里是一级指针本身的地址,而不是所指向变量的地址,因为二级指针就是用来接收一级指针地址的。

第三种:如果是一个指针数组,可以将数组名 arr 传递过去,因为数组名就是首元素的地址,而指针数组里面存放的是指针,所以相当于第二种,把一级指针本身的地址传递了过去。


后面我会继续分享函数指针、 函数指针数组指向函数指针数组的指针回调函数。(后面内容博客连接关于C语言数组、指针和函数的相关内容2_昵称就是昵称吧的博客-CSDN博客

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值