《嵌入式C语言笔记(十六):字符串搜索、动态内存与函数指针精要》

发布于:2025-08-01 ⋅ 阅读:(10) ⋅ 点赞:(0)

今日学习内容

字符串操作进阶

查找与统计长字符串中的短字符串,使用strncmp

遍历长字符串每个起始位置,比较从该位置开始的子串与短字符串。

int subString(const char *s, const char *sub)
{
	int i;
	int sLen = strlen(s);
	int subLen = strlen(sub);
	int k = sLen - subLen;

	for(i = 0;i <= k;++i)
	{
		if(strncmp(s + i, sub, subLen) == 0)
		{
			return i;
		}
	}
	return -1;
}

int main(void)
{
	printf("%d\n", subString("Hello He is handsome He He She", "He"));
	return 0;
}

计数,匹配成功时计数增加。需处理重叠匹配情况。

int counterString(const char *s, const char *sub)
{
	int counter = 0;
	int t = 0;
	int ret;
	ret = subString(s, sub);

	while(ret >= 0)
	{
		++counter;
		t = t + ret + strlen(sub);
		ret = subString(s + t, sub);
	}
	return counter;
}


int main(void)
{
	printf("%d\n", counterString("Hello He is handsome He He She", "He"));
	return 0;
}

strcpy自复制优化

标准库函数strcpy(s, s)能安全处理自复制情况。

char *Strcpy(char *dest, const char *src)
{
	if(dest == src)
	{
		return dest;
	}
	char *ret = dest;
	while(*src)
	{
		*dest++ = *src++;
	}
	*dest = 0;
	return ret;
}

strcat自连接陷阱

strcat(s, s)会覆盖结束符导致无限循环。动态内存分配是可靠解决方案:

char *Strcat(char *dest, const char *src)
{
	char s[100];
	char *p = s;
	if(dest == src)
	{
		strcpy(s, src);
	}
	char *ret = dest;
	while(*dest)
	{
		++dest;
	}
	while(*p)
	{
		*dest++ = *p++;
	}
	*dest = 0;
	return ret;
}


int main(void)
{
	char s[100] = "Hello";
	Strcat(s, s);
	puts(s);

	return 0;
}


动态内存管理

核心函数

函数 原型 功能
malloc void* malloc(size_t size) 分配未初始化内存
calloc void* calloc(size_t num, size_t size) 分配归零内存
realloc void* realloc(void *ptr, size_t new_size) 调整内存块大小
free void free(void *ptr) 释放内存

使用规范

        使用完内存后,要用free释放内存

malloc

char *p = malloc(strlen(s) + 1);

realloc:重新分配,并且复制原来的内容,归还之前的内存

p = realloc(p, strlen(p) + strlen(t) + 1);

calloc:分配为零的内存

int *p = calloc(n, sizeof(int));

free:归还内存,不归还会导致内存泄漏。

    free(p);
	p = NULL;


函数指针与多级指针

函数指针声明

语法:返回类型 (*指针名)(参数列表)

能指向函数入口地址的指针

回调函数

        降低耦合性

int add(int a, int b)
{
	return a + b;
}

int sub(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;
}

排序(qsort)

传参方式,例子在下下一个。

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
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 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;
}

指针的指针(二级指针)应用,修改一级指针的内容

void printStrings(char **p, int len)
{
	int i;
	for(i = 0;i < len;++i)
	{
		puts(p[i]);
	}
}

void reverseStrings(char **p, int len)
{
	int i;
	for(i = 0;i < len / 2;++i)
	{
//		char t[100];
//		strcpy(t, p[i]);
//		strcpy(p[i], p[len - i - 1]);
//		strcpy(p[len - i - 1], t);
		char *t;
		t = p[i];
		p[i] = p[len - i - 1];
		p[len - i - 1] = t;
	}
}

char *maxStrings(char **p, int len)
{
	char *max;
	max = p[0];
	int i;
	for(i = 1;i < len;++i)
	{
		if(strcmp(max, p[i]) < 0)
		{
			max = p[i];
		}
	}
	return max;
}

void swap(char **a, char **b)
{
	char *t;
	t = *a;
	*a = *b;
	*b = t;
}

void sortStrings(char **p, int len)
{
	int i, j;
	for(i = 0;i < len - 1;++i)
	{
		for(j = i + 1;j < len;++j)
		{
			if(strcmp(p[i], p[j]) > 0)
			{
				swap(p + i, p + j);
		//		char *t;
		//		t = p[i];
		//		p[i] = p[j];
		//		p[j] = t;
			}
		}
	}
}

指针数组参数

        指针数组作为函数的参数,形参是指向指针的指针(下面的代码是调用上面的函数)

int main(void)
{
	char *s[] = {"Hello", "World!", "China"};
	int len = sizeof(s) / sizeof(*s);
	const char *n = "China";
//	puts(maxStrings(s, len));
//	reverseStrings(s, len);
	sortStrings(s, len);
	printStrings(s, len);
	return 0;
}

关键技巧

  1. 字符串安全:动态内存解决自操作问题,始终验证缓冲区容量
  2. 堆内存铁律malloc/calloc必须配对freerealloc用临时变量接收
  3. 函数指针:实现策略模式,减少模块耦合
  4. 多级指针:二级指针通过pppvalue间接访问数据

常见问题

内存泄漏

char *p = malloc(100);
// 忘记free导致泄漏

解决方案:

free(p);
p = NULL;

野指针访问

int *p = malloc(sizeof(int));
free(p);
printf("%d", *p);  // 危险!

解决方案:

free(p);
p = NULL;  // 立即置空


核心要点

  1. 字符串操作:动态内存是解决自连接问题的根本方案
  2. 堆内存管理realloc优先尝试原地扩展,需手动控制生命周期
  3. 函数指针:本质是代码入口地址,实现"相同接口不同行为"
  4. 多级指针:理解main函数参数传递机制与指针数组内存布局