【C语言进阶】指针与数组、转移表详解

简介: 本文讲解:C语言进阶指针与数组、转移表详解


image.gif编辑

前言

大家好我是程序猿爱打拳,我们在学习完指针的基本概念后知道了指针就是地址,我们可以通过这个地址并对它进行解引用从而改变一些数据。但只学习指针的基础是完全不够的,因此学习完指针的基础后我们可以学习关于指针的进阶,其中包括指针数组、数组指针、函数指针等。这篇文章的末尾也有模拟实现计算器源码及讲解。

目录

1.字符指针

2.指针数组

3.数组指针

3.1&数组名和数组名

3.2数组指针的定义

3.3数组指针的使用

4.数组参数、指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5.函数指针

6.函数指针数组

6.1函数指针数组定义

7.实现计算器

7.1使用switch实现

7.2使用转移表实现


1.字符指针

经过学习指针的基础后,我们知道了有一种指针类型为字符指针char*。一般这样写代码:

#include<stdio.h>
int main()
{
  char ch = 'a';
  char *p = &ch;
  *p = 'b';
  printf("%c\n", ch);
  return 0;
}

image.gif

以上代码最终输出的值为b,对指针p进解引用并赋新值从而改变了ch的值我们不难理解。还有一种写代码方式:

#include<stdio.h>
int main()
{
  const char* p = "Hello World";
  printf("%s\n", p);
  return 0;
}

image.gif

输出结果

image.gif编辑

以上代码,我们把Hello World的首字符地址赋值给了指针p并不是把整个Hello World赋值给了指针p,因此在输出的时候是从H开始依次往后面输出的。当然我们在指针初阶学过以""初始化一个字符串的时候,字符串末尾会默认生成'\0'(结束标识符)。

image.gif编辑


在理解以上程序后,我们来看一组代码:

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

输出结果:

image.gif编辑

以上代码,可能有些朋友认为str1不是和str2一模一样吗,为啥输出else后面的结果呢。其实是这样的。

str1和str2没有被const修饰的话是分别在内存中占不同的空间,str3和str4两个字符串都被const修饰了因此占用的空间是一致的。所以str1不等于str2,str3等于str4。

image.gif编辑


2.指针数组

在指针基础知识中我们学到了指针数组是存放指针的数组。如以下代码:

int* arr1[3];//整型指针的数组
  char *arr2[4];//一级字符指针的数组
  int **arr3[5];//二级字符指针的数组

image.gif

我们拿整型指针的数组来举例:

#include<stdio.h>
int main()
{
  int arr1[2] = { 1,2 };
  int arr2[2] = { 3,4 };
  int arr3[2] = { 5,6 };
  int* arr4[3] = { arr1,arr2,arr3 };
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 2; j++)
    {
      printf("%d", arr4[i][j]);
    }
  }
  return 0;
}

image.gif

输出结果:

image.gif编辑

以上代码中int *arry4[3]就是一个存放整型指针的数组,它的每一个元素都存放的是一个地址,这些地址分别是arr1,arr2,arr3的数组名也就是第一个元素的地址。通过这些个地址就能依次访问到这个地址及这个地址以后的内容,如通过arr1的地址访问到了1和2。

image.gif编辑


3.数组指针

数组指针是什么呢,指针还是数组?其实它是指针。我们在指针初阶知道了整型指针可以这样定义:int * p;说了了p指向的是一个整型。浮点型指针可以这样定义:float * p;说明了p指向的是一个单精度浮点型。


3.1&数组名和数组名

我们在数组学习的时候已经知道了数组名就是数组的首元素地址。那么&数组名到底是什么呢?我们来看一组代码:

#include<stdio.h>
int main()
{
  int arr[2] = { 3,4 };
  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;
}

image.gif

输出结果:

image.gif编辑

以上代码我们可以看到,数组名&数组名还是有很大差异的。前两个结果看不太出,后两个代码我们可以看出单个的数组名+1也就是arr(首元素)+1只是增长了4,而&数组名+1也就是&arr+1却增长了8。我们知道整型是占四个字节而arr数组里面刚好有两个整型数字,因此我们得到的结论是&数组名是&整个数组的地址


3.2数组指针的定义

我们可以这样写:int (*p)[10];解释:首先*先和p结合说明p是一个指针,其次int 和[10]结合。因此p指向的是一个有10个整型元素的数组。在3.1中我们知道了,单个的数组名只是数组的首元素地址,而&数组名得到是整个数组的地址,因此我们在初始化的时候应该这样:int (*p)[2]={&arr1,&arr2};以上为两个地址样式。

注意:[]号的优先级要高于*号,所以必须加上()来保证p先和*结合。


3.3数组指针的使用

上面我们说到了,数组指针代表着指针指向的是数组,那么数组指针中存放的就是数组的地址了。比如:

#include<stdio.h>
void print_arr1(int(*arr1)[4],int x,int y)
{
  for (int i = 0; i < x; i++)
  {
    for (int j = 0; j < y; j++)
    {
      printf("%d ", arr[i][j]);
    }
  }
}
void print_arr2(int arr2[3][4], int x, int y)
{
  for (int i = 0; i < x; i++)
  {
    for (int j = 0; j < y; j++)
    {
      printf("%d ", arr2[i][j]);
    }
  }
}
int main()
{
  int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
  print_arr1(arr, 3, 4);
  printf("\n");
  print_arr2(arr, 3, 4);
  return 0;
}

image.gif

输出结果:

image.gif编辑

以上代码展示了数组指针的用法,在函数print_arry1(arr,3,4)中数组名arr表示首元素的地址。也就是二维数组的第一行地址。所以int (*arr1)[4]接受的arr其实是第一行的地址,可能有的朋友就有疑问了那为啥[]里面不是3而是4。因为二维数组的每一行有四个元素,因此[]里面是4。

image.gif编辑


4.数组参数、指针参数

我们在写代码的时候难免会把数组或者指针传给函数,那么函数里面的参数该如何设计呢?下面我们来看四种情况。


4.1一维数组传参

#include<stdio.h>
void test1(int arr[])//函数1
{}
void test1(int arr[10])//函数2
{}
void test1(int* arr)//函数3
{}
void test2(int* arr[20])//函数4
{}
void test2(int** arr)//函数5
{}
int main()
{
  int arr1[10] = { 0 };
  int arr2[20] = { 0 };
  test1(arr1);
  test2(arr2);
  return 0;
}

image.gif

函数1,没问题,数组传参过去,函数未指定大小的数组接收,可行。

函数2,没问题,数组传参过去,函数指定了大小的数组接收,可行。

函数3,没问题,数组传参过去,函数中未指定大小的指针来接受,可行。

函数4,没问题,数组传参过去,函数中指定大小的指针来接收,可行。

函数5,没问题,数组传参过去,函数中未指定大小指针来接收,可行。


4.2二维数组传参

#include<stdio.h>
void tset(int arr[3][5])//函数1
{}
void test(int arr[][])//函数2
{}
void test(int arr[][5])//函数3
{}
void test(int* arr)//函数4
{}
void test(int(*arr)[5])//函数5
{}
void test(int** arr)//函数6
{}
int main()
{
  int arr[3][5] = { 0 };
  test(arr);
  return 0;
}

image.gif

函数1,没问题,二维数组传参,函数中二维数组接收。

函数2,有问题,二维数组传参,函数中二维数组不能省略列数。因为对一个二维数组来说可以不知道有多少行,但不能不知道有多少列。这样才能方便计算。

函数3,没问题,二维数组传参,可以省略行数。

函数4,有问题,二维数组传参,函数中用一级指针来接收不可行。

函数5,没问题,二维数组传参,函数中用数组指针来接受,在外面3.3中有讲解到。

函数6,有问题,二维数组传参,函数中用二级指针来接收不不可行,因为二级指针接收的是一级指针,而二维数组传过去的参数是第一个元素也就是第一行的地址。


4.3一级指针传参

我们直接来看一组代码:

#include<stdio.h>
void print(int* p, int sz)
{
  for (int i = 0; i < sz; i++)
  {
    printf("%d\n", *(p + i));
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
  int* p = arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  print(p, sz);
  return 0;
}

image.gif

以上代码,中print函数就是一级指针的接收。在main函数中我们把arr的地址给了指针p,因此print(p,sz)传参过去的就是arr的首元素地址arr数组元素的个数。因此print函数可以通过首元素地址依次访问到该数组结束。

注意:sizeof操作符和&符号对数组名进行操纵时,此时的数组名代表的是整个数组。

image.gif编辑


4.4二级指针传参

#include<stdio.h>
void test1(int** ptr1)
{
  printf("num= %d\n", **ptr1);
}
void test2(int** ptr2)
{
  printf("num= %d\n", **ptr2);
}
int main()
{
  int num = 10;
  int* p = &num;
  int** pp = &p;
  test1(pp);
  test2(&p);
  return 0;
}

image.gif

输出结果:

image.gif编辑

以上代码,为二级指针的接收。二级指针的接收可以是一级指针的地址也可以是二级指针的地址。但无论是接收那个一种,解引用必须要解引用两次。

我们可以这样理解:p指针里面存放的是num,pp指针里面存放的是p。

image.gif编辑


5.函数指针

首先我们来看一组代码:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  return 0;
}

image.gif

输出结果:

image.gif编辑

我们发现函数也是有地址的,因此我们可以把函数的地址存起来形成一个函数指针!

以上个代码为例:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*p)(int, int) = Add;
  return 0;
}

image.gif

以上代码为例,我们可以这样存放函数的地址,首先我们要用一个指针p来接受,指针p的类型跟函数的返回类型一致(Add返回类型为int,因此p的类型为int),其次指针p后面紧接着要说明函数的参数类型(Add参数为两个int,因此p后面要说明参数类型为int,int),最后把函数的地址赋值给指针p。

注意:

1.函数名等同于&函数名,如Add=&Add

2.函数指针中指针的类型根据函数的返回类型来定

3.函数指针后面要说明函数的参数类型


函数指针怎么用呢?还是根据以上代码进行修改:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*p)(int, int) = Add;
  int sum = (*p)(4, 6);
  printf("sum=%d\n", sum);
  return 0;
}

image.gif

输出结果:

image.gif编辑

我们已经知道了,函数指针如何去赋值。用法也并不难,只是把对应的数据放在函数指针后面的()里面即可实现功能。


6.函数指针数组

函数指针数组的作用是:转移表,转移表是什么呢?

我们在写代码的时候,会遇到使用switch语句的情况。当我们使用switch来编写代码的时候,会发现得使用成千甚至上万条代码。但经过转移表的使用,代码的篇幅将会大大减少。


6.1函数指针数组定义

我们在前几节学到了指针数组的用法,如:char* arr[10]存放的是字符指针,此时arr数组的每个元素为char*int* arr[10]存放的是整型指针,此时arr数组的每个元素为int*

那我们可不可以把函数指针存放在数组里面呢?是可以的!所以函数指针数组是存放函数指针的数组。它的定义方法如下:

我们在定义函数指针数组的时候,需要要在函数指针的基础上加上一个[]。使得函数指针变为函数指针数组。[]里面为函数指针的个数。以下代码演示了如何在函数指针基础上改变方法。

#include<stdio.h>
int Add(int x, int y)
{}
int main()
{
  int (*p)(int, int) = Add;//这是一个函数指针
  int (*p[5])(int, int) = { Add };//这是一个函数指针数组
  return 0;
}

image.gif

以上代码中,要注意的是:

1.函数指针数组定义时只是比函数指针多了一个[],[]的个数代表着函数指针的个数。

2.函数指针数组在赋值的时候只能是地址。

3.函数名等同于&函数名。


7.实现计算器

我们在认识道具函数指针数组的含义以及定义方式后,我们可以用转移表的方式来实现计算器。


7.1使用switch实现

#include<stdio.h>
void menu()
{
  printf("************************\n");
  printf("*    1.Plu  2.Sub      *\n");
  printf("*    3.Mul  4.Div      *\n");
  printf("*    0.Exit            *\n");
  printf("************************\n");
}
int Plu(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int key = 0;
  do
  {
    menu();
    printf("请输入你的选项:>");
    scanf("%d", &input);
    switch (input)
    {
    case 0:
      printf("你已退出程序!");
      break;
    case 1:
      printf("请输入两个整数:>");
      scanf("%d %d", &x, &y);
      key = Plu(x, y);
      printf("两数之和为:%d\n",key);
      break;
    case 2:
      printf("请输入两个整数:>");
      scanf("%d %d", &x, &y);
      key = Sub(x, y);
      printf("两数之差为:%d\n", key);
      break;
    case 3:
      printf("请输入两个整数:>");
      scanf("%d %d", &x, &y);
      key = Mul(x, y);
      printf("两数之积为:%d\n",key );
      break;
    case 4:
      printf("请输入两个整数:>");
      scanf("%d %d", &x, &y);
      key = Div(x, y);
      printf("两数之商为:%d\n",key);
      break;
    default:
      printf("请输入正确的选项!\n");
      break;
    }
  } while (input);
  return 0;
}

image.gif

效果展示:

image.gif编辑

如果我们使用switch语句来实现这样一个简易的计算器我们会发现,每当我要添加一个功能的时候。都需要增加一个case语句,比如我要增加一个&运算,我得再加上一个case语句。因此我们可以使用函数指针数组(转移表)来实现,会简易很多。


7.2使用转移表实现

#include<stdio.h>
int Plu(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int input = 1;
  int x = 0;
  int y = 0;
  int key = 0;
  int (*p[5])(int x, int y) = { 0,Plu,Sub,Mul,Div };
  while (input)
  {
    printf("************************\n");
    printf("****  1.Plu  2.Sub  ****\n");
    printf("****  3.Mul  4.Div  ****\n");
    printf("****  0.Exit        ****\n");
    printf("************************\n");
    printf("请输入你的选项:>");
    scanf("%d", &input);
    if ((input <= 4 && input >= 1))
    {
      printf("请输入两个整数:>");
      scanf("%d %d",&x,&y);
      key = (*p[input])(x, y);
      printf("得到的结果为:%d\n", key);
    }
    else
    {
      if (input != 0)
      {
        printf("请输入正确的选项!\n");
      }
      else
      {
        printf("您已退出程序!");
        break;
      }
    }
  }
  return 0;
}

image.gif

效果显示:

image.gif编辑

以上代码如果我们想要增加程序的功能,只需要添加函数、增加菜单栏内容、if语句的判断条件即可。


以上就是本篇博客的内容,感谢你的阅读

image.gif编辑

Never Give Up

相关文章
|
10天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
24 3
|
21天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
44 0
|
9天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
18 2
|
17天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
24 1
|
23天前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
35 6
|
20天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
21天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
21天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
27天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
20天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
15 0