指针
指针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...前缀):
推导出
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 }