深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)(1)

简介: 深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)(1)

💤 本章介绍

可能有伙伴就要问了,咋一来就进阶指针!

不要慌问题不大,bug郭之前就写个一篇博客,介绍指针基础知识!

有兴趣的伙伴可以点击查看C语言指针,楼下大爷都能学会的小细节(和bug郭一起学C系列),建议收藏!

大家都复习完了指针基础吧,那我们就开始指针进阶的学习吧!


指针基础的一些概念:


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

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

指针是有类型,指针的类型决定了指针的+-

整数的步长,指针解引用操作的时候的权限。

指针的运算。

本章重点

6. 字符指针

7. 数组指针

8. 指针数组

9. 数组传参和指针传参

10. 函数指针

11. 函数指针数组

12. 指向函数指针数组的指针

13. 回调函数

14. 指针和数组面试题的解析


字符指针

字符指针顾名思义就是一个指针变量,指针指向的空间存放的是一个字符!


char* 字符指针


//基本用法
int main()
{
  char ch = 'c';
  char* pc = &ch;   
  *pc = 'w';
  return 0;
}

这种基本的用法,bug就不介绍了,相信大家都会!


//进阶
int main()
{ 
  char* pstr = "abcdef"; 
  //pstr字符指针存了字符串,第一个字符(a)的地址
  printf("%s",pstr);
  return 0;
}

代码char* pstr = "abcdef";特别容易让我们以为是把字符串abcedf放到字符指针pstr里了,但是本质是把字符串abcdef 首字符的地址放到了pstr中。

image.png

我们可以知道通过字符指针pstr我们可以找到字符串abedef。

为啥我们不直接创建一个字符串变量,而要用这种方式,有何不同呢?


//测试
#include <stdio.h>
int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    char *str3 = "hello world.";
    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.png


可以看到,字符数组str1和str2不相同,因为str1和str2是数组名,而数组名就是第一个数组的地址。str1和str2分别开辟了两个数组空间,只不过它们存放的内容一样而已!

而str3和str4它们都是指向的同一块空间,因为它们指向的是字符串常量hello world.


这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,

当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

image.png

数组指针

数组指针,我们首先要明确的就是,数组指针是指针而不是数组,它是指向数组的指针!


//判断数组指针
int* arr1[3];  //指针数组
int (*arr2)[3]; //数组指针

我们之前学过C语言操作符,建议收藏,我们知道操作符[]的优先级高于*。

所以arr2是数组指针,而arr1是指针数组。


int (arr2*)[3] : arr2是一个指针,指向的对象是整型数组,数组的元素个数为3


数组指针的使用

#include<stdio.h>
int main()
{
  int arr[4] = { 1,2,3,4 };
  int(*parr)[4] = &arr;  //数组指针存放数组arr的地址!
  return 0;
}

大家肯定很少见代码这么写吧,数组指针很少这样使用!

我们已经知道了数组名就是,数组的首元素地址,而取地址数组名是数组的地址 。

那&arr和arr有啥区别呢?


#include<stdio.h>
int main()
{
  int arr[4] = { 1,2,3,4 };
  int(*parr)[4] = &arr;  
  printf("arr :%p\n",arr);
  printf("&arr:%p\n", &arr);
  return 0;
}

image.png

居然都是第一个元素的地址!

但是我们知道,指针的类型决定了指针加减的步长!


#include<stdio.h>
int main()
{
  int arr[4] = { 1,2,3,4 };
  int(*parr)[4] = &arr;     
  printf("arr :%p\n",arr);
  printf("&arr:%p\n", &arr);
  printf("arr+1 :%p\n", arr+1); //整型指针加1,加一个整型类型大小
  printf("&arr+1:%p\n", &arr+1);//数组指针加1,加一个数组类型大小
  return 0;
}

image.png

可以看到,数组指针和首元素地址,指针的类型不同


数组名arr:指针类型是整型 指针加减1,步长为整型大小(4bit)

&数组名:指针类型是数组 指针加减1,步长为数组大小(16bit)


数组指针正确使用


#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);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

image.png

学了数组指针,是不是发现有点懵了!


//捋一捋
int *arr1[3]; //指针数组
//数组个数是3,元素是int指针类型的数据
int (*arr2)[3];//数组指针
//指针,指向数组,且数组的类型是int类型,且元素个数为3
int* (*arr3)[3]; //数组指针
//指针,指向数组,数组元素是int*类型,且元素个数为3
int (*arr4[3])[3]; //数组指针指针
//指针,指向一个数组指针,数组指针的类型是int(*) [3] 指向数组且为为int类型,元素个数为3
......

就捋到吧,再捋下去就更懵了,兄弟们慢慢学,你可以了的!


数组参数、指针参数

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


一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}  //int arr[] 接收就是以int * arr形式接收,因为*arr等价与 arr[]
void test(int arr[10])//ok?
{}  //int arr[10] 同上在形参中都是一个整型指针,形参中的数组长度无意义
void test(int* arr)//ok?
{} //整型指针接收数组名就是首元素地址也就是整型指针
void test2(int* arr[20])//ok?
{} //int* arr[20]等价于 int* arr[]等价于  int**arr 即二级指针
   //而实参就是一个指向整型指针的指针也就是二级指针
void test2(int** arr)//ok?
{} //二级指针接收
int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr);
    test2(arr2);
}

二维数组传参

void test(int arr[3][5])//ok?
{}  //二维数组传参二维数组接收
void test(int arr[][])//ok?
{} //error 不知道二维数组中一维数组中元素个数
void test(int arr[][5])//ok?
{} //可以省略行不能省略列
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{} //error 二维数组的数组名就是首元素地址,即一维数组的地址,
// 也就是数组指针,应该用数组指针接收 
void test(int* arr[5])//ok?
{}   //error,指针数组,实参是数组指针
void test(int (*arr)[5])//ok?
{}  //实参为数组指针与形参类型相同
void test(int **arr)//ok?
{} //error 
int main()
{
    int arr[3][5] = {0};
    test(arr);
}

我们来总结一下!


二维数组的数组名就是首元素地址,而二维数组的元素就是一维数组,所以数组名的类型就是数组指针。

当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。


一级指针传参

#include <stdio.h>
void print(int *p, int sz)  //一级指针传参,一级指针接收
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d\n", *(p+i));
    }
}
//void print(int p[],int sz) 
//数组接收,也即一级指针接收,不提倡这样写
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;
}

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?


//以int型指针为例
void test(int* p)
{
}
int main()
{
  int x=0;
  int* px=&x;
  int arr[10];
  test(&x);//整型地址
  test(px);//一级指针
  test(arr);//一维数组名,即首元素地址,int*
  return 0;
}

二级指针传参

void test(char** p )
{
}
int main()
{
  char ch = 'c';
  char* pc = &ch;
  char* *ppc = &pc;
  char* arr[3];
  test(&pc); //一级指针的地址,即二级指针
  test(ppc); //二级指针
  test(arr); //数组名,首元素地址,首元素为一级指针,所以为二级指针
  return 0;
}

思考:

当函数的参数为二级指针的时候,可以接收什么参数?


目录
相关文章
|
29天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
84 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
29天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
50 9
|
29天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
44 7
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
119 3
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
44 1
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
146 13
|
3月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
41 0
|
4月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
150 4