C语言指针(十日终焉版)

大家好,我是小张同学,不知不觉就写到了指针的最后一篇,我把这几篇文章的链接一并放在这里,希望对大家有所帮助!

指针基础、NULL指针、void指针、指针初始化:C语言指针(一)-CSDN博客

指针运算、指针运算表达式分析:C语言指针(二)-CSDN博客

数组和指针、指针数组、二维数组指针:C语言指针(三)-CSDN博客

函数指针:C语言指针(四)-CSDN博客

结构体指针、函数指针补充:C语言指针(五)-CSDN博客

本文主要介绍指针剩下的几类用法

目录

1. 命令行参数

2. 指针类型转换

2.1 示例 1

2.2 示例 2 大小端判断

2.3 示例 3 malloc 函数

2.4 示例 4 回调函数

2.5 示例 5 指针和整型强制类型转换

3. 复杂指针分析


1. 命令行参数

main函数也可以传入数据,如下:

int main(int argc, char *argv[])
int main(int argc, char **argv)

以上两种写法都可以,使用命令行传参时,传入的数据以字符串的形式存在,多份数据之间以空格分隔,即,用户输入的多份数据在程序中表现为多个字符串。

argc表示传递的字符串的数目,argv是一个指针数组,每个指针指向一个字符串,或者也可以将argv写成二级指针形式。

这两个参数通常取名为 argc 和 argv,也可以取其他的名字,如果你喜欢的话。

举个例子,如下:

#include <stdio.h>

int main(int argc, char **argv)
{
    printf("Receive &d parameters\n", argc);
    for(int i = 0; i < argc; i++)
    {
        printf("%d %s\n", i, argv[i]);
    }
    return 0;
}

运行结果如下:

PS F:\19_learning\1_C_Programming> ./argc.exe liang chen
Receive 3 parameters
0 F:\19_learning\1_C_Programming\argc.exe
1 liang
2 chen
PS F:\19_learning\1_C_Programming>

从运行结果可以看出,包括程序名以及它后面的字符串都会被程序接收,程序名就是第一个参数

2. 指针类型转换

在之前所举的例子中,赋值号左边是一个指针变量,赋值号右边是一个指针表达式,指针变量和指针表达式的类型应当是一致的,如果不一致就要进行强制类型转换。

对于指针类型转换需要明白的是:类似于普通变量之间的强制类型转换,普通变量进行强制类型转换时,只是改变了对内存中二进制的解读方式,指针的强制类型转换改变了访问内存的方式

举几个例子:

2.1 示例 1

typedef struct node
{
    int x;
    char c;
    long long l;
}Node;

int main()
{
    Node nodeInfo;
    nodeInfo.x = 300;
    nodeInfo.c = 120;
    nodeInfo.l = 500;

    Node *pNode = &nodeInfo;
    char *p = (char*)&nodeInfo;
    
    printf("%p, %p\n", pNode, p);
    printf("%d, %d, %d\n", *p, *(p+1), *(p+2));

    return 0;

那如果想让指针 p 指向 结构体 nodeInfo ,赋值号左边 p 的类型为 char*,而表达式 &nodeInfo 的类型是 Node*,两者不一致, 不能直接赋值,必须进行强制类型转换。

对上面这个程序进行编译运行,结果如下:

000000000061FE00, 000000000061FE00
44, 1, 0

可以看出,指针 p 和指针 pNode 的值(地址值)是一样的,那么强制类型转换的结果是,改变了访问内存的方式,char*指针以字节为单位访问内存,每次增加指针 p 的值时,指针会向后移动一个字节。

这段代码实际上在打印 nodeInfo 的前三个字节的值。因为 nodeInfo.x 的值是 300,且在大多数现代计算机上 int 类型占用 4 个字节且采用小端模式(least significant byte first),所以整数 300 的内存表示是 0x2C 0x01 0x00 0x00。这里的 0x2C 等于十进制的 44,0x01 等于十进制的 1,这就是为什么输出是 44, 1, 0

2.2 示例 2 大小端判断

static int isBigEndian(void)
{
    unsigned int x = 1;
    char *c = (char*)&x;
    return (*c == 0);
}

通过强制类型转换,可以使用 char* 指针访问 x 的第一个字节,如果 *c 的值为1,就是小端,如果 *c 的值为0,就是大端。

2.3 示例 3 malloc 函数

int *array;
array = (int*)malloc(10 * sizeof(int));
if(NULL == array)
{
    //提示错误信息
}

比如要创建一个存储10个int类型数据的数组,可以使用malloc函数,此时需要进行强制类型转换。另外在链表或者二叉树中需要动态创建节点时,也会经常用到这种情况。

2.4 示例 4 回调函数

让我们来构造一个场景,希望有一个函数可以在整数链表中查找一个值,如下:

Node* search_list(Node *node, int const value)
{
    while(NULL != node)
    {
        if(node->value == value)
        {
            break;
        }
        node = node->next;
    }
    return node;
}

上面这个函数只适用于 value 值为 int 类型的链表,如果我们需要在一个 value 值为 char 类型的链表中查找,那就需要再写另外一个新函数,新函数和上面的函数绝大部分代码都相同,只是 value 类型不同。

一种更为通用的方法是让查找函数 search_list 与类型无关,这样函数就可以对任何类型的值进行比较,这就需要用到函数指针了。需要编写一个比较函数,然后将这个比较函数的指针作为参数传递给查找函数 search_list,如下:

Node* search_list(Node *node, void const *value, int (*compare)(void const *, void const *))
{
    while(NULL != node)
    {
        if(compare(&node->value, value) == 0)
        {
            break;
        }
        node = node->next;
    }
    return node;
}

我看上面 search_list传入的函数指针不顺眼,来使用typedef 试试,于是改成了下面这样:

typedef int (*compare)(void const *, void const *);          
Node* search_list(Node *node, void const *value, compare pCompare)
{
    while(NULL != node)
    {
        if(pCompare(&node->value, value) == 0)
        {
            break;
        }
        node = node->next;
    }
    return node;
}

如果需要在整数链表中查找,就编写一个比较函数 compare_ints,如下:

int compare_ints(void const *a, void const *b)
{
    if(*(int*)a == *(int*)b)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}
//注意这里的强制类型转换

这个比较函数就要像下面这样使用:

desired_node = search_list(root, &desired_value, compare_ints);

我们上面使用就是回调函数,回调函数就是将一个函数指针作为参数传递给其他函数,后者将"回调"这个函数。

如果希望在字符串链表中查找,就使用以下代码:

desired_node = search_list(root, "desired_value", strcmp);

2.5 示例 5 指针和整型强制类型转换

假设变量a存储在位置100,那下面这条语句的作用是什么?

*100 = 25;

它看上去像是把25赋值给a,我一开始也是这么认为的,但是,这是错的!

实际上,这条语句就是错的,字面值100的类型是整型,间接访问操作只能用于指针类型,如果想把25存储在位置100,就必须强制转换。

*(int*)100 = 25;

实际上,使用这种技巧的机会非常少,因为我们无法预测编译器会把某个特定的变量存在什么位置,所以无法预知它的地址。

直接对寄存器进行访问时,可能会遇到将整型强转为指针的情况。

3. 复杂指针分析

在这篇文章里面提到了几个复杂指针,还有一些容易与指针混淆的,一并总结如下:

int p[3];      //数组
int *p, f;     //p是指针,f是int类型
int *p[3];     //指针数组
int (*p)[3];   //二维数组指针
int **p;       //二级指针
int p(int);    //函数,参数为int,返回int
int *p(int);   //指针函数,参数为int,返回int*
int (*p)(int); //函数指针,指向参数为int,返回int的函数
int* (*p)(int);//函数指针,指向参数为int,返回int*的函数
int (*f[])(int);  //函数指针数组
int* (*f[])(int); //函数指针数组

对于复杂的指针,应该从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键,然后按照运算符的优先级顺序进行解析。

举个例子。

int *p[3],从 p 开始理解,它的左边是 *,右边是[ ],[ ] 的优先级高于 *,所以先运算p[3],表明p是一个拥有三个元素的数据,然后 int* 是元素的类型,所以 p 是一个指针数组。

int (*p)[3],从 p 开始理解,( )的优先级最高,先运算 (*p),表明p是一个指针,然后 int[3] 是 p 指向的数据类型,即 p 指向的是一个数组,那么 p 就是一个二维数组指针。

注意,数组指针的类型实际上和数组的元素类型有关

int *p(int),从 p 开始理解,( ) 的优先级高于 *,所以先运算p(int),表明 p 是一个函数,参数类型为int,然后前面的 int* 为函数返回值类型,所以p是一个指针函数。

int(*p)(int),从p开始理解,先运算(*p),表明p是一个指针,然后后面 (int) 表明p指向的是一个函数,括号中的 int为函数参数类型,开头的int为函数返回类型,所以p是一个指向原型为 int func(int) 的函数的指针,int* (*p)(int)类似,只不过是指向原型为 int* func(int) 的函数的指针。

int (*f[ ])(int),从 f 开始理解,先运算(*f[ ]),在(*f[ ])中,[ ]的优先级更高,f[ ]表明 f 是一个数组,*f[ ] 表明这是一个指针数组,然后剩下的 int(int)是指针数组中每个指针所指向的类型,是一个原型为 int func(int) 的函数,所以 f 是一个函数指针数组,int* (*f[ ])(int)类似,只不过函数原型为 int* func(int)。

到此,指针的内容就完结了,虽然看到这里的你还有可能有些懵懂,但别害怕,让我们耐心跟着敲一般,多多实践才能领悟其中的原理所在!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值