一、指针
1.1概念
在计算机中,所有的数据都是存放在存储器中的,不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址。比如:int 类型占 4 个字节,char 类型占 1 个字节等。系统在内存中,为变量分配存储空间的首个字节单元的地址,称之为该变量的地址。地址用来标识每一个存储单元,方便用户对存储单元中的数据进行正确的访问。在高级语言中地址形象地称为指针。
1.1.1指针就是个变量,用来存放地址,地址唯一表示一块内存空间。
内存编号 = 地址 = 指针
1.1.2指针的大小是固定的4/8个字节(32位平台/64位平台)
1.2指针变量及其定义
指针变量是存放一个内存地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量。定义指针变量的一般形式为:类型说明符*变量名。
类型说明符表示指针变量所指向变量的数据类型;*表示这是一个指针变量;变量名表示定义的指针变量名,其值是一个地址,例如:char*p1;表示 p1 是一个指针变量,它的值是某个字符变量的地址。
#include <stdio.h>
//打印的变量(i或者p)的地址可能每次都不一样
int main(int argc,char** argv)
{
int i = 0x12345678;
int* p = &i;
printf("0x%08x \n", i); //打印结果: 0x12345678
printf("0x%08x \n", &i); //打印结果: 0x004ffed0
printf("%d \n", sizeof(i)); //打印结果: 4
printf("0x%08x \n", p); //打印结果: 0x004ffed0
printf("0x%08x \n", &p); //打印结果: 0x004ffecc
printf("%d \n", sizeof(p)); //打印结果: 4
// 指针变量p指向的对象是变量i
// 1.指向的对象变量i的地址是&i(0x004ffed0)
// 2.指向的对象变量i的类型是int
// 3.指向的对象变量i的内容是0x12345678
// 4.指针变量p本身的地址是&p(0x004ffecc)
// 5.指针变量p本身的类型是int*
// 6.指针变量p本身的内容是&i(0x004ffed0)
return 0;
}
1.3指针类型
1.3.1、int* p[10]
表示一个包含10个整型指针的数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
1.3.2、int (*p)[10]
表示一个指向有10个整型数数组的指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
1.3.3、int *p(int)
是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
1.3.4、int (*p)(int)
函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
1.3.5、指针的类型决定了指针+-整数的步长,指针解引用时候的权限。
int main()
{
int a = 4;
int* p1 = &a;
char* p2 = &a;
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p1 + 1);
printf("%p\n", p2 + 1);
return 0;
}
结果:
解释
最开始p1和p2地址是一样的,但后面让p1和p2分别进行+1,后面的结果就不同了,p1加的1是int类型的1,而p2+1加的是char类型的1。上面我们说到指针的大小是固定的4/8个字节,假设是32位平台,那么一个指针就占4个字节。如果这时我定义一个整型指针和字符指针,那么这个整型指针在解引用时就可以访问4个字节,而字符指针就只能访问1个字节。
1.4野指针
1.4.1概念:指针的位置是不可知的
1.4.2成因:1.指针未被初始化 2.指针的越界访问
1.4.2.1未被初始化:就是我们在创建指针变量的时候没有让它指向任何对象
例如: int* p; 这样p就是一个局部变量,p就是一个随机值
int main()
{
int a;
int* p;
*p = 20;
return 0;
}
1.4.2.2指针的越界访问
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
int i = 0;
for (i = 0; i < 6; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
int* p = arr; 这里arr是数组名,数组名是首元素的地址
那么现在p就是首元素的地址 对p进行解引用就是*p ,*p的值就是 1
p++; 这行代码就是让p的地址++;指针的大小是固定的4/8个字节, int型数据在C语言中也是4/8个字节,我们拿到的指针都是数据第一个字节的地址,而数组在内存中又是连续的,p++就是刚好往后移动一个数据。
但是现在arr数组一共就只有5个元素,但是循环6次必然会导致数组的越界
前面5个数就是arr数组里面的数,第6个值就是一个随机值。因为当循环到第6次时,p已经没有指向的对象了,此时p就是一个野指针了。
1.4.3如何避免野指针
1.善于使用NULL,及时对指针进行初始化
如果你在定义指针变量的时候,就已经想到指针变量指向的对象,那就直接进行初始化。
如果你在定义的时候,还不清楚指针指向的对象,也不清楚后面要不要使用指针,那就对指针变量赋值为NULL
NULL就是空的意思,如果int *p=NULL; 那么此时p就是一个空指针,后面可以重新赋值,并不影响后面的使用。如果一个指针是空指针,在你还没初始化前不要使用它。
2.避免指针的越界
3.避免返回局部变量的地址
1.5、指针运算
int main()
{
//指针地址加减整数
int arr[5] = { 1,2,3,4,5 };
int* p1 = arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *p1);
p1++;
}
printf("\n");
//解引用后的指针加减整数
int b = 10;
int* p2 = &b;
(*p2)++;
printf("%d", *p2);
return 0;
}
结果
p1++是对地址进行加减整数。
而(*p2)++, 是定义了一个b变量,然后赋值给了10,然后把b的地址给了p2,*p2通过解引用得到的就是10,(*p)++ 相当于 10++ ,得到的就是11。
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%d", &arr[4] - &arr[0]);
return 0;
}
没用指针变量相减,其实是一样的,毕竟指针就是地址。两个指针指向同一块空间时,指针减指针的绝对值得到的就是这两个指针之间数据的个数。
1.6指针和数组
1.6.1.数组是可以通过指针来访问的。
1.6.2.通常情况下 数组名是首元素的地址
例外:
1.sizeof(数组名) 得到的是整个数组的大小
2.&+数组名 这里取出的是整个数组的地址。
1.6.3.在进行函数传参时,如果形参是数组,可以把形参设计成指针,当然如果形参是数组,也可以传指针作为实参。
1.7二级指针
二级指针就是用来存放一级指针(指针变量)的地址。
int main()
{
int a = 5;
int* pa = &a;
int** ppa = &pa;
printf("%d\n", **ppa);
** ppa = 20;
printf("%d\n", **ppa);
printf("%d\n", a);
return 0;
}
结果
此时pa是一级指针,ppa就是二级指针,ppa是把pa的地址取出来放在ppa里面
**ppa就是*pa找到pa,在对pa进行解引用找到a
1.8、指针数组
指针数组的定义:int* 数组名[大小]
int main()
{
int arr1[3] = { 1,2,3 };
int arr2[3] = { 4,5,6, };
int* arr3[2] = { arr1,arr2 };
int i = 0;
int j = 0;
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", *(arr3[i] + j));
}
printf("\n");
}
}
结果