大家好,我是小张同学,不知不觉就写到了指针的最后一篇,我把这几篇文章的链接一并放在这里,希望对大家有所帮助!
指针基础、NULL指针、void指针、指针初始化:C语言指针(一)-CSDN博客
指针运算、指针运算表达式分析:C语言指针(二)-CSDN博客
数组和指针、指针数组、二维数组指针:C语言指针(三)-CSDN博客
函数指针:C语言指针(四)-CSDN博客
结构体指针、函数指针补充:C语言指针(五)-CSDN博客
本文主要介绍指针剩下的几类用法
目录
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)。
到此,指针的内容就完结了,虽然看到这里的你还有可能有些懵懂,但别害怕,让我们耐心跟着敲一般,多多实践才能领悟其中的原理所在!