C++實現字符串分割函數split()

目錄

使用strtok()完成分割

使用strsep()完成分割

使用strtok_r()完成分割

實現字符串分割


前言

最近遇到了一個字符串分割的問題,在C++標準庫裏面沒有字符分割函數split()。我想這個問題今後可能還會再遇見,所以使用C/C++中的字符串分割方法完成相應的功能。

需求:對一個字符串進行分割,然後將分割後的數據存起來,想用的時候像用數組那樣拿就行了。所以在完成分割後,需要使用vector將相應的數據保存起來。

 

使用strtok()完成分割

char * strtok ( char * str,const char * delim );
其中str是需要分割的字符串。delim是分隔符標識。
在<cstring>  <string.h>中定義

該函數被設計爲多次調用,以從同一字符串中獲取連續的token。

如果 str != NULL,該調用被視爲 strtok 對此特定字符串的第一個調用。該函數搜索中不包含的第一個字符 delim。

        如果找不到這樣的字符,則根本沒有標記str,並且該函數返回空指針。

        如果找到了這樣的字符,則這是token的開頭。然後,函數從該點開始搜索包含delim的第一個字符。

                如果未找到這樣的字符,str則只有一個標記,以後調用strtok將返回一個空指針

                如果找到了這樣的字符,則將其替換爲空字符'\0',並將指向下一個字符的指針存儲在靜態位置中,以供後續調用。

        然後,該函數將指針返回到token的開頭

如果str == NULL,則該調用將被視爲對後續調用strtok:該函數從上次調用中保留的位置繼續。行爲與先前存儲的指針作爲str傳遞的行爲相同。

#include <cstring>
#include <iostream>
 
int main() 
{
    char input[100] = "A bird came down the walk";
    char *token = std::strtok(input, " ");
    while (token != NULL) {
        std::cout << token << '\n';
        token = std::strtok(NULL, " ");
    }
}

筆記:

(1)此函數具有破壞性:它將 '\0' 字符寫入字符串的元素中str。

(2)每次對該函數的調用都會修改一個靜態變量:不是線程安全的。

(3)與大多數其他strtok不同,每個後續標記中的定界符可以不同,甚至可以取決於先前標記的內容。

 

源自百度百科:

下面的說明摘自於Linux內核2.6.29,說明了這個函數已經不再使用,由速度更快的strsep()代替。

/** linux/lib/string.c** Copyright (C) 1991, 1992 Linus Torvalds*//** stupid library routines.. The optimized versions should generally be found
* as inline code in <asm-xx/string.h>
* These are buggy as well..
* * Fri Jun 25 1999, Ingo Oeser <[email protected]>
* - Added strsep() which will replace strtok() soon (because strsep() is
* reentrant and should be faster). Use only strsep() in new code, please.
** * Sat Feb 09 2002, Jason Thomas <[email protected]>,
* Matthew Hawkins <[email protected]>
* - Kissed strtok() goodbye
*/

 

使用strsep()完成分割

strsep函數用於分解字符串爲一組字符串。其原型爲:

char *strsep(char **s, const char *delim);

 

存在的問題

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//char *strsep(char **str, const char *delim); 
int main(void) {
	char source[] = "hello, world! welcome to china!";
	char delim[] = " ,!";
	char *input = strdup(source);
	char *token = strsep(&input, delim);
		while (token != NULL) {
			printf(token);
			printf("-");
			token = strsep(&input, delim);
		}
	free(input);
	printf("\n");
	return 0;
}

爲什麼使用strsep()分割字符串後會多出幾個" ?

原因是:如果輸入的串的有連續的多個字符屬於delim,strtok會返回NULL,而strsep會返回空串 ""。如果想用strsep函數分割字符串必須進行返回值是否是空串的判斷。

 

使用strtok_r()完成分割

strtok是一個線程不安全的函數,因爲它使用了靜態分配的空間來存儲被分割的字符串位置,線程安全的函數是strtok_r()。

char *strtok_r(char *str, const char *delim, char **saveptr);

strtok_r函數是strtok函數的可重入版本。str爲要分解的字符串,delim爲分隔符字符串。char **saveptr參數是一個指向char *的指針變量,用來在strtok_r內部保存切分時的上下文,以應對連續調用分解相同源字符串。

第一次調用strtok_r時,str參數必須指向待提取的字符串,saveptr參數的值可以忽略。連續調用時,str賦值爲NULL,saveptr爲上次調用後返回的值,不要修改。一系列不同的字符串可能會同時連續調用strtok_r進行提取,要爲不同的調用傳遞不同的saveptr參數。

strtok_r實際上就是將strtok內部隱式保存的this指針,以參數的形式與函數外部進行交互。由調用者進行傳遞、保存甚至是修改。需要調用者在連續切分相同源字符串時,除了將str參數賦值爲NULL,還要傳遞上次切分時保存下的saveptr。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
 
//char *strtok_r(char *str, const char *delim, char **saveptr);
int main()
{
	char *Src = "Can I help you";
	char Buffer[100];
	char *delim = " ";
	char *saveptr = NULL;
	char *input = NULL;
	strncpy(Buffer, Src, sizeof(Buffer) - 1);
	input = Buffer;
	while(NULL != ( input = strtok_r( input, delim, &saveptr)))
	{
		printf("input[%s]   pchStrTmpIn[%s]\n", input,saveptr);
		input = NULL;
	}
	return 0;
}

 

實現字符串分割

以上函數都會改變源字符串,所以在完成split函數功能時,要先複製一個副本,對副本進行分割後返回相應的值纔是正確的思路。

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

namespace tyler
{
//strtok版本
//char *strtok(char s[], const char *delim);
	vector <string> stringsplit(const string &str, const char *delim)
	{
		vector <std::string> strlist;
		int size = str.size();
		char *input = new char[size];
		strcpy(input, str.c_str());
		char *token = std::strtok(input, delim);
		while (token != NULL) {
			strlist.push_back(token);
			token = std::strtok(NULL, delim);
		}
		delete []input;
		return strlist;
	}
	
//另一種寫法
//for(token = strtok(input, delim); token != NULL; token = strtok(NULL, delim)) 
//{
//}

//strsep版本
//char *strsep(char **stringp, const char *delim);
	vector <string> stringsplit1(const string &str, const char *delim)
	{
		vector <std::string> strlist;
		char *p = const_cast<char*>(str.c_str());
		char *input = strdup(p);       //strdup()在內部調用了malloc()爲變量分配內存,不需要使用返回的字符串時,需要用free()釋放相應的內存空間,否則會造成內存泄漏。
		char *token = strsep(&input, delim);
		while (token != NULL) {
			if(strcmp(token, "") == 0)
				token = strsep(&input, delim);
			else
			{
				strlist.push_back(token);
				token = strsep(&input, delim);
			}
		}
		free(input);
		return strlist;
	}

//strtok_r版本
//char *strtok_r(char *str, const char *delim, char **saveptr);
	vector <string> stringsplit2(const string &str, const char *delim)
	{
		vector <std::string> strlist;
		char *saveptr = NULL;
		char *p = const_cast<char*>(str.c_str());
		char *input = strdup(p);       //strdup()在內部調用了malloc()爲變量分配內存,不需要使用返回的字符串時,需要用free()釋放相應的內存空間,否則會造成內存泄漏。
		while(NULL != ( input = strtok_r( input, delim, &saveptr) ))
		{
			//printf("input[%s] saveptr[%s]\n",input,saveptr);
			strlist.push_back(input);
			input = NULL;
		}
		free(input);
		return strlist;
	}
}

int main()
{
	string str = "hello, world! welcome to china!";
	cout << str <<endl;
	vector<string> asd = tyler::stringsplit2(str, " ,!");
	cout << "StringList:";
	for(int i = 0; i < asd.size(); ++i)
	{
		cout << "[" << asd[i] << "]";
	}
	cout <<endl;
	cout << "String:" << str <<endl;
	return 0;
}

 

參考:

https://baike.baidu.com/item/strtok_r

https://blog.csdn.net/yafeng_jiang/article/details/7109285

https://www.cnblogs.com/carsonzhu/p/5859552.html

https://blog.csdn.net/zhouzhenhe2008/article/details/74011399

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章