C语言—数组—系统整理

本文介绍了C语言中数组的基本概念,包括一维数组和二维数组的创建、初始化以及使用。文章详细讲解了数组的内存布局、下标操作、以及如何避免下标越界。还讨论了C99中的变长数组和指定初始化器,并提到了数组作为函数参数时的注意事项,强调了数组名在不同情况下的含义差异。
摘要由CSDN通过智能技术生成

首先,我们先简单介绍一下数组,人们通常要借助计算机完成处理大量相同类型的数据,例如每月的支出、日常降雨、季度销售额等任务。所以在C语言中就避免不了要出现数组的概念,即数组由数据类型相同的一系列元素组成。

  1. 一维数组的创建和初始化

数组的创建:

//元素类型   数组名 常量表达式,用来指定数组的大小。
     int    arr1 [10];
 
//特例:                               
int n=10;
int art[n];    //C99中引入了变长数组的概念,允许数组的大小用变量来指定,如果编译器不支持C99变长数组,那就不能使用。
//在vc2022中是不支持变长数组的,linux系统中支持c99。
//并且在c99中变长数组不能初始化。

数组的初始化:

int arr1[10]={1,2,3,4};           //不完全初始化,只初始化一部分,剩下部分自动初始化为0.
int arr2[10]={1,2,3,4,5,6,7,8,9,10};   //完全初始化。
char ch1[]={'a','b','c'};   //初始化时没有设定数组的大小,具体大小是由初始化内容的项数决定。
char ch2[]={'a',98,'c'};     //ch1和ch2这两种表示方法相同,  
char arr3[]="abc";             //这里默认数组的大小是4,因为字符串,结尾有'\0'
char arr4[]={'a','b','c'};    //这里默认数组的大小是3。

int  arr3[10];               //当我们只创建,不初始化,里面的内容是随机值      

补充:静态变量和全局变量在未初始化下,会默认初始化为0.

(1.静态变量可以分为静态全局变量和静态局部变量,即由static所修饰的全局变量和局部变量。2.并且我们还需要区分静态全局变量和全局变量的区别,虽然说未初始化下它都会默认为0.但是这两个概念是完全不同的,具体细节可以留言,我会再出一章进行解释。3.因为存放在静态区,所以这种方式也称为静态存储类别。)

局部变量和形式参数在未初始化下,是随机值。

(这是由auto关键字定义的变量,auto一般会省略,将这种变量存放在栈区,栈区的变量并不会进行默认初始化。并且这种方式也叫做动态存储类别。 其实本质上就是因为它们放在内存的不同区域)

关于初始化的问题我们只简单介绍到这里,其实还有内容的,关于C99中增加了一个新特性:指定初始化器。有兴趣的可以自行搜索。

  1. 一维数组的使用

首先我们先介绍一个操作符:[ ]----下标引用操作符,用于数组访问的。

例如:int arr[10]; //arr和4是[ ]该操作符的两个操作数。

#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int sz = sizeof(arr)/sizeof(arr[0]);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        arr[i]=i;
    }
    for (i = 0; i < sz; i++)
    {
       printf("%d ",arr[i]);
    }
    return 0;
}
//简单的对数组进行赋值,然后输出。

注:1.我们只是在创建和初始化的时候数组下标不能使用变量,而我们在使用时可以使用变量。

2.上面的代码中使用循环给数组的元素一次赋值。C语言不允许把数组作为一个单元赋给另外一个单元,除初始化外也不允许使用花括号列表的形式赋值

//演示错误复制。
int arr1[5]={5,3,2,4};
int arr2[5];

arr2=arr1;                    //不允许
arr2[5]=arr1[5];             //数组下标越界
arr2[5]={2,3,4,5};          //不允许,总之这些形式都是错误的赋值方式。
  1. 一维数组在内存中的存储

一维数组在内存中是连续存放的!随着数组下标的增长,地址是由低到高变化的

思考一下下面的代码:

&arr[i] 和 p+i 为什么是一样的功能?

arr[i]数组是一个int整形的数组。一个元素占4个字节,所以在下标+1是跳过了4个字节.

p表示该数组的首元素的地址,且变量p是指向int整形常量,所以指针变量p+1,意思是指向下一个int整形元素的地址,而一个int型的元素占四个字节,也就是跳过了4个字节。

回到正题,到底为什么功能一样,本质是因为数组在内存中是连续存放的。

同样我们也可以用指针打印元素:

  1. 二维数组的创建和初始化

首先我们先简单理解一下二维数组:

试着想一下统计五年每月的降水量,我们应该怎么定义这个二位数组呢?

float rain[5][12];  //很明显我们因该这么定义,但是我们应该怎么理解这个数组呢
                    //可以理解: 内含5个数组元素的数组,每个数组元素内涵12个float类型的元素。
                    //查看中间部分rain[5],rain是一个内含5个元素的数组。
                    //再看其余部分 float [12],每个元素是一个内含12个float元素的数组

二维数组的创建和初始化

int main()
{
    int arr[3][5];  //二维数组的创建。        
    int arr[3][5]={1,2,3,4,5,6};   //不完全初始化。未初始化的默认为0.
    int arr[3][5]={{1,2},{3,4},{5,6}};//这种也是不完全初始化。只是元素位置不一样
    int arr[][5]={1,2,3,4,5,6};  //初始化时行可以省略掉行,因为我们可以根据多少列多少元素,计算出行。但列不可以省略掉。
    
    return 0;
}
  1. 二维数组的使用

这里我们仔细观察这里的行和列的求法,可以结合上面数组理解方法。

  1. 二维数组在内存中的存储

也由于二维数组是连续存放的,因此我们在初始化时候不能省略列的存在。

可以把二维数组理解成一维数组:

第一行认为一维数组的第一个元素,第二行作为一维数组的第二个元素。

  1. 数组越界

在使用数组时,要防止数组下标超出边界,也就是说,必须确保下标是有效的值。

例如:int arr[10];

那么在使用该数组的时,要确保程序中使用的数组下标在0~9的范围内,因为编译器不会检查这种错误。(但是,一些编译器会发出警告,然后继续编译程序)

使用越界的数组下标会导致程序改变其他变量的值,或者导致程序运行终止。

那为什么从语言为允许这种麻烦的事情发生呢?1.主要归功于C语言信任程序员的原则。2.不检查边界,可以程序运行的更快。所以我们在编写代码的时候因自行检查下标越界。

  1. 数组作为函数参数

数组名就是数组首元素的地址:

有2个例外:

1.sizeof(数组名) ;这里数组名不是首元素的地址,数组名表示整个数组。计算的是整个数组的大小。

2.&数组名;数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址

int main()
{
     int arr[10]={1,2,3,4,5,6,7,8,9,10};
     printf("%p\n",arr);
     printf("%p\n",&arr[0]);
     printf("%p\n",&arr);
    return 0;
}

结果是

都是同一份地址,那怎么区分这几种表达呢?

int main()
{
     int arr[10]={1,2,3,4,5,6,7,8,9,10};
     printf("%p\n",arr);
     printf("%p\n",arr++1);
     printf("%p\n",&arr[0]);
     printf("%p\n",&arr[0]+1);
     printf("%p\n",&arr);
     printf("%p\n",&arr+1);
    return 0;
}

结果是

这就说明前两种arr、&arr[0]表示方法,指向的只是一个int型的元素。

而&arr的表示方法,指向int的型的整个数组。

那么,接下来我们思考下面的代码:

void bubble_sort(int arr[])
{
    int sz = sizeof(arr) / sizeof(arr[0]);//这样对吗?
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        int j = 0;
        for (j = 0; j < sz - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
    bubble_sort(arr);//是否可以正常排序?
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

为什么结果是:

其实通过上面个的结论,我们可以知道我们传递过去的只是指向第一个数组元素的地址。

并且,虽然在形参中我们用int arr[];来接收数组首元素地址,但本质上是一个指针,因为地址就应该用指针接收,等价于 int* arr;这种写法。

而形参接收时,本质上创建了一个整形指针变量 int* arr; 虽然实参和形参名字一样,但意义截然不容,实参代表数组的首元素地址,在sizeof()中代表整个数组,即这是一个数组名。而形参arr只是一个指向整形的指针变量名

所以我们在编译上面这行代码的时候。

int sz = sizeof(arr) / sizeof(arr[0]);

sz最后结果是1。问题就在于这里的arr并不是数组名,而是变量名。

指针和数组其实还有很多内容,这里由于篇幅问题,后期进行跟新,有问题可以留言。

如有错误,感谢指出!

参考书籍:C Primer Plus 第六版

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值