当前位置:网站首页>7.数组、指针和数组的关系
7.数组、指针和数组的关系
2022-07-06 09:19:00 【是王久久阿】
本文采用了《C Primer Plus》、《C陷阱与缺陷》以及比特科技的教学视频。
对C语言数组、指针和数组的关系进行了详细讲解;并将初学者在使用数组时遇到的问题进行了总结,希望对各位读者有所帮助。
目录
1.一维数组
一维数组的创建
数组就是由数据类型相同的一系列元素组成,创建普通变量时的数据类型,在创建数组中均适用。
一维数组创建的一般格式:元素类型 数组名 [常量表达式];
示例1:
#include<stdio.h>
int main()
{
int arr1[10];//10个int类型元素的数组
char arr2[20];//10个char类型元素的数组
double arr3[30];//10个double类型元素的数组
return 0;
}
这里arr1、arr2、arr3都是数组名,[]中的10、20、30均是常量,很好理解。
示例2:
#include<stdio.h>
#define n 10
int main()
{
int arr1[n];//10个int类型元素的数组
return 0;
}
通过用define定义常量,也可以实现数组的创建。
示例3:
#include<stdio.h>
int main()
{
int n = 10;
char a[n];
return 0;
}
以上方法为C99标准所支持的变长数组(VLA)的概念,在C99标准之前[]中必须赋予一个常量。
值得注意的是:并不是所有编译器都支持变长数组(VS编译器就不支持),并且变长数组存在很多弊端,不建议使用。
数组的创建和使用都是静态的,无论用那种方式创建数组,在使用中不可将其空间大小改变。若是想动态的改变空间存放数据,则需要学到动态规划的知识。
一维数组的初始化
(一)传统方法
数组的初始化是指:在创建数组的同时给数组的内容一些合理初始值(初始化)。
int arr1[5] = {1,2,3,4,5};//完全初始化
int arr2[10] = {1,2,3};//不完全初始化
int arr3[] = {1,2,3,4};//编译器按输入内容自行定义数组大小
- 完全初始化:为数组内的每一元素都赋予其值。
- 不完全初始化:未赋值的元素,编译器自动初始化为0。
- 省略[]中的常量:编译器会自动根据输入的内容来合理分配空间。
需要区分以下两种初始化形式
char arr1[] = "abc";
char arr2[4] = {'a','b','c','\0'};
字符串的末尾默认为‘\0’,以字符串形式初始化不用输入‘\0’;若是以单个字符输入时,需要在结尾单独输入‘\0’,并且‘\0’占一个字节的空间。
如果不进行初始化会怎样?
#include<stdio.h>
int main()
{
char arr1[10];
printf("%s\n", arr1);
}
如上述代码所示,创建了arr1数组,其中包含10个char类型的数据,在创建之初并没有对其进行初始化,打印arr1数组。
这是因为没有对数组进行初始化,数组中存放的都是垃圾信息,并且arr1数组是char类型的,编译器找不到终止符‘\0’,打印时会一直打印,超出数组的大小限制。
(二)指定初始化器(C99)
传统方法中,若是想初始化一个数组中的最后一个元素,必须将该元素以及之前的元素全部初始化。而在C99标准中,支持针对某一个元素进行初始化,而其他未被初始化的元素默认初始化为0。
int arr1[5]={0,0,0,0,1};//将第五个元素初始化为1
int arr2[5] = {[4]=1};//数组下标从0开始,4表示第五个元素
arr1和arr2的初始化结果是一样的。
示例1:
int main()
{
int arr1[10] = {[2]=1,[4]=2,3,4,[2]=10};
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
通过以上示例可以明白指定初始化器的两个特性:
- 如果初始化器后面有更多的值:[4]=2,3,4,那么这些值将赋予指定初始化元素后面的元素。arr[4]被初始化为2后,紧接着arr[5]被初始化为3,arr[6]被初始化为4。
- 如果再次初始化指定元素,那么最后一次初始化将会取代前一次初始化。例如[2]=1,最开始将1的值赋给arr1[2],但是arr1[2]又被后来的[2]=10初始化为10。
不指定数组大小会怎样?
int main()
{
int arr1[] = {[2]=1,[4]=2,3,4};
return 0;
}
编译器会根据指定初始化的内容合理分配空间,初始化的最后一个元素为arr[6],所以编译器为其分配7个元素的空间。
一维数组的使用
声明完数组后,借助数组的下标可对其进行赋值。
#define num 5
int main()
{
int arr1[num] = {0};
int i = 0;
for (i = 0; i < num; i++)
{
arr1[i] = i;
}
return 0;
}
C语言中不允许把数组作为一个值赋给另一个数组,并且除了初始化外,不允许以花括号的形式进行赋值,以下为错误示例:
#define num 5
int main()
{
int arr1[num] = { 1,2,3,4,5 };
int arr2[num] = { 0 };
arr2 = arr1;//不允许
arr1[num] = arr2[num];//数组下标越界
arr2[num] = { 1,2,3,4,5 };//无效
return 0;
}
- arr1[num] = arr2[num]理解为:将arr2中下标为num的元素,将其值赋给arr1中下标为num的元素,但是arr1和arr2中的元素下标最大为num-1,访问越界。
- 若将其改成arr1[num-1] = arr2[num-1],仅为下标是[num-1]元素的赋值操作,并不是数组赋值操作。
数组边界
在使用数组时,需要防止数组下标超出边界,必须确保数组下标是有效值。
例如:
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
putchar('\n');
return 0;
}
arr1数组中有效下标为0~4,访问越界时,打印的就是无意义的数。但是编译器并不会报错,可以正常运行。这是因为C语言信任程序员的原则,不检查边界。为了C语言可以运行的更快,编译器没有必要捕获所有的下标错误。为了防止访问出界,在声明数组时用符号常量来表示数组的大小。
指定数组的大小
在前文创建数组时,举了三种例子来指定数组的大小,这里更加详细的介绍指定数组大小的各种方法:
#define size 5
int main()
{
int a1[size];//可以,整数符号常量
int a2[5];//可以,整数字面常量
int a3[5 * 2 - 1];//可以,整数常量表达式
int a4[0];//不可以,数组大小必须大于0
int a5[-1];//不可以,数组大小必须大于0
int a6[2.5];//不可以,数组大小必须是整数
int a7[(int)2.5];//可以,强制类型转换成整型
int a8[sizeof(int)];可以,sizeof表达式被视为整型常量
int n = 5;
int a9[n]; //变长数组VLA,C99之前不允许
return 0;
}
值得注意的是,const修饰的常变量,虽然具有常量的属性,但本质还是变量,C99标准前不允许用const修饰的常变量指定数组的大小。
一维数组在内存中的存储
创建一个整型数组,打印其地址(以X86环境为例)
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
结果如下:
通过观察可以得知:数组元素在存放时是连续存放的,随着下标的增长由低地址指向高地址,本例中arr为整型数组,所以每一个元素之间间隔4个字节。
2.二维数组
二维数组的创建
一维数组创建的一般格式:元素类型 数组名 [常量表达式1][常量表达式2];表达式1为数组的行,表达式2为数组的列。
int arr1[3][3];
char arr1[3][3];
double arr1[3][3];
二维数组的初始化
int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };//只要对应的数值正确,也可以省略内部的小括号
int arr[][3] = { 1,2,3,4,5,6,7,8,9 };//初始化时,行可以省略,列不能省略
例1:
下述代码初始化数组的结果是什么?
int arr4[][3] = { {1,2,3},{4,5},{7} };
结果:
与一维数组一样,未初始化的元素默认为0。
例2:
下述代码初始化数组的结果是什么?
int arr5[][3] = { {1,2,3},{(4,5),5},{7}};
结果:
(4,5)为逗号表达式,赋值时取后者5。
二维数组的使用
与一维数组一样,二维数组的使用也是用for循环遍历,区别在于二维数组需要用到两个for循环嵌套使用。
打印一个二维数组:
int arr[][3] = {1,2,3,4,5,6,7,8,9};
int i = 0, j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
二维数组在内存中的存储
打印二维数组中每一位的地址
#include <stdio.h>
int main()
{
int arr[3][4];
int i = 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]);
}
}
return 0;
}
结果:
通过观察发现,二维数组的存储也是连续的,先存储第一行的元素,然后再存储第二行的,以此类推。
3.指针和数组
指针提供一种以符号形式使用地址的方法,使用指针可以更加有效率的存储数据,而数组则是变相的使用指针?
数组名是什么?
对于一维数组来说,数组名就是首元素的地址。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr);
return 0;
}
打印arr的地址和arr首元素arr[0]的地址,发现二者是相同的。通过对arr地址的解引用*,可以找到该数组的首元素1。
对于二维数组来说,数组名就是第一行元素的地址。
#include <stdio.h>
int main()
{
int arr[][3] = { 1,2,3,4,5,6,7,8,9 };
printf("%p\n", arr);
printf("%p\n", &arr[0][0]);
printf("%d\n", (*arr)[0]);
return 0;
}
结果:
第一行元素的地址又等于其第一行中首元素的地址,打印arr的地址和第一行第一个元素的地址,发现其二者相同。(*arr)[0]表示为:通过对arr进行解引用找到第一行,再从第一行中找到下标为0的元素,即1。
通过地址对数组进行操作
打印一维数组的每一位
#include <stdio.h>
#define n 5
int main()
{
int arr[n] = { 1,2,3,4,5};
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", *(arr + i));
}
return 0;
}
打印二维数组的每一位
#include <stdio.h>
#define n 3
int main()
{
int arr[][n] = { 1,2,3,4,5,6,7,8,9};
int i = 0, j = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
printf("%d ", *( * (arr + i)+j));
}
putchar('\n');
}
return 0;
}
数组参数、指针参数
设计一个函数,返回值是数组内所有整数的和。
一维数组传参的几种形式:
数组形式:
#include <stdio.h>
int text1(int arr[],int sz)//int arr[3]亦可,arr[]里有没有数都一样
{
int i = 0;
int sum = 0;
for (i = 0; i < sz; i++)
{
sum += arr[i];
}
return sum;
}
int main()
{
int arr[3] = {1,2,3};
int sz = sizeof(arr) / sizeof(arr[0]);
return 0;
}
因为数组名是首元素的地址,传入到text函数中,看似arr还是数组,其实只是首元素的地址,还需要将数组的大小sz也传入text函数中。
指针形式:
int text(int* arr, int sz)
{
int i = 0;
int sum = 0;
for (i = 0; i < sz; i++)
{
sum += *(arr+i);
}
return sum;
}
二维数组传参的几种形式:
数组形式:
#include <stdio.h>
#define ROW 3
#define COL 3
int text(int arr[][ROW], int row,int col)//int arr[][ROW]接收时,可以省略行,不能省略列
{
int i = 0, j = 0;
int sum = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
sum += arr[i][j];
}
}
return sum;
}
int main()
{
int arr[ROW][COL] = {1,2,3,4,5,6,7,8,9};
printf("%d\n", text(arr, ROW,COL));
return 0;
}
指针形式:
#include <stdio.h>
#define ROW 3
#define COL 3
int text(int (*arr)[ROW], int row, int col)
{
int i = 0, j = 0;
int sum = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
sum += *(*(arr+i)+j);
}
}
return sum;
}
int main()
{
int arr[ROW][COL] = { 1,2,3,4,5,6,7,8,9 };
printf("%d\n", text(arr, ROW, COL));
return 0;
}
数组形式和指针形式的本质都是地址的使用,二者在实质上没有区别。
边栏推荐
- First acquaintance with C language (Part 1)
- 系统设计学习(二)Design a key-value cache to save the results of the most recent web server queries
- Interview Essentials: talk about the various implementations of distributed locks!
- Network layer 7 protocol
- IPv6 experiment
- Tyut outline of 2022 database examination of Taiyuan University of Technology
- 面试必备:聊聊分布式锁的多种实现!
- Introduction pointer notes
- A brief introduction to the database of tyut Taiyuan University of technology in previous years
- Redis介绍与使用
猜你喜欢
继承和多态(上)
Cloud native trend in 2022
Alibaba cloud microservices (I) service registry Nacos, rest template and feign client
View UI plus released version 1.2.0 and added image, skeleton and typography components
Music playback (toggle & playerprefs)
Interview Essentials: talk about the various implementations of distributed locks!
TYUT太原理工大学2022数据库大题之概念模型设计
Heap sort [handwritten small root heap]
TYUT太原理工大学2022“mao gai”必背
Answer to "software testing" exercise: Chapter 1
随机推荐
Small exercise of library management system
Tyut Taiyuan University of technology 2022 introduction to software engineering examination question outline
View UI plus released version 1.3.1 to enhance the experience of typescript
西安电子科技大学22学年上学期《射频电路基础》试题及答案
View UI Plus 發布 1.3.1 版本,增强 TypeScript 使用體驗
架构师怎样绘制系统架构蓝图?
Inheritance and polymorphism (I)
Ten minutes to thoroughly master cache breakdown, cache penetration, cache avalanche
IPv6 experiment
分支语句和循环语句
TYUT太原理工大学2022数据库大题之概念模型设计
记录:下一不小心写了个递归
如何保障 MySQL 和 Redis 的数据一致性?
Design a key value cache to save the results of the most recent Web server queries
TYUT太原理工大学往年数据库简述题
How do architects draw system architecture blueprints?
Alibaba cloud microservices (I) service registry Nacos, rest template and feign client
(super detailed II) detailed visualization of onenet data, how to plot with intercepted data flow
继承和多态(上)
First acquaintance with C language (Part 1)