【C语言进阶】深入理解指针

简介: 【C语言进阶】深入理解指针

字符指针


指向字符的指针称为字符指针,用char*表示。一般使用如下表示方法

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


这里的pc就是一个指向ch的字符指针,还有另一种表示方法:

int main()
{
    const char* pstr = "hello world!";
    printf("%s\n", pstr);
    return 0;
}


注意:这里的pstr的类型是const char*, 字符指针是不能指向字符串的,这里的pstr指向的是常量字符串hello world!的第一个字符h。

例:

#include<stdio.h>
int main()
{
    const char* p1 = "abcdef";
    const char* p2 = "abcdef";
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    if(p1 == p2)
        printf("p1==p2\n");
    else
        printf("p1!=p2\n");
    if(p3 == p4)
        printf("str1==str2\n");
    else
        printf("str1!=str2\n");
    return 0;
}

问最终输出的结果是什么?

  由于p1,p2指向的是常量字符串abcdef的首字符地址,因此p1,p2的内存放的地址是相同的,所以第一个输出p1==p2,str1和str2是在栈区分别开辟两块空间存放两个相同的字符串,因此str1和str2是两个不同的地址,所以输出str1!=str2。


指针数组


   存放字符的数组叫做字符数组,存放整型的数组叫做整型数组,以此类推,那么指针数组就存放指针的数组,所以指针数组是数组,这和后面的数组指针要区分,因为数组的创建公式是:

数组类型 变量名[元素个数] = { 初始化内容 };


因此创建指针数组只要把数组类型改一下就可以。例:

char* p1[5] = {0};
int* p2[5] = {0};
float* p2[5] = {0};


数组指针


       整型有整形指针,字符型有字符指针,那么指向数组的指针也是必要存在的,因此,C语言引入了数组指针的概念。

int main()
{
    int arr[10] = {0};
    int (*p)[10] = &arr;
    return 0;
}


  用()让p先与*结合,表示这是一个指针,[10]表示这是一个数组,int表示这个指针指向的数组内的元素是int类型。


       我们知道数组名表示的是首元素地址,那么就会有一个问题,上面的代码中&arr取到的地址不就应该是地址的地址,也就是二级指针了,但是事实不是这样


除以下两种情况外,数组名均表示首元素地址:

1.sizeof(数组名),此时表示的是整个数组,计算的结果是整个数组的大小,单位是字节

2.&数组名,此时表示的是整个数组,取出的是整个数组的地址


数组指针的使用      

       现在我们引入了数组指针的概念,那么数组指针要怎么用呢?下面是一个例子:

#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(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);
    print_arr2(arr, 3, 5);
    return 0
}


如果我们要对二维数组进行传参,就有上面两种方式,print_arr1的方式就是用一个二维数组接收,这种方式显然是中规中矩的,没有问题。但是,第二种方式,我们传过去的参数是二维数组的数组名,这里的数组名表示首元素地址,二维数组的首元素是一个一维数组的地址,所以,我们在设计print_arr2的参数时,我们应该用一个数组指针来接收,这就是数组指针的一种使用方式。


数组传参、指针传参


上面我们说到了二维数组传参的一种方式,那么现在,我们来总结以下数组和指针应该怎么传参,有多少种传参方式。


一维数组传参

void test1(int arr[])//用一维数组接收
{}
void test2(int arr[10])//用一维数组接收
{}
void test3(int* arr)//用一级指针接收
{}
int main()
{
  int arr[10] = { 0 };
  test1(arr);
  test2(arr);
  test3(arr);
  return 0;
}


二维数组传参

void test1(int arr[][10])//用二维数组接收
{}
//多维数组传参的时候,只能省略第一个[]的内容
void test2(int arr[10][10])//用二维数组接收
{}
void test3(int (*arr)[10])//用数组指针接收
{}
int main()
{
  int arr[10][10] = {0};
  test1(arr);
  test2(arr);
  test3(arr);
  return 0;
}


一级指针传参

如果我们要传一个一级指针,我们应该如何设计函数的参数?显而易见,应该用相同类型的一级指针,那么反过来想,如果函数参数是一个一级指针,那么我们可以传什么参数

void test(int* p)
{}
int main()
{
  int a = 0;
  test(&a);//传变量的地址
  int* ptr = &a;
  test(ptr);//传一级指针
  int arr[10] = { 0 };
  test(arr);//传一维数组的数组名
  return 0;
}


二级指针传参

如果函数的参数是一个二级指针,那么我们可以传什么参数

void test(int** p)
{}
int main()
{
  int a = 0;
  int* pa = &a;
  test(&pa);//传一级指针的地址
  int** ppa = &pa;
  test(ppa);//传二级指针
  int* arr[10] = { 0 };
  test(arr);//传指针数组的数组名
  return 0;
}


综上所述,传参的时候,只要传过去的参数与函数的参数类型一致就可以。


函数指针


函数指针的表示

上文中,我们讲到了数组指针,那么函数有没有指针呢?答案是有的。类比于数组指针,指向函数的指针就是函数指针。

56d0ba7421844707ae44efb21f16b5b8.png

从上面这个例子中我们可以看出来函数名和取地址函数名的地址是一样的,那么取出来的地址也就只能用函数指针存放了。一个函数,要有函数的返回值,参数,和函数名三个要素,因此,我们对于函数指针,要表示出函数的参数和函数名,所以函数指针的表示也就显而易见了:

int (*pf)(int, int) = &test;


函数指针的使用

4a7b50429ae646d49fd2c97beb9d3fd3.png

函数指针的调用和函数的调用方法一致,可以理解成用函数指针代替函数,但是值得一提的是,函数指针可以当作参数传递给另一个函数。那问题来了,我们可以直接调用函数,为什么要绕这么一大圈,这么麻烦的调用呢?因为函数指针要和函数指针数组一起使用。


函数指针数组


存放函数指针的数组叫做函数指针数组,函数指针数组的定义方式是怎样的呢?


函数指针数组是一个数组,所以数组名首先要跟[]结合,所以假设数组名为parr,那么首先就有parr1[],这个数组存放的类型是函数指针,假设这个函数指针指向的是一个返回值为int,参数为两个int的函数,数组有十个元素,那么函数指针数组的定义就是int (*parr[10] ) (int ,int)。


函数指针的用途:转移表


这里有一个小例子,用函数指针数组实现一个计算器,由于内容过多,放在下一篇博客里单独成一份。


指向函数指针数组的指针


指向函数指针数组的指针是一个指针,指针指向的是一个数组,数组内存放的是函数指针。

所以定义方式如下:

54dc73b416d04b5e9b9ef1e4fe1e8ae4.png

后面就是无限的“套娃”了,就不过多赘述。


回调函数


回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

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