指针与数组
1.数组的概念
数组是一个集合,该集合由若干个想同类型的普通变量组成,当用户需要再工作中同时声明多个相同类型的变量的时候,就可以使用数组,数组的内存空间是连续的。
2.数组的定义
元素数据类型 数组名字[元素个数]
元素数据类型:基本数据类型(char/short/int..)、数组、指针、结构体。
数组名字 -> 与定义C语言变量规则一致。
元素的个数 -> 给一个确定的个数,例如: 10、100、200..
例如: 定义一个具有100个int类型变量的数组,int A[100]
3.从内存的角度分析数组
int A[3];//在内存中连续申请12个字节的空间,用变量A间接去访问这片内存空间。
4.数组的赋值
1)定义的同时初始化
int A[3]={1,2,3};
int A[3]={1,2};//没有赋值的元素系统会自动赋值0
int A[]={1,2,3};//可以不设置数组元素个数,系统会自动确定个数
int A[3]={1,2,3,4};//错误,元素个数超过预定的个数了
2)先定义,不初始化
全局变量 int A[3];//全部的元素都是0
局部变量 int A[3];//全部的元素都是随机值
3)先定义,后初始化
int A[];A={1,2,3};//错误,数组只有在定义的时候可以整体初始化,之后只能一个一个进行赋值
5.数组的下标
int A[3];//在内存中连续申请12个字节的空间,用变量A间接访问这片空间,在访问数组的时候,使用数组的下标来对数组的成员进行访问。
int A[n];//下标的范围 0~n-1,最后一个元素是n-1
6.数组名的含义
1)当sizeof()作用于数组名的时候,数组名就是整个数组的内存空间。
int A[3];
sizeof(A) 12
2)当sizeof()不作用于数组名时,数组名就是数组首元素的地址。
int A[3];
数组名 A
数组首元素 A[0]
数组首元素的地址 &A[0]
结论:A = &A[0]
7.指针的定义:指针指的是内存上的地址,例如:0x40000,指针是一个唯一的地址能够确定申请到的内存空间在哪里。
指针变量:专门用于存放地址的变量,例如:*p
例子:int a;//在内存中申请四个字节的空间,用变量a间接去访问这篇内存空间
&a;//获取变量a所指向的内存空间的地址
8.究竟&a获取到的这个地址,存放在哪里?
100 -> 整型数据 -> 存放整型变量 int a; -> a = 100(把100赋值给a变量)
0xbf9ecbac -> 指针 -> 存放指针变量 int *p -> p = 0xbf9ecbac(把0xbf9ecbac地址赋值给p变量)
9.指针定义的步骤
1)先写一个*
2)在*后面写一个指针变量名(*p)
3)确定该指针所指向的内容 例如:int a
4)将第三部的变量名去掉 例:int
5)将第二部的结果放在第四部的结果后面 例:int *p,即指向一个int类型数据的指针变量
结果:int *p //指针变量
变量名:p
数据类型:int *,指针变量的数据类型与该指针所指向的数据的类型一致
例子:int a=100;
int *p = &a;//将a的地址取出来给指针p
10.已知指针变量的值,如何求出该地址指向的内容?
取址符: 已知变量a,求地址 &a
解引用: 已知指针p,求变量 *p
* 与 &是一对逆运算。
11.指针变量所占的空间有多大?
在32位linux系统中,地址的长度都是4个字节,即所有指针变量都是占四个字节的空间
12.野指针与空指针
定义了一个指针变量之后,但是没有给该变量赋值,系统就会赋一个随机的值给它,这个指针变量就成了野指针。
int *p;//野指针
13.如何解决野指针的问题?
1)在定义的同时初始化
int a=100;
int *p=&a;
2)让野指针指向空指针
空指针:NULL,空指针实际上是一个宏,真正的NULL等于0
int *p = NULL;
3)让野指针指向安全的内存空间
在内存中有一段内存是属于安全区域内存空间,地址范围是"0x00000000~0x08048000",只要指针指向该范围,那么用户就不能通过指针
去访问指针里面的东西。
14.通用类型指针 void *
1)void *不是某一种特定的数据类型的指针,而是一种通用数据类型指针,所有类型的指针变量都可以赋值给void *类型的变量。
例子:int a;
int *p = &a;
void *pb = p;//这里p指针就指向未知的数据类型
char *px = pb;//将void *类型的指针的值赋给char *类型的指针,实际上就是强制类型转换
在实际应用中,很少用void *类型的指针作为中介转换指针类型,都是直接强制转换
int a;
int *pa = &a;
char *pb = (char *)pa;
2)void *类型指针变量的解引用?
由于并没有void类型的变量,所以不能对void *类型的指针进行解引用,只有将其类型转换之后才能解引用。
3)void *类型指针开发的意义:
这是在回调函数中的经典用法,1.增强函数参数接口的兼容性,2.为了返回一个整体的大资源,而该资源的类型是不确定的。
15.指针的运算
1)指针的加法
int a;
int *pa = &a;
pa+1 = ?
//+指的是向上移动,-指的是向下移动,“1”指的是移动一个单位,由于pa指向的是int类型的数据,所以每个单位占4个字节,pa+1指的就
是pa向上移动一个单位,4个字节,移动的字节数是根据指向的数据类型确定的,char型就是两个字节。
2)指针的减法
int a,c;
int *pa = &a;
int *pb = &c;
pa - pc = ?
//这是可以编译通过的,这个结果就是两个指针在内存中的地址相差多少个int型大小的字节.
16.数组的运算
数组的名字就是数组首元素的地址,一旦空间申请下来之后,就不允许修改数组首元素的地址,即改地址固定,也就是说数组名是一个
常量。
int A[5];
A 数组首元素的地址
A+1 将首元素的地址往上移动一个单位,首元素是int类型,即移动四个字节。
*(A+0) 等价于A[0]
*(A+1) 等价于A[1],解引用首元素的地址再往上移动一个单位,得到第二个元素
结论:数组的下标实际上就是数组首元素往上移动的单位个数。A[n] = *(A+n)
常见难题:
int a[5] = {1,2,3,4,5};
int *p = a;
*p++;//先取指针p指向的值(数组的首元素,即1),再将该值+1,变成2。
(*p)++;//先取指针p指向值(数组的首元素,1),再将该值自增1,即数组的第一个元素变成2。
*++p;//先将指针p自增1(此时指向数组的第二个元素),再解引用,将指向的值取出来,即2。
++*p;//先取指针p指向的值(数组的第一个元素,1),再将该值+1,即第一个元素的值变成2。
17.复杂指针
简单指针:一般指向基本的数据类型,int/char之类的。
复杂指针:一般指向非基本的数据类型,例如:函数,结构体,数组,指针。
1)二级指针:指向指针的指针,还有三级指针,四级...之类的(二级之后的都不常用)。
int a = 100;//在内存中连续申请4个字节的空间,使用变量a间接访问这片空间,将常量区100赋值给a。
int *pa = &a;//在内存中连续申请四个字节的空间,使用变量pa间接访问这片空间,将整型变量a的地址赋值给pa,即pa所指向的内存地址中存储的内容是变量a 的地址。
int **px = &pa;//在内存中连续申请4个字节的空间,使用变量px间接访问这片内存空间,将指针变量pa的地址赋值给px,即px所指向的内存空间存储的内容是指针pa的地址。
2)数组指针:指向数组的指针。
数组指针的定义方法与普通指针一样。
int a; int A[3];
int *pa = &a; int (*pa)[3] = A[3];//表示这个指针指向一个具有三个int型数据的数组
&A 与A 的区别:&A指向整个数组的基地址,而A指向数组首元素的地址。
int(*pa)=A;//这是错误的,因为pa的类型是int *,并不是int(*)[3]。
解引用:
int a=100;int *pa = &a;*pa得到100,int A[3] = {100,200,300}; int (*p)[3] = &A;
问题一:*p得到什么?得到数组首元素的地址,*p = *(&A) = A = &A[0]
问题二:p[0]/p[1]得到什么?什么也得不到,p[0] = *(p+0) = *p = &A[0],p[1] = *(p+1) = *(&A+1);//此时p+1已经越界访问了,解引用就是访问未知区域了。
问题三:(*p)[1]得到设么?(*p)[1] = (*&A)[1] = A[1] = 200
做指针运算的时候,要记住两个公式:
1. * 与 &是一对逆运算,所以可以约掉
2. a[n] = *(a+n)
3)函数指针:指向一个函数的指针
定义函数指针:
int a; int fun(int x,int y)
int *pa = &a; int (*p)(int,int) = &fun;//这个指针指向一个返回值为int类型,形式参数有两个int型数据的函数
数据类型:int(*)(int,int) 变量名:p
在C语言中,函数的名字实际上就是函数的地址,int fun(int x,int y);函数名fun等价于&fun,即int(*)(int,int) = &fun;
结论:函数指针很少单独使用(定义一个指针,通过指针来调用这个函数),一般的,函数指针都是用于接收函数的地址,即传递一个函数给另外一个函数作为参数,形式参数就是一个函数指针。
18.指针的用法
1)在函数调用的时候改变某个变量的值
int i;func(&i);//所有需要改变某个变量的值的函数都需要传递该变量或者内存的地址。
经典例子,二级指针,int *p;func(&p);//这里如果你想要的改变p的值,就必须传输p的地址,而p本身就是个指针,这就有了二级指针的概念,二级指针只会出现在函数的形式参数列表中,一般不会随意
的定义二级指针。
2)访问匿名内存的时候
在C语言中,由于立即寻址空间访问硬件以及资源的原理,很多资源只是有具体的内存地址,却没有变量名字(能够用名字访问内存的只有全局变量或者局部变量(函数名也是一个地址)),因此我
们经常直接引用地址。
例子:某GPIO数据寄存器的内存地址是0x12345678,我们会将该地址转化为指针类型,然后直接解引用改地址。
#define GPIO_DATA *((volatile unsigned int*)0x12345678
3)优化加速函数调用
19.字符指针与字符数组
例子:
char *string = "hello";//将hello这个字符串常量的地址赋值给string这个指针变量
printf("string=%p, hello=%p\n", string, "hello");//两个地址是一样的
char array[] = {'h','e', 'l', 'l', 'o', '\0'};//数组原本初始化的过程,因为太麻烦,C语言给字符数组给了点方便
char array[] = "hello";//特殊的操作方式,在声明数组的过程中,将"hello"里面的数据拷贝到数组array中,注意,只能是声明的过程才能这么做
printf("array=%p, hello=%p\n", array, "hello");//地址是不一样的,因为只是将"hello"的数据拷贝给array,array本身是有内存的
20.指针与数组的异同
1)数组名不能给其赋值(指针常量)
2)sizeof用来测量数组名,得到的是整个数组的大小,测量指针的话,永远都是系统位数那么大
3)在我们的函数传参过程中,函数的形式参数就是一个指针
4)数组是不能够在函数中返回出去的(语法不会报错,只是没有意义,会被释放掉)
PS:第一次发布总结博客,如果哪里有错误请指正,0_0