C语言--数组

一维数组的创建和初始化

数组的创建:在创建数组时,我们必须定义数组的类型和大小,数组的大小不能为0,数组中的元素类型都是相同的。

int arr[10];//[]内必须是常量/常量表达式(3+8),不能是一个变量(x...)

数组的初始化:在数组创建时,我们也要必须为数组初始化。

int arr1[3] = {1, 2, 3};
int arr2[] = {1, 2, 3};//在这里,我们arr[3]里边的数字可以不用写;
int arr3[3] = {1, 2};//也是可以的,只是把最后一个数初始化为0了而已
int arr4[3] = {1, 2, 3, 4};//是不可以的,不能超过数组长度  
char arr5[3] = {'a', 98, 'c'};//因为是字符类型,所以98其实就是字符'b'
char arr6[] = "abcdef";

一维数组的使用

数组是使用下标来访问的,下标是从0开始。
数组的大小可以通过计算得到。

(sz = sizeof(arr)/sizeof(arr[0]));
#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	//i<11是不可以的,不可以越界访问
	{
		arr[i] = i;
	}
	return 0; 
}

一维数组在内存中的存储

#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
	{
		printf("&arr[%d] = %p\n", i, &arr[i]);
	}

	return 0; 
}


&arr[0] = 000000000062FDF0
&arr[1] = 000000000062FDF4
&arr[2] = 000000000062FDF8
&arr[3] = 000000000062FDFC
&arr[4] = 000000000062FE00
&arr[5] = 000000000062FE04
&arr[6] = 000000000062FE08
&arr[7] = 000000000062FE0C
&arr[8] = 000000000062FE10
&arr[9] = 000000000062FE14

从输出结果我们可以看出数组元素在内存中的存储是:地址是由低到高并且是连续存储的。

指针的初步介绍

指针可以理解为一个变量,是一个专门用来存放地址的一个变量。

int *ptr = NULL;
//定义一个整型的指针变量,初始化为NULL

char *ptr = NULL;
//定义一个字符的指针变量,初始化为NULL

一维数组的指针访问

#include<stdio.h>

int main()
{
	int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	printf("%p\n", arr);
	printf("%d\n", *arr);
//注意返回类型
//地址为%p,指针返回为%d或者%c
	return 0; 
}

数组名其实存放的就是首元素的地址
*数组名 是一个指针,指向首元素

000000000062FDF0
0

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int *p = arr;
	for (i = 0; i < sz; i++)
	{
	    //arr[i] = i; (1)
		*(p + i) = i; (2)
	}

	for (i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i]);//(1)和(2)的输出结果一样
	}

	return 0; 
}


我们定义了一个指针p,指向arr,然后我们通过指针来访问数组。

  • 指针在定义的时候需要加上*,且后面接一个地址(数组名字,or &元素)
  • 后面不需要,*指针名字表示指针指向的元素!

二维数组的创建和初始化

#include<stdio.h>

int main()
{
	int arr[3][4] = { 1, 2, 3, 4, 5, 6 };
	//int arr[][4] = {{1, 2},{3, 4, 5},{6}};可以
	//arr[3][] = {{1, 2},{3, 4, 5},{6}};是不可以的

	return 0; 
}

一行有四个元素,总共有三行,当后边元素没有初始化的时候,全默认为0。

二维数组的使用

二维数组的使用也是通过下标方式

#include<stdio.h>

int main()
{
	int arr[3][5] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			arr[i][j] = i * 5 + j + 1;
		}
	}

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	

	return 0; 
}


二维数组在内存中的存储

可以通过打印它的地址来观察它是如何存储的

#include<stdio.h>

int main()
{
	int arr[3][5] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("&arr[%d][%d] = %p\n", i, j, &arr[i]);
		}
	}	
	return 0; 
}

可以看出数组元素的地址是连续的,并没有进行换行,我们只是把它假想成了三行。

#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("---------------\n");
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("---------------\n");
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);

	return 0;
}

000000000062FDF0  // +4
000000000062FDF4
---------------
000000000062FDF0
000000000062FDF4
---------------
000000000062FDF0   //  + 40
000000000062FE18   
&arr,取的是数组的地址,对它进行加1,就相当于跳过整个数组
又因为数组是int型,总共有10个元素,所以跳过的字节大小是40

二维数组的指针访问


#include<stdio.h>

int main()
{
	int arr[3][5] = { 0 };
	int *p = &arr[0][0];
	int i = 0;
	int j = 0;
	for (i = 0; i < 15; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}

	return 0;
}


有关数组的运算

#include<stdio.h>

int main()
{
	//一维数组
	int a[] = { 1, 2, 3, 4 };
	printf("%d\n", sizeof(a));//16  
	//1.数组名单独放在sizeof内部,数组名表示整个数组,所以sizeof(数组名)计算的是是数组总大小,单位是字节
	//2.&数组名,数组名表示整个数组,所以&数组名取出的是整个数组的地址
	//3.除此之外,所有的数组名都表示首元素的地址
	printf("%d\n", sizeof(a + 0));//4    a代表首元素地址,a+i代表第i个元素的地址,在32位平台下所有的地址的大小都是4个字节
	printf("%d\n", sizeof(*a));//4       a是首元素地址,*a是首元素--1,int型占4个字节大小
	printf("%d\n", sizeof(a + 1));//4    a是首元素地址,a+1是第二个元素的地址,它还是一个地址
	printf("%d\n", sizeof(a[1]));//4     a[1]--第二个元素
	printf("%d\n", sizeof(&a));//4       &a虽然取出的是整个数组的地址,但它还是一个地址
	printf("%d\n", sizeof(*&a));//16     &a取出的是整个数组的地址,对它进行解引用,就是这个数组,这个数字的大小就是16
	printf("%d\n", sizeof(&a + 1));//4   &a取出的是整个数组的地址,加1跳过了整个数组(16个字节),但它还是一个地址
	printf("%d\n", sizeof(&a[0]));//4    &a[0]取的是第一个元素的地址
	printf("%d\n", sizeof(&a[0] + 1));//4   &a[0] + 1取的是第二个元素的地址

	return 0;
}

在这里插入图片描述

#include<stdio.h>

int main()
{
	//字符数组
	char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	printf("%d\n", sizeof(arr));
	//6             数组的长度
	printf("%d\n", sizeof(arr + 0));
	//8            首元素地址
	printf("%d\n", sizeof(*arr));
	//1           首元素地址解引用是首元素(a),char类型占1个字节
	printf("%d\n", sizeof(arr[1]));
	//1         第二个元素的长度,即 b 的长度 char 类型一个字节
	printf("%d\n", sizeof(&arr));
	//8           数组的地址
	printf("%d\n", sizeof(&arr + 1));
	//8       下一个数组的地址,跳过了f,也是一个地址
	printf("%d\n", sizeof(&arr[0] + 1));
	//8    第二个元素的地址

    printf("%d\n", strlen(arr));
    //随机值       strlen()求的是字符串长度,以'\0'为结束标志,这里并没有'\0',所以会一直往后数
	printf("%d\n", strlen(arr + 0));
	//随机值   还是从'a'开始数,但没有'\0',所以停不下来
	printf("%d\n", strlen(*arr));
	//程序会崩掉  strlen()接收的是一个地址,*arr是字符'a',这里把'a'的ASCII码值(97)作为一个地址访问,这一块的地址是不能被访问的
	printf("%d\n", strlen(arr[1]));
	//错误      传的是'b',和传的是'a'效果一样
	printf("%d\n", strlen(&arr));
	//随机值      &arr虽然取的是数组的地址,但数组的地址和数组首元素的地址是一样的,也是从‘a'开始数,但并没有'\0'
	printf("%d\n", strlen(&arr + 1));
	//随机值  但这个随机值和前边的随机值意义不同,它是把'a','b','c','d','e','f'跳过去了,从f后边开始数
	printf("%d\n", strlen(&arr[0] +1));
	//随机值   这个是从'b'开始往后数的


	return 0;
}


在这里插入图片描述

#include<stdio.h>

int main()
{
    char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	//7   **里边还有'\0',只不过我们看不到而已**
	printf("%d\n", sizeof(arr + 0));
	//8     arr+0---首元素地址
	printf("%d\n", sizeof(*arr));
	//1   对首元素地址解引用是首元素
	printf("%d\n", sizeof(arr[1]));
	//1 第二个元素的长度 char型
	printf("%d\n", sizeof(&arr));
	//8    数组的地址也是地址,地址的长度为8字节
	printf("%d\n", sizeof(&arr + 1));
	//8  也是一个地址,不过这个地址在'\0'后边,跳过了整个数组
	printf("%d\n", sizeof(&arr[0] + 1));
	//8   从b开始的一个地址



	printf("%d\n", strlen(arr));
	//6   strlen()以'\0'为结束标志,但不算'\0'
	printf("%d\n", strlen(arr + 0));
	//6  arr+0与arr都代表首元素地址
	printf("%d\n", strlen(*arr));
	//错误   这传进来的不是一个地址,而是一个字符
	printf("%d\n", strlen(arr[1]));
	//错误
	printf("%d\n", strlen(&arr));
	//6    数组的地址也是首元素地址,地址的位置是一样的
	printf("%d\n", strlen(&arr + 1));//随机值   跳过了'\0',从'\0'往后数,不知道会数到哪里去
	printf("%d\n", strlen(&arr[0] + 1));//5    从第二个元素(b)开始往后数,遇到'\0'结束


	return 0;
}


数组作为函数参数

我们在写代码的时候,会将数组作为参数传给函数

void bubble(int arr[])
{
    int sz = sizeof(arr)/sizeof(arr[0]);//这是错误的
    ...
}

数组作为函数参数时,不会把整个数组传递过去,实际上只是把数组的首元素地址传递过去了。

数组用作函数参数有两种形式

  • 一种是把数组元素(下标变量)作为实参使用
  • 另一种是把数组名作为函数的形参和实参使用

数组元素作函数实参

数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。

  • 判别一个整数数组中各元素的值,若大于0 则输出该值,若小于等于0则输出0值
#include<stdio.h>


void nzp(int v)
{
	if(v>0) printf("%d",v);
	else printf("0");
}

void main(void)
{
	int a[5],i;
	printf("input 5 numbers\n");
	for(i=0;i<5;i++)
	{
		scanf("%d",&a[i]);
		nzp(a[i]);
	}
}

本程序中首先定义一个无返回值函数nzp,并说明其形参v为整型变量。在函数体中根据v值输出相应的结果。

在main函数中用一个for语句输入数组各元素,每输入一个就以该元素作实参调用一次nzp函数,即把a[i]的值传送给形参v,供nzp函数使用。

数组名作为函数参数

数组元素作实参时,只要数组类型函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。

数组名作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。

在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢?

数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间

  • 数组a中存放了一个学生5门课程的成绩,求平均成绩
#include <stdio.h>

double aver(double a[5])
{
	int i;
	double av,s=a[0];
	for(i=1;i<5;i++)
	{
		s+=a[i];
	}	
	av = s/5;
	return av;
}

int main(void)
{
	double sco[5],av;
	int i;
	printf("请输入:");
	for(i=0;i<5;i++)
	{
		scanf("%lf",&sco[i]);
	}
	av = aver(sco) ;
	printf("average score is %5.2f",av);
	
}

  • 题目同【例8.7】。改用数组名作函数参数。
#include <stdio.h>
void nzp(int a[5]){
    int i;
    printf("\nvalues of array a are:\n");
    for(i=0;i<5;i++){
        if(a[i]<0) a[i]=0;
        printf("%d ",a[i]);
    }
}
int main(void){
    int b[5],i;
    printf("\ninput 5 numbers:\n");
    for(i=0;i<5;i++)
        scanf("%d",&b[i]);
    printf("initial values of array b are:\n");
    for(i=0;i<5;i++)
        printf("%d ",b[i]);
    nzp(b);
    printf("\nlast values of array b are:\n");
    for(i=0;i<5;i++)
        printf("%d ",b[i]);
    return 0;
}

函数nzp的形参为整数组a,长度为5。主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。

返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b的初值和终值是不同的,数组b的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。

用数组名作为函数参数时还应注意以下几点:
①形参数组和实参数组的类型必须一致,否则将引起错误。

②形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。

在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。例如,可以写为:

   void nzp(int a[])

或写为

void nzp( int a[], int n )

其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。由此,【例8-10】又可改为【例8-11】的形式。

#include <stdio.h>

void nzp(int a[],int n){
    int i;
    printf("\nvalues of array a are:\n");
    for(i=0;i<n;i++){
        if(a[i]<0) a[i]=0;
        printf("%d ",a[i]);
    }
}

int main(void){
    int b[5],i;
    printf("\ninput 5 numbers:\n");
    for(i=0;i<5;i++)
        scanf("%d",&b[i]);
    printf("initial values of array b are:\n");
    for(i=0;i<5;i++)
        printf("%d ",b[i]);
    nzp(b,5);
    printf("\nlast values of array b are:\n");
    for(i=0;i<5;i++)
        printf("%d ",b[i]);

    return 0;
}

本程序nzp函数形参数组a没有给出长度,由n 动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。

通过指针访问数组

通过指针访问一维数组

int a[4]={1,2,3,4};
int *p=a;

对于一维数组的数组指针p,数组名p实际上是指向数组第一个元素的指针,即p为int 类型,由于其指向int型数据,因此(p+i)就相当于在p的基础上偏移了isizeof(int)的地址大小,就等于数组第i个元素的地址(i=0,1,2…)。

通过指针访问二维数组

指向元素的指针

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p=&a[0][0];

在这种定义方式下,对于数组元素的访问就需要一个个元素往后推,结合本文开头所说的,二维数组是按行优先存储的,第i行第j列元素实际上是整个二维数组中的第i*c+j个元素,其中c为二维数组的列数,相对于第一个元素,第i*c+j个元素的地址偏移量为4*(i*c+j),而由于p为int指针,p+x的地址偏移量为p+4*x,因此a[i][j]=*(p+i*c+j)

指向每一行的指针(指针数组方式)

这种方式是定义指针来指向二维数组的某一行,定义方式如下:

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p=a[0]

让p指向a[0],实际上就相当于指向a[0]的第一个元素,也就是a[0][0]了,此时的a[0][0]是整形变量,因此让p指向a[0]是可以的

当然,也可以让p指向a[1]、a[2]…如果让p指向第i行,那么此时的p+j就相当于是一维数组中第j个元素的地址了,即p+j就指向了a[i][j]。

这样看来,对于有r行的二维数组,就需要定义r个指针指向每一行,这样其实就可以定义一个装有r个指针的数组,其中每一个指针分别指向二维数组的每一行,定义方式如下:

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p[3];
 
for(int i=0;i<3;i++)p[i]=a[i];

在这种定义方式下,p[i]即指向数组的第i行,也就是a[i][0],知道了a[i][0]的地址,要想访问a[i][j],地址即是a[i][0]+4j,这里由于p[i]是int型的指针变量,因此刚好就等于p[i]+j,即a[i][j]=(p[i]+j)。这就是一种定义指向二维数组的指针的方式,这里的p是以指针作为元素的数组,也就是指针数组。

   值得注意的是,这种方法必须知道二维数组的行数。

指向整个数组的指针(数组指针方式)

   这种方式是定义指针来指向整个数组,定义方式如下:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4]=a;

这里的p是什么意思呢?如果看不懂(*p)[4],那就把*p用b去替换掉,就成了b[4],这里的b就很明显了,就是一个有4个元素的数组首地址,那么这里的p的含义也就不难得出了:指向一个有四个元素的数组的首地址,尤其需要注意的是,这里p是指向首地址,并非就是首地址,p才是首地址,也就是指向第一个元素,因此这里的p也就是指向指针的指针*。

int (*p)[4]=a中,p就是指向了第一个含四个元素的数组(就是把a的每一行看出一个元素,a就有四个元素,p就指向了了a,p的四个指针指向a的每个元素)的首地址,也就是p指向a的首地址也就是a[0]*p

指向a[0]的首地址也就是a[0][0],要访问a[0][0],就就是*(*p)了;

既然p是指向一个有四个元素的数组首地址的指针,那么p+i呢?p+i呢?要分析清楚这个问题,我们还是参考b[4],前面说过,这里的p是指向首地址的指针,因此p+i就对应了&b+i,因此p+i就指向了&b+i所在的位置;*p是b的首地址,指向b的第一个元素,*p+i就对应了b+i,因此p+i就指向了b+i所在的位置

对于p+i,我们来看看&b+i和&b之间偏移了多少:
在这里插入图片描述

可以看到,&b+1偏移了16个字节,&b+2偏移了32个字节,而16个字节刚好就是数组b的大小,因此&b+i实际上偏移的地址为isizeof(b)=ic*sizeof(int)。也就是说p+i会跳过整个包含四个整型元素的数组指向下一个包含四个元素的数组,回到a[3][4]中,p指向的是a[0],那么p+1就指向a[1],p+i就指向a[i]了,因此,p+i偏移i个b数组大小的地址,也就是指向二维数组的第i行。

而对于p+i,就比较简单了,可以看做p=b,那么p+i也就是b+i,即是第i个元素的地址,因此也可得出p+i指向当前有四个元素的数组的第i个元素。

好了,那么我们要是想访问数组中的a[i][j]怎么办呢?前面说过,要访问数组中的元素,必须先得到其地址,对于a[i][j],它是a数组中第i行的子数组中的第j个元素,综合上面分析的,p+i即指向了数组a的第i行*(p+i)就是该行的首地址,也就是指向了a[i][0],而对于a[i]这个一维数组的第j个元素当然就是a[i]+j,也就是*(p+i)+j了,因此,a[i][j]的地址就是*(p+i)+ja[i][j]=*(*(p+i)+j)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值