指针内涵指针的作用指针通常有两种常见的用途,第一种是给变量起个别名,用来指代某个变量;第二种是操纵动态的内存空间,根据需要进行扩大或者缩小。
12345int i = 10;int *p = &i;*p = 8;printf("i: %d, *p:%d", i, *p);// i: 8, *p:8
修改了*p的同时,i的值也被修改了,可以操作指针修改指针指向的变量,要注意的是赋予指针的值一定是个地址。在C的函数中,如果你想交换入参的值,就不得不传入指针。
12345678910111213void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp;}int a = 1, b = 2;printf("a:%d,b:%d\n",a,b);swap(&a,&b);printf("a:%d,b:%d\n",a,b);// a:1,b:2// a:2,b:1
指针的作用就是指向内存当中某个地址的值,并且可以对该值进行修改。与一般变量存在本质差别的是,指针存储的是一个地址值,而别的一般变量存储的是特定类型的值。
第一种用途的指针只要注意解引用符号基本不会出现太多的问题,因为不涉及到堆内存的空间分配。动态分配则不同,需要主动释放空间,下文会细说,这里暂且不表。
指针的类型123456789int *p1;float *p2;double *p3;printf("sizeof p1 %d\n", sizeof(p1));printf("sizeof p2 %d\n", sizeof(p2));printf("sizeof p3 %d\n", sizeof(p3));// sizeof p1 8// sizeof p2 8// sizeof p3 8
这里我为了方便而没有给指针赋初值,具有一定的风险
指针的类型并不决定指针的大小,一般来说它的大小随着操作系统固定的。指针的类型是为了标识指针所指向的区域存储的值类型,当你对指针进行加法操作的时候,类型的差别就开始显露了。
12345678910111213141516171819202122int *a = NULL;int arr[] = {1, 2, 3};a = arr;double *b = NULL;double arrf[] = {1.1, 1.2, 1.3};b = arrf;printf("%p\n", a);printf("%p\n", a + 1);printf("%p\n", a + 2);printf("%p\n", b);printf("%p\n", b + 1);printf("%p\n", b + 2);// 000000000061FE04// 000000000061FE08// 000000000061FE0C// 000000000061FDE0// 000000000061FDE8// 000000000061FDF0
看来编译器会根据指针的类型去计算对应地址的起始位置,当出现加减操作时,根据指针的类型大小进行计算
万能指针既然指针的类型并不会对指针产生本质的影响。你也可以定义指针的类型为void,这样的指针叫做万能指针,它可以指向任意类型的值,不过在用之前记住指向区域的值类型,不然真的很容易出错。
1234void *p = (void *)malloc(sizeof(int));*(int *)p = 12;printf("%d %p", *(int *)p, p);// 12 00000000001D1400
这里给p分配了int类型大小的空间,然后初始化指向值。每次在解引用指针时需要进行强制转化,不然无法编译通过。
数组和指针数组是内存中连续的相同类型值的集合,数组的标志是[],这个在别的语言里面似乎也是这样,好像是约定俗成的。之所以会把数组和指针搞混,可能是因为数组可以和指针进行相似的操作,下面两种访问数组的结果是一致的。
123456int arr[] = {1, 2, 3};printf("%d\n",*(arr + 0));printf("%d\n",*(arr + 1));printf("%d\n",arr[0]);printf("%d\n",arr[1]);
可以总结为,a[n] = *(a + n) 这对指针也适用。而且指针不止可以指向单个值,还可以指向连续的多个值,并且可以动态地进行扩大或者缩小,这就突显了指针与数组的最大区别——灵活性。数组很不灵活,你只能初始化的时候固定数组的大小,然后就无法随心所欲地改变数组大小了。arr就只能表示数组的首地址,如果是指针的话还可以修改指向的地址。所以指针的功能更为强大,它甚至可以直接替代数组做到相同的效果。
动态内存分配在编程的时候我们可以将内存空间视为三类,栈内存、堆内存和静态内存,这边主要介绍前两者。栈内存主要存储局部变量,比如函数域内定义的变量,特点是会在程序执行离开域之后变量会自动被释放,想栈一样后进先出;堆内存则不会收到函数域或者是块区域的影响,只会在程序退出之后清理。通过调用内存分配函数,我们可以将堆内存分配好的地址赋予指针,并且可以同时分配超过两个以上。
1234567int *p = (int *)malloc(3 * sizeof(int));p[0] = 0;p[1] = 1;p[2] = 2;free(p);p = NULL;
以上为一个指针分配了三个连续的空间,存储值为int类型,使用起来就像数组一样。最后用完不要忘了使用free()释放对应的内存空间。由于内存空间被释放,指针p的就指向了未定义的地址,所以最好将指针赋值空地址,避免直接使用导致的异常。
1234567891011121314int **p = (int **)malloc(3 * sizeof(int *));*p = (int *)malloc(sizeof(int));*(p + 1) = (int *)malloc(sizeof(int));*(p + 2) = (int *)malloc(sizeof(int));**p = 0;**(p + 1) = 1;**(p + 2) = 2;for (size_t i = 0; i < 3; i++) { free(p[i]);}free(p);p = NULL;
这里演示了二级指针的内存分配情况,三级四级也是同理,只不过二级以上的指针用起来会很繁琐,也很容易出错,所以实际当中很少会用到(我从来没在项目代码中见过)。需要注意的是,free()需要由内而外释放。
错误例子以下的代码是错误的案例,演示了为啥一不小心会出现内存泄露。
12345678/* wrong code */void alloc_int_w(int *p, int data) { p = (int *)malloc(sizeof(int)); *p = data;}int *p = NULL;alloc_int_w(p,10);
这里给*p分配了空间,这个函数的本意是想输入一个指针,然后给这个指针分配空间并初始化。但是没有注意*p是个形参,导致指针切换了指向,最后函数结束时形参自动释放,p指向的空间未及时释放,导致内存泄露。正确的例子如下
12345678/* right code */void alloc_int(int **p, int data) { *p = (int *)malloc(sizeof(int)); **p = data;}int *p = NULL;void alloc_int(&p, 10);