深入理解指针2
深入理解指针2
数组名的理解
数组名就是首元素的地址
int arr[]={1,3,2};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
但是有两种情况除外,
1.sizeof(数组名),sizeof操作符统计的是整个数组的大小,并不是第一个元素的大小, 单位是字节
2.&数组名,它取的整个数组的地址,整个数组的地址其实就是首元素的地址,数组地址+1,跳过的是整个数组
除此之外,你遇到的任何数组名都表示首元素的地址
int arr[]={1,2,34};
printf("%d",sizeof(arr));
//如果是一维数组,打印数组的地址其实就是首元素的地址;
int arr[]={1,3,2};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
printf("%p\n",&arr);//三者打印结果一样
printf("%p\n",arr+1);//int类型的指针+1,跳过4个字节
printf("%p\n",&arr[0]+1);//跳过4个字节
printf("%p\n",&arr+1);//并不是跳过4个字节,跳过整个数组
//如果是二维数组,打印出来的地址是第一行的地址,也就是第一个一维数组的地址,而第一个一维数组的地址也就是首元素的地址
int main()
{
int stp[2][3] = { {1,2,3},{4,5,6} };
printf("%p\n", stp);
printf("%p\n",&stp[0]);
printf("%p\n",&stp);//三者打印结果一样
printf("%p\n", &stp[0] + 1);
printf("%p\n", stp+1);
printf("%p\n", &stp + 1);
return 0;
}
指针访问数组
代码示例1
int main()
{
int arr[10]={0};
int*p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<sz;i++)
{
scanf("%d",p+i);
}
//循环结束后,p里面存的还是arr,i已经变成10了
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
//printf("%d ",*(p+i));//这样写也是可以的
}
return 0;
}
代码示例2
int main()
{
int arr[10]={0};
int*p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<sz;i++)
{
scanf("%d",p++);//通过指针加减也就是指针偏移的方式,让指针移动
}
//循环结束后,指针p指向下标为10的元素的地址了,i已经变成10了
//p++ 的方式会使 p 的值发生改变,p + i 的方式本身不会改变指针 p 的值,所以这里p要重新赋值
p=arr;
for(i=0;i<sz;i++)
{
printf("%d ",*(p+i));
}
return 0;
}
为了更透彻的理解指针,再举一个例子
//打印结果都是一样的
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9};
int*p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
printf("%d ",*(p+i));
printf("%d ",*(arr+i));//因为p=arr,p里面存放的就是arr的地址,所以还可以写成下面的样子
printf("%d ",p[i]);
printf("%d ",i[arr]);
arr[i]=*(arr+i)//arr[i]这样的写法只是一种形式,即使这样写,编译器处理的时候也会转成后面的写法,转换成指针偏移的方式
p[i]=*(p+i);//同理
//我们知道加法是支持交换律的,所以下面的写法也是对的,不管怎么写,编译器最终都会转成指针偏移
arr[i]=*(arr+i)=*(i+arr)=i[arr];
}
return 0;
}
p+i到底是不是下标为i的地址呢?我们可以验证一下
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9};
int*p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<sz;i++)
{
printf("%p========%p\n",p+i,&arr[i]);
}
return 0;
}
一维数组传参的本质
想一下下面的代码为什么会出错?
void print(int arr[])//形参写成数组的形式,一维数组传参,形参的大小可以省略
{
int i=0;
int sz=sizeof(arr)/sizeof(arr[0]);//其实是得不到数组元素的个数的,统计的其实是指针变量的大小,也就是地址的大小
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[]={1,2,3,4,5};
print(arr);//print函数负责把数组里的元素都打印出来,传给形参的其实是地址
return 0;
}
因为数组传参的本质其实是把数组的首元素的地址传给形参,所以形参接收到的其实是地址,也就是指针,所以统计 sizeof (arr)其实是统计的地址大小,如果平台是32位的,那么它的大小就是4个字节,而sizeof(arr[0])的大小是4 ,所以sz=4/4=1,所以打印在屏幕上只打印了1一个数字
//那既然函数内部不能统计数组元素的个数,只能在main函数内部统计数组元素个数我们就在main函数内部计算,然后作为实参传给形参。
//因为实参传给形参的数组名其实是首元素地址,那么用sizeof统计的其实是指针变量的大小
void print(int*arr,int sz)//形参写成指针的形式
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[]={1,2,3,4,5};
int sz=sizeof(arr)/sizeof(arr[0]);
print(arr,sz);//print函数负责把数组里的元素都打印出来,传给形参的其实是地址
return 0;
}
//我们用下面的代码验证下就知道了,数组名其实就是地址
void print(int arr[],int sz)
{
printf("%d\n",sizeof(arr));
}
void print(int*arr,int sz)
{
printf("%d\n",sizeof(arr));
}
int main()
{
int arr[]={1,2,3,4,5};
int sz=sizeof(arr)/sizeof(arr[0]);
print1(arr,sz);
print2(arr,sz);
return 0;
}
总结:
数组传参,无论形参是写成数组的形式还是指针的形式,本质上都是一样的,只是形式不同而已。
编译器最终都会转成指针的方式处理。
冒泡排序
方法1
void double_paixu(int * arr,int sz)
{
int i = 0;
//排序第一步根据元素个数确定排序的轮数
for (i=0;i<sz-1;i++)//i的最大取值为3
{
//排序第二步,在每一轮排序中确定要比较的元素对数,随着轮数的增加,要排序的元素对数依次递减,所以比较的次数
//根i有关系,因为变量i代表的是比较的轮数
int j = 0;//j表示元素的下标,又表示元素比较的对数
//内层for循环用于比较元素大小和交换,如果是降序,发现前一个元素小于后一个元素,就交换
for (j = 0; j <sz-1-i ;j++)//
{
if (arr[j] < arr[j + 1])//一共有5个元素,但是j最大的取值为3,当j=3时,arr[3]和arr[4]进行比较
//如果j=4,那就数组越界了,因为arr[4]和arr[5]进行比较,但是数组只有5个元素
{
int d = 0;
d = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = d;
}
}
}
}
int main()
{
int arr[] = { 12,21,2,45,1};
int sz = sizeof(arr) / sizeof(arr[0]);
double_paixu(arr,sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
//优化版:如果要排序的序列本身是有序的,那么第一轮比较后发现没有交换,就证明是有序的,就不用进入下一轮的比较了
void double_arr(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)//i的最大值为2
{
int j = 0;
int flag = 1;//假设是有序的
//如果是降序的话,就是找出最小值,如果a<b就交换
for (j = 0; j < sz - 1 - i; j++)//j的最大值为2a
{
if (arr[j] > arr[j + 1])
{
int temp = 0;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//如果进来了,就说明是无序的
flag = 0;
}
}
//如果第一轮循环结束后,flag还是等于1,说明是有序的,就不用进入下一轮了
if (flag == 1)
{
break;
}
}
//跳出循环来到这
}
void print(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 1,2,3,4};
int sz = sizeof(arr) / sizeof(arr[0]);
double_arr(arr, sz);
print(arr, sz);
return;
}
方法2:用qsort函数的方法
二级指针
指针变量也是变量,既然是变量就要像内存申请一块空间,既然有自己的内存空间,那肯定就有地址,那么指针变量的地址存放在哪里呢?这就是二级指针
1.二级指针变量创建
int a=10;
int*p=&a;//p存放的是a的地址,*本身p是一个指针变量,int表示p指向一个整型数据,int*是变量p的类型,整型指针
int* *pa=&p;//pa存放的是一级指针变量p的地址,*表示pa是一个指针变量,int*表示pa指向一个一级指针地址,int**是变量pa的类型,是一个二级指针
int** *paa=&pa;//paa存放的是二级指针变量pa的地址,*表示paa是一个指针变量,int**表示paa指向一个二级指针地址,int***是变量paa的类型,是一个三级指针
2.二级指针解引用
int main()
{
int a = 10;
int* p = &a;
int* * pa = &p;
int** * paa = &pa;
printf("%d\n",*p);//p解引用后,打印a的值
printf("%d\n",*pa)//pa解引用后,打印一级指针变量p的地址
printf("%d\n",*paa);//paa解引用后,打印二级指针变量pa的地址
//如果想打印a的值
printf("%d\n",*p);//对一级指针变量解引用
printf("%d\n",*(*pa));//对二级指针变量解引用
printf("%d\n",*(*(*paa)))//对三级指针变量解引用
printf("%d\n", **pa);//这样写也是对的,不带括号也是可以的
printf("%d\n", ***paa);//对三级指针变量接
return 0;
}
总结:
1.二级指针变量没有什么牛逼的,和一级指针变量一样,都是指针变量,用来存放地址
2.二级指针变量用来存放一级指针变量的地址,同理,三级指针变量用来存放二级指针变量的地址
指针数组
数组有很多种类型,我们之前已经学过的有字符数组,整型数组,字符数组是用来存放字符串的,整型数组用来存放整型。
今天我们学习的指针数组,就是用来存放指针的,即地址。指针数组里的每一个元素都是指针。当然指针数组也有很多种类型,有int*,char*, long*, double*等等
//定义一个指针数组,然后把指针数组里每一个指针指向的元素打印出来
int main()
{
int a=10;
int b=20;
int c=40;
int*arr[]={&a,&b,&c};//arr[]表示数组,int*表示数组里的每一个元素都是int*类型的指针,即都是指向int类型的数据
int i=0;
for(i=0;i<3;i++)
{
printf("%d ",*(arr[i]));
}
return 0;
}
//上面的代码没什么问题,但是我们很少这样去用,一般都指针数组模拟二维数组
int main()
{
int arr1[]={1,2,3,4};
int arr2[]={5,6,7,8};
int arr3[]={9,10,11,12};
int*p[]={arr1,arr2,arr3};
int i=0;
//外层循环控制指针数组arr1 arr2 arr3里的每一个元素
for(i=0;i<3;i++)
{
int j=0;
//内层循环控制指针数组里的每一个指针指向的整型
for(j=0;j<4;j++)
{
printf("%d ",*(p[i]+j));//p[i]是数组p里下标为i的元素,当i=0时,拿到的是arr1,也就是数组名,然后再加j,就是让指针指向arr1数组里下标为j的地址,然后解引用,就拿到arr1数组里的元素
// printf("%d ",p[i][j]);
// printf("%d ",*(*(p+i)+j));
//这几种写法都可以
}
printf("\n");
}
return 0;
}