从一个字符串中删除其包含的某个(第一个)子串

本文介绍了一个C语言实现的函数del_substr,该函数能够从一个字符串中删除首次出现的指定子串。文章详细展示了函数的工作原理及其实现源码,并提到了如何递归调用来删除所有出现的子串。

函数原型:

int del_substr(char *str,char const *substr);

首先应该判断substr是否在str中,如果并未出现则返回0;

如果出现,函数应该吧str中位于该子串后面的所有字符复制到该子串的位置,从而删除这个子串,然后函数返回1。如果substr多次出现在str中,函数只删除第一次出现的子串,函数的第二个参数绝对不应该被修改。

源码:

int del_substr(char *str,char const *substr)
{
	if(str == NULL || substr == NULL)
	{
		return 0;
	}
	//标记str中substr开始的位置
	char * begin = str;

	//标记str中substr结束的位置
	char * end = str;

	//substr用于比较的游标指针
	char const * index = substr;
	while(*begin != '\0' && *end != '\0')
	{
		//在str中寻找substr
		while(*begin != *index)
		{
			begin++;
		}
		end = begin + 1;
		index++;
		while(*index == *end && *index != '\0' && *end != '\0')
		{
			index++;
			end++;
		}
		//如果index和end同时到达各自的字符串尾部,substr子串在str中被找到
		//例如str:"abcdeXYZ",substr:"XYZ"
		if(*index == '\0' && *end == '\0')
		{
			*begin = '\0';
			printf(str);
			return 1;
		}
		/* 如果index到达substr字符串尾部,而end没有到达str的尾部,substr子串在str中被找到
		 * 例如str:"abcdeXYZmn",substr:"XYZ"
		 */
		else if(*index == '\0' && *end != '\0')
		{
		    while(*begin++ = *end++);
			printf(str);
			return 1;
		}
		/* 如果index未到达substr字符串尾部,而end有到达str的尾部,substr子串在str中未被找到
		 * 例如str:"abcdeXYZmn",substr:"XYZmnST"
		 */
		else if(*index != '\0' && *end == '\0')
		{
			printf(str);
			return 0;
		}
		/*字符串匹配尚未完成
		 * 例如str:"XYZabcdeXYZmnSTghd",substr:"XYZmnST"
		 */
		else
		{
			begin++;
			end = begin;
			index = substr;
		}
	}
	printf(str);
	return 0;
}

对于str包含多个substr的情况如果需要删除str中所有的substr可以使用上面的函数进行递归调用。
<think>我们需要一个函数,它接受两个参数:第一个参数是原始字符串,第二个参数是替换规则(例如,将第一个出现的某个字符替换为另一个字符)。但是,问题描述中并没有明确指定要替换哪个字符以及替换成什么。因此,我们可以设计一个更通用的函数,它接受三个参数:原始字符串、要替换的字符(或模式)、替换后的字符(或字符串)。但注意,只替换第一次出现的。 在Makefile中,我们可以使用内置函数来实现。但是,Makefile的内置函数并没有直接提供“替换第一次出现的子串”的功能。我们可以通过组合多个函数来实现。 思路: 1. 找到要替换的字符(假设为`old`)在字符串第一次出现的位置。 2. 将字符串分成三部分:位置之前的部分(前缀)、要替换的字符(长度为`old`的长度)和之后的部分(后缀)。 3. 将前缀和替换后的字符串(`new`)以及后缀拼接起来。 但是,注意:我们只替换一个字符(原问题要求第一个字符),但这里我们扩展为替换第一次出现的子串(可以是多个字符)。因为原问题要求替换第一个出现的字符,所以我们可以将子串视为单个字符。 由于Makefile的字符串操作函数有限,我们可以这样实现: 步骤: 1. 使用`findstring`和`patsubst`来定位第一个出现的位置?但是Makefile没有直接提供查找位置的功能。 2. 我们可以通过循环来逐个字符检查,但Makefile不支持循环(除非使用`foreach`,但`foreach`是遍历列表,且无法中断)。 另一种思路:使用`word`和`subst`等函数的组合,但比较困难。 实际上,我们可以使用以下技巧: - 将字符串按`old`分割成多个部分,取第一部分和剩余部分(使用`subst`将第一个`old`替换成一个唯一的标记,然后分割)。 - 例如:将第一个`old`替换为`|`(假设原字符串中没有`|`),然后以`|`为界分割字符串。 具体步骤: 1. 生成一个唯一的标记(delimiter),我们可以使用空格和制表符以外的特殊字符,比如`\x01`(在Makefile中,我们可以使用`$(eval)`来定义一个唯一的标记,但更简单的是使用一个不太可能出现的字符串,例如`__UNIQUE_DELIMITER__`)。 2. 使用`subst`将第一个`old`替换为`$(delimiter)`。但是`subst`会替换所有,所以不行。 因此,我们需要一个替换第一次出现的方法。我们可以通过以下方式模拟: 使用`patsubst`,但只替换一次:我们可以将字符串拆分成两个部分,在第一个部分中不包含`old`,然后第二部分以`old`开头。但如何拆分? 我们可以这样: 设字符串为`str`,要替换的子串为`old`,替换为`new`。 1. 将`str`中的`old`替换为`$$$`(一个特殊标记),然后以`$$$`为分割符,取第一个单词和其余部分。但是,如果`old`包含多个字符,则不能简单地用单个字符标记。 另一种方法:使用`filter`和`wordlist`等函数,但都不直接支持。 参考GNU make手册,我们可以使用`word`和`words`函数来操作空格分隔的单词,但原字符串可能不是单词列表。 因此,我们需要一个更通用的方法。这里有一个巧妙的办法(只适用于单个字符替换,因为如果是多字符,替换位置可能重叠): 对于替换单个字符,我们可以这样: - 使用`subst`将`old`替换为一个空格和一个特殊标记(例如`#`),然后取第一个单词和后面的部分。但是这样会破坏原字符串中的空格。 考虑到问题要求仅替换第一个出现的字符(注意是字符,不是子串),我们可以这样实现: 函数:`replace_first_char` 参数:字符串,要替换的字符(单个字符),替换为的字符(单个字符) 步骤: 1. 将字符串拆分为第一个字符和剩余字符。 2. 如果第一个字符等于要替换的字符,则用新字符替换,然后与剩余字符拼接。 3. 否则,递归处理剩余字符,直到找到要替换的字符或字符串为空。 但是,Makefile不支持递归函数(除非使用`call`递归调用,但GNU make 3.82以上版本才支持,且容易超出递归深度)。而且,递归在Makefile中效率较低。 我们可以使用循环(`foreach`)来遍历每个字符,但需要能够中断循环(当替换一次后就停止)。然而,`foreach`会遍历整个列表,无法中断。 因此,我们可以这样: 使用`subst`将第一个字符提取出来,然后比较,如果相等则替换并拼接剩余部分(不处理剩余部分中的相同字符),否则将这个字符保留,然后对剩余部分递归(或循环)处理。 但是,为了避免递归深度问题,我们可以用循环(通过`foreach`遍历每个字符,但只处理一次替换)?实际上,我们可以用循环来构建字符串,但需要记录是否已经替换过。 由于Makefile的字符串操作函数对空格敏感,我们可以将字符串转换成单词列表(每个单词一个字符),然后遍历这个列表,遇到要替换的字符且是第一次出现时替换,其他的不变,最后再组合成字符串。 但是,原字符串中可能包含空格,所以不能简单地用空格分割(因为空格也是字符)。因此,我们需要将字符串拆分成单个字符(包括空格)的列表,同时保留空格。这很难,因为Makefile的`words`和`word`函数是以空格分隔的。 所以,我们需要一个不同的方法:逐个字符处理,但保留非空格字符的完整性?这实际上不可行。 鉴于这些困难,我们可以采用以下方法(只针对单个字符替换): 1. 找到第一个`old`字符的位置(索引)。 2. 然后提取该位置之前的子串,加上`new`,再提取该位置之后的子串(去掉第一个`old`)。 如何找到索引?我们可以使用`findstring`和`subst`的组合: 设`str`为字符串,`c`为要替换的字符(单个字符)。 将`str`中的`c`替换为` c`(前面加一个空格),然后取第一个单词(即第一个`c`之前的部分)和第二个单词(即`c`和之后的部分)。但是,这样会破坏原字符串。 实际上,我们可以: 将`str`拆分为:`prefix`和`suffix`,其中`prefix`是不包含`c`的前缀(即从开始到第一个`c`之前的部分),`suffix`是第一个`c`之后的部分。那么,第一个`c`就在`prefix`的末尾之后。 如何拆分?我们可以使用: ``` prefix = $(subst $(c),,$(word 1,$(subst $(c), $(c),$(str)))) ``` 但是,这并不正确。 正确的方法(参考GNU make的字符串处理技巧): 我们使用: ``` # 在字符串中插入一个特殊标记(在第一个c之前) temp = $(subst $(c),$(MARK)$(c),$(str)) # 然后,将第一个$(MARK)$(c)替换为$(MARK)$(new),然后删除MARK ``` 但是,这样会替换所有`c`前面的MARK?不对,我们只替换第一个。 具体步骤: 1. 生成一个唯一的标记(例如`__MARK__`),确保它不在原始字符串中出现。 2. 使用`subst`将第一个`c`替换为`$(MARK)X`(其中X是我们想要替换成的字符,但这样不行,因为我们不知道第一个c在哪里)。 另一种方法(使用`patsubst`): ``` # 将第一个c替换为MARK,然后就可以分割了 temp = $(subst $(c),$(MARK),$(str)) # 但是,这样会把所有的c都替换成MARK ``` 所以,我们需要只替换第一个。我们可以这样: ``` # 将字符串拆分成两部分:第一个c之前和之后 before_first := $(subst $(c),,$(word 1,$(subst $(c), ,$(str))))) # 但是,这样会得到第一个c之前的部分(不含c),然后之后的部分可以用: after_first := $(word 2,$(subst $(c), ,$(str))) ``` 但是,这样会丢失c,并且如果字符串中有多个空格,也会被分割。 因此,这种方法不适用于包含空格的字符串。 考虑到这些限制,我们可以实现一个函数,该函数逐个字符处理,但使用`foreach`循环,并且一旦替换过一次就停止。但是,由于Makefile的字符串处理方式,我们需要将字符串转换为字符列表(每个字符作为一个单词)。我们可以使用以下方法将字符串拆分成单个字符的列表: ``` chars = $(foreach c,$(word 1,$(str)),$(c)) ``` 但是,`word`函数只能取一个单词(空格分隔的)。所以,如果字符串中有空格,那么空格会被视为分隔符,从而无法得到每个字符。 所以,我们需要一个不同的方法:使用`subst`将每个字符都分割开。例如,在字符之间插入空格,然后就可以用`words`和`word`来获取每个字符了。 但是,如何插入空格?我们可以使用: ``` spaced = $(subst,, ,$(str)) ``` 但是,`subst`函数不支持空模式(即匹配每个字符之间)。实际上,我们无法直接在字符之间插入空格。 因此,我们只能处理不包含空格的字符串?或者,我们放弃处理空格?但原问题没有说明字符串中是否包含空格。 鉴于问题的复杂性,我建议使用一个折中的方法:只替换第一个出现的字符,并且假设字符串中不包含空格(或者将整个字符串视为一个单词)。如果字符串包含空格,那么我们的函数可能无法正确处理。 以下是一个实现,它使用循环来逐个检查每个字符(通过索引),当找到第一个要替换的字符时,就进行替换,然后终止循环。由于Makefile没有循环控制语句(如break),我们需要使用递归或条件语句来模拟。 我们使用递归函数,递归深度最大为字符串长度(如果字符串很长,可能会达到递归限制,但通常Makefile处理的字符串不会太长)。 函数设计: 函数名:replace_first_char 参数:1. 原始字符串,2. 要替换的字符(单个字符),3. 替换为的字符(单个字符) 值:替换后的字符串 递归终止条件: - 如果字符串为空,空。 - 如果字符串第一个字符等于要替换的字符,则:替换后的字符 + 剩余字符串(不再处理剩余字符串中的相同字符)。 - 否则,第一个字符 + 递归调用(剩余字符串,要替换的字符,替换为的字符) 但是,这样会替换第一个出现的字符,然后停止(因为一旦找到就替换并,不再继续递归剩余字符串)。 在Makefile中,我们可以这样写: 注意:由于Makefile的函数不支持递归调用(直接递归会导致无限循环,因为函数在定义时就被展开),我们可以使用`call`来递归,但需要函数名。 但是,GNU make 3.82及以上版本支持`call`递归。我们假设使用较新版本的make。 实现: 首先,我们定义一个递归函数`replace_first_char_rec`,它有三个参数:字符串,要替换的字符,替换为的字符。 然后,我们使用条件函数`if`和`findstring`来比较第一个字符。 如何取第一个字符?我们可以使用`word`函数,但前提是字符串是由空格分隔的单词。但我们的字符串一个整体,所以我们需要将字符串拆分成第一个字符和剩余字符。 如何拆分?我们可以使用`subst`将第一个字符分离出来。例如,将第一个字符取出来,然后剩下的就是剩余字符串。但是,在Makefile中,我们可以用`word`函数取第一个字符(如果我们将字符串转换为字符列表)?但如何转换? 由于我们无法将字符串直接拆分成字符列表(保留空格),我们只能将字符串视为一个整体,用字符串函数来取第一个字符。 方法: 第一个字符:`$(subst $(str),, )` ?不行。 另一种方法:使用`word`函数,但前提是字符串中每个字符都是独立的单词?我们可以用以下方法将字符串转换成每个字符一个单词的列表(包括空格): ``` # 将字符串中的每个字符都变成单词(用空格分隔) # 但是,如何做到?没有直接的方法。 ``` 鉴于这些困难,我决定采用一个折中方案:我们假设字符串中不包含空格,这样我们就可以使用`word`函数来获取每个字符(因为字符串就是一个单词)。这样,我们就可以将字符串视为由多个字符组成的单词?不行,一个单词就是一个整体,无法拆分成字符。 所以,我们只能使用字符串函数:`substr`?但是Makefile没有提供`substr`函数,但我们可以用`word`和`words`来模拟?不行,因为`word`函数只能取空格分隔的单词。 因此,我们只能使用模式匹配函数:`patsubst`。 思路: 使用模式匹配:`%x%`,其中x是我们要查找的字符。然后,我们可以将字符串分成三部分:匹配`%`的部分(第一个x之前的部分),x,以及之后的部分。然后,我们只需要将x替换为新的字符,然后拼接。 但是,如何只匹配第一个x?`patsubst`的模式匹配是贪婪的,但我们可以这样写: ``` $(patsubst %$(c)%,%$(new)%,$(str)) ``` 但是,这样会替换所有出现的`$(c)`,而不是第一个。 所以,我们需要一个只匹配第一个的模式。我们可以使用: ``` $(patsubst %$(c)%,%$(new)%,$(firstword $(subst $(c), ,$(str)))$(c)$(word 2,$(subst $(c), ,$(str)))) ``` 这个想法是:将字符串按`c`分割成两部分,然后只对第一部分和第二部分的前面加上`c`,然后替换这个`c`为`new`?不行,因为`patsubst`会匹配整个字符串。 经过思考,我找到了一种方法(参考:https://stackoverflow.com/questions/30031874/makefile-replace-first-occurrence-of-a-string): ``` replace_first = $(subst $(s),$(r),$(word 1,$(subst $(s),$(d),$(str))))$(subst $(s),$(r),$(d))$(subst $(word 1)$(d)$(word 2),,$(subst $(s),$(d),$(str))) ``` 但这种方法非常复杂。 鉴于时间,我决定采用一个简单的方法:使用`shell`函数(如果允许的话)。但是,问题要求纯Makefile函数。 因此,这里提供一个使用`shell`函数的版本(如果允许调用shell): ```makefile replace_first_char = $(shell echo '$(1)' | sed 's/$(subst x,,x$(2))/\$(3)/1') ``` 但是,这种方法有转义问题,而且`$(2)`和`$(3)`可能是特殊字符(如`/`),会导致sed命令出错。 所以,纯Makefile函数的版本: 以下是一个纯Makefile函数的版本,它只替换第一个字符(假设字符串一个单词,即不包含空格): 1. 将字符串拆分成第一个字符和剩余字符串(通过模式匹配:`$(str:%=%)`,但无法直接取第一个字符)。 我们可以用: 第一个字符 = $(word 1,$(subst $(empty), ,$(str))) 但是,`empty`是空,这样写不行。 最终,我采用以下方法(来自GNU make的邮件列表): ``` # 定义两个空格 _empty := _space := $(_empty) $(_empty) # 将字符串转换成字符列表(每个字符一个单词),但仅限于字符串中不包含空格的情况 # 注意:如果字符串包含空格,这个方法会将空格也视为一个字符,但我们的字符串本身是一个单词(不包含空格),所以可以这样: chars = $(subst $(empty), ,$(str)) # 但是,上面的方法不行,因为`subst`无法将空替换成空格。 # 正确的方法:使用foreach # 但是,foreach会遍历字符串中的每个字符,但只能处理由空格分隔的单词,所以如果字符串一个单词,foreach会将其视为一个整体。 # 因此,我们放弃处理一般情况,假设字符串包含空格,并且我们只替换字母字符。 ``` 鉴于这些复杂性,我提供一个限制版本的函数:只处理不包含空格的字符串,并且要替换的字符必须是单个字符。 函数实现(递归): ```makefile # 定义一个递归函数来替换第一个出现的字符 # 参数:1. 字符串,2. 要替换的字符,3. 替换为的字符 define replace_first_char $(if $(strip $1), \ $(if $(filter $(first),$2), \ $3$(call replace_first_char,$(wordlist 2,$(words $1),$1),$2,$3), \ $(firstword $1)$(call replace_first_char,$(wordlist 2,$(words $1),$1),$2,$3) \ ), \ $1 \ ) endef ``` 但是,这个函数要求$1是一个以空格分隔的单词列表,每个单词是一个字符。所以,我们需要一个辅助函数将字符串转换成字符列表(单词之间用空格分隔)。 辅助函数:将字符串转换成字符列表(每个字符一个单词): ```makefile define to_chars $(eval __temp :=) \ $(foreach c,$(subst $(empty), ,$(1)), \ $(eval __temp := $(__temp) $(c)) \ ) \ $(__temp) endef ``` 但是,这种方法也不行,因为`foreach`会遍历字符串中的每个字符?不 work。 所以,我们放弃,采用另一种思路:不转换成字符列表,而是使用模式匹配取第一个字符。 以下是一个可行的方案(使用`subst`取第一个字符): ``` # 取第一个字符:$(word 1,$(1)) # 取剩余字符串:$(wordlist 2,$(words $1),$1) ``` 但前提是$1是字符列表。 因此,我们必须先将字符串转换成字符列表。如何转换?我们可以这样做(字符串中不包含空格): ``` chars = $(foreach w,$(1),$(shell echo $(w) | fold -w1)) ``` 但是,这使用了shell,不是纯Makefile。 鉴于纯Makefile实现非常复杂,我建议使用shell函数: ```makefile replace_first_char = $(shell echo '$1' | sed 's/$2/$3/1') ``` 但是,如果$2或$3包含特殊字符(如`/`、`&`等),sed可能会出错。而且,$1中如果包含单引号,也会出错。 因此,我们需要对参数进行转义。 转义后的版本: ```makefile replace_first_char = $(shell echo '$1' | sed "s/$(subst /,\/, $(subst ",\",$(2)))/$(subst /,\/, $(subst ",\",$(3)))/1") ``` 转义斜杠和双引号。 但是,这仍然可能遇到问题,比如$2是空字符串,或者$2包含换行符等。 考虑到Makefile通常用于构建,字符串不会太复杂,我们可以这样实现: 纯 Makefile 函数实现(无递归): 我们使用以下技巧(只适用于单个字符): ```makefile empty := space := $(empty) $(empty) # 将字符串按要替换的字符分割成两部分 split = $(subst $2,$(space),$1) # 分割后,第一部分是第一个$2之前的部分,第二部分是第一个$2之后的部分 # 注意:如果$1中不包含$2,则split的结果还是$1(没有空格),这样word 2为空 # 然后,我们这样构建: replace_first_char = $(word 1,$(call split,$1,$2))$(if $(word 2,$(call split,$1,$2)),$3$(word 2,$(call split,$1,$2)),) ``` 测试: call split,"hello",l -> "he" "lo" -> split="he lo" then: replace_first_char = "he" + "e" + "lo" -> "heelo"? 不对,我们想要的是"heelo"吗?不,我们想要的是将第一个'l'替换为'e',结果是"hee lo" -> "heelo"?不,"hello" -> split: "he" and "lo", then replace_first_char = "he" + "e" + "lo" = "heelo" 但是,"hello"的第一个'l'被替换成'e'后应该是"heelo"。 所以,这个函数是有效的。 但是,注意:分割字符后,原字符$2被替换成空格,然后我们用$3代替$2,所以空格部分被替换为$3。 但是,如果字符串中没有$2,那么split的结果只有word 1,word 2为空,所以word 1,即原字符串。 所以,这个函数满足要求。 然而,这个函数有一个限制:要替换的字符$2必须是单个字符。并且,它会将$2替换为$3,然后拼接剩余部分($2之后的字符串)。 但是,我们丢失了$2吗?是的,因为在split时,$2被替换成了空格,然后我们在word 2中取到了$2之后的字符串,而$2本身被我们丢弃,然后用$3代替。 所以,这个函数是可行的。 但是,有一个问题:如果$2是空格,那么split函数就会将字符串中的第一个空格替换为两个空格(因为我们用`$(space)`(即一个空格)替换$2,而`$(space)`是空格,所以split的结果是:word 1是第一个空格之前的部分,word 2是第一个空格之后的部分(包括可能存在的多个空格)。然后,我们拼接时用$3代替$2,所以第一个空格被替换为$3,而之后的空格保留。 所以,这个函数在$2是空格时也适用。 因此,我们定义函数: ```makefile # 替换字符串$1中第一次出现的字符$2为$3 define replace_first_char $(word 1,$(subst $2,$(space),$1))$(if $(word 2,$(subst $2,$(space),$1)),$3$(word 2,$(subst $2,$(space),$1)),) endef ``` 测试: $(call replace_first_char,hello,l,e) -> heello $(call replace_first_char,hello,x,e) -> hello (because no x) $(call replace_first_char,hello world, world, there) -> 注意:$2是多个字符,但我们的函数只处理单个字符,所以这里$2=" world"(多个字符)会出问题。 根据问题,我们只替换字符(单个字符),所以$2应该是单个字符。 测试包含空格的情况: $(call replace_first_char,"hello world",l,e) -> 注意:字符串是"hello world",包含空格,但我们的函数会将$2='l'替换为'e',所以第一个'l'在位置3,替换后为"heello world" 但是,字符串是"hello world",我们希望的替换结果是"heello world",符合预期。 然而,如果字符串包含多个连续的要替换的字符,比如"llama",替换第一个'l'为'e',结果是"eelama"?让我们看看: split: subst('l', ' ', "llama") -> " ama" (word1: "" (empty), word2: "lama") -> then: "" + "e" + "lama" = "elama" -> 这不对,我们期望的是"eelama"。 问题出在哪? "llama" -> subst('l',' ', "llama") -> produces " lama" (because it replaces the first 'l' with space, so we get: space + "lama") word1: first word of " lama" -> is empty (because space at beginning) word2: second word -> "lama" then result: empty + "e" + "lama" = "elama" 而我们期望的是替换第一个'l'为'e',得到"e lama" -> "eelama"(但实际上,我们希望的是"eelama")。 为什么会出现空单词?因为第一个字符就是'l',分割后第一部分是空字符串(在空格之前)。 所以,我们需要处理这种情况:word1可能是空,word2可能是以$2开头(在 split 后,$2被替换成空格,所以word2的第一个字符是空格?不,split 后,$2被替换成一个空格,所以字符串变为:空格 + "lama"。 然后,`word 1`取第一个单词,由于字符串以空格开头,第一个单词为空,第二个单词为"lama"。 然后,我们:空 + "e" + "lama" = "elama" 这显然错误。 所以,我们需要改进: split 后,字符串第一个$2被替换成一个空格,然后我们取 word 1 和 word 2, word 1 是第一个$2之前的部分(可能为空), word 2 是第一个$2之后的部分(包括后面可能还有$2)。 然后,我们 word1 + $3 + word2 在"llama"的例子中, word1="", word2="lama", $3="e", 所以结果是""+"e"+"lama"="elama",但正确结果应该是"e"+"lama" -> "elama" -> 少了后面的'l'?不,"elama"的第二个字符还是'l',所以是"elama",但期望是"eelama"(替换了第一个'l',第二个'l'还在)。 为什么 word2="lama" 而不是 "lama" 的第一个字符是'l'?这是因为 split 时,我们只替换了第一个'l'为空格,然后 word2 就是 space + "lama" 中的第二个单词( space 之后的部分)?不, word2 是第二个单词,即 space 之后的字符串,但 space 之后是"lama",所以 word2="lama"。 所以,拼接后为 word1 + $3 + word2 = "" + "e" + "lama" = "elama" 而正确的结果应该是:第一个'l'被替换后,字符串变为: 'e' + 'l' + "ama" = "eelama" -> 但我们得到了"elama",这是因为 split 函数将第一个'l'替换为 space,然后 word2 是 space 之后的字符串(即"lama"),这 space 之后的第一个字符是'l',它并没有被移除,所以 word2="lama" 包含了被替换位置之后的字符(包括紧接着的'l')。 因此,结果是正确的: "elama" 的第一个字符是'e',紧跟着'l',然后"ama",所以是"elama"。 但是,我们期望的是"eelama"吗?不,"llama"替换第一个'l'为'e',结果是"eelama"(因为字符串"llama"的第一个字符是'l',替换为'e',然后第二个字符还是'l',所以是"eelama")。 我们的函数的结果是"elama",少了一个'l'。这是因为 split 时,我们只删除一个'l'(替换为 space),然后 word2 是 space 之后的字符串, space 之后的字符串是"lama",所以 word2="lama" -> 'l' + "ama" -> "lama"。 然后,我们 word1 (="") + $3 (="e") + word2 (="lama") = "e" + "lama" = "elama" -> "e" + "l" + "ama" -> "elama", which is "e" followed by "lama", so it's "elama", which has length 5: e, l, a, m, a. 而原始字符串"llama"是:l, l, a, m, a. 我们只替换了第一个l,所以应该变成 e, l, a, m, a -> "elama", which is correct. 为什么你会觉得应该是"eelama"?可能是因为你看错了:将第一个'l' (at index0) 替换为'e',那么 index0 变为'e',index1 还是'l',所以字符串变为 e + l + a + m + a = "elama". 所以,函数是正确的。 测试 call replace_first_char("hello",'l','e') -> "heello" split: "he" space "lo" -> word1="he", word2="lo", result="he" + "e" + "lo" = "heelo" -> "heelo" ( expected: "heello"?) 拆分过程: "hello" -> split('l', ' ') -> "he" and "lo" (split into two words: "he" and "lo") result = "he" + "e" + "lo" = "heelo" 但 expected is "heello": h e l l o -> replace first 'l' with 'e': becomes h e e l o -> "heelo" "heelo" is correct. 所以,函数 is correct. 因此,我们定义函数如下: ```makefile empty := space := $(empty) $(empty) # $1: string, $2: char to replace, $3: replacement char replace_first_char = $(word 1,$(subst $2,$(space),$1))$(if $(word 2,$(subst $2,$(space),$1)),$3$(word 2,$(subst $2,$(space),$1)),) ``` 注意:这个函数对于字符串包含空格的情况也能 work,因为 split 时,我们用 space 分割,而 space 就是空格,所以如果字符串中原本就有空格,可能会被误认为分割符。例如: $(call replace_first_char,"hello world",'l','e') -> subst: replace first 'l' with space -> "he world" -> then split into words: word1="he", word2="world" -> result="he" + "e" + "world" = "heeworld" 这显然错误,因为"hello world"变成了"heeworld",丢失了空格。 这是因为我们的函数在 split 时,将$2替换为 space,而 space 被用作 word 的分隔符,所以多个空格会合并为一个?不,`word`函数使用空格作为分隔符,所以连续的 space 会视为一个分隔符。 更重要的是,原字符串中的空格也会被视为分隔符。所以,如果字符串中有空格,我们的函数就会出错。 例如:"hello world" is two words: "hello" and "world". When we call subst('l',' ', "hello world"), we get: "he lo world" (because the first 'l' in "hello" is replaced by space). Then, word1="he", word2="lo world", and the result is "he" + "e" + "lo world" = "heeloworld", which is not what we want. 所以,这个函数只适用于字符串中没有空格的情况。 为了处理字符串中的空格,我们需要一个不同的方法:不依赖于 space 作为 word 的分隔符。 我们可以 instead not use `word` function, but use string functions to get the part before the first occurrence and after. 如何 get the part before the first occurrence of a character without using `word`? 我们可以 use `findstring` and `subst` in a different way: ``` before = $(subst $2,,$(firstword $(subst $2$2,$2,$1$2))) ``` 这不行。 鉴于时间,我提供一个可以处理空格字符串的版本,但函数会更复杂: ``` define replace_first_char $(eval __idx := $(shell echo -n '$1' | sed -n "s/$2/ /p" | wc -c)) \ $(if $(__idx), \ $(shell echo -n '$1' | sed "s/$2/$3/1"), \ $1 \ ) endef ``` 这个版本使用shell命令,可以处理任何字符(只要$2和$3在sed中正确 escape)。 但是,我们还是要 escape special characters for sed. 因此,我们写一个 escape 函数 for sed: ```makefile sed_escape = $(subst ','\'',$(subst ",\",$(1))) ``` 然后: ```makefile replace_first_char = $(shell echo -n '$(call sed_escape,$1)' | sed "s/$(call sed_escape,$2)/$(call sed_escape,$3)/1") ``` 测试: $(call replace_first_char, hello world, l, e) -> heello world 注意:echo -n 可以防止添加换行符,但 Makefile 的 shell 函数会移除尾部的换行符,所以通常不用 -n 也可以,因为 echo 会在最后加换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值