指针

148 阅读7分钟

指针

指针pointer是指向(point to)的意思,和引用类似,实现了对象的间接访问。不同在于指针本身自己是对象,可以进行赋值和拷贝,同时初始化没有赋值时会有一个随机值。

基本使用

  1 #include <stdio.h>
  2
  3 void InitPointer() {
  4     int a;
  5     printf("a address: %p\n", &a); //a address: 0x7ffc0fc5301c
  6     int *p = &a;
  7     printf("p value: %p, p address: %p\n", p, &p); //p value: 0x7ffc0fc5301c, p address: 0x7ffc0fc53020
  8     a = 5;
  9     printf("a = %d, *p = %d\n", a, *p); //a = 5, *p = 5
 10     *p = 8;
 11     printf("a = %d", a); //a = 8
 12 }
 13
 14 int main() {
 15     InitPointer();
 16     return 0;
 17 }

指针p值是a的地址, 修改*p的值会同时修改a的值。

指针类型和void指针

  1 #include <stdio.h>
  2
  3 int main()
  4 {
  5     //为什么指针需要表明类型,因为p记录的是引用的地址,而类型是用来计算地址偏移取值的。
  6     //比如int指针,int是4字节,所以会读取指针地址+4字节的范围内容。
  7     int a = 1027;
  8     int *p = &a;
  9
 10     printf("size of integer is %d bytes.\n", sizeof(int));
 11     printf("p = %d, *p = %d\n", p, *p); //*p读取的是00000010 00000011,值是1027
 12     printf("p = %d, *(p + 1) = %d\n", p + 1, *(p + 1));
 13     char *q = (char*) p;
 14     printf("size of char is %d bytes.\n", sizeof(char));
 15     printf("q = %d, *q = %d\n", q, *q); //*q获取的是低八位的值00000011,所以是3
 16     printf("q = %d, *(q + 1) = %d\n", q + 1, *(q + 1)); //*(q + 1)获取的是00000010,所以是4
 17     void *z = p; //不用像char*需要强制转换类型
 18     printf("z = %d"); //但是不能计算指针+1
 19
 20     /**
 21         size of integer is 4 bytes.
 22         p = 1270976724, *p = 1027
 23         p = 1270976728, *(p + 1) = 1270976724
 24         size of char is 1 bytes.
 25         q = 1270976724, *q = 3
 26         q = 1270976725, *(q + 1) = 4
 27         z = 1270976724
 28     **/
 29     // 1027 = 1024 + 2 + 1 = 2^10 + 2^1 + 2^0 = 00000000 00000000 00000010 00000011
 30     return 0;
 31 }

int类型是4字节,所以int a = 1027用二进制表示为:

//1024 + 2 + 1
00000000 00000000 00000010 00000011

指针存储引用对象的地址,指针的类型记录引用对象的地址范围,比如int指针会读取4字节,char指针会读取1字节。

所以强制转换int指针p为char指针q后,虽然引用的地址还是a的地址,但是读取的值变为3了,因为只读取了1字节的范围。

定义void指针,就没办法知道引用对象的类型了,所以不能对void指针使用自增。

指向指针的指针

  1 #include <stdio.h>
  2
  3 int main()
  4 {
  5     int x = 5;
  6     int* p = &x;
  7     int** q = &p;
  8     int*** r = &q;
  9
 10     printf("*p = %d\n", *p);
 11     printf("*q = %d\n", *q);
 12     printf("**q = %d\n", *(*q));
 13     printf("**r = %d\n", *(*r));
 14     printf("***r = %d\n", *(*(*r)));
 15     x = 10;
 16     printf("x = %d\n", x);
 17     return 0;
 18
 19
 20     /**
 21     output:
 22         *p = 5
 23         *q = 245159884
 24         **q = 5
 25         **r = 245159884
 26         ***r = 5
 27         x = 10
 28     **/
 29 }

关系相当于r -> q -> p -> a, 像是单向的链表。

指针与数组

  1 #include <stdio.h>
  2
  3 int main()
  4 {
  5     int A[] = {2,4,5,8,1};
  6     int x = 5;
  7     int *p = A;
  8     p++; //A++操作不行,因为A是数组地址,而p是整形指针
  9
 10     printf("A地址: %p\n", A);
 11     printf("A[0]地址: %p\n", &A[0]);
 12     printf("p的地址: %p\n", p);
 13     for (int i = 0; i < 5; i++) {
 14         printf("A+%d地址: %p\n", i ,A + i);
 15         printf("&A[%d]地址: %p\n", i, &A[i]);
 16
 17         printf("A+%d的值: %d\n", i, *(A+i));
 18         printf("A[%d]的值:%d\n", i, A[i]);
 19     }
 20
 21     return 0;
 22 }

输出值:

A地址: 0x7ffc62c356f0
A[0]地址: 0x7ffc62c356f0
p的地址: 0x7ffc62c356f4

A+0地址: 0x7ffc62c356f0
&A[0]地址: 0x7ffc62c356f0
A+0的值: 2
A[0]的值:2
A+1地址: 0x7ffc62c356f4
&A[1]地址: 0x7ffc62c356f4
A+1的值: 4
A[1]的值:4
A+2地址: 0x7ffc62c356f8
&A[2]地址: 0x7ffc62c356f8
A+2的值: 5
A[2]的值:5
A+3地址: 0x7ffc62c356fc
&A[3]地址: 0x7ffc62c356fc
A+3的值: 8
A[3]的值:8
A+4地址: 0x7ffc62c35700
&A[4]地址: 0x7ffc62c35700
A+4的值: 1
A[4]的值:1

C语言的数组本质是指针(?),A+i等于&A[i],*(A+i)等于A[i]。但是数组不能像指针对象一样自增。

当数组作为参数

  1 #include <stdio.h>
  2
  3 int SumOfElements(int A[], int size)
  4 {
  5     //为什么要再main计算好size再传参进来? 因为SOE这个函数的A数组经过编译器优化后,是一个整形指针,所以大小在64位里是8字节
  6     int sum = 0;
  7     printf("SOE - Size of A = %d, Size of A[0] = %d\n", sizeof(A), sizeof(A[0]));
  8     for (int i = 0; i < size; i++) {
  9         sum += A[i];
 10     }
 11     return sum;
 12 }
 13
 14 void DoubleArrays(int A[], int size) {
 15     for (int i = 0; i < size; i++) {
 16         A[i] = 2 * A[i];
 17     }
 18 }
 19
 20 int main()
 21 {
 22     int A[] = {1,2,3,4,5};
 23     int size = sizeof(A) / sizeof(A[0]);
 24     int total = SumOfElements(A, size);
 25     printf("Sum of Elements = %d \n", total);
 26     printf("Main - Size of A = %d, Size of A[0] = %d\n", sizeof(A), sizeof(A[0]));
 27
 28     DoubleArrays(A, size);
 29     for (int i = 0; i < size; i++) {
 30         printf("A[%d] = %d\n", i, A[i]);
 31     }
 32
 33     return 0;
 }

输出值:

SOE - Size of A = 8, Size of A[0] = 4
Sum of Elements = 15
Main - Size of A = 20, Size of A[0] = 4
A[0] = 2
A[1] = 4
A[2] = 6
A[3] = 8
A[4] = 10

数组作为参数时,调用函数实际使用的是整形指针,所以占8字节。在原来的main函数中是20字节。所以要在主函数中计算好了数组的size再传到要调用的函数中。

在调用函数中,可以修改A数组的内容,因为调用函数中A是整形指针。

指针与字符数组

  1 #include <stdio.h>
  2
  3 void print(char *c)
  4 {
  5     while(*c != '\0')
  6     {
  7         printf("%c", *c);
  8         c++;
  9     }
 10     printf("\n");
 11 }
 12
 13 int main ()
 14 {
 15     char c[20] = "Hello";
 16     print(c);
 17     return 0;
 18 }

c中有两种方法定义字符串,一种是字符数组,一种是字符指针。

字符数组:

char c1[5]; //定义长度要大于等于字符串长度,定义长度比较大也会自动以NUL结尾
char c1[]; //会自动定义长度,结尾以NUL表示

字符指针:

char *c2 = "Hello";

指针与数组的区别:
1.可以直接定义char *c2 = c1, 直接让字符指针等于字符数组,但是反过来不行。
2.可以直接访问*(c2 + i)来访问c2[i],第i个元素。
3.指针能自增,c2++。
4.传参字符数组时,要以字符指针的接受方式,如上面的print函数。因为使用字符数组比较大的话,复制成本会大一些。
5.使用字符数组时初始化时分配的是栈空间,可以修改字符数组中的参数如c[0]。使用字符指针初始化时,分配的是常量区的空间,无法修改其中元素。
6.在print函数中这个字符指针却可以修改个别元素,如c[0]。因为这个字符指针指向的是字符数组的地址,而不是初始化的常量空间。

指针与多维数组

  1 #include <stdio.h>
  2
  3 int main()
  4 {
  5     int B[2][3] = {{2,3,6}, {4,5,8}};
  6     printf("B = %d, &B = %d\n", B, &B);
  7     printf("*B = %d, B[0] = %d, &B[0][0] = %d\n", *B, B[0], &B[0][0]);
  8     printf("B+1 = %d, &B[1] = %d\n", B+1, &B[1]);
  9     printf("*(B+1) = %d, B[1] = %d, &B[1][0] = %d\n", *(B+1), B[1], &B[1][0]);
 10     printf("*(B+1)+2 = %d, B[1]+2 = %d, &B[1][2] = %d\n", *(B+1)+2, B[1]+2, &B[1][2]);
 11     printf("*(*B+1) = %d\n", *(*B+1));
 12     printf("B[1][2] = %d, *(*(B+1)+2) = %d\n", B[1][2], *(*(B+1)+2));
 13     return 0;
 14     /**
 15     output:
 16         B = 458075168, &B = 458075168
 17         *B = 458075168, B[0] = 458075168, &B[0][0] = 458075168
 18         B+1 = 458075180, &B[1] = 458075180
 19         *(B+1) = 458075180, B[1] = 458075180, &B[1][0] = 458075180
 20         *(B+1)+2 = 458075188, B[1]+2 = 458075188, &B[1][2] = 458075188
 21         *(*B+1) = 3
 22         B[1][2] = 8, *(*(B+1)+2) = 8
 23     **/
 24 }

二维B[2][3]对应分配的内存地址如下(省略了458075...前缀):

image.png

推导出

B[i][j] = *(B[i] + j)  
        = *(*(B + i) + j)
        
        
//三维数组时
B[i][j][k] = *(B[i][j] + k)
           = *(*(B[i] + j) + k)
           = *(*(*(B + i) +j) + k)

当三维数组作为传参的参数时:

void Func(*(C)[x][y]) { //三维数组传字符指针时, 很多人会传***C,s 是不对的
    ...
}

函数指针

  1 #include <stdio.h>
  2
  3 void A()
  4 {
  5     printf("Hello");
  6 }
  7
  8 void B(void (*ptr)())
  9 {
 10     ptr();
 11 }
 12
 13
 14 int main()
 15 {
 16     B(A);
 17
 18     return 0;
 19 }

这里将A函数,作为一个函数指针作为参数传递到B函数,由B函数执行。一般来说函数指针作为回调函数和事务处理。

快速排序的第4个参数就是一个函数指针的比较函数, 这里compare参数是const是因为防止修改了指向的值。注意到qsort返回类型是空的,因为快速排序能对任何类型进行排序,只要给出比较函数。

  1 #include <stdio.h>
  2 #include <math.h>
  3 #include <stdlib.h>
  4
  5 int compare(const void* a, const void* b)
  6 {
  7     int A = *((int *) a);
  8     int B = *((int *) b);
  9     return A - B;
 10 }
 11
 12 int main()
 13 {
 14     int i, A[] = {-31, 22, -1, 50, -6, 4};
 15     qsort(A, 6, sizeof(int), compare);
 16     for (int i = 0; i < 6; i++) {
 17         printf("%d ", A[i]); //-31 -6 -1 4 22 50
 18     }
 19 }