目录
1.认识指针
1.1指针==地址
1.2指针变量==存放地址的变量
2.为什么要用指针
2.1指针实现数据交换
2.2指针指向固定地址
3.指针与数组
3.1指针变量指向数组
3.2指针偏移遍历数组
4.指针与二维数组
4.1 初步认知
4.2 数组遍历的传统写法与指针写法对比
4.3数组指针
1.认识指针
变量有四要素:变量类型、变量名、变量值、变量地址。
1.1指针==地址
定义:指针存放的是变量的地址;同样的,指针也有地址,指针的指针存放的就是指针的地址(二级指针)。
如图,变量i的值是3,地址是2000;指针i_pointer的值是2000,地址是3020;说明指针存放的是变量的地址。
int main()
{
int a=10;
printf("a=%d\n",a);
printf("a的地址是:0x%p\n",&a);
printf("a=%d\n",*(&a));//*()是取值运算符,把内存地址的值取出来
}
1.2指针变量==存放地址的变量
定义:指针变量就是存放指针/地址的变量;同样的,整型变量是存放整型数的变量;字符变量是存放字符型数据的变量。
下面介绍了 *的标识作用、*的运算作用、变量访问的两种方式
int main()
{
int a=10;
int *p ;//这里的 * 是一个标识符,告诉系统这是一个指针变量,用来保存别人的地址
p = &a;
printf("变量名访问a:%d\n", a);
printf("a的地址是:0x%p\n", &a);
printf("地址访问a:%d\n",*(&a));//这里的 * 是取值运算符,把内存地址的值取出来
printf("指针变量的方式访问a:%d\n", *p);
//一个变量的访问有两种方式:1.通过变量名a 2.通过地址 *(&a)
return 0;
}
备注:
1.* 只产生在指针变量定义或声明的时候,其他情况下是取值运算符或者乘法运算符
2.指针变量的定义:int *p;指针变量的赋值:p = &a; 可以写成一条语句 int *p=&a;
既然指针变量是存放别人地址的变量,那什么要区分类型呢?
前面的代码中,用了 int *P来储存地址,试问我们能不能用 char?
int main()
{
int a=0x1234;
int *p=&a;
int *c=&a;
printf("%p=%p\n",p);
printf("%p=%p\n",p);//能取同一个地址
printf("a=%x\n",*p);输出a=1234
printf("a=%x\n",*c);输出a=34
//取值的时候出了问题,取值运算符会根据指针变量的类型,访问不同大小的空间
//int型有4个Byte=32bit, char只有1Byte=8bit
return 0;
}
指针变量的类型决定了指向空间的大小和增量的大小
2.为什么要用指针
2.1指针实现数据交换
void changeData(int data, int data2)
{
int tmp;
tmp = data;
data = data2;
data2 = tmp;
}
int main()
{
int data = 10;
int data2 = 20;
printf("交换前:data=%d,data2=%d\n", data, data2);
changeData(data, data2);
printf("交换后:data=%d,data2=%d\n", data, data2);
return 0;
}
交换前:data=10,data2=20
交换后:data=10,data2=20
通过内存分析,函数封装并没有改变main函数里的内存空间。因此需要通过间接访问的方式,取出data、data2的值,再进行交换。
void changeData(int *pdata, int *pdata2)
{
int tmp;
tmp = *pdata;
*pdata = *pdata2;
*pdata2 = tmp;
}
int main()
{
int data = 10;
int data2 = 20;
printf("交换前:data=%d,data2=%d\n", data, data2);
changeData(&data, &data2);
printf("交换后:data=%d,data2=%d\n", data, data2);
return 0;
}
通过函数封装,修改被调函数的值,必须通过指针间接访问,如果通过原值传递,达不到相应的效果。
2.2指针指向固定地址
int main()
{
int a = 10;
printf("address of a is 0x%p\n", \&a);//这里找到a=10的地址是0x00BAF71C,我们想让指针p占用这个地址
//00BAF71C 地址是一个整数,要转化成int \* 使它变为地址,因此 地址是 (int *)0x00BAF71C
int* p = (int\*)0x00BAF71C;
printf("p=0x%p\n", p);
return 0;
}
完整写法:volatile unsigned int* p = (volatile unsigned int*)0x00BAF71C; unsigned 无符号的整形数, volatile 防止编译器优化地址
3.指针与数组
3.1指针变量指向数组
可以用指针指向一个数组元素
int a[3]={1,5,9};
int *p; //定义p为指向整型变量的指针变量
p=&a[0]; //把a[0]元素的地址赋给指针变量p
p=a; //数组名代表数组中首元素的地址
3.2指针偏移遍历数组
p+i中的i为偏移量,而非对p进行加1,偏移多少与指针p的变量类型有关,int型偏移4个字节,char型偏移一个字节。
int main()
{
int arr[3] = { 1,2,3 };
int* p;
p = &arr[0];// 显然&arr[1]的地址就是p+1
printf("0元素是:%d\n", *p);
printf("1元素是:%d\n", *(p+1));
printf("2元素是:%d\n", *(p + 2));
for ( int i = 0;i < 3;i++)
{
printf("adress:0x%p,%d\n", (p+i),*(p+i));
}
return 0;
}
0元素是:1
1元素是:2
2元素是:3
adress:0x00CFFA04,1
adress:0x00CFFA08,2
adress:0x00CFFA0C,3
对于printf("adress:0x%p,%d\n", (p + i), *(p + i));的其他写法①②
for ( int i = 0;i < 3;i++)
{
printf("%d",*p++);//写法1
}
或者
for ( int i = 0;i < 3;i++)
{
printf("%d");
p++;//写法2
}
如果需要再次遍历数组,需要重新回到数组首元素 p=arr!
for ( int i = 0;i < 3;i++)
{
printf("%d");
*p++;
}
p=arr;
for ( int i = 0;i < 3;i++)
{
printf("%d",*p++);
}
其他写法③④
int arr[3]={1,2,3};
int *p=arr;
for(i=0;i<3;i++)
{
printf("%d",p[i]);//写法3:指针当做数组名,下标法访问
}
p=arr;
for(i=0;i<3;i++)
{
printf("%d",*(arr+i));//写法4:数组名当做首地址
}
不能使用 printf("%d",*arr++),两个原因如下:
\*p是指针变量 且sizeof(p)=8八个字节表示一个地址
\*arr是指针常量 且sizeof(arr)=12=3\*4
3.3 练习:函数封装数组初始化及遍历
void initArray(int *parr, int size)//初始化数组
{
int i;
for (i = 0;i < size;i++)
{
printf("请输入第%d个元素的数据:", i + 1);
scanf("%d", parr);
parr++;
}
}
void printArray(int *parr, int size)//输出数组
{
int i;
for (i = 0;i < size;i++)
{
printf("%d ", *parr);
parr++;
;
}
}
int main()
{
int arry[5];
int size = sizeof(arry) / sizeof(arry[0]);//数组的大小=数组所有元素的大小/首元素的大小
initArray(arry, size);//数组的输入
printArray(&arry[0], size);//arry=%arry[0],对于数组,变量名可以作为指针,两种写法都可以
return 0;
}
4.指针与二维数组
4.1 初步认知
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
对于二维数组a,我们将其分为行和列,行数组:a[0], a[1], a[2]; 列数组:a[0][0], a[0][1], a[0][2], a[0][3]
a是二维数组的数组名==行数组“a[0]、a[1]、a[2]”的首地址==a[0]的地址,因此 *a==a[0] 的值
此时a[0]是列数组“a[0][0]、a[0][1]、a[0][2]、a[0][3]”的数组名==列数组a[0][0]的首地址==2000
小结: 对于i行j列的二维数组a: *(a+i)+j==a[i]+j==&a[i][j]
由于a=a[0]的地址,a[0]这一行的地址起始于a[0][0],所以也有a=*(a+i)+j==a[i]+j==&a[i][j]
a+1就是行数组的+1项a[1]的地址,就是行数组偏移一次,偏移16个字节; a[0]+1就是列数组偏移一次,偏移4个字节, sizeof(int)=4
4.2 数组遍历的传统写法与指针写法对比
int main()
{
int arr[3][4] = { {11,22,33,44},{12,13,14,15},{22,66,77,88} };
int i;
int j;
for (i = 0;i < 3;i++)
{
for (j = 0;j < 4;j++)
{
printf("地址是%p,值为%d\n", &arr[i][j], arr[i][j]);//传统写法
printf("地址是%p,值为%d\n", arr[i]+j, *(arr[i]+j));//数组名==地址
printf("地址是%p,值为%d\n", *(arr+i)+j, *(*(arr + i) + j));//上面提到arr[i]==*(arr+i)
puts("=================\n");
}
}
return 0;
}
总结:
4.3数组指针
对于二维数组,arr++与p++不对应,所以能不能定义一个指针,让指针偏移的时候也偏移对应大小的数组? 数组指针应运而生,定义一个指针,指向数组。
int main()
{
int arr[3][4] = { {11,22,33,44},{12,13,14,15},{22,66,77,88} };
int i,j;
int(*p)[4];//数组指针//[ ]重点在列
p = arr;
for (i = 0;i < 3;i++)
{
for (j = 0;j < 4;j++)
{
printf("%d\n", *(*(p+ i)) + j);//p=arr
}
}
return 0;
}
int(*p)[4]:首先从P 处开始,先与*结合,说明P是一个指针然后再与[ ]结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的。
所以P是一个指向含有4个元素的数组的指针。
下面打印地址验证,地址偏移16个字节:
printf("%p\n", p);
printf("%p", ++p);
结果为:
00B3F7A4
00B3F7B4