串的表示和实现(串的堆分配存储、串的定长顺序存储结构)
在C 语言中,字符串存于字符型数组中。无论数组有多大,用数值0 表示串结束。图
41 表示了“but”字符串在C 语言中的存储结构。
其中数组a 的定义为
char a[10];
C 语言还在库函数string.h 中提供了许多串处理
的基本操作,如求串长函数strlen()、串拷贝函数
strcpy()等。
算法语言本身提供的字符串存储结构及其基本操作不一定能满足实际应用的需要,我
代码的运行结果如下:
代码的运行结果如下:
串的堆分配存储结构(由c4-2.h 定义)根据串的长度,动态地分配存储空间。这样既
保证满足需要,又不浪费空间,对于串长也没有限制。串的定长存储结构(由c4-1.h 定义)
就没有这样灵活了。在Concat()、StrInsert()和Replace()中,总要检查串是否被截断,且
对于短串的情况,空间浪费较大。故堆分配存储结构较好。
41 表示了“but”字符串在C 语言中的存储结构。
其中数组a 的定义为
char a[10];
C 语言还在库函数string.h 中提供了许多串处理
的基本操作,如求串长函数strlen()、串拷贝函数
strcpy()等。
算法语言本身提供的字符串存储结构及其基本操作不一定能满足实际应用的需要,我
们往往还要根据具体情况另外定义字符串的存储结构及基于该存储结构的基本操作。
// c4-1.h 串的定长顺序存储结构(见图4.2) #define MAX_STR_LEN 40 // 用户可在255(1个字节)以内定义最大串长 typedef char SString[MAX_STR_LEN+1]; // 0号单元存放串的长度
// bo4-1.cpp 串采用定长顺序存储结构(由c4-1.h定义)的基本操作(13个),包括算法4.2,4.3,4.5 // SString是数组,故不需引用类型 #define DestroyString ClearString // DestroyString()与ClearString()作用相同 Status StrAssign(SString T,char *chars) { // 生成一个其值等于chars的串T int i; if(strlen(chars)>MAX_STR_LEN) return ERROR; else { T[0]=strlen(chars); for(i=1;i<=T[0];i++) T[i]=*(chars+i-1); return OK; } } void StrCopy(SString T,SString S) { // 由串S复制得串T int i; for(i=0;i<=S[0];i++) T[i]=S[i]; } Status StrEmpty(SString S) { // 若S为空串,则返回TRUE;否则返回FALSE if(S[0]==0) return TRUE; else return FALSE; } int StrCompare(SString S,SString T) {// 初始条件:串S和T存在。操作结果:若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0 int i; for(i=1;i<=S[0]&&i<=T[0];++i) if(S[i]!=T[i]) return S[i]-T[i]; return S[0]-T[0]; } int StrLength(SString S) { // 返回串S的元素个数 return S[0]; } void ClearString(SString S) { // 初始条件:串S存在。操作结果:将S清为空串(见图4.3) S[0]=0; // 令串长为零 } Status Concat(SString T,SString S1,SString S2) // 算法4.2改 { // 用T返回S1和S2联接而成的新串。若未截断,则返回TRUE;否则返回FALSE int i; if(S1[0]+S2[0]<=MAX_STR_LEN) { // 未截断 for(i=1;i<=S1[0];i++) T[i]=S1[i]; for(i=1;i<=S2[0];i++) T[S1[0]+i]=S2[i]; T[0]=S1[0]+S2[0]; return TRUE; } else { // 截断S2 for(i=1;i<=S1[0];i++) T[i]=S1[i]; for(i=1;i<=MAX_STR_LEN-S1[0];i++) T[S1[0]+i]=S2[i]; T[0]=MAX_STR_LEN; return FALSE; } } Status SubString(SString Sub,SString S,int pos,int len) { // 用Sub返回串S的自第pos个字符起长度为len的子串。算法4.3 int i; if(pos<1||pos>S[0]||len<0||len>S[0]-pos+1) return ERROR; for(i=1;i<=len;i++) Sub[i]=S[pos+i-1]; Sub[0]=len; return OK; } int Index(SString S,SString T,int pos) { // 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数值为0。 // 其中,T非空,1≤pos≤StrLength(S)。算法4.5 int i,j; if(1<=pos&&pos<=S[0]) { i=pos; j=1; while(i<=S[0]&&j<=T[0]) if(S[i]==T[j]) // 继续比较后继字符 { ++i; ++j; } else // 指针后退重新开始匹配 { i=i-j+2; j=1; } if(j>T[0]) return i-T[0]; else return 0; } else return 0; } Status StrInsert(SString S,int pos,SString T) { // 初始条件:串S和T存在,1≤pos≤StrLength(S)+1 // 操作结果:在串S的第pos个字符之前插入串T。完全插入返回TRUE,部分插入返回FALSE int i; if(pos<1||pos>S[0]+1) return ERROR; if(S[0]+T[0]<=MAX_STR_LEN) { // 完全插入 for(i=S[0];i>=pos;i--) S[i+T[0]]=S[i]; for(i=pos;i<pos+T[0];i++) S[i]=T[i-pos+1]; S[0]+=T[0]; return TRUE; } else { // 部分插入 for(i=MAX_STR_LEN;i>=pos+T[0];i--) S[i]=S[i-T[0]]; for(i=pos;i<pos+T[0]&&i<=MAX_STR_LEN;i++) S[i]=T[i-pos+1]; S[0]=MAX_STR_LEN; return FALSE; } } Status StrDelete(SString S,int pos,int len) { // 初始条件:串S存在,1≤pos≤StrLength(S)-len+1 // 操作结果:从串S中删除自第pos个字符起长度为len的子串 int i; if(pos<1||pos>S[0]-len+1||len<0) return ERROR; for(i=pos+len;i<=S[0];i++) S[i-len]=S[i]; S[0]-=len; return OK; } Status Replace(SString S,SString T,SString V) // 此函数与串的存储结构无关 { // 初始条件:串S,T和V存在,T是非空串 // 操作结果:用V替换主串S中出现的所有与T相等的不重叠的子串 int i=1; // 从串S的第一个字符起查找串T Status k; if(StrEmpty(T)) // T是空串 return ERROR; do { i=Index(S,T,i); // 结果i为从上一个i之后找到的子串T的位置 if(i) // 串S中存在串T { StrDelete(S,i,StrLength(T)); // 删除该串T k=StrInsert(S,i,V); // 在原串T的位置插入串V if(!k) // 不能完全插入 return ERROR; i+=StrLength(V); // 在插入的串V后面继续查找串T } }while(i); return OK; } void StrPrint(SString T) { // 输出字符串T。另加 int i; for(i=1;i<=T[0];i++) printf("%c",T[i]); printf("\n"); }
// main4-1.cpp 检验bo4-1.cpp的主程序 #include"c1.h" #include"c4-1.h" #include"bo4-1.cpp" void main() { int i,j; Status k; char s,c[MAX_STR_LEN+1]; SString t,s1,s2; printf("请输入串s1: "); gets(c); k=StrAssign(s1,c); if(!k) { printf("串长超过MAX_STR_LEN(=%d)\n",MAX_STR_LEN); exit(0); } printf("串长为%d 串空否?%d(1:是0:否)\n",StrLength(s1),StrEmpty(s1)); StrCopy(s2,s1); printf("拷贝s1生成的串为"); StrPrint(s2); printf("请输入串s2: "); gets(c); k=StrAssign(s2,c); if(!k) { printf("串长超过MAX_STR_LEN(%d)\n",MAX_STR_LEN); exit(0); } i=StrCompare(s1,s2); if(i<0) s='<'; else if(i==0) s='='; else s='>'; printf("串s1%c串s2\n",s); k=Concat(t,s1,s2); printf("串s1联接串s2得到的串t为"); StrPrint(t); if(k==FALSE) printf("串t有截断\n"); ClearString(s1); printf("清为空串后,串s1为"); StrPrint(s1); printf("串长为%d 串空否?%d(1:是0:否)\n",StrLength(s1),StrEmpty(s1)); printf("求串t的子串,请输入子串的起始位置,子串长度: "); scanf("%d,%d",&i,&j); k=SubString(s2,t,i,j); if(k) { printf("子串s2为"); StrPrint(s2); } printf("从串t的第pos个字符起,删除len个字符,请输入pos,len: "); scanf("%d,%d",&i,&j); StrDelete(t,i,j); printf("删除后的串t为"); StrPrint(t); i=StrLength(s2)/2; StrInsert(s2,i,t); printf("在串s2的第%d个字符之前插入串t后,串s2为\n",i); StrPrint(s2); i=Index(s2,t,1); printf("s2的第%d个字母起和t第一次匹配\n",i); SubString(t,s2,1,1); printf("串t为"); StrPrint(t); Concat(s1,t,t); printf("串s1为"); StrPrint(s1); k=Replace(s2,t,s1); if(k) // 替换成功 { printf("用串s1取代串s2中和串t相同的不重叠的串后,串s2为"); StrPrint(s2); } DestroyString(s2); // 销毁操作同清空 }
代码的运行结果如下:
/* 请输入串s1: ABCD 串长为4 串空否?0(1:是0:否) 拷贝s1生成的串为ABCD 请输入串s2: 123456 串s1>串s2 串s1联接串s2得到的串t为ABCD123456 清为空串后,串s1为 串长为0 串空否?1(1:是0:否) 求串t的子串,请输入子串的起始位置,子串长度: 3,7 子串s2为CD12345 从串t的第pos个字符起,删除len个字符,请输入pos,len: 4,4 删除后的串t为ABC456 在串s2的第3个字符之前插入串t后,串s2为 CDABC45612345 s2的第3个字母起和t第一次匹配 串t为C 串s1为CC 用串s1取代串s2中和串t相同的不重叠的串后,串s2为CCDABCC45612345 Press any key to continue */
// c4-2.h 串的堆分配存储(见图4.4) struct HString { char *ch; // 若是非空串,则按串长分配存储区;否则ch为NULL int length; // 串长度 };
// bo4-2.cpp 串采用堆分配存储结构(由c4-2.h定义)的基本操作(14个)。包括算法4.1,4.4 #define DestroyString ClearString // DestroyString()与ClearString()作用相同 void StrAssign(HString &T,char *chars) { // 生成一个其值等于串常量chars的串T(见图4.5) int i,j; if(T.ch) free(T.ch); // 释放T原有空间 i=strlen(chars); // 求chars的长度i if(!i) { // chars的长度为0 T.ch=NULL; T.length=0; } else { // chars的长度不为0 T.ch=(char*)malloc(i*sizeof(char)); // 分配串空间 if(!T.ch) // 分配串空间失败 exit(OVERFLOW); for(j=0;j<i;j++) // 拷贝串 T.ch[j]=chars[j]; T.length=i; } } void StrCopy(HString &T,HString S) { // 初始条件:串S存在。操作结果:由串S复制得串T int i; if(T.ch) free(T.ch); // 释放T原有空间 T.ch=(char*)malloc(S.length*sizeof(char)); // 分配串空间 if(!T.ch) // 分配串空间失败 exit(OVERFLOW); for(i=0;i<S.length;i++) // 拷贝串 T.ch[i]=S.ch[i]; T.length=S.length; } Status StrEmpty(HString S) { // 初始条件:串S存在。操作结果:若S为空串,则返回TRUE;否则返回FALSE if(S.length==0&&S.ch==NULL) return TRUE; else return FALSE; } int StrCompare(HString S,HString T) { // 若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0 int i; for(i=0;i<S.length&&i<T.length;++i) if(S.ch[i]!=T.ch[i]) return S.ch[i]-T.ch[i]; return S.length-T.length; } int StrLength(HString S) { // 返回S的元素个数,称为串的长度 return S.length; } void ClearString(HString &S) { // 将S清为空串(见图4.6) free(S.ch); S.ch=NULL; S.length=0; } void Concat(HString &T,HString S1,HString S2) { // 用T返回由S1和S2联接而成的新串 int i; if(T.ch) free(T.ch); // 释放旧空间 T.length=S1.length+S2.length; T.ch=(char *)malloc(T.length*sizeof(char)); if(!T.ch) exit(OVERFLOW); for(i=0;i<S1.length;i++) T.ch[i]=S1.ch[i]; for(i=0;i<S2.length;i++) T.ch[S1.length+i]=S2.ch[i]; } Status SubString(HString &Sub, HString S,int pos,int len) { // 用Sub返回串S的第pos个字符起长度为len的子串。 // 其中,1≤pos≤StrLength(S)且0≤len≤StrLength(S)-pos+1 int i; if(pos<1||pos>S.length||len<0||len>S.length-pos+1) return ERROR; if(Sub.ch) free(Sub.ch); // 释放旧空间 if(!len) // 空子串 { Sub.ch=NULL; Sub.length=0; } else { // 完整子串 Sub.ch=(char*)malloc(len*sizeof(char)); if(!Sub.ch) exit(OVERFLOW); for(i=0;i<=len-1;i++) Sub.ch[i]=S.ch[pos-1+i]; Sub.length=len; } return OK; } void InitString(HString &T) { // 初始化(产生空串)字符串T。另加 T.length=0; T.ch=NULL; } int Index(HString S,HString T,int pos) // 算法4.1 { // T为非空串。若主串S中第pos个字符之后存在与T相等的子串, // 则返回第一个这样的子串在S中的位置;否则返回0 int n,m,i; HString sub; InitString(sub); if(pos>0) { n=StrLength(S); m=StrLength(T); i=pos; while(i<=n-m+1) { SubString(sub,S,i,m); if(StrCompare(sub,T)!=0) ++i; else return i; } } return 0; } Status StrInsert(HString &S,int pos,HString T) // 算法4.4 { // 1≤pos≤StrLength(S)+1。在串S的第pos个字符之前插入串T int i; if(pos<1||pos>S.length+1) // pos不合法 return ERROR; if(T.length) // T非空,则重新分配空间,插入T { S.ch=(char*)realloc(S.ch,(S.length+T.length)*sizeof(char)); if(!S.ch) exit(OVERFLOW); for(i=S.length-1;i>=pos-1;--i) // 为插入T而腾出位置 S.ch[i+T.length]=S.ch[i]; for(i=0;i<T.length;i++) S.ch[pos-1+i]=T.ch[i]; // 插入T S.length+=T.length; } return OK; } Status StrDelete(HString &S,int pos,int len) { // 从串S中删除第pos个字符起长度为len的子串 int i; if(S.length<pos+len-1) return ERROR; for(i=pos-1;i<=S.length-len;i++) S.ch[i]=S.ch[i+len]; S.length-=len; S.ch=(char*)realloc(S.ch,S.length*sizeof(char)); return OK; } Status Replace(HString &S,HString T,HString V) // 此函数与串的存储结构无关 { // 初始条件:串S,T和V存在,T是非空串 // 操作结果:用V替换主串S中出现的所有与T相等的不重叠的子串 int i=1; // 从串S的第一个字符起查找串T if(StrEmpty(T)) // T是空串 return ERROR; do { i=Index(S,T,i); // 结果i为从上一个i之后找到的子串T的位置 if(i) // 串S中存在串T { StrDelete(S,i,StrLength(T)); // 删除该串T StrInsert(S,i,V); // 在原串T的位置插入串V i+=StrLength(V); // 在插入的串V后面继续查找串T } }while(i); return OK; } void StrPrint(HString T) { // 输出T字符串。另加 int i; for(i=0;i<T.length;i++) printf("%c",T.ch[i]); printf("\n"); }
// main4-2.cpp 检验bo4-2.cpp的主程序 #include"c1.h" #include"c4-2.h" #include"bo4-2.cpp" void main() { int i; char c,*p="God bye!",*q="God luck!"; HString t,s,r; InitString(t); // HString类型必须初始化 InitString(s); InitString(r); StrAssign(t,p); printf("串t为"); StrPrint(t); printf("串长为%d 串空否?%d(1:空0:否)\n",StrLength(t),StrEmpty(t)); StrAssign(s,q); printf("串s为"); StrPrint(s); i=StrCompare(s,t); if(i<0) c='<'; else if(i==0) c='='; else c='>'; printf("串s%c串t\n",c); Concat(r,t,s); printf("串t联接串s产生的串r为"); StrPrint(r); StrAssign(s,"oo"); printf("串s为"); StrPrint(s); StrAssign(t,"o"); printf("串t为"); StrPrint(t); Replace(r,t,s); printf("把串r中和串t相同的子串用串s代替后,串r为"); StrPrint(r); ClearString(s); printf("串s清空后,串长为%d 空否?%d(1:空0:否)\n",StrLength(s),StrEmpty(s)); SubString(s,r,6,4); printf("串s为从串r的第6个字符起的4个字符,长度为%d 串s为",s.length); StrPrint(s); StrCopy(t,r); printf("复制串t为串r,串t为"); StrPrint(t); StrInsert(t,6,s); printf("在串t的第6个字符前插入串s后,串t为"); StrPrint(t); StrDelete(t,1,5); printf("从串t的第1个字符起删除5个字符后,串t为"); StrPrint(t); printf("%d是从串t的第1个字符起,和串s相同的第1个子串的位置\n",Index(t,s,1)); printf("%d是从串t的第2个字符起,和串s相同的第1个子串的位置\n",Index(t,s,2)); DestroyString(t); // 销毁操作同清空 }
代码的运行结果如下:
串t为God bye! 串长为8 串空否?0(1:空0:否) 串s为God luck! 串s>串t 串t联接串s产生的串r为God bye!God luck! 串s为oo 串t为o 把串r中和串t相同的子串用串s代替后,串r为Good bye!Good luck! 串s清空后,串长为0 空否?1(1:空0:否) 串s为从串r的第6个字符起的4个字符,长度为4 串s为bye! 复制串t为串r,串t为Good bye!Good luck! 在串t的第6个字符前插入串s后,串t为Good bye!bye!Good luck! 从串t的第1个字符起删除5个字符后,串t为bye!bye!Good luck! 1是从串t的第1个字符起,和串s相同的第1个子串的位置 5是从串t的第2个字符起,和串s相同的第1个子串的位置 Press any key to continue
串的堆分配存储结构(由c4-2.h 定义)根据串的长度,动态地分配存储空间。这样既
保证满足需要,又不浪费空间,对于串长也没有限制。串的定长存储结构(由c4-1.h 定义)
就没有这样灵活了。在Concat()、StrInsert()和Replace()中,总要检查串是否被截断,且
对于短串的情况,空间浪费较大。故堆分配存储结构较好。
每当夜深人静的时候,想想今天发生了什么,失去了什么,得到了什么,做了什么,没做什么,该做什么,不该做什么,明天要做什么!