指针的进阶(上)

简介: 指针的进阶(上)

我们在指针初阶已经了解了指针的概念,


1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

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

3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4. 指针的运算

那今天来了解一下指针进阶部分。


1. 字符指针


在指针的类型中我们知道有一种指针类型为字符指针 char*;

例:

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


还有

int main()
{
    const char* pstr = "hello world";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

const char* pstr = "hello world" 这一句代码是将整个hello world 存入字符指针 pstr 中了吗?           事实上pstr仅仅只是存了 ‘ h ’  的地址,它是通过 ‘ h ’ 的地址去找后面其他字母的地址。


 看这样一个面试题

#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;
}

a93777ed3a0543abbcd61a1e9ac25e41.png


事实上 == 是比较左右两边的地址。那么str1 和 str2 好理解,开辟了两个空间,必然不相等。

而str3 和str4它们存储的都是首字符的地址,所以它们的地址相同。


2. 指针数组


我们已经在之前讲到过了,这里就简单复习一下


int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组


3. 数组指针


3.1定义


数组指针是指向数组的指针,他的本质是指针。我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针。


int *p1[10];

int (*p2)[10];

//p1, p2分别是什么?


我们得先了解* 和 [ ] 谁的优先级高,可以查一下,是 [ ] 的优先级高于 * 所以 p1 会先和 [ ] 结合,再与 * 结合 那么第一行代码就相当于 (int *) p1 [ ] ,是一个数组,每个元素都是一个 int * 类型。第二行代码 ,我们先让 p2 与 * 结合,表示它是一指针,而指针指向的是一个int [ 10 ] 的数组。


3.2 &数组名VS数组名

假设:

int arr[10];


&arr  和 arr 有什么区别呢?

其实,我们之前也已经说到过了 &arr 和 arr都表示首元素的地址,但是它们的含义不一样


bee011c0a66d4276a1522904d71d2372.png


可见arr + 1 只跳过了四个字节,而&arr + 1 却跳过了 40 个字节,刚刚好为整个数组。

3.3 数组指针的使用


那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0;
}

一个数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
}
void print_arr2(int(*arr)[5], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    for (int j = 0; j < col; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
}
int main()
{
  int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
  print_arr1(arr, 3, 5);
  //数组名arr,表示首元素的地址
  //但是二维数组的首元素是二维数组的第一行
  //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
  //可以数组指针来接收
  print_arr2(arr, 3, 5);
  return 0;
}


4. 数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢

4.1 一维数组传参


#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
}


这些形式都是正确的。

4.2 二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?  不ok,连编译都无法通过
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok? 这种写法是数组指针的形式
{}
void test(int** arr)//ok?
{}
int main()
{
  int arr[3][5] = { 0 };
  test(arr);
}

4.3 一级指针传参


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


5. 函数指针


先看一段代码


#include <stdio.h>
void test()
{
  printf("hehe\n");
}
int main()
{
  printf("%p\n", test);
  printf("%p\n", &test);
  return 0;
}


2fbb03e5e5c14b3987d7440ae46eb1a5.png


我们可以看到,&函数名和函数名都表示同一个地址。                                                                   那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();


首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?

答案是:

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。


6. 函数指针数组


数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?


int (*parr1[10])();

int *parr2[10]();

int (*)() parr3[10];


答案是第一个, [  ]先和parr1结合,说明parr1是个数组,它的类型是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");
      break;
    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;
}

我们用函数指针数组去保存了加减乘除函数的地址,很大程度的减低了代码冗余。

相关文章
|
4月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
4月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
4月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
4月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
4月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
4月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
42 4
|
4月前
指针进阶(3)
指针进阶(3)
36 1
|
4月前
|
C++
指针进阶(1)
指针进阶(1)
40 1
|
4月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
40 2
|
4月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
43 0