zoukankan      html  css  js  c++  java
  • 《C专家编程》数组和指针并不同--多维数组

    《C专家编程》数组和指针并不同

    标签(空格分隔): 程序设计论著笔记


    1. 背景理解

    1.1 区分定义与声明 p83

    • 声明相当于普通声明:它所说明的并不是自身,而是描写叙述其它地方创建的对象,声明能够多次出现;
    • 定义相当于特殊声明:它能够为对象分配内存。仅仅能出如今一个地方。

    1.2 数组和指针的訪问方式

    • 左值和右值
               X = Y ;
      • 符号X的含义是X所代表的地址。这被称为左值,左值在编译时可知,左值表示存储结果的地方。

      • 符号Y的含义是Y所代表的地址的内容,这被称为右值。右值直到执行时可知,如无特别说明,右值表示“Y的内容”。
      • :数组名能够用于确定对象在内存中的位置,也是左值,但它不能作为赋值对象,因此数组名是个左值但不是可改动的左值。
    • 数组和指针怎样被訪问
      • 数组:char a[9]="abcdefgh";... c=a[i];
        编译器符号表具有一个地址,9980,执行步骤1:取i的值。将它与9980相加。执行步骤2。去地址(9980+i)的内容。
      • 指针:char* p ... c=*p;,其快速编译器p是一个指针(在很多现代的机器里它是四个字节对象),它指向的对象是一个字节。

        为了取得这个字符,必须得到地址p的内容。把它作为字符的地址并从这个地址中取得这个字符。指针的訪问要灵活的多,但要添加一次额外提取:
        编译器有一个符号p,它的地址为4624。执行时步骤1。取地址4624的内容,就是‘5081’;执行步骤2:取地址5081的内容。

    2. 数组与指针的差别

    2.1 不同点:

    序号 指针 数组
    1 保存数据的地址 保存数据
    2 间接訪问数据。首先取得指针的内容,把它作为地址,然后从这个地址提取数据。

    假设指针有一个下标[i],就把指针的内容加i作为地址,从中提取数据

    直接訪问数据。a[i]仅仅是简单的以a+i为地址取得数据
    3 通经常使用于动态数据结构 通经常使用于存储固定数目且数据类型同样的元素
    4 先关函数为malloc(),free() 隐士分配和删除
    5 通常指向匿名数据 自身即为数据明

    注。注!

    注!
    数组和指针都能够在他们的定义中使用字符串常量进行初始化。虽然看上去一样。但底层机制不一样。
    定义指针时。编译器并不为指针所指向的对象分配空间。它仅仅是分配指针本身的空间。除非在定义的同一时候赋给指针一个字符串常量进行初始化。
    char *p="breadfruit";
    注意仅仅有对字符串常量才是如此。不能指望浮点数之类的常量分配空间。如:
    float *pip=3.14;/*错误,无法编译通过*/
    在ANSI C中,初始化指针所创建的字符串常量被定义为仅仅读(存储在静态区)。假设试图通过指针改动这个字符串的值,程序出现没有定义(出错)
    数组也能够用字符串进常量进行初始化:
    char a[]="gooseberry";
    与指针相反,由字符串常量初始化的数组是能够改动的(存储在栈中)。当中单个字符在以后能够改变:
    strncpy(a,"black",5);

    2.2 同样点:

    • tips:全部作为函数參数的数组名总是能够通过编译器转换为指针。且被当做指向该数组第一个元素的指针
      编译器仅仅向函数传递数组的地址。而不是整个数组的拷贝。事实上处于效率考虑。
      所以,一下形式均合法。且终于被编译器转化为指针形似:

      func(int* a);
      func(int a[]);
      func(int a[10]);
      

      所以,在函数内部。使用sizeof(a)无法得到数组的大小,由于数组a[]在作 为形參时被自己主动转化为指针。所以sizeof(a)一般为4(存储指针的空间)

    • a[i]这样的形式对数组进行訪问总是被编译器“改写”成或解释为像*(a+i)这样的指针訪问。

    3. C语言的多维数组

    C语言中,定义和引用多维数组唯一的方法是使用数组的数组:

    ——————————————————————
    char carrot[10][20];//声明一个10*20的多维数组
    或者声明能够看上去更像“数组的数组”形式
    typedef char vegetable[20];
    vagetable carrot[10];

    不论哪种情况。訪问单个字节都能够通过carrot[i][j]的形式,
    编译器在编译时会把它解析为*(*(carrot+i)+j)的形式
    ——————————————————————
    tips:C语言的数组就是一维数组:
      当提到C语言中的数组时,就把它看作是一种向量(vector)。也就是某种对象的以为数组。数组的元素能够是还有一个数组。

    ### 3.1 内存中数组的布局
    在C语言多为数组中,最右边的下标是最先变化的,这个约定被称为“行主序”.事实上线性存储, a[i][j] 与 *(*(a+i)+j)等价
    ### 3.2 多维数组初始化:
    int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

    3.2 多维数组的声明

    指针数组
    char *pea[4]; //一维指针数组。每一个指针指向一个字符串,数组的每一个元素内为一个char*指针
    (注意差别:char(*pea)[4];数组指针,一个指向具有4个字符类型元素的数组
    能够进行例如以下初始化:
    for (j=0;j<=4;j++)
    pea[j]=malloc(6);

    也能够一次性的用malloc分配整个数组:
    malloc(row_size*column_size*sizeof(char));
    软件信条
    对于s[i][j]这样形式的原型声明:

    int s[2][3];/* int 型 二维 数组*/
    int *s[2]; /* int 指针数组,每一个指针指向一个含有3个元素的以为一维数组*/
    int **s; /* 指向指针的指针*/
    int (*s)[3];/* 数组指针,一个指向为int数组(长度为3)的指针

    都是由于作为左值的数组名本编译器当做指针。

    锯齿状数组:
    假设声明一个字符串指针数组。并依据须要为这些字符串分配内存。将会大大节省系统资源:

    char* turnip[UMPTEEN]
    char my_string[]="your message here";
    /*贡献字符串*/
    turnip[i]=&my_string[0];
    /*拷贝字符串*/
    turnip[j]=malloc(strlen(my_string)+1);
    strcpy(turnip[j],my_string);

    tips: p225
    数组和指针參数被编译器的改动规则:

    实參 所匹配的形參
    数组的数组 char c[2][3] char(*)[3] 数组指针
    指针数组 char *c[2] char** 指针的指针
    数组指针(行指针) char (*a)[3] char (*a)[3] 不改变
    指针的指针 char **a char**a 不改变

    3.3 使用指针向函数传递一个多维数组

    1. func(int array[10][20]);
      这样的办法最简单,但作用最小,其迫使你仅仅处理10行20列的int型数组
    2. 省略第一维长度:
      func(int array[][20]);
      但其依旧限定每行必须是20个整数的长度
      相似的也能够声明为:
      func(int (*array)[20]);传递一个数组指针。数组的长度为20.
    3. func(int ** array);
      动态数组形式:二维数组在堆上分配,各行地址空间不一定连续,函数參数使用指针形式:
      为了进一步提高泛用性,把二维数组空间的分配也动态化。使用malloc()在堆上分配空间,反复一下前言中的方式例如以下:
    //1. 数组声明
    int **array;
    array = (int **)malloc(m *sizeof(int *));
    for(i=0;i<M;i++)
        array[i] = (int *)malloc(n *sizeof(int));
    这时,在分配空间的作用域里。对0<=i<M,0<=j<N。array[i][j]的訪问全然没有问题。那么,相应地。函数写作
    //2. 函数声明
    
    int func(int **array,int m,int n) {
        ...
        printf("%d ", *(*(array+i)+j));
        ...
    }
    
    值得注意的是,虽然malloc()每次分配的空间在地址上是连续的,可是多次malloc()分配的空间之间并不一定是连续的。这与在栈上分配的二维矩阵有着根本的不同。对于二维数组array[3][3]。不能再用array[1][4]来訪问array[2][1]了,前者地址越界。

    1. 折中形式:用堆上分配的一维数组表示二维数组,函数參数使用指针形式
      用一维数组来实现二维数组。是一种折中方案,可是非常好理解,也不易出错。这样分配的数组空间是连续的。使用时须要把两维下标转化为一维下标。
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    int func(int *array, int m, int n) {
        int i,j;
        for(i=0;i<m;i++) {
            for(j=0;j<n;j++)
                printf("	%d",*(array+i*n+j));
            printf("
    ");
        }
        return 0;
    }
    
    int main(int argc,char** argv) {
        int m,n,i;
        int *array;
        assert(argc == 3);
        m = atoi(argv[1]);
        n = atoi(argv[2]);
        array = (int*)malloc(m*n*sizeof(int));
        for(i=0;i<m*n;i++)
            array[i] = i;
        func(array,m,n);
        return 0;
    }
    1. 较新的编译器:用栈上分配的直到执行时才确定大小的二维数组(即C99的变长数组,它同意使用变量定义数组各维)
    int quarters = 4;
    int regions = 5;
    double sales[quarters][regions]; //一个变长数组VAL

    变长数组有一些限制:变长数组必须是自己主动存储类的,意味着它们必须在函数内部或作为函数參数声明,并且声明时不能够进行初始化。


    C90不支持这样的形式,C99支持,因此一些较新的编译器能够对以下的代码进行执行。

    注意print()的參数顺序不能改变。

    void print(int x, int y, int a[x][y]){
        printf("
    ");
        int i, j;
        for(i = 0; i < x; i++){
            for(j = 0; j < y; j++)
                printf("%d     ", a[i][j]);
            printf("
    ");
        }
    }
    
    // Function to initialize the two-dimensional array
    void init_2d(int *a, int x, int y){
        int i, j;
        for(i = 0; i < x; i++){
            for(j = 0; j < y; j++){
                a[i*y + j] = i + j;
            }
            printf("
    ");
        }
    }
    
    int main(){
        int m , n ;
        scanf("%d %d",&m,&n);
        int a[m][n];  // a two dimensional whose size has been defined using variables
        init_2d(a, m, n);
        print(m, n, a);
    }
    

     这段代码出自http://stackoverflow.com/questions/17181577/two-dimensional-arrays-in-c

      (2013.7.28更新)

      另外,这样的分配方式仍然是在栈上,相关讨论可见于http://bbs.csdn.net/topics/90350681

    spf 20160121 二维数组使用測试

    1. 动态二维数组,在堆上分配,但存在内存不连续情况
    
    void PrintArray(int **array, int m, int n)
    {
        if (array == NULL || (*array) == NULL)
            return;
        printf("********打印二维数组**************
    ");
    
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
                printf("%d   ", array[i][j]);
            printf("
    ");
        }
    
    }
    int main()
    {
        int M, N;
    
    
        printf("请输入二维数组的行和列。以空格隔开:
    ");
    
        scanf("%d %d", &M, &N);
    
        int **array=NULL;//声明array[m][n] 二维数组
        array = (int**)malloc(M*sizeof(int*));
        if (array == NULL)
            return -1;
        for (int i = 0; i < M; i++)
        {
            array[i] = (int*)malloc(N*sizeof(int));
            if (array[i] == NULL)
                return -1;
        }
    
    
        printf("请输入%d行%d列的二维数组
    ",M,N);
        for (int i = 0; i < M; i++)
            for (int j = 0; j < N; j++)
                scanf("%d", &array[i][j]);
        PrintArray(array, M, N);
    
        for (int i = 0; i < M; i++)
        {
            free(array[i]);
            array[i] = NULL;
        }
    
        free(array);
        array = NULL;
    
        free(NULL);
        system("pause");
        return 0;
    }
    
    1. 动态一维数组,当做二维数组使用(推荐)
    void Print(int* array, int m, int n)
    {
        if (array == NULL)
            return;
        printf("********打印二维数组**************
    ");
    
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
                printf("%d   ", *(array+i*n+j));
            printf("
    ");
        }
    }
    int main()
    {
        int M, N;
    
    
        printf("请输入二维数组的行和列,以空格隔开:
    ");
    
        scanf("%d %d", &M, &N);
        int* array = (int*)malloc(M*N*sizeof(int));
        if (array == NULL)
            return -1;
    
        printf("请输入%d行%d列的二维数组
    ", M, N);
        for (int i = 0; i < M; i++)
            for (int j = 0; j < N; j++)
                scanf("%d",array+i*N+j);
        Print(array, M, N);
    
        free(array);
        array = NULL;
    
        system("pause");
        return 0;
    }

    4. 參考文献:

    1. http://www.cnblogs.com/wuyuegb2312/archive/2013/06/14/3135277.html
    2. http://www.cnblogs.com/cpoint/p/3368380.html
  • 相关阅读:
    linux 安装jdk
    linux 安装 Redis
    jvisualvm监控远程jvm的两种连接方式
    list转map(JDK8-Lambda表达式)
    循环删除list中的某一元素的三种方式
    SpringBoot-@ControllerAdvice 拦截异常并统一处理
    Spring-@ControllerAdvice 拦截异常并统一处理
    Tomcat系列
    thinkPHP5.1模型User设计
    uniapp后台api设计(微信user表)
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7227442.html
Copyright © 2011-2022 走看看