C语言的灵魂---指针(进阶)

简介: C语言的灵魂---指针(进阶)

1.指针思维导图:

2.字符指针

字符指针没什么好讲的,主要就强调一下一个点

const char* pstr="hello world";


这里的pstr指针表示的不是指向hello world整个字符串,而是指向首字符h的地址,究其原因还是因为它是char指针,步长为一个字节,也就只能一个字符一个字符的指,若存在String指针,你要说它指向hello world那就应该没问题,但这里两个指针表示的地址是相同的,只是他们步长不同,所包围笼罩的范围不同;上述两个指针都加整数1,char跳过一个字符(字节),String跳过"hello world"对应的12个字节(末尾省略了\0)

用一道有趣的面试题来巩固一下:

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

这里答案是什么呢?我们运行一下来看看,实践出真知:


1)str1与str2数组不同,并且数组是可变参数,所开辟的空间不同,数组名所表示的首元素地址也就不同;

2)str3与str4指向的是一个常量字符串,而且指向的是同一个常量字符串,原因是常量字符串不可更改,所以在空间中也没有创建两个的必要性,所以常量字符串在空间中是惟一的,str3、str4都指向同字符串的首字符地址,所以相同;


这里关于字符指针就不多赘述了;


3.数组指针与指针数组

哈哈,终于来这个头疼的一对兄弟了,这里是我初学时候的噩梦呀,见一次,查一次哈哈,这里我会用自己总结的方法来讲解,希望对大家有帮助!

  • 再讲数组指针和指针数组之前,我们回顾一下操作符的优先级,这里我们就重点回顾
    “*”与“【】”优先级,我们了解到“【】”优先级是大于 “星号 ”的,这里就先铺垫一下;


3.1指针数组

  • 指针数组:我们从字面理解,就是装着指针的数组嘛,本质是数组,只不过是比较特殊的数组罢了,里面的元素都是指针;

  • 若元素为一级指针,那便是一级指针数组 int* arr[5]------>前面int*就是arr数组元素类型;
  • 那二级指针数组怎么表示呀,哈哈,就是int** arr[5]呗,是不是明了一些了呢。
  • 注: 这里我是用int指针为例的,若数组元素为char那便前面也用char* arr[]即可;
  • 这里也注意下:int* arr[10],[10]优先级高于星号,所以先和arr结合,也就是说明它是数组;


3.2数组指针

  • 数组指针:也就是指向一个数组的指针,本质是指针,只不过指向的对象是数组而已;

  • 这个就讲一下表达形式: int(*arr)[5] ,这里arr先和星号结合,所以它是指针;
  • 对于上述的例子呢,它表达的含义就是一个指向含有五个整形元素 的 数组首元素地址的指针

大家在这里一定注意区分,arr先和谁结合,就相应对应什么类型,先和星号结合那么就是指针,先和中括号结合,那么就是数组;这里就先简单介绍到这,其他的会在后面一步步巩固。


3.3 &数组名与数组名

我们直到数组名代表着数组首元素的地址,但&数组名又代表什么呢,先不慌,我们来看一个代码:

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


这里我们发现两者表示的地址相同,难道它们没有区别吗?我们接着测试:

#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);
 return 0;
}

  • 我们发现两者虽然初始位置相同,但是在步长上完全不同,甚至更细节一点,我们发现arr+1步长为一个整形(4个字节),&arr+1步长为一个数组长度(40字节:这里是十个整形组成的数组);
  • 于是,我们得出结论:&数组名表示的是该数组的首地址,数组名表示的是该数组的首元素的地址;

4.数组参数和指针参数

4.1 一维数组传参

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


如上面的代码,我们在使用函数的时候该如何接收呢?

这里我们列出一系列的传参方式,我来为大家分析是否可行:

//arr[10]传参:
void test(int arr[]){}
//这种方案是可行的,只是传入的数组大小不够明确,所以不建议这样
void test(int arr[10]){}
//此方案补足了上一种方案的不足,原封不动的传参当然可以
void test(int *arr){}
//这里是指针传参,arr本质为数组首元素地址,类型为int* 所以这里也没有问题
//arr2[20]传参:
void test2(int *arr[20]){}
//直接把原来的一维数组照搬,肯定没问题;
void test2(int **arr){}
//arr2的类型是int** 所以也没有问题

这里可以留意下,函数传参位置也就是上述的void test()括号内,必须保证类型一样即可,不必要求参数名称一致;

4.2 二维数组传参

//arr[3][5]={0};
void test(int arr[3][5]){}
//直接照搬,毫无问题;
void test(int arr[][]){}
//这里就会出现问题了,对于二维数组我们在定义的时候就明确表示,二维数组行可以省略,列不可以省略;这里形参部分其实也相当于一个创建局部变量的问题,所以这里也无法成立;
void test(int arr[][5]){}
//这个形参满足类型要求,也满足二维数组创建要求,所以可以;
//test(arr);
void test(int *arr){}
//arr是一级指针,但是二维数组数组名表示的是第一行开头的地址,也就是说它的元素是第一行,而int* arr表示的步长为int,元素也就是int类型,所以类型不符
void test(int* arr[5]){}
//这是个指针数组,类型完全不符合;
void test(int (*arr)[5]){}
//数组指针,指向每行有五个元素的数组的指针,诶,这不就刚好和我们上面说的一致了吗?所以可行
void test(int **arr){}
//二维数组的数组名是一级指针不能用二级指针接收
int main()
{
 int arr[3][5] = {0};
 test(arr);
 }


4.3 一级指针和二级指针传参

这个就不多赘述了,简单总结下:传入什么类型的指针,就用相同类型指针接收,但要注意上述数组传参中用指针接收,要注意类型;


5.函数指针

5.1 函数指针的定义

  • 顾名思义,函数指针也就是指向一个函数首地址的指针,int(*func)(int);
  • 表达式:(函数返回类型)(*函数名)(函数传入参数)
  • 在我看来函数指针用途不大,反而是函数指针数组用途比较大,所以接下来就直接讲函数指针数组;


5.2 函数指针数组的用途

  • 函数指针数组:int(*parr1[10])(int);我们一起来解读一下:parr1先和【10】结合,所以它是数组,然后把parr1[10]抹去,还剩下int(星号)(int)这个不就是函数指针吗,所以整体就是一个函数指针数组;函数指针数组用途:转移表

举个例子:

#include <stdio.h>

int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a*b;
}
int div(int a, int b)
{
 return a / b;
}

int main()
{
 int x, y;
 int input = 1;
    int ret = 0;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
       {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
                printf("退出程序\n");
 breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
}

如果用函数指针数组呢:

#include <stdio.h>
int add(int a, int b)
{
           return a + b;
}
int sub(int a, int b)
{
           return a - b;
}
int mul(int a, int b)
{
           return a*b;
}
int div(int a, int b)
{
           return a / b;
}

int main()
{

     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
      scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
          printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0;

从上面这个简单的例子,我们不难看出函数指针数组在多个函数类同,作用方式相似的案例中,可以非常有效的缩短代码长度,起到简化的效果;

尘世熙熙攘攘,希望大家可以守护自己心中的一亩方田!获得片刻宁静~一起加油!

相关文章
|
8天前
|
安全 程序员 编译器
【C语言基础】:深入理解指针(二)
【C语言基础】:深入理解指针(二)
【C语言基础】:深入理解指针(二)
|
1天前
|
C语言
C语言----深入理解指针(5)(一)
C语言----深入理解指针(5)
|
1天前
|
C语言
C语言---深入指针(4)(二)
C语言---深入指针(4)
|
1天前
|
C语言
C语言---深入指针(4)(一)
C语言---深入指针(4)
|
8天前
|
算法 Java 程序员
面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性
【6月更文挑战第15天】面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性。封装可使用结构体封装数据和方法,如模拟矩形对象。继承则通过结构体嵌套实现静态继承。多态可通过函数指针模拟,但C不支持虚函数表,实现复杂。C语言能体现OOP思想,但不如C++、Java等语言原生支持。
26 7
|
8天前
|
C语言
【C语言基础】:深入理解指针(终篇)
【C语言基础】:深入理解指针(终篇)
|
8天前
|
存储 C语言 C++
【C语言基础】:深入理解指针(三)
【C语言基础】:深入理解指针(三)
|
8天前
|
存储 编译器 C语言
【C语言基础】:深入理解指针(一)
【C语言基础】:深入理解指针(一)
|
1天前
|
C语言
C语言----深入理解指针(5)(二)
C语言----深入理解指针(5)
|
1天前
|
C语言
C语言----关于二维数组传参的本质相关的知识点(数组指针、指针数组)
C语言----关于二维数组传参的本质相关的知识点(数组指针、指针数组)