一、指针的定义
一般格式:
数据类型 * 指针变量名 [=初始地址值];
数据类型是指针所指向的地址处的数据类型,如 int、char、float 等。
符号 *
用于通知系统,这里定义的是一个指针变量,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。比如,char * 表示一个指向字符的指针,float * 表示一个指向浮点数的指针。
取址运算符和取值运算符
取址运算符,使用 &
符号表示,作用是取出变量的内存地址
。如果要格式化输出地址,需使用格式占位符 %p。
取值运算符,使用 * 符号表示,作用是取出指针指向的内存地址处的数据值,也称为解引用运算符或间接引用运算符。
案例
通过指针修改所指向的值
#include <stdio.h>
int main()
{
// 通过指针修改所指向位置的值
double num = 2.88;
// 创建指针变量p1指向num变量的地址
double *p1 = #
// 创建指针变量p2指向指针变量p1(p1中存储的是num变量的地址,所以p2中存储的也是num变量的地址)
double *p2 = p1;
printf("%.2f \n", num);
*p1 = 3.88;
printf("%.2f \n", num);
*p2 += 10;
printf("%.2f \n", num);
return 0;
}
二、指针运算
1.指针加减整数
指针与整数的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动),指针移动多少,与指针指向的数据类型有关,数据类型占据多少个字节,每单位就移动多少个字节,比如一个 int 类型指针,+1 向后移动 4 个字节,-2 向前移动 8 个字节。
数组的元素在内存中连续存储的,我们通过数组元素来演示指针加减整数的情况。
代码如下:
#include <stdio.h>
int main()
{
int nums[] = {10, 20, 30, 40, 50};
int *ptr = &nums[0];
// 打印指针变量ptr的值,和指针变量ptr指向的值
printf("ptr = %p,*ptr = %d \n", ptr, *ptr);
// ptr指针加3,指针变量ptr指向int类型,占4个字节,此时指针向后移动12个字节
ptr += 3;
printf("ptr = %p, *ptr = %d \n", ptr, *ptr);
// ptr指针减2,指针变量ptr指向int类型,占4个字节,此时指针指向向前移动8个字节
ptr -= 2;
// 此时指针会向前移动8个字节
printf("ptr = %p, *ptr = %d \n", ptr, *ptr);
return 0;
}
2.指针自增自减
指针自增、自减本质上就是指针加减整数,自增地址后移,自减地址前移。下面我们利用指针的自增自减实现数组的遍历,代码如下:
#include <stdio.h>
int main()
{
short nums[] = {10, 20, 30, 40, 50};
// 计算元素个数(数组长度)
const int len = sizeof nums / sizeof nums[0];
short *ptr = &nums[0]; // 定义short*类型的指针变量ptr指向nums数组中第一个元素的地址
for (int i = 0; i < len; i++)
{
printf("元素索引:%d,元素地址:%p,元素值:%d \n", i, ptr, *ptr);
ptr++;
}
printf("=============\n");
// 利用指针自减再次遍历数组
// 此时指针超出数组界限,需先自减一次
for (int i = len - 1; i >= 0; i--)
{
ptr--;
printf("元素索引:%d,元素地址:%p,元素值:%d \n", i, ptr, *ptr);
}
return 0;
}
3.同类型指针相减
在C语言中,指针相减的结果表示的是指针之间元素的个数。
当两个指针相减时,结果是通过计算两个指针所指向的元素之间的差异得到的。这个结果是一个整数,表示了两个指针所指向的元素在内存中的相对位置差异。具体来说,如果指针p指向数组a[i],指针q指向数组a[j],那么p-q的结果就等于i-j,即两个元素之间的索引差。这个结果是一个整数,其值取决于指针所指向的数据类型的大小。
例如,如果指针是指向字符类型的,那么相减的结果就是两个字符在内存中占用的字节数之差;如果指针是指向整数类型的,那么相减的结果就是两个整数在内存中占用的字节数之差。
需要注意的是,指针相减的结果会自动转化为整形,这是因为指针算术运算的结果类型是整数。这个转化是通过将指针之间的差异除以数据类型所占的字节数来得出的。例如,如果两个指针之间相差10个字节,而数据类型是字符型(每个字符占1个字节),那么相减的结果就是10;如果数据类型是整数型(每个整数占4个字节),那么相减的结果就是2(因为10除以4得到2余2,但结果会向下取整为2)。
此外,C语言支持3种格式的指针算术运算,包括指针加上整数、指针减去整数和两个指针相减。
相同类型的指针可以进行减法运算,返回它们之间的距离,即相隔多少个数据单位。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
同类型指针相减的结果是一个 ptrdiff_t
类型数据,ptrdiff_t
类型是一个带符号的整数,格式输出中对应的格式占位符是 %td,相关案例如下:
#include <stdio.h>
int main()
{
int nums[] = {10, 20, 30, 40, 50};
// 定义指针变量指向数组中第一个元素的地址
int *ptr1 = &nums[0];
// 定义指针变量指向数组中第四个元素的地址
int *ptr2 = &nums[3];
printf("ptr2 - ptr1 = %td \n", ptr2 - ptr1); // 3 相差3个数据单位
printf("ptr1 - ptr2 = %td \n", ptr1 - ptr2); // -3 相差3个数据单位
// 定义两个指针相减
double d1 = 1.0, d2 = 2.0;
double *p1 = &d1, *p2 = &d2;
printf("p2 - p1 = %td \n", p2 - p1); // -1 相差一个数据单位
printf("p1 - p2 = %td \n", p1 - p2); // 1 相差一个数据单位
return 0;
}
4.指针的比较运算
指针之间可以进行比较运算,如 ==、<、 <= 、 >、 >=,比较的是各自指向的内存地址的大小,返回值是 int 类型整数 1 (true)或 0 (false)。案例演示如下:
#include <stdio.h>
int main()
{
// 比较指针
int nums[] = {10, 20, 30, 40, 50};
double n = 1.0;
// 定义指针变量指向数组第一个元素的地址
int *ptr1 = &nums[0];
// 定义指针变量指向数组第四个元素的地址
int *ptr2 = &nums[3];
// 定义指针变量指向数组第一个元素的地址
int *ptr3 = &nums[0];
// 定义指针变量指向变量n的地址
double *ptr4 = &n;
// 输出指针指向的地址
printf("ptr1 = %p \n", ptr1);
printf("ptr2 = %p \n", ptr2);
printf("ptr3 = %p \n", ptr3);
printf("ptr4 = %p \n", ptr4);
// 比较
printf("ptr1>ptr2 = %d \n", ptr1 > ptr2);
printf("ptr1<ptr2 = %d \n", ptr1 < ptr2);
printf("ptr1==ptr3 = %d \n", ptr1 == ptr3);
// 由于是不同类型的指针进行比较,所以会有一个警告
printf("ptr4>ptr1 = %d \n", ptr4 > ptr1);
return 0;
}
三、指针和数组
1.指针数组
指针数组(Pointer Array)是一个数组,其中的每个元素都是指针。
语法规则:
数据类型 * 指针数组名 [长度];
示例代码:
#include <stdio.h>
int main()
{
int num1 = 10, num2 = 20, num3 = 30;
// 定义一个整型指针数组
int *arr[3];
// 指针数组中的每个元素指向不同的变量地址
arr[0] = &num1;
arr[1] = &num2;
arr[2] = &num3;
for (int i = 0; i < 3; i++)
{
printf("%d,%p,%d \n", i, arr[i], *arr[i]);
}
return 0;
}
2.数组指针
数组指针(Array Pointer)是一个指针,它指向一个数组。注意,数组指针指向的是整个数组的地址而不是第一个元素的地址,虽然二者值是相同的,但在运算中会表现出不同。
语法规则:
数据类型 (*数组指针名)[长度]
示例代码:
#include <stdio.h>
// 数组指针:本身是一个指针,指向的是一个数组地址
int main()
{
int arr[5] = {10, 20, 30, 40, 50};
// 定义一个数组指针变量,指向数组(&nums就是整个数组地址)
int(*ptr)[5] = &arr;
// arr的类型是int *,ptr类型是int(*)[]
// int *ptr2[5] = arr; // 报错,无效的初始值设定项
// 二者值相同
printf("%p \n", arr);
printf("%p \n", ptr);
// 数组指针指向的是数组的地址,而不是第一个元素的地址
// 数组指针+1,会向后移动4*5=20个字节(整个数组的字节向后移动一倍),数组+1会向后移动4个字节
printf("arr+1 = %p \n", arr + 1);
printf("ptr+1 = %p \n", ptr + 1);
for (int i = 0; i < 5; i++)
{
printf("%d \n", (*ptr)[i]);
}
return 0;
}
数组指针和数组名的区别:
- 指向不同:数组名指向元素首地址,数组指针指向数组的地址。
- 类型不同:上面案例中,arr 的类型是 int[5],;ptr 的类型是 int(*)[5]。
- 可变性:数组名通常是不可变的;数组指针是可变的,你可以将它指向不同的数组。
- 初始化:数组名不需要显式初始化,它会自动指向数组的首元素;数组指针需要显式初始化,指定它将指向的数组。
- 访问元素:数组名访问数组元素不需要解引用;数组指针通常需要解引用才能访问数组元素。窗体顶端
四、指针和函数
1.传递指针给参数
当函数的形参类型是指针类型时,使用该函数时,需要传递指针、地址或者数组给该形参。
传地址或指针给函数
传数组给函数
数组名本身就代表数组首地址,因此传数组的本质就是传地址。