前言:
知不足而奋进,望远山而前行!
目录:
1.指针是什么:
1,指针是最小内存的编号,也就是地址。
2.指针变量就是用来存放地址的,存放在指针变量中的值都会被理解为地址。
总结:内存单元的编号==地址==指针。
2.指针变量和地址:
2.1 取地址操作符 (&)
#include<stdio.h> int main() { int a = 10;//创建整形变量a,内存中申请4个字节,用于存放整数10 int* pa = &a;//取出a所占的4个字节中地址较小的地址值存放在指针变量pa中 return 0; }
2.2 如何拆解指针类型
2.3 解引用操作符 (*)
在C语言中,我们只要拿到了地址,就可以通过地址(指针)找到地址(指针)所指向的对象,这里得使用解引用操作符(*)
#include<stdio.h> int main() { int a = 0; int* pa = &a; *pa = 0; //这里 *pa 的意思是通过pa中存放的地址,找到指向的空间,*pa 其实就是a变量了 // *pa = 0 就相当于 a = 0 return 0; }
2.4 指针变量的大小
指针变量的大小取决于地址的大小,32位平台下地址是32个bit位(即4个字节),64位平台下地址是64个bit位(即8个字节)。
#include<stdio.h> int main() { //64位平台 printf("%zd\n", sizeof(char *));//8 printf("%zd\n", sizeof(int *));//8 printf("%zd\n", sizeof(short *));//8 printf("%zd\n", sizeof(double *));//8 }
#include<stdio.h> int main() { //32位平台 printf("%zd\n", sizeof(char *));//4 printf("%zd\n", sizeof(int *));//4 printf("%zd\n", sizeof(short *));//4 printf("%zd\n", sizeof(double *));//4 }
注意:指针变量的大小与和类型是无关的,只要是指针类型的变量,在相同的平台下,大小都是相同的。
3.指针变量类型的意义:
3.1 指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)
比如:char* 的指针接引用就只能访问一个字节,而 int* 的指针解引用能访问四个字节。
3.2 指针+-整数
#include<stdio.h> int main() { int n = 10; char* pc = (char*)&n; int* pi = &n; printf("%p\n\n", &n); printf("%p\n", pc); printf("%p\n\n", pc + 1); printf("%p\n", pi); printf("%p\n", pi + 1); return 0; }
代码运行结果如下:
这里我们可以看到,char* 类型的指针变量+1跳过一个字节,int* 类型的指针变量+1跳过4个字节,故指针的类型决定了指针向前或者向后走一步有多大(距离)。
3.3 void* 指针
在指针类型中有一种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(也可以叫泛型指针),void* 可以用来接受任何类型地址。但也有局限性,void* 类型的指针不能直接进行指针的 +- 整数和解引用的运算。
4.指针运算:
指针的基本运算有三种,分别是:
4.1 指针+-整数
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; //数组在内存中是连续存放的,只要知道第一个元素的数组,就能知道后面的所有元素 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); //p+i 是第i+1个元素的地址,对其解引用就是第i个元素 } return 0; }
4.2 指针 - 指针
#include<stdio.h> int my_strlen(char* p1)//使用指针-指针模拟实现strlen函数 { char* p2 = p1; while (*p2 != '\0') { p2++; } return p2 - p1;//指针-指针,求得两个指针中的元素个数 } int main() { printf("%d", my_strlen("abcdef"));//6 return 0; }
注意:指针-指针的条件是两个指针必须指向同一块空间。
4.3 指针的关系运算
#include<stdio.h> int main() { //打印数组元素 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; int sz = sizeof(arr) / sizeof(arr[0]); while (p < arr + sz)//两个指针比较大小 { printf("%d ", *p); p++; } return 0; }
5.野指针:
概念:野指针就是指针指向的位置是不可知的(随机的 ,不正确的 ,没有明确限制的)
5.1 野指针成因
1.指针未初始化:
#include<stdio.h> int main() { int * p;//局部变量指针未初始化,默认为随机值 return 0; }
2.指针越界访问:
#include<stdio.h> int main() { int arr[10] = { 0 }; int i = 0; int* p = &arr; for (i = 0; i <= 11; i++) { //当指针指向的范围超出数组的arr的范围时,p就是野指针 p[i] = i; p++; } return 0; }
3.指针指向的空间释放:
#include<stdio.h> int* test() { int n = 0; return &n; } int main() { int* p = test();//局部变量是在函数里定义的变量,局部变量仅作用在局部区域中, //从定义开始到大括号或者return结束,该块空间会还给内存。 return 0; }
5.2 如何规避野指针
1.指针初始化
2.小心指针越界
3.规避返回局部变量的地址
4.指针变量不再使用时,及时置NULL,指针使用之前检查有效性
5.assert断言
6.const修饰指针:
6.1 const修饰变量:
变量的内容不能通过赋值改变,但能够通过指针被改变。
6.2 const修饰指针变量:
1. const 放在 * 的左边:修饰指针指向的内容,保证指针指向的内容不能通过指针来改变。但指针变量本身的内容可被改变。
2. const 放在 * 的右边:修饰的是指针变量本身,保证指针变量本身的内容不能修改,但指针指向的内容可以通过指针被改变。
7.指针和数组:
7.1 数组名的理解:
1.数组名是数组首元素(第一个元素)的地址。
2.两个例外:sizeof ( 数组名)——计算的是整个数组的大小 和 &数组名——取出的是整个数组的地址。
7.2 使用指针访问数组:
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++)//输入 { scanf("%d", p + i); //scanf("%d", arr + i); } for (i = 0; i < sz; i++)//输出 { printf("%d ", *(p + i)); //printf("%d ",p[i]); //printf("%d ", *(arr + i)); } return 0; }
7.3 一维数组传参的本质:
先看一个例子:
#include<stdio.h> int test(int arr[]) { int sz2 = sizeof(arr) / sizeof(arr[0]); return sz2; } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz1 = sizeof(arr) / sizeof(arr[0]); int sz2 = test(arr); printf("%d\n", sz1);//10 printf("%d\n", sz2);//1 return 0; }
由此可知在函数内部是求不出数组元素个数的。是因为数组名是数组首元素的地址,那么在数组传参时,传递的是数组名,也就是说数组传参传本质上传递的是数组首元素的地址。所以s2求的是指针的大小 / int类型的大小,故为1.
7.4 二级指针:
![]()
7.5 指针数组和数组指针:
1. 指针数组就是存放指针的数组,指针数组的每个元素是地址又可以指向一块空间。
2. 数组指针就是存放数组地址的,能够指向数组的指针。
注意:[]的优先级要高于*,所以必须加上()来保证 p 和 * 先结合。
7.6 指针数组模拟二维数组:
#include<stdio.h> 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 i = 0,j =0; //数组名是数组首元素地址,是int*的存放在arr数组中 int* arr[3] = { arr1,arr2,arr3 }; for (i = 0; i < 3; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }
7.7 二维数组传参的本质:
再次了解一下二维数组,二维数组起始可以看作是每个元素是一维数组的数组,也就是二维数组的每个元素是一维数组。那么二维数组的每一行,是一个一维数组。
如下图:
根据数组名是数组首元素的地址,二维数组的数组名表示的是第一行的地址,是一维数组的地址,第一行的一维数组的类型是int [5] , 所以第一行的地址的类型是 int* [5] ,所以二维数组传参本质上传的也是地址,传的是第一行一维数组的地址,那么形参可以写成指针形式的。
#include<stdio.h> void test(int (*p)[5], int r, int c)//数组指针,一定要使用(),使 p 先和 * 结合 { int i = 0, j = 0; for (i = 0; i < r; i++) { for (j = 0; j < c; j++) { printf("%d ", *(*(p + i) + j)); } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; int r = 3, c = 5; test(arr,r,c); return 0; }
8.指针和函数 :
8.1 函数指针:
1.函数指针变量: 函数指针变量是用来存放函数的地址的,将来可以使用函数指针来调用函数。函数名就是函数的地址,也可以通过&函数名获得函数的地址。
2. 函数指针类型:
8.2 函数指针数组:
1. 元素为函数指针的数组叫做函数指针数组
2. 函数指针数组的用途:转移表
示例:
#include<stdio.h> void menu() { printf("********************************\n"); printf("******** 1.加法 2.减法*******\n"); printf("******** 3.乘法 4.除法*******\n"); printf("********************************\n"); } int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a * b; } int Div(int a, int b) { return a / b; } int main() { int input; do { menu(); int x = 0; int y = 0; int z = 0; printf("请输入你要选择的操作:"); scanf("%d", &input); int(*arr1)(int ,int ) = Add; int(*arr2)(int, int) = Sub; int(*arr3)(int, int) = Mul; int(*arr4)(int, int) = Div; int (*arr[5])(int, int) = {0, Add,Sub,Mul,Div }; if (1 <= input && input <= 4) { int x = 0; int y = 0; int z = 0; printf("请输入两个操作数:"); scanf("%d %d", &x, &y); z = (*arr[input])(x,y); printf("%d\n", z); } else if (input == 0) { printf("退出计算机!"); } else { printf("输入错误!\n"); } } while(input); return 0; }
9.回调函数:
9.1 回调函数是什么:
回调函数就是一个通过函数指针调用的函数。例如,如果把函数指针(地址)作为一个参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。
9.2 qsort使用举例:
1. 使用qsort函数来排列整形数据
#include<stdio.h> int int_cmp(const void* pum1, const void* pum2)//其返回值为int ,先将参数强制类型转换为int类型的指针,再对其解引用 { return *(int*)pum1 , *(int*)pum2; } void test1() { int arr[10] = { 9,8,2,1,10,5,3,7,6,4 }; void qsort (void* base, //要排序的的对象的第一个指针 size_t num, //数组中的元素个数 size_t size, //数组中每个元素的大小 int (*compar)(const void*, const void*)); // 指向两个元素比较的函数的指针 size_t sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(int), int_cmp); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { //整型数据 test1(); return 0; }
2. 使用qsort函数分别按照年龄和姓名排列结构体数据:
#include<stdio.h> #include<string.h> int struct_cmp_age(const void* pum1, const void* pum2) { return ((struct student*)pum1)->age - ((struct student*)pum2)->age; } int struct_cmp_name(const void* pum1, const void* pum2) { return strcmp(((struct student*)pum1)->name, ((struct student*)pum2)->name); } void test2() { struct student arr[3] = { {"zhangsan",18},{"lisi",20},{"wangwu",19} }; size_t sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), struct_cmp_age); for (int i = 0; i < sz; i++) { printf("%s %d\n", arr[i].name, arr[i].age); } } void test3() { struct student arr[3] = { {"zhangsan",18},{"lisi",20},{"wangwu",19} }; size_t sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), struct_cmp_name); for (int i = 0; i < sz; i++) { printf("%s %d\n", arr[i].name, arr[i].age); } } int main() { test2();//使用qsort函数按照名字排列结构体数据 test3();使用qsort函数按照名字排列结构体数据 return 0; }