目录
1. 指针的基本概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的 + - 整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。
2. 不同类型的指针的意义是什么?
指针本身在内存中都是4/8(32位平台/64位平台)个字节大小,区别就在于不同指针的步长是不相同的。在于被解引用时改变内存空间的多少个字节,不同的指针改变不同的内存空间字节大小。
int main() { int a = 1111111; char* ps = (char*)&a; // 强制类型转换成char* printf("%d",*ps ); // 71 return 0; }
int和char指针控制指针偏移量不同 所以修过地址时会发生数据截断,整形空间存储的是4个字节大小,而字符串指针的步长是一个字节大小,这里强制类型转换会截断数据,ps存放的是一个字节的数据,而删除了高位的三个地址(字节)。
3. 野指针是什么?
指向未知、无效或已释放的内存位置的指针被称为野指针。
int* p; *p = 10; // 非法访问内存 p就是野指针
p没有初始化就意味着没有明确指向,一个局部变量没有初始化 放的就是随机值 0xcccccc。
int *test() { int a = 10; return &a; } int main() { // *p可以获取a的值 前提是没有其他变量占领a的空间否则就是乱码 int*p = test(); return 0; }
即使这里p获取到了a的地址,但是也无法通过解引用去改变a的值,可以获取a的值为10,不过依然无法改变,a变量的地址已经被销毁了。
这里涉及到函数栈帧的概念,可以去查看这篇文章,很好的解释了什么是栈帧。【详解】函数栈帧——多图(c语言)_指针与函数值传递-CSDN博客
4.指针加减整数
指针的类型决定了指针的 + - 整数的步长,指针加n,即跳过n * 指针步长的字节,就是指向同一指针类型的第n + 1个元素。
int main() { // 用指针的方式初始化数组 int arr[5]; int* ps; for (ps = arr; ps < &arr[5];) { *ps++ = 1; // *ps = 1; // ps++ } return 0; }
这里通过指针++的方式,初始化数组。&arr[5]不存在数组越界问题,因为没有访问他的地址,只是用于判断。
5.指针运算 (指针 - 指针)
指针 - 指针 得到的是指针之间的元素个数
不是所以指针都可以相减 指向同一内存空间的两个指针才能相减
int my_strlen3(char* str) { char *start = str; // 存储首字符地址 while (*str != '\0') { str++; // 向后寻找 直到寻找到\0 } return str - start; // 指针 - 指针 } int main() { int len = my_strlen3("abcdef"); // 第六个指针 - 第0个指针 printf("%d", len);// 6 return 0; }
这是一个使用指针运算的方式重写Strlen内置函数的案例,my_strlen3函数最后return返回的是两个指针相减的结果,在同一内存空间中第6个指针减去第0个指针,得出的是元素的个数也就是6。
6.一级指针和二级指针以及多级指针是什么意思?
一级指针指向一块内存空间,其变量值即为该地址,通过“ * ”运算可以获取该地址存储的值。
二级指针是指向指针的指针,其变量值为一级指针的地址。
以此类推三级指针就是指向指针的指针,其变量值为二级指针的地址....
int a = 10; int* pa = &a; // 一级指针 int** paa = &pa; // 二级指针 **paa = 20;
二级指针通过解引用 *paa的方式可以访问到a的内存空间,从而修改a的值。
7.指针数组
指针数组是一个数组,其中的每个元素都是指向某种数据类型的指针。
指针数组存储了一组指针,每个指针可以指向不同的数据对象。
int arr1[4] = { 1,2,3,4 }; int arr2[4] = { 2,2,3,4 }; int arr3[4] = { 3,2,3,4 }; // 将arr1 arr2 arr3的数组首元素地址存储到数组中 int* parr[] = { arr1,arr2,arr3 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", parr[i][j]); } printf("\n"); }
这里模拟了一个二维数组的结构,将三个数组以地址的形式存放到parr指针数组中,再通过双重for循环遍历parr来获取每个数组中元素的值。
8.数组指针
数组指针本质是一个指针,指向了一个数组。
数组指针常用于接收二维或多维数组的函数传参。
int main() { int* p1 = arr; // 存放的是数组的首元素地址 int(*p2)[10] = &arr; // 存放的是整个数组的地址 return 0; }
简单演示数组指针的使用,数组指针存放的是整个数组的地址。
- 那么如何理解数组指针的写法和意思呢?
int main() { char* arr[10] = { 0 }; // (*ps)代表这是一个指针变量名叫ps // char* 代表存放的数组中的元素是char* // [] 中必须和存放数组的元素个数一样 char* (*ps)[10] = &arr; return 0; }
初始化一个字符串指针类型的数组arr,再用指针数组来存放arr,而这个数组指针存放的arr数组中元素是char *类型,千万不要误以为是二级指针!
- 如何使用指针数组呢?
//数组指针常用于 二维数组甚至三维数组! void print2(int(*ps)[5], int r, int c) // 实参用 int (*)[5] 这个类型来接受 是第一行数组的大小 { int i = 0; for (i = 0; i < r; i++) { int j = 0; for (j = 0; j < c; j++) { // 这两种写法的结果都相同 // 因为 *(ps + i) == ps[i] //printf("%d ", *(*(ps + i) + j)); printf("%d ", ps[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; // 这里arr 传的是第一行的地址 print2(arr, 3, 5); return 0; }
我们初始化一个二维数组arr,再传入print2函数的第一个参数中,此时arr实际传入的是第一行的数组,而我们函数中第一个形参类型就是数组指针 int(*ps)[5] ,于是我们接收到了二维数组传入进来的第一行数组的地址。
1. 那我们解释一下第一中写法,ps+i 等同于第一行数组向下移动 i 行,*(ps + i )就获取了第 i 行数组的地址,数组的地址也就是首元素地址又等同于数组名。第 i 行的数组名 + j就是数组首元素地址加上 j ,此时二维数组的全部元素都被遍历了一遍。
2. 第二种写法就很好理解了,*(ps + i )等同于ps[ i ],因为传进函数的是arr,所以arr[ i ]就是数组的第 i 行。最后ps[ i ]加上[ j ]就是完整的二维数组写法,也就能将二维数组全部遍历一次了。
- 一维数组和二维数组的传参时的规范和易错点
// 一维数组传参 void test(int arr[]); //ture void test(int* arr); //ture void test(int arr[10]); //ture void test2(int* arr[20]); //ture void test2(int** arr); //ture int main() { int arr[10] = { 0 }; int* arr2[20] = {0}; test(arr); test2(arr2); return 0; }
一维数组
// 二维数组传参 void test(int arr[3][5]); // ture void test(int arr[][]); // false void test(int arr[][5]); // ture void test(int*arr); // false 因为是第一行数组地址 所以不能用整形指针接收 void test(int*arr[5]); // false void test(int(*arr)[5]); // ture 要用数组指针这个类型来接收第一行数组的地址 void test(int**arr); // ture int main() { int arr[3][5] = { 0 }; test(arr); return 0; }
二维数组
9.函数指针
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
- 函数指针的基本写法和调用方法
int Add(int x, int y) { return x + y; } int main() { // &函数名等于函数名 都可以获取函数的地址 //printf("%p", &Add); == printf("%p", Add); // 函数指针写法 int (*ps)(int, int) = &Add; int ret = (*ps)(3, 2); // 通过指针的方式调用函数 也可以这样写 ps(3, 2) printf("%d", ret); return 0; }
1.&函数名等于函数名 都可以获取函数的地址,只是写法上的不同。
2.函数指针就是获取函数的地址,int (*ps)(int, int) 括号中写的是函数形参的数据类型。
3.可以通过函数指针调用函数,可以省略(*)不写,效果也是一样
- 函数指针数组也被称为转移表
int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } void munu() { printf("*********************\n"); printf("********1. 相加*************\n"); printf("********2. 相减*************\n"); printf("********3. 退出*************\n"); printf("*********************\n"); printf("*********************\n"); } int main() { // 函数指针数组 - 存放函数指针的数组 // 转移表 int (*arr[2])(int, int) = { Add,Sub }; int x = 0; int y = 0; int input = 0; do { munu(); scanf("%d", &input); if (input == 3) { printf("退出游戏\n"); } else if (input >= 1 && input <= 2) { printf("请输入坐标:>"); scanf("%d %d", &x,&y); int ret = arr[input-1](x, y); printf("%d\n", ret); } else { printf("输入错误的选项\n"); } } while (input); return 0; }
这个案例简单实现了加减数的功能。我们将加法函数和减法函数的地址存储到函数指针数组中,函数指针数组是可以存放多个函数地址的数组,但函数指针数组中的形参类型都要相同。再通过用户输入和简单的判断调用指定的函数。
- 指向【函数指针数组】的指针
int main() { // 函数指针数组 int (*arr[2])(int, int) = { Add,Sub }; // 指向【函数指针数组】的指针 int (*(arr)[2])(int, int) = &arr[2]; return 0; }
可以无限套娃,但是不建议
- 无具体类型指针
int main() { int a = 10; void* b = &a; // void*是无具体类型的指针,可以接收 // void*是无具体类型指针 不可以进行指针+-和引用操作 return 0; }
无具体类型指针void * 通常用于函数形参,不确定传入的是什么数据类型时使用。
好了这就是C语言指针进阶部分的内容,如有哪里错误或者混淆的地方请指出或者留言。
标签:ps,arr,进阶,int,C语言,地址,数组,指针 From: https://blog.csdn.net/m0_64574726/article/details/141782183