指针(2)
1.数组名的理解
使用指针访问数组的内容时,有这样的代码:
int arr[10]={1,2,3,4,5,6,7,8,9,10}int * p=&arr[0];
&arr[0] 的方式拿到了数组的第一个元素的地址,但是其实数组名本来就是地址,而且还是首元素的地址:
int main()
{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;
}
可以从上面的运行结果中就可以看见:确实数组名表示的就是数组首元素的地址。
如果是这样的话,嗯~那所有的情况都是数组名表示的是首元素的地址吗?
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0;
}
运行结果为什么是 40 呢?如果是数组首元素地址的话,结果应该是 4 or 8
其实数组名是数组首元素地址这个说法是正确的,但是有两种特殊情况:
- sizeof(数组名),数组名表示的是整个数组的大小
- &数组名,数组名表示的是整个数组,取出的是整个数组的地址
除此之外,遇到所用的数组名表示的都是数组首元素的地址
int main()
{int arr[10] = { 0 };printf("%d\n", sizeof(arr));printf("\n&arr=%p\n", &arr); //代码1 //取出的是整个数组的地址printf("arr=%p\n", arr); //代码2 //数组名表示的是数组首元素,所以取出的是数组首元素地址printf("&arr[0]=%p\n", &arr[0]);//代码3 //取出的是数组首元素地址//运行上面的代码会发现代码123的结果都是一样的,why???
为什么三个输出的结果都是一样的呢?
代码23相同是因为,本身表示的就是数组首元素地址,代码1和代码23相同是因为 &arr--取出整个数组的地址时一般都是取首个数组元素的地址,这样也可以顺藤摸瓜找到整个数组的所有地址
上面的解释是否正确?还需要进行进一步的检测:
int main()
{int arr[10] = { 0 };printf("%d\n", sizeof(arr));printf("\n&arr = %p\n", &arr); //代码1 //取出的是整个数组的地址printf("arr = %p\n", arr); //代码2 //数组名表示的是数组首元素,所以取出的是数组首元素地址printf("&arr[0] = %p\n", &arr[0]);//代码3 //取出的是数组首元素地址//运行上面的代码会发现代码123的结果都是一样的,why???//代码23相同是因为,本身表示的就是数组首元素地址,代码1和代码23相同是因为 &arr--取出整个//数组的地址时一般都是取首个数组元素的地址,这样也可以顺藤摸瓜找到整个数组的所有地址//让我们进行“+1操作”再次检验一下,上面的解释是否正确?printf("\n&arr+1 = %p\n", &arr+1); // ? +40printf("arr+1 = %p\n", arr+1); // int * +4printf("&arr[0]+1 = %p\n", &arr[0]+1); // int * +4return 0;
}
2.使用指针访问数组
int main()
{int arr[10] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;//p<==>arrint i = 0;for (i = 0; i < sz; i++){scanf("%d", &arr[i]);//scanf("%d", p + i);//scanf("%d", arr + i);}for (i = 0; i < sz; i++){printf("%d ", arr[i]);//arr[i]=*(arr+i)// arr[i]=*(p+i)// p[i]=*(p+i)//加法交换律:arr[i]=*(arr+i)=*(i+arr),arr[i]=*(p+i)=*(i+p)//*(i+arr)=i[arr],*(i+p)=i[p]//printf("%d ", i[p]);//printf("%d ", i[arr]);//*(i+arr)-->*(arr+i)-->arr[i]//printf("%d ", *(p + i));//printf("%d ", *(arr + i));//上面打印的语句多种多样,但是常常使用的就是第一个,其余作为拓展}
数组名 arr 是数组首元素的地址,可以赋值给 p ,其实数组名 arr 和 p 在这里是等价的,我们可以通过 arr [ i ] 来访问数组,那么 p [ i ] 也同样可以访问数组。
将 p [ i ] 换成 *( p + i )也是可以打印的,所以本质上 p [ i ] 是的等价于 * ( p + i )。
同理 arr[ i ] 也等价于 * ( arr + i ),其实数组的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后进行解引用的操作来访问的。
3.一维数组传参的本质
之前我们都是在函数外部计算数组的大小,那么可以在函数内部求得数组的元素个数吗?
void test(int * arr) //数组的形参可以有两种形式,虽然形式不同但是本质是一样的
{int sz = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sz);//得到的并不是元素个数:1(x86) or 2(x64)
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };test(arr);//这里的数组名就是数组的首元素的地址return 0;
}
运行结果为什么是 1 呢?
数组传过去时是不需要再次创建新的数组,因为传的是数组的首元素地址
void test(int arr[])//int arr[]中的arr是一个指针变量,等价于 int * arr ,是一个接收变量的指针变量那么指针变量的大小就只与环境有关,要么是(4/4=1),要么是(8/4=2),数组的形参可以用数组的形式来接收,因为数组的本质就是指针:*(arr+i)
总结:
- ⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
- 要计算数组的元素个数,只能在主函数传参之前进行,将元素个数(sz)作为参数传入,这样才可以遍历数组
4.冒泡排序
核心思想:两两相邻的元素进行比较。
//冒泡排序
int count = 0;
BubbleSort(int arr[], int sz)
{int i = 0;//趟数for (i = 0; i < sz - 1; i++){//一趟冒泡排序的过程for (int j = 0; j < sz-1-i; j++){count++;//一对元素的比较if (arr[j] > arr[j + 1]){//进行元素的交换int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}int main()
{int arr[10] = { 9,0,1,2,3,4,5,6,7,8};//降序//想把数组调整成升序int sz = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\ncount=%d\n", count);return 0;
}//优化代码:
int count = 0;
BubbleSort(int arr[], int sz)
{int i = 0;//趟数for (i = 0; i < sz - 1; i++){ int flag = 1;//假设已经有序了//一趟冒泡排序的过程for (int j = 0; j < sz - 1 - i; j++){count++;//一对元素的比较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[10] = { 9,0,1,2,3,4,5,6,7,8 };//降序//想把数组调整成升序int sz = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\ncount=%d\n", count);return 0;
}
5.二级指针
指针变量也是变量,是变量就有地址,那么指针变量的地址存放在哪里?
存放指针变量地址的指针就是二级指针。以此类推,也有三级指针,存放的就是二级指针变量的地址,四级、五级指针变量……也是有的,但是用的最多就是二级指针变量了,三级指针用的很少。
int main()
{int a=10;int * pa=&a;int * * ppa=&pa; // * ppa中的* 代表的是 ppa 是一个指针变量, int * 是 pa 变量//的类型return 0;
}
对于二级指针的运算:
* * ppa 先通过 * ppa 找到 pa 的地址 ,然后对 pa 进行解引用操作:* pa ,那找到的就是 a
* * ppa =19;
//等价于 *pa = 19;
//等价于 a = 19;
6.指针数组
指针数组类比于整型数组(存放的是整型数据的数组)、字符数组(存放的是字符类型的数组),那么指针数组存放的就是指针类型的数组。也就是指针数组的元素是指针。
指针数组的每个元素是地址,又可以指向一块区域。
7.指针数组模拟二维数组
//指针数组模拟二维数组
int main()
{int arr1[] = {1,2,3,4,5};int arr2[] = {2,3,4,5,6};int arr3[] = {3,4,5,6,7};//数组名是首元素的地址,类型是 int * int *arr[3] = {arr1,arr2,arr3};int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");//打印完一行换一行}return 0;
}
parr[ i ] 是访问的是 parr 数组的元素,parr[ i ]找到的数组元素的指的是整型一维数组的地址,parr[i][j]访问的就是整型一维数组中的元素。
指针数组模拟出二维数组:实际上是指针数组模拟出来的二维数组的效果,而不是真正的二维数组,这是因为指针数组里面的元素是不连续的,而二维数组里面的每个数组是连续的