指针进阶(万字深层次指针解析) |
您所在的位置:网站首页 › printf指针 › 指针进阶(万字深层次指针解析) |
❤️ 作者简介 :对纯音乐情有独钟的阿甘 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识,对纯音乐有独特的喜爱 📗 日后方向 : 偏向于CPP开发以及大数据方向,如果你也感兴趣的话欢迎关注博主,期待更新 指针进阶 1.字符指针2. 指针数组3.数组指针3.2 &数组名VS数组名3.3数组指针的使用 4. 数组参数、指针参数4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4二级指针传参 5.函数指针5.1函数指针的定义5.2函数指针的使用 6.函数指针数组7. 指向函数指针数组的指针8.回调函数8.1实现计算器(简)8.2 qsort函数:一.无类型指针void*1.整形排序2.结构体名字大小排序3.结构体年龄排序 二.源码 内存会划分为一个个的内存单元,每个内存单元都有有个独立的编号——编号也称为地址 地址在C语言中也称为指针 指针(地址)需要存储起来—存储到变量中,这个变量就叫做指针变量![]() 在指针的类型中我们知道有一种指针类型为字符指针 char* ; 让我们通过代码演示来理解:
接下来让我们看一道面试题: 我们看到程序,str1和str2存储的两个字符串,我们可以注意到,我们使用数组名的时候,他们应该是数组首元素的地址,str1和str2是不同的常量,需要在内存中开辟不同的空间,所以两个数组首元素的地址不同,也就是str1和str2不同 这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会 开辟出不同的内存块。所以str1和str2不同,str3和str4相同。 2. 指针数组让我们来看看下面这段代码分别是什么意思: int* arr1[10]; //整形指针的数组 char *arr2[4]; //一级字符指针的数组 char **arr3[5];//二级字符指针的数组在这里就运用了多级指针的知识 我们一般的整形数组应该是: int arr[10]; 类似于这样的,arr中存储的是是个整形 那我们在将int变成int*,那么数组中存储的是什么呢? 我们可以看到,类型为int,数组里面存储的是int,那么我们现在是int*,那数组里面必然存储的是int*的内容,也就是整形指针的数组 char *arr2[4]与其类似,可以自行理解一下 我们来看到char **arr3[5],这个时候有人就会问了,一个*是指针,那两个呢? 我们int*类型中存储的是int类型的地址,也就是我们int*存储的是一块地址,其地址上是int类型,我们解引用的结果是地址上的int内容,而我们int**存储的也是一块地址,他是int*的地址,我们解引用也就得到的是int*的内容,是int的地址 如果听不懂的话,我们采用另一个方式来描述: 指针可以当做一个指向另一个,我们int*就相当于我们定义了一个类型int*,它指向的内容是int类型,而(*)只是用来表明它是一个一维指针 我们的二维指针**同理,定义为int** 类型时,**表示其为二维指针,指向的是int/*类型 这个时候我们来解释: char **arr3[5];//二级字符指针的数组 这个数组就是二级字符指针的数组,里面存放的都是二级字符指针 3.数组指针数组指针是指针?还是数组? 答案是:指针。 我们已经熟悉: 整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数据的指针。 那数组指针应该是:能够指向数组的指针。 int *p1[10]; int (*p2)[10]; //p1, p2分别是什么? int (*p)[10]; //解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。 //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。 这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合。 3.2 &数组名VS数组名对于下面的数组: int arr[10]; arr 和 &arr 分别是啥? 我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥? 我们看一段代码: #include int main() { int arr[10] = {0}; //%p是地址类型 printf("%p\n", arr); printf("%p\n", &arr); return 0; }我们来看输出结果: 难道两个是一样的吗? 我们再看一段代码: #include int main() { int arr[10] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr+1); printf("&arr+1= %p\n", &arr+1); return 0; }输出结果: 根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。 实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下) 本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型 数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40. 总结: 数组名是数组首元素的地址 有两个例外: 1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节 2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址 除此之外,所有的地方和数组名都是数组首元素的地址 3.3数组指针的使用那数组指针是怎么使用的呢? 既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。 看代码: 来看输出结果: 这里我们要了解一个概念: 我们接下来用数组指针: 输出结果: 总: 数组指针,是指针,是指向数组的指针 指针数组,是数组,是存放指针的数组 4. 数组参数、指针参数在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢? 4.1 一维数组传参我们通过一系列例子来感受: #include void test(int arr[])//ok? {} arr为整形数组,这个可行,省略了长度也可以 void test(int arr[10])//ok? {} arr微整形数组,长度未省略,可行 void test(int *arr)//ok? {} 传参为arr数组名,为首元素地址,也是指针,可行 void test2(int *arr[20])//ok? {} 这里的参数为指针数组,类型匹配,可行 void test2(int **arr)//ok? {} 原数组为指针数组,数组名传参为二级指针,类型匹配可行 int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); }总结: 一维数组传参,函数参数可以为一维指针,一维数组 一维指针数组传参,函数参数可以为二维指针,一维指针数组 4.2 二维数组传参 void test(int arr[3][5])//ok? {} 可行,参数就是二维数组 void test(int arr[][])//ok? {} 不可行,二维数组行可以省略,列不可省略 void test(int arr[][5])//ok? {} 可行 总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 这样才方便运算。 void test(int *arr)//ok? {} 不可行,二维数组传参为首一维数组的地址,一维指针与其不兼容 void test(int* arr[5])//ok? {} 这是一维指针数组,不可行 void test(int (*arr)[5])//ok? {} 加了括号,这为指针数组且列标对得上,可行 void test(int **arr)//ok? {} 二维指针不可以,应该为数组指针,指向的是数组 int main() { int arr[3][5] = {0}; test(arr); } 4.3 一级指针传参我们先来看一段代码:
这是一个简单的打印函数,它的实参是一个指针和一个int类型的变量,他的形参也是一个同类型的指针接收。 另外我们要知道其实它还可以用数组接收 当一个函数的参数部分是一级指针时,函数可以接收什么类型的参数? 举个栗子:(其他类型的同理) void test(char* p) {}这个函数可以接收什么类型的参数? 1.一维字符数组名 2.一个字符变量的地址(&ch(ch是字符变量)) 3.char*的指针变量 4.4二级指针传参先来看一个二级指针传参数的例子: void test(int** pstr) { printf("%d\n", **pstr); } int main() { int n = 5; int* p = &n; int** pp = &p; test(pp); test(&p); return 0; }这是一个二级指针传参,p是一级指针变量(存的是变量的地址),pp是二级指针变量(存的是一级指针变量的地址) test(pp)和test(&p)是一样的 二级指针传参其实还可以是指针数组 当一个函数的参数部分是二级指针时,函数可以接收什么类型的参数? 比如: void test(char** p) {}这个函数可以接收什么类型的参数? 1.取地址一级指针变量(&p (p是一级指针变量)) 2.二级指针变量 3.数组指针 5.函数指针我们都知道了int、char、数组等等很多指针类型,我们还有一个函数指针,平常很难碰到,今天我们来讲一讲 5.1函数指针的定义我们知道数组指针是指向数组的指针,那函数指针是不是指向函数的指针呢? 让我们来看看到底是不是 数组指针里面存的是数组的地址,类比一下,函数指针里面存的是不是函数的地址呢?这里你肯定会好奇函数也有地址吗?其实是有的。 让我们来看一段代码: #include void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }我们来输出一下: 我们这里定义的 int ( *pf )( int , int )=&Add pf就是我们的函数指针名,它与*结合成(*pf)这就是说明其为指针,前面的int就是函数add的返回值类型,后面括号以及里面的两个int也就是add函数的两个参数 由此,我们不难得出函数指针定义方式 type (*变量名)(形式参数,形式参数,…)=&(函数名) 我们清楚了函数指针的定义,接下来让我们看看它是如何使用的: 我们和上面一样,采用代码的形式理解: 我们想使用Add函数来实现两个数的加减: int Add(int x, int y) { return x + y; } int main() { int x = 3; int y = 5; int(*pf)(int, int) = &Add; 我们普通的做法就是: printf("普通做法:%d\n", Add(x, y)); 如今我们定义了函数指针,我们来试着使用一下 我们一般使用指针的时候采取的是解引用的方式: 例如 int a = 5; int* p = &a; 我们想用指针来修改a的值: *p = 4; printf("我们用指针修改常量的值:%d\n", a); 我们通过函数指针是不是也是这样呢? printf("我们用指针的做法:%d", (*pf)(x, y)); 答案是肯定的我们对pf前面加*号,表示解引用 注意:这里的*和上面定义的*可不一样 上面定义的*是表示pf为指针,下面的*是用来给pf解引用 return 0; }注释为了好看没有加注释符,如果需要测试需要将注释一一加上注释符(//)哦 接下来我们来看看,这到底有没有报错: 数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如: int *arr[10]; 数组的每个元素是int*那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢? 我们先来定义四个函数 比如计算器加减乘除四个函数 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* ass[] = { 0 }; 那我们定义函数指针数组呢? 我们就应该这样写: int(*p[5])(int x, int y) = {add,sub,mul,div}; 之前我们一个函数指针我们是这样的 int(*p)(int x,int y) = add; 我们用变量p来接收一个函数add 现在我们将变量p变成p[]。自然而然就对应的是函数数组 由此就可以看到函数指针数组的定义为 type(*数组名[])(参数,参数)={函数名、函数名、} } 7. 指向函数指针数组的指针指向函数指针数组的指针是一个 指针,指针指向一个 数组 ,数组的元素都是 函数指针 ; 如何定义? 让我们看几个例子: 函数指针:int(*pf)(int,int) 函数指针数组:int(*pfarr[4])(int,int) 我们当初思考二维指针的时候,我们在一维指针前面 加上一个*号就表示其为二维指针 我们的指向函数指针数组的指针是不是也一样的呢? 答案是肯定的 int(*(*p)[4]))(int,int)=&pfarr;//函数指针数组的地址 p就是指向函数指针数组的指针 可以看到我们就是在函数指针数组前 加了一个*号表示其为函数指针数组的指针 8.回调函数回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 我们通过代码的演示来学习: 8.1实现计算器(简)我们想通过代码实现简单的计算器(实现加减乘除基本运算) 这是我们正常代码: #include int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } void menu() { printf("***************************\n"); printf("***** 1.add 2.sub ******\n"); printf("***** 3.mul 4.div ******\n"); printf("***** 0.exit ******\n"); printf("***************************\n"); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误,重新选择\n"); break; } } while (input); return 0; }我们会发现我们在swich—case语句中,有很多重复冗余的部分,每次进入语句都会需要写输入语句以及函数调用,使得我们的代码量大大增加,我们能否省略这些冗余的部分呢? 接下来我们采用回调函数,相关操作实现 #include int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } void menu() { printf("***************************\n"); printf("***** 1.add 2.sub ******\n"); printf("***** 3.mul 4.div ******\n"); printf("***** 0.exit ******\n"); printf("***************************\n"); } void Calc(int (*pf)(int, int)) //回调函数-->指向函数指针 { int x = 0; int y = 0; int ret = 0; printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = pf(x, y); //我们通过调用pf,用pf接受了传进来的函数地址,也就是调用传进来的函数 printf("ret = %d\n", ret); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: Calc(Add); break; case 2: Calc(Sub); break; case 3: Calc(Mul); break; case 4: Calc(Div); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误,重新选择\n"); break; } } while (input); return 0; }我们可以看到 我们可以通过函数指针省略非常大一部分的代码量,实现我们想要的操作 接下来我们介绍一个函数: 8.2 qsort函数:qsort:q就是quick,sort就是排序,也就是快速排序 看一下www.cplusplus.com 里面的函数定义: void* base —>指向了需要排序的数组的第一个元素 size_t num —>排序的元素个数 size_t size —>一个元素的大小,单位是字节 int (*cmp)(const void*,const void*) 函数指针类型 这个函数指针指向的函数,能够比较base指向数组的两个元素的大小 这个函数通常叫排序函数(需要自己实现) 一.无类型指针void*注: void* 指针 --无具体类型的指针 void* 类型的指针可以接受任意类型的地址 这种类型的指针是不能直接解引用操作的 也不能直接进行之指针运算的 在上面的函数里只是代表了所有的函数类型 当我们在函数中参数使用void*指针来接收的时候,虽然能接收任意类型的参数指针,但是接收后类型依然是void*,我们在函数内部使用的时候我们需要对其进行强制转换,转换成我们需要的参数类型进行比较才可以 下面我们就用三个栗子来了解qsort以及void*的用法:(分别是用qsort排序整型,用qsort排结构体名字和年龄) 1.整形排序 int cmp_int(const void* arr1, const void* arr2) { return (*(int*)arr1 - *(int*)arr2); } void print(int* arr, int sz) { for (int i = 0; i int arr[10] = { 1,3,5,7,9,2,4,6,8,10 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); return 0; }void*指针的用法也是接下来实现所有类型qsort的一个必要条件 知道了qsort函数接下来我们看代码: 这是我们简单调用qsort函数 #include //qosrt函数的使用者得实现一个比较函数 int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int i = 0; qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i return (*( int *)p1 - *(int *) p2); } void _swap(void *p1, void * p2, int size) { int i = 0; for (i = 0; i int i = 0; int j = 0; for (i = 0; i if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0) { _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size); } } } } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; //char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0; bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |