5.3 指针与数组
5.3.1 数组的几个概念
什么是数组?数组是组织数据的一种方式。
如何创建一个数组?
int numbers[5]; // 定义一个包含 5 个整数的数组
为什么要定义数组?
组织数据:数组允许你将一组相关的数据元素组织在一起,从而更容易管理和访问这些数据。
节省内存:数组在内存中是连续存储的,这意味着它们占用的内存空间相对较小,因为它们没有额外的开销用于存储数据之间的链接信息。
快速访问:通过索引,你可以快速访问数组中的任何元素,而不必遍历整个数据集。
生活中的例子:
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 存在一个问题:只支持整数输入。 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
#include <stdio.h>
int main()
{
// 定义一个整数数组来存储一周中每天的温度
int temperatures[7];
// 输入每天的温度数据
for (int i = 0; i < 7; i++)
{
printf("Enter temperature for day %d: ", i + 1);
scanf("%d", &temperatures[i]);
}
// 打印一周中每天的温度
printf("Weekly temperatures:\n");
for (int i = 0; i < 7; i++)
{
printf("Day %d: %d degrees\n", i + 1, temperatures[i]);
}
return 0;
}
➜ ~
Enter temperature for day 1: 37
Enter temperature for day 2: 38
Enter temperature for day 3: 26
Enter temperature for day 4: 40
Enter temperature for day 5: 38
Enter temperature for day 6: 36
Enter temperature for day 7: 26
Weekly temperatures:
Day 1: 37 degrees
Day 2: 38 degrees
Day 3: 26 degrees
Day 4: 40 degrees
Day 5: 38 degrees
Day 6: 36 degrees
Day 7: 26 degrees
Saving session...completed.
5.3.2 数组下标
数组的索引通常从 0 开始为什么?数组索引从 0 开始的设计是历史上的一种约定,起初是为了更有效地实现数组的内部表示和访问。这个设计选择有一些历史原因和技术因素:
直接内存访问:在底层的内存管理中,数组的元素是按照连续的内存地址排列的。通过从零开始的索引,可以更容易地计算出每个元素的内存地址。例如,第一个元素的地址就是数组的起始地址,第二个元素的地址是第一个元素地址加上一个固定的偏移量,以此类推。这种设计使得数组的元素可以通过简单的数学运算来直接访问,而无需复杂的指针计算。
历史原因:早期的编程语言,特别是汇编语言和低级语言,通常使用从 0 开始的索引。C语言在设计时受到了这些语言的影响,因此也采用了从 0 开始的索引。这种约定的延续使得C语言的设计更具一致性和可预测性。
方便性:从 0 开始的索引也具有一些方便之处。例如,数组的长度就是最后一个元素的索引加1,这使得计算数组的长度更加直观和方便。
尽管从 0 开始的索引在历史上有一些技术原因,但它已经成为了一种编程的标准约定。这种约定在C语言中被广泛采用,也在许多其他编程语言中得到了继承和延续,因此程序员通常期望数组的索引从0开始。虽然它可能在某些情况下导致初学者困惑,但一旦习惯了,它可以提供更加一致和方便的编程体验。在定义数组的时候,其实已经申请了一个内存空间,数组在内存中有也自己的一个“序号”=“数组下标”。
5.3.3 数组的初始化
数组可以进行完全初始化、不完全初始化。
还可,进行指定初始化:
int a[5]={[2]=3,[3]=2};
5.3.4 数组长度计算
求一个未知长度的数组的长度,并进行遍历打印:
#include <stdio.h>
int main()
{
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
// printf("%d\n", length);
int i = 0;
for (int i = 0; i < length; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
打印数组占用字节数:
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 多样的数组长度有关的计算 │
│ OUTPUT: │
│ 4 │
│ 20 │
│ 5 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
#include <stdio.h>
int main() {
int arr[]={1,2,3,4,5};
//打印int类型占用的字节数
int length1 =sizeof(int);
printf("%d\n", length1);
//打印数组占用的字节数
int length2 =sizeof(arr);
printf("%d\n", length2);
//打印数组中元素的个数
int length3 =sizeof(arr) /sizeof(arr[0]);
printf("%d\n", length3);
return 0;
}
5.3.5 二维数组(矩阵)及其初始化
注意:作为一个 idiom 记忆,内存花括号的省略会是很危险的。
int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
二维数组初始化 SOL1:

二维数组初始化 SOL2 不够补零 :


5.3.6 二维数组、指针与冒泡排序
数组作为函数的参数,并以指针的形式出现。
略
// Bubble sort (冒泡排序)2
#include <stdio.h>
void bubble_sort( int arr[] , int length ) {
// /*int arr[] 形式上是数组,实际上是指针*/
//确定需要几次冒泡
// int length=sizeof(arr)/sizeof(arr[0]);
//内部求解长度不可行, 在外部求解完之后再进函数
int i=0;
for (i=0; i<length-1; i++){ //length-1 趟
int j=0; //下标
for (j=0; j< length-i-1 ; j++) {
if (arr[j] > arr[j+1] ) // 两对数之间进行比较,没进去一趟对数-1
{
/* 交换 */
int Tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = Tmp;
}
}
}
}
int main() {
int arr[] = {5, 2, 1, 4, 3, 0}; //arr是数组名
int length=sizeof(arr)/sizeof(arr[0]); //length是数组的长度
bubble_sort(arr, length); //这一步就在调用函数 bubble_sort(),把上面两个“变量”作为参数传给函数
int i=0;
for ( i = 0; i < length; i++) {
printf("%d", arr[i]);
}
return 0;
}
5.3.7 数组名作为指针
指针与数组更为关键的联系:可以用数组的名字作为指向数组第一个元素的指针。
注意:虽然能把数组名用作指针,但是不能给数组名赋新的值。如果要更改,采取下列先复制再更改的方法:
p=a;
while (*p!=0)
p++;
#include <stdio.h>
int main()
{
int arr[] = {1, 2, 3, 4, 5};
int *p;
int sum = 0; // 声明并初始化 sum
/*
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ p=&a[0]:将指针 p 设置为数组 a 的第一个元素的地址 │
│ p<=&a[N]:p 的值(也就是地址)小于或等于数组 a 的最后一个元素的地址,循环就会继续 │
│ p++:这是 for 循环的更新部分。每次循环结束时,p 的值都会增加,使其指向数组的下一个元素。 │ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
for (p = &arr[0]; p <= &arr[4]; p++)
{
sum += *p; //*p 代表 p 所指向的值
printf("%d\n", *p);
}
// 如果你想打印 sum 的值,可以添加以下代码
printf("Sum: %d\n", sum);
return 0;
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 使得遍历数组循环更加方便 │
│ 运行~ │
│ 1 │
│ 2 │
│ 3 │
│ 4 │
│ 5 │
│ Sum: 15 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ for (p = &arr[0]; p <= &arr[4]; p++) │
│ { │
│ sum += *p; │
│ printf("%d\n", *p); │
│ } │
│ 改写: │
│ │
│ p = &arr[0]; │
│ while (p <= p <= &arr[4]) │
│ { │
│ sum += *p++; │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
代码解释:
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 注意:*p是内容,p是地址
| │
| sum += *p++; │
│ 等价于 │
│ sum += *p; │
│ p++; 将 p 的值(也就是地址)增加一定的量,使其指向下一个元素 │
│ sum += *p; │
│ 等价于 │
│ sum = sum + *p; 将 sum 和指针 p 所指向的元素的值相加,然后将结果赋值给 sum │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
5.3.8 指针与数组的神奇公式
把数组取下标和指针算术运算关联起来的神奇公式:
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ OUTPUT:
│ a[0]: 7 │
│ a[1]: 12 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
#include <stdio.h>
int main()
{
int a[10];
*a = 7; // a[0] = 7;
*(a + 1) = 12; // a[1] = 12;
printf("a[0]: %d\n", *a);
printf("a[1]: %d\n", *(a + 1));
return 0;
}
通常情况下,a+i
等价于 &a[i]
,也就是两者都表示指向数组 a 中元素 i 的指针。并且,*(a+i)
等价于 a[i]
两者都表示元素 i 本身。换句话说,可以把数组的取下标看成是指针算术运算的一种形式。
Last updated
Was this helpful?