zoukankan      html  css  js  c++  java
  • 算法集锦(二)

    二叉查找树(二叉排序树)(C语言)

    #include<stdio.h>
    #include "fatal.h"
    struct TreeNode;
    typedef struct TreeNode *Position;
    typedef struct TreeNode *SearchTree;
    typedef int ElementType;
    SearchTree MakeEmpty(SearchTree T);
    Position Find(ElementType X,SearchTree T);
    Position FindMin(SearchTree T);
    Position FindMax(SearchTree T);
    SearchTree Insert(ElementType X,SearchTree T);
    SearchTree Delete(ElementType X,SearchTree T);
    ElementType Retrieve(Position P);
    struct TreeNode 
    {
        ElementType Element;
        SearchTree left;
        SearchTree right;
    };
    
    SearchTree MakeEmpty(SearchTree T)
    {
        if(T!=NULL)
        {
            MakeEmpty(T->left);
            MakeEmpty(T->right);
            free(T);
        }
        return NULL;
    }
    
    Position Find(ElementType X,SearchTree T)
    {
        if(T==NULL)
            return NULL;
        if(X<T->Element)
            return Find(X,T->left);
        else if(X>T->Element)
            return Find(X,T->right);
        else 
            return T;
    }
    
    Position FindMin(SearchTree T)
    {
        if(T==NULL)
            return NULL;
        if(T->left==NULL)
            return T;
        else
            return FindMin(T->left);
    }
    
    Position FindMax(SearchTree T)
    {
        if(T==NULL)
            return NULL;
        else if(T->right==NULL)
            return T;
        else
            return FindMax(T->right);
    }
    
    SearchTree Insert(ElementType X,SearchTree T)
    {
        if(T==NULL)
        {
            T=malloc(sizeof(struct TreeNode));
            if(T==NULL)
                FatalError("Out of space!!!");
            else
            {
                T->Element=X;
                T->left=T->right=NULL;
            }
        }
        else if(X<T->Element)
            T->left=Insert(X,T->left);
        else if(X>T->Element)
            T->right=Insert(X,T->right);
        return T;
    }
    
    SearchTree Delete(ElementType X,SearchTree T)
    {
        Position TmpCell;
        if(T==NULL)
            Error("Error not found");
        else if(X<T->Element)
            T->left=Delete(X,T->left);
        else if(X>T->Element)
            T->right=Delete(X,T->right);
        else if(T->left&&T->right)
        {
            TmpCell=FindMin(T->right);
            T->Element=TmpCell->Element;
            T->right=Delete(X,T->right);
        }
        else
        {
            TmpCell=T;
            if(T->left==NULL)
                T=T->right;
            else if(T->right=NULL)
                T=T->left;
            free(TmpCell);
        }
        return T;
    }
    ElementType Retrieve(Position P)
    {
        if(P==NULL)
            return -1;
        else
            return P->Element;
    }

    二叉查找树(二叉排序树)的详细实现

    1、序

         详细实现了二叉查找树的各种操作:插入结点、构造二叉树、删除结点、查找、  查找最大值、查找最小值、查找指定结点的前驱和后继

    2、二叉查找树简介

         它或者是一棵空树;或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树

    3、二叉查找树的各种操作

            此处给出代码,注释非常详细,具体操作请参考代码:

      1 /*************************************************************************
      2   这是一个二叉查找树,实现了以下操作:插入结点、构造二叉树、删除结点、查找、
      3   查找最大值、查找最小值、查找指定结点的前驱和后继。上述所有操作时间复杂度
      4   均为o(h),其中h是树的高度
      5   注释很详细,具体内容就看代码吧
      6 *************************************************************************/
      7 
      8 #include<stdio.h>
      9 #include<stdlib.h>
     10 
     11 //二叉查找树结点描述
     12 typedef int KeyType;
     13 typedef struct Node
     14 {
     15     KeyType key;          //关键字
     16     struct Node * left;   //左孩子指针
     17     struct Node * right;  //右孩子指针
     18     struct Node * parent; //指向父节点指针
     19 }Node,*PNode;
     20 
     21 //往二叉查找树中插入结点
     22 //插入的话,可能要改变根结点的地址,所以传的是二级指针
     23 void inseart(PNode * root,KeyType key)
     24 {
     25     //初始化插入结点
     26     PNode p=(PNode)malloc(sizeof(Node));
     27     p->key=key;
     28     p->left=p->right=p->parent=NULL;
     29     //空树时,直接作为根结点
     30     if((*root)==NULL){
     31         *root=p;
     32         return;
     33     }
     34     //插入到当前结点(*root)的左孩子
     35     if((*root)->left == NULL && (*root)->key > key){
     36         p->parent=(*root);
     37         (*root)->left=p;
     38         return;
     39     }
     40     //插入到当前结点(*root)的右孩子
     41     if((*root)->right == NULL && (*root)->key < key){
     42         p->parent=(*root);
     43         (*root)->right=p;
     44         return;
     45     }
     46     if((*root)->key > key)
     47         inseart(&(*root)->left,key);
     48     else if((*root)->key < key)
     49         inseart(&(*root)->right,key);
     50     else
     51         return;
     52 }
     53 
     54 //查找元素,找到返回关键字的结点指针,没找到返回NULL
     55 PNode search(PNode root,KeyType key)
     56 {
     57     if(root == NULL)
     58         return NULL;
     59     if(key > root->key) //查找右子树
     60         return search(root->right,key);
     61     else if(key < root->key) //查找左子树
     62         return search(root->left,key);
     63     else
     64         return root;
     65 }
     66 
     67 //查找最小关键字,空树时返回NULL
     68 PNode searchMin(PNode root)
     69 {
     70     if(root == NULL)
     71         return NULL;
     72     if(root->left == NULL)
     73         return root;
     74     else  //一直往左孩子找,直到没有左孩子的结点
     75         return searchMin(root->left);
     76 }
     77 
     78 //查找最大关键字,空树时返回NULL
     79 PNode searchMax(PNode root)
     80 {
     81     if(root == NULL)
     82         return NULL;
     83     if(root->right == NULL)
     84         return root;
     85     else  //一直往右孩子找,直到没有右孩子的结点
     86         return searchMax(root->right);
     87 }
     88 
     89 //查找某个结点的前驱
     90 PNode searchPredecessor(PNode p)
     91 {
     92     //空树
     93     if(p==NULL)
     94         return p;
     95     //有左子树、左子树中最大的那个
     96     if(p->left)
     97         return searchMax(p->left);
     98     //无左子树,查找某个结点的右子树遍历完了
     99     else{
    100         if(p->parent == NULL)
    101             return NULL;
    102         //向上寻找前驱
    103         while(p){
    104             if(p->parent->right == p)
    105                 break;
    106             p=p->parent;
    107         }
    108         return p->parent;
    109     }
    110 }
    111 
    112 //查找某个结点的后继
    113 PNode searchSuccessor(PNode p)
    114 {
    115     //空树
    116     if(p==NULL)
    117         return p;
    118     //有右子树、右子树中最小的那个
    119     if(p->right)
    120         return searchMin(p->right);
    121     //无右子树,查找某个结点的左子树遍历完了
    122     else{
    123         if(p->parent == NULL)
    124             return NULL;
    125         //向上寻找后继
    126         while(p){
    127             if(p->parent->left == p)
    128                 break;
    129             p=p->parent;
    130         }
    131         return p->parent;
    132     }
    133 }
    134 
    135 //根据关键字删除某个结点,删除成功返回1,否则返回0
    136 //如果把根结点删掉,那么要改变根结点的地址,所以传二级指针
    137 int deleteNode(PNode* root,KeyType key)
    138 {
    139     PNode q;
    140     //查找到要删除的结点
    141     PNode p=search(*root,key);
    142     KeyType temp;    //暂存后继结点的值
    143     //没查到此关键字
    144     if(!p)
    145         return 0;
    146     //1.被删结点是叶子结点,直接删除
    147     if(p->left == NULL && p->right == NULL){
    148         //只有一个元素,删完之后变成一颗空树
    149         if(p->parent == NULL){
    150             free(p);
    151             (*root)=NULL;
    152         }else{
    153             //删除的结点是父节点的左孩子
    154             if(p->parent->left == p)
    155                  p->parent->left=NULL;
    156             else  //删除的结点是父节点的右孩子
    157                  p->parent->right=NULL;
    158             free(p);
    159         }
    160     }
    161 
    162     //2.被删结点只有左子树
    163     else if(p->left && !(p->right)){
    164         p->left->parent=p->parent;
    165         //如果删除是父结点,要改变父节点指针
    166         if(p->parent == NULL)
    167             *root=p->left;
    168         //删除的结点是父节点的左孩子
    169         else if(p->parent->left == p)
    170             p->parent->left=p->left;
    171         else //删除的结点是父节点的右孩子
    172             p->parent->right=p->left;
    173         free(p);
    174     }
    175     //3.被删结点只有右孩子
    176     else if(p->right && !(p->left)){
    177         p->right->parent=p->parent;
    178         //如果删除是父结点,要改变父节点指针
    179         if(p->parent == NULL)
    180             *root=p->right;
    181         //删除的结点是父节点的左孩子
    182         else if(p->parent->left == p)
    183             p->parent->left=p->right;
    184         else //删除的结点是父节点的右孩子
    185             p->parent->right=p->right;
    186         free(p);
    187     }
    188     //4.被删除的结点既有左孩子,又有右孩子
    189     //该结点的后继结点肯定无左子树(参考上面查找后继结点函数)
    190     //删掉后继结点,后继结点的值代替该结点
    191     else{
    192         //找到要删除结点的后继
    193         q=searchSuccessor(p);
    194         temp=q->key;
    195         //删除后继结点
    196         deleteNode(root,q->key);
    197         p->key=temp;
    198     }
    199     return 1;
    200 }
    201 
    202 //创建一棵二叉查找树
    203 void create(PNode* root,KeyType *keyArray,int length)
    204 {
    205     int i;
    206     //逐个结点插入二叉树中
    207     for(i=0;i<length;i++)
    208         inseart(root,keyArray[i]);
    209 }
    210 
    211 int main(void)
    212 {
    213     int i;
    214     PNode root=NULL;
    215     KeyType nodeArray[11]={15,6,18,3,7,17,20,2,4,13,9};
    216     create(&root,nodeArray,11);
    217     for(i=0;i<2;i++)
    218         deleteNode(&root,nodeArray[i]);
    219     printf("%d
    ",searchPredecessor(root)->key);
    220     printf("%d
    ",searchSuccessor(root)->key);
    221     printf("%d
    ",searchMin(root)->key);
    222     printf("%d
    ",searchMax(root)->key);
    223     printf("%d
    ",search(root,13)->key);
    224     return 0;
    225 }

    二叉搜索树的建立

    二叉搜索树最大特征是:左边子结点的值<当前结点的值,右边子结点的值>当前结点的值。

    依照这个特征,可以使用递归和非递归两种方式建立一颗二叉搜索树。

    下面是我的代码,分明列举了递归和非递归的建立方式。最初写的代码与正确版本大致相同,但程序总是运行不通过,debug后发现问题在于指针操作错误。自以为对c语言非常熟稔了,但还是犯下如此幼稚的错误,所以贴出这个错误,作为一个警示。

    2014/5/24 ps:原来二叉搜索树最难的地方在于删除操作,所以补充一个删除操作。此外,还明白了书本介绍二叉搜索树的原因,因为很多更复杂的树结构,是以此为基础的,如b树,b+树,avl树等等。

    #include <stdio.h>
    #include <assert.h>
    #include <stdlib.h>
    
    typedef struct NODE
    {
        NODE * pleft;
        NODE * pright;
        int ivalue;
    } node;
    
    /*  错误示例,实际上这个函数并没有连接起新建的结点
    void insert_bitree(node *pt, int value)
    {
        if(pt == NULL)
        {
            pt = (node *) malloc ( sizeof(node) );
            pt->ivalue = value;
            pt->pleft = NULL;
            pt->pright = NULL;
            return ;
        }
        else if( value < pt->ivalue)
        {
            insert_bitree(pt->pleft, value);   
        }
        else
        {
            insert_bitree(pt->pright, value);
        }
    }
    */
    
    void insert_bitree(node **ppt, int value) //递归方式1
    {
        if(*ppt == NULL)
        {
            *ppt = (node *) malloc ( sizeof(node) );
            (*ppt)->ivalue = value;
            (*ppt)->pleft = NULL;
            (*ppt)->pright = NULL;
            return ;
        }
        else if( value < (*ppt)->ivalue)
        {
            insert_bitree(&((*ppt)->pleft), value); //将指向指针的指针重定位到当前结点的pleft
        }
        else
        {
            insert_bitree(&((*ppt)->pright), value); //将指向指针的指针重定位到当前结点的pright
        }
    }
    
    node* create_searchtree(node *t, int  temp)   //递归方式2
    {  
        if (t == NULL) {    // 若当前树为空  
            t = (node *)malloc(sizeof(node) * 1);  
            if (t == NULL) {  
                printf("内存分配失败!
    ");  
                exit(EXIT_FAILURE);  
            }  
            t->ivalue = temp;  
            t->pleft = NULL;  
            t->pright = NULL;  
        }else if (t->ivalue  > temp) {   // 如果比当前结点小,则插入左子树  
            t->pleft = create_searchtree(t->pleft, temp);  
        }else if (t->ivalue < temp){    // 如果比当前结点大,则插入右子树  
            t->pright = create_searchtree(t->pright, temp);  
        }  
       
        return t;  
    }
    
    node * creat_bitree(int value[], int len) //非递归方式
    {
        int i, flag;
    
        node *before;
        node *tmp;
        node *pt = (node *)malloc( sizeof(node) );
    
        pt->ivalue = value[0];
        pt->pleft = pt->pright = NULL;
    
        flag = 0;
        for(i = 1; i < len; i++)
        {
            tmp = pt;
            while(tmp != NULL)
            {
                if ( value[i] < (tmp)->ivalue)
                {
                    before = tmp; // 存储当前结点的位置
                    flag = -1;  //标志位,表明向左子树探索
                    tmp = (tmp)->pleft;
                }
                else
                {
                    before = tmp;
                    flag = 1;
                    tmp = tmp->pright;
                }
            }
            
            if(flag == -1) //将输入值插入当前结点的左子树
            {
                before->pleft = (node *) malloc (sizeof(node) );
                before->pleft->ivalue = value[i];
                before->pleft->pleft = before->pleft->pright = NULL;
            }
            else if( flag == 1)
            {
    
                before->pright = (node *) malloc (sizeof(node) );
                before->pright->ivalue = value[i];
                before->pright->pleft = before->pright->pright = NULL;
            }
        }
        return pt;
    }
    
    void preorder(node *pt)  //先序访问二叉树
    {
        if(pt != NULL)
        {
            printf("%d
    ", pt->ivalue);
            preorder(pt->pleft);
            preorder(pt->pright);
        }
    }
    
    void postorder(node *pt)
    {
        if(pt != NULL)
        {
            postorder(pt->pleft);
            postorder(pt->pright);
            printf("%d
    ", pt->ivalue);
        }
    }
    
    int main()
    {
        int a[8] = {1, 2, 7, 4, 5, 19, 9, 3};
        int len = sizeof(a) / sizeof(int);
    
    #if 1
        int i;
        node *pt = (node *)malloc(sizeof(node));
        assert(pt != NULL);
        pt->ivalue = a[0];
        pt->pleft = pt->pright = NULL;
        for(i = 1; i < 8; i++)
        {
            //pt = create_searchtree(pt, a[i]);
            insert_bitree(&pt, a[i]);
        }
    #else 
    
        node *pt = creat_bitree(a, len);
    
    #endif
    
        preorder(pt);
    
        return 0;
    }
    node * find_elem(int x, node * t)
    {
        if( t == NULL)
            return NULL;
        if( x < t->ivalue)
            return find_elem(x, t->pleft);
        else if ( x > t->ivalue)
            return find_elem(x, t->pright);
        else
            return t;
    }
     
    node * find_min(node *t)
    {
        if( t == NULL)
            return NULL;
        else if( t->pleft == NULL)
            return t;
        else
            return find_min(t->pleft);
    }
     
    node * delete_elem(int x, node *t)
    {
        node * tmp;
     
        if( t == NULL) // 已到树底,并且树中不存在该元素
        {
            printf("element: %d doesn't exist
    ", x);
            exit(-1);
        }
        else if( x < t->ivalue ) // 进入左子树
            t->pleft = delete_elem(x, t->pleft );
        else  if( x > t->ivalue )
            t->pright = delete_elem(x, t->pright);
        else if( t->pleft && t->pright) // 找到该元素,但该元素还有两个子节点,从最右端节点找到最小点,进行替换该元素所在节点,替换后该树仍为二叉搜索树
        {
            tmp  = find_min(t->pright);
            t->ivalue = tmp->ivalue;
            t->pright = delete_elem(t->ivalue, t->pright);
        }
        else // 找到该元素,但该元素仅有一个子节点
        {
            tmp = t;
            if( t->pleft == NULL)
                t = t->pright;
            else if( t->pright == NULL)
                t = t->pleft;
            free(tmp);
        }
        return t;
    }
    

    指针数组,数组指针,指针函数,函数指针,二级指针详解

    先看个简单的:char *p,这定义了一个指针,指针指向的数据类型是字符型,char  *(p)定义了一个指针P;

    char *p[4], 为指针数组,由于[]的优先级高于*,所以p先和[]结合,p[]是一个数组,暂时把p[]看成是q,也就是char *(q),定义了一个指针q,只不过q是一个数组罢了,故定义了一个数组,数组里面的数据是char *的,所以数组里面的数据为指针类型。所以char *p[4]是四个指针,这四个指针组成了一个数组,称为指针数组,既有多个指针组成的数组。

    char(*p)[4],数组指针,强制改变优先级,*先与p结合,使p成为一个指针,这个指针指向了一个具有4个char型数据的数组。故p中存放了这个char型数组的首地址,可用数组指针动态内存申请:

                                          char (*p)[10];

                                          p=(char*)malloc(sizeof(char[x])*N);

    char *f(char,char),指针函数,()的优先级高于*,故f先与()结合,成为函数f(),函数的返回值是char *类型的,故返回值是一个指针。

    char (*f)(char,char),函数指针,*与f结合成为一个指针,这个指针指向函数的入口地址。函数名就是函数的首地址。函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:调用函数和做函数的参数。

     int func(int x); /* 声明一个函数 */ 
     int (*f) (int x); /* 声明一个函数指针 */ 
     f=func; /* 将func函数的首地址赋给指针f */

    以后如果要调用函数func(),也就可以这样调用:(*f)();

    /****************************************************二级指针**************************************************/

    二级指针简单来说就是指向指针的指针。

    char a=200;

    char *p;

    char **q;//q是一个二级指针

    p=&a;

    q=&p;  //q指向指针p

    假设变量a在内存中的地址为2000H,则它们的关系就如下面的示意图:

    指针数组,数组指针,指针函数,函数指针,二级指针详解 - chopin_tech - chopin_tech的博客

    指针p指向a,p的值是2000H,*p就是取地址2000H中的值即a为200,而p本身的地址是4000H,q指向指针p,*q就是取地址4000H中的值即p的值为2000H,而**q就是取地址2000H中的值即200。

    所以:

    *p==200;

    *q=2000H;

    **q=200;

    以上的q是一个指针指针的二级指针,然而还有指向数组的二级指针。

     当一个指针变量指向另一个指针变量时,则形成二级指针。使用二级指针可以在建立复杂的数据结构时提供较大的灵活性,能够实现其他语言所难以实现的一些功能。定义二级指针的形式是:

      类型标识符**二级指针变量名

      定义指针的同时可以对其赋值,然后就可以使用了。

      如果定义一个指针数组,则指针数组名就是一个二级指针。用指针数组元素值指向长度同的字符串,操作时可以节省内存空间,而对地址进行操作,提高了运行效率。

    char   s[3][5]={ "abc ", "uio ", "qwe "}; 
    可以看成是三个指向字符串的一级指针(s[0],s[1],s[2]),由s[3]得。 
    而s[3]本身又是一个一维数组存储s[0],s[1],s[2]三个一级指针,则s就可以看作是一个二级指针,即指向指针的指针。 
    这时定义一个二级指针char**p;就能通过p访问二维数组了。

    也可以这样char *p[] = {“ab“, “cd“, “ef“};定义了一个指针数组.

    char **sp = p;

    就可以使用sp[i]来访问字符串了。

    大家都知道,要想在函数中改变形参的值,形参用指针传递就行了。

    比如:

    void f(char *p1,char *p2)

    {

        *p1=10;

        *p2=20;

    }

    void main()

    {

          char a,b;

          char *p,*q;

          p=&a;

          q=&b;

          f(p,q);

    }

     执行后此时a=10,b=20;

    原理如下:

    当调用函数f后,p1指向a,p2指向b;

    指针数组,数组指针,指针函数,函数指针,二级指针详解 - chopin_tech - chopin_tech的博客
     接着*p1=10; *p2=20;使p1指向的地址空间的值赋为10,p2指向的地址空间的值赋为20;
    指针数组,数组指针,指针函数,函数指针,二级指针详解 - chopin_tech - chopin_tech的博客
     然后函数调用结束,这时a=10,b=20;
    如果要在函数中改变指针的值,比如改变p,q的值就需要用到二级指针。
    void GetMemory(char **p, int num)
    {
           *p = (char *)malloc(sizeof(char) * num);
    }

    以上函数,就实现了在函数中改变指针的值,使指针指向新申请的空间。

    附:/*************************以下内容转载自http://msfengyun.spaces.live.com/blog/cns!7E7030E1847FD490!188.trak***////////////

    指针是C语言的一大特色,它就像一把双刃剑:使用得当能够给程序带来极大的灵活性和高效性;反之,程序就会变得难以调试,漏洞百出!

    众所周知,指针实质就是地址!一个变量的地址即称为此变量的“指针”。如果有这样一种变量:它的存储单元里存放的是其它变量的地址!我们就称之为“指针变量”。(请注意两者之间的区别:两个完全不同的概念!)

    我们都知道,数组名和函数名就是它们的入口地址。同理,一个变量名其实也是此变量的所在地址!C语言中有一种运算符为“&”:取址运算符。因为数组名与函数名本身代表的就是地址,通常不会对并且也不能对它们进行取址操作或其它运算操作(其实对于函数名的直接引用与对它取址是等价的)。这也是它们被称为“常量”的原因!但对于一个变量来讲,情况就不一样了。要想获得它的地址,就必须进行“&”运算,尽管它本身表示的也是地址值!而对变量直接进行引用得到却是它所在的内存单元的数据内容!“指针变量”作为一种变量当然也不能例外!只不过它与其它普通变量的差别是,它的内容是其它变量(包括“指针变量”)的地址,在WIN32上,它的大小恒为32位,4BYTE。而普通变量则不会有大小上的限制!对指针变量所指向的地址的数据内容的获取则是通过操作符“*”。在理解上我们将“提领操作符*”视为类型的一部分,并且这种数据类型是一种变量地址类型(均对每一个“*”而言)!

    只要明白了以上常识,“指针”将不会再是程序设计中的“拦路虎”!

    从内存的存储映象的角度来讲,C的规则数组(不包括通过数据结构设计的多维数组)不存在多维,也就是说所有的数组本质上都是一维的,而一级指针就等价于一维数组!关键的不同在于多维数组与一维数组语义上的差别!而我们理解多维数组通常将之形象地描述成“矩阵”形式。更为精确的理解是多维数组的每个元素就是一个数组,如此递归下去直至最后每个元素是一个简单的变量类型,最终得到的就是一个特殊的一维数组!

    看如下一个例程:

    #include<stdio.h>

    #include<stdlib.h>

    void main()

    {

       int a[][3]={{250,250,250},{250,250,250}};

       int* p=(int*)a;-------------------@

    }

    注:语句@在.C文件中可以写成int* p=a;但会给出警告;若是在.CPP文件中写成int* p=a;是通不过编译的!所以规范起见,最好在任何时候都要进行强制类型转换!

    通过进行调试,在WATHCH窗口中查看变量的内存地址情况如下:

    从图上可以看出二维数组在内存单元中是线性增长的。倘若此时有一个二级指针int** q=NULL;如何通过q来操作二维数组a[2][3]呢?

    通过q=a;如何呢?在.C文件中可以编译通过,但会给出警告。若是在.CPP文件中则不会编译通过!我相信很多人的第一反应是加上强制类型转换:q=(int**)a;如此以来,程序编译、链接畅通无阻,连警告也没有!但一运行就会出问题:这是当然的!下面进行详细分析。。。。。。

      

        根据我上面讲述的:q可视为int**类型,且是int*变量的地址类型变量!对q (指针变量)的引用,得到是的其(即q)内存单元的数据,即int*变量的地址,*q则是获取q所指向的int*变量类型地址的内容,相当于int* Q变量Q的直接引用,得到是int类型变量的地址。q所占的内存为4BYTE,*q所占的内存也为4BYTE。一切都清楚了。

       现在来分析二维数组a的数据类型。我们知道指针与数组的联系的常见具体应用有两种:一种是“数组指针”:形如(*ptr)[];另外一种是“指针数组”:形如*ptr[]。两者之间的区别想必大家都清楚。如果我定义一个:“数组指针”并初始化:int (*pp)[3]=a;那么通过pp完全可以操作a[2][3]。来分析一下“数组指针”(*ptr)[SIZE],ptr所指的对象是有SIZE个某种数据类型值的数组。而ptr本身又是一级指针,一级指针又等价于一维数组。a[2][3]的低维是一个维度为3的一维数组。高维是一个维度为2的一维数组,不难理解,正如前面所述:二维数组的每个元素是一个一维数组,相当于一维数组的两次嵌套。比如变量a[0]是一个维度为3的一维数组,a[1]亦是一样。这样一来,高维的那一部分可视为一个指针!一个胆大的设想出来了:二维数组本质上就等同于“数组指针”!这种想法虽然无懈可击,但想归想,事实是怎样的还得验证。现借用C++的类型识别,得出两者的数据类型:(以下语句需用头文件<typeinfo>)

    cout<<typeid(a).name()<<endl;

    cout<<typeid(pp).name()<<endl;

    输出结果为:int (*)[3](换行) int (*)[3]

    两者完全相同,与设想一致!

        

        现在回到问题上来,q=(int**)a;强制转换成功,但却不可能正确运行!原因已浮出水面:q这个地址单元存放的是int*类型的“指针变量”的地址,而二维数组a骨子里却是一个“数组指针”。两者完全是“八竿子打不着”!想一想它们的内存分布情况,前者(地址)所指向的内存大小恒为4BYTE,后者(地址)所指向的内存大小是随着你定义的数组维数而不断变化的!即使通过强制类型转换成功,q的内存值就是a所代表的地址,但这个地址仅仅是一个地址,而q的内存值不仅要求是一个地址,而且还必须是一个“指针变量”的地址!只有这样通过*q(前面说过:*q则是获取q所指向的int*变量类型地址的内容,即一个int变量的地址)才能操作一个普通变量的地址,否则就是用“*”来操作普通变量,想一下int x=250;*x表示的是什么呢?

       或许有人会问:你不是说二维数组实质上就是一维数组吗,怎么二维数组实质上又是“数组指针”?这里有必要强调一下:我是从它们的存储映象上来讲的,但编译器的语义实现上两者是绝不能划等号的!你能够将一个二维数组赋值给一个一维数组吗?显然是不行的!因此我们这样想:语句q=(int**)a;是将一个一维数组(等价于一级指针)赋给一个二级指针(要通过“&”赋一级指针的地址才行),地球人都知道这是行不通的!虽然乍听起来还蛮合理的,其实此般理解无异于穿凿附会。刚才解释过,两者的语义迥异!不过,这样理解似乎更能深刻且方便地知道那样做错在哪里了,呵呵。。。。。。

    另:

    /*******************************以下内容转载自http://zhidao.baidu.com/question/126882280*************///////////////////////////

    #include <iostream.h>
    void main()
    {
    int a[2][3];
    int**p=a;
    }

    请问为什么是错误的??(请不要说数组名是一个指针这个我知道,我想知道为什么不能用二级指针指向二维数组)

    本来不想写什么的,但是看看,我觉得都没有我想要的答案,于是我像楼主推荐我的拙见。其实这个数组与指针的问题,要写的话,一句话,两句话是将不清楚的。
    首先数组和指针的概念你没分清楚,数组的本质你没搞清楚。这是导致问题出现的根源。
    int x[5]; 这个定义里面,我们说定义了一个数组x,此数组有5个数组元素,元素的类型为int类型。首先要问的是,x到底为什么东西? 我知道,在谭浩强的书上面说x是数组名,x代表了数组第一个元素的首地址。没错,x确实是数组的名字,x的值也确实是第一个数组元素的地址值。注意这里我们说x代表的值与数组第一个元素的地址值相等,但是并不是说他们的类型是一样的。那么x的类型到底是什么呢? 有人说就是int * 类型。有如下语句可以做证:
    int *p=x; //这句话是正确的。
    x的类型真是int *吗,我们说不是,因为下面的语句是不正确的:
    int a=10;
    x=&a; // int *类型的变量时可以接受值的。所以x不是int*
    那么我们可以猜测x的类型是不是 int *const呢。也就是说x是一个地址值不可以改变的指针。这句话貌似有点正确。但是请大家看看下面的例子:
    int x[5]={0};
    int a=sizeof(x); // a的值到底是多少?实际上这里a的值是5*4=20
    我这里使用的编译器是VC++ 6.0 int类型数据占用4个字节空间,所以这里的道的是整个数组占用的字节数。 我们不是说x的类型是iint * const类型的吗,也就是x应该是一个指针类型,应该是4个字节的啊,为什么sizeof出来时整个数组占用的字节数呢。例如
    sizeof(int *)这个的结果就是4。所以有此可以看出,x的类型并不是int*,也不是int * const。
    int x[5];中的x到底是什么呢,我们说x是数组,此数组有5个元素,并且每个元素都是int类型。 我们有一个识别数据类型的规律例如:
    int x; //x类型为int
    int *x;//x类型为int *
    int **x;//x类型为int **
    int (*x)[10];//x类型为int(*)[10]实际上是指向数组的指针
    int (*x)(int ,int);//x的类型为int(*)(int,int)实际上是指向函数的指针
    由此可以看出,一个符号是什么数据类型,我们只要在其定义的表达式中去掉符号本身,剩下的就是符号的类型了。照此推断,int x[5];中x的类型应该是 int [5]这个类型,可以看出此类型并不是int *类型。
    那么int x[5];中的x可以这样赋值: int *p=x; 为什么呢,只能说这里面将x的类型隐式转换为了int *类型。所以这里是可以赋值的,因为进行了类型转换。 再请看下面的例子:
    void function(int x[5])
    {
    cout<<sizeof(x)<<endl; //这里输出4
    }
    为什么会输出4,而不是4*5呢,可以看出上面的函数形参实际上类型是int*,并不是数组类型,所以我们在定义函数的时候,下面的都是与上面等价的:
    void function(int x[])//元素个数是多少可以省略
    {
    cout<<sizeof(x)<<endl; //这里输出4
    }
    void function(int *x) //直接写成指针变量也没错
    {
    cout<<sizeof(x)<<endl; //这里输出4
    }
    他们都是等价的。
    那么我们看一个类似的问题:
    int x[5];
    int **p=&x; //为什么会报错? 因为类型不匹配。
    p的类型是int **,而&x的类型却不是int **。 &x的类型实际上是int(*)[5],因为去的是x的地址,也就是说这个地址是数组的地址,并不是指向数组第一个元素的指针的指针(也就是二维指针),而是整个数组的地址。所以我们可以改成下面的:
    int (*p)[5]=&x;//这就对了。
    指向数组的指针,和指向数组元素的指针有什么不同?
    我们说对于一个指针变量,要几点是我们必须注意的,例如int *p;我们要注意的是,p的类型是int*,p占用的空间4个字节,p指向的数据类型是int。p指向的数据类型占用4个字节。所以对于指针变量,我们要明白指针变量本身是占用空间的,本身是有类型的,其次指针变量所指向的空间是有类型的,是有空间的。
    那么int *p; char *p1; 对于指针变量来说p,p1里面都放的是地址值,说白了就是一个数值,他们都占用4个字节的空间,但是他们的类型不一样,p里面的地址指向的是int类型的数据,p1指向的是char类型的数据,这主要体现在p++与p1++中他们在内存中移动的字节数是不一样的,我们假设int占4个字节,char占1个字节。那么对于p来说向前移动了4个字节,p1来说移动了一个字节。这就是他们的类型不同,导致运算过程中的不同。
    int x[5];
    int (*p3)[5]; 此时p3指向数组x,那么p3++实际上向前移动了多少呢,可以算出移动了4*5个字节。也就是p3指向的是一个数组,是整个数组,所以p3移动的时候是将一个数组当做一个整体来看待的。所以向前移动了一整个数组的距离。
    再看你的问题之前,我们来看一个类似的问题:
    int a[2][3];
    int**p=&a; //这里我用&a来赋值行不行呢。是不行的。
    这里为什么是错误的,原因就是因为&a的类型不是int**类型。所以类型不兼容,导致不能赋值,同时这两种类型是不可以相互转换的。 那么&a到底是一个什么样的类型呢。 我们说&a去的是整个数组的地址,那么&a自然就是指向整个数组的指针了。 int (*p)[2][3]=&a; 此时这样赋值才是正确的。如果我们要用a直接赋值,那该定义一个什么样的变量来接受它呢,首先要明白,数组名代表的地址类型是指向数组的第一个元素的指针,例如:
    int a[10];
    int *p=a; 实际上这里与 int *p=&a[0];是等价的。因为指向a[0]的指针类型就是int*类型。  那么&a的是去数组的地址,其类型是指向数组的指针,而不是指向数组第一个元素的指针,整个是要区别的,他们的类型就不一样。 int(*p)[10]=&a;
    所以说这里的a和&a绝对不是同一个东西,虽然本质上他们的地址值是一样的,但是他们的类型不一样。就决定他们代表不同的意义。
    那么刚刚说了对于下面的例子:
    int a[2][3];
    int (*p)[2][3]=&a;//我们可以定义这样的一个变量p来接受&a的值。
    那么我们要接受a应该定义一个什么样的变量呢。a[2][3]是一个二维数组,可以看成是这样的a是一个数组,具有两个元素,分别为a[0],a[1]其中这两个元素的值a[0],a[1]他们的值又是一个具有3个元素的数组。此时我们可以将a[0],a[1]看成是数组名,那么a[0][0]就是数组a[0]的第0个元素了。对应关系如下:
    a[0] ---->  a[0][0],a[0][1],a[0][2]
    a[1] ---->  a[1][0],a[1][1],a[1][2]
    那么a到底是什么,其实a数组有两个元素,a[0],a[1],那么a的值自然就是其第一个元素的地址了,也就是&a[0]了。这是一个什么类型?  我们知道如果我们将a[0]看成一个整体,例如我们用A来代替a[0],那么A[0],A[1]就相当于a[0][0],a[0][1] 。 此时A就是一个int类型的数组,&A,的类型实际上就是 int(*p)[3]这个类型。
    所以下面的代码也是正确的:
    int a[2][3];
    int(*p)[3]=a; //所以对于你的问题,可以这样子。。
  • 相关阅读:
    流处理 —— Spark Streaming中的Window操作
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.8 提供带注解的限定符元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.7 为自动检测组件提供作用域
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.6 给自动检测组件命名
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.5 在组件中定义bean的元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.4 使用过滤器自定义扫描
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.3 自动检测类和注册bean的定义
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.2 元注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.1 @Component和深层的构造型注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10 类路径扫描和被管理的组件
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8464210.html
Copyright © 2011-2022 走看看