C语言--数组和指针

数组

数组由数据类型相同的一系列元素组成。下面是一些数组声明:

int main(void)
{
	int Month[12];	//内含12个整型元素
	float salary[25];	//内含25个浮点型元素
	char id[12];		//内含12个字符元素
}

要访问数组中的元素,通过使用数组下标数(也成为索引)表示数组中的各元素。
数组元素编号从0开始,所以Month[0]是第一个元素。

初始化数组

只存储单个值的变量有时也称为标量变量,如:
int a = 1;
float b = 1.0f;
数组的初始化为:
int num[5] = {1, 2, 3, 4, 5};
用以逗号分隔的值列表来初始化数组。
注意
有时需要把数组设置为只读,这样程序只能从数组中检索值,不能把新值写入数组。示例:

	const int days[MONTHS] = { 31, 28, 31,30,31,30,31,31,30,31,30,31 };

一但声明为const,便不能再给它赋值。

给数组元素赋值

声明数组后, 可以借助数组下标给数组元素赋值,示例:

//给数组元素赋值
#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
int main(void)
{
	int ar[SIZE] = {};		//声明一个数组,这里其实把所有元素初始化为0了
	
	for (int i = 0; i < SIZE; ++i)
	{
		ar[i] = rand() % 100;
	}
	for (int i = 0; i < SIZE; ++i)
	{
		printf("%5d", ar[i]);
	}
	printf("\n");
	return 0;
}

输出结果:
在这里插入图片描述

这段程序充分体现了如何利用数组下标来访问数组元素的。
C不允许把数组作为一个单元赋给另一个数组。
错误示例:

#define SIZE 5
int main(void)
{
	int ar[SIZE] = { 1,2,3,4,5 };
	int br[SIZE];
	br = ar;		//不允许
	br[SIZE] = ar[SIZE];	//数组下标越界
}

数组边界

在使用数组时,要防止数组下标超出边界。也就是说,必须确保下标是有效的值。
假设有: int ar[20];
那么在使用该数组时,要确保程序中使用的数组下标在0~19的范围内。

指定数组的大小

在C99标准之前,声明数组时只能在方括号内使用整型常量表达式
另外,表达式的值必须大于0。
示例:

int main(void)
{

	int n = 5;
	int m = 8;
	float a1[5];					//合法
	float a2[5 * 2 + 1];			//合法
	float a3[sizeof(int) + 1];		//合法
	float a4[-4];					//不可以,数组大小必须大于0
	float a5[0];					//不可以,数组大小必须大于0
	float a6[2.5];					//不可以,数组大小必须是整数
	float a7[(int)2.5];				//可以,已被强制转换为整型
	float a8[n];					//VS2019不可以,表达式必须是常量,n是变量
}

多维数组

二维数组

示例:
若要计算五年内每个月的降水量,这些数据分开存储是要比创建一个内含60个元素的数组好。
可以这样创建数组:

float Rain[5][12];

主数组有五个元素(每个元素表示一年),每个元素是内含12个元素的数组(每个元素表示一个月)。
Rain的首元素Rain[0]是一个内含12个float类型值的数组。

在这里插入图片描述
该图可以方便理解二维数组的两个下标。

初始化二维数组

方便理解,可以这样初始化二维数组:

#include<stdio.h>
#define ROW 4
#define COL 4
int main(void)
{
	int arr[ROW][COL] =
	{
		{1, 2, 3, 4},
		{5, 6, 7, 8 },
		{9, 10, 11, 12},
		{13, 14, 15, 16}
	};
}

访问二维数组中的元素

若要依次访问二维数组中的元素,可以通过循环的嵌套来实现:

#include<stdio.h>
#define ROW 4
#define COL 4
int main(void)
{
	int arr[ROW][COL] =
	{
		{1, 2, 3, 4},
		{5, 6, 7, 8 },
		{9, 10, 11, 12},
		{13, 14, 15, 16}
	};

	for (int i = 0; i < ROW; ++i)
	{
		for (int j = 0; j < COL; ++j)
		{
			printf("%4d", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

输出结果:
在这里插入图片描述

指针和数组

首先要知道:
数组名是数组首元素的地址。即:
假设ar是一个数组,则有
ar == &ar[0];
两者都是常量,可以把他们赋值给指针变量

在C中,指针加一指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。

  • 指针的值是它所指向对象的地址。
  • 在指针前面使用*运算符可以得到该指针所指向对象的值。
  • 指针加1,指针的值递增它所指向类型的大小(以字节为单位)。

下面的等式体现了C语言的灵活性:

date + 2 = &date[2];	//相同的地址
*(date + 2) = date[2];	//相同的值

从本质上看,同一个对象有两种表示法。定义ar[n]的意思是 *(ar + n)。

函数,数组和指针

声明数组形参

因为数组名是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。
只有在这种情况下,C才会把int ar[]和int * ar解释成一样。
下面4种原型都是等价的:

int sum1(int* ar, int n);
int sum2(int*, int);
int sum3(int ar[], int n);
int sum4(int[], int);

使用指针形参

函数要处理数组必须知道何时开始,何时结束。
介绍两个办法:
一是函数使用一个指针形参标识数组开始,再用一个整数形参表面待处理数组的元素个数。
二是传递两个指针,第一个指针指明数组的开始处,第二个指针指向数组的结束处。
示例:

#include<stdio.h>
#include<assert.h>
//第一种
int Sum_1(int* ar, int n)
{
	assert(ar != nullptr);
	int sum = 0;
	for (int i = 0; i < n; ++i)
	{
		sum += ar[i];
	}
	return sum;
}
//第二种
int Sum_2(int* start, int* end)
{
	assert(start != nullptr && end != nullptr);
	int sum = 0;
	while (start < end)
	{
		sum += *start;	
		start++;	//让指针指向下一个元素
	}
	return sum;
}
int main(void)
{
	const int SIZE = 10;
	int ar[SIZE] = { 1,2,3,4,5,6,7,8,9,10 };
	
	printf("sum1 = %d\n", Sum_1(ar, SIZE));
	printf("sum2 = %d\n", Sum_2(ar, ar + SIZE));
	return 0;
}

运行结果:
在这里插入图片描述

指针运算中的优先级问题

程序:

#include<stdio.h>
int main(void)
{
	int data[2] = { 100, 200 };
	int moredata[2] = { 300, 400 };

	int* p1, * p2, * p3;
	p1 = p2 = data;
	p3 = moredata;

	printf("*p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3);
	printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n", *p1++, *++p2, (*p3)++);
	printf("*p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3);
	return 0;
}

运行结果:
在这里插入图片描述
第二行中,*p1++, p1先和++结合,但它是后置++,所以解引用符号提出了当前p1中的值,在这个表达式运行结束后p1再++,指向了后一个元素。

指针操作

  • 赋值:可以把地址赋给指针。例如数组名,带地址运算符(&)的变量名,另一个指针。
  • 解引用:*运算符给出指针指向地址上存储的值
  • 取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址。
  • 指针与整数相加:整数会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。例如:指针ptr + 4与&ptr[4]等价。
  • 递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一个元素。
  • 指针减去一个整数:指针必须是第一个运算对象,整数是第二个运算对象。该数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。
  • 递减指针:除了递增指针还可以递减
  • 指针求差:计算两个指针的差值。通常,求差的两个指针分别指向同一个数组的不同元素,通过计算得出两元素之间的距离。插值的单位与数组类型的单位相同。假如:两个指针指向一个整型数组,ptr1 - ptr2得2,意思是这两个指针所指向的两个元素相隔两个int。
  • 比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。

保护数组中的数据

对形参使用const

如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形参时应使用关键字const
像这样:
*int sum(const int ar, int n);
这里使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。

const的其他内容

例如:

#define MONTH 12
...
const int days[MONTH] = { 31,28,31,30,31,30,31,31,30,31,30,31 };

1.如果程序尝试改变数组元素的值,就会报错

days[2] = 29;	//编译错误

2.若有:

int days[MONTH] = {};
const int* ptr1 = days;

则有:

*ptr1 = 30;		//不允许,指针被const限定
ptr1[2] = 30;	//不允许
days[2] = 30;	//允许,这个数组没有被限定
//可以让指针指向别处:
ptr1++;		//允许

3,关于指针赋值和const 需要注意的规则:
把const数据或非const数据的地址初始化为指向const的指针或为其赋值时合法的。
示例:

int ar[5] = { 1,2,3,4,5 };
	const int br[5] = { 1,2,3,4,5 };
	const int* ptr1 = ar;		//合法
	ptr1 = br;					//合法
	ptr1 = &ar[2];				//合法

但是,只能把非const数据的地址赋给普通指针:

int* ptr2 = ar;			//可以
	ptr2 = br;			//无效
	ptr2 = &ar[2];		//可以

4,可以声明并初始化一个不能指向别处的指针,例如:

int ar[5] = { 1,2,3,4,5 };
	int* const ptr = ar;		//ptr指向数组的开始
	ptr = &ar[2];				//不允许,该指针不能指向别处
	*ptr = 10;					//可以,可以更改ar[0]的值

可以用这种指针修改它所指向的值,但是只能指向初始化时设置的地址。
5,还可以在创建指针时使用两次const,该指针既不能更改它所指向的地址,也不能修改指向地址上的值:

int ar[5] = { 1,2,3,4,5 };
	const int* const ptr = ar;

	ptr = &ar[3];	//不允许
	*ptr = 10;		//不允许

指针和二维数组

假设有以下声明:

int arr[2][4];

数组名arr是该数组首元素地址。arr的首元素是一个内含四个int值的数组,所以arr是这个内涵四个int值得数组的地址。

  • 所以arr的值和&arr[0]的值相同。而arr[0]本身是一个数组,所以arr[0]的值和它首元素的地址(即&arr[0][0]的值)相同。
  • 解引用一个指针:*arr代表数组首元素arr[0]的值,但arr[0]表示arr[0][0]的地址; *arr[0]表示arr[0][0]上的值;*arr与&arr[0][0]等价。
  • 地址的地址或指针的指针就是双重间接
    指针表示法示例:
int arr[2][4];
	arr;			//二维数组首元素的地址
	arr + 1;		//二维数组第二个元素的地址
	*(arr + 1);		//二维数组第二个元素的首元素的地址
	*(arr + 1) + 1;	//二维数组第二个元素的第二个元素的地址
	*(*(arr + 1) + 1);	//二维数组第二个一维数组的第二个int类型的值,即arr[1][1];

结束

还有更多的操作需要大家在今后的学习中不断发现,谢谢。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_索伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值