文章标题】: c数组与指针学习笔记
【文章作者】: evilkis
--------------------------------------------------------------------------------------------------------
1.数组的定义
int a[5];定义了一个数组a,它可以存放5个整型数据,注意:在定义了一个数组时,编译系统会按照数组类型和数组元素个数来分配一段连续的存储空间来存储数组元素。则数组存放数组a的空间大小为5*sizeof(int)= 20字节。
2 数组的引用
当我们引用数组元素时下标从0开始,小于元素的个数,且只能逐个引用数组元素,而不能直接引用整个数组。如:int a[2]={1,2};int b[2]=a;这句就是错误的!原因是引用了整个数组。
3数组在内存中的存放
数组在内存中式按照下标的顺序来一次存放的可以理解为线性存放,多维数组也是按照这个顺序存放
4 数组的初始化
数组的初始化可以在定义的时候用{}全部赋值 如int a[2]={1,2};如果把定义和赋值分开那就错了如:int a[2];a[2]={1,2};其中第二句本意是把1,2赋值给数组a,但是此时a[2]的意义变了,他不在代表一个数组,而是下标引用数组元素,即数组a的第1个元素。所以数组初始化的赋值方式只能在数组定义时使用,定义之后在赋值,只能一个元素一个元素的赋值
5 多维数组
多维数组简单的如二维数组,他其实可以看成一个特殊的一位数组,他的元素是一个一维数组,例如:int a[2][3]可以看做由2个一维数组a[0]和a[1]组成。
好了简单的了解了下数组的基本知识,现在我么开始讨论一些常见的问题
数组名是什么?等价于指针?
数组名是一个地址,而且是一个不可被修改的常量。这一点类似于符号常量,数组名这个符号代表了容纳数组的那块内存的首地址,注意:不是数组名的值是那块内存的首地址,数组名本身没有地址,只是代表了那块地址,他是一个右值,而指针是一个左值,所以数组名与指针不同。例如:int a[3];虽然数组名a没有地址,但是对其取地址&a,得到的依然是数组首地址。把数组名想象成一个符号常量即可,即数组名没有地址,只是代表了这个数组的首地址。又因为数组名是个地址常量所以不能修改它的值,即a++,a--是错误的!只有两种情况数组名不表示地址常量
即sizeof(a)和&a,前者表示数组的整个长度,后者是数组地址,但并不意味着a有地址,数组名是数组元素的首地址,不是数组的首地址
一维数组的寻址公式
如int a[6];
则a[n]的地址为(int)a+sizeof(int)*n(当然n<=6),其原理即:首地址+偏移地址其中a一定要做强制类型转换因为a是有类型的,必须把其转换成整数,sizeof(int)即是一个元素的大小
推而广之
对于TYPE array[m];
则array[n]的地址为:(int)array+sizeof(TYPE)*n(n<=m)//做逆向的时候很有用
二维数组的寻址公式
如:int a[4][5]
则a[2][3]的地址为:(int)a+sizeof(int[5])*2+sizeof(int)*3
| | |
| | |
数组首地址 行偏移 列偏移
即:先算确定在第几行,在确定在第几列最后加上首地址
或者第二种方式:
a[2][3]的地址为:(int)a+sizeof(int)*(2*5+3)
|
|
直接定位元素看跨越了多少个元素然后乘以他们的数据类型大小
因为数组a每一行有5个元素故2*5,3是目标行的元素偏移
故推而光之
对于TYPE array[x][y]二维数组
则array[m][n]的地址为:(int)a+sizeof(int[y])*m+sizeof(TYPE)*n
或array[m][n]的地址为:(int)a+sizeof(TYPE)*(m*y+n)
这些寻址公式在逆向过程中很有用,至少看到这样的寻址方式我们应该能想到是个数组,其实把数组在内存中的存放想成线性的就很好理解这些寻址方法。
实践一下:
int a[5]={0,1,2,3,4};
int b;
b=*((int)a+sizeof(int)*2);
问b是否能取到a[2]的值?
如果你说不能,恭喜你,你很理解地址与数值的概念,如果你说能,也不能怪你,下面我们就分析一下地址和数值的关系,由上面的讲述我们知道((int)a+sizeof(int)*2)这里得到的确确实实是a[2]的地址,然后对地址取内容理所当然取的是a[2]的值啊,呵呵,初学者可能都会这么想,但是那都是我们一厢情愿所造成的,其实((int)a+sizeof(int)*2)只是一个整数,不是地址的含义,没有任何指向意义,也就是a[2]的地址值等于((int)a+sizeof(int)*2),但是并不意味着((int)a+sizeof(int)*2)是地址,实际上地址本来就是一个基本数据类型,也就是说地址是有指向意义有类型这是地址和地址值的区别,如果没有类型,操作系统从这首地址取值根本不知道要取多少字节,
所以我们要加上强制类型转换,使他有类型有指向意义即若改成*(int*)((int)a+sizeof(int)*2)即可取到a[2]的值
4 数组名作函数参数
当数组名作函数参数时,传递的是数组首地址的一份拷贝,虽然数组名不可以自加,但是形参可以这里注意,因为形参是变量,存放的只是数组首地址的一份拷贝。
6指针
指针的定义:
通俗的讲:指针就是一个变量,他存放另一个变量的地址;特殊就特殊在它存放的是地址。
弄清关于指针的4个方面的内容:指针的类型 指针所指向的类型 指针的值 指针本身所占的内存区
如:
1指针的类型
把指针的名字去掉剩下的就是指针的类型:
int *p //指针的类型为 int*
char **p //指针的类型为char**
int (*p)[5] //指针得到类型为int(*)[5]
在32位平台下里所有指针类型的大小均为4字节
2 指针所指向的类型
当我们通过指针来访问指针所指向的内存区时,指针的所指向的类型决定了编译器将那块内存里的内容当做什么来看待
把指针的名字和指针声明符*去掉,剩下的即指针所指向的类型
int *p //指针所指向的类型为int
char **p //指针所指向的类型为int*即p指向一个指针
int (*p)[5] //指针所指向的类型为int()[5]
3指针的值
指针的值指针变量存放的地址值,他指向首地址为他的一片内存区,大小为sizeof(指针所指向的类型)
4 指针本身所占的内存区
因为指针也是一个变量,所以指针本身也占内存,在32平台下大小为4字节
运算符&和*
&a的结果是产生了一个指针,*p的结果是p所指向的东西
指针的运算
指针加减一个整型数据得到的还是一个指针,指针加减一个指针得到的是一个整型数据。
关于复杂的指针声明
指针数组:如 int *ptr[10],首先它是一个含有是个元素的数组,其次元素类型为int*,即元素为指针,
数组指针:int (*p)[10],首先他是一个指针,其次他指向一个int[10]的数组
如果实在弄混的话就想想牛奶和奶牛的区别,牛奶是奶,而奶牛是牛,即中国人说话主语都在后面,说堆栈,其实说的是栈.
函数指针:
int (*p) (int x,int y)因为指针指向函数而函数名的类型为int(int x,int y);所以定义的时候相当于 int (int x,int y) (*p)
如果把*p两边的括号去掉则int *p(int x,int y)表示函数p返回值是个指针
9 二维数组与指针
int ptr[2][3]={1,2,3,4,5,6};
关于二维数组主要就是要搞清楚 数组的指针 某一行的指针 某一行某一列的指针之间的区别
单纯的数组名ptr则是数组第0行的指针 她指向第0行,同理ptr+1指向第1行,这两个均为行指针,问题来了 *(ptr+1)可能有很多人认为它的值为4,即ptr[1][0]的值,因为ptr+1是第1行的首地址,所以对其取内容应该为ptr[1][0]的值,实际上是错的因为 ptr+1是一个行指针 而我们要取元素的话要使用列指针,所以要做个强制类型转换 即*(ptr+1)为列指针其值与ptr+1的值相等但意义不同如果不理解请接着看
例如:int a[i]
这样数组名加下标的形式,被编译器理解为
*(a+i)
同理,a[i][j]被理解为:
*(a[i] + j)
= *(*(a + i) + j)
a+1表示a[1]的地址,a[1]表示a[1][0]的地址,尽管他们的值是一样的!
其实数组与指针并不难,只要平时多练习多用久而久之就会了。笔记就到这里了写的比较乱,希望对初学者有所帮助,有不对的地方请指正