C语言-指针(二)
一级指针
一级指针指的是存储了变量地址的指针
一级指针的变量类型是 类型 + *
一级指针的类型与变量的类型有些不同
例:int * p 前面的int * 是该地址的类型
int a = 0;
int * p = a;
这里的指针 p 就是一级指针
二级指针
指针变量也是变量因此也会有地址
而存储指针变量地址的指针称为二级指针
语法结构:指针类型 ** 变量名 int ** pp = &p;
二级指针的类型是 类型 + **
例:int ** p 前面的int ** 是该二级指针的类型
int a = 10;
int *p = &a; // p 是一级指针,存储变量a的地址
int **pp = &p; // pp 是二级指针,存储指针p的地址
// 通过 pp 修改 a 的值
**pp = 20; // 等价于 *(*pp) = 20 → *p = 20 → a = 20
二级指针的变量名可以随意取,上述例子使用“ pp ”来表示,是一种命名习惯
并不表示二级指针的变量名要进行双写
指针形式得理解
int a = 10; int *p = &a; 这里的int表示的是被取变量地址的类型是int*p ,前面的 * 表示的是p是一个指针变量
int * *pp = &p; int* 表示的是被取的一级指针的变量是int**pp 前面的 * 表示的是pp是一个指针变量
指针的套娃
一级指针到二级指针在形式上就是多加了一个 *
在二级指针上在多加一个 * 就变成了三级指针,三级指针上在加一个 * 就变为四级以此类推,可以无限的套娃,但在平时使用中,二级指针已经满足使用要求,一般不使用三级以后的指针
一级指针二级指针的解引用
int a = 10;
int * p =&a;
int ** pp =&p;printf(“%p”,*pp); 这里使用解引用的个数决定了访问pp的那个地址的数据一个 * 表示访问的是二级指针 pp 的地址
printf(“%p”,**pp); 两个 * 表示访问的二级指针指向的一级指针的地址
printf(“%d”,**pp); 如果要访问一级指针的值则将前面的 %p 改为 %d
数组指针
数组指针的本质上还是数组,只是数组中存放的元素变成了指针
语法结构为:数据类型 * 数组名 [ 数组长度 ] ;
int *ptrArray[5]; // 声明一个包含5个int型指针的数组
char *strArray[3]; // 声明一个包含3个char型指针的数组
数组指针模拟二维数组
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};int* parr[2] = {arr1, arr2};//数组指针存放的地址是数组首元素的地址模拟二维数组的访问
printf("%d",arr[i][j])
访问数组时,使用两个下标来确认访问那个数组,即该数组的两个元素
这里的arr[i][j] 等价于 *( *(arr+1)+j)
*( *(arr+i)+j) 中间的*(arr+1)表示的取下标为i的数组外层的*( +j)表示的是取数组下标为j的元素
形式上虽然和二维数组相同,但本质上并不是二维数组
在运行时,是以指针的方式进行计算的在使用printf进行打印时,可以使用for循环进行遍历
将i,j的值进行循环即可将数组指针的所以值打印出来
字符指针变量
字符指针变量本质上是存放字符地址的指针
存储的是字符类型(char
)数据的内存地址
字符指针变量存储的是字符串中第一个字符的地址
因为字符是连续存储的,所以可以通过运算符,下标来访问对应的字符
char ch = 'A'; // 定义一个字符变量
char *p = &ch; // p存储ch的地址,即指向字符'A'
char *str = "Hello"; // str存储字符串"Hello"的首字符'H'的地址
数组指针变量
数组指针变量是用于存放数组地址的指针,指向的是数组的地址
语法结构:数据类型 (*指针名)[数组长度];
int (*p)[4]; // 定义一个指向包含4个int元素的数组的指针
数组指针初始化
二维数组的初始化无需使用&
int arr[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int (*p)[4] = arr; // p指向二维数组的首元素,即二维数组中的第一行数组(arr=arr[0])
//输出
printf("%d\n", p[1][2]); // 输出 arr[1][2] 的值(7)
一维数组的初始化必须使用&
int row[4] = {100, 200, 300, 400};
int (*p)[4] = &row; //使用&来获取一维数组的地址
//输出
printf("%d\n", (*p)[2]); // 输出 row[2](300)
二维数组传参的本质
二维数组传参的本质是通过数组指针传递并计算内存地址
当二维数组作为参数传递给函数时,数组名会退化为指向其首行的数组指针
传递的是二维数组中第一行的数组的指针的地址,而非单个元素的指针
二维数组的所有操作本质上都可以通过数组指针来理解(并不是只要传参才通过数组指针来进行的)
int arr[3][4] = {...};
void func(int (*p)[4], int rows); 参数p是数组指针
func(arr, 3); arr退化为 int(*)[4] 类型的指针将数组名去掉剩下的就是指针的类型
函数指针变量
用于存储函数的地址,允许通过指针间接调用函数
语法结构:函数的返回类型 ( * 指针名)(参数类型1,参数类型2);
int add (int a,int b)
{int c = a + b;return c;
}
int (*pf)(int, int); 指向函数的参数是两个int类型pf在命名习惯上表示存放函数的地址
使用函数指针调用函数
语法结构:( * 指针名)(参数类型1,参数类型2);
int result = pf(3, 4); 利用函数名调用函数int result = (*pf)(3, 4); 利用函数指针调用函数
(*pf)等价于pf , (3,4)表示的是给函数传递的参数
typedef关键字
用于为已有的数据类型定义新的名称(别名)
它的核心目的是简化复杂类型的声明,提高代码的可读性和可维护性
语法结构:typedef 原类型 新类型名;
typedef int Integer; // 将 int 重命名为 Integer
Integer a = 10; // 等价于 int a = 10;
指针类型 //此时的Integer表示为int
typedef int * pf;
pf a ; //等价于int * a;
数组指针
typedef int(*pf)[5]; //取得别名要放在数组名的位置上,不能放在末尾
函数指针
typedef void(*pf)(int);//函数指针与数组指针同理//取得别名要放在数组名的位置上,不能放在末尾
typedef
与 #define
的区别
在声明指针时
typedef int* IntPtr;
IntPtr a, b; // a 和 b 均为 int* 类型#define IntPtr int* //仅第一个变量被替换为指针,第二个为int
IntPtr a, b; // 展开为 int* a, b; → a 是 int*,b 是 int(错误!)
因为#define的底层机制是单纯的文本替换,不具有任何的类型类型逻辑
替换后的代码中,* 仅作用于紧邻的变量,导致多个变量声明时类型不一致
特性 | typedef | #define |
---|---|---|
处理阶段 | 编译时类型处理 | 预处理时文本替换 |
作用域 | 受限于块作用域 | 全局替换,无作用域限制 |
类型安全性 | 严格类型检查 | 无类型检查,可能引入错误 |
适用场景 | 类型别名 | 宏定义、常量、代码片段替换 |
函数指针数组
将多个函数的地址存入数组中,那么这个数组称为函数指针数组
只有函数指针类型都相同的函数才能将他们的地址存放到同一个函数指针数组中
语法结构:返回类型 ( * 数组名 [ 数组长度 ] ) ( 参数类型1,参数类型2 ) ;
声明
int (*pf[3])(int, int);定义
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);// 初始化函数指针数组
int (*funcArray[3])(int, int) = {add, sub, mul};