指针详解2
运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主
1.数组名的实质
-
数组名就是数组首元素的地址,但是有两个意外
-
sizeof(数组名),sizeof中单独存放数组名,此处的数组名表示整个数组,计算的是整个数组的大小,单位为字节 -
&数组名,此处的数组名表示整个数组,取出的是整个数组的地址,该地址与数组首元素的地址有区别,具体参考下文代码
// 代码1:分别打印数组名和数组首元素的地址,结果一样,数组名就是数组第一个元素的地址 #include <stdio.h> int main(int argc, const char * argv[]) { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; printf("&arr[0] = %p\n", &arr[0]); printf("arr = %p\n", arr); return 0; }![image]()
// 代码2:由于sizeof中单独放数组名,表示的是整个数组,计算数组占用内存的大小,共 40B #include <stdio.h> int main(int argc, const char * argv[]) { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; printf("%zd\n", sizeof(arr)); return 0; }![image]()
// 代码3:数组首元素的地址、数组名和数组的地址在数值上一样 #include <stdio.h> int main(int argc, const char * argv[]) { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; printf("&arr[0] = %p\n", &arr[0]); printf("arr = %p\n", arr); printf("&arr = %p\n", &arr); return 0; }![image]()
// 代码4:数组首元素的地址、数组名和数组的地址分别加 1 的区别 #include <stdio.h> int main(int argc, const char * argv[]) { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 两者相差4个字节,&arr[0] 是首元素的地址,加 1 就是跳过一个元素 printf(" &arr[0] = %p\n", &arr[0]); printf("&arr[0] + 1 = %p\n", &arr[0] + 1); // 两者相差4个字节,arr 也是数组首元素的地址,加 1 就是跳过一个元素 printf(" arr = %p\n", arr); printf(" arr + 1 = %p\n", arr + 1); // 两者相差40个字节,因为 &arr 是数组的地址,加 1 操作是跳过整个数组 // 十进制40的十六进制为28,16fdf280 + 00000028 = 16fdf2a8 printf(" &arr = %p\n", &arr); printf(" &arr + 1 = %p\n", &arr + 1); return 0; }![image]()
-
2.利用指针访问数组
-
在积累了前面的知识后,结合数组的特点,可以很方便地使用指针访问数组
-
编译器在处理数组元素访问的时候,通过首元素的地址加偏移量求得元素的地址,然后解引用来访问
#include <stdio.h>
int main(int argc, const char * argv[]) {
int arr[10] = {0};
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int *p = &arr[0];
// int *p = arr; // 这里的数组名就是数组首元素的地址,两者等价
// 输入
for (i = 0; i < sz; i++) {
scanf("%d", p + i);
// scanf("%d", arr + i);
}
// 输出
for (i = 0; i < sz; i++) {
printf("%d ", *(p + i));
// printf("%d", p[i]); // p[i] 本质上等价于 *(p + i)。类似地,arr[i] 等价于 *(arr + i)
// printf("%d", *(arr + i));
// printf("%d", arr[i]);
// printf("%d", i[arr]); // [] 作为运算符,i 和 arr 是它两边的运算数,类比加法运算,[] 满足交换律
}
printf("\n");
return 0;
}
3.一维数组传参的实质
-
3.1 数组名作为函数参数
- 之前的代码通常在自定义函数外部求数组元素的个数。如果将数组传给一个自定义函数,理论上在函数内部也能求出数组的元素个数。参考以下代码
#include <stdio.h> void test1(int arr[]) { int sz1 = sizeof(arr) / sizeof(arr[0]); printf("sz1 = %zd\n", sz1); } void test2(int arr[10]) { int sz2 = sizeof(arr) / sizeof(arr[0]); printf("sz2 = %zd\n", sz2); } int main(int argc, const char * argv[]) { int arr[10] = {0}; int sz = sizeof(arr) / sizeof(arr[0]); printf("sz = %zd\n", sz); test1(arr); test2(arr); return 0; } //![image]()
-
观察运行结果发现在函数内部并没有获得正确的数组元素个数
-
数组名是数组首元素的地址,那么在数组传参时传递的是数组名,数组传参在本质上传递的是数组首元素的地址
-
理论上,函数形参的部分应使用指针变量来接收首元素的地址,函数内部的
sizeof(arr)计算的是一个地址的大小(单位是字节)而不是数组的大小(单位是字节) -
正是因为函数的参数部分本质是指针,所以在函数内部无法求得数组元素的个数
-
3.2 指针作为函数参数
- 一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式
#include <stdio.h> void test1(int arr[]) { printf("%zd\n", sizeof(arr)); } void test2(int *arr) { printf("%zd\n", sizeof(arr)); } int main(int argc, const char * argv[]) { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; test1(arr); test2(arr); return 0; }![image]()
4.冒泡排序
-
核心思想:升序排序时,两两相邻的元素进行比较,如果前面的比后面的大,那就交换这两个数。详细的过程可参考排序专题
-
代码实现
// 方法1
#include <stdio.h>
void bubble_sort(int arr[], int sz) {
int i = 0;
for (i = 0; i < sz - 1; i++) {
int j = 0;
for (j = 0; j < sz - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main(int argc, const char * argv[]) {
int arr[] = {3, 1, 7, 5, 8, 9, 0, 2, 4, 6};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
return 0;
}
// 方法2
#include <stdio.h>
int count = 0; // 计数变量,统计相邻两个数比较的次数
void input(int *arr, int sz) {
int i = 0;
for (i = 0; i < sz; i++) {
scanf("%d", arr + i);
}
}
void print_arr(int *arr, int sz) {
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
void bubble_sort(int *arr, int sz) {
int i = 0;
int j = 0;
int temp = 0;
for (i = 0; i < sz - 1; i++) {
int flag = 1; // 假设这一趟数已经有序了
for (j = 0; j < sz - i - 1; j++) {
count++;
if (arr[j] > arr[j + 1]) {
flag = 0; // 发生交换就说明此时数组无序
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (flag == 1) { // 一趟过后没有交换说明已经有序,后续无需在排列
break;
}
}
}
int main(int argc, const char * argv[]) {
int arr[10] = {0};
int sz = sizeof(arr) / sizeof(arr[0]);
input(arr, sz);
bubble_sort(arr, sz);
print_arr(arr, sz);
printf("count = %d\n", count);
return 0;
}
5.二级指针
-
5.1 定义和指向原理
-
C语言中的普通变量都有地址。类似地,指针变量是一种特殊的变量,它也应当有地址
-
指针变量的地址存放在二级指针中
#include <stdio.h> int main(int argc, const char * argv[]) { int a = 10; int *pa = &a; int **ppa = &pa; printf("&a = %p\n", &a); printf("&pa = %p\n", &pa); printf("&ppa = %p\n", &ppa); return 0; }![image]()
![image]()
-
-
5.2 二级指针的运算
*ppa通过解引用ppa中的地址,找到了pa,*ppa访问的其实就是pa
int a = 20; *ppa = &a; // 等价于 pa = &a;**ppa先通过*ppa找到pa,然后对pa进行解引用操作,即*pa找到了a
**ppa = 30; // 等价于 *pa = 30; // 等价于 a = 30;
6.指针数组
-
6.1 定义和指向原理
-
整型数组是存放整型数据的数组,字符数组是存放字符型数据的数组
-
类似地,指针数组应该是存放指针类型数据的数组
![image]()
-
-
6.2 指针数组模拟二维数组
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素- 代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行不是连续的
#include <stdio.h> int main(int argc, const char * argv[]) { int arr1[] = {1, 2, 3, 4, 5}; int arr2[] = {2, 3, 4, 5, 6}; int arr3[] = {3, 4, 5, 6, 7}; // 数组名是数组首元素的地址,类型是int *的,就可以存放在parr数组中 int *parr[] = {arr1, arr2, arr3}; int i = 0, j = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 5; j++) { printf("%d ", parr[i][j]); // printf("%d ", *(*(parr + i) + j)); } printf("\n"); } return 0; }![image]()











浙公网安备 33010602011771号