C语言——指针详解(进阶)

轻松学会C语言指针

前言:
学完 C语言初阶后,应该对指针有了初步的了解,下面学习进阶的内容,让我们更快的掌握C语言指针。
指针的概念:

  1. 指针就是一个变量,用来存放地址,地址与内存空间一一对应
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)
  3. 指针是有类型的,指针的类型决定了指针的±整数的步长,指针解引用操作的权限。
  4. 指针变量的类型和它所指向变量的类型必须一致
  5. 指针变量中只能存放地址,不能和整型变量混淆

一、字符指针

字符指针:char*

const char* p="hello word";

上面代码的意思是把一个常量字符串的首字符h的地址放到指针变量p中。

二、数组指针

整型指针------指向整型变量的指针

字符指针------指向字符型变量的指针

数组指针------指向数组的指针

2.1 数组指针的定义

int (*p) 10;

说明:p先和 * 结合,说明p是一个指针变量,指针变量指向的是一个大小为10个整型的数组。因此,p是一个指针,指向一个数组,也叫数组指针。

2.2 &数组名VS数组名

&数组名和数组名有什么区别?

arr是数组名,数组名表示数组首元素的地址

&arr是数组的地址,而不是数组首元素的地址

例1:

c 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("%p", arr);
    printf("%p", &arr);
    return 0;
}

地址虽然一样,但是意义不一样

例2:

c 复制代码
#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,跳过整个数组的大小,所以&arr+1,相对于&arr跳过了40个字节。

数组首元素的地址+1,跳过了一个元素的大小,所以arr+1,相当于arr跳过了4个字节。

2.3 数组指针的使用

数组指针指向的是数组,数组指针中存放的是数组的地址

数组指针的使用

c 复制代码
#include <stdio.h>
void arr1(int arr[][5], int row, int col)
{
    int i, j;
    for (i = 0; i < row; i++)
        for (j = 0; j < col; j++)
            printf("%d ", arr[i][j]);
    printf("\n");
}
void arr2(int(*arr)[5], int row, int col)
{
    int i, j;
    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 };
    arr1(arr, 3, 5);
    //数组名arr表示数组首元素地址
    //二维数组的首元素地址是二维数组的第一行
    //这里传递的arr,就是第一行的地址,就是一维数组的地址
    //可以用数组指针来接收
    arr2(arr, 3, 5);
    return 0;
}

打印的结果

三、指针数组

整型数组------存放整型的数组

字符数组------存放字符型数据的地址

指针数组------存放指针的数组

int* arr110;//整型指针数组

char* arr24;//一级字符指针的数组

char** arr35;//二级字符指针的数组

四、数组参数和指针参数

函数参数的设置

4.1 一维数组传参

c 复制代码
#include <stdio.h>
void test(int* p)//对的
{}
void test(int arr[10])//对的
{}
void test(int arr[])//对的
 {}
void test1(int** arr1)//对的
{}
void test1(int* arr1[20])//对的
{}
int main()
{
    int arr[10] = { 0 };
        int *arr1[20] = { 0 };
        test(arr);
        test1(arr1);
    return 0;
}

4.2 二维数组传参

c 复制代码
#include <stdio.h>
void test(int arr[][5])//形参部分,行可以省略,但是列不可以省略
{}//对的
void test(int  arr[3][5])
{}//对的
void test(int (*arr)[5])//数组指针
{}//对的
void test(int *arr[5])//存放指针的数组
{}//错的
void test(int arr[][])
{}//错的
void test(int **arr)
{}//错的
void test(int *arr)
{}//错的
int main()
{
    int arr[3][5] = {0};
    test(arr);//首元素的地址,一行的地址
    return 0;
}

总结:形参和实参的类型要一样;

二维数组传参,函数形参的设计只能省略第一个\[\]的数字,因为对于一个二维数组,可以不知道有多少行但必须知道一行有多少元素。

4.3 一级指针传参

c 复制代码
#include <stdio.h>
void test(int* p, int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
        printf("%d ", *(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]);
    test(p, sz);//一级指针p,传给函数
    return 0;
}
c 复制代码
void test(char* p)
{}
char ch='9';
char* ptr=&ch;
char arr[]="abcdef";
参数为一级指针的时候接收什么参数
test(&ch);
test(ptr);
test(arr);

4.4 二级指针传参

c 复制代码
void test(char** p)
{}

char n='p';
char* p1=&n;
char** p2=&p1;
char* arr[5];
参数为二级指针的时候接收什么参数
test(&p1);
test(p2);
test(arr);

五、函数指针

  • 如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码开辟一段存储空间。
  • 这段存储空间的首地址就称为函数的地址 ,而且函数名表示的就是这个地址
  • 既然是地址,我们就可以定义一个指针变量来存放,这个指针变量就叫做函数指针变量 ,简称为函数指针

函数指针定义的形式:

数据类型符(*函数名)(形参表);
**注意:**形参表可以省略。

c 复制代码
#include <stdio.h>
void test()
{
    printf("hehe");
}
int main()
{
    printf("p\n", test);//函数名是函数的首地址
    printf("p\n", &test);//&函数名也是函数的地址
    return 0;
}

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

pf是函数指针变量

分享两段有趣的代码

代码1:

( *( char( * )())0)()

0被强制转换为函数指针类型

调用0地址处的这个函数

代码2:

void(*sig(int,void ( * )(int)))(int)

sig是一个函数声明

sig函数有两个参数,第一个参数类型是int,第二个参数类型是void( * )(int)函数指针类型

该函数指针指向的函数有一个int类型的参数,返回类型为void

可以给代码2简化:

typedef void ( * pf )(int);

pf sig(int,pf);

六、函数指针数组

把函数的地址存到数组中,这就叫做函数指针数组

形式:

int(*pf10)();

pf是个数组,数组里面的内容是int( *)()类型的函数指针。

函数指针数组的用途:转移表

例子:

c 复制代码
#include <stdio.h>
void menu()
{
    printf("*******************\n");
    printf("**1.加法   2.减法**\n");
    printf("**3.乘法   4.除法**\n");
    printf("*******************\n");
}
int add(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;
    int ret = 0;
    int a,  b;
    int (*py[5])(int a, int b) = { 0,add,sub,mul,div };//定义一个函数指针数组,5个元素,每个元素存放一个函数
    do
    {
        menu();//菜单
        scanf("%d", &input);
        if (input >= 1 && input <= 4)//根据取值范围,进入函数
        {
            printf("请输入操作数:\n");
            scanf("%d %d", &a, &b);
            ret = (*py[input])(a, b);//调用函数指针数组
            printf("%d\n", ret);
        }
        else if (input == 0)
            ;
        else
            printf("输出错误,请重新输入:\n");
    } while (input);//非零的值就一直循环
}

七、指向函数指针数组的指针

指向函数指针数组的指针是一个指针 ,指针指向一个数组,数组的元素都是函数指针

int(*pf)(int,int); //函数指针

int(*p5)(int,int); //函数指针数组

int( *(*pff)5)(int,int)=&p ; //函数指针数组的地址

p就是指向函数指针数组的指针

c 复制代码
#include <stdio.h>
void test(const char* str)
{
    printf("%s\n", str);
}
int main()
{
//函数指针pf
    void(*pf)(const char*) = test;//pf是函数指针变量
    //函数指针数组pff
    void (*pff[10])(const char* str);//pff是存放函数指针的数组
    pff[0] = test;//函数名是函数的地址,&函数名也是函数的地址
    //指向函数指针数组pff的指针pfff
    void (*(*pfff)[5])(const char*)=&pff;//pfff是指向函数指针数组的指针
    return 0;
}

八、回调函数

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

例子:

使用回调函数,模拟实现qsort(冒泡的方式)

c 复制代码
#include <stdio.h>
//回调函数
int cmp(const void* p1, const void* p2)
{
    return *(int*)p1-*(int*)p2;
}
void _swap(void* p1, void* p2,  int size)
{
    int i = 0;
    for (i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1+i)= *((char*)p2 + i);
        *((char*)p2 + i) =tmp;
    }
}

void bubble(void* ba,int count,int si,int(*cmp)(void*, void*))//函数指针调用的函数,函数指针作为参数传递给另外的函数
{
    int i = 0, j = 0;
    for (i = 0; i < count - 1; i++)
    {
        for (j = 0; j < count - 1; j++)
        {
            if (cmp((char*)ba + j * si, (char*)ba + (j + 1) * si) > 0)
                _swap((char*)ba + j * si, (char*)ba + (j + 1) * si, si);
        }
    }
}
int main()
{
    int arr[] = { 11,2,32,4,5,61,7,81,9 };
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
return 0;
}
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨8 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区10 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜11 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa11 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio