首页 > 代码库 > 指针与数组

指针与数组

说起指针啊,真是让我牙痒痒,这个让我又爱又恨的小妖精!

刚开始学的时候,怎么也理解不了指针这个东西,指针到底是个什么东西啊。。。。直到我看到了赵岩老师的书。

也许,生活中我们就是缺少这样的一个老师,告诉我们这个就是这样的!清楚的告诉我们关于他的一切来龙去脉。要是还是不知道的话,只怪当时我没看到这本书。

 

其实吧,知道整形变量吧,他用来保存一个整数。知道字符变量吧,他用来保存一个字符,那么知道指针吧,他就用来保存一个地址。

何为地址?地址就是电脑内存中的一块区域的编号。有了编号,整个世界才能井井有条,快捷高效!

指针功能很强大,他能指向内存中任意一个地址,并且可以通过指针解引用修改那个地址上的值。

可以这样定义一个指针: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

 

指针与数组