前言:继续C系列,一天不学点感觉不踏实。
1.指针与地址
一元运算符&可用于取一个对象的地址。
一元运算符*是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。
int x = 1, y = 2, z[10];
int *ip; /* ip is a pointer to int */
ip = &x; /* ip now points to x */
y = *ip; /* y is now 1 */
*ip = 0; /* x is now 0 */
ip = &z[0]; /* ip now points to z[0] */
2.指针与函数参数
由于一元运算符&用来取变量的地址,这样&a就是一个指向变量a的指针。swap函数的所有参数都声明为指针,并且通过这些指针来间接访问他们指向的操作数
void swap(int *px, int *py) /* interchange *px and *py */
{
int temp;
temp = *px;
*px = *py;
*py = temp;
}
3.指针与数组
为了了解上面的定义,我们来看个例子:
在这里随便定义一个数组
int arr[5];
arr现在就是数组名, arr 代表的是该数组整块内存,即sizeof(arr) == 20 (假设sizeof(int) == 4), arr 里的内容是该块内存的首地址,即 arr == &arr[0] 。 arr可以看做是一个常量,也就不可以使用 arr++ 之类的运算。
int *p;
p = arr;
p是一个指向int类型的指针,p = arr,就是把数组的首地址(arr的内容就是数组的首地址,这个前面有分析)赋给p,即 p 现在就是指向数组的首地址,通过 p 就可以访问整个数组,但是 p 这里只是是个指针变量,也就是 p 的本质没有改变,p 不能和 arr 一样代表整个数组的内存, 所以 sizeof(p) == sizeof(int*) != sizeof(arr)。
把数组的首地址赋给 p,但 p 的本质一个int类型的指针变量,所以也就可以对 p 进行 ++ 之类的运算。
我们可以通过对 p ,arr 的偏移(int类型的指针 +1 或 -1, 是向上或向下偏移 sizeof(int) 个byte)来访问数组里的元素, *( p + i ) ,*(arr + i),也可以通过传统的 arr[i] 访问数组。
举个例子(例子来源于老师上课时讲解的):
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int*)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr - 1));
这里的输出的值应该是?
a 是 代表(不是指向)的是整个数组内存,a 的值是该数组内存的首地址, 对 a 取地址(&a),即......,所以这里的 &a 是指向整个数组的内存块,所以 a 的值与 &a 的值是一样的,都是该数组的首地址,但他们的含义是不一样的。
这里 &a 的每次偏移是移动整个内存块的大小,这里就是移动 sizeof(a),即40 byte,所以这里的 &a + 1, 是指针向下移动个40byte(数组内存块的大小),&a+1的指向 是下个 sizeof(a)大小的内存块。
下面是内存分配图:
&a ---> ========== 假设这里的地址值是 0x11111111 &a 和 a 的值都是 0x11111111
|| 1 ||
==========
|| 2 ||
==========
|| 3 ||
==========
|| 4 ||
========== <-----ptr-1 int类型的指针每偏移是 sizeof(int) 个 字节
|| 5 ||
&a +1 ---> ========== <-----ptr 上面的题目中 让 int 类型的指针 ptr 也指向了这里
|| ? ||
==========
|| ? ||
==========
|| ? ||
==========
|| ? ||
==========
|| ? ||
&a+2 ---->==========
|| ? ||
==========
""""""
""""""
""""""
所以上面题目的输出结果 是 :
2,5
以上都是我个人对于C中指针与数组的理解,若有什么不对的地方,谢谢指出。
4.地址算术运算
如果p是一个指向数组中某个元素的指针,那么p++将对p进行自增运算并指向下一个元素,而p+=i将对p进行加i的增量运算,使其指向指针p当前所指向的元素之后的第i个元素。这类元素是指针或地址算术运算中最简单的形式。
C语言中的地址算术运算方法是一致且有规律的,将指针、数组和地址的算术运算集成在一起是该语言的一大优点。为了说明这一点,我们来看一个不完整的存储分配程序。它由两个函数组成。第一个函数alloc(n)返回一个指向n个连续字符存储单元的指针,alloc函数的调用者可利用该指针存储字符序列。第二个函数afree(p)释放已分配的存储空间,以便以后重用。之所以说是这两个函数是“不完善的”,是因为对afree函数的调用次序必须与调用alloc函数的次序相反。换句话说,alloc与afree以桟的方式(即后进先出的列表)进行存储空间的管理。标准库中提供了具有类似功能的函数malloc和free,它们没有上述限制。
最容易的实现方法是让alloc函数对一个大字符数组alocbuf中的控件进行分配。该数组是alloc和afree两个函数私有的数组。由于函数alloc和afree处理的对象是指针而不是数组小标,因此,其他函数无需知道该数组的名字,这样,可以在包含alloc和afree的源文件中奖该数组声明为static类型,使得它对外不可见。实际实现时,该数组甚至可以没有名字,它可以通过调用malloc函数或向操作系统申请一个指向无名存储块的指针获得。
allocbuf中的空间使用状况也是我们需要了解的信息。我们使用指针allocp指向allocbuf中的下一个空闲单元。当调用alloc申请n个字符的空间时,alloc检查allocbuf数组中有没有足够的剩余空间。如果有足够的空闲空间,则alloc返回allocp的当前值(即空闲块的开始位置),然后allocp加n以使它指向下一个空闲区域。如果空闲空间不够,则alloc返回0.如果p在allocbuf的边界之内,则afree(p)仅仅只是将allocp的值设置为p。
static char allocbuf[ ALLOCSIZE];
static char * allocp= allocbuf;
char * alloc( int n)
{
}
void afree( char * p)
{
}
一般情况下,同其他类型的变量一样,指针也可以初始化。通常,对指针有意义的初始化值只能是0或者表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定义的具有适当类型的数据的地址。例如,声明
static char *allocp=allocbuf;
将allocp定义为字符类型指针,并将它初始化为allocbuf的起始地址,该起始地址是程序执行时的下一个空闲位置。上述语句也可以写成下列形式
static char *allocp=&allocbuf[0];
这是因为该数组名实际上就是数组第0个元素的地址。
下列if测试语句:
if(allocbuf+ALLOCSIZE-allocp>=n){...};
检查是否有足够的空闲空间以满足n个字符的存储空间请求。如果空闲空间足够,则分配存储空间后alloc将返回一个指向所需大小的字符块首地址的指针(注意函数本身的声明)。如果申请无法满足,alloc必须返回某种形式的信号以说明没有足够的空闲空间可供分配。C语言保证,0永远不是有效的数据地址,因此,返回值0可用来表示发生了异常事件。在本例中,返回值0表示没有足够的空闲空间可供分配。
指针与整数之间不能相互转换,但0是惟一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0是指针的一个特殊值。符号常量NULL定义在标准头文件<stddef.h>中。我们在后面部分经常会用到NULL。
类似于
if(allocbuf+ALLOCSIZE-allocp>=n){...};
以及
if(p>=allocbuf&&p<allocbuf+ALLOCSIZE);
的条件测试语句表明指针算术运算有以下几个重要特点。首先,在某些情况下对指针可以进行比较运算。加入,如果指针p和q指向同一个数组的成员。那么它们之间就可以进行类似于==、!=、<、>=的关系比较运算。如果p指向的数组元素的位置在q指向的数组元素位置之前,那么关系表达式
p<q;
的值为真(true)。任何指针与0进行相等或不等的比较运算都有意义。但是指向不同数组的元素的指针之间的算术或比较运算没有定义。(这里有一个特例:指针的算术运算中可使用数组最后一个元素的下一个元素的地址。)
其次,我们从前面可以看到,指针可以和整数进行相加或相减运算。例如,结构
p+n;
表示指针p当前指向的对象之后第n个对象的地址。无论指针p指向的对象是何种类型,上述的结论都成立。在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。例如,如果int类型占4个字节的存储空间,那么在int类型的计算中,对应的n将按4的倍数来计算。
指针的减法运算也是有意义的:如果p和q指向相同数组中的元素,且p<q,那么q-p+1就是就是位于p和q指向的元素之间的元素的数目。我们由此可以编写出函数strlen的另一个版本,如下所示:
{
}
在上述程序段的声明中,指针p被初始化为指向s,即指向该字符串的第一个字符。while循环语句将依次检查字符串中的每个字符,知道遇到标识字符数组结束的字符‘\0’为止。由于是指向字符的指针,所以每执行一次p++,p就将指向下一个字符的地址,p-s则表示已经检查过的字符数,即字符串的长度。(字符串中的字符数有可能超过int类型所能表示的最大范围。头文件<stddef.h>中定义的类型ptrdiff_t足以表示两个指针之间的带符号差值。但是,我们在这里使用size_t作为函数strlen的返回值类型,这样可以与标准库中的函数版本相匹配。size_t是由运算符sizeof返回的无符号整型。)
指针的算术运算具有一致性:如果处理的数据类型是比字符型占据更多存储空间的浮点类型,并且p是一个指向浮点类型的指针,那么在指向p++后,p就爱那个指向下一个浮点数的地址。因此,只需要将alloc和afree函数中所有的char类型替换为float类型,就可以得到一个适用于浮点类型而非字符型的内存分配函数所有的指针运算都会自动考虑它所指向的对象的长度。
有效的指针运算包括相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。其他所有形式的指针运算都是非法的,例如两个指针间的加法、乘法、除法、移位或屏蔽运算;指针同float或double类型之间的加法运算;不经强制类型转换而直接将指向一种类型对象的指针赋值给指向另一种类型对象的指针的运算(两个指针之一是void*类型的情况除外)。