• C语言 指针与数组


    引言

    1. 指针与数组之间的联系

    在 C语言中,虽然我们平时访问数组的时候是用 arr[ i ] 进行表示,但在底层解析的时候,其实是通过 *(arr + i) 这样的指针配合解引用的方式来做到的。这是指针与数组之间所能联系的核心所在。

    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    arr[i]; // *(arr + i);
    
    • 1
    • 2

    观察上面两行代码,arr 作为数组名,表示首元素的地址,即 &arr[0].
    由于数组的地址是由低到高连续存储的,所以知道了首元素的地址,就能够间接访问到数组的剩余元素了。

    2. 指针与字符串之间的联系

    由于在 C语言中,字符串通常由字符数组构造,所以字符串也可以通过 " 头部指针 " 来间接地拿到每个字符。这样一来,指针就像一条线一样,可以 " 顺藤摸瓜 " 。

    #include 
    
    int main() {
    
    	char arr[] = "hello world";
    	printf("%s\n", arr); // arr 表示首元素的地址
     
    	return 0;
    }
    
    // 输出结果:hello world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一、指针与数组

    1. 指针数组与数组指针

    #include 
    
    int main() {
    
    	int a = 1, b = 2, c = 3, x = 4, y = 5;
    
    	int arr[5] = { 1,2,3,4,5 }; 			// 整型数组
    	int* parr[5] = { &a, &b, &c, &x, &y }; 	// 指针数组
    	int (*parr2)[5] = &arr; 				// 数组指针
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意事项: [ ] 的优先级要高于 * 号

    ① 整型数组表示数组,[ ] 先与 arr 结合,所以 arr 本质上就是一个数组。
    ② 指针数组表示数组,[ ] 先与 parr 结合,所以 parr 本质上就是一个数组。
    数组指针表示指针,* 先与 parr2 结合,所以 parr2 本质上就是一个指针,它指向长度为 5 的数组。

    1-1

    2. 指针数组的用法

    指针数组即存放指针的数组,或者说,指针数组是存放地址的数组。

    程序清单:打印二维数组

    #include 
    
    int main() {
    
    	int arr1[] = { 1,2,3,4,5 };
    	int arr2[] = { 2,3,4,5,6 };
    	int arr3[] = { 3,4,5,6,7 };
    
    	int* parr[] = { arr1,arr2,arr3 };
    	for (int i = 0; i < 3; i++) {
    		for (int j = 0; j < 5; j++) {
    			printf("%d ", parr[i][j]);
    		}
    		printf("\n");
    	}
    
    	return 0;
    }
    
    // 输出结果:
    // 1 2 3 4 5
    // 2 3 4 5 6
    // 3 4 5 6 7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分析:

    1-2

    3. 数组指针的用法

    数组指针表示指向数组的指针。

    程序清单:打印二维数组

    #include 
    
    void print(int (*arr)[5], int row, int column) {
    
    	for (int i = 0; i < row; i++) {
    		for (int j = 0; j < column; j++) {
    			printf("%d ", *(*(arr+i) + j ));
    		}
    		printf("\n");
    	}
    }
    
    int main() {
    
    	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7} };
    	int row = sizeof(arr) / sizeof(arr[0]);
    	int column = sizeof(arr[0]) / sizeof(arr[0][0]);
    
    	print(arr, row, column);
    
    	return 0;
    }
    
    // 输出结果:
    // 1 2 3 4 5
    // 2 3 4 5 6
    // 3 4 5 6 7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    1-3

    二、数组参数、指针参数

    1. 一维数组传参

    #include 
    
    void test(int arr[]) {} 	// √
    void test(int* arr) {} 		// √
    
    void test2(int* arr[]) {} 	// √
    void test2(int** arr) {} 	// √
    
    
    int main() {
    
    	int arr1[10] = { 0 };
    	int* arr2[10] = { 0 };	// 指针数组
    
    	test(arr1); // 首元素的地址 &arr1[0]
    	test2(arr2); // 首元素的地址 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意事项:

    ① arr1 是一个一维整型数组,它存放着 10 个 int 类型的元素。arr1 表示首元素的地址,即 &arr1[0],所以形参在接收时,可以利用 int* 指针接收。

    ② arr2 是一个一维指针数组,它存放着 10 个 int* 类型的元素。arr2 表示首元素的地址 (首元素本身就是地址,所以传的参数就是地址的地址),所以形参在接收时,可以利用 int** 指针接收。

    2. 二维数组传参

    #include 
    
    void test(int arr[3][5]) {} 	// √
    void test(int arr[][5]) {} 		// √
    void test(int arr[][]){} 		// X
    void test(int (* arr)[5]){} 	// √
    
    int main() {
    
    	int arr[3][5] = { 0 };
    	test(arr);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意事项:

    二维数组的数组名表示第一行数组的地址。在函数形参接收时,可以利用数组名直接接收 (但不能省略列数);也可以利用数组指针的方式来接收。

    三、指针与函数 (了解)

    1. 其实函数也有地址

    程序清单:

    #include 
    
    int add(int a, int b) {
    
    	return a + b;
    }
    
    int main() {
    
    	add(10, 20);
    
    	printf("%p\n", &add);
    	printf("%p\n", add);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出结果:

    1-4

    注意事项:

    ① 函数在内存中也是有对应的地址的。
    ② &函数名 和 函数名,两者是一样的意思,都可以当作函数的地址使用。

    2. 函数指针

    函数指针即一个函数地址,或者说指针指向一个函数。

    #include 
    
    int add(int a, int b) {
    	return a + b;
    }
    
    int main() {
    	
    	int (*p) (int, int) = &add; // 1 (变量 p 是一个函数指针)
    	int ret = (*p)(20, 30); 	// 2
    	printf("%d\n", ret);
    
    	return 0;
    }
    
    // 输出结果:50
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意事项:

    ① 注释1,我们可以说 p 指向 add 函数。第一个 int 表示 add 返回值为 int,(*p) 声明了这就是一个指针,(int, int) 表示 add 的形参类型。

    ② 注释2,(*p) 表示解引用 p,它的含义就等价于 add 函数。通过先解引用,再传参,就相当于使用了 add 函数。

    *p <==> *(&add) <==> add	// 解引用
    
    • 1

    3. 函数指针数组

    函数指针数组,顾名思义就是存放函数指针的数组,或者说存放函数地址的数组。

    #include 
    
    int add(int a, int b) {
    	return a + b;
    }
    
    int sub(int a, int b) {
    	return a - b;
    }
    
    int mult(int a, int b){
    	return a * b;
    }
    
    int div(int a, int b) {
    	return a / b;
    }
    
    int main() {
    
    	int (* parr[4])(int, int) = { add, sub, mult, div }; // 函数指针数组
    
    	for (int i = 0; i < 4; i++) {
    
    		int ret =  parr[i](40, 20);		// 函数名直接充当函数的地址
    		printf("%d ", ret);
    	}
    
    	return 0;
    }
    
    // 输出结果:
    // 60  20  800  2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    注意事项:

    ① 对于下面这行代码, parr 和 [ ] 先结合,所以 parr 本质上就是一个数组。

    int (* parr[4])(int, int) = { add, sub, mult, div };
    
    • 1

    ② 对于下面这行代码, 函数名直接充当函数的地址。

    parr[1] 等价 add,add 等价 &add.
    parr[2] 等价 sub,sub 等价 &sub.
    parr[3] 等价 mult,mult 等价 &mult.
    parr[4] 等价 div,div 等价 &div.

    int ret =  parr[i](40, 20);
    
    • 1

    4. qsort 函数中的 compare 函数指针

    下面是我之前写的 qsort 函数的博客,qsort 参数中有一个 compare 指针,用来指定用哪个自定义排序函数。如果读者感兴趣的,可以看一下。

    qsort 函数博客

    四、指针与数组的笔试题

    结论

    结论1:

    对于一维数组来说,数组名就是首元素的地址;
    对于二维数组来说,数组名就是第一行数组的地址。

    但有两个例外:

    ① sizeof(数组名),此时数组名表示整个数组,计算的是整个数组占用内存的大小。
    ② &数组名,此时数组名表示整个数组,取出的是整个数组的地址。

    结论2:

    指针变量是用来存放地址的。所以,地址的存放需要多大空间,指针变量的大小就应该是多大。

    ① 32位 机器,支持 32位 虚拟地址空间,其产生的地址就是 32位,所以此时指针变量就需要 32位 的空间存储,即 4字节。
    ② 64位 机器,支持 64位 虚拟地址空间,其产生的地址就是 64位,所以此时指针变量就需要 64位 的空间存储,即 8字节。

    结论3:

    ① sizeof 是一个操作符,它是用来计算变量 (类型) 所占内存空间大小的,计算单位是 " 字节 "。

    ② 字符串的结束标志是一个 ’ \0 ’ 的转义字符。在使用格式化输出时, ’ \0 ’ 的作用相当于告诉了编译器, ’ \0 ’ 是一个停止的标志。在使用 strlen 这个库函数计算字符串的长度时,也是一样的道理,它只计算 ’ \0 ’ 之前的长度。

    例题1

    #include 
    
    int main() {
    
    	int a[] = { 1,2,3,4 };
    
    	printf("%d\n", sizeof(a)); // 求整个数组所占内存的大小 -> 16
    
    	printf("%d\n", sizeof(a + 0)); // sizeof(&arr[0]) -> 4/8
    
    	printf("%d\n", sizeof(*a)); // sizeof(arr[0]) -> 4
    
    	printf("%d\n", sizeof(a + 1)); // sizeof(&arr[1]) -> 4/8
    
    	printf("%d\n", sizeof(a[1])); // 4
    
    	printf("%d\n", sizeof(&a)); // 拿到整个数组的地址,依然是地址 -> 4/8
    
    	printf("%d\n", sizeof(*&a)); // sizeof(a) -> 16
    
    	printf("%d\n", sizeof(&a + 1)); // 跳过整个数组,拿到的还是地址 -> 4/8
    
    	printf("%d\n", sizeof(&a[0])); // 4/8
    
    	printf("%d\n", sizeof(&a[0] + 1)); // sizeof(&arr[1]) 4/8
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    例题2

    #include 
    
    int main() {
    
    	char arr[] = { 'a','b','c','d','e','f' }; // [ a b c d e f ]
    
    	printf("%d\n", sizeof(arr)); // 求整个数组所占内存的大小 -> 6
    
    	printf("%d\n", sizeof(arr + 0)); // sizeof(&arr[0]) -> 4/8
    
    	printf("%d\n", sizeof(*arr)); // sizeof(arr[0]) -> 1
    
    	printf("%d\n", sizeof(arr[1])); // 1
    
    	printf("%d\n", sizeof(&arr)); // 拿到整个数组的地址,依然是地址 -> 4/8
    
    	printf("%d\n", sizeof(&arr + 1)); // 跳过整个数组,拿到的还是地址 -> 4/8
    
    	printf("%d\n", sizeof(&arr[0] + 1)); // sizeof(&arr[1]) -> 4/8
    
    	printf("%d\n", strlen(arr)); // 这里 arr 表示首元素的地址,由于 '\0' 不知道在什么位置,所以 strlen 求得为随机值
    
    	printf("%d\n", strlen(arr + 0)); // 随机值
    
    	printf("%d\n", strlen(*arr)); // strlen(arr[0]) ->  strlen 需要接收的是一个指针变量,这里非法访问
    
    	printf("%d\n", strlen(arr[1])); // 同理,非法访问
    
    	printf("%d\n", strlen(&arr)); // 由于 '\0' 不知道在什么位置,随机值
    
    	printf("%d\n", strlen(&arr + 1)); // 由于 '\0' 不知道在什么位置,随机值
    
    	printf("%d\n", strlen(&arr[0] + 1)); // 由于 '\0' 不知道在什么位置,随机值
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    例题3

    #include 
    
    int main() {
    
    	char arr[] = "abcdef"; // [ a b c d e f \0 ]
    
    	printf("%d\n", sizeof(arr)); // 7
    
    	printf("%d\n", sizeof(arr + 0)); // sizeof(&arr[0]) -> 4/8
    
    	printf("%d\n", sizeof(*arr)); // sizeof(arr[0]) -> 1
    
    	printf("%d\n", sizeof(arr[1])); // 1
    
    	printf("%d\n", sizeof(&arr)); // 4/8
    
    	printf("%d\n", sizeof(&arr + 1)); // 4/8
    
    	printf("%d\n\n", sizeof(&arr[0] + 1)); // 4/8
    
    	printf("%d\n", strlen(arr)); // 6
    
    	printf("%d\n", strlen(arr + 0)); // 6
    
    	printf("%d\n", strlen(*arr)); // 非法访问
    
    	printf("%d\n", strlen(arr[1])); // 非法访问
    
    	printf("%d\n", strlen(&arr)); // 6
    
    	printf("%d\n", strlen(&arr + 1)); // 跳过整个数组,由于 '\0' 不知道在什么位置,随机值
    
    	printf("%d\n", strlen(&arr[0] + 1)); // strlen(&arr[1]) -> 5
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    例题4

    #include 
    
    int main() {
    
    	char* p = "abcdef"; // p 指向 字符 'a',[ a b c d e f \0 ]
    
    	printf("%d\n", sizeof(p)); // 字符 'a' 的地址 -> 4/8
    
    	printf("%d\n", sizeof(p + 1)); // 字符 'b' 的地址 -> 4/8
    
    	printf("%d\n", sizeof(*p)); // sizeof('a') -> 1
    
    	printf("%d\n", sizeof(p[0])); // p[0] <==> *(p+0),sizeof('a') -> 1
    
    	printf("%d\n", sizeof(&p)); // 指针 p 的地址 -> 4/8
    
    	printf("%d\n", sizeof(&p + 1)); // 跳过指针 p 的地址 -> 4/8
    
    	printf("%d\n\n", sizeof(&p[0] + 1)); // 字符 'b' 的地址 -> 4/8
    
    	printf("%d\n", strlen(p)); // 6
    
    	printf("%d\n", strlen(p + 1)); // 从字符 'b' 往后数字符数 -> 5
    
    	//printf("%d\n", strlen(*p)); //  非法访问
    
    	//printf("%d\n", strlen(p[0])); // 非法访问
    
    	printf("%d\n", strlen(&p)); // 随机值
    
    	printf("%d\n", strlen(&p + 1)); // 随机值
    
    	printf("%d\n", strlen(&p[0] + 1)); // 从字符 'b' 往后数字符数 -> 5
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    例题5

    #include 
    
    int main() {
    
    	int a[3][4] = { 0 };
    
    	printf("%d\n", sizeof(a)); // 求整个二维数组所占内存的大小 -> 12 * 4 = 48
    
    	printf("%d\n", sizeof(a[0][0])); // 4
    
    	printf("%d\n", sizeof(a[0])); // a[0] 单独放在了 sizeof 的内部,二维数组第一行元素所占内存大小 -> 4*4 = 16
    
    	printf("%d\n", sizeof(a[0] + 1)); // a[0] 并没有单独放在 sizeof 的内部,所以 a[0] 在这里就作为第一行第一个元素的地址
    	// 所以 a[0] + 1 在这里就作为第一行第二个元素的地址,sizeof(&arr[0][1]) -> 4/8 
    
    	printf("%d\n", sizeof(*(a[0] + 1))); // sizeof(arr[0][1]) -> 4
    
    	printf("%d\n", sizeof(a + 1)); // 二维数组第二行的地址 ->  4/8
    
    	printf("%d\n", sizeof(*(a + 1))); // 对二维数组第二行的地址解引用 -> 第二行元素的所有元素所占内存的大小 -> 4*4 = 16
    
    	printf("%d\n", sizeof(&a[0] + 1)); // 二维数组第二行的地址 -> 4/8
    
    	printf("%d\n", sizeof(*(&a[0] + 1)));// 对二维数组第二行的地址解引用 -> 第二行元素的所有元素所占内存的大小 -> 4*4 = 16
    
    	printf("%d\n", sizeof(*a)); // 对二维数组第一行的地址解引用 -> 第一行元素的所有元素所占内存的大小 -> 4*4 = 16
    
    	printf("%d\n", sizeof(a[3])); // a[3] 单独放在了 sizeof 的内部,二维数组第四行元素所占内存大小 -> 4*4 = 16
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    五、指针笔试题

    例题1

    #include 
    
    int main()
    {
    	int arr[5] = { 1, 2, 3, 4, 5 };
    	
    	int* ptr = (int*)(&arr + 1); // 将数组指针转换成一个整型指针
    	// arr+1 表示跳过一个元素,&arr+1 表示跳过整个数组
    	printf("%d, %d\n", *(arr + 1), *(ptr - 1)); 
    	return 0;
    }
    
    // 输出结果:2, 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    分析:

    1-5

    例题2

    #include 
    
    int main()
    {
    	int arr[3][2] = { {2, 4}, {6, 8}, {3, 7} };
    	int* p = arr[0]; // arr[0] 表示数组名,p指向二维数组的第一行的第一个元素
    
    	printf("%d\n", p[1]); // p[1] -> *(p + 1) -> 第一行第二个元素
    	return 0;
    }
    
    // 输出结果:4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    分析:

    1-6

    例题3

    #include 
    
    int main()
    {
    	int arr[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    	int* ptr1 = (int*)(&arr + 1); // 跳过整个数组
    	int* ptr2 = (int*)(*(arr + 1)); // arr为数组名,表示数组的第一行地址,所以 arr+1 为数组的第二行开头
    
    	printf("%d, %d\n", *(ptr1 - 1), *(ptr2 - 1));
    	return 0;
    }
    
    // 输出结果:10, 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    分析:

    1-7

    例题4

    #include 
    
    int main()
    {
    	char* arr[] = { "go","to","school" };
    	char** pa = arr; // arr 表示首元素的地址
    	
    	pa++;
    	printf("%s\n", *pa);
    	return 0;
    }
    
    //输出结果:to
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1-8

  • 相关阅读:
    java计算机毕业设计基于ssm的基于android的家庭理财系统
    ssm 基于springboot的车辆故障管理系统Java
    期货的含义及交易特点
    音视频—图像基础
    浅谈中小企业的供应商管理
    Visio 安装与操作小结
    Apache Hudi Timeline:支持 ACID 事务的基础
    Spring Cloud Alibaba Nacos注册中心(单机)
    学会make/makefile基本用法
    【云原生】k8s-----集群调度
  • 原文地址:https://blog.csdn.net/lfm1010123/article/details/127962201