1. 指针是什么
内存是什么?
内存是电脑上特别重要的存储器,计算机中的程序的运行都是在内存中进行的,为了有效使用内存,我们会把内存划分为一个个小的内存单元,比如把4G/8G/16G/32G的内存划分为一个个以字节为单位的空间,一个内存单元的大小是一个字节,每个字节(内存单元)都会有一个编号,这个编号就是地址,即编号==地址==指针。
指针就是地址,在我们日常的口语中,指针即为指针变量。
指针变量:存放在指针中的任何值都被当做指针变量
指针变量是用来存放地址的,地址是唯一一个标识内存单元的。
int main() {int a = 100;int * pa = &a;*pa = 0;return 0; }
&a:
&取地址符号,取出a的地址(a是int类型,占4个字节)
int * pa:
pa,指针变量,是专门用来存放地址(指针)的
*,表示pa是指针变量
int,pa指向的a的类型是int
int*,pa这个指针变量的类型
a 是int类型,占用四个字节的空间,将a四个字节的第一个字节的地址存放在pa变量中
指针的大小:在32位平台是4个字节,在64位平台是8个字节。
2. 指针和指针类型
指针定义方式:type+*
type* p;
type是p指向的对象的类型
*说明p是指针变量
p解引用访问的大小是sizeof(type)
char* short* int* long* float* double*
无论是什么类型的指针,大小都一样
//无论什么类型的指针,大小都是一样的 int main() {printf("%d\n", sizeof(char*));//1printf("%d\n", sizeof(short *));//2printf("%d\n", sizeof(int *));//4printf("%d\n", sizeof(long*));//printf("%d\n", sizeof(float *));//4printf("%d\n", sizeof(double*));//8return 0; }
我们可以通过sizeof观察一下
指针类型的意义:
char*类型的指针是为了存放char变量的地址
short*类型的指针是为了存放short变量的地址
int*类型的指针是为了存放int变量的地址
......
指针类型可以决定指针解引用的时候访问多少字节(指针的权限)。
我们以如下代码为例,一个是int*一个是char*:
int main() {int a = 0x11223344;int * pa = &a;*pa = 0;return 0; }
int main() {int a = 0x11223344;char* pa = &a;*pa = 0;return 0; }
我们可以观察到,当pa类型为int*时,访问了四个字节
我们同样也可以观察到,当pa类型为char*时,访问了一个字节
指针类型也会决定指针+1操作(指针运算)的步长。
我们借助如下代码来研究:
//决定了+1操作的步长 //+/-n 决定了加减n*sizeof(type)个字节 int main() {int a = 10;int* pa = &a;char* pc = &a;printf("%p\n", pa);printf("%p\n", pc);printf("%p\n", pa+1);printf("%p\n", pc+1);return 0; }
观察结果我们可以发现,前两个打印出来的结果是一样的,后两个一个是加4以后的结果,一个是加1以后的结果。由此,指针类型也会决定指针+1操作(指针运算)的步长。
3. 野指针
定义:野指针指向的定义和位置是不可知的(随机的,不正确的,没有明确限制的),很危险
成因:
1.指针未初始化
2.指针越界访问(当指针指向范围超出数组范围,p就是野指针。比如在循环中或者函数的返回值)
例子如下:
int main() {int* p;*p = 20;printf("%d\n", *p);return 0; }
在此段代码中,局部变量p没有进行初始化时,内容是随机的
这里面p就是一个野指针
如何规避野指针?
1.指针初始化(明确知道指针应当初始化的地址,不知道就暂时置NULL(意思是0))
2.小心指针越界
3.指针指向的空间释放时,及时置NULL
4.避免返回局部变量的地址
如下段代码是一个野指针的案例,程序仍可正常运行,但会出现警告:
int* test() {int a = 110;return &a; }int main() {int* p = test();printf("%d\n", *p);return 0; }
在test()函数中,return &a;在main()函数中,p就是一个野指针。a的空间进入函数test创建,出函数test()则销毁。
5.指针使用之前检查指针的有效性
int main() {int* p; //野指针int* ptr = NULL;//置NULLreturn 0; }
如上所示,p时野指针,可以类比为一条会乱咬人的野狗,当置NULL以后,相当于给野狗身上拴上绳子
ptr是一个空指针,不能直接使用,没有指向任何有效空间
4. 指针运算
4.1指针+/-整数
不使用下标进行对数组的访问
//指针+/-整数 int main() {int arr[10] = { 0 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 1; i <= sz; i++){*p = i;p++;}p = arr;for (i = 0; i < sz; i++){printf("%d ", *(p + i));}return 0; }
在这段代码中,第一个for循环进行对于数组arr的初始化,p刚开始指向数组的首元素地址,*p对p进行解引用,修改p的值为i,则数组中第一个元素为1,然后依次进行循环,最终数组结果为{1,2,3,4,5,6,7,8,9,10}。在第一个for循环中,对于p进行修改,p++以后,p的值进行更改,增加四个字节的地址(p是int*类型),*p进而指向数组中的第2个元素。
第二个for循环中,是对赋值后的数组进行打印。p = arr;中,数组名代表数组首元素的地址,在此过程中,*(p + i)并未像第一个for循环对于p值进行修改,而是进行了类似于p+1,p+2...的操作进行对数组一个个的访问。
代码运行结果如下:
在这里,我们需要注意的一点是,[ ]仅仅只是一个操作符,i和arr是它的操作数
即arr[i]===*(arr+i)==*(i+arr)==i[arr]
4.2指针-指针(地址-地址)
指针-指针得到的数值的绝对值是它们之间的元素个数
前提条件: 指针1和指针2指向了同一块空间,否则是无意义的
//指针和指针相减的前提条件是二者指向了同一块空间 int main() {int arr[10] = { 0 };char ch[5] = { 0 };printf("%d\n", &arr[4] - &arr[0]);printf("%d\n", &arr[0] - &arr[4]);printf("%d\n", &ch[0] - &arr[4]);return 0; }
运行结果:
4.3指针的关系运算
指针是有大小的,指针的关系运算就是比较指针的大小
//倒着初始化数组内容 #define N_VALUES 5 int main() {float values[N_VALUES];//浮点数数组valuesfloat* vp;for (vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0; }
标准规定:允许指向数组元素的指针与指向数组的最后一个元素后面的那个内存位置的指针进行比较,但是不允许与第一个元素之间的那个内存位置的指针进行比较。
即为P可以和P2进行比较但不能和P1进行比较
5. 指针和数组
指针变量就是指针变量,不是数组
指针变量的大小是4/8个字节,专门用来存放地址
数组就是数组,不是指针,数组是一块连续的空间,可存放1个或者多个类型相同的数据。
联系:
数组中,数组名其实是数组首元素的地址,数组名==地址==指针
数组是通过指针来访问的
当我们知道数组首元素的地址的时候,因为数组是连续存放的,所以通过指针可以遍历数组。
6. 二级指针
二级指针变量用来存放一级指针变量地址
指针变量也是指针,是变量就有地址,指针变量的地址存在二级指针中~
int**pp;
int*:pp指向的类型
*(第二个):说明pp是指针变量
int main() {int a = 10;int* pa = &a;int** p = &pa;printf("%p\n", pa);printf("%p\n", p);return 0; }
理解方式如下:
7. 指针数组
存放指针(地址)的数组,数组的每个元素都是指针
int main() {char arr1[] = "abcdef";char arr2[] = "hello world";char arr3[] = "cuihua";//指针数组char* parr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){printf("%s\n", parr[i]);}printf("%p\n", parr[1]);return 0; }
在上段代码中,parr就是指针数组,一个数组里面存放了3个指针。
数组名是数组首元素地址。
运行结果: