1.指针
程序中一般通过变量名对变量进行存取操作,其实质是通过地址进行的。对变量的访问分为直接访问和间接访问,如语句“printf(“%d”,i);”,执行的时候,根据变量名 与地址的对应关系,找到i的地址,依次取相应的字节数中的数据(即i的值),把它输出。这种按照变量地址存取变量值的方式成为直接访问;还可以将变量i的地址存放在另一个变量中,假设该变量为i_pointer(在32位Windows操作系统下,无论指向什么类型的指针都只有4个字节),通过语句i_pointer=&i;将i的其实地址存放到i_pointer中,若要取变量,先找到存放i的地址的变量i_pointer,从中取出i的地址,然后从该地址开始的4个字节中取出i的值,这就是间接访问。一个变量的地址成为该变量的指针。用来存放变量地址的变量是指针变量。
指针变量可以做函数参数,我们知道,函数传参方式是“单向值传递”,即在被调用的函数中改变形参的值是无法改变调用处实参的值,因为形参是存放在栈中的,调用结束后,内存被释放,该数据的值也就不存在了。但是利用指针变量做函数参数,在函数执行的过程中使指针变量所指向的变量的值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了*”通过调用函数使变量的值发生变化,在主调函数中使用这些改变了的值“的目的。这种方式也遵循单向的”值传递“,即调用函数是不会改变指针变量的值,但是可以改变实参指针变量所指向变量的值。
2.数组指针
数组指针的实质还是指针,我们可以将其解释为指向由m个元素组成的一维数组的指针变量。我们先来了解二维数组名代表的含义,定义一个二维数组int a[3][4]={0};,我们知道,数组名代表数组首元素的地址,现在的首元素不是一个整型变量,而是由4个整型元素组成的一维数组,因此a代表的是首行的起始地址,即&a[0],a+1代表a[1]行的首地址,即&a[1]。假设a的首行起始地址为2000,则a+1所代表的地址为2000+4*4=2016。a[0],a[1],a[2]既然是一维数组名,而数组名又代表数组首元素地址,因此a[0]代表数组a[0]中0列元素的地址,即&a[0][0], a[1]的值是&a[1][0]。言归正传,现在我们想将数组名a赋值给一个指针变量,用语句 int *p=a;肯定是不行的,因为p指向一个整型数据,而a指向的是首行的4个数据,因此,我们定义语句int (*q)[4];其中p就是一个指针变量,它指向包含4个整型元素的以为数组。我们称q为数组指针。
3.指针数组
和数组指针一样,指针数组的实质是数组。如果一个数组,其元素均为指针类型数据,该数组称为指针数组。语句int p[4];中,[ ]比优先级高,因此p先和[ ]结合,显然p是一个数组名,有4个元素。再与p前面的结合,代表此数组是指针类型的,每个数组元素(相当于一个指针变量)都可指向一个整形变量。如 char name[]={“abc”,”def”,”ghi”};name是一个指针数组,他有3个元素,其初值分别是”abc”,”def”,”ghi”的起始地址。
下面我们来看一段运用指针数组的代码。
//对字符串从小到大排序
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
void sort_str(char *name[], int n)
{
int i, j, min;
char *tem=NULL;
for (i = 0; i < n - 1; i++)
{
min = i;
//找最小元素下标
for (j = i + 1; j < n;j++)
if (strcmp(name[min], name[j])>0)//利用地址间接访问,name[i]即为第i个字符串首地址,根据地址找到字符串
min = j;
if (i != min)//如果最小元素下标变了,就进行交换,提高运行效率
{
tem = name[i];
name[i] = name[min];
name[min] = tem;
}
}
}
int main()
{
char *name[] = { "china","apple", "hello","world"};
int n=sizeof(name)/sizeof(name[0]);//结果为12,因为一个地址占4个字节
sort_str(name, n);
for (int i = 0; i < n; i++)
printf("%s\n", name[i]);//name[i]中存放的是字符串的首地址
system("pause");
return 0;
}
4.指向指针的指针
指向指针数据的指针称为指向指针的指针。在上面的代码语句char name[] = { “china”,”apple”, “hello”,”world”};中,name是一个指针数组,它的每一个元素都是一个指针型数据,分别指向不同的字符串。数组名name代表该指针数组首元素的地址,name+是name[i]的首地址,由于name[i]的值是地址,因此name+i就是一个指向指针型数据的指针。所以,可以定义一个指针变量p,它指向指针数组的元素,由于该元素是指针型数据,所以,p就是指向指针型数据的指针变量,即指向指针的指针。定义一个指向指针型数据的指针变量格式为:类型 ()p;如char ()p; 。运算符的结合性是从右至左的,因此可以改写为 char * * p;。
下面我们来看一段运用指针数组的代码。
#include <stdio.h>
#include<stdlib.h>
int main()
{
char *name[] = { "china", "apple", "hello", "world" };
char **p;
p = name;//把首字符串的首地址赋值给p
printf("%s\n", *p);
//*p中存放的是"china"的首地址,根据这个地址,找到字符串"china"并输出。*p的类型是char *型的,即指向字符类型的指针变量。
printf("%c\n", **p);
//**p表示取*p这个地址所指向的数据,*p中存放的是"china"的首地址,根据首地址输出首字符c;**p是char 类型的,即一个字符。
system("pause");
return 0;
}
//结果是china c
再来看下面这段代码,使我们对以上四个定义进一步了解:
#include <stdio.h>
#include<stdlib.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
//int *parr[10];//指针数组,parr是数组名,存放10和整型数据的地址
//char** parr[10];//parr数组的大小10, 每个元素是char**类型的,即指向指针数据的指针
//char ** (*ptr)[10];//ptr是指针,指向包含10个char **型元素的一维数组
int(*ptr)[10];//数组指针,ptr是指针,指向包含10个整型元素的一维数组
ptr = &arr;
/*for (int i = 0; i < 10; i++)
printf("%d ", (int *)ptr[i]);//加的是整个数组的偏移量,即ptr+1指向了0的下一个地址*/
system("pause");
return 0;
}
这段代码中只有定义ptr为int(ptr)[10];,即数组指针的形式, ptr = &arr;才能编译过去,因为&arr是取整个数组赋值给prt,所以接收它的prt也必须是指向10个整型元素的指针;注意这个语句,for (int i = 0; i < 10; i++) ,printf(“%d “, (int )ptr[i]);我将其注释掉,也就是不能企图通过这种方式将数组的10个元素输出,应为prt+1,1代表的是偏移量,此处偏移量为整个数组的长度,即10,所以,当i=0的时候输出1,i=1的时候输出的是数组最后一个元素的下一段内存地址。