详解数组与指针的那些知识 一

每一次盘复,相信都会有一定的收获。                                                       ----日向晚,声声慢


目录

一.数组:

1.数组的创建与初始化

2.数组内存中的存储

二.二维数组

1.二维数组的创建与初始化

2.二维数组在内存中的存储

三.注意事项

1.越界访问-《C陷阱与缺陷》中的一道经典题型

2.数组名

3.下标

二.指针

1.指针的含义

1.1内存地址如何产生的?

2.野指针

野指针的成因 

3.指针的类型存在的意义

4.指针的运算

4.1指针加减整数

4.2指针之间的比较

4.3指针减指针

5.二级指针

6.指针数组

三.指针与数组之间的联系

1.操作符之间的转换

2.注意事项

总结 


一.数组:

数组是什么?数组是能存放同一种类型的集合。

组成部分:存放内容的类型 数组名 [常数]。

在c99中有变长数组的概念,变长数组指的就是,[]中可以是变量,但是变长数组在创建的时候不能初始化。在VS的编译器下不支持变长数组,一些刷题网站上是支持的,例如牛客网上。

7ad8101ddff348fea4e9615981b7a164.png

这是在牛客网上,没有按照题目要求,但编译能通过。在使用变长数组的时候,一般通过循环来赋值,在强调一次,变长数组不能初始化。

1.数组的创建与初始化

b7bdc1e01e954c89a380c96b179386e1.png

f722a5b5e293439b809c411a79c282e8.png

 比较字符串长度与元素个数:

61f52234141f4b75a6d522f465cabdd2.png

字符串长度存在差异主要是因为strlen()在求字符串的长度时是寻找’ \0 ‘,计算在' \0 '之前的元素。

在求arr7的字符串长度时,在arr7[16]的这个位置找到了'\0'。

这里还要关注到我们数组元素个数的求法。下面稍后就会讲到。

2.数组内存中的存储

6a00564b42ff4589a23775a7dc2268fa.png

bb6f247238e3407fa6aa60cbb5c4dda8.png

 %p是打印地址。

为什么是相差4呢,因为我们数组的类型是int类型的,int类型占4个字节。

图片那里每一个空格代表的是4个字节。

数组在内存中的存储是连续的,并且随着下标的增长,地址也在由低到高的变化。

二.二维数组

组成部分:

 存放内容的类型 数组名 [常数][常数]  ( [ 行 ][ 列 ] )

也存在变长的数组。在使用变长数组的时候,赋值就用双层循环就可以了。

1.二维数组的创建与初始化

e37fe779feb54722ad2a78b2028a0d77.png

初始化

5924b5eee589483db7aaca3ec7ffdf16.png

2.二维数组在内存中的存储

3c3340b9c85f47cf86b0629851b5206e.png

62a1f2cb0a634a509b163bb1110b38c5.png

图中每个空格代表的是4个字节。 

三.注意事项

1.越界访问-《C陷阱与缺陷》中的一道经典题型

题目是这样的,以下代码会出现什么问题?为什么?

#include <stdio.h>
int main()
{
    int i = 0;
    int arr[10] = {0};
    for(i=0; i<=12; i++)
   {
        arr[i] = 0;
        printf("hehe\n");
   }
    return 0; 
}

 答案是死循环。为什么呢?

 我们先介绍一下内存。内存中有栈区,堆区,静态区。还有一些其他的,先讲这三个。

3c2acafe782c4fd7942c99a3632f81ad.png

我们这里讲栈区

e97f7c85007041d2ab5a9aae825aefa6.png

 程序在执行的时候先是创建变量,再是创建数组。我们来看图

127eede793ea446ba6080f73805fefeb.png

这段代码,两个问题越界和死循环。因为越界导致死循环。

我们在图中可以发现,死循环的原因是,arr[12]的地址就是i的地址,执行到arr[12]=0,这时i的值也随之变化,i=0。

又重新进去循环,一直这样下去,因此会死循环。

变量i和数组的地址之间差8个字节(图片上每一个空格代表的是一个整形-->4个字节)

这个差值是随机值吗?这个得看编译器。在gcc编译器中这个差值是4个字节,VS编译器下是8个字节,vc 6.0编译器中这个差值为0个字节。

 重复一下原因:

1.局部变量存放在栈区。

2.因为栈区在使用的时候先使用高地址在使用低地址(变量i先使用,在高地址处,数组后使用在变量i的下面)

3.因为数组的内存存储,是连续的,地址随着下标的增长由低到高的变化。

这些原因导致数组越界的时候,会找到i的地址。

4.先创建i,在创建数组也可以算一个原因。如果先创建数组,在创建i就不会发生死循环,只会越界。

解决方法就是,数组不要越界。 

2.数组名

数组名是首元素的地址,存在两个例外,其它时候都是指首元素地址。

第一个例外

sizeof()中,大家有发现,在之前我们求数组元素个数的时候就用到了,sizeof(数组名)求的是整个数组在内存中占用多大的内存,单位是字节。

sizeof中数组名单独出现的时候代表是整个数组

第二个例外

&数组名,指的是整个数组的地址。

63b61f463db94d098ad091342ec50ff3.png

3.下标

有两个方面

第一个不能越界。越界了,我们编译是不会报错的,写的时候需要注意。

如果写循环的话,推荐用前闭后开的写法

1611624bc32946aba4589f534c4de974.png

第二个起始下标为0。

二.指针

1.指针的含义

指针是内存中最小单元的编号,每一个编号是一个地址,指针是编号,也是地址。

需要注意下,我们口头上说的指针,指的是指针变量。

#include <stdio.h>
int main()
{
 int a = 10;//在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
 r

起始地址存在指针中。 

1.1内存地址如何产生的?

什么是内存中最小单元的编号呢?地址又是如何产生的呢?其实是一个问题。

我们电脑中有32根地址线(32位平台),在高电压和低电压的作用下,每根地址线都会产生1或0。

32根地址线由0-1表示

00000000000000000000000000000000

00000000000000000000000000000001

00000000000000000000000000000002

............

10000000000000000000000000000000

10000000000000000000000000000001

..........

11111111111111111111111111111111

产生的这些二进制序列就是最小单元的编号,也是地址。不过编译器下为了方便一般会转化为16进制的。总共有2^32个编号(地址)

92f60a14615e4202932c15b2c25c0774.png

每个最小单元是1个字节(8位)

每个地址对应内存空间中一个字节

所以内存大小为2^32Byte,1024的转换下去就是4GB

也有64根地址线的(64位的电脑)

最小单元还是1个字节

能产生2^64Byte内存大小

那指针(地址)的大小是多少呢?

32根地址线,产生的2进制序列有32位--->4个字节

64根地址线,产生的2进制序列有64位--->8个字节

所以指针占4/8个字节,看电脑是32位的还是64位的。

2.野指针

什么是野指针?野指针就是指针指向的位置是不可知的。 

野指针的成因 

1.指针未初始化。

2.在调用函数的时候,返回值为地址,主函数内指针接收这个地址。这个指针也是野指针。

3.越界访问,指针与数组连用的时候,可能会越界。

第二点: 

14948d6010604c20ac07fbd98267810e.png

当我们调用Fun函数的时候,Fun函数才会向内存(栈区)开辟空间,调用完Fun函数,它开辟的空间就会释放回去,里面所创建的变量,同样也会释放回去。

这样的话,arr是可以接收到&a,可以找到它,但这个时候这个地址不在属于我们了,已经还给操作系统了,属于是非法当问。

另一种理解,局部变量的生命周期是在{}内部,在回到主函数后,已经离开了a所在的{},a开辟的空间自然就释放回去了。

3.指针的类型存在的意义

有两个意义:

1.类型决定了指针能操作的内存大小

int 类型能操作4个字节

char类型能操作1个字节

......

896bc7981adc45f2ba78c8e14de10986.png

927f34b45f73424bad9f2fec51f6d42e.png

2.类型决定了指针加减整数时候的步长

int类型的指针+1向后走4个字节

char类型的指针+1能像后走1个字节

......

 fc23e7a023f0406baf2a5709dc28c003.png

 e7cc3a8ef51c403aad2a4fd033cb724c.png

两者对比一下,我们就能发现问题。 

 cb4c248ece6c4f1db16341724c8fac87.png

想深入了解存储方式,可以看看 整形与浮点型的存储

4.指针的运算

4.1指针加减整数

这个就是上面类型的意义中有提到的,加(减)整数向后(前),偏移多少个字节取决与类型。

4.2指针之间的比较

我们就看个例题,逆序字符串

void reverse_string(char* string)
{
	char* left = string;
	char* right = string + strlen(string) - 1;
	while (left <= right)
	{
		char tmp = 0;
		tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
int main()
{
	char arr[50] = { 0 };
	gets(arr);
	reverse_string(arr);
	printf("%s", arr);
	return 0;
}

535aca934d374206b0c7d7e3d10157b9.png

cc4df13549fa43a5a373deb1ecb0a182.png

4.3指针减指针

指针减指针 ,算出来的结果为两指针中间的元素个数。

 afe89badca0848d9bcfae16e15184957.png

 71c21b6eed494f5faf00005de07cb84e.png

5.二级指针

int a = 0 ;   int* pa =&a 这样的叫一级指针, 二级指针是一级指针的地址,二级指针变量指向一级指针变量。

int* * pb = &pa。 我在写的时候,特意分开了一些, 中间的 * 表示它是个指针, int* 表示指针pb的指向地址所存放内容的类型。

 f5b9306a009d418a9fb9387fb1f5fbb4.png

同样也可以通过*改变a的值 

 3487ba6637224f629faaf7aa8fc15a56.png

6.指针数组

指针数组是指针还是数组呢?是数组,存放指针的数组,也就是存放地址的数组。

 393a16223bab4a71a466850e182a07ec.png

简单应用 :二级指针模拟实现二维数组。

c1dca1723a294c57ab6e312680fd45f5.png

 5e9840e8f361445ea934ebae820f171e.png

三.指针与数组之间的联系

1.操作符之间的转换

17b276bfce864bac9146447e47eb8982.png

5afdefa31525428ab4040999d7f77bef.png

这样一讲再去理解二级指针模拟二维数组应该更好理解了。 

 8d2cd939d6b14db5a095f4da094bdd0f.png

arr4+i-->arr4[i]  后面的这个arr4[i],是因为arr4本身就是数组。借用数组的写法。

2.注意事项

在使用函数的时候,实参为数组需要注意。

68ee164dbf6e42ce80aaaf60d9987bde.png

我这样写对吗?不对,为什么呢? 

40da3c843bf14765bb21a8f5339b8b15.png

原因很简单,实参是arr,arr是数组名,数组名就是地址,我们讲过了,地址占内存的大小是根据电脑是32位平台还是64位平台。sizeof(arr)-->4   sizeof(arr[0])-->4 -->求的是整形的大小。

怎么解决呢?在主函数中求出数组的大小,在进行传参。

形参写的时候的两种方法,一种就是用数组,另一种是指针。传过来数组,用数组接收没毛病吧。传过来是个地址,用指针接收也没毛病。

3c743b3e27334fe6986dadea60d0236c.png

b4ef105465494d9bb8bcfcbfc056b91e.png

总结 

小结一下,我们回顾了:

数组的创建初始化,以及存储时候的特点,一些注意事项。《c陷阱与缺陷》的那道题需要多多留意。

指针:指针是什么,指针类型的意义,指针的运算,二级指针,野指针,指针数组,也有一些注意事项。

我们接着回顾了下指针和数组之间的联系。

对内存大体上有所了解,三个大区,栈区,堆区,静态区。复习了,内存是如何编址的,最小的内存单元。

......

  • 30
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值