1.数组名理解
- 数组名就是首元素(第一个元素)地址,和&arr[0] 意思一样。
- 首元素的意思是第一个元素的地址。
- 看这样图都是4个字节,他俩一样的
int main()
{
int arr[10] = { 0 };
int arr2[10] = { 0 };
printf("arr = %p\n", arr);
printf("arr +1= %p\n", arr + 1);
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0] + 1 = %p\n", &arr[0] + 1);
return 0;
}
1.2&arr
- 两个特殊情况
- sizeof(数组名),sizeof中单独放数组名,这里表示整个数组,计算的是整个数组的大小,单位字节
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
- 注意:整个数组的地址和数组首元素地址是有区别的,虽然地址都是一样的,但是对其进行指针+-运算的时候就能看出差异
- 地址的大小取决于平台(看例子)
- 32位(x86)平台下为 4个字节
- 63位(x64)平台下为 8个字节
int main()
{
int arr[4] = { 0 };
printf("arr = %p\n", arr);
printf("arr +1 = %p\n", arr + 1);
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("%zd", sizeof(arr));//这里表示整个数组
printf("%zd", sizeof(&arr));//计算的是地址的大小,拿到的是地址,4/8个字节
return 0;
}
2. 使用指针访问数组
- [ ]数组下标访问符号,通过下标访问到对应的元素,但是arr的本质是数组的首元素地址
- 因为arr把地址给到了pr,pr也有访问权限了。arr + j 先访问到对应地址,然后 *(arr + j) 对其解引用,就能访问到对应元素
- pr[ j ] == *(pr + j) == arr[ j ] == *(arr + j) *(j + arr)== j[arr]
- 我们知道arr[ j ] == *(arr + j)。arr[ j ]在访问的时候,编译器也会 转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的
-
如果还对 * (解引用)和 指针 +- 运算不了解,建议去看看我的这篇文章指针详解(1)
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* pr = arr;//arr表示首元素地址
for (int i = 0; i < sz; i++)
{
scanf("%d", arr + i);//因为scanf函数需要的是一个地址
}
for (int j = 0; j < sz; j++)
{
printf("%d ", pr[j]);// arr[j],[]数组下标访问符号,通过下标访问到对应的元素,但是arr的本质是数组的首元素地址
return 0;
}
补充:
- 数组就是数组,是一块连续的空间(数组的大小和数组元素个数和元素类型都有关系)
- 指针(变量),就是指针,是一个变量大小是(4/8个字节),可以使用指针访问数组
3. 一维数组传参的本质
- 传参过来是地址,sizeof此时计算的是地址的大小了
- arr[10],写成arr[],当然也没问题,本来就是用来声明有10个元素的,但是我通过首元素地址就能够访问数组的所有元素了
- 数组传参的本质是传递了数组首元素地址,所以形参和实参是同一个数组
//错误写法
void Print(int* arr1)//int arr[10],写成arr[]
{
int sz = sizeof(arr1) / sizeof(arr1[0]);//传参过来是地址,sizeof此时计算的是地址的大小了
for (int i = 0; i < 10; i++)
{
printf("%d ", *(arr1 + i));
}
//printf("%d ", arr + 1);
}
//正确写法
void Print2(int* arr, int sz)
{
for (int i = 0; i < 10; i++)
{
printf("%d ", *(arr + i));
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
Print(arr);//这里不能直接 &arr,拿不到整个数组地址
Print2(arr, sz);//正确写法
}
补充:
- sizeof本身是计算元素大小的,上面方法本质是先计算总数组大小,然后除以一个元素大小得出数组个数
- 假如传整个数组地址很尴尬,用起来麻烦,直接在外面算好传过去好很多
4. 冒泡排序
- 冒泡排序的核心思想就是:两两相邻的元素进行比较。 建议直接看图更加便于理解
- 最后一趟已然有序不用交换
- 该个数字交换最后一次时,两个交换都会到对应位置
- 每交换完一次,趟数都会逐渐减少
//以升序为例
void BubblSort(int* arr ,int sz)
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1;j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[6] = { 6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
BubblSort(arr, sz);
//输出
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
4.1稍微优化一下
- 因为通过观察可以发现,趟数和交换次数都是固定的,如图上假如经过第一趟后,已经有序,后面无需交换
- 此时可以给代码稍微优化下,在趟数循化内弄一个变量flag = 1;
- 在交换次数的循环里,如果元素一次都没有交换,说明数字已然有序,则flag == 1;即可跳出趟数循环
//以升序为例
void BubblSort(int* arr ,int sz)
{
//趟数
for (int i = 0; i < sz - 1; i++)
{
int flag = 1;//假设有序情况
//一趟过程交换
for (int j = 0; j < sz - i - 1;j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)//如果这一趟有序,后面不用排序了
break;
}
}
int main()
{
int arr[6] = { 6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
BubblSort(arr, sz);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
5. 二级指针
- 我们可以发现,对pp解引用(*pp),就是拿到a的地址,和&a一样
- 可以看下面的图以便理解
int main()
{
int a = 20;
int* p = &a;//p是一级指针
int** pp = &p;//pp是二级指针
printf("%p\n", *pp);
printf("%p\n", &a);
printf("%d\n", **pp);
printf("%d\n", *&a);//先取地址然后对其解引用,相当于脱裤子放屁,结果还是20
return 0;
}
6. 指针数组 存放指针的数组
- 数组的每个元素是指针类型的时候,这个时候就是指针数组。
- 指针数组:存放指针的数组
int main()
{
int va = 10;
int* pva = &va;
int* arr[5] = { 0 };//指针数组
char* arr2[5] = { 0 };//字符指针数组
double* arr3[5] = { 0 };//浮点指针数组
arr[0] = pva;//把va的地址放到了数组的第一个元素
return 0;
}
- 数组名表示首元素的地址,通过首元素的地址可以访问到其他成员
- 理解方式1:arr 指针+-运算,访问到里面的成员,因为指针数组存储的是每个数组的首元素地址,再通过 首元素 + j,即可访问到对应元素
- 理解方式2:先通过[ ],下标访问符号访问到对应下标元素,因为存储的是数组,所以也可以通过[ ] 下标访问,访问到对应元素
- *(rep1 + j) == rep[ j ]
- 补充:其本质都是指针+-运算,计算写成rep1[j]的形式,编译器也会转换成 (rep1 + j)的形式
-
int main() { int rep1[5] = { 1,2,3,4,5 }; int rep2[5] = { 2,3,4,5,6 }; int rep3[5] = { 3,4,5,6,7 };//数组名表示首元素地址 int* arr[3] = { rep1,rep2,rep3};//arr指针数组里存储的是每个数组的首元素地址通过数组的首元素地址可以访问到里面的其他成员 for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d ", *(*(arr + i) + j));//先访问到对应下标然后再访问里面的数组,数组的访问也是[],所以就是这样 } printf("\n"); } return 0; }
- 我们只是通过指针数组模拟而已
- 模拟二维数组,不是真正的数组,真正的二维数组是连续存放的
总结:
- 最后重要的是要多去写写代码,看的懂地,不一定会敲代码的,脑子会了手不会
- 所以要把会的输出出来,通过写代码和写博客,讲给别人听