首页 > 代码库 > 指针与数组
指针与数组
说起指针啊,真是让我牙痒痒,这个让我又爱又恨的小妖精!
刚开始学的时候,怎么也理解不了指针这个东西,指针到底是个什么东西啊。。。。直到我看到了赵岩老师的书。
也许,生活中我们就是缺少这样的一个老师,告诉我们这个就是这样的!清楚的告诉我们关于他的一切来龙去脉。要是还是不知道的话,只怪当时我没看到这本书。
其实吧,知道整形变量吧,他用来保存一个整数。知道字符变量吧,他用来保存一个字符,那么知道指针吧,他就用来保存一个地址。
何为地址?地址就是电脑内存中的一块区域的编号。有了编号,整个世界才能井井有条,快捷高效!
指针功能很强大,他能指向内存中任意一个地址,并且可以通过指针解引用修改那个地址上的值。
可以这样定义一个指针:int *p;这里,p就是那个地址,p = &a;*p就是指针指向的那个数值,只不过我这里没有对指针进行初始化,是不合适的。
在定义指针的时候,要规定他指向的数据类型。比如前面我定义的int,他就是告诉编译器,这个指针是指向int类型的。所以”本身保存的地址“和”指向变量的类型“是指针的两个属性。
当你定义指针没有初始化的时候,你的指针是一个野指针,要避免,不然暴露了你是新手的事实。定义空指针的时候,让他等于 NULL ;
指针赋值原则:一个xx型的指针保存一个xx型的地址(指针真理)。
int a[5];定义了一个5个元素的数组a,数组变量a本身代表一个地址!也就是数组中第一个元素的地址,等价于&a[0];
所以有了数组真理:xx型数组变量代表一个xx型地址。所以:可以这样说 p = a;他们都是地址嘛。
指针和数组是两个完全不一样的东西,只是在某些情况下他们是等价的。
当用 a[i] 在一个表达式中的时候,编译器会自动将其转换成指针加偏移量 *(a+i) 的形式。a[i] 这种形式是给程序猿写程序的时候准备的,也是为了让别人看到方便。
所以在c中,数组的变量和下标是可以互换的。a[3] == 3[a];
我们将指针与数组进行排列组合就得到四个名词:指针指针,指针数组,数组指针,数组数组。
1、指针指针 int **pp;
顾名思义,指向指针的指针。int a;int *p ;p = &a;这是没错的,指针变量p保存常量a的地址;那如果我们想要保存指针p的地址呢,以此类推,我们定义一个指向指针的指针。
int **pp = &p;就酱。
2、指针数组 int *pa[5];
对于一个指针数组,数组中保存的都是指针,数组名就是指针型地址。这里执行 pa+1 是错误的,这样赋值也是错误的:pa=a;因为pa是个不可知的表示,
只存在pa[0]、pa[1]、pa[2]...p[n-1],而且它们分别是指针变量,可以用来存放变量地址。但可以这样 *pa=a; 这里*pa表示指针数组第一个元素的值,a的首地址的值。
一个例子来表示
char *a[] = {"zhao","yan","is","a","good","teacher",NULL}; char **p; for(p=a;*p!=NULL;p++) { printf("%s\n",*p); }
3、数组指针 int (*ap)[n];
这时,(*ap)是一个指针,指针指向的类型为 int [n];指向一个整型的一维数组,这个一维数组的长度是n,也可以说是ap的步长。
也就是说执行 ap+1 时,ap要跨过 n 个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。行指针,每次加1后指向下一行。 但是在实际工程中,很少用指针的形式访问二维数组,还是a[i][j]比较方便
数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。
指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
4、数组数组(二维数组) int aa[][];
其实c中并没有二维数组,只是这样叫比较形象,便于理解。二维数组在内存中也是按照一维数组的方式储存的,只不过每一行又成了一个一维数组。
就是一维数组型的一维数组。
二、动态内存分配 malloc和calloc
使用这两个需要引用<stdlib.h>
int main() { char s[] = "123456"; char *t = (char*)malloc(strlen(s)+1);//动态内存分配,不浪费任何一个内存 strcpy(t,s); printf("%s\n",t);
free(t);//养成好习惯,释放空间 }
看书的时候我就感觉这里面只有这一个程序还是比较有用的,其他的对于堆和栈的操作让我有点不知所措,可能还没到一定程度,暂时不是很懂,就不写那个了。
三、字符串
在看到这个名字的时候,我一直以为字符串很容易,不就是一行字母嘛。
大错特错。
现在才知道字符串原来还可以这样的啊。
其实 c 中并没有字符串这种数据类型,他是利用字符数组来模拟的。
*gp = "hello_gp",定义字符串的时候 gp 指向字符串的首地址,
char ga[] = "hello_ga",ga也是指向字符串的首地址。
下面用一个程序来说明字符串在储存时的特点。
1 #include<stdio.h> 2 3 char *gp = "hello_gp"; //保存在常量储存区 ,在此区域的东东,无法对其进行操作 ,不能改变 4 char ga[] = "hello_ga";//静态储存区 ,他有自己独立的储存空间,内容允许修改 5 6 char *f() 7 { 8 char *p = "hello_p";//常量储存区 9 char a[] = "hello_a";//保存在栈上 ,他有自己独立的储存空间,内容允许修改 10 p[0] = ‘z‘;// 错误 ,这种错误会导致程序运行终止。 11 gp[0] = ‘z‘;//错误 12 gp = a;//保存在常量储存区的东西内容不能改变,但是指向他的指针可以改变指向 ,可以让他指向a的地址 13 gp[0] = ‘z‘;//这时就可以了, 14 return a; 15 16 } 17 18 19 int main() 20 { 21 char *str = f(); //str获取数组a的地址 ,但是a是保存在栈上的, 22 //当f函数结束的时候,所有局部变量 都从栈中弹出消失 ,所以a不在存在,str得到了一个空地址。 23 24 str[0] = ‘z‘;//错误 25 ga[0] = ‘z‘; 26 27 }
四、指针函数
首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,
函数返回值必须赋给同类型的指针变量。,int *f(x,y);定义一个指针函数f(x,y)。
float *fun();
float *p;
p = fun(a);
在书中只是说使用指针函数可以避免一定的内存泄露。
(内存泄露:指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,
由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。)
int *f() { int *ptr = (int*)malloc(sizeof(int)); *ptr = 999; return ptr; } int main() { int *p = f(); printf("%d",*p); free(p);//避免内存泄露 }
五、函数指针
函数指针是指向函数的指针变量,即本质是一个指针变量。
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将 func 函数的首地址赋给指针 f */
因为用函数指针调用函数比较麻烦,不如直接调用方便,所以更多的情况下,函数指针经常用在需要使用回调函数的场景中。
所谓回调函数,网上有很多优秀的帖子,看了一些,感觉还是很难理解。就大致说一下吧,感觉在硬件开发上面,这个好像很少用到,主要用在软件开发上面,为程序提供一个接口。方便他人使用。
所谓的回调,就是程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序。程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法。
关于回调函数,这里有一个帖子很好:http://blog.csdn.net/callmeback/article/details/4242260/
关于复杂声明的定义,在这篇博客里写的很好:http://www.cnblogs.com/afarmer/archive/2011/05/05/2038201.html
指针与数组