学习笔记-C语言7(函数设计进阶)

目录

函数指针

函数指针数组

函数指针用作函数参数

例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;
}

运行结果:

学习笔记-C语言7(函数设计进阶)_第1张图片

函数指针数组

若需要使用一组函数指针,则可以定义函数指针数组。函数指针数组与指针数组或者是普通变量的数组没有本质区别,在初始化的时候可以使用相同的方式,它就是记录函数地址、指向函数的一组指针

程序代码:

#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;
}

执行结果:

学习笔记-C语言7(函数设计进阶)_第2张图片

函数指针用作函数参数

我们可以将函数指针作为函数参数传递给函数,这样函数就可以根据指针所指向的函数不同而调用不同的函数了。在这个函数中,通过该函数指针调用的函数被称为回调函数,这种开发方式的用途非常之广泛。

回调函数,就是某函数的使用者定义一个函数,使用者实现这个函数的程序内容,然后把这个函数作为参数传入该函数中,该函数在运行时通过函数指针调用的函数。换句话说,就是在别人写的函数的运行期间回调你实现的函数。

学习笔记-C语言7(函数设计进阶)_第3张图片

运行程序:

#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;
}

执行结果:

学习笔记-C语言7(函数设计进阶)_第4张图片

例1-通用的选择排序

学习笔记-C语言7(函数设计进阶)_第5张图片

学习笔记-C语言7(函数设计进阶)_第6张图片

学习笔记-C语言7(函数设计进阶)_第7张图片

学习笔记-C语言7(函数设计进阶)_第8张图片

学习笔记-C语言7(函数设计进阶)_第9张图片

 完整代码:

#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;
}

运行结果:

学习笔记-C语言7(函数设计进阶)_第10张图片

函数间共享变量:全局变量

在某些情况下,我们希望所有的函数之间共享变量,这种变量称为全局变量,声明为全局的变量可以在它声明后的任意位置访问,全局变量声明方式与一般变量相同,它声明的位置决定了该变量是否是全局的或可以被哪些函数所访问。

一般来讲,不建议将变量声明为全局,因为这样的变量是线程不安全的,即多线程调用时可能出现不一致的结果。这种问题在测试中常被称为单多线程结果不一致。若将变量声明为全局且希望保证结果的一致性,则需要加线程锁,而过多的使用锁又会导致程序性能问题。

代码示例:

#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;
}

运行结果:

学习笔记-C语言7(函数设计进阶)_第11张图片

Main函数

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;
}

程序执行结果: 

学习笔记-C语言7(函数设计进阶)_第12张图片

参数数量可变的函数

在开发程序时,有时设计函数时需要函数的参数数量与类型是可变的,例如输入函数scanf与输入函数printf。标准库中提供了相应的方法来实现参数数量可变的函数。在设计函数原型时,固定参数需要几个写几个,至少需要提供1 个固定参数,“...”表示可变参数

参数数量可变的函数原型如下:

函数返回值 函数名(参数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;
}

执行结果:

学习笔记-C语言7(函数设计进阶)_第13张图片

例2-简单版本的printf函数

设计一个简单版本的printf函数:simple_print(),simple_print使用方法与printf完全一样,但只支持打印整型、字符型、字符串三种数据类型的变量打印,对应的转换说明符为 %d,%c,%s。在打印过程中只可以使用int putchar(int ch);函数,它的作用是在屏幕上以字符形式打印 ch 。在simple_print中, % 号后的字符只需要考虑 d,c,s三个字符。程序不可包含头文件,putchar的函数原型在程序中给出。

思考与分析:

函数原型

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;
}

 运行:

 

算法设计

学习笔记-C语言7(函数设计进阶)_第14张图片

代码实现:

#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语言7(函数设计进阶)_第15张图片

C语言库函数-快速排序qsort

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语言7(函数设计进阶)_第16张图片

C语言库函数-二分查找bsearch

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;
}

执行结果:

学习笔记-C语言7(函数设计进阶)_第17张图片

例-火柴棍摆正方形(LeetCode 473. Matchsticks to Square)

已知一个数组,保存了n个(n<=15)火柴棍,问可否使用这 n 个火柴棍摆成 1 个正方形?

学习笔记-C语言7(函数设计进阶)_第18张图片

n 个火柴杆(n<=15),每个火柴杆可以属于正方形的4个边中的其中1个。故,暴力搜索(回溯搜索)有4^15种可能。

思考:

  1. 回溯算法如何设计,与第5课算法设计与提高, 求子集题目有哪些相似的地方?
  2. 如何设计递归函数,递归的回溯搜索何时返回真,何时返回假?
  3. 普通的回溯搜索可否解决该问题,如何对深度搜索如何优化(剪枝),即可使得到回溯搜索更加高效?
  4. 该题是否也可以用位运算的方式解决?

无优化的回溯搜索:

想象正方形的4条边即为4个桶,将每个火柴杆回溯的位置放置在每个桶中,在放完 n 个火柴杆后,检查4个桶中的火柴杆长度和是否相等。相同返回真,否则返回假;在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。

学习笔记-C语言7(函数设计进阶)_第19张图片

优化1: n 个火柴杆的总和对4 取余需要为0,否则返回假。

优化2:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆。

优化3:火柴杆按照从大到小的顺序排序,先尝试大的减少回溯可能

学习笔记-C语言7(函数设计进阶)_第20张图片

重要代码练习:

学习笔记-C语言7(函数设计进阶)_第21张图片

学习笔记-C语言7(函数设计进阶)_第22张图片

  代码实现:

#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;
}

位运算法:

学习笔记-C语言7(函数设计进阶)_第23张图片

 填代码:

学习笔记-C语言7(函数设计进阶)_第24张图片

代码实现:

#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;
}

运行结果:

 

 

 

你可能感兴趣的:(C语言学习)