美文网首页
传统数组

传统数组

作者: gpfworld | 来源:发表于2018-12-31 23:07 被阅读0次

传统数组
目录:
1、一维数组
1.1、什么是一维数组,一位数组在内存中的结构是什么?结构中每个单元空间的大小是多少?
1.2、一维数组的定义:
1.3、一维数组的数组名如a的含义包括哪些?
1.4、如何给值给一维数组?
1.5、如何使用
1.6、一维数组的传参
1.7、探讨下a和&a的区别与联系
2、二维数组
2.1、什么是二维数组
2.2、二维数组的定义:
2.3、二维数组的数组名如a的含义包括哪些?
2.4、如何给值给二维数组?
2.4、如何使用
2.5、有关二维数组参数传递
2.6、二维数组中的一维数组的数组首地址和第一个元素的地址关系
2.7、全局数组和局部数组
3、一维数组和二维数组之间的关系
3.1、将一位数组看作是二维数组
3.2、将二维数组看成一位数组
4、数组指针和指针数组
5、char p[] = “hello”;与char *p = “hello”;区别,字符串时详细讲

授课知识点安排:
1、一维数组
    1.1、为什么需要一维数组,一维数组在内存中的结构是什么?结构中每个单元空间的大小是多少?
1.2、一维数组的定义:
1.2.1、定义的形式是什么?
1.2.2、定义的目的:从内存的相关区域分配一片连续的内存空间,便于快速定义和访存
1.3、一维数组的数组名如a的含义包括哪些?以下均以int a[n]为例进行讨论。
    1.3.1、数组的别名,指代整个数组空间,sizeof(a); 就是此含义,与int a; sizeof(a)一样
    1.3.2、数组第一个元素的首字节地址(等价于&a[0]),所以int *p = a; 就等价于 int *p = &a[0];
    1.3.3、a是常量,存放的是第一个元素的首字节地址,所以不能改变其值,所以a++是错误的
    1.3.4、&a表示整个数组的首地址,其也是一个常量,int (*p)[n] = &a;
1.3.5、&a和a的关系,值相等(可打印检验),但是含义却完全不相同,因此a+1,&a+1的含义         也是完全不同的,a+1加的是一个元素空间大小,&a+1加的是一个数组空间大小。
 &a实际上对于一维数组来说没有什么意义,在多维数组中才有意义。

1.4、对一维数组,如何给值?
    1.4.1、初始化:定义数组的同时,给定具体的初始化值
        (1)指定空间大小的初始化
                a、不初始化, 如int a[10]; 其初始值随具体情况而定
                b、全部初始化为0,如int a[10] = {};等价于int a[10] = {0};全部初始化为0
                c、完全初始化,如int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
                d、部分初始化,如int a[10] = {0, 1, 2, 3, 4, 5};其余值为0,打印结果看下,反例a[10] = 
{,,,,,,1,,2,,3,,};是错误的
                e、个别初始化,如 int a[10] = {[4]=10, [7]=9, [5]=3};其余未指定的,被初始化为0,但是
这种情况c支持,但是c++不支持。

    (2)不指定空间大小的初始化
            a、如int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};数组空间的大小由实际初始化的元素个数来决定的。
反例:int a[];既不给定空间大小又不初始化是不行的,编译时编译器就不知道应该从内存的相关区域开多大空间的支票。

    1.4.2、赋值:定义时不给具体的值,只能在函数中利用相关的赋值语句来给值,
        a、不能整体赋值,如:
int a[10];
   a = {1, 2, 3, 4, 5, 6};//错误的

        b、不能将一个数组整体的赋给另一个数组,如:
            int a[10];
            int b[10] = {0, 1, 2, 3, 4, 5, 6};
            a = b;//错误,但是值得强调的是结构体支持整体赋值

        c、只能是一个一个的赋值,赋值语句只能在函数体内,如:
            int a[10];
            Int main(int argc, char **argv) 
            {
                int i, j;
                for(i=0; i<10; i++)
                {
                    scanf(“%d”, &a[i]);//或者a[i] = xxx;或者*(a+i) = xxx;赋值语句只能在函数体内
                }
            }

1.5、一维数组的访问(遍历)
    1.5.1、普通方式:利用下标法,如:
        int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//全局变量
        int main(void)
        {
            for(i=0; i<10; i++)
                Printf(“%d”, a[i]);//a++ 不可以的
            Printf(“\n”);
        }

    1.5.2、特殊方式,地址法和指针法
            我们通过指针的学习,已经知道想要访问一个变量,只需要知道他的首地址就好,恰好我们
从前面知道,a同时也代表了第一个元素的首字节地址,所以肯定能通过a访问到第一个变量,  通过a+i 就能访问到后面的任意变量。

例子1:地址法(指着常量方式)
            int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//全局变量
            int main(void)
            {
                for(i=0; i<10; i++)
                    Printf(“%d”, *(a+i));//a[i]
                Printf(“\n”);
            }
例子2:指针变量方式方式
            int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//全局变量
            int main(void)
            {
                int *p = a;
                for(i=0; i<10; i++)
                    Printf(“%d”, *(p++));//但也可以*(p+i), p[i]
                Printf(“\n”);
            }
例子1和例子2的打印结果是一致的,思考,可不可以把例子1的*(a+i)改成*(a++),试试看,为什么?

    1.6、一维数组的参数传递
        1.6.1、普通的传参方式
#include <stdio.h>
void fun(int n, int a[])//a[n],对于一维来说,n有与没有无任何影响,等价于int *a
{
 printf("sizeof(a) = %d\n", sizeof(a)); //打印的值应该是多少?
 int j;
                 for(j=0; j<=n; j++)
                 printf("%d\n", a[j]);
}
int main(void)
{    int i = 0, j = 0;
             int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
             printf("##sizeof(a) = %d\n", sizeof(a));
                 fun(sizeof(a)/sizeof(a[0]), a);  //传递的是第一个元素的首字节地址,等价于fun(&a[0]);
 return 0;    
}
当数组传递参数时,数组就会转换为相应类型的指针的用法,不再是数组本身的用法。
    1.7、探讨下a和&a的区别与联系
        以下均以int a[n];为例进行探讨 
a 和&a   的值相同,只不过是语言的编译器定义这样的用法标记,这样就是表示这样的含义,和&和*的平常的取地址和解析地址没有任何的关系。
    含义  联系  区别
a   a == &a[0]; 第一个元素的首字节地址 (1) a与&a的值相等,但含义不同
(2)Int *p=a 等价于int *p =&a[0];
   p==a==&a[0];   *p==*a==a[0];  
(3)int (*q)[n]=&a;  q==&a; *q==a; 
(4)都是常量     a+1,加的是一个元素类型空间的大小
&a  整个数组的
数组首地址       和&a+1,加的是一个数组空间的大小

从上表我们的可以得到一维数组的(第一个元素首字节地址)和(数组首地址)的一个抽象关系,如下:
        数组首地址      ==  &(第一个元素首字节地址)//这里的&也是一种强制转换
  
        第一个元素首字节地址  ==  *(数组首地址)//这里的*是一种强制转换

????

        从上面的式子,我们知道*和&,是逆向运算的关系,基本类似+-,*/之间的关系。

需要注意的是,讲指针时涉及的&是取空间地址,*是解引用寻找地址指向的空间,但是上面与数组相
关的&和*只是起到强制转换的作用。

    1.8、全局数组和局部数组
        1.8.1、定义位置
            a、全局的在所有的函数体外定义
            b、局部的在函数体内定义

        1.8.2、空间分配时机
            a、全局的:编译时就已经预定好空间,当程序加载时,在内存中的静态存储区分配
            b、局部的:当运行,在栈中分配空间,当我用static修饰,存储位置从栈变为了静态存储区
            note:
静态数据区:只允许在程序加载时分配编译时早已制定好的空间,而且在整个进程结束之前,这       些空间数据会一直存在,不可以被释放,也不可以重新开空间。
栈:函数调用时会在栈中分配自动局部变量(没有static修饰),一旦某个函数运行完,跟这个函
数相关的栈空间会全部被释放。但有static修饰的局部变量,会在静态存储区中开辟空间,子
函数运行结束后,这个变量空间并不会被释放。

    局部数组的例子:
    将int a[10]改为static int a[10],然后再看下结果,试着分析原因。

2、二维数组
2.1、什么是二维数组
2.1.1、为什么需要二维数组
2.1.2、二维数组在内存中的结构,每个元素所占空间的大小
2.1.3、将其结构与一维数组做一个比较

2.2、二维数组的定义:
2.2.1、定义的形式是什么?int a[10][5]; 思考int a[10][]; int a[][5];int a[][]; 是否合法。 
2.2.2、定义的目的:从内存的相关区域分配一大片连续的内存空间。

2.3、二维数组的数组名如a的含义包括哪些?
    2.3.1、数组的别名,指整个内存空间,sizeof(a); 
    2.3.2、第一个一维数组的数组首地址(a==&a[0]),int (*p)[n] = a; 等价于 int (*p)[n] = &a[0];
如何证明a == &a[0];  利用if( a == &a[0]),如果编译器不判错,代表是同类型指针.
a[0],第一个一维数组的第一个元素的首字节地址,a[0]==&a[0][0]
    2.3.3、a也是常量,所以不能改变其值(地址), a++仍然是是错误的
    2.3.4、根据一维数组中 数组首地址 和 第一个元素首字节地址 的关系,推知*a指的是第一个一维
   数组的第一个元素的首字节地址,也就是&a[0][0],利用printf(“%d\n”, a[0][0]), 和printf(“%d\n”, 
    *(*a)),printf(“%d\n”, *a[0])看它们是否相等来验证,或if(&a[0][0] == *a == a[0])。
2.3.4、通过前面已经知道,a和*a的不同含义,但是他们的值是完全相同的,这里可以类比一维数组,  
      但是他们的含义完全不同的,所以a+1,*a+1的含义也是是完全不同的。

2.4、对于二维数组,如何给值?
    2.4.1、初始化:定义数组的同时给具体的值
        (1)给定空间的初始化
                a、不初始化, 如int a[2][2]; 其初始值随具体情况而定
                b、全部初始化为0,如int a[2][2] = {};等价于int a[2][2] = {0};全部初始化为0
                c、完全初始化,如int a[2][2] = {0, 1, 2, 3}; 或者 = {{0, 1}, {2, 3}};
                d、部分初始化,如int a[2][2] = {0, 1, 2};或则 =  {{0, 1}, {2}}其余值为0,打印结果看下,
反例a[][2] = {,,,,,, 1,, 2,, 3,,};是不对的
                e、个别初始化,如 int a[2][2] = {[1][0]=10, [0][1]=9, [1][1]=3};其余的值为0,c++不支持个                  别初始化,只有c支持。
            
    给二维数组分配空间,由行列大小来决定,如果不初始化,直接定义,如int a[m][n],
则行列必须同时决定。

        (2)不给定空间的初始化
a、如int a[][2]= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};实际上我们仍然是给定了空间的。行 = 元素个                数/m,列=m,不能被m整除,补0增加元素个数,直到能被m整除,空间大小 = 行*列。
int a[2][]= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};这样的写法时错误的,不支持。
int a[][] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 会怎样?编译器连行列都不知道,更别谈知道空间大小。

        2.4.2、赋值:定义时不给具体的值,只能在函数中利用相关的赋值语句来给值,
            2.4.2.1、与一维数组一样只能是一个元素一个元素的赋值,例如:
    
2.4、二维数组的使用
        2.4.1、普通下标法,利用数组名a来引用, int a[m][n]
            例子:
                    for(i=0; i<n; i++)
                        for(j=0; j<m; j++)
                            printf(“%d\n”, a[i][j]);

        2.4.2、地址法(指针常量)
                    for(i=0; i<n; i++)
                        for(j=0; j<m; j++)
                            printf(“%d\n”, *(*(a+i)+j)); //a++可否?否 *(a[i]+j)可否?是 *(&a[i][0]+j) 是,(*(a+i))[j]可否?是

2.4.2、指针法(指针变量)
Int (*p)[] = a;//错误的  正确的为int (*p)[m]=&(*a);
                    for(i=0; i<n; i++)
                        for(j=0; j<m; j++)
                            printf(“%d\n”, *(*(p+i)+j));//也可以p[i][j],p++否

例子:
#include<stdio.h>
int main(void)
{
    int a[3][4]={0};
    int i,j;
    for(i=0;i<3;i++)
     {
         for(j=0;j<4;j++)
            printf("%d ",*(*(a+i)+j));
         printf("\n");
     }
    printf("\n");
    for(i=0;i<3;i++)
     {
         for(j=0;j<4;j++)
            printf("%d ",*(a[i]+j));
         printf("\n");
     }
    printf("\n");
    for(i=0;i<3;i++)
     {
         for(j=0;j<4;j++)
         {
            printf("%d ",(*(a+i))[j]);
            printf("-%d ",*(&a[i][0]+j));
         }
        printf("\n");
     }
    printf("\n");
    int (*p)[4]=&(*a);   //p等价于数组的一个维度  但是sizeof时是4
    for(i=0;i<3;i++)
     {
         for(j=0;j<4;j++)
         {

            printf("%d ",p[i][j]);
          //  printf("%d ",*(p++));
            printf("*%d ",*(*(p+i)+j));
         }
         printf("\n");
     }

    return 0;
}


2.5、有关二维数组参数传递
    讲一维数组和二维数组关系时再举例子。

2.6、二维数组中的第一个一维数组的数组首地址和它的第一个元素的首字节地址的关系
    含义  联系  区别
a   a == &a[0]; 
第一个一维数组
的数组首地址  (1)它们的值相等,但是
含义完全不同
(2) int (*q)[n];  q=a;
(3) q==a==&a[0];
 *q==*a==a[0]=&a[0][0];     a+1,加的是一个一维数组空间的大小,也就是一个列的大小,是第二个一维数组空间的数组首地址,类推a+i
*a
(==a[0])    整个二维数组第一个元素的首字节地址,也可以说成是,第一个一维数组的第一个元素的首字节地址,==&a[0][0]     *a+1,指向第一个一维数组的第一个元素(从第0个算起),
类推*a+i ,类推*(a+j)+i
a[i]    第i个一维数组的第一个元素的首字节地址,==&a[i][0]      a[0]+1, 指向第一个一维数组的第一个元素的首地址,类推a[0]+i,类推a[j]+i

        从上表我们可以得知第一个一维数组的 (第一个元素地址)和 (它的数组首地址)依然满足如抽象关系:
            数组首地址             ==  &(第一个元素首字节地址) 
            第一个元素首字节地址   ==  *(数组首地址) 

&和*是多维数组不同维级指针类型强制转换的方式。

2.7、全局数组和局部数组
    概念和一维数组完全一致,参考前面一维数组。

3、一维数组和二维数组以及多维数组的关系
3.1、将一维数组看作是二维数组
3.2、将二维数组看成是一个一维数组
3.2.1、将二维数组中每个小的一维数组看成是单个元素(正常二维数组的传参的方式)
如果二维数组中每个小的一维数组看作以一个元素的话,二维数组就变成了一个一维数组,传递
一维数组的数组名时,实际上传递的是第一个元素的地址,但是又有二维数组中第一个元素是一
个小的一维数组,因此传递的是一个数组地址,形参因此需要使用一个一维数组指针存放。
根据被调函数的设置的形参的方式不同,又分为如下几种情况:

 3.2.2、将二维数组看成巨大的一维数组
如果二维数组中每个小的一维数组看作以一个元素的话,二维数组就变成了一个一维数组,传递
一维数组的数组名时,实际上传递的是第一个元素的地址,但是又有二维数组中第一个元素是一
个小的一维数组,因此传递的是一个数组地址,形参因此需要使用一个一维数组指针存放。
根据被调函数的设置的形参的方式不同,又分为如下几种情况:

int a[3][6] = {0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25};
int a[3][6] = {{0, 1, 2, 3, 4, 5}, {10, 11, 12, 13, 14, 15}, {20, 21, 22, 23, 24,25}};

[2][5]        25
[2][4]        24
[2][3]        23
[2][2]        22
[2][1]        21
[2][0]        20
[1][5]        15
[1][4]        14
[1][3]        13
[1][2]        12
[1][1]        11
[1][0]        10
[0][5]        5
[0][4]        4
[0][3]        3
[0][2]        2
[0][1]        1
[0][0]        0

    3.3对于三维数组参数
    (1)正常传参方式(将3维中的二维看成是单个的元素)
三维数组的数组名是第一个二维数组的数组地址,需要使用二维数组指针存放。

    (2)将整个三维数组看成是一个大数组


4、数组指针和指针数组
    前面在谈到一维和二维数组的参数传递时,已经提到过了数组指针的概念,比如一位数组int a[n],fun(&a),传的是数组的首地址,那么形参就应该是void fun(int (*p)[n]); 如果是二维数组int b[m][n]时,fun(b),传的是二维数组的第一个一维数组的数组首地址,那么形参就应该是void fun(int(*p)[n]);  现在我们来认真探讨一下什么是数组指针。

4.1、数组指针:首先它是指针,放数组首地址 指向数组的指针
基本形式: 类型说明 (*p)[n]
p只是一个某类型的一级指针变量,在内存中只占用4个字节的内存单元,它是用来存放某个数      组的数组首地址。与数组指针名词的叫法刚好相反的还有一个指针数组,很容易混淆。

4.2、指针数组:首先它是数组,是用来存放指针的数组,   指针类型的数组 元素为指针类型
      基本形式: 类型说明 *p[n]  做形参时等价于类型说明 **p,因此可以看出本质上是一个二级指针,
   (*p)[n]本质上只是一个一级指针,至于以及一级指针和二级指针的区别在讲指针时已经讲
过。对于指针数组我们用图来说明:char *p[2] = {“hello”, “over”};一格代表一个字节:
所以我们在打印hello, over, good这几个字符串的时候,只要找到它们的第一个字符的地址就可以,具体例子如下:
我们看到左右两边的结果出了第一行之外,全部是相同的,为什么第一行会不同呢?不是说自定义的数组和malloc构建的数组原理是一样的吗?不错原理是一样,但是仍然是有区别的,我们现在来仔细地探讨一下:
上面的指针数组可以看成是一种特殊的二维数组,只是每个一维数组间并不是连续的,如果这些空间是malloc出来的话,我们在释放空间时,必须释放多次保证每个一维数组都被释放,但是int a[m][n],这个二维数组的每个一维数组间是连续,其实本质还是一个一位数组,所以释放空间时,只需要free(a)一次就可以。

5、char p[] = “hello”;与char *p = “hello”;存储方式上的区别,字符串时详细讲

相关文章

  • 传统数组

    传统数组目录:1、一维数组1.1、什么是一维数组,一位数组在内存中的结构是什么?结构中每个单元空间的大小是多少?1...

  • 2017-12-1总结及这个星期的计划

    c语言中传统数组的缺点: 1、数组的长度必须事先定义,且只能是常数,不能是变量。 2、传统形式的数组,该内存的数组...

  • for循环

    传统方式 1.遍历数组的传统方式 2.遍历Collection对象的传统方式 简单方式 1.遍历数组 2遍历Col...

  • Swift 基础笔记 - 数组

    OC中定义数组 Swift中定义数组 初始化空数组 定义数组时指定数组类型 遍历数组中的所有元素(传统写法) 不建...

  • Shell编程之数组

    Shell编程之数组 1.什么是数组: 数组其实也算是变量,传统的变量只能是存储一个值,但是数组可以存储多个值。 ...

  • JavaScript--数组和字符串常用方法

    数组高级API 遍历数组的几种方法利用传统循环来遍历数组for(let i = 0; i < arr.length...

  • 84-数组高级API-数组遍历

    遍历数组1. 利用传统循环来遍历数组 let arr = [1, 3, 5, 7, 9]; for (...

  • 87-删除数组元素注意点

    需求: 遍历数组的同时删除数组中所有的元素通过传统遍历数组的方式弊端: 删除不尽元素 let arr = [...

  • 关联数组与对象

    数组一般用数字索引取值,这是传统的数组。当不用数字索引而是明确给出下标,这样的数组叫关联数组 例如:var len...

  • javascript循环性能比较

    1.数组循环遍历方法 javascript传统的数组遍历有for循环,while循环,以及for-in。本篇文章要...

网友评论

      本文标题:传统数组

      本文链接:https://www.haomeiwen.com/subject/utrglqtx.html