目录
1.指针
在C语言中有很多不同的数据类型,int、char等等,指针也是一种数据类型,也是变量,int存储整型,char存储字符类型,而指针专门存储一个数据在内存中的地址,需要完全理解指针,必须了解内存地址的概念,C语言编程最重要的是对内存的理解。
以下假设的是虚拟内存,进程的内存布局不做详细介绍了
//不同系统,指针的大小不同(在内存中占的字节数),32位系统下占4字节,大小与指针本身类型,所指向的数据,没有任何关系的!!!
//假设存在以下代码 (可以参照图看1.1)
void main(void)
{
unsigned char a = 55; //向系统申请了一块uchar类型大小的内存,内存名为a,内存中存放值55
short int b = 23; //申请了2字节大小的内存,内存名为b,小端序存放(参考博主另一篇文章),低地址字节存放值23,高地址字节存放0
short int *p = &b; //申请了一块4字节大小的内存,内存中存放short int 类型数据的地址,并且是其基地址
while(1);
}
假设其内存图如下:
- 在内存中,每一字节的内存都有其专属地址,如上图所示,运行时这些变量会有自己专属的位置;
- 指针指向的是一大块地址,其值只是这部分地址的基地址,即最低的地址数;
- 指针指向的是某个数据的基地址。
1.1指针相关标识符
1.1.1 取地址符号 &
初始化时:
int a = 100;
int *p = &a; //或者int *p = NULL; p = &a;
&为取地址的符号,上述两种初始化的理解:
int *p = &a; //申请了一块内存,存放int类型数据的地址,这块内存(变量名)名为p,内存中的值为&a
int *p = NULL; //定义了一个int 类型的指针,先指向NULL位置,或者说先存放0x0000 0000这个地址
p = &a; //将a的地址赋给p
1.1.2 解引用符号 *
只有在定义指针时,* 符号不代表解引用,只代表指针的标识,让编译器明白此变量是个指针
例如:int a = 100;
int *p; //此时 * 只是使p代表指针
p = &a;
*p = 20; //取得该地址所指向内存的使用权,并将该内存的数值置为20
即间接访问内存,通过地址访问内存,操作里面的数据,此过程为解引用。
1.1.3 const 符号
需要了解变量和常量 — 可读可写和只读
const与指针:分清楚 常量指针(指向常量的指针) 和 指针常量(此指针是个常量不可改变他存储的地址)
int i = -1;
1.const int ic = i; 合法,定义了一个常量
2.const int *pic = 合法,定义了一个指针指向一个常量 int const *p = const int *p
3.int *const cpi = 非法,定义了一个 指针常量 指向一个变量,而ic是常量
4.const int *const cpic = 合法,定义了一个常量指针,指向一个常量
1.1.4 算数运算(偏移概念)
指针算术运算(指针偏移量)
指针支持算术加减: 加-向后偏移,减-向前偏移
偏移量,根据指针本身的类型而定—> int *的指针+1之后,移动了一个int(4字节)
不支持直接的算术乘除(除非强转了)。
如: 都是访问数组的第二个元素
float a[3] = {1.1,1.2,5.5};
1. float mya = *(a+1); 2. float *p = a; float mya2 = *(p+1); 3. a[1]
//在下面配合数组讲更清晰
1.2野指针
野指针:一个指针所指向的地址不确定,此时对它进行赋值会产生不可估计的错误。
一般造成的错误为段错误,即操作了非法数据,非法数据是没有向系统申请的或者是丧失使用权的数据,合法例如int a; 此数据是申请了合法内存的,可以随便使用。
非法数据,例如:
- int *p; //只定义不初始化:是个野指针,所存的地址是乱码(随机的),必须初始化,如果直接解引用赋值,操作了个随机地址的数据。
- p=(int *)malloc(100); free§; //此时只是释放了内存,使这块内存不能使用,再使用就是非法的,会造成程序错误
解决:#define NULL ((void *)0x00 ) free§; p = NULL;
1.3多级指针
既然指针是个数据类型,存储在内存中,那么指针也会有一个专属地址。
二级指针:
存储指针地址的指针
int a = 10;
int *p = &a;
int *(*pp) = &p; //申请了一块4字节内存,存储int * 类型的数据的地址(即指向int类型的指针),内存名字为pp,或者说变量名为pp。
//=======================
当然还存在3、4、5、6级指针,也基本用不上
1.4复杂指针
//指针指向函数,数组等
数组指针:指向数组的指针 运算符优先级,数组指针和指针数组
int (*p)[3] //指针:指向 int [3] 的数组类型(如int arr[3])
指针数组:存储一系列指针的数组
int *arr[3] //数组:存储了3个 int *类型的指针
(如字符串数组:char *str[3] = {"hello", "world", "hahaha"};)
函数指针:指向函数的指针
int func_max(int a, int b);
int (*p1)(int, int) = func_max;
printf("%d\n", p1(a, b));
指针函数:返回值为指针的函数
int *func_test(int a, int b) //函数:形参为 int,int。返回值为 int *
2.数组
数组: 一整块连续的内存,存储了具体长度的相同类型数据。(变量的集合)
任何数组名都被视为一个指向其首元素的指针 — 指针常量特性
//数组的定义
int arr[3];
1.int 元素类型(里面所有元素的数据类型)2.arr 数组名(数组名就是首元素地址)属于指针常量
3.[3] 数组长度(数组中有3个相同的int类型数据)
(数组类型: int [3])
//如:int a[2] [3] 定义了一个数组,有两个元素,每个元素存放的都是int [3]类型的数据
定义的解释:
通俗解释:定义了一个3个int的数组
内存角度:向系统申请一块连续内存,规定了内存中每个类型为int整型,长度为3。
//使用数组中的元素:arr[0] 使用数组中第1个元素(下标范围:0 ~ 2)
2.1数组与指针(重点)
任何数组名都被视为一个指向其首元素的指针,首元素为即为数组中第一个元素:
int a[3] = {0,1,2}; //首元素为 a[0] = a[0] = *a = 0;
int a[3][2] ={6,7,2,8,3,7,} //首元素为a[0],此a[0]代表的是第一个数组,如下图中的小红圈,*a[0] = a[0][0] = 6;
在此处首元素可以从内存图中看出。
首先了解二维数组:
在C语言中,其实是不存在二维数组这种数据类型的!是为了便于理解交流的一种说法,它在本质上也是一维数组,在内存中也是以一维数组形式存在。
例如: int arr[3][2]
内存的角度:向系统申请一个连续的内存,长度为3个元素,每个元素为 int [2]类型。数组名会和最近的一个[ ]结合。
如上图所示:
int arr[3][2] = {6,7,2,8,3,7};
int (*p)[2] = arr; //定义一个指针指向int [2]类型的数据。
int ***p = &arr; //代表整个数组的地址.
- arr :代表首元素的地址,其首元素是上图中的小红圈,所以对其+1,指向的是数字2那个位置。
- arr[0]:代表第一个int [2]类型的首元素的地址,即数字6的地址,*a[0] = a[0][0], 对其+1,指向的是数字7. ---- arr[1],指向下一个int[2]数组.
- arr[0][0]:代表首元素,就是数字6.
- &arr:代表的是整个二维数组的地址,不能对其地址偏移载操作,可能出现段错误
1.数组名也可以当成指针进行运算。
2.指针也可以使用数组下标的形式,来对数组元素进行访问。
3.如指针的下标为负数,则表示指针向左偏移。
4.数组名不能进行自加操作。
5.如果只想对地址进行数值上的算术加减
必须对地址进行强制类型转换,转换为数值形式,才能进行算术加减。
printf("%p \n",arr); printf("%lx \n",(unsigned int)arr );
2.2复杂数组
2.2.1变长数组
int i = 3;
int a[i]; //这一项在传统C语言中不可初始化,在C99标准和C++中是合法使用的,之前的要求是数组大小必须为常量。
2.2.2柔性数组(零长数组)
int a[0];
柔性数组既数组大小待定的数组,C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度.
它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题用法
在一个结构体的最后,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量,但对于这个数组的大小,我们可以进行动态分配malloc
//============ 例子 //同样在C99标准和C++中合法使用
struct ZeroArrTest
{
int a;
int b;
int test[0]; //不占内存大小
};
// 此时的 8 这个位置,可以方便的自加
struct ZeroArrTest *p = (struct ZeroArrTest *)malloc(sizeof(struct ZeroArrTest) + 8);
p->a = 1;
p->b = 2;
p->test[0] = 3;
p->test[1] = 4; //在malloc时,后面的8 为两个int类型数据,一般用sizeof(int)*2
int nub = *(&p->b + 1); //nub = 3;
sizeof(struct ZeroArrTest); //8