C语言的本质(12)——指针与函数
往往,我们一提到指针函数和函数指针的时候,就有很多人弄不懂。下面详细为大家介绍C语言中指针函数和函数指针。
1、指针函数
当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。
格式:
类型说明符 * 函数名(参数)
由于返回的是一个地址,所以类型说明符一般都是int。
在C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元存放一个地址值,而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:
#include <stdio.h> void say_hello(const char *str)
{
printf("Hello %s\n", str);
} int main(void)
{
void (*f)(const char *) = say_hello;
f("Guys");
return 0;
}
分析一下变量f的类型声明void (*f)(const char *),f首先跟*号结合在一起,因此是一个指针。(*f)外面是一个函数原型的格式,参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello的参数是const char *,返回值是void,正好是这种函数,因此f可以指向say_hello。注意,say_hello是一种函数类型,而函数类型和数组类型类似,做右值使用时自动转换成函数指针类型,所以可以直接赋给f,当然也可以写成void (*f)(const char *) = &say_hello;,把函数say_hello先取地址再赋给f,就不需要自动类型转换了。
可以直接通过函数指针调用函数,如上面的f("Guys"),也可以先用*f取出它所指的函数类型,再调用函数,即(*f)("Guys")。可以这么理解:函数调用运算符()要求操作数是函数指针,所以f("Guys")是最直接的写法,而say_hello("Guys")或(*f)("Guys")则是把函数类型自动转换成函数指针然后做函数调用。
2、函数指针
指向函数的指针包含了函数的地址,可以通过它来调用函数。声明格式如下:
类型说明符 (*函数名)(参数)
其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明笔削和它指向函数的声明保持一致。
指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。
例如:
void (*fptr)();
把函数的地址赋值给函数指针,可以采用下面两种形式:
fptr=&Function;
fptr=Function;
取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
可以采用如下两种方式来通过指针调用函数:
x=(*fptr)();
x=fptr();
第二种格式看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。下面举一个例子:
void (*funcp)();
void FileFunc(),EditFunc();
int main(void)
{
funcp=FileFunc;
(*funcp)();
funcp=EditFunc;
(*funcp)();
}
void FileFunc()
{
printf(\"FileFunc\\n\");
}
void EditFunc()
{
printf(\"EditFunc\\n\");
}
程序输出为:
FileFunc
EditFunc
下面再举几个例子区分函数类型和函数指针类型。首先定义函数类型F:
typedef int F(void);
这种类型的函数不带参数,返回值是int。那么可以这样声明f和g:
F f, g;相当于声明:
int f(void);
int g(void);
下面这个函数声明是错误的:
F h(void);
因为函数可以返回void类型、标量类型、结构体、联合体,但不能返回函数类型,也不能返回数组类型。而下面这个函数声明是正确的:
F *e(void);
函数e返回一个F *类型的函数指针。如果给e多套几层括号仍然表示同样的意思:
F *((e))(void);
但如果把*号也套在括号里就不一样了:
int (*fp)(void);
这样声明了一个函数指针,而不是声明一个函数。fp也可以这样声明:
F *fp;
3、指针类型的参数和返回值
首先看下面的程序:
#include <stdio.h> int *swap(int *px, int *py)
{
inttemp;
temp= *px;
*px= *py;
*py= temp;
returnpx;
} int main(void)
{
inti = 10, j = 20;
int*p = swap(&i, &j);
printf("nowi=%d j=%d *p=%d\n", i, j, *p);
return0;
}
我们知道,调用函数的传参过程相当于用实参定义并初始化形参,swap(&i, &j)这个调用相当于:
int *px = &i;
int *py = &j;
所以px和py分别指向main函数的局部变量i和j,在swap函数中读写*px和*py其实是读写main函数的i和j。尽管在swap函数的作用域中访问不到i和j这两个变量名,却可以通过地址访问它们,最终swap函数将i和j的值做了交换。
上面的例子还演示了函数返回值是指针的情况,return px;语句相当于定义了一个临时变量并用px初始化:
int *tmp = px;
然后临时变量tmp的值成为表达式swap(&i,&j)的值,然后在main函数中又把这个值赋给了p,相当于:
int *p = tmp;
最后的结果是swap函数的px指向哪就让main函数的p指向哪。我们知道px指向i,所以p也指向i。
C语言的本质(12)——指针与函数的更多相关文章
- 【C语言】14-返回指针的函数与指向函数的指针
前言 前面我们花了接近3个章节学习指针,应该都感受到指针的强大了吧.指针可以根据地址直接操作内存中的数据,使用得当的话,不仅能使代码量变少,还能优化内存管理.提升程序性能.关于指针的内容还非常多,比如 ...
- 我的c++学习(12)指针作为函数参数
◆ 引用调用与指针传值调用C++中函数的参数的基本使用方法是传值.为了弥补单纯传值的不足,以引用作为函数的参数,从逻辑上讲引用是别名,在函数中对参数的操作,就是对实参的操作,而在物理上是传实参的地址. ...
- go语言基础之数组指针做函数参数
1.数组指针做函数参数 示例: package main //必须有个main包 import "fmt" //p指向实现数组a,它是指向数组,它是数组指针 //*p代表指针所指向 ...
- C语言的本质(18)——函数的可变参数
一般而言,在设计函数时会遇到许多数学和逻辑操作,是需要一些可变功能.例如,计算数字串的总和.字符串的联接或其它操作过程. 实现一个函数,要求在函数中计算传入的所有参数之和,并输出到屏幕上.这个函数实现 ...
- C语言的本质(16)——函数接口的传入参数与传出参数
如果函数接口有指针参数,既可以把指针所指向的数据传给函数使用(称为传入参数),也可以由函数填充指针所指的内存空间,传回给调用者使用(称为传出参数),例如strcpy的函数原型为 char *strcp ...
- C语言笔记 07_枚举&指针
emum(枚举) 枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读. 枚举语法定义格式为: enum 枚举名 {枚举元素1,枚举元素2,--}; 举个例子,比如:一星期有 7 天,如果 ...
- 【C语言】学不会的指针
指针 前言: 指针是C语言程序的核心,刚开始学指针,嗯....这样呀,貌似不难呀:之后开始用指针,&p,p,*p,**p,这些指针在用的时候,额.....什么东东?每次都要想半天,特别是遇到双 ...
- 12-返回指针的函数&&指向函数的指针
前言 接下来我只讲指针的最常见用法,比如这一章的内容----返回指针的函数 与 指向函数的指针 一.返回指针的函数 指针也是C语言中的一种数据类型,因此一个函数的返回值肯定可以是指针类型的. 返回 ...
- C语言的本质(15)——C语言的函数接口入门
C语言的本质(15)--C语言的函数接口 函数的调用者和其实现者之间存在一个协议,在调用函数之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能. 函数接口通过函数名,参数和返 ...
随机推荐
- 禁止Chrome浏览器缓存的方法
web开发的人经常chrome和firefox作为开发调试工具,有些时候需要禁止chrome浏览器缓存,最近也用到禁止缓存,以下介绍几种禁止chrome浏览器缓存的方法作为记录. HTML: < ...
- I/O事件
I/O事件 最近在研究tornado和gevent,里面涉及了非阻塞I/O.在了解非阻塞I/O之前,需要先了解I/O事件 我们知道,内核有缓冲区.假设有两个进程A,B,进程B想读进程A写入的东西(即进 ...
- 字符排序(hdoj1106)
Problem Description 输入一行数字,如果我们把这行数字中的‘5’都看成空格,那么就得到一行用空格分割的若干非负整数(可能有些整数以‘0’开头,这些头部的‘0’应该被忽略掉,除非这个整 ...
- setTimeout setInterval 带参数的问题
转载http://www.jb51.net/article/36233.htm 在JS中无论是setTimeout还是setInterval,在使用函数名作为调用句柄时都不能带参数,而在许多场合必须要 ...
- java学习:AWT组件和事件处理的笔记(1)--Frame
1.java的抽象窗口工具包(AWT)中包含了许多类来支持GUI设计2.AWT由java的java.awt包提供3.再进行GUI编程时,要理解:容器类(Container),组件(component) ...
- SQL Server 引起磁盘IO的原因
1. 要访问的数据不在内存中.这会引起IO 2. update/delete/insert 会把变化写入数据文件.还要记录日志 : 3. checkpoint 4. lazy writer 5. db ...
- shell基础——创建临时文件
(1)当前时间命名临时文件: 按照一定的格式获取当前时间:date +%y-%m-%d_%H:%M:%S #generate the file name tmpfile=`date +%y-%m-%d ...
- SQL别名解析(转载)
通过使用 SQL,可以为列名称和表名称指定别名(Alias). 其实,select列的时候取别名有三种方法,这三种方法并不是所有数据库都适用. 方法一.直接在字段名称后面加上别名,中间以空格隔开. 方 ...
- NFC-P2P MODE
今日看见有关国内电信业者要合组TSM (Trusted Service Manager)提供NFC 服务的新闻, 这是属于NFC 所能提供的3种Mode中的Card emulation mode (就 ...
- C# 迪杰斯特拉(Dijkstra)算法
Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止. 其基本思想是,设置顶点集合S并不断地作 ...