2023.08.14
通过指针引用数组元素
当指针变量指向数组元素时,数组元素的指针就是数组元素的地址。
int main()
{
int arr[3]={1,2,3};
int *parr=&arr[0];//定义一个指向整型变量的指针变量,把arr[0]的地址赋给指针变量parr
return 0;
}
而在C语言中,数组名代表数组中首元素(即序号为0的元素)的地址,因此在赋值时这两句话等价。
int *parr=&arr[0]; p的值是a[0]的地址
int *parr=arr; p的值是数组arr首元素(即a[0])的地址
除了这两种情况:
sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的长度
&(数组名),这里数组名表示整个数组的地址
引用数组元素
下标法:a[i]
指针法:*(parr+i) 或 *(arr+i)
指针的运算
指针就是地址,对地址没有必要进行乘除运算,那对于加减运算,意义与步骤该如何理解?
在什么情况下需要对地址进行加减运算呢?
我们知道,数组元素在内存中是连续存放的,当需要对一个数组中的所有元素进行地址使用时,可以依照内存地址的连续存放,对地址进行加减运算从而得到其他数组元素的地址。
在指针已经指向一个数组元素时:
加一个整数(+或者+=) p+1 指向同一数组的下个元素
减一个整数(- 或者 -=) p-1 指向同一数组的上个元素
自加运算 p++
自减运算 p--
两个指针相减(只有p1和p2都指向同一个数组中的元素时才有意义) p1-p2
注意:在执行指针加减运算时,不是单纯的将指针的值(地址)加上/减去1,而是加上一个指针变量所指向变量的类型占用的字节数,即p+1*d(d为一个所指变量类型占用的字节数)。
如果p=&a[0],那么 (p+i) 和 (a+i) 都为 a[i] 的地址,*(p+i) 和 *(a+i) 都为 a[i] 。(等价)
指针相减:p2-p1 的结果并不为单纯的地址相减,而是两个地址之差除以数组元素的长度(一个数组元素所占的字节个数),结果为p2所指的元素和p1所指的元素之间相差的元素个数。
有一个整形数组a,其中有10个元素,要求输出数组中的全部元素。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* parr = arr;
//等价 int* parr = &arr[0];
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
printf("%d ", *(parr + i));
printf("%d ", *(arr + i));
//引用数组中元素有三种方法:下标法,数组名计算数组首元素地址,指针变量指向数组元素
}
return 0;
}
//for循环也可以这样写:
//for(p=arr;p<(arr+sz);p++)
//{
// printf("%d ",*p);
//}
//不可以写成arr++; 因为数组名代表的是数组首元素的地址,是一个指针型常量。
//注意在执行循环之前,注意p的指向要为&arr[0],否则会导致输出错误。
下标法直观,但是运行效率没有直接使用指针变量指向数组元素效率高。
指向数组元素的指针变量也可以带下标(p[i])。
程序编译时,对下标处理是这样的:将下标转换为地址,将p[i]处理成*(p+i)。
使用数组名作为函数参数
在前面也学习有将数组名(数组首元素的地址)作为函数实参传递给调用函数,这里阅读书本,进行一个更加清晰的总结。
实参类型 | 变量名 | 数组名 |
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组首元素的地址 |
通过函数调用能否改变实参的值 | 不能改变实参变量的值 | 能改变实参变量的值 |
常使用这种方法通过调用函数来改变实参数组的值。
将数组a中的10个整数按相反顺序存放。
void change(int* arr)//指针变量作为形参接收地址,int arr[]也可以
{
int j = 0;
int temp = 0;
for (j = 0; j < 5; j++)
{
temp = *(arr + j);
*(arr + j) = *(arr + 9 - j);
*(arr + 9 - j) = temp;
}
}
//下面这个是另一种交换
//void inv(int* arr, int n)
//{
// int temp = 0;
// int i =0,j=0 ;
// int m = (n - 1) / 2;
// for (i = 0; i <= m; i++)
// {
// j = n - 1 - i;
// temp = arr[i];
// arr[i] = arr[j];
// arr[j] = temp;
// }
// return;
//}
int main()
{
int array[10] = { 0 };
int i = 0;
for (i = 0; i < 10; i++)
scanf("%d", &array[i]);
change(array);//将数组名(数组首元素地址)传递过去
i = 0;
for (i = 0; i < 10; i++)
printf("%d ", array[i]);
return 0;
}
定义实参数组时必须指定数组大小,因为内存要开辟相对应的储存空间,
定义形参数组时可以不指定数组大小,因为形参数组名实际上是一个指针变量。
用指针方法对10个整数按由大到小顺序排序。
void change(int arr[])
{
int j = 0;
int k = 0;
for (j = 0; j < 9; j++)
{
for (k = 9; k >=j; k--)
{
if (arr[k] < arr[k + 1])
{
int temp = arr[k];
arr[k] = arr[k + 1];
arr[k + 1] = temp;
}
}
}
}
int main()
{
int array[10] = { 0 };
int* p = array;
int i = 0;
for (i = 0; i < 10; i++)
{
scanf("%d", p++);
}
change(array);
for (i = 0,p=array; i < 10; i++)
{
printf("%d ", *p++);
}
return 0;
}
使用数组名(地址)作为实参变量,在输入与打印时使用指针变量。
另外注意的是,这里按由大到小的顺序输出整型数组元素,在写循环时使用p--比较合适。
指针数组与数组指针
指针数组
顾名思义,指针数组是存放指针的数组,每个数组元素都是指针变量。
数组的命名格式为: 类型说明符 数组名[常量表达式]
指针数组当然也按照这个格式: 类型说服符 * 数组名[常量表达式]
例如:int *p[10] char *p[2]
使用指针数组可以模拟多维数组
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };//指针数组存放的是每个数组的首元素地址
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ",*(*(parr+i)+j));
}
printf("\n");
}
return 0;
}
如上面的代码,定义三个一维数组,然后定义一个指针数组,利用指针数组可以将这三个一维数组变成一个二维数组。
指针数组里面的数组元素都为数组名(数组首元素地址),在打印的时候,对于%d的指向内容,可以有这两种写法:
下标型:parr[ i ][ j ] 这是引用数组元素时最基本的写法。
指针型:parr是地址
parr+i 是指针数组中某数组元素的地址
*(parr+i) 是指针数组中某元素,这是个地址
*(parr+i)+j 是某数组中某整型元素的地址
*(*parr+i)+j) 是某数组中某整形元素