文章目录

  • ​​1.指向指针的指针​​
  • ​​2.高级声明​​
  • ​​3.函数指针​​
  • ​​(1)用途1:回调函数​​
  • ​​(2)用途2:转移表​​
  • ​​4.传递命令行参数​​
  • ​​5.字符串常量​​
  • ​​6.总结​​

1.指向指针的指针

  • 看eg:
int i;
int *pi;
int **ppi;

ppi=π//把ppi初始化为指向变量pi
*ppi=&i;//把pi(通过ppi的间接访问)初始化为指向变量i,这里的*ppi指针就是pi指针

//变量i是一个整数,pi是一个指向整型的指针,ppi是一个指向pi的指针,所以它是一个指向整型的指针的指针。
等价于://按照我下面的写法比较好
pi=&i;
ppi=π
  • 经过上面的两条语句后,变量变成下面的样子:
  • 现在,下面的语句的赋值效果是一样的
i='a';
*pi='a';
**ppi='a';

2.高级声明

int f;//一个整型变量
int *f;//一个指向整型的指针,表达式*f声明为一个整数,f是指向整型的指针
int *f();//函数调用的操作符()的优先级高于间接访问的操作符,so,f是一个函数,它的返回值类型是一个指向整型的指针;
int (*f)();//第一个括号迫使间接访问在函数调用之前进行,so,f成为了一个函数指针,它所指向的函数返回一个整数值;
int *(*f)();//f是一个函数指针,函数的返回值是一个指针;

int *f[];//下标的优先级高于间接访问的操作符,so,f是一个数组,它的元素类型是指向整型的指针;
int f()[];//非法!f是一个函数,返回值是整型数组,而函数不能返回数组
int f[]();//非法!f似乎是一个数组,它的元素类型是返回值为整型的函数。而数组元素必须有相同的长度,但不同的函数显然
可能具有不同的长度

int (*f[])();//f是一个元素为某种类型的指针的数组,函数元素的类型是函数指针,它所指向的函数的返回值是一个整型值;
int *(*f[])();//声明创建了一个指针数组,指针所指向的类型是返回值为整型指针的函数

//ANSI C提倡的函数原型的风格如下
int (*f)(int ,foat);//f声明为一个指针,它所指的函数接受两个参数:一个int,一个float,并返回一个int值
int (*g[])(int, float);//g声明为一个数组,数组的元素类型是一个函数指针,它所指向的函数接受两个参数:一个int
,一个float,并返回一个int型指针

3.函数指针

(1)用途1:回调函数

  • 初始化函数指针
int f(int);//函数f的原型
int (*pf)(int)=&f;//创建函数指针pf,并把它初始化为指向函数f;
//初始化表达式的&操作符是可选的,因为函数名被使用时,总是由编译器把它转化为函数指针
  • 初始化函数指针,如何调用函数?
ans=f(25);//使用名字调用函数f,函数名f首先被转换为一个函数指针,该指针指定函数在内存中的位置,然后,函数调用操
作符来调用该函数,执行开始于这个地址的代码

ans=(*pf)(25);//将函数指针转换为一个函数名。其执行效果和上面的语句类似

ans=pf(25);//间接访问操作并非必须,因为编译器需要的是一个函数指针。其显示了函数指针通常是如何使用的。
  • 回调函数callback function
    定义:用户把一个函数的指针作为参数传递给其他函数,后者将回调用户的函数。
    *我们无法在上下文环境中为回调函数编写一个准确的原型,所以把其参数类型声明为void ,表示一个指向未知类型的指针
  • eg:
//在一个单链表中查找一个指定值的函数。它的参数是一个指向链表的第一个节点的指针,一个指向我们需要查找的值的指针和一个函数指针,
//他所指向的函数用于比较存储于链表中的类型的值。
#include <stdio.h>
#include "node.h"

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

//用于整型链表中查找的比较函数如下,将指向该函数的指针和指向需要查找的值的指针传递给serach_list函数
//比较函数的参数必须声明为void*以匹配serach_list函数的原型,然后它们再强制转换为int*类型,用于比较整型值。
int (*compare)(void const *a, void const *b)
{
if (*(int *)a==*(int *)b)
return 0;
else
return 1;
}

//使用
desired_node=serach_list(root, &desired_value, compare_ints);

(2)用途2:转移表

  • 下面是实现一个计算器
输入两个数op1和op2和一个操作符oper,来选择调用哪个函数
switch (oper)
{
case ADD:
result=add(op1,op2);
break;
case SUB:
result=sub(op1,op2);
break;
case MUL:
result=mul(op1,op2);
break;
case DIV:
result=div(op1,op2);
break;
。。。
}
优点:把具体操作和选择操作的代码分开是一种良好的设计方法。
缺点:若由上百个操作符,switch会很长
  • 转换表:转换表就是一个函数指针数组
创建一个转换表:
(1)声明并初始化一个函数指针数组,要确保这些函数的原型出现再这个数组的声明之前
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
...

//初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。
//假定ADD=0,SUB=1,MUL=2
double (*oper_func[])(double, double)={add,aub,mul,div,。。。}

(2)用下面的语句替换前面的整条switch语句
result=oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数

4.传递命令行参数

  • C程序的main函数具有两个参数:第1个参数称之为argc,表示命令行参数的数目,第2个参数称为argv,它指向一组参数值,argv指向这组参数值(本质是一个数组,这些元素的每个都是指向一个参数文本的指针)的第1个元素
int main(int argc, char **argv)

//命令行如何传递
cc -c -o main.c insert.c -o test

argv是一个指针数组,这个数组的每个元素都是一个字符指针,数组的末尾是一个NULL指针
argv指向数组的第一个元素,所以被声明为指向字符的指针的指针的原因
第一个参数是程序的名称

(第13章)高级指针话题_函数指针

  • 打印命令行参数的eg,类似于unix的echo命令
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char**argv)
{
//打印参数,直到遇到NULL指针,程序名被跳过
while (*++argv!=NULL)
printf("%s\n", *argv);
return EXIT_SUCCESS;
}

//上面的代码把每个参数都与表示列表末尾的NULL指针进行比较
//程序名被跳过了。。
//printf的%s格式码要求:参数是一个指向字符的指针,printf假定该字符是一个以NULL字符结尾的字符串的第1个字符。
  • 命令行参数和命令行参数前面的选项参数
命令行参数前面的选项参数:每个选项都以一条横杠开头,后面是一个字母
如果命令行中没有文件名,就对标准输入进行处理
prog -a -b -c name1 name2 name3
  • 如何区分选项参数和文件名参数?
//处理命令行参数
#include <stdio.h>
#define TRUE 1

//执行实际任务的函数的原型
void process_standard_input(void);
void process_file(char *file_name);

int option_a, option_b;

void main(int argc, char **argv)
{
//处理选项参数
while (*++argv!=NULL && **argv=='-')
{
/*
switch语句和下面的语句时等价的。若字符非NULL,那么就像前面一样使用switch语句
来设置合适的变量。
while((opt=*++*(argv)!='\0'))
{
switch(opt)
{
case 'a':
option_a=TRUE;
break;
}
}
*/
//检查横杠后面的字母
switch(*++(*argv))//第一个*访问argv所指的位置,然后进行自增操作,最后一个*操作根据自增后的指针进行访问
{
case 'a':
option_a=TRUE;
break;
case 'b':
option_b=TRUE;
break;
}
//处理文件名参数
//当不再存在其他选项时,程序就处理文件名。如果argv指向NULL指针,命令行参数里就没有别的东西了,
//程序就处理标准输入。
//否则,程序就逐个处理文件名。
if (*argv==NULL)
process_standard_input();
else
{
do
{
process_file(*argv);
}while(*++argv!=NULL);
}
}
}
  • 访问参数和访问参数中的下一个字符的图如下,

5.字符串常量

  • 当一个字符串常量出现于表达式中时,它的值是一个指针常量。编译器把这些指定字符的一份拷贝再内存的某个位置,并存储一个指向第1个字符的指针。
    当数组名用于表达式时,它们的值也是指针常量。我们可以对他们进行下标引用,间接访问,以及指针运算。
"xyz"+1
表示:指针值+1,结果是个指针,指向字符串的第二个字符:y

*"xyz"
表示:因为对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型时”指向字符的指针“,所
以该结果就是:他所指向的字符:x,

"xyz"[2]
表示:表达式的值就是字符z

*("xyz"+4)
表示:因为偏移量4超出了这个字符串的范围,所以该表达式的结果时一个不可预测的字符
  • 接受一个整型值,把他转换为字符的eg
//接受一个整型值,把他转换为字符,并打印出来,前导零被去除
void binary_to_ascii(unsigend int value)
{
unsigend int quotient;
quotient=value/10;
if (quotient!=0)
binary_to_ascii(quotient);//把二进制值转换为字符并把它们都打印出来
putchar(value%10+'0');
}

代码要求:
以十六进制的形式打印结果值
remainder=value%16的余数可能是0-15的任何值,10-15的值应该以字母A-F来表示
在任何常见的字符集中,字母A-F并不是立即位于数字的后面

典型的方法如下:
remainder=value%16;
if (remainder<10)
putchar(remainder+'0');
else
putchar(remainder-10+'A');


又一种方法如下:
putchar("0123456789ABCDEF"[value%16]);
  • 神秘函数
//参数是一个0-100的值
#include <stdio.h>

void mystery(int n)
{
n+=5;
n/=10;
printf("%s\n","*********"+10-n);
}
//若参数为0,就打印0个星号
//若参数为100,就打印10个星号
//位于0-100的参数值就打印出0-10个星号

6.总结

  • 一个指针变量可以指向另一个指针变量,一个指向指针的指针在它使用之前必须进行初始化
  • int *a; *a声明为一个整型,a是一个指向整型的指针
  • 函数指针:函数指针可以实现回调函数,一个指向回调函数的指针作为参数传递给另一个函数,后者使用这个指针调用回调函数;转移表也可以使用函数指针,转移表像switch语句一样执行选择,转移表由一个函数指针数组组成(这些函数必须具有相同的原型),函数通过下标选择某个指针,再通过指针调用对应的函数。
  • 命令行参数:argc和argv。他们传递给main函数,argc是一个整数,用于表示参数的数量,argv是一个指针,它指向一个序列的字符型指针,该序列中的每个指针指向一个命令行参数,该序列以一个NULL指针作为结束标志。
    第一个参数是程序的名字。
  • 字符串常量就是一个常量指针,它指向字符串的第1个字符。和数组名一样,可以用指针表达式,也可以用下标来使用字符串常量。

参考:<C和指针>