第12讲:深入理解指针(2)
前言
1. 数组名的理解
数组名就是数组首元素(第⼀个元素)的地址,但是有两个例外:
• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,
计算的是整个数组的大小,单位是字节
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
(整个数组的地址和数组首元素的地址是有区别的)
• 除此之外,任何地方使用数组名,数组名都表示首元素的地址。
#include <stdio.h>
int main ( )
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p1 = &arr[0];
int* p2 = arr;
//验证数组名是数组首元素的地址
printf("&arr[0] = %p\n", &arr[0]);//首元素的地址
printf("arr = %p\n", arr);//首元素的地址
//区分sizeof(数组名)与&数组名的意义
printf("&arr = %p\n", &arr);//整个数组的地址
printf("arr的大小为:%d\n", sizeof(arr));//整个数组的大小
//运算
printf("&arr[0] = %p\n", &arr[0]);//首元素的地址
printf("&arr[0]+1 = %p\n", &arr[0]+1);//第二个元素的地址
printf("arr = %p\n", arr);//首元素的地址
printf("arr+1 = %p\n", arr+1);//第二个元素的地址
printf("&arr = %p\n", &arr);//取出整个数组的地址
printf("&arr+1 = %p\n", &arr+1);//整个数组的地址的隔壁
printf("sizeof(arr):%d\n", sizeof(arr));//整个数组的大小,40
printf("sizeof(arr+1):%d\n", sizeof(arr[2]));//第二个元素的大小,4
return 0;
}
第一部分
2. 使用指针访问数组(根基:指针运算)
arr[i] <==> *(arr+i) <==>*(i+arr)<==>i[arr],数组元素的访问在编译器处理的时候,
也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
#include <stdio.h>
#define MAX_LENGTH 3
int main()
{
//使用指针访问数组,输入及输出
int arr[MAX_LENGTH] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//此处记得解引用
}
return 0;
}
3. 一维数组传参的本质
本质上数组传参本质上传递的是数组首元素的地址
#include <stdio.h>
#define MAX_LENGTH 3
void Scanf(int arr[], int sz)//参数写成数组形式,本质上还是指针
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", arr + i);//记得取地址
}
}
void Printf(int* arr, int sz)//参数写成指针形式,接收数组首元素的地址
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));//记得解引用
}
}
void Test1 (int* arr)//参数写成指针形式,接收数组首元素的地址
{
int ret1 = sizeof(arr) / sizeof(arr[0]);//传递的是地址,无法计算数组元素个数
printf("\n ret1 = %d \n", ret1);
}
void Test2(int arr[])//参数写成数组形式,本质上还是指针
{
int ret2 = sizeof(arr) / sizeof(arr[0]);
printf("\n ret2 = %d \n", ret2);
}
int main ( )
{
int arr[MAX_LENGTH] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
Scanf(arr, sz);
Printf(arr, sz);
Test1(arr);
Test2(arr);
return 0;
}
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式
4. 冒泡排序
冒泡排序的核心思想就是:两两相邻的元素进行比较
#include <stdio.h>
#define MAX_LENGTH 10
void Scanf(int*arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", arr + i);//记得取地址
//scanf("%d", &arr[i]);
}
}
int count = 0;
void Bubble_Sort(int* arr, int sz)
{
//1 2 3 4 5 6
//趟数sz-1
int i = 0;
for (i = 0; i < sz - 1; i++)//趟数就是冒泡的个数,最后一个不用冒泡,已经暴漏了,所以是sz-1
{
int flag = 1;//假设是有序
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//为什么是sz-1-i,思想是两两相比较,已经冒泡的不用比较,所以要-i,对剩下的进行比较
{
count++;
if (arr[j] > arr[j + 1])//升序
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;
}
}
if (1 == flag)
{
break;
}
}
}
void Printf(int* arr, int sz)//参数写成指针形式,接收数组首元素的地址
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr+i));//记得解引用
//printf("%d ", arr[i]);
}
}
int main()
{
int arr[MAX_LENGTH] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("请输入10个数据进行排序:\n");
Scanf(arr, sz);
Bubble_Sort(arr, sz);
printf("10个数据的顺序为:\n");
Printf(arr, sz);
printf("\n count = %d \n", count);
return 0;
}
第二部分
5. 二级指针
指针变量也是变量,是变量就有地址,指针变量的地址存放在二级指针中。
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
//向下逐级存入
printf("a=%d\n", a);//变量a
printf("pa=%p\n", pa);//a的地址
printf("ppa=%p\n", ppa);//pa的地址
printf("\n");
//向上逐级取出,第一种
printf("ppa=%p\n", ppa);//pa的地址
printf("*ppa=%p\n", *ppa);//a的地址
printf("**ppa=%d\n", **ppa);//变量a
printf("\n");
//向上逐级取出,第二种
printf("ppa=%p\n", ppa);//pa的地址
printf("*ppa=%p\n", *ppa);//a的地址
printf("*pa=%d\n", *pa);//变量a
return 0;
}
6. 指针数组(文件指针)【本质就是数组,一个存放指针的数组】
指针数组本质上是数组,数组名无括号
数组指针:是用来存放数组首元素的地址,本质上是指针
【硬核】指针数组传参(区别于二维数组传参)
类比一维数组,可以是数组形式,也可以是指针形式(二级指针),和数组指针变量没啥关系,不要想太多
void Printf(int* parr[])//1.参数写成数组形式
void Printf(int**parr)//2.参数写成指针形式,二级指针
这种方法可以正常工作,并且在很多情况下,它更清晰地表达了函数接收的是一个指针的集合,而不是一个传统的多维数组。
这种方式在处理动态分配的多维数组或不规则的数据结构时特别有用。
7. 指针数组模拟二维数组
注意:指针数组在这里存放的是各个一维数组的地址,并不连续,不是真实的二维数组
不要混用指针数组与二维数组,特别是在函数传参的时候
它们在内存中的表示和操作方式是不同的
#include <stdio.h>
enum dimension
{
MAX_ROW=3,
MAX_COLUMN=3
};
void Scanf(int* parr[])//1.参数写成数组形式
//void Scanf(int** parr)//2.参数写成指针形式
{
int i = 0;
for (i = 0; i < MAX_ROW; i++)
{
int j = 0;
for (j = 0; j < MAX_COLUMN; j++)
{
//scanf("%d", &parr[i][j]);
if (scanf("%d", (*(parr + i) + j)) != 1)
{
printf("error");
return;
}
//*(parr+i)二维数组首元素的地址+i,再解引用,得到一维数组整个数组
//*(parr+i)就是新的数组的数组名
//*(parr+i)+j是一维数组首元素的地址+j
//(*(*(parr+i)+j)),解引用后,得到的是一维数组中的数组元素
}
}
}
void Printf(int* parr[])//1.参数写成数组形式
//void Printf(int**parr)//2.参数写成指针形式
{
int i = 0;
for (i = 0; i < MAX_ROW; i++)
{
int j = 0;
for (j = 0; j < MAX_COLUMN; j++)
{
//printf("%d", parr[i][j]);
printf("parr[%d][%d]=%d <=> %p ", i,j,*(*(parr + i) + j), (*(parr + i) + j));
}
printf("\n");
printf("\n");
}
}
int main()
{
int arr1[MAX_COLUMN];
int arr2[MAX_COLUMN];
int arr3[MAX_COLUMN];
//指针数组模拟二维数组的实现,伪二维数组
//思想:将一维数组的首元素的地址存入指针数组
int* parr[MAX_ROW] = { arr1,arr2,arr3 };
//&parr存放的是整个数组的地址
//parr单独存在时指的是数组首元素的地址,即arr1
//指针数组传参的本质,就是数组传参的本质
//传递的是数组首元素的地址,要使用什么要接收
printf("请输入九个数据:\n");
Scanf(parr);
printf("矩阵为:\n");
Printf(parr);
return 0;
}