11、指针运算与一维数组
多数变量都能进行算数运算,但并不是所有变量都可以。幸运的是,指针变量能作算术运算操作。除了引用和解引用内存地址外这也是指针的最重要的用途之一。
内存是连续块排列,这自然让我们想到了数组数据类型,因为数组索引也是连续排列。
1、数组内存排列
数组是最基本的数据结构之一。更具定义数组是相同数据类型的集合,内存排列里占用连续内存单元并可通过索引进行访问。
下面代码演示的内存转储。
#include <stdio.h> #include <stdlib.h> #include <string.h> void main() { int iArray[32]; int i; for (i = 0; i < 32; i++) { iArray[i] = i; } for (i = 0; i < 32; i++) { printf("a[%d] %u %d", i, &iArray[i], iArray[i]); if ((i % 4 == 0)&&(i!=0)) printf("\n"); } }
运行结果:
仔细分析上面的内存转储,会发现每个连续的数组索引器内存地址也是连续的。例如:第0个数组索引a[0]的地址为7338376,第一个数组索引地址为7338380,后一个内存地址减去前一个连续的地址(&a[1]-&a[0]=4).结果为4.因为一个整型变量内存中占用一个字长(32位/4字节)。所以变量a[0]内存地址为7338376~7338380。
字节序
字节序描述数据将存储在内存时的格式/排列。加载/存储指令从内存读取数据,在运行完数据处理指令后从寄存器将数据写会内存。
在存储和加载时,CPU必须采用硬件支持的字节序格式。其可分为大端和小端。一个字长为4字节/32位。我们假设将0x1234存储到变量中。
(1)大端时,高字节存储在第一个位置,次高字节存储在存储次邻位置,并以此类推。
(2)小端是,低字节存储在第一个位置,次高字节存储在存储次邻位置并以此类推。
下面通过一个示例检测我们的系统是打断还是小端:
#include <stdio.h> #define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1 int endian() { short int word = 0x0001; char *byte = (char*)&word; return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN); } void main() { int value; value = endian(); if (value == 1) { printf("大端\n"); } else printf("小端\n"); }
运行结果如下:
2、指针运算
指针运算能进行使用如下操作符:
++ -- + -
值得注意的是:不允许使用除法(/)和乘法(*)操作符。
在之前的字节序中讲过了,内存中数据是连续排列的。能利用数组索引访问内存块中相邻的每个数组元素,这也是指针的一种常见操作。
例如:int arry[10]//包含10个整数的整型数组。
int* ptr; ptr=arr;//数组第一个索引的指针
1、指针加法
+操作用于加法处理,指针指向某个数据类型时,它始终指向数据类型在内存单元中的首字节。在指针运算中,做加法运算后指针变量指向的地址取决于有数值与数据类型相乘表达式的值。下面给出一个示例:
#include <stdio.h> int main() { int i = 0; int data = 9; int *iptr; char *cptr; iptr = &data; cptr = (char*)&data; printf("value of data=%d,hex value =%x\n", data, data); printf("Address of data=%p\n", &data); printf("整型指针指向:%p\n", iptr); printf("char型指针指向:%p\n", cptr); for (i = 0; i < 4; i++) { printf("address =%p value=%x\n", cptr, *cptr); cptr++; } return 0; }
程序运行结果如下;
不难看出值为9的int变量占用了四个字节,从00B3FC78~00BCFC7B.这里调用char指针来说明这个事实。
其实我们如果查看汇编输出我们会发现,指针运算中编译器会做以下转换:
<指针变量>=<指针变量>+<增加值>
到
<指针变量>=<指针变量>+<指针变量数据类型大小>*<增加值>
值得注意的是,指针变量+指针变量是合法的,但是不允许将指针变量加入到另外一个指针变量中。
2、指针减法
同理的,指针运算中对于指针减法会做以下转换:
<指针变量>=<指针变量>-<增加值>
到
<指针变量>=<指针变量>-<指针变量数据类型大小>*<增加值>
但是与指针加法不同,两个指针变量可以做减法,下面给出一个代码示例:
#include <stdio.h> int main() { int data[4] = { 1,2,3,4 }; int *iptr1; int *iptr2; int val; iptr1 = &data[1]; iptr2 = &data[2]; val = iptr2 - iptr1; printf("两个指针地址之间的距离为:%d\n", val); return 0; }
运行结果如下:
结果为1是因为这两个连续位置之间只存在一个元素。当两个指针变量分别指向数组连续内存地址的不同位置,让它们彼此相减得到的结果差表示两个指针变量间存在的元素数目。
3、一维数组
想要了解指针和数组之间的使用与相关性,必须探究有关数组变量常用语法的意义。这里顺便重提一下数组的定义:存储在连续内存单元内的一系列的数据类型值。
前面提到的数组索引我们知道
数字名=&数组[0]
如果我们增加它的偏移量技能便利数组数据元素的连续地址。
我们可以认为
arr_var+offset=&arr_var[offset]
和
(arr_var+offset)=arr_var[offset]
下面进行给出一个相关示例:
#include <stdio.h> int main() { int arr[4] = { 1,2,3,4 }; printf("地址为%p\n", arr); printf("地址为%p\n", &arr[0]); return 0; }
运行结果如下:
值得注意的是:虽然数组变量名代表数组的第0个索引地址,但禁止修改它的值,即不能让它指向其他位置。如下:
int arr_var[5]; arr_var=arr_var+1;//这样做是不允许的,因为表达式试图将数组名指向下一个整数变量的地址。 arr_var++;//非法语句,试图修改数组变量的起始地址
1、动态数组
有时在数组定义时不知道要存储的数组元素数目。程序运行时,数组元素数目可能增加也可能减少。所有我们在数组中存储这类元素时,数组必须是动态的,因为数组大小在运行时能改变。诸如我们之前的定义
int arr_stat[10];
该声明确保编译时已知数组大小,称为静态数组。
指针能够帮助我们操作内存区域实现根据需求增加或降低内存的预期行为。利用指针和堆实现该目标。调用malloc()在堆中分配内存。下main我们用一个示例进行演示。
示例:需求:用户插入数据时,适当调整内存大小以存储数据元素。用户删除数据时,释放内存。
#include <stdio.h> #include <malloc.h> int *ptr = NULL; static int count = 0; void insert(int data) { if (ptr == NULL) { ptr = (int*)malloc(sizeof(int));//从对分配空间给第一个data单元 ptr[0] = data;//利用数组符号访问内存地址来存储数据。 } else { ptr = (int*)realloc(ptr, sizeof(int)*(count + 1)); ptr[count] = data;//利用数组符号访问内存地址来存储数据。 } count++; } void show() { int i = 0; for (i = 0; i < count; i++) { printf("%d\n", ptr[i]); } } int main() { int c = 0; int data; while(c!=3) { printf("输入选择\n"); printf("输入1插入数据\n"); printf("输入2显示数据\n"); printf("输入3退出数据\n"); scanf("%d", &c); if (c == 3) break; switch (c) { case 1: printf("数据=\n"); scanf("%d", &data); insert(data); break; case 2: printf("数组中的数据\n"); show(); break; } } return 0; }
运行结果:
2、指针数组
按照定义指针数组是指在连续单元内存中在储指针变量。该数组中每个位置包括内存中某个数据的内存地址。
指针数组声明:<数据类型*><变量名>[数组元素数目]
int * arr_ptr[10]//指向10个整型变量
示例:
#include <stdio.h> int main() { int arr[4] = { 1,2,3,4 }; int* arr_ptr[4]; int i; for (i = 0; i < 4; i++) { arr_ptr[i] = arr + i; } printf("数组元素地址为\n"); for (i = 0; i < 4; i++) { printf("元素%d的地址为%p\n", i, arr + i); } printf("数组元素的值为\n"); for (i = 0; i < 4; i++) { printf("元素%d的值为%p\n", i, arr_ptr[i]); } return 0; }
运行结果为:
分析上述输出,指针数组arr_ptr包含数组arr中每个元素的地址。
2、数组指针
依据定义数组指针为指向数组的指针变量。
声明:<数据类型>(*<变量名>)[数组元素数目]
例如:
int (* ptr2arr)[4];//指向包含长度为4数组的指针
与其他指针变量一样,数组指针每次仅能指向一个位置。
示例:
#include <stdio.h> int main() { int arr[4] = { 1,2,3,4 }; int(*ptr2arr) [4]; int i; int *ptr = arr; ptr2arr = &arr; for (i = 0; i < 4; i++) { printf("元素的地址为%p\n", arr + i); } printf("值在%d\n", *(ptr2arr[0] + 1)); for (i = 0; i < 4; i++) { printf("地址%p的值为%d\n", (ptr2arr[0] + i), *(ptr2arr[0] + i)); } return 0; }
运行结果如下;
ptr2为数组指针,指向存储四个整型类型的数据元素的数组。