📄 第7章 指针和内存分配.txt
字号:
}
请参见:
7.8 把一个值加到一个指针上意味着什么?
7.12 两个指针可以相加吗?为什么?
7.27 可以对void指针进行算术运算吗?
7.8 把一个值加到一个指针上意味着什么?
当把一个整型值加到一个指针上后,该指针指向的位置就向前移动了一段距离。就纯粹的内存地址而言,这段距离对应的字节数等于该值和该指针所指向的对象的大小的乘积;但是,就C指针真正的工作机理而言,这段距离对应的元素数等于该整型值。
在例7.7末尾,当程序将8和&array[o]相加后,所得的指针并不是指向&array[0]后的第8个字节,而是第8个元素。
仍以本章开头介绍的街道地址的比喻为例,假设你住在沃克大街744号,在你这一侧用连续的偶数作为街道地址,每家之间的地址间距是2。如果有人想知道你家往前第3家的地址,他就会先将2和3相乘,然后将6和你家的地址相加,得到他想要的地址750号。同理,你家往回第1家的地址是774+(-1)*2,即742号。
街道地址的算术运算只有在一个特定的街区中进行才有意义,同样,指针的算术运算也只有在一个特定的数组中进行才有意义。仍以上一段所介绍的背景为例,如果你想知道你家往回第400家的地址,你将得到沃克大街-56号,但这是一个毫无意义的地址。如果你的程序中使用了一个毫无意义的地址,你的程序很可能会被彻底破坏。
请参见:
7.7两个指针可以相减吗?为什么?
7.12两个指针可以相加吗,为什么?
7.27可以对void指针进行算术运算吗?
7.9 NULL总是被定义为0吗?
NULL不是被定义为o,就是被定义为(void *)0,这两种值几乎是相同的。当程序中需要一个指针时(尽管编译程序并不是总能指示什么时候需要一个指针),一个纯粹的零或者一个void指针都能自动被转换成所需的任何类型的指针。
请参见:
7.10NULL总是等于0吗?
7.10 NULL总是等于0吗?
对这个问题的回答与“等于”所指的意思有关。如果你是指“与。比较的结果为相等”,例如:
if(/* ... */)
{
p=NULL;
}
else
{
p=/* something else */;
}
/* ... */
if(p==0)
那么NULL确实总是等于0,这也就是空指针定义的本质所在。
如果你是指“其存储方式和整型值。相同”,那么答案是“不”。NULL并不必须被存为一个整型值0,尽管这是NULL最常见的存储方式。在有些计算机中,NULL会被存成另外一些形式。
如果你想知道NULL是否被存为一个整型值0,你可以(并且只能)通过调试程序来查看空指针的值,或者通过程序直接将空指针的值打印出来(如果你将一个空指针强制转换成整类型,那么你所看到的很可能就是一个非零值)。
请参见:
7.9NULL总是被定义为0吗?
7.28怎样打印一个地址?
7.11 用指针作if语句的条件表达式意味著什么?
当把一个指针作为条件表达式时,所要判断的条件实际上就是“该指针是否为一空指针”。在if,while,for或do/while等语句中,或者在条件表达式中,都可以使用指针。请看下例:
if(p)
{
/*dO something*/
}
else
{
/* dOsomethingelse */
}
当条件表达式的值不等于零时,if语句就执行“then”子句(即第一个子句),即“if(/*something*/)”和“if(/*something*/!=0)”是完全相同的。因此,上例和下例也完全相同:
if(p !=0)
{
/* dO something(not anull pointer)*/
}
else
{
/* dOsomethingelse(a null pointer)*/
}
以上两例中的代码不易读,但经常出现在许多C程序中,你不必编写这样的代码,但要理解这些代码的作用。
请参见:
7.3 什么是空指针?
7. 12 两个指针可以相加吗?为什么?
两个指针是不能相加的。仍以街道地址的比喻为例,假设你住在湖滨大道1332号,你的邻居住在湖滨大道1364号,那么1332+1364指的是什么呢?其结果是一个毫无意义的数字。如果你的C程序试图将两个指针相加,编译程序就会发出警告。
当你试图将一个指针和另外两个指针的差值相加的时候,你很可能会误将其中的两个指针相加,例如,你很可能会使用下述语句:
p=p+p2-p1;
上述语句是不正确的,因为它和下述语句完全相同:
p=(p+p2)-p1;
正确的语句应该是:
p=p+(p2-p1);
对此例来说,使用下述语句更好:
p+=p2-p1;
请参见:
7.7 两个指针可以相减吗?为什么?
7.13 怎样使用指向函数的指针?
在使用指向函数的指针时,最难的一部分工作是说明该指针。例如,strcmp()函数的说明如下所示:
int strcmp(const char*,const char*);
如果你想使指针pf指向strcmp()函数,那么你就要象说明strcmp()函数那样来说明pf,但此时要用*pf代替strcmp:
int (*pr)(const char*,const char*);
请注意,*pf必须用括号括起来,因为
int *p{ (constchar * ,constchar * ); /* wrong */
等价于
(int *)pr(const char *,const char * ); /* wrong */
它们都只是说明了一个返回int *类型的函数。
在说明了pf后,你还要将<string.h>包含进来,并且要把strcmp()函数的地址赋给pf,即:
pf=strcmp;
或
pf=Slstrcmp; /* redundant& */
此后,你就可以通过间接引用pf来调用strcmp()函数:
if(pr(strl,str2)>0) /*...*/
请参见:
7.14 怎样用指向函数的指针作函数的参数?
7.14 怎样用指向函数的指针作函数的参数?
函数的指针可以作为一个参数传递给另外一个函数,这一点非常有意思。一个函数用函数指针作参数,意味着这个函数的一部分工作需要通过函数指针调用另外的函数来完成,这被称
为“回调(callback)”。处理图形用户接口的许多C库函数都用函数指针作参数,因为创建显示风格的工作可以由这些函数本身完成,但确定显示内容的工作需要由应用程序完成。
举一个简单的例子,假设有一个由字符指针组成的数组,你想按这些指针指向的字符串的值对这些指针进行排序,你可以使用qsort()函数,而qsort()函数需要借助函数指针来完成这项任务(关于排序的详细介绍请参见第3章“排序和查找”。qsort()函数有4个参数:
(1) 指向数组开头的指针;
(2) 数组中的元素数目;
(3) 数组中每个元素的大小;
(4) 指向一个比较函数的指针。
qsort()函数返回一个整型值。
比较函数有两个参数,分别为指向要比较的两个元素的指针。当要比较的第一个元素大于、等于或小于第二个元素时,比较函数分别返回一个大于o,等于。或小于。的值。一个比较两个整型值的函数可能如下所示:
int icmp(const int *p1,const int *p2)
{
return *p1-*p2;
}
排序算法和交换算法都是qsort()函数的部分内容。qsort()函数的交换算法代码只负责拷贝指定数目的字节(可能调用memcpy()或memmove()函数),因此qsort()函数不知道要对什么样的数据进行排序,也就不知道如何比较这些数据。比较数据的工作将由函数指针所指向的比较函数来完成。
对本例来说,不能直接用strcmp()函数作比较函数,其原因有两点:第一,strcmp()函数的类型与本例不符(见下文中的介绍);第二,srtcmp()函数不能直接对本例起作用。strcmp()函数的两个参数都是字符指针,它们都被strcmp()函数看作是字符串中的第一个字符;本例要处理的是字符指针(char *s),因此比较函数的两个参数必须都是指向字符指针的指针。本例最好使用下面这样的比较函数;
int strpcmp(const void *p1,const void *p2)
{
char * const *sp1 = (char * const *)p1;
char'const *sp2=(char *const *)p2;
return strcmp(*sp1,*sp2);
}
本例对qsort()函数的调用可以如下所示:
qsort(array,numElements,sizeof(char *),pf2);
这样,每当qsort()函数需要比较两个字符指针时,它就可以调用strpcmp()函数了。
为什么不能直接将strcmp()函数传递给qsort()函数呢?为什么strpcmp()函数中的参数是如此一种形式呢?因为函数指针的类型是由它所指向的函数的返回值类型及其参数的数目和类型共同决定的,而qsort()函数要求比较函数含两个const void *类型的参数:
void qsort(void *base,
size_t numElernents,
size_t sizeOfElement,
int(*compFunct)(const void *,const void *));
qsort()函数不知道要对什么样的数据进行排序,因此,base参数和比较函数中的两个参数都是void指针。这一点很容易理解,因为任何指针都能被转换成void指针,并且不需要强制转换。但是,qsort()函数对函数指针参数的类型要求就苛刻一些了。本例要排序的是一个字符指针数组,尽管strcmp()函数的比较算法与此相符,但其参数的类型与此不符,所以在本例中strcmp()函数不能直接被传给qsort()函数。在这种情况下,最简单和最安全的方法是将一个参数类型符合qsort()函数的要求的比较函数传给qsort()函数,而将比较函数的参数强制转换成strcmp()函数所要求的类型后再传给strcmp()函数;strpcmp()函数的作用正是如此。
不论C程序在什么样的环境中运行,char *类型和void。类型之间都能进行等价的转换,因此,你可以通过强制转换函数指针类型使qsort()函数中的函数指针参数指向strcmp()函数,而不必另外定义一个strpcmp()这样的函数,例如:
char table[NUM_ELEMENTS][LEMENT_SIZE);
/* ... */
/* passing strcmp() to qsort for array Of array Of char */
qsort(table,NUM_ELEMENTS,ELEMENT_SIZE,
(int(*)(const void *,const void *))strcmp);
不管是强制转换strpcmp()函数的参数的类型,还是强制转换指向strcmp()函数的指针的类型,你都必须小心进行,因为稍有疏忽,就会使程序出错。在实际编程中,转换函数指针的类型更容易使程序出错。
请参见:
7.5 什么是void指针?
7.6 什么时候使用void指针?
7.13 怎样使用指向函数的指针?
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -