C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)

简介: C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)

数组的理解


数组的含义


       在C语言中,用于存储多个相同类型的元素。它可以被简单地定义为包含多个元素的容器。数组中每个元素都可以通过索引来访问,索引从零开始递增。 C语言中的数组可以包含任何基本数据类型,例如整数、字符、浮点数等。要定义一个数组,需要指定数组的类型和元素的数量。


数组的创建


type_t   arr_name   [const_n];

type_t 是指数组的元素类型

const_n 是一个常量表达式,用来指定数组的大小


例如:

int a[5];
char c[2];
double* ptr[10];


数组创建的其他几种实例情况

#include<stdio.h>
#define COUNT 10
enum Count {
  Count = 10,
};
int main()
{
  //写法一
  int count = 10;
  int arr[count];
  //写法二
  const int count = 10;
  int arr[count];
  //写法三
  int arr[COUNT];
  //写法四
  int arr[Count];
  return 0;
}


以上代码中包含了4种不同的数组定义方式,分别如下:


1. 使用变量定义数组大小:

```

int count = 10;

int arr[count];

```

此种方式定义数组的长度使用的是一个变量,变量count的值为10,因此定义了一个包含10个元素的数组。

但需要注意的是,在博主的vs编译器中,采用的是C90的标准(数组大小只能是常量表达式),并不支持C99的标准(引入变长数组的概念,使得数组在创建的时候可以使用变量,但是不可以被初始化),所以这样的变量长度的数组会被编译器判定错误。


而在gcc编译器环境下,它是支持C99标准的特性,就允许这样的写法。

2. 使用常量定义数组大小:


```

const int count = 10;

int arr[count];

```

此种写法只是将变量count改为了常量count,但是count还是常量,只不过它拥有了常量属性,本质还是变量,原因和第一种方法相同。


3. 使用宏定义定义数组大小:

```

#define COUNT 10

int arr[COUNT];

```

通过宏定义,我们将数组大小定义为一个常量,因此在整个程序中都可以使用宏定义的值。此种方式定义的数组大小,与使用常量定义的数组大小基本相同。


4. 使用枚举定义数组大小:

```

enum Count

{  

       Count = 10,

};

int arr[Count];

```

使用枚举定义数组大小与使用宏定义相似,只不过使用的是枚举类型。其中,我们定义了一个名为Count,值为10的枚举常量,然后通过Count来定义数组大小。最终,以上四种方式定义的数组类型都是相同的,都定义了一个包含10个元素的数组。


总结:数组创建,在C99标准之前,[ ]([ ]内的值必须是整型)中要给一个常量、宏定义或者枚举常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。


数组的初始化


       数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

int arr1[5] = {1,2,3,4,5};//完全初始化
int arr2[10] = {1,2,3};//不完全初始化,剩余的元素都是0
int arr3[] = {1,2,3,4};//省略数组的大小,数组会根据初始化的内容来确定
int arr4[3] = {0};//省略数组的内容,就必须要指定数组的大小
//下面解释
char arr5[] = {'a','b','c'};
char arr6[] = "abc";


  数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。


现在我们来看数组中字符数组在创建为什么有两种形式(char arr5[] 和 char arr6[ ] ),以及在内存是怎么分配的。


```

char arr5[] = {'a','b','c'};

```

这种方式就是简单的使用{ }进行初始化 。

```

char arr6[] = "abc";

```

这种创建char数组的方式叫做字符串字面值初始化,可以简化char数组的定义和初始化。

由两张图我们可以得出:

第二种写法等价于:

```

char arr6[] = {'a', 'b', 'b', '\0'};

```

注意:两种写法有区别,第二种写法比第一种写法多了'\0',我们以后创建使用的时候不能混淆了。


一维数组的使用


数组访问的操作符: [ ]

下标从'0'开始

#include <stdio.h>
int main()
{
 int arr[10] = {0};//数组的不完全初始化
 //计算数组的元素个数
 int sz = sizeof(arr)/sizeof(arr[0]);
 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
 int i = 0;//做下标
 for(i=0; i<10; i++)//这里写10,好不好? -  可以清晰的看到元素的总个数
 {
     arr[i] = i;
 } 
 //输出数组的内容
 for(i=0; i<10; ++i)
 {
     printf("%d ", arr[i]);
 }
 return 0;
}


总结:


       1. 数组是使用下标来访问的,下标是从0开始。

       2. 数组的大小可以sizeof关键字通过计算得到。


一维数组在内存中的存储


上面我们打印的是数组每个元素的内容,现在我们来打印一下数组每个元素的地址

//输出数组每个元素的地址
  for (i = 0; i < 10; ++i)
  {
    printf("[%d]==%p\n", arr[i], &arr[i]);
  }

总结:数组在内存中是连续存储的。


二维数组的含义


二维数组是一种数组类型,它可以存储具有相同数据类型的元素值,但是以二维网格的形式存储。


例如:

二维数组由行和列组成,其中每个元素可以通过其行和列的索引来访问。通常,我们使用两个下标来访问二维数组的元素,第一个下标表示要访问的行数,第二个下标表示要访问的列数。


二维数组的创建


int arr[3][4];

char arr[3][5];

double arr[2][4]


二维数组的初始化


int arr[3][4] = {1,2,3,4};

int arr[3][4] = {{1,2},{4,5}};

int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略


二维数组的使用


       二维数组的使用也是通过下标的方式,行下标和列下标都从0开始。

#include <stdio.h>
int main()
{
  int arr[3][4] = { 0 };
  int i = 0;
  for (i = 0; i < 3; i++)//行下标
  {
    int j = 0;
    for (j = 0; j < 4; j++)//列下标
    {
      arr[i][j] = i * 4 + j;
    }
  }
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 4; j++)
    {
      printf("%d ", arr[i][j]);
    }
  }
  return 0;
}


二维数组在内存中的存储

//输出数组每个元素的地址
for (i = 0; i < 3; i++)
{
  int j = 0;
  for (j = 0; j < 4; j++)
  {
    printf("&arr[%d][%d] = %p\n", i,j, &arr[i][j]);
  }
}


二维数组初始化为什么可以省略行,不能省略列


二维数组是由多个一维数组按照行排列而成的,每一个一维数组被称为一行,它们在内存中是连续存放的,行与行之间是相互独立的。当我们初始化一个二维数组时,需要指定其行数和列数。


对于指定行数,我们可以采用以下两种方式:

1. 显示指定行数

```

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

```

这种方式明确指定了行数为2。


2. 隐式指定行数

```

int arr[ ][3] = {{1, 2, 3}, {4, 5, 6}};

```

这种方式没有指定行数,但是编译器会根据初始化中元素的个数来计算出行数,这里由于有两个一维数组,每个一维数组包含3个元素,所以行数为2。

对于指定列数,我们需要显式指定数组列数,因为在分配内存空间时,需要先知道每一行应该分配多少个元素。如果省略了列数,则编译器无法确定每一行应该分配多少个元素。因此,初始化二维数组时可以省略行数,但必须指定列数。


数组越界


       数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。 C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的, 所以我们在写代码时,最好自己做越界的检查。

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    for(i=0; i<=10; i++)
   {
        printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
   }
 return 0;
}

  这段代码定义了一个长度为10的整型数组,并将其初始化为1到10的连续数字。然后通过循环遍历数组,依次输出数组中的元素,但是需要注意的是,在循环中判断的条件是 `i<=10`,这意味着循环将会执行11次,而数组下标从0开始,最大只能取到9。


       因此,在循环的最后一次迭代中,`i`等于10,此时访问了数组下标为10的元素,即超出了数组的范围,这个错误我们称之为数组越界访问。


       越界访问是一种常见的编程错误,它可能导致程序崩溃、产生不可预知的结果或安全隐患等问题。为了避免越界访问,我们需要在访问数组元素时,保证数组下标的合法性,即数组下标不能小于0,且不能大于等于数组长度。所以代码中应该把循环判断条件修改为 `i<10`。


数组作为函数参数


       在C语言中,我们可以将数组作为函数的参数来传递。当我们将一个数组作为参数传递给函数时,实际上传递的是数组的地址。在函数中,我们可以通过传递进来的地址来访问数组中的元素,对数组进行读取、修改等操作。


数组名怎么理解


数组名通常情况下就是数组的首元素的地址。


但是有两个例外:

       1.sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小。

       2.&数组名,这里的数组名表示整个数组,这里取出的是整个数组的地址。


我们来用函数作为参数写一个冒泡排序实现整形数组排序。

#include <stdio.h>
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 - 1 - i; 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);//是否可以正常排序?
  int i = 0;
  for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}


 在这段代码中,定义了一个名为 `bubble_sort` 的函数,它接受一个整型数组 `arr` 作为参数。在函数内部,使用双重循环实现了冒泡排序算法,将传入的数组进行排序。


       在 `main` 函数中,定义并初始化了一个整型数组 `arr`,然后将其作为参数传递给 `bubble_sort` 函数进行排序。


       然而,这段代码存在一个问题:在 `bubble_sort` 函数中,计算数组长度的方式不确。 在 C 语言中,数组作为函数参数时,我们只能传递数组的地址,也就是指向数组第一个元素的指针。因此,当 `bubble_sort` 函数被调用时,传递给它的是数组的地址,而不是数组本身的大小。因此,`sizeof(arr)` 将会返回数组指针的大小,而不是整个数组的大小。我们试着打印一下发现结果确实是4,并且程序也提示警告。

为了解决这个问题,我们可以在调用函数时将数组的长度一起传递给函数,或者在函数内部使用另一个参数来表示数组的长度。在这段代码中,我们可以在定义函数时加上一个表示数组长度的参数 `int len`,然后在调用函数时将数组的长度传递给它。


修改后的冒泡排序算法:

#include <stdio.h>
//void bubble_sort(int *arr,int sz)
void bubble_sort(int arr[],int sz)
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)//比较的趟数
  {
    int j = 0;
    int flag = 1;//表示有序
    for (j = 0; j < sz - 1 - i; j++)//两两比较的次数
    {
      if (arr[j] > arr[j + 1])
      {
        int tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
        flag = 0;//表示无序
      }
    }
    if (flag == 1)//本轮比较的过程没有元素交换
      break;
  }
}
int main()
{
  int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr,sz);
  int i = 0;
  for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}


 在代码中,用一个flag变量来表示当前的数组是否已经有序了。在每一轮比较之前,将flag值赋为1,表示当前的数组已经有序。如果在比较的过程中发现有元素需要进行交换,那么就将flag值改为0,表示当前的数组无序。 在每一轮比较结束之后,检查flag的值。如果flag值还是1,说明这一轮比较过程中没有进行任何元素的交换,也就是整个数组已经有序了。此时就可以直接退出排序循环,不再进行无用的比较,从而提高排序的效率。这个优化方法能够有效地减少排序循环的次数,尤其在处理大数据量时效果更为明显。因为如果数组已经有序,那么不必再进行多余的比较操作,这样可以大大减少算法的时间复杂度。


相关文章
|
16天前
|
存储 安全 程序员
内存越界写入
【10月更文挑战第13天】
31 4
|
14天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
29 1
|
15天前
|
Rust 安全 Java
内存数组越界
【10月更文挑战第14天】
20 1
|
16天前
|
Java 编译器 C++
内存越界读取
【10月更文挑战第13天】
24 2
|
19天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
17天前
|
存储 容器
内存越界访问(Out-of-Bounds Access)
【10月更文挑战第12天】
101 2
|
23天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
28天前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
37 4
|
26天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
48 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
337 0