文章目录
- C语言笔记9:指针
-
- 一、指针变量和地址
- 二、野指针
- 三、传值调用和传址调用
- 四、数组名的理解
- 五、使用指针访问数组
- 六、一维数组传参的本质
- 七、冒泡排序
- 八、二级指针
- 九、指针数组
- 十、字符指针变量
- 十一、数组指针变量
- 十二、函数指针
-
- 拆解复杂函数指针
-
- [第一步:`void (*) ()`](#第一步:
void (*) ()) - [第二步:`(void (*) ())0`](#第二步:
(void (*) ())0) - [第三步:`(*(void (*) ()) 0)()`](#第三步:
(*(void (*) ()) 0)()) - 第一步:`void(*)(int)`
- [第二步:`signal(int ,void(*)(int))`](#第二步:
signal(int ,void(*)(int))) - [第三步:`void (*signal(int, void(*)(int)))(int)`](#第三步:
void (*signal(int, void(*)(int)))(int))
- [第一步:`void (*) ()`](#第一步:
- 使用typedef
- 函数指针数组
- 十三、回调函数
C语言笔记9:指针
一、指针变量和地址
取地址
cint main() { int a = 10; int * pa = &a; return 0; }&a取出来的地址是存放a的四个字节中,地址较小的那个。
指针变量的大小
指针变量的大小和平台有关
32位4字节
64位8字节
指针变量类型的意义
对指针变量的操作无非两种:1、指针±整数 2、指针解引用访问指向的空间
有了指针变量类型,指针±整数的时候跳过的地址数就由指针变量的类型决定。指针解引用访问指向的空间修改字节数,和修改的方式也由指针变量的类型决定。
void*类型
void*类型的指针不能直接进行指针±整数和解引用操作 ,void*的作用是用来接收不同类型的指针变量。
const修饰指针
const修饰了变量之后变量本身不能修改,但是如果对该变量取地址然后再修改就可以。
const放在
*左边和*右边是不同的效果,放在左边表示解引用操作不能修改指针指向的空间,放在右边表示指针不能进行±整数的操作了。
指针-指针、指针之间的大小比较
指针-指针只能发生在同类型的指针上。
指针之间可以通过>、<、==、来比较大小。
二、野指针
野指针成因
创建指针变量却未初始化
默认是cc cc cc cc
指针越界访问
cint main() { int arr[10] = {0}; for(int i = 0;i < 12;i++) { arr[i] = i; } return 0; }当指针对超出数组范围的空间进行修改时,就是越界访问了。
指针指向的空间释放了
cint * test() { int n = 0; return &n; } int main() { int * pn = test(); *pn = 10; return 0; }函数返回局部变量的地址时,指针指向空间释放了。
规避野指针
c#ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endifC和C++对于
NULL在定义上有区别,C++引入了nullptr防止重载函数类型匹配的时候出现错误。因为C++的NULL是int类型。
针对未初始化、越界访问、指针指向空间释放三种原因,规避野指针的方法如下1、始终初始化指针,不知道赋什么值就给NULL
2、小心别越界,越界之后把指针置空。
3、不返回局部变量地址,或者在使用完局部变量地址后将该指针变量置空。
assert断言
c#include <assert.h> int test(int * pa) { assert(pa != NULL); return 0; } int main() { int a = 10; test(&a); return 0; }不想使用断言也很简单在头文件前加#define NDEBUG
c#define NDEBUG #include <assert.h>VS下提供选项,默认release模式是加了这个宏定义的。
三、传值调用和传址调用
模拟实现strlen
csize_t my_strlen(const char* str) { assert(str != NULL); int count = 0; while(*str) { str++; count++; } return count; } int main() { int ret = mystrlen("abcde");//"abcde"会被解释成一个指向只读数据段(静态区)的指针。const char* return 0; }
swap
cvoid swap(int *a,int* b) { int temp = 0; temp = *a; *a = *b; *b = temp; } int main() { int x = 10; int y = 20; swap(&x,&y); return 0; }传址调用才能修改实参的值。
四、数组名的理解
数组名就是数组首元素的地址
c#include <stdio.h> int main() { int arr[10] = {0}; printf("%zd\n",sizeof(arr)); //结果为40 }
c#include <stdio.h> int main() { int arr[10] = {0}; printf("%p\n",&arr + 1); //跳过了一整个数组 }数组名是数组首元素地址不假,但是arr的类型是int [10],&arr的类型是int(*) [10]。sizeof()计算这个类型的大小肯定是40,指针进行整数加减肯定跳过了整个数组。
五、使用指针访问数组
[]下标引用操作符的理解
c#include <stdio.h> int main() { int arr[10] = {0,1,2}; int * pa = arr; printf("%d\n",arr[0]); printf("%d\n",*(pa + 0)); }arr[0] 等价于 *(arr + 0),这和我们刚刚说arr是int [10]类型似乎有些冲突。这是因为当我们对arr进行整数±或解引用的时候,它会退化成
int*类型。那二维数组呢?
c#include <stdio.h> int main() { int arr[3][5] = { {1,2}, { 3,4 }, { 5,6 } }; int (*pa)[5] = arr; printf("%d\n", arr[0][0]); printf("%d\n", *(*(pa + 0) + 0)); }
六、一维数组传参的本质
c#include <stdio.h> size_t test(int arr[]) { int sz = sizeof(arr)/sizeof(arr[0]); return sz; } int main() { int arr[10] = {0}; int sz1 = sizeof(arr)/sizeof(arr[0]); int sz2 = test(arr); printf("sz1= %zd,sz2= %zd\n",sz1,sz2); return 0; }
上述代码和结果说明,test函数内部认为arr的类型就是int*,所以写形参的时候,写成int*类型和写成int arr[]是一样的。
七、冒泡排序
c#include <stdio.h> void swap(int* a, int* b) { int tmp = 0; tmp = *a; *a = *b; *b = tmp; } void bubble_sort(int arr[], int sz) { for (int i = 0; i < sz - 1; i++) { int flag = 0; for (int j = 0; j < sz - 1 - i; j++) { if (arr[j] > arr[j + 1]) { flag = 1; swap(&arr[j], &arr[j + 1]); } } if (flag == 0) { break; } } } int main() { int arr[10] = { 3,6,2,1,4,7,9,0,8,5 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
八、二级指针
c#include <stdio.h> int main() { int a = 10; int* pa = &a; int** ppa = &pa; **ppa = 20; printf("%d\n",a); return 0; }存指针变量的地址就是二级指针
九、指针数组
c#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* parr[3] = { arr1,arr2,arr3 }; printf("%d %d %d\n", parr[0][0], parr[1][0], parr[2][0]); return 0; }
parr的类型是int* [3]。
十、字符指针变量
cint main() { char str1[] = "hello,world."; char str2[] = "hello,world."; const char* str3 = "hello,world."; const char* str4 = "hello,world."; return 0; }str1,和str2是栈上的不同字符数组的首元素地址,而str3和str4则指向静态区的只读数据段。
字符串字面量不同于其他字面量,它在代码段中表现为一个指针(指向只读数据段),而真正存放字符串内容的是只读数据段中的一块区域

十一、数组指针变量
cint (*pa1) [5]; int* pa2 [5];pa1是数组指针变量,pa2是指针数组。由于
[]下标引用操作符的优先级高于*,所以加了()的让*pa1结合,没加()的让pa1[5]结合了。
二维数组传参的本质
前面我们知道
int arr[]类型的一维数组传参其实是传int *类型。那同理,二维数组传的也是指针,只不过是数组指针。
c#include <stdio.h> void test(int (*pa) [5]) { printf("%d\n",pa[0][0]); } int main() { int arr[3][5] = {{1,2},{3,4},{5,6}}; test(arr); return 0; }前面说数组名是数组首元素地址,一维数组
int arr[10]的数组元素是int,那二维数组int arr[3][5]的数组元素是什么呢?是int [5]类型的数组。
十二、函数指针
c#include <stdio.h> int Add(int x,int y) { return x + y; } void test() { printf("haha\n"); } int main() { int (*pf1) (int,int) = Add; void (*pf2) () = test; return 0; }函数指针变量创建方式。
拆解复杂函数指针
代码1:
c(*(void (*) ())0)();这是一个函数调用从右边的
*开始拆分。
第一步:void (*) ()
第二步:(void (*) ())0
第三步:(*(void (*) ()) 0)()
表示将0强制类型转换成
void (*) ()类型的函数指针,然后调用
代码2:
cvoid (*signal(int ,void(*)(int)))(int);这是一个函数声明,从右边的
*开始拆分
第一步:void(*)(int)
第二步:signal(int ,void(*)(int))
第三步:void (*signal(int, void(*)(int)))(int)
表示声明一个signal函数,参数是
(int,void(*)(int)),返回值类型是一个void(*)(int)类型的函数指针。
使用typedef
简化代码2:
ctypedef void (*pfun_t) (int); pfun_t signal(int,pfun_t);
函数指针数组
c//先看一个函数指针 int Add(int x,int y) { return x + y; } int (*pf) (int,int) = Add; //再看一个函数指针数组 int (*pf[4]) (int,int) = {Add,Sub,Mul,Div};使用函数指针数组通过传递不同的下标,就能调用相同类型的不同函数了。
十三、回调函数
简单来说,回调函数就是一个通过函数指针调用的函数。
实现一个函数之后,把这个函数作为一个参数传递给另一个函数,这个函数在符合某些条件的时候调用这个函数指针。
利用回调函数实现qsort
cvoid swap(void* p1,void* p2,int ele_size) { char tmp = 0; for(int i = 0;i < ele_size;i++) { tmp = *((char*)p1 + i); *((char*)p1 + i) = *((char*)p2 + i); *((char*)p2 + i) = tmp; } } void my_qsort(void* base,int arr_count,int ele_size,int (*cmp) (void*,void*)) { for(int i = 0;i < arr_count - 1;i++) { int flag = 0; for(int j = 0;j < arr_count - 1 - i;j++) { if(cmp( (char*)base + j*ele_size, (char*)base + (j+1)*ele_size ) > 0) { flag = 1; swap( (char*)base + j*ele_size ,(char*)base + (j+1)*ele_size ,ele_size); } } if(flag == 0) break; } }




