zoukankan      html  css  js  c++  java
  • 指针与数组

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

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

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

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

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

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

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

  • 相关阅读:
    推荐一个很好得电子书下载网站
    C# 线程手册 第四章 线程设计原则 管道线程模型
    C# 线程手册 第六章 线程调试与跟踪
    C# 线程手册 第五章 扩展多线程应用程序 剖析ThreadPool 类
    C# 线程手册 第六章 线程调试与跟踪 代码跟踪
    C# 线程手册 第五章 多线程应用程序 .NET 中的扩展性
    C# 线程手册 第五章 扩展多线程应用程序 CLR 和 线程
    C# 线程手册 第五章 扩展多线程应用程序 什么是线程池
    C# 线程手册 第五章 多线程应用程序 一个多线程微软消息队列(MSMQ)监听器
    C# 线程手册 第六章 线程调试与跟踪 使用不同的监听器程序
  • 原文地址:https://www.cnblogs.com/qsyll0916/p/6759395.html
Copyright © 2011-2022 走看看