普通变量作为函数参数是按值调用的,实参和形参地址不一样,形参值改变不会影响实参。
数组作为函数参数是按地址低用的,实参和形参数组共享同一段内存空间
从数组开始,从简单的数据结构到复杂的数据结构
C语言的高效主要就是指针(pointer)的作用
很多“mission impossible”都可以由指针来完成
比如,执行结果和源代码一模一样。
强转和指针,并称C语言的两大神器
C语言很多错误都是由于数组和指针引起的非法内存访问导致的
内存中的每个字节都有唯一的编号(地址)
地址按字节编号,其字长一般与主机相同
32位机器使用32位地址,的最多支持2^32字节内存(4G)
地址是一个无符号整数,从0开始,依次递增。在表达和交流时,通常把地址写成十六进制数。
例:使用取地址运算符取出变量的地址,然后将其显示在屏幕上。
%p表输出变量的地址值
scanf("%d", &a) ----&a 直接寻址:按变量的地址直接访问
以下程序会发生什么情况?
1 int i; 2 3 scanf("%d",i);
i的值被当做地址,如i的值为100,则输出的整数就会从地址100开始写入内存。
1 char c; 2 scanf("%d", &c);
输入数据以int的二进制形式写到c所在的内存空间
c所占内存不足以放下一个int,其后所占的空间也被覆盖。
间接寻址:通过存放变量地址的其他变量访问该变量。
用什么类型的变量来存放地址呢?
-指针(pointer)类型
指针变量-具有指针类型的变量
变量的指针←→变量的地址
保存32位地址值的指针变量占4个字节的内存,保存了一个地址,但是只知道这些是不够的的,往下读变量的值占用几个字节不知道
从这个地址开始多少个字节内的数据是有效的呢?
指针的基类型就是回答这个问题的
例:使用指针变量在屏幕上显示变量的地址。
指针变量指向的数据类型,称为基类型。
指针变量使用之前一定要初始化,永远不要使用没有初始化的指针变量。普通变量没有初始化访问的话可能也就是一串乱码。
如果开始的时候初始化不知道指向什么,就将指针变量初始化为NULL
何为空指针?
值为NULL的指针,即无效指针
既然0(NULL)用来表示空指针,那么空指针就是地址为0的单元的指针吗?
不一定。每个C编译器都被允许用不同的方式来表示空指针;但并非所有的编译器都使用0地址。某些编译器为空指针使用不存在的内存地址。
硬件会检查出这种试图通过空指针访问内存的方式
* - 取内容。*p 即取p指向的内存中存储的内容
指针变量只能指向同一基类型的变量
例3 使用指针变量,通过间接寻址输出变量的值
指针的解引用(pointer dereference):引用指针指向的变量的值 *pa
int *p; 基类型
char *pc;
为什么要通过指针来间接寻址?
比如主函数在调用另外一个函数的时候,定义一个指针变量来保存变量的地址。这是指针非常重要的一个应用之一。
用指针变量作为函数参数。
普通变量作函数参数--按值调用
实参的值不随形参值的改变而改变
形参 <-实参变量的值
指针做函数参数-按地址调用
为了在被调函数中修改其无法直接访问的实参的值
指针形参 <-实参变量的地址
演示一下有什么区别?
例9.5:函数传递变量的值,形参值的改变不影响对应的实参
例9.6:指针变量作为形参,函数传递变量的地址,可以修改变量的值。
演示按值调用
通过返回值可以修改变量的值,但是return方式只能返回一个值,多个值只能用形参传递方式
例9.6 编写函数实现两数的互换。
这个程序无法实现两数交换的功能,为什么呢?
用普通变量作参数的时候,实现了形参x和y的交换,但是实参a和b并没有互换。交换的只是形参的值,而形参的值在函数运行结束后就释放了。
另外一个用指针变量作为参数,函数是传地址调用,对参数进行解引用,*X是a的值,*Y代表b的值,交换之后,*X变为9,*Y变为5,即a变为9,b变为5.
尝试一下,调用的时候将函数参数改为实参,把取地址符号去掉,会发生什么情况?
这个时候就会体现出写函数原型的作用,编译器会自动检查实参类型与形参类型是否匹配。
某些编译器检查实参和形参的数据类型是否匹配,不匹配则给出警告。但是有的编译器会直接传给形参,会导致非法访问内存。
使用数组完成两数的交换
这种使用数组的方式必须保证这两个形参数据类型要相同。
下面这种方式编译的时候就会报错。指针pTemp未初始化,指针pTemp指向哪里未知,对未知单元写操作是很危险的,不能借助一个未初始化的指针变量进行两数交换。
下面这种方式会出错吗?这种方式交换的是地址值,并没有实现a和b的值。这个也是错误的。
例9.7 计算并输出最高分及相应学生的学号
第一种方式:使用数组的方式,返回数组的下标,从而获取数组元素
第二种方式:直接返回对应的数组元素
该程序使用普通参数形参来返回得分和学号的值,但是形参的值改变不会影响实参,虽然在函数中可以得到最高分和学号,调用完最后不会传给主函数
直接调用函数,在VC上报出警告:局部变量maxScore和maxNum没有被初始化就调用。
maxScore和maxNum被传递给形参的时候必须是被赋值的,在没有调用函数的时候,这两个值是随机的,随机的值传给形参,虽然在调用的函数中计算完,但是没有反向传给maxScore和maxNum。所以检测出来这两个变量没有被初始化,并不是直接指出这个错误所在。真正原因:普通变量作函数参数按值调用,不能在被调函数中改变相应实参的值。
怎么修改呢?将形参改为指针变量类型,调用的时候,取变量的地址传给形参来间接访问变量值,在函数调用时解引用。
下面介绍指针变量的另外一个应用,就是将指针变量指向一个函数,即所谓的函数指针。
上节回顾:刚才我们讲了一个指针的重要应用,即指针变量作为函数参数。
什么情况下使用指针变量?就是在被调函数中没办法访问修改主调函数的值,那只能是间接的修改它 ,即通过将要修改的主调函数参数的地址值作为形参,被调函数得到参数的地址值后就可以通过指针的解引用间接的访问修改主调函数中的变量值。
指针另外一个应用:编写通用的函数。需要通过函数指针来实现。
函数指针(Function Pointer):指向函数的指针变量
数据类型 (*指针变量名)(形参列表);
例 int (*f)(int a, int b); 表示函数指针f指向的函数原型为 int 函数名(int a, int b);
假设存在Fun函数:
int Fun(int a, int b);
f = Fun; // Fun代表编译器为函数分配的入口地址。
令f = Fun,就是让f指向函数fun()
编译器将不带()的函数名解释为改函数的入口地址
函数指针变量存储的是函数在内存中的入口地址。
常见错误:
1、忘了写前一个() 即: int *f(int a, int b); 表示声明了一个函数名为f,返回值为整型指针类型的函数。
2、忘了写后一个()即:int (*f); 表示定义了一个整型指针变量
3、定义是的参数类型(实型)与指向的函数参数类型(整型)不匹配,int (*f)(float a, float b);
例:函数指针的应用,求最大值与最小值两数之和。
函数指针的应用是编写通用性很强的函数:一是计算定积分,二是编写通用的排序函数
1、计算定积分
当需要计算函数为f1时:
当需要计算的函数为f2....时
可以发现,以上方法不同函数的定积分计算需要分别定义不同的函数。要想通过一个函数来解决不同函数的定积分,那就要用到函数指针。
增加一个函数指针作为形参,解引用方式通过函数指针调用它所指向的函数,调用同一个函数实现了不同函数定积分的计算,只是第一个实参发生了变化。
2、编写通用排序
例9.8:修改例8.8中的排序函数,使其能够实现对学生成绩的升序排序,又能实现对学生成绩的降序排序
先不使用函数指针编程:
只能通过编写两个排序函数(升序和降序)
主函数:
采用选择法排序:这两个函数基本上都相同,就是大于号小于号的区别
最后两个函数
使用函数指针编程
通用的交换法排序函数:
可以使用刚刚的两数交换函数实现,即Swap(&a[i], &a[k]);一定要加上取地址运算符。
本章
一种特殊类型的变量,指针是地址是一种不严谨的说法,指针是一种数据类型。
指针变量:指针类型的变量,指针类型的变量保存特殊的数据,即地址。
指针变量与普通变量的共性:
1、都是在内存中占据一定大小的内存单元,对于指针变量而言,在32位机器中,占用4个字节的内存,按基类型理解地址单元中的数据,从这个地址开始读几个字节看它的基类型。
2、都需要先定义后使用,尤其对于指针变量而言,一定要先初始化,如果不确定指向 哪里,先让它指向NULL
指针变量的特殊性
1、指针变量中保存的内容只能是地址(变量或者函数的地址)
2、只能指向同一基类型的变量
3、可参与的运算类型有限,加减整数,自增自减,关系运算、赋值运算
定义指针变量的基类型目的:明确了指针指向的内存单元可以存放的数据类型,从这个地址开始几个字节的单元是有效的,可以存放什么类型数据。
初始化的目的:明确指针变量具体指向了哪里。
使用指针变量的基本原则:
1、明确指向哪里 -- 初始化
2、明确指向单元的内容 基类型
3、永远不要使用未初始化--的指针变量
4、一个(xx型)的指针指向(xx类型)的变量
指针的一个重要应用就是作为函数参数,向函数换地变量或函数的地址。一种是指针变量作为函数参数,一种是函数指针对位函数参数。
指向变量的指针,作函数参数
-按地址调用,传递变量在内存中的地址(用取地址运算符)
-被调函数根据改地址读写它不能直接访问的变量的值(用间接寻址运算符,指针的解引用)
函数指针作为参数,可以编写一个通用的函数,传递的是函数在内存中的入口地址,可以通过解引用来调用函数,实现不同的函数地址调用不同的函数。
指针还有其他方面的应用,后面章节介绍。
原文:https://www.cnblogs.com/west20180522/p/13629251.html