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;

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

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

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
56 0
|
20天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
73 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
20天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
45 9
|
20天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
40 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
105 13
|
24天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
24天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
78 3
|
25天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
52 11
|
24天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
35 1