嵌入式第十五课!!!!指针函数(续)+函数指针+二级指针+指针数组!!!
指针函数(续)
1.molloc函数
这个函数是用于堆区空间的申请,它是这么定义的:
#include<stdlib.h>
void *malloc (size_t size)
在调用这个函数之前,先添加头文件(标准库),在括号里填充想要申请的空间大小(单位是字节),且申请的空间是连续的。举个例子:
char *p = malloc (1024 * 1024 * 100);
即在堆区空间里申请了一个100mb的空间,且存储的是char型的变量 ,指针 p 指向这个空间的首地址。
需要注意的是,虽然申请的空间是栈区,但是指针p 还是存储在栈区;在申请空间的时候,需要注意并不是每次都会申请成功,这和内存空间剩余内存与硬件内存有关系:32位操作系统会最多申请到2.4G的空间,而64位系统则会更多。
在无空间可以申请的时候,函数返回值是一个空指针,所以在给申请到的空间进行存储的时候,要注意判断返回值是否是一个空指针:
if (p != NULL)
如果当前申请的空间在使用中发现过小,无法满足需求时,不可以在目前的空间直接往后延,只能重开一个空间,再继续判断是否申请成功(这个函数是realloc,在后文进行详解)
molloc函数的运用
int main(void)
{const char *s = "Hello World!";char *p = malloc(strlen(s) + 1);strcpy(p, s);puts(p);return 0;
}
如果在申请空间之后不给指针 p 赋地址,指针p就会成为一个野指针。
如果我们只申请空间,不进行销毁的话会导致内存泄漏爆满,这时需要free函数来进行销毁空间。
2.free函数
free函数是这样的形式:
free (void *)
free(NULL)也不会报错,,所以就算没有申请到空间,也可以使用这个函数来释放。
修改上述代码为:
int main(void)
{const char *s = "Hello World!";char *p = malloc(strlen(s) + 1);strcpy(p, s);puts(p);free(p);p = NULL;return 0;
}
此时 p 在运行 free函数后,是一个野指针,故给它赋为一个空指针。
需要注意的是:在申请的空间首地址保存到指针 p 里时,不能在销毁时随意篡改 p ,如free(p + 1)就是不允许的,在销毁时会找不到空间;就像是去图书馆借书,借了一部《三国演义》,还的时候却还了一部《天龙八部》,图书馆就会找不到你的借阅信息,也就还不了书了。
在Linux系统里有一个工具——垃圾回收器,在程序运行结束后会识别到未销毁的空间,然后自动销毁。但我们要养成释放空间的好习惯,在裸机驱动的时候要自己进行手动销毁。
3.realloc函数
这个函数就是上文介绍malloc函数时说的重新分配空间函数,它的一般形式是这样的:
realloc (原地址 , 重开空间的内存大小)
举个例子:
int main(void)
{const char *s = "Hello World!";char *p = malloc(strlen(s) + 1);strcpy(p, s);puts(p);const char *t = "China";p = realloc(p, strlen(p) + strlen(t) + 1);strcat(p, t);puts(p);free(p);p = NULL;return 0;
}
使用strcat函数后,原先的内存空间就不够用了,故现在要使用realloc函数重开一个能满足两个字符串相连的空间:需要注意的是,因为我们需要存储的是字符串,一个字符只有一个字节;如果我们存储的是其他数据类型,如 int 型 、 short 型等,我们要在空间大小里(* sizeof(数据类型))。
在调用这个函数之后,原先存储的字符串会自动拷贝到新空间。
4.calloc函数
这个函数也可以申请空间,以数组的形式:
calloc (元素个数 , 每个元素所占的字节数)
举个应用的例子:
int main(void)
{int n = 10;
// int *p = malloc(n * sizeof(int));int *p = calloc(n, sizeof(int));int i;for(i = 0;i < n;++i){*(p + i) = i + 1;}printArray(p, n);free(p);return 0;
}
函数指针
函数指针,顾名思义就是指向函数的指针;在之前的学习中,我们了解到函数在程序运行时会存储到代码区,普通的指针是不能直接指向函数的,如:
int * p = add;
这样是不允许的,我们只能存储函数的入口地址,举个例子:
int add(int a, int b)
{return a + b;
}int main(void)
{int (*p)(int , int ) = NULL;p = add;printf("%d\n", p(10, 20));return 0;
}
此时,我们要求指针存储的是函数add的入口地址,所以在定义指针的时候要注意,此时指针的基类型是返回值为int ,传参2个int型的函数:
int (*p)(int , int ) = NULL;
指针名的括号(*p)不可以省略,省略括号的话系统会认为定义了一个函数——返回值为int *型的名为p的函数;
此时定义好的指针没有设定指向的函数,是个野指针,要赋为空指针;
p = add;
此时赋地址的时候不用加(),因为其目前左右类型是匹配的;
printf("%d\n", p(10, 20));
在调用时直接给p后输入实际参数即可。
我们来看一个使用案例:
int pow2(int n)
{return n * n;
}int fn(int n)
{return n;
}void sort(int *a, int len, int (*pfn)(int))
{int i, j;for(i = 0;i < len - 1;++i){for(j = i + 1;j < len;++j){if(pfn(a[i]) > pfn(a[j])){int t = a[i];a[i] = a[j];a[j] = t;}}}
}
void printArray(int *a, int len)
{int i;for(i = 0;i < len;++i){printf("%d ", a[i]);}puts("");
}int main(void)
{int a[] = {-1,2,-3,4,-5,6,-7,8,-9,0};int len = sizeof(a) / sizeof(*a);sort(a, len, pow2);printArray(a, len);return 0;
}
这是一个给一维int型数组排序的函数sort,在实际应用过程中,我们可能被要求使用不同的条件进行排序,这时我们可以给函数添加一个参数,即函数指针;通过改变主函数调用sort时使用的函数pow2可以改变排序条件:
sort(a, len, pow2);
此时排序的条件就变为元素的平方了,这种函数(如pow2、fn)被称为回调函数,函数设计者只需要设定好在不同的条件下会运行不同结果的函数即可,回调函数会由函数的使用者来编写,且使用者在主函数进行调用,就会输出用户想要的结果。
1.qsort函数
这个函数在之前进行对数组的快排时编写过,在系统标准库函数里,它是这么定义的:
#include<stdlib.h>
void qsort(void *base , size _t nmemb , size_t size , int (*compar)(const void* , const void*)
这个函数的参数含义是这样的:
第一个参数void*base是指参与排序的数组首元素地址、
第二个参数size_t nmemb指的是数组里的元素个数、
第三个参数size_t size指的是一个元素所占的字节、
第四个参数时一个函数指针,它的返回值是int ,来判断进行比较的(const void* , const void*)的大小;
应用方法如下:
int intcmp(const void *p1, const void *p2)
{int *q1 = (int *)p1;int *q2 = (int *)p2;if(*q1 > *q2){return 1;}else if(*q1 == *q2){return 0;}else{return -1;}}int main(void)
{double a[] = {1,2,3,4,5,6,7,8,9,0};qsort(a, sizeof(a) / sizeof(*a), sizeof(*a), intcmp);int i ;for(i = 0;i < sizeof(a) / sizeof(*a);++i){printf("%d\n", a[i]);}return 0;
}
2.跳转特定的地址(只能在驱动用)
int main(void)
{void (*pfn)(void);pfn = (void (*)(void))0x30008000;pfn();return 0;
}
二级指针
二级指针就是指向指针的指针,这样想来可能会有一点不得要领,可以看图来加深理解:
为了表示 q 是 指针p 的指针 ,我们将其定义为:
int **q = & p;
二级指针的基类型是int *,q前面的 * 用来说明其是一个指针:
void getMem(char **p)
{*p = malloc(100);
}void fn(int *p)
{*p = 1000;
}int main(void)
{char *s;getMem(&s);strcpy(s, "Hello");puts(s);return 0;
}
我们在函数getMem里面要改变指针 s 本尊,所以在传参的时候,要传指针 s 的首地址,即 *p 就是 指针 s 本尊;
指针数组
为了更好的理解指针数组,举个例子:
char *s[3] = {"Hello "," World" ,"China"};
在空间里是这样的:
指针数组的每一个元素都是一个指针,分别指向不同字符串的首地址,用法如下:
void printStrings(char **p, int len)
{int i;for(i = 0;i < len;++i){puts(p[i]);}
}
int main(void)
{char *s[] = {"Hello", "World!", "China"};int len = sizeof(s) / sizeof(*s);printStrings(s, len);return 0;
}
在数组指针作为函数参数时,形参时指向指针的指针,即 *p[ ] = **p;
标准主函数
标准主函数的形式如下:
int main(int argc , char *argv);
整型int argc 指的是执行程序输入字符数组的元素个数 ; char * argv指的是执行程序输入的字符串:
int main(int argc, char *argv[])
{int i;for(i = 0;i < argc;++i){puts(argv[i]);}return 0;
}
运行结果如下:
在执行程序时输入的字符串直接被打印了出来。
以上就是今天和大家分享的内容!!!!感谢你的阅读!!!如有遗漏和错误,欢迎各位大佬评论区进行批评和指正!!!!