目录
函数指针
函数指针数组
函数指针用作函数参数
例1-通用的选择排序
函数中的静态变量
函数间共享变量:全局变量
Main函数
参数数量可变的函数
例2-简单版本的printf函数
C语言库函数-快速排序qsort
C语言库函数-二分查找bsearch
例-火柴棍摆正方形(LeetCode 473. Matchsticks to Square)
函数的内存地址存储了函数开始执行的位置,它保存在函数名中(可以将函数名当作一个常量看待)。使用指针保存函数的地址,即使指针指向这个函数,指向函数的指针被称为是函数指针。
通过函数指针可以灵活的调用各种形式相同(函数返回值与函数参数列表相同)但是功能不同的函数,大大增加了代码的灵活程度。
示例代码:
#include
int sum(int x, int y){
return x + y;
}
int difference(int x, int y){
return x - y;
}
int product(int x, int y){
return x * y;
}
int main(){
int a = 5;
int b = 8;
int (*p_func)(int, int);
printf("p_func sizeof = %d\n", sizeof(p_func));
p_func = sum;
printf("sum's address = %p\n", sum);
printf("p_func = %p\n", p_func);
printf("sum = %d\n", p_func(a, b));
p_func = difference;
printf("difference = %d\n", p_func(a, b));
p_func = product;
printf("product = %d\n", p_func(a, b));
return 0;
}
运行结果:
若需要使用一组函数指针,则可以定义函数指针数组。函数指针数组与指针数组或者是普通变量的数组没有本质区别,在初始化的时候可以使用相同的方式,它就是记录函数地址、指向函数的一组指针。
程序代码:
#include
int sum(int x, int y){
return x + y;
}
int difference(int x, int y){
return x - y;
}
int product(int x, int y){
return x * y;
}
int main(){
int a = 5;
int b = 8;
int (*p_func[3])(int, int) = {
sum,
difference,
product
};
char name[3][20] = {
"sum",
"difference",
"product"
};
printf("p_func sizeof = %d\n", sizeof(p_func));
int i;
for (i = 0; i < 3; i++){
printf("%s(%d, %d) = %d\n",
name[i], a, b, p_func[i](a, b));
}
return 0;
}
执行结果:
我们可以将函数指针作为函数参数传递给函数,这样函数就可以根据指针所指向的函数不同而调用不同的函数了。在这个函数中,通过该函数指针调用的函数被称为回调函数,这种开发方式的用途非常之广泛。
回调函数,就是某函数的使用者定义一个函数,使用者实现这个函数的程序内容,然后把这个函数作为参数传入该函数中,该函数在运行时通过函数指针调用的函数。换句话说,就是在别人写的函数的运行期间来回调你实现的函数。
运行程序:
#include
int sum(int x, int y){
return x + y;
}
int difference(int x, int y){
return x - y;
}
int product(int x, int y){
return x * y;
}
int compute_func(int (*p_func)(int, int), int x, int y){
return p_func(x, y);
}
int main(){
int a = 5;
int b = 8;
int result;
result = compute_func(sum, a, b);
printf("sum = %d\n", result);
result = compute_func(difference, a, b);
printf("difference = %d\n", result);
result = compute_func(product, a, b);
printf("product = %d\n", result);
return 0;
}
执行结果:
完整代码:
#include
#include
#include
void swap(char *a, char *b, int width){
char tmp;
while(width){
tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
width--;
}
}
void sort_array(void *base, int num, int width,
int (*compare)(const void *, const void *)){
int i, j;
void *temp = malloc(width);
for (i = 0; i < num; i++){
for (j = i + 1; j < num; j++){
void *element1 = base + i * width;
void *element2 = base + j * width;
if (compare(element1, element2) > 0){
swap(temp, element1, width);
swap(element1, element2, width);
swap(element2, temp, width);
}
}
}
free(temp);
}
int int_cmp(const void *a, const void *b){
return *(int *)a - *(int *)b;
}
int double_cmp(const void *a, const void *b){
return *(double *)a > *(double *)b ? 1 : -1;
}
int str_cmp(const void *a, const void *b){
return strcmp((char *)a, (char *)b);
}
int str_ptr_cmp(const void *a, const void *b){
return strcmp(*(char **)a, *(char **)b);
}
int main(){
int int_array[8] = {2, 50, 3, -7, 2, 9, 0, 0};
double double_array[8] = {2.5, 13.12, -0.7, 0, -50, 9, -0.123, 0};
const char *str_array_ptr[8] = {
"zzz",
"zzzz",
"xxxxxxxxx",
"p",
"abc",
"ccckkk",
"aaa",
"mmwordilovecoding"
};
char str_array[8][100] = {
"zzz",
"zzzz",
"xxxxxxxxx",
"p",
"abc",
"ccckkk",
"aaa",
"mmwordilovecoding"
};
int i;
sort_array(int_array, 8, sizeof(int), int_cmp);
printf("int_array:\n");
for (i = 0; i < 8; i++){
printf("%d ", int_array[i]);
}
printf("\n\n");
sort_array(double_array, 8, sizeof(double), double_cmp);
printf("double_array:\n");
for (i = 0; i < 8; i++){
printf("%lf ", double_array[i]);
}
printf("\n\n");
sort_array(str_array_ptr, 8, sizeof(str_array_ptr[0]), str_ptr_cmp);
printf("str_array_ptr:\n");
for (i = 0; i < 8; i++){
printf("%s\n", str_array_ptr[i]);
}
printf("\n");
sort_array(str_array, 8, sizeof(str_array[0]), str_cmp);
printf("str_array:\n");
for (i = 0; i < 8; i++){
printf("%s\n", str_array[i]);
}
return 0;
}
static 关键字可将变量指定为静态变量,若在函数的作用域内定义静态变量,当函数退出后,静态变量不会销毁。声明在函数中的静态变量只在该函数第一次执行时初始化一次,虽然它只在包含它的函数中可见,但它是一个全局变量。
代码示例:
#include
void func_test1(){
int count = 0;
count++;
printf("func_test1 count = %d\n", count);
}
void func_test2(){
static int count = 0;
count++;
printf("func_test2 count = %d\n", count);
}
int main(){
int i;
for (i = 0; i < 5; i++){
func_test1();
}
printf("\n");
for (i = 0; i < 5; i++){
func_test2();
}
return 0;
}
运行结果:
在某些情况下,我们希望所有的函数之间共享变量,这种变量称为全局变量,声明为全局的变量可以在它声明后的任意位置访问,全局变量声明方式与一般变量相同,它声明的位置决定了该变量是否是全局的或可以被哪些函数所访问。
一般来讲,不建议将变量声明为全局,因为这样的变量是线程不安全的,即多线程调用时可能出现不一致的结果。这种问题在测试中常被称为单多线程结果不一致。若将变量声明为全局且希望保证结果的一致性,则需要加线程锁,而过多的使用锁又会导致程序性能问题。
代码示例:
#include
int count = 0;
void func_test1(){
count++;
printf("func_test1 count = %d\n", count);
}
void func_test2(){
count++;
printf("func_test2 count = %d\n", count);
}
int main(){
int i;
for (i = 0; i < 5; i++){
func_test1();
}
printf("\n");
for (i = 0; i < 5; i++){
func_test2();
}
return 0;
}
运行结果:
main函数程序执行的起点,main函数也可以有参数列表,即在命令执行时(程序执行时)直接给程序传递参数。
main函数的参数: int main( int argc, char *argv[])
main函数一般没有参数或有两个参数(也可以有更多,或者设置顺序不一样也能编译通过,但是会使得参数没有什么用处)。代表命令提示符下输入数量与具体的字符串默认第1个字符串是该程序的名字。
main函数的返回值:
main函数的返回值返回给操作系统,标准的main函数应该返回int,当然也可以返回double、char*或者什么都不返回void,但这么做操作系统就无法理解该程序的返回值了。main函数的返回值会在程序运行结束后,返回给操作系统,在windows下使用环境变量 %errorlevel% 获取。
示例代码:
#include
int main(int argc, char *argv[]){
printf("argc = %d\n", argc);
int i;
for (i = 0; i < argc; i++){
printf("%s\n", argv[i]);
}
return 999;
}
程序执行结果:
在开发程序时,有时设计函数时需要函数的参数数量与类型是可变的,例如输入函数scanf与输入函数printf。标准库
参数数量可变的函数原型如下:
函数返回值 函数名(参数1类型 参数1名称,参数2类型 参数2名称,...);
在获取参数列表时,需要创建 va_list类型的变量,并同时使用三个宏(注意不是函数):
va_list parg; 定义参数列表parg。
va_start(参数列表parg, 最后一个固定的函数名):这个宏接收两个参数,分别是参数列表指针和该函数原型中最后一个固定参数名。
va_arg(参数列表parg, 类型):这个宏接收两个参数,分别是参数列表指针与当前获取的这个参数的类型。实际上,虽然每次输入的参数数量与类型可以不同,但程序必须指定需要读入多少个参数与每个参数的类型。
va_end():结束时需要调用va_end宏,它会重置parg为NULL,函数结束前该宏必须调用,否则程序后面可能无法正常工作。
示例代码:
#include
#include
double average(int value, ...){
double sum = value;
int count = 1;
va_list parg;
va_start(parg, value);
value = va_arg(parg, int);
while (value != -1){
sum += value;
count++;
value = va_arg(parg, int);
}
va_end(parg);
return sum / count;
}
int main(){
printf("%lf\n", average(1, -1));
printf("%lf\n", average(1, 2, -1));
printf("%lf\n", average(1, 2, 3, -1));
printf("%lf\n", average(1, 2, 3, 4, -1));
printf("%lf\n", average(1, 2, 3, 4, 5, -1));
return 0;
}
执行结果:
设计一个简单版本的printf函数:simple_print(),simple_print使用方法与printf完全一样,但只支持打印整型、字符型、字符串三种数据类型的变量打印,对应的转换说明符为 %d,%c,%s。在打印过程中只可以使用int putchar(int ch);函数,它的作用是在屏幕上以字符形式打印 ch 。在simple_print中, % 号后的字符只需要考虑 d,c,s三个字符。程序不可包含
思考与分析:
函数原型
void simple_print ( const char *control,...){
//需要打印的变量,变量的个数,类型均不确定,由control中的转换说明符来说明
}
simple_print("My name is %s, I'm %d years old.\n,name,age);
My name is ZhangSan, I'm 3 years old
思考:
1. 在打印过程中,如何将control字符串中的常量字符、并根据转换说明符将参数列表的其他变量打印出来?
2. 如何利用putchar函数将整数变量或字符串变量打印在屏幕上?
3. 整体算法是怎样的?代码:
int putchar(int ch);
void print_int(int value){
char temp[30] = {0};
int cnt = 0;
while(value){
temp[cnt++] = value % 10;
value = value / 10;
}
while(cnt){
cnt--;
putchar(temp[cnt] + '0');
}
}
void print_string(const char *value){
while(*value){
putchar(*value);
value++;
}
}
int main(){
int a = 1234567890;
char *str = "abcdefg ### ppp";
print_int(a);
putchar('\n');
print_string(str);
putchar('\n');
return 0;
}
运行:
算法设计
代码实现:
#include
int putchar(int ch);
void print_int(int value){
char temp[30] = {0};
int cnt = 0;
while(value){
temp[cnt++] = value % 10;
value = value / 10;
}
while(cnt){
cnt--;
putchar(temp[cnt] + '0');
}
}
void print_string(const char *value){
while(*value){
putchar(*value);
value++;
}
}
void simple_print(const char *control, ...){
va_list parg;
va_start(parg, control);
while(*control){
if (*control == '%'){
char type = *(control+1);
if (type == 'd'){
int value = va_arg(parg, int);
print_int(value);
}
else if (type == 'c'){
int value = va_arg(parg, int);
putchar(value);
}
else if (type == 's'){
const char *value = va_arg(parg, const char *);
print_string(value);
}
if (type == 'd' || type == 'c' || type == 's'){
control++;
}
}
else{
putchar(*control);
}
control++;
}
va_end(parg);
}
int main(){
char name[20] = "LinMu";
int age = 28;
simple_print("My name is %s, I'm %d years old.\n", name, age);
simple_print("My name is spelled:\n");
int i = 0;
while(name[i]){
simple_print("%c\n", name[i]);
i++;
}
return 0;
}
程序运行:
C语言函数自带的快速排序函数,时间复杂度nlogn,使用qsort时,需要包含头文件
void qsort (void *base, int num, int width, int (*compart) (const void *, const void *))
void *base: 指向待排序的数组; int num:待排序数组的元素个数;int width:待排序数组的元素大小
int(* compare)(const void *, const void *): 排序时的回调函数,用户传进来一个回调函数,在比较两元素的时候,使用这个回调函数对元素进行比较。
#include
#include
int int_cmp(const void *a, const void *b){
return *(int *)a - *(int *)b;
}
int double_cmp(const void *a, const void *b){
return *(double *)a > *(double *)b ? 1 : -1;
}
int main(){
int int_array[8] = {2, 50, 3, -7, 2, 9, 0, 0};
double double_array[8] = {2.5, 13.12, -0.7, 0, -50, 9, -0.123, 0};
int i;
qsort(int_array, 8, sizeof(int), int_cmp);
printf("int_array:\n");
for (i = 0; i < 8; i++){
printf("%d ", int_array[i]);
}
printf("\n\n");
qsort(double_array, 8, sizeof(double), double_cmp);
printf("double_array:\n");
for (i = 0; i < 8; i++){
printf("%lf ", double_array[i]);
}
printf("\n\n");
return 0;
}
执行结果:
C 语言函数库自带的二分查找函数,时间复杂度logn,函数原型与qsort相似,只是多了一个参数key,它指向待查找元素。如果base数组中找到key, 返回对应的数据地址,否则返回空。一般bsearch与qsort结合起来使用,即先快排后二分。
函数原型:
void *bsearch( const void *key, const void *base, int num, int width, int (*compare)(const void *, const void *));
代码:
#include
#include
int int_cmp(const void *a, const void *b){
return *(int *)a - *(int *)b;
}
int main(){
int int_array[8] = {2, 50, 3, -7, 2, 9, 0, 0};
int key[5] = {3, 7, 9, 0 , 5};
qsort(int_array, 8, sizeof(int), int_cmp);
int i;
printf("array:\n");
for (i = 0; i < 8; i++){
printf("%d %p\n", int_array[i], &int_array[i]);
}
printf("\n");
printf("result:\n");
for (i = 0; i < 5; i++){
void *res = bsearch(&key[i], int_array, 8, sizeof(int), int_cmp);
if (res){
printf("%d %p\n", *(int *)res, res);
}
}
return 0;
}
执行结果:
已知一个数组,保存了n个(n<=15)火柴棍,问可否使用这 n 个火柴棍摆成 1 个正方形?
n 个火柴杆(n<=15),每个火柴杆可以属于正方形的4个边中的其中1个。故,暴力搜索(回溯搜索)有4^15种可能。
思考:
无优化的回溯搜索:
想象正方形的4条边即为4个桶,将每个火柴杆回溯的位置放置在每个桶中,在放完 n 个火柴杆后,检查4个桶中的火柴杆长度和是否相等。相同返回真,否则返回假;在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。
优化1: n 个火柴杆的总和对4 取余需要为0,否则返回假。
优化2:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆。
优化3:火柴杆按照从大到小的顺序排序,先尝试大的减少回溯可能。
重要代码练习:
代码实现:
#include
#include
bool generate(int i, int nums[], int numsSize, int target, int bucket[]){
if (i >= numsSize){
return bucket[0] == target && bucket[1] == target
&& bucket[2] == target && bucket[3] == target;
}
int j;
for (j = 0; j < 4; j++){
if (bucket[j] + nums[i] > target){
continue;
}
bucket[j] += nums[i];
if (generate(i + 1, nums, numsSize, target, bucket)){
return true;
}
bucket[j] -= nums[i];
}
return false;
}
int int_cmp(const void *a, const void *b){
return *(int *)b - *(int *)a;
}
bool makesquare(int* nums, int numsSize) {
if (numsSize < 4){
return false;
}
int sum = 0;
int i;
for (i = 0; i < numsSize; i++){
sum += nums[i];
}
if (sum % 4){
return false;
}
qsort(nums, numsSize, sizeof(int), int_cmp);
int bucket[4] = {0};
return generate(0, nums, numsSize, sum / 4, bucket);
}
int main(){
int nums[] = {1, 1, 2, 4, 3, 2, 3};
printf("%d\n", makesquare(nums, 7));
return 0;
}
位运算法:
填代码:
代码实现:
#include
#define MAXN 500000
bool makesquare(int* nums, int numsSize) {
if (numsSize < 4){
return false;
}
int sum = 0;
int i, j;
for (i = 0; i < numsSize; i++){
sum += nums[i];
}
if (sum % 4){
return false;
}
int target = sum / 4;
int ok_subset[MAXN] = {0};
int ok_subset_len = 0;
int ok_half[MAXN] = {0};
int ok_half_len = 0;
int all = 1 << numsSize;
for (i = 0; i < all; i++){
int sum = 0;
for (j = 0; j < numsSize; j++) {
if (i & (1 << j)){
sum += nums[j];
}
}
if (sum == target){
ok_subset[ok_subset_len++] = i;
}
}
for (i = 0; i < ok_subset_len; i++){
for (j = i + 1; j < ok_subset_len; j++){
if ((ok_subset[i] & ok_subset[j]) == 0){
ok_half[ok_half_len++] = ok_subset[i] | ok_subset[j];
}
}
}
for (i = 0; i < ok_half_len; i++){
for (j = i + 1; j < ok_half_len; j++){
if ((ok_half[i] & ok_half[j]) == 0){
return true;
}
}
}
return false;
}
int main(){
int nums[] = {1, 1, 2, 4, 3, 2, 3};
printf("%d\n", makesquare(nums, 7));
return 0;
}
运行结果: