使用自定义堆,代替系统缺省堆?

本文探讨了为避免内存泄漏,通过自定义全局堆来替代系统默认堆的实现。作者详细介绍了自定义堆的管理结构、分配与释放策略,并在遇到问题时进行了优化,包括合并连续的空闲块以减少碎片。同时,文章提到了使用自定义堆可能带来的二进制文件大小增加和全局区域大小的设定问题。虽然解决了内存管理问题,但作者也提醒了线程安全和数据溢出等潜在风险。

前一篇文章追踪heap使用,检测内存泄漏?既然没有解决问题(上一篇主要是帮助发现问题),那么能不能避开缺省堆呢,使用自己管理的堆,调用接口又能完全兼容呢?于是,有了如下想法:
1. 使用一大段全局区作为自定义的堆(代码中的g_heap[MAX_HEAP_SIZE]),所有的堆分配请求,和用于堆管理的内存(HEAP_BLOCK)都使用这段内存。
2. 将这一大段区域,处理成HEAP_BLOCK为结点的双向链表,每个结点HEAP_BLOCK要么处于已分配状态(ALLOCATED),要么可分配状态(FREE),每个结点还包含内存大小等信息,具体见HEAP_BLOCK定义。注意:为了避免使用系统缺省堆,结点在自定义heap区域是依次连续的,只是结点状态和内存大小会随着分配和释放不停变化。初始状态下,整个自定义heap区域被视为一个FREE态的HEAP_BLOCK。
3. 分配请求的处理:在链表中寻找第一个未分配且buff足够大的HEAP_BLOCK,如找到就进行分配(即1个FREE的HEAP_BLOCK可能分裂成1个ALLOCATED的HEAP_BLOCK和1个FREE的HEAP_BLOCK);如果没找到,先尝试合并连续的FREE态的HEAP_BLOCK(即将多个连续的FREE态HEAP_BLOCK合并成一个大的FREE态HEAP_BLOCK)。
4. 释放请求的处理:根据传入的地址,将对应的HEAP_BLOCK标记为FREE。
5. 已考虑:统计自定义堆的内存使用峰值,避免多个很小的FREE态HEAP_BLOCK(避免太多内存碎片),等等,具体见代码。
6. 未考虑:线程安全;已尽可能考虑数据大小溢出的问题。
以下是代码(macro USE_MY_HEAP包含的是本文描述的内容,macro USE_SYS_HEAP_TRACK包含的是上一篇文章描述的内容),和遇到问题的简单总结。


完整的头文件的内容如下(heap_wrapper.h):
 

#pragma once

#define USE_MY_HEAP               //use the heap implemented based on a big range of global data
//#define USE_SYS_HEAP_TRACK        //use the system heap and we track the heap usage
//#define USE_SYS_HEAP_NO_TRACK     //just the system heap


#if defined(USE_MY_HEAP)
void * custom_calloc(uint32_t num, uint32_t size, const char* function_name, uint32_t line_num);
void custom_free(void* ptr);
void print_heap_info();

#define NEW_CALLOC(num,size)  custom_calloc(num,size,__FUNCTION__,__LINE__)
#define NEW_FREE(ptr)         custom_free(ptr)
#define PRINT_HEAP_INFO()     print_heap_info()
#endif

#if defined(USE_SYS_HEAP_TRACK)
void * custom_calloc(uint32_t num, uint32_t size, const char* function_name, uint32_t line_num);
void custom_free(void* ptr);
void print_heap_info();

#define NEW_CALLOC(num,size)  custom_calloc(num,size,__FUNCTION__,__LINE__)
#define NEW_FREE(ptr)         custom_free(ptr)
#define PRINT_HEAP_INFO()     print_heap_info()
#endif

#if defined(USE_SYS_HEAP_NO_TRACK)
#define NEW_CALLOC            calloc
#define NEW_FREE              free
#define PRINT_HEAP_INFO()     print_heap_info()
#endif

完整的源代码文件如下(heap_wrapper.c):
 

#include "heap_wrapper.h"

#if defined(USE_MY_HEAP)
//use a big range of global area as the heap
//all heap request from callers and heap management is based on it

//the global heap area is a list of blocks, either in allocated state (used for a heap memory request)  or free state(not used and available for request)
//each block begins with a fixed-size block information (state, start/size, function_name/line_num, next/previous) and a variable buffer (specified by start/size in block information)

//initially, the whole heap area is one block of free area

//Process a calloc request:
//step 1: search the first free block that is big enough, 
//step 2: if found, 
//step 2.1: do the allocation: that is 1 free block changed into 1 allocated block and 1 free block(if more bytes left)
//Step 2.2: inserted the 2 new blocks into the list to replace the original one
//Step 3:  if not found, merge the continous free blocks: multiple continous free block are replaced with 1 big free block in the list, goto step 1 and try again

//Process a free request:
//per the input address, find the block in the list and mark it as free

//limitation not thread safe



//if after a allocate request, the left memory is not big enough, not to mark it as a new free block
//to avoid too many small free blocks
#define MIN_BLOCK_AVAILABLE_BUFF_SIZE   (32)   
#define MAX_HEAP_SIZE                   (2*1024*1024)  //2MB

#dfine MAX_FUNCTION_NAME_LEN            (32)
static char    g_function_name[MAX_FUNCTION_NAME_LEN] = "unkown";
static uint8_t g_heap[MAX_HEAP_SIZE];

typedef enum _HEAP_BLOCK_STATE_
{
	HEAP_BLOCK_FREE = 0,
	HEAP_BLOCK_ALLOCATED,
}HEAP_BLOCK_STATE;

typedef struct _HEAP_BLOCK_ HEAP_BLOCK, *PHEAP_BLOCK;
typedef struct _HEAP_BLOCK_
{
	HEAP_BLOCK_STATE state;
	void*            start;       //start of buffer for caller's request (not including this HEAP_BLOCK)
	uint32_t         size;        //buffer size available for caller's request (not including this HEAP_BLOCK)
	uint32_t         line_num;
	char             function_name[MAX_FUNCTION_NAME_LEN];   //NOTE: trunc it if too long
	PHEAP_BLOCK      next;
	PHEAP_BLOCK      previous;	
}HEAP_BLOCK, *PHEAP_BLOCK; 

typedef struct _HEAP_CONTEXT_
{
	uint64_t heap_size;          //size of all the reserved heap memory
	uint64_t peak_size;          //peak size of all the requests from callers
	uint64_t total_alloc_size;   //current allocated size from callers
	uint32_t total_alloc_count;  //increment for every request, decrement for every free
	uint32_t total_unexpected;   //tracking unexpected cases
	uint32_t total_merge_count; //count of merging continous free areas
	PHEAP_BLOCK head;
	PHEAP_BLOCK tail;	
}HEAP_CONTEXT;

static HEAP_CONTEXT g_heap_context;

//initially, the whole heap area is one block of free area
static void init_heap_context()
{
	if(NULL == g_heap_context.head)
	{
		PHEAP_BLOCK first_block = (PHEAP_BLOCK)g_heap;
		memset(first_block, 0, sizeof(HEAP_BLOCK));
		first_block->state = HEAP_BLOCK_FREE;
		first_block->start = (uint8_t*)first_block + sizeof(HEAP_BLOCK);
		first_block->size = (uint32_t)(sizeof(g_heap) - sizeof(HEAP_BLOCK));
		
		//populate the g_heap_context
		memset(&g_heap_context, 0, sizeof(g_heap_context));
		g_heap_context.heap_size = (uint64_t)sizeof(g_heap);
		g_heap_context.head = first_block;
		g_heap_context.tail = first_block;
	}
}

//merge the continous free blocks: multiple continous free block are replaced with 1 big free block in the list
static void merge_continuous_free_block()
{
	PHEAP_BLOCK free_block = NULL;            //first free block found
	PHEAP_BLOCK next_allocated_block = NULL;  //next allocated block after <free_block> in the list, 
	PHEAP_BLOCK temp = NULL;
	uint32_t total_size = 0;
	uint8_t  meet_merge_condition = 0;
	if(g_heap_context.head)
	{
		free_block = g_heap_context.head;
		while(free_block)
		{
			while(free_block && (HEAP_BLOCK_FREE != free_block.state))  //to find the free block
			{
				free_block = free_block.next;
			}
			
			if(free_block)  //found a free block, then to find next allocated block
			{
				next_allocated_block = free_block.next;
				while(next_allocated_block && (HEAP_BLOCK_ALLOCATED != next_allocated_block.state)) //to find the allocated block
				{
					next_allocated_block = next_allocated_block->next;
				}
				
				//merge condition: more than 1 free blocks (including <free_block>), multiple free blocks in the end of list
				//merge condition: more than 1 free blocks (including <free_block>) | allocated block
				meet_merge_condition = 0;
				if(NULL == next_allocated_block && NULL != free_block->next)
				{
					meet_merge_condition = 1;
				}
				if((NULL != next_allocated_block) && (next_allocated_block != free_block->next))  //not the merge condition
				{
					meet_merge_condition = 1;
				}
				
		        if(!meet_merge_condition)  //not the merge condition
				{
					free_block = next_allocated_block;
					continue;
				}
				else  //meet the merge condition: from <free_block> to revious of <next_allocated_block> (could be NULL) to merge into 1 big free block
				{
					temp = free_block;
					total_size = 0;
					
					//calc the total size
					while(temp->next != next_allocated_block)
					{
						total_size += (sizeof(HEAP_BLOCK) + temp->size);
						temp = temp->next;
					}
					
					free_block->size = total_size - sizeof(HEAP_BLOCK);
					free_block->next = next_allocated_block;
					//no changes to other fields
					memset(free_block->start, 0, free_block->size);
					
					if(NULL != next_allocated_block)
					{
						next_allocated_block->previous = free_block;						
					}
					else
					{
						g_heap_context.tail = free_block;
					}
					
					free_block = next_allocated_block;
					g_heap_context.total_merge_count++;					
				}				
			}
		}
	}
}

void * custom_calloc(uint32_t num, uint32_t size, const char* function_name, uint32_t line_num)
{
	void* ret_addr = NULL;
	uint32_t request_size = num * size;
	uint32_t temp_available_size = 0;
	uint32_t merge_count = 0;
	uint8_t  retried_flag = 0;
	PHEAP_BLOCK temp_block = NULL;
	PHEAP_BLOCK new_block = NULL;
	char* func_name = NULL;
	
	if(request_size <= 0 || request_size > sizeof(g_heap))
	{
		LOGD("heap input wrong size %d", request_size);
		return NULL;
	}
	
	if(NULL == g_heap_context.head)
	{
		init_heap_context();
	}
	
	func_name = (char*)function_name;
	if(NULL == func_name)
	{
		func_name = g_function_name;
	}
	
	LOGD("heap input size %d, %s:%d", request_size, func_name, line_num);
	
try_again:
     
	//search the first free block that is big enough
	temp_block = g_heap_context.head;
	while(temp_block)
	{
		if((HEAP_BLOCK_FREE == temp_block->state) && (temp_block->size >= request_size))
		{
			break;
		}
		else
		{
			temp_block = temp_block->next;
		}
	}
	
	//if not found, merge the continous free blocks: multiple continous free block are replaced with 1 big free block in the list, goto step 1 and try again
	if(NULL == temp_block)
	{
		if(0 == retried_flag)
		{
			retried_flag = 1;
			g_heap_context.total_unexpected++;
			merge_count = g_heap_context.total_merge_count;
			merge_continuous_free_block();
			if(merge_count != g_heap_context.total_merge_count)
			{
				goto try_again;	
			}
		}
	}
	else
	{
		//step 2.1: do the allocation: that is 1 free block changed into 1 allocated block and 1 free block(if more bytes left)
		//Step 2.2: inserted the 2 new blocks into the list to replace the original one
		//big enough but not too much, not split it
		if(temp_block->size < (request_size + sizeof(HEAP_BLOCK) + MIN_BLOCK_AVAILABLE_BUFF_SIZE))  //NOTE: this is to avoid too many small free blocks
		{
			ret_addr = temp_block->start;
			temp_block->state = HEAP_BLOCK_ALLOCATED;
			//temp_block->size not changed  //the whold block is allocated and no new free block generated
			temp_block->line_num = line_num;
			strncpy(temp_block->function_name, func_name, sizeof(temp_block->function_name));
		}
		else  //big enough and too much, split into 2
		{
			ret_addr = temp_block->start;
			temp_available_size = temp_block->size;   //split into request_size | new_block | available size of new_block
			
			//first block of the 2
			temp_block->state = HEAP_BLOCK_ALLOCATED;
			//temp_block->start   //not changed
			temp_block->size = request_size;
			temp_block->line_num = line_num;
			strncpy(temp_block->function_name, func_name, sizeof(temp_block->function_name));
			
			//second block of the 2
			new_block = (PHEAP_BLOCK)((uint8_t*)temp_block->start + temp_block->size);
			new_block->state = HEAP_BLOCK_FREE;
			new_block->start = (uint8_t*)new_block + sizeof(HEAP_BLOCK);
			new_block->size = temp_available_size - request_size - sizeof(HEAP_BLOCK);
			new_block->next = temp_block->next;
			new_block->previous = temp_block;
			
			temp_block->next = new_block;
			if(g_heap_context.tail == temp_block)
			{
				g_heap_context.tail = new_block;
			}
		}
		
		g_heap_context.total_alloc_count++;
		g_heap_context.total_alloc_size += temp_block->size;
		if(g_heap_context.total_alloc_size > g_heap_context.peak_size)
		{
			g_heap_context.peak_size = g_heap_context.total_alloc_size;
		}		
	}
	
	if(ret_addr)
	{
		print_heap_info();
	}
	else
	{
		LOGD("heap alloc failed");
	}
	
	return ret_addr;
}

void custom_free(void* ptr)
{
	PHEAP_BLOCK temp_block = NULL;
	if(ptr)
	{
		temp_block = g_heap_context.head;
		while(temp_block && (ptr != temp_block->start))
		{
			temp_block = temp_block->next;
		}
		
		if(NULL == temp_block)
		{
			g_heap_context.total_unexpected++;
			LOGD("heap address not found");
		}
		else
		{
			if(HEAP_BLOCK_ALLOCATED != temp_block->state)
			{
				g_heap_context.total_unexpected++;
			}
			
			LOGD("heap free node size %d, %s:%d", temp_block->size, temp_block->function_name, temp_block->line_num);
			temp_block->state = HEAP_BLOCK_FREE;
			memset(temp_block->function_name, 0, sizeof(temp_block->function_name));
			temp_block->line_num = 0;
			memset(temp_block->start, 0, temp_block->size);
			
			g_heap_context.total_alloc_count--;
			g_heap_context.total_alloc_size -= temp_block->size;
		}
	}
}

void print_heap_info()
{
	PHEAP_BLOCK temp_block = g_heap_context.head;
	while(NULL != temp_block)
	{
		if(HEAP_BLOCK_ALLOCATED == temp_block->state)
		{
			LOGD("heap alloc node size %d, %s:%d", temp_block->size, temp_block->function_name, temp_block->line_num);
		}
		else
		{
			LOGD("heap free node size %d", temp_block->size);
		}
		
		temp_block = temp_block->next;
	}
	
	LOGD("heap count %d, size %lld, peak %lld, fail %d, merge %d", g_heap_context.total_alloc_count, 
	g_heap_context.total_alloc_size, g_heap_context.peak_size, g_heap_context.total_unexpected, g_heap_context.total_merge_count);	
}

#endif

#if defined(USE_SYS_HEAP_TRACK)  //track the system heap usage information
//just track the function_name:line_number which requests buffer from heap
//track when it's freed
//then when know if there is any heap memory leak

#define MAX_FUNCTION_NAME_LEN        (32)

typedef struct _HEAP_BLOCK_ HEAP_BLOCK, *PHEAP_BLOCK;
typedef struct _HEAP_BLOCK_
{
	void*            start;       //system allocated buffer address
	uint32_t         size;        //request size from callers
	uint32_t         line_num;
	char             function_name[MAX_FUNCTION_NAME_LEN];   //NOTE: trunc it if too long
	PHEAP_BLOCK      next;
}HEAP_BLOCK, *PHEAP_BLOCK; 

typedef struct _HEAP_CONTEXT_
{
	uint64_t total_alloc_size;   //current allocated size from callers
	uint32_t total_alloc_count;  //increment for every request, decrement for every free
	uint32_t total_unexpected;   //tracking unexpected cases
	PHEAP_BLOCK head;
	PHEAP_BLOCK tail;	
}HEAP_CONTEXT;

static HEAP_CONTEXT g_heap_context;


void * custom_calloc(uint32_t num, uint32_t size, const char* function_name, uint32_t line_num)
{
	void* ret_addr = calloc(num * size);
	
	LOGD("heap input size %d, %s:%d", num * size, function_name, line_num);
	
	if(ret_addr)
	{
		PHEAP_BLOCK temp_block = (PHEAP_BLOCK)calloc(1, sizeof(HEAP_BLOCK));
		if(temp_block)
		{
			temp_block->start = ret_addr;
			temp_block->size = num * size;
			temp_block->line_num = line_num;
			strncpy(temp_block->function_name, func_name, sizeof(temp_block->function_name));
			
			g_heap_context.total_alloc_count++;
			g_heap_context.total_alloc_size += temp_block->size;
			
			if(NULL == g_heap_context.head)
			{
				g_heap_context.head = temp_block;
				g_heap_context.tail = temp_block;
			}
			else
			{
				g_heap_context.tail->next = temp_block;
				g_heap_context.tail = temp_block;
			}
		}
		else
		{
			g_heap_context.total_unexpected++;
		}
	}
	else
	{
		g_heap_context.total_unexpected++;
	}
	
	
	if(ret_addr)
	{
		print_heap_info();
	}
	else
	{
		LOGD("heap alloc failed");
	}
	
	return ret_addr;
}

void custom_free(void* ptr)
{
	PHEAP_BLOCK temp_block = NULL;
	PHEAP_BLOCK previous_block = NULL;
	if(ptr)
	{
		temp_block = g_heap_context.head;
		previous_block = NULL;
		while(temp_block && (ptr != temp_block->start))
		{
			previous_block = temp_block;
			temp_block = temp_block->next;
		}
		
		if(NULL == temp_block)
		{
			g_heap_context.total_unexpected++;
			LOGD("heap address not found");
		}
		else
		{			
			LOGD("heap free node size %d, %s:%d", temp_block->size, temp_block->function_name, temp_block->line_num);
			if(g_heap_context.head != temp_block && g_heap_context.tail != temp_block )
			{
				previous_block->next = temp_block->next;
			}
			else  //found block could be head or tail or head and tail(only 1 block)
			{		  
				if(g_heap_context.head == temp_block)
				{
					g_heap_context.head = temp_block->next;				
				}
				
				if(g_heap_context.tail == temp_block)
				{
					g_heap_context.tail = previous_block;
					if(NULL != previous_block)
					{
						previous_block->next = NULL;
					}
				}
			}
			free(temp_block);
			
			g_heap_context.total_alloc_count--;
			g_heap_context.total_alloc_size -= temp_block->size;
		}
		
		free(ptr);
	}
}

void print_heap_info()
{
	PHEAP_BLOCK temp_block = g_heap_context.head;
	while(NULL != temp_block)
	{
		LOGD("heap alloc node size %d, %s:%d", temp_block->size, temp_block->function_name, temp_block->line_num);
		
		temp_block = temp_block->next;
	}
	
	LOGD("heap count %d, size %lld, fail %d", g_heap_context.total_alloc_count, g_heap_context.total_alloc_size, g_heap_context.total_unexpected);	
}
#endif

调试过程中的问题总结:
1. 使用自定义堆,会直接增大编译生成的二进制文件大小;可以减少编译脚本(链接文件)中的HEAP_SIZE,有时可能要权衡一下。问题来了,这个HEAP_SZIE可以减少到0吗? 肯定是不行的,虽然我们代码不用缺省堆,但调用我们代码的,和我们代码调用的,对我们来说都是黑盒子,能保证他们都不用缺省堆吗?保险起见,可以先使用缺省值吧。
2. 那么这个全局区域最大设为多少呢? 可以根据peak_size统计进行设置,甚至也可以实现方案略加修改,变为自定义堆+缺省堆的方案。
3. 低级错误1:日志打印的%s和%d与实际的参数不对应导致的问题。由此可见,本地搭建完整的测试环境的重要性。
4. 低级错误2:最初内存分配custom_calloc的入参num/size为uint16_t,程序实际使用过程中会溢出。由此可见,测试驱动开发,测试用例设计的重要性。
5. 低级错误3:custom_calloc中最初实现的查找“第一个未分配且buff足够大的HEAP_BLOCK”的while循环的条件没有指定正确。这类条件设定,逻辑一定要越简单越清晰越好。

那最终问题解决了吗? 解决了,不是内存泄漏,不是一个问题。
 

【2022年5.1节 重庆】

<think>嗯,用户现在问的是“函数的缺省”,也就是函数的默认参数。首先,我需要确认他们指的是默认参数,因为在编程中,有时候不同的术语可能有不同的叫法。比如在C++里叫默认参数,Python里也是类似的概念,而有些语言可能用不同的机制实现类似功能。 接下来,我要回忆一下之前用户的问题历史。之前他们问过函数重载,而默认参数和重载在某些情况下可以解决类似的问题,比如让函数处理不同数量的参数。可能需要对比一下两者的区别,帮助用户更好地理解什么时候用默认参数,什么时候用重载。 用户可能的需求是什么?他们可能是在学习函数的不同特性,想了解如何让函数更灵活。比如,如何让函数在调用时不必每次都传递所有参数,特别是在某些参数有常用值的情况下。例如,一个计算价格的函数,税率可能通常是固定的,但偶尔需要调整,这时候默认参数就很有用。 然后,我需要考虑用户的知识水平。他们之前已经了解了函数重载,所以对函数的基本概念应该有一定了解。需要以简单的例子开始,逐步说明默认参数的作用、语法以及注意事项。同时,要指出不同编程语言中的差异,比如Python和C++中默认参数的写法可能不同,但JavaScript ES6之后也支持默认参数。 还要注意用户可能遇到的常见问题。比如,默认参数的位置问题,是否必须在参数列表的最后;或者默认参数的值是在函数定义时计算还是每次调用时重新计算。例如,在Python中,默认参数如果是可变对象(比如列表),可能会导致意料之外的行为,因为默认值只在函数定义时初始化一次。 然后,举一些实际的例子,比如创建用户信息的函数,或者计算数值的例子,这样用户能直观地看到默认参数如何简化调用。同时,对比默认参数和函数重载的适用场景,帮助用户理解两者的优缺点。 最后,总结一下默认参数的优势和使用时的注意事项,确保用户不仅知道怎么用,还能避免常见的陷阱。比如,在C++中,默认参数只能从右往左设置,而Python中也是如此,如果中间参数有默认值,后面的所有参数都必须有默认值。 可能用户的实际应用场景包括处理配置选项、减少重复代码、提高API的易用性等。例如,GUI库中的窗口创建函数可能有多个可选参数,如标题、大小、是否可调整等,使用默认参数可以让常用配置更简洁,同时允许需要时自定义。 需要确保回答结构清晰,分点说明,每个部分有例子,并且语言通俗易懂。同时,注意使用中文,避免专业术语过多,必要时进行解释。</think>函数的缺省参数(默认参数)是编程中一种简化函数调用的特性,它允许在定义函数时为某些参数**预先指定默认值**。当调用函数时,若未传递这些参数,则自动使用默认值;若传递了值,则覆盖默认值。它的核心作用是**减少重复代码**并**提高代码的灵活性**。 --- ### 缺省参数的作用 1. **简化调用**:隐藏非必要参数的复杂性,使常用场景的调用更简洁。 2. **向后兼容**:新增参数时,不影响已有代码的调用。 3. **逻辑集中**:避免为不同参数组合编写多个重载函数。 --- ### 具体语法(以Python为例) ```python def 函数名(参数1, 参数2=默认值): # 函数体 ``` --- ### 实际例子 #### 1. 基础用法:用户注册信息 ```python def create_user(name, role="普通用户", is_active=True): print(f"创建用户:{name},角色:{role},状态:{'活跃' if is_active else '禁用'}") # 调用 create_user("张三") # 使用默认角色和状态 create_user("李四", "管理员") # 覆盖角色,状态仍用默认 create_user("王五", is_active=False) # 跳过role参数,通过关键字指定is_active ``` 输出: ``` 创建用户:张三,角色:普通用户,状态:活跃 创建用户:李四,角色:管理员,状态:活跃 创建用户:王五,角色:普通用户,状态:禁用 ``` #### 2. 数学计算:带默认精度的除法 ```python def divide(a, b, precision=2): result = a / b return round(result, precision) print(divide(10, 3)) # 输出 3.33(默认精度2位) print(divide(10, 3, 4)) # 输出 3.3333(指定精度4位) ``` #### 3. Web请求:带默认超时时间 ```python def send_request(url, timeout=5, retry=3): print(f"请求 {url},超时:{timeout}秒,重试次数:{retry}") send_request("https://api.example.com") # 使用全部默认值 send_request("https://api.example.com", retry=5) # 通过关键字参数跳过timeout ``` --- ### 注意事项 1. **默认参数的位置**: 缺省参数必须定义在**参数列表末尾**,否则会报错: ❌ 错误示例:`def func(a=1, b)` ✅ 正确示例:`def func(b, a=1)` 2. **默认值只计算一次**(重要!): 当默认值为**可变对象**(如列表、字典)时,会产生意外行为: ```python def add_item(item, items=[]): # 默认空列表在函数定义时创建 items.append(item) return items print(add_item(1)) # 输出 [1] print(add_item(2)) # 输出 [1, 2](而不是预期的[2]!) ``` **正确做法**:用`None`代替可变默认值: ```python def add_item(item, items=None): if items is None: items = [] # 每次调用时新建列表 items.append(item) return items ``` 3. **语言差异**: - C++/JavaScript:类似Python的语法 - Java:原生不支持,需通过方法重载模拟 --- ### 与函数重载的对比 | 特性 | 缺省参数 | 函数重载 | |--------------|-----------------------------------|------------------------------| | 实现方式 | 通过定义默认值 | 定义多个同名函数 | | 适用场景 | 参数有明确默认值的场景 | 参数类型/数量差异大的场景 | | 代码量 | 更简洁 | 需编写多个函数 | | 典型语言 | Python, C++, JavaScript (ES6+) | Java, C# | --- ### 实际应用场景 1. **配置选项**:图形界面库中窗口的创建(默认尺寸、颜色等) 2. **API调用**:网络请求库的请求参数(超时时间、重试次数) 3. **数据转换**:字符串格式化函数(默认分隔符、编码方式) ```python # 文件保存函数示例 def save_file(content, filename="untitled.txt", mode="w", encoding="utf-8"): with open(filename, mode, encoding=encoding) as f: f.write(content) save_file("Hello") # 保存到默认文件 save_file("Data", mode="a") # 追加模式 save_file("こんにちは", encoding="shift_jis") # 指定日文编码 ``` --- ### 总结 缺省参数通过为参数提供默认值,使函数在**常用场景**下调用更简洁,同时保留**扩展性**。合理使用可以显著减少代码冗余,但需特别注意**可变默认值**的陷阱。它常与函数重载配合使用,共同提升代码的可用性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值