当前位置:网站首页>C语言数组的深度分析
C语言数组的深度分析
2022-08-04 05:31:00 【π指针】
此文章在学习二维数组和数组指针,指针数组要对指针有一定的认识才容易读懂
此文章讲述了一维数组和二维数组要必须掌握的东西,以及二维数组在内存中存储的深入解析,数组指针一维数组指针和二维数组指针的不同操作
大家发现有错误和改善的地方,希望提出
文章目录
一、一维数组(除字符数组)
1. 一维数组的初始化与定义方式
int arr[10] = {
1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {
1,2,3,4};
int arr3[] = {
1,3,4,5,6};
// int arr4[10];不推荐
int arr4[10] = {
0};
// int arr5[];//错误
点击调试一刻,变量声明提前,得到的值是内存中的乱值
调试完毕后,才得到有效值,除了int arr4[10];未定义的,系统也没有赋予0
总结:
1. 没有赋值或定义时给足够多的空间时,多余的部分系统默认以0来初始化
2. int arr5[ ] ; 是一种错误的写法,
3. 不推荐int arr4[10]不赋值,建议赋予0。arr[10] = { 0 };
2. 一维数组在内存中的存储
int main(){
//一维数组的内存存储位置
int i , j;
int arr[10] = {
0};
for(j=0;j<10;j++){
printf("arr[%d]的内存存储位置为%X\n",j,&arr[j]);
}
}
结果如下
总结:
1. 一维数组的在内存中存储是连续的,一旦开辟则是开启一连串连续的空间
2. 在定义为int 类型时,是4个字节,所以他们的地址相继为4个,同理float为8个,char为1个,sort int为1个等
二、二维数组
1.二维数组的初始化与定义
int main(){
int arr[3][4] = {
{
1,2,3,4},{
1,2,3,4},{
1,2,3,4}};
int arr2[3][4] = {
{
1,2,3,4}};
int arr3[3][4] = {
{
1},{
2},{
3}};
int arr4[3][4] = {
0};
int arr5[][4] = {
1,2,3,4,5,6};
int arr6[3][4]; //不推荐
// int arr5[][] 错误写法
}
总结
1. 二维数组的第二个中括号不可以省略数字
2. 未赋值的系统默认用0来替代
2.二维数组的使用
int main(){
//二维数组的使用
//用循环遍历二维数组
int arr[3][4] = {
{
1,2,3,4},{
4,5,6,7},{
8,7,8,9}};
int len = sizeof(arr) / sizeof(arr[0][0]);
printf("%d\n",len);
int i , j;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
结果如下:
12
1 2 3 4
4 5 6 7
8 7 8 9
注意: sizeof( ) 是求字节数的, 在这里 arr数组为int的类型占48个字节(其实底层是用计算地址来得出多少个字节的)。同理arr[0][0]为int类型计算出来占用了4个字节,从而就得出了长度len为12个。在C语言上一般用这种方式来实现求数组长度。这里之所以打印出来,是为了看一下是否有错误(可有可无的)
3.二维数组在内存中的存储方式
int main(){
int arr[3][4] = {
{
1,2,3,4},{
4,5,6,7},{
8,7,8,9}};
int i , j;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
printf("arr[%d][%d] 的内存地址是 %X",i,j,&arr[i][j]);
printf("\n");
}
}
}
总结:
1. 由此可见,二维数组的背后内存也是连续的
2. 其实也是一个线性的,本质上是个一维数组,只不过呀只是一维数组里面的元素是一个数组
对总结第2点来进行详细的说明,若理解了,则对二维数组和指针的结合有着巨大的帮助!!
1.现在我们都知道二维数组本质是一维数组里面嵌套着数组了,那么
2.现在证明一下
int main(){
int arr[3][4] = {
{
1,2,3,4},{
4,5,6,7},{
8,7,8,9}};
printf("%d\n",arr[0]);
printf("%x\n",arr[0]);
printf("%d\n",arr[1]);
printf("%x\n",arr[1]);
printf("%d\n",arr[2]);
printf("%x\n",arr[2]);
}
结果如下:
结论:
1. 无论是用%d 还是 %x 都是打印出一个地址,无疑上面的图很好的说明了二维数组的结构
2. 因为arr[ 0 ],arr[ 1 ],arr[ 2 ]默认的是arr[ 0 ][ 0 ],arr[ 1 ][ 0 ],arr[ 2 ][ 0 ]的地址,所以他们相差的恰巧是3个字符,也就是12个字节
3. 二维数组不能省略后中括号的原因也可以知道了,那就是不告诉数组,数组不知道要怎么分配多少个,并且空间大小是多少
4. 此思维建议用于学习二维数组指针难点
三、一维字符数组
一维字符数组可以表示为字符串,但是一维数组可以存字符串也可以存字符(一般来说我们画一个等号)
1.一维字符数组的初始化与定义
int main(){
char arr[20] = {
"ni hao a"};
char arr2[10] = "ni hao a";
char arr3[] = "ni hao a";
char arr4[] = {
'w','o','h','e','n','h','a','o'};
char arr5[5] = "12345";//这种初始化可能出问题,因为字符数组都是默认以'\0'为结束标志的,所以要放一个进去,没空间放可能出错
补充:
char arr6[20] = "";//这种方式是初始化,默认每一项都是'\0',注意不是空格
}
总结:
1. 定义的空间大于初始化的字符时,默认用’\0’来替补
2. 字符数组是以’\0’为结束标志的
3. \0 在 ASCII表中代表的是第一个数字0,代表空字符,所以遇到’\0’就转义为空字符
4. 字符数组里面的字符,是用对应的ascii码值进行存储的
2.一维字符数组的细节
1.字符串的输入
大多数人都想到的是scanf,但是注意的是,scanf缺点是,不可以输入空格,若输入空格,则结果如下
int main(){
char s[20];
scanf("%s",s);
printf("%s",s);
}
输入: nihao ma
输出: nihao
但是除了<,?:"} 等等,空格也是字符,那么解决输入空格的问题,录入字符串的时候我们用gets函数,然而配套的也有puts函数
int main(){
char s[20];
gets(s);
puts(s);
}
输入: nihao ma
输出: nihao ma
puts函数和printf函数是一样的输出功能,只是puts能自动换行–相当于多了一个\n
2.字符的输入
int main(){
char s[20];
scanf("%c",&s[1]);
scanf("%c",&s[2]);
printf("%c %c\n",s[1],s[2]);
printf("%d %d\n",s[1],s[2]);
}
结果如下:
s
s
115 10
当两个scanf同时使用,想输入一个s后按下确认键打算给s[2]赋值是,可惜的是,第一个接收是正常了,第二个接收到了一个换行键’\n’,然后程序以为你已经输入完毕了,一个是s,一个是’\n’,然后结束了。
打印结果也是如此,s[2]结果是10,在ASCII表中是换行键的值
我们要解决这种输入字符出现的问题,引用fflush(stdin);这种函数的作用是清除缓存,清除确认键带来的缓存
int main(){
char s[20];
scanf("%c",&s[1]);
fflush(stdin);
scanf("%c",&s[2]);
printf("%c %c\n",s[1],s[2]);
printf("%d %d\n",s[1],s[2]);
}
结果如下
N
H
N H
78 72
3.一维字符数组在内存中的存储方式
int main(){
char s[12] = "how are you";
int i;
int len = sizeof(s) / sizeof(s[0]);
for(i=0;i<len;i++){
printf("第%d个字符的地址是%X\n",i,&s[i]);
}
}
地址相差1,也是一个连续的内存地址,和上面的都一样
四、学习数组作为形参时,先具备一些知识
int main()
{
int arr[10] = {
1,2,3,4,5,6,7};
printf("%X\n",arr);
printf("%X\n",&arr[0]);
printf("%X\n",&arr[1]);
}
结果如下:
62FDF0
62FDF0
62fdf4
五、数组作为形参
1.一维数组作为形参
int check(int arr[],int len)
{
printf("\n\n\n\n");
printf("这是arr的地址首地址%X\n",arr);
printf("这是arr下一个元素的地址%X\n",arr+1);
printf("这是&arr默认为形式参数int arr[]首地址%X\n",&arr);
printf("这是&arr+1取整个形参数组的下一个地址%X\n",&arr+1);
}
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10};
int len = sizeof(arr) / sizeof(arr[0]);
printf("这是arr的地址首地址%X\n",arr);
printf("这是arr下一个元素的地址%X\n",arr+1);
printf("这是&arr取整个数组的地址,默认为首地址%X\n",&arr);
printf("这是&arr+1取整个数组的下一个地址%X\n",&arr + 1);
printf("arr[9]的地址%X\n",&arr[9]);//arr[9]指向的是这个单元格的第一个字节的地址62FE14,固然他的尾巴是62FE17
printf("arr[9] + 1的地址%X\n",&arr[9] + 1);//所以他的 &[9] + 1 是 62FE18
check(arr,len);
}
总结:
1. 除了普通变量的传递是复制的以外,数组并不是通过复制的方式来传递,试想一下如果数组非常长的时候,那么性能是非常差的,所以用到了很好的传递方式,传地址的方式
2. 用形参作为参数的时候,在传递的那一刻,int arr[ ] 接收到了一个地址(这个地址是arr的地址,也就是arr[0]首元素的地址),假设arr[0]的地址是62FDF0。 相当于 int arr[] => 62FDF0;这个arr保存的是一个地址,作用相当于指针,所以用指针 int * X 接收也是一样的
3. 传递参数完成后,他就可以正常的像数组一样进行操作了
4. 在求数组长度的时候,要先求好了,用传参的方式传过去,否则求不到真实长度
5. 形参的接收格式有 : int arr[ ];int arr[20]里面如果写的话最好写的比原来的大;int * x;
2.二维数组作为形参
#include <stdio.h>
int check(int arr[],int len)
{
printf("\n\n\n\n");
printf("这是arr的地址首地址%X\n",arr);
printf("这是arr下一个元素的地址%X\n",arr+1);
printf("这是&arr默认为形式参数int arr[]首地址%X\n",&arr);
printf("这是&arr+1取整个形参数组的下一个地址%X\n",&arr+1);
}
int main()
{
int arr[3][4] = {
1,2,3,4,5,6,7,8,9,10,0,0};
int len = sizeof(arr) / sizeof(arr[0][0]);
printf("这是arr的地址首地址%X\n",arr);
printf("这是arr下一个元素的地址%X\n",arr+1);
printf("这是&arr取整个数组的地址,默认为首地址%X\n",&arr);
printf("这是&arr+1取整个数组的下一个地址%X\n",&arr + 1);
printf("&arr[2][3]的地址%X\n",&arr[2][3]);
printf("&arr[2][3] + 1的地址%X\n",&arr[2][3] + 1);
check(arr,len);
}
总结:
1. 形参的书写方式一定要在第二个中括号带上值
2. 可以写为指针 int (*p)[ 4 ],可写为二维数组int arr[ ][ 4 ],arr[ 3 ][ 4 ]
3. 二维数组大致都和一维数组一样,传的是一个地址
六、数组指针和指针数组
*符号优先级是 ( ) > [ ] > ***
数组指针,那就是一个指向数组的指针 int ( p)[5] ,p先和*结合,然后再和中括号结合
1.一维数组的数组指针
int main()
{
int arr[3] = {
1,2,3};
int (*p)[3];
p = &arr; //要用p来赋值,不带星号,就像指针一样,int *p; p = &a;一样
printf("%X\n",*p);//62FE00
printf("%X\n",*&arr);//62FE00
printf("%X\n",*(*p+1));//2
printf("%X\n",*(arr+1));//2
printf("%d\n",arr[0]);//1
printf("%d\n",(*p)[0]);//1
}
下面是对一维数组指针的解释
总结:
1. 为什么要用p=&arr 不用 p = arr,本质上都是地址,但是&arr是指向整个数组,把数组作为一个整体,而arr就指首个单元
2. 把p=&arr赋值后,p自然就指向了&arr 也就是 arr,所以在用数组指针操作的时候,可以操作为p + 1 =>相当于arr + 1。可以操作为(p)[0] =>相当于arr[0],要加( )括号的目的是,中括号的优先级比高,所以要保持arr等价于p那就让他们不可分开
2. 二维数组的数组指针
根据上面解释的二维数组在内存中的存储方式的总结2中说的很清楚,其实二维数组是一维数组,只是一维数组里面的元素存的是数组(可以想象存的是数组的地址)
二维数组的指针
int main()
{
int arr[3][4] = {
{
10,20,30,40},{
50,60,70,80},{
90,100,110,120}};
int (*p)[4] = arr;//这里和一维数组的取值不一样,这里取arr首地址就可以了 (因为第一层arr存的是地址)
printf("%d\n",*p);
printf("%d\n",arr[1][1]); //60
printf("%d\n",*(*(p+1) + 1)); //60
printf("%d\n",(*(p+1))[1]); //60
}
总结:
1. 要注意小括号,中括号,星号的优先级问题,这样就轻而易举解决数组指针的困难题了。
2. 一维数组的数组指针和二维数组的数组指针还是有区别的(一维数组赋值需要取地址赋值,二维数组不需要),原因都是为了在使用*号取值的时候解决问题,如果不按照规定去写,有的编译器还会报错误像
dev++报了个警告
七、指针数组
指针数组无疑,是在数组里面保存的都是指针,他只存了指针也就是一些地址。你们仔细思考一下,是不是和二维数组第一层的那个数组很相似呢?
走入正题
指针数组的定义和初始化
int * p[5] 先和5结合,然后在和*号结合
int main()
{
int *p[5];
int arr[10] = {
1,2,3};
int arr2[5] = {
1,2,3};
int arr3[] = {
1,23,4};
int arr4[3][5]={
1,23,4,3,43,5,7,4,6};
int arr5[][4]={
1,23,36,6,34,5,1};
p[0] = arr;
p[1] = arr2;
p[2] = arr3;
p[3] = arr4;
p[4] = arr5;
int i;
for(i=0;i<5;i++){
printf("%d\n",p[i]);
}
}
结果如下:
6487472
6487440
6487424
6487360
6487328
八、最后
作者也是一名C语言学习者和C语言爱好者,阅读多次C语言对数组部分的总结,若是阅读到,希望大家认可。
边栏推荐
- Unity ML-agents 参数设置解明
- A code example of the PCL method in the domain of DG (Domain Generalization)
- MNIST手写数字识别 —— Lenet-5首个商用级别卷积神经网络
- MySQL批量修改时间字段
- MNIST handwritten digit recognition, sorted by from two to ten
- [开发杂项][编辑器][代码阅读]ctags&vim
- 机器学习——分类问题对于文字标签的处理(特征工程)
- Cut the hit pro subtitles export of essays
- MNIST Handwritten Digit Recognition - Image Analysis Method for Binary Classification
- 中国联通、欧莱雅和钉钉都在争相打造的秘密武器?虚拟IP未来还有怎样的可能
猜你喜欢
MNIST Handwritten Digit Recognition - Image Analysis Method for Binary Classification
迅雷关闭自动更新
TensorRT 5 初步认识
tensorRT教程——tensor RT OP理解(实现自定义层,搭建网络)
LeetCode_Dec_2nd_Week
题目1000:输入两个整数a和b,计算a+b的和,此题是多组测试数据
Chapter One Introduction
深度学习理论——过拟合、欠拟合、正则化、优化器
MNIST手写数字识别 —— 图像分析法实现二分类
[Deep Learning Diary] Day 1: Hello world, Hello CNN MNIST
随机推荐
MNIST Handwritten Digit Recognition - Building a Perceptron from Zero for Two-Classification
LeetCode_Dec_3rd_Week
YOLOV5 V6.1 详细训练方法
MVC自定义配置
IEEE802.X协议族
Copy Siege Lion 5-minute online experience MindIR format model generation
第三章 标准单元库(上)
LeetCode_22_Apr_2nd_Week
[开发杂项][调试]debug into kernel
LeetCode_Dec_2nd_Week
LeetCode_Dec_1st_Week
Copy攻城狮的年度之“战”|回顾2020
Design and implementation of legal aid platform based on asp.net (with project link)
Completely remove MySQL tutorial
No matching function for call to 'RCTBridgeModuleNameForClass'
打金?工作室?账号被封?游戏灰黑产离我们有多近
Amazon Cloud Technology Build On 2022 - AIot Season 2 IoT Special Experiment Experience
Golang环境变量设置(二)--GOMODULE&GOPROXY
SQL注入详解
深度学习,“粮草”先行--浅谈数据集获取之道