首先感谢CSDN的朋友们,写本书困惑时总能受到你或TA的启发和指导,在此再次表示诚挚的感谢。
*周老师,在56上看到视频教程出到第9章指针(3)。不知道有没有出新的视频呢?请问一下,有没有全套的视频?我想下载或者购买全套的视频教程呢!
如果有请通知我一下吧!谢谢
这是我邮箱:chuanwei2004.student@sina.com
-你好!开学了比较忙没有录视频,现在只有1-9章的视频,后面的自定义数据类型、文件和位运算暂时只能看课件或看C语言研究性学习的路线了。视频下载地址博客中有,我也会发到你邮箱中。学习C语言时有什么心得,欢迎来信交流。
*计算机中0.1+0.1并不等于0.2 这句话是什么意思呢? 求高手解答
-建议看一下视频C01预备知识二进制,本书的例2-8回答了这个问题(可看视频也可看样章)。“小数部分是由0.5 0.25 0.125 拼起来的”的形容很形象。
*谁用vc呀,gCC不爽?
-C语言与操作系统无关,也与编译系统无关。对于初学者,哪个编译系统容易上手,可能就是最佳的选择。
*其实讲清楚指针就很好啦...
-我也经常这么想,可惜我是个教师要面对N多学生.......(特意把指针一章附于后面,谢谢你给我的勇气)
*楼主的整理的确是一个重要的一环——基础。但是,我认为我认为还有一环——应用能力,更重要。试想,当我们熟知了C语言了的规则了以后,却还是连一个使用的程序都写不出来,甚至不能看懂别人写的一个播放器程序,那么这些关于c规则的学习又有什么意义呢。所以要用,一定要在用的过程中专研。所以不要动不动谈语言的特性和语法规则。比如,我们经常说c语言数组越界问题。这算是一种语法规则(或者漏洞)还是一种可以利用的特性。据我所知,有一种病毒就是利用这个特性来越过系统的密码的。所以我们千万不能单独谈语言本身及其特性。
-这会是一个系列,关于C语言的方方面面。我同意你的看法,算法或者说语言的应用是精髓。(我想请你到zeq126.56.com看一下“递归算法课件版” 并继续关注本系列中关于算法的部分)。
*研究型学习肯定离不开汇编的,堆、栈的理解等等。
-研究是分层次的,C语言是高级语言。这里的“研究”只是针对C语言的每个知识点提问“为什么”。
*纯广告贴! 当做少儿读物可以,太基础了。。。
-。确实如此。
本书的前四章内容可到http://wenku.baidu.com/view/fd0aa30879563c1ec5da71b6.html下载。
大家可能最感兴趣的指针一章首发(:-)很早就想发了,但总害怕处理图片)CSDN。(建议不喜欢看书的同学直接看视频)
其它章节只能到百度文库下载课件,通过课件了解内容了,还好课件还算详细。(也可到115下载课件http://115.com/file/clqui38l#
新编C语言程序设计教程ppt.rar)
第9章 指针
指针是C语言的精华,指针让C语言威力无穷,魅力四射。为什么scanf函数的实参有时要加一个&操作符,有时却不加?为什么数组作为参数时就可以改变实参的值?这些前面遗留的问题都与指针有关,这些问题的答案均可在本章中找到。
指针是C语言中特殊的数据类型。整型变量所标识的存储单元中存放整数,浮点型变量中存放浮点数,那么指针变量所标识的存储单元中存放的显然是指针,但是指针是什么呢?
9.1 指针类型
9.1.1 变量的左值和右值
变量用于标识存储单元。计算机中的内存以字节为单位编号。编号多为32位的二进制数,从0号开始,即0x0000 0000、0x0000 0001、……、0xffff ffff。计算机中只用内存编号(又称内存地址)标识内存单元。
如果定义并初始化了一个整型变量如int i = 5;,则计算机中的内存状态可能如图9-1所示。
图9-1 变量i的内存状态图
从图9-1可知,整型变量i所标识的存储单元共4个字节,通常以存储单元的首字节地址作为该存储单元的地址,即整型变量i所标识的存储单元的地址为0x0012 ff00,类型为整型。当取变量i的值时,计算机会把从0x0012 ff00处开始的4个字节作为一个整体,取出其中的内容,然后按整型解码最终得到变量i的值为5。
存储单元如宿舍,其地址像宿舍号(如408),其存储的内容如住宿者(如王五),相关变量名如宿舍的雅称(如liaozhai)。
由以上分析可知,变量既标识存储单元的地址又标识其存储的内容,因此变量比如整型变量i也有两个值。整型变量i的一个值是地址0x0012 ff00,另一个值是内容5。变量i在使用时表现为何值呢?
例9-1分析语句i = 5; j = i;中整型变量i的值。
分析:
语句i = 5;的操作结果是把5放入变量i所标识的存储单元中,也就是把5的补码存入地址为0x0012 ff00的存储单元中,变量i的值此时实为地址0x0012 ff00。
语句j = i;的操作结果是把变量j所标识的存储单元的状态设置成与变量i的相同,即把变量j的值也设置成5,变量i的值在此处实为内容5。
位于赋值操作符的左边时,变量的值通常表现为地址,由此称变量的地址为变量的左值;位于赋值操作符的右边时,变量的值通常表现为内容,由此称变量的内容为变量的右值。
变量的左值都为地址,而右值则可能为整数、浮点数、字符等。指针变量的特殊之处在于其右值也为某存储单元的地址,其左值和右值均为地址。如果认为指针变量存储的是指针,则这里的指针显然为某存储单元的地址。需强调:存储单元的地址只是其首字节地址,仅仅凭地址而不知道类型(存储单元的大小、编码格式等)是无法正确访问存储单元的。
9.1.2 指针变量定义和初始化
指针变量用“*”号定义,但仅用*号标明变量的类型为指针还不行,必须知道指针变量存储的是何种存储单元的地址。因此,指针变量的一般定义形式为:
类型 *标识符
其中,类型规定了与指针变量存储的地址相关的存储单元是何类型,也就是说,一个指针变量只能存储规定类型的存储单元的地址。类型可以是C语言中的整型、浮点型、数组等。
如语句int *pi;定义了一个指针变量pi,pi的内容(右值)应为一个int型存储单元的地址;语句double *pf;定义了一个指针变量pf,pf的右值应为一个double型存储单元的地址。
有int i=5, *pi;,且已知变量i的左值为0x0012 ff00。现设指针变量pi的内容为0x0012 ff00,则相关的内存状态可能如图9-2所示。
图9-2 变量i和变量pi的存储状态图
从图9-2可知,指针变量pi的左值为0x0012 ff80,右值为0x0012 ff00;整型变量i的左值为0x0012 ff00,右值为5。
如果指针变量所指向的存储单元为整型,则称之为整型指针变量;如果指针变量pi的右值为变量i的左值(如图9-2所示),则称指针变量pi指向变量i。
怎样把变量i的左值赋给指针变量pi呢?
第一种作法:pi = 0x0012 ff00;
分析:
假设变量i的左值为0x0012 ff00,在语句pi = 0x0012 ff00;中,0x0012 ff00仅被看做一个十六进制的整型字面量,指针变量要求用地址为其赋值,两者类型不匹配。
第二种作法:pi = i;
分析:
语句pi = i;中变量i表现为右值5,因此语句pi = i;相当于pi = 5;,同样类型不匹配。
类型不匹配时可以用强制类型转换,正确的作法为:pi = (int*)0x0012 ff00;。该语句在执行时,会先定义一个临时无名指针变量(假设为int *ptemp;),然后强制地把整型字面量0x0012 ff00作为地址赋值给临时无名指针变量(此时整型指针变量ptemp的右值为0x0012 ff00),最后再用临时无名指针变量给指针变量pi赋值(pi = ptemp;),从而使指针变量pi的右值变为0x0012 ff00。
由于编程时不可能知道某变量的左值,因此这种通过强制类型转换把变量的左值赋给指针变量的方法实际上是行不通的。
9.2 指针操作符和空指针
9.2.1 指针操作符
取地址操作符&和间接引用操作符*与指针相关,故称为指针操作符。
单目操作符&的操作对象是一个变量,操作结果是该变量的地址。使用&i就可以获得变量i的左值,即变量i的地址。
有int i = 5,*pi;,用语句pi = &i;就可把变量i的地址赋值给指针变量pi,从而使整型指针变量pi指向整型变量i。
间接引用操作符*也是单目操作符,操作对象是一个地址型的量(如指针变量),操作结果为该地址所标识的存储单元的内容。
例9-2 设有如图9-2所示的i和pi,则对于整型变量j,分析语句j = *pi;和语句j = *i;。
分析:
在语句j = *pi;中,指针变量pi表现为右值即地址0x0012 ff00,间接引用操作符*会获得此存储单元的内容,也就是变量i的右值5,故语句j = *pi;相当于j = 5;。
在语句j = *i;中,变量i也表现为右值整数5,间接引用操作符*的操作对象必须是一个地址型的量,显然此处类型不匹配,有语法错误。
改正这个错误有两种方法。
第一种,用取地址操作符&获得变量i的地址,把原语句改为j = *(&i);。在语句j = *(&i);中,&i的操作结果为变量i的左值0x0012 ff00,间接引用操作会获得此存储单元的内容5,故此时j = *(&i);相当于j = *pi;或j = i;。
第二种,用强制类型转换操作把语句j = *i;中的变量i的右值整数5强制转换为一个地址,原语句变为j = *(int*)i;,实际上为j = *(int*)5;。在语句j = *(int*)5;中,强制类型转换操作把整数5强制转换成编号为5的地址。此时虽然没有了语法错误,但是地址为0x0000 0005的存储单元通常不会分配给应用程序且不允许应用程序读写,因此在执行该语句时会因为读写不允许读写的存储单元而出现非法内存访问错误。
例9-3 如有int i = 5, *pi = &i;,则一般情况下*pi可以与变量i互换使用。
分析:
设指针变量pi与整型变量i的内存状态如图9-2所示。
由例9-2可知,*pi在赋值操作符右边时,其值就为变量i的内容5,即j = *pi;与j = i;相同,因此,当变量i位于赋值操作符右边表现为内容时,*pi与变量i可以互换使用。
当变量i为左值时,如语句i = 3;中变量i的值为地址0x0012 ff00。在语句*pi = 3;中*pi的值为多少呢?此时指针变量pi也表现为左值即它本身的地址0x0012 ff80,间接引用操作符*将取此地址处的内容,即*pi的结果为0x0012 ff00,语句*pi=3;的作用也是将整数3存储到地址为0x0012 ff00处的存储单元中,与语句i = 3;相同。
综上所述,当整型指针变量pi指向整型变量i时,*pi与变量i在一般情况下可以互换使用。
例9-4 分析下面程序的运行情况。
#include <stdio.h>
#define PR(x, y) printf("%3.1f, %3.1f\n", x, y)
void main( )
{
float fa = 2.3, fb = 3.2, *pf1 = &fa, *pf2 = &fb;
float *pt, f;
PR(*pf1, *pf2);
pt = pf1;
pf1 = pf2;
pf2 = pt;
PR(*pf1, *pf2);
PR(fa, fb);
f = *pf1;
*pf1 = *pf2;
*pf2 = f;
PR(*pf1, *pf2);
PR(fa, fb);
}
图9-3 例9-4中变量的关系图
分析:
程序执行时变量fa,fb,pf1与pf2的关系如图9-3(1)所示,故此时*pf1和*pf2的输出值为2.3和3.2。语句pt = pf1;执行后,变量的关系图如图9-3(2)所示。语句pf1 = pf2;执行后,变量的关系图如图9-3(3)所示。语句pf2 = pt;执行后,变量关系图如图9-3(4)所示。因此,*pf1和*pf2的输出值为3.2和2.3,而fa和fb的值仍为2.3和3.2。
对于下面的三条语句,因为*pf1与pf1所指向的变量fb可互换使用,*pf2与变量fa可互换使用,所以,最后输出时*pf1和*pf2的值为2.3和3.2,而fa与fb的值为3.2和2.3。
9.2.2 空指针
例9-5 下面这段程序有问题吗?
#include <stdio.h>
void main( )
{
int i, *pi;
*pi = 5;
printf("%d,%d\n", i, *pi);
}
分析:
语句*pi = 5;中,*pi是指针变量pi的内容,即某存储单元的地址,整数5将存储在此存储单元中,但是指针变量pi的值是多少呢?
指针变量pi和变量i都没有初始化。C语言中变量没有初始化时,全局变量的值默认为0,局部变量的值则没有规定,因此指针变量pi的值在此处通常不可预知。语句*pi = 5;执行时将向一个位置不可预知的存储单元写入数据,这样的操作可能导致非法内存访问错误或程序逻辑上的错误。
类似本程序中的指针变量pi,右值为不属于程序所拥有存储单元的地址的指针变量称为“野指针”。间接引用野指针的右值将导致非法内存访问错误或其他不可预知的错误。预防由野指针引发的错误的常用办法是把没有明确指向的指针变量设置成“空指针”,而在使用指针变量时检测它是否为空指针。
所谓空指针是指右值为0的指针变量。地址为0的存储单元不可能分配给应用程序使用,因此约定指向此处的指针变量为空指针。空指针指向明确的存储单元不再是野指针。此外,0是一个特殊的整数,它是唯一不用类型转换就能直接赋值给指针变量的整数。如语句int *pi = 0; double *p = 0;等均合法。
直接用整数0给指针变量赋值容易让人误解,为此在stdio.h中定义了一个值为0的NULL宏来取代0。如int *pi = NULL; 和float *pf = NULL;语句定义了两个指针变量pi和pf,并且它们被初始化成空指针。
把没有明确赋值的指针变量设置成空指针(p = NULL),在使用时检测指针是否为空指针(if(p == NULL))可以有效地避免野指针带来的问题。
9.3 指针与函数
9.3.1 指针作为函数参数
函数的参数可以是指针类型,当参数为指针这种特殊的数据类型时,函数的调用过程呈现出了“新”的特点。
例9-6 分析下面的函数,并编程测试。
void swap(int *px, int *py)
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
分析:
swap函数用于交换两个整型变量的右值,它的形参是两个整型指针。用swap函数交换整型变量i(int i = 2;)和j(int j = 3;)的值时,用语句swap(i,j);调用函数可以吗?
C语言是传值调用,因此函数调用swap(i, j)相当于swap(2, 3)。被调用的函数执行时会用实参的值给形参赋值,即有px = 2;和py = 3;,此处显然有语法错误,整数2和3是不能作为地址直接赋值给指针变量的。使用swap函数时,应该把变量i和j的地址传值给形参,正确地调用形式为swap(&i, &j)。
测试程序如下:
#include <stdio.h>
void main( )
{
int i=2, j=3;
printf("%d,%d\n", i, j);
swap(&i, &j);
printf("%d,%d\n", i,j);
}
设main函数执行时,变量i和j的内存状态可能如图9-4所示。
图9-4 变量i和j的内存状态图
下面详细分析函数调用swap(&i, &j)的执行过程。
由图9-4可知,&i和&j的值分别为0x0012 ff00和0x0012 ff04,则swap函数调用执行时,形参px和py的右值分别为0x0012 ff00和0x0012 ff04。
在语句temp = *px;中,px的右值为0x0012 ff00,故*px的值为2!这里怎么使用了main函数中的变量i,得到了i的值2?swap函数中没有使用变量i,因为根本就没有出现变量i。*px的值为2是通过把地址为0x0012 ff00类型为整型的存储单元的内容取出得到的。只要存储单元为程序所拥有,间接引用操作符*就可以通过地址得到其储存内容。程序拥有地址为0x0012 ff00的存储单元的时间实际上是变量i的生存期,在swap函数执行期间,变量i一直存在,即相关存储单元一直为程序所拥有,虽然在swap函数中由于变量作用域的限制不能通过变量i访问该存储单元。现在,利用指针变量通过地址在swap函数中可以访问该存储单元了!
语句*px = *py;把