串(字符串)是一种特殊的线性表,它的数据元素仅有一个字符组成。一般情况下处理的非数值型数据对象经常是字符串数据,例如在事务处理中,顾客的姓名、地址、货物产地等,一般都作为字符串处理。通常以“串的整体”作为处理对象。
字符串str1、str2连接,三种存储方式
一 、串的存储结构
串的顺序存储结构是用一组地址连续的存储单元来存储字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般用定长数组来定义。
串的实际长度可以在预定义长度的范围内随意,超出预定义长度的串值则会被舍弃,称之为“截断”。
1.定长顺序存储
2.堆分配存储(也是顺序存储结构)
3.块链存储
1 定长存储
1.串的顺序存储结构
串的顺序存储结构是用一组地址连续的存储单元来存储字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般用定长数组来定义。
#define MAXLEN 255
typedef struct {
char ch[MAXLEN + 1];
//存储串的一维数组,ch[0]~ch[255],共256个;
//通常情况下ch[0]存放串的长度,或者闲置不用,真正串的类容从ch[1]开始
int length;//串的当前长度
}String;
2 块链存储
串要进行频繁插入删除操作时,顺序存储不方便,就要用到链式存储
串的块链存储,指的是使用链表结构存储字符串。
从图 2 可以看到,使用链表存储字符串,其最后一个节点的数据域不一定会被字符串全部占满,对于这种情况,通常会用 ‘#’ 或其他特殊字符(能与字符串区分开就行)将最后一个节点填满。
链表各节点存储数据个数的多少可参考以下几个因素:
串的长度和存储空间的大小:若串包含数据量很大,且链表申请的存储空间有限,此时应尽可能的让各节点存储更多的数据,提高空间的利用率(每多一个节点,就要多申请一个指针域的空间);反之,如果串不是特别长,或者存储空间足够,就需要再结合其他因素综合考虑;
程序实现的功能:如果实际场景中需要对存储的串做大量的插入或删除操作,则应尽可能减少各节点存储数据的数量;反之,就需要再结合其他因素。
1 串的链式存储
串的块链存储,指的是使用链表结构存储字符串。
缺点存储密度低
如图第一个结点存储了字符A,占用一个字节,假如地址占用4个字节,那么存储密度1/(1+4)=0.2
2 块链存储
为了解决存储密度引入,链式存储的块链
将多个字符存储在一个结点,那么这一个节点叫做一个存储块,即块链
如图第一个结点存储了字符A、B、C、D,4个字符占用4个字节,假如地址占用4个字节,那么共占用8个字节,则存储密度 4/(4+4)=0.5
如果每个块存放50个字符,那么存储密度50/(50+4)=0.92
3 块链存储的定义
二、串连接三种方式实现(定长、堆、块链)
字符串str1、str2连接,分别用定长存储、堆存储、块链存储实现,不能直接调用函数库中自带的连接函数。
1 定长存储
#include<iostream>
using namespace std;
#define MAXLEN 255//定义串的最大长度为255,<=255以内的任何串可用
typedef struct {
char ch[MAXLEN + 1];//存储串的一维数组,ch[0]~ch[255],共256个
int length;//串的当前长度
}String;
void StrAssign(String* Str, char str[])//生成一个其值等于字符串常量 str 的串 Str
{
int i = 0;
while (str[i] != '\0') i++;//计算str的长度
Str->length = i;
for (i = 0; i < Str->length; i++) {
Str->ch[i] = str[i];//从第一个字符开始,着个赋
}
}
void StrConcat(String str1, String str2, String* str3)//连接字符串1和2,并存在字符串3中
{
str3->length = str1.length + str2.length;
int i;
for (i = 0; i < str1.length; i++) {//当串1的字符全部赋值给串3,退出当前for循环
str3->ch[i] = str1.ch[i];
} //将str1赋值到str
for (; i < str3->length; i++) {//此for循环的i从串1长度str1.length开始到str3->length结束
str3->ch[i] = str2.ch[i - str1.length];
} //将str2赋值到str
}
void print(String* str)
{
int i;
for (i = 0; i < str->length; i++) {
cout << str->ch[i];
}
cout << endl;
}
int main()
{
char st1[255], st2[255];
String str1, str2, str3;
cout << "请输入第一个串" << endl;
cin >> st1;
StrAssign(&str1, st1);
cout << "请输入第二个串" << endl;
cin >> st2;
StrAssign(&str2, st2);
StrConcat(str1, str2, &str3);
print(&str3);
return 0;
}
2 堆分配存储
#include<iostream> //堆存储
using namespace std;
#define MAXSIZE 100
typedef struct st {
char* ch; //串存放的起始地址
int length; //串的长度
int strsize; //分配的存储空间的大小
}String;
String CreateNullString() { //初始化
String str;
str.length = 0;
str.ch = (char*)malloc(MAXSIZE * sizeof(char));
str.strsize = MAXSIZE;
return str;
}
void StrAssign(String* str1, char str[]) {//生成一个其值等于字符串常量 str 的串 str1
int i = 0;
while (str[i] != '\0') i++;//计算str的长度
if (str1->length < i) {
//增加存储空间,将较长的空间赋值为新的值
str1->ch = (char*)malloc(sizeof(char));
str1->strsize = i;
}
str1->length = i;
for (i = 0; i < str1->length; i++) {
str1->ch[i] = str[i];//从第一个字符开始,着个赋
}
}
void StrConcat(String* str, String str1, String str2) {
if (str->strsize < str1.strsize + str2.strsize) {
str->ch = (char*)realloc(str->ch, (str->length + str1.length) * sizeof(char));
str->strsize = str1.length + str2.length;
}
str->length = str1.length + str2.length;
int i;
for (i = 0; i < str1.length; i++) {
str->ch[i] = str1.ch[i];
} //将str1赋值到str
for (; i < str->length; i++) {
str->ch[i] = str2.ch[i - str1.length];
} //将str2赋值到str
}
void print(String* str) {
int i;
for (i = 0; i < str->length; i++) {
cout << str->ch[i];
}
cout << endl;
}
int main()
{
char st1[255], st2[255];
String str1, str2, str3;
str1 = CreateNullString();
str2 = CreateNullString();
str3 = CreateNullString();
cout << "请输入第一个串" << endl;
cin >> st1;
StrAssign(&str1, st1);
cout << "请输入第二个串" << endl;
cin >> st2;
StrAssign(&str2, st2);
StrConcat(&str3, str1, str2);
print(&str3);
return 0;
}
3 块链存储
思路(1):两个子串先分成块,再连接
//将两个串先分别转换成块存储
//将已经分好的块进行连接
但这样有个问题,第一个串的最后一块可能不满,显然有些不合理,造成了空间浪费
#include <stdio.h>
#include <iostream>
#include <cstring>
#define CHUNKSIZE 4//用户自定义块的大小
using namespace std;
typedef struct Chunk { //块链存储结构体
char ch[CHUNKSIZE];
struct Chunk* next;
}Chunk;
Chunk* initChunk(char[]);//初始化一个串
void print_Chunk(Chunk* h);//打印Chunk串
void contract(Chunk* t, Chunk* s);//将串s插入到串t某个字符后
int main()
{
Chunk* s, * t;
char s_str[100];
char t_str[100];
cout << "请输入t串:";
cin >> t_str;
cout << "请输入s串:";
cin >> s_str;
t = initChunk(t_str);//将两个串先分别,转换成块存储
s = initChunk(s_str);//将两个串先分别,转换成块存储
cout << "链接后为:" << endl;
contract(t, s); //将已经存储好的块进行连接
print_Chunk(t);//打印连接后的块链
return 0;
}
Chunk* initChunk(char str[])
{
Chunk* h, * temChunk, * lastChunk;
h = new Chunk;//为头结点分配内存, 头结点不储存信息
lastChunk = h;
int num = strlen(str) / CHUNKSIZE;//计算出所需块链的个数
int mod = strlen(str) % CHUNKSIZE;
if (mod != 0)num++;//如果不是这整数个块,则块数num+1
int i, j;
//对每一个块赋值
for (i = 0; i < num; i++)
{
temChunk = new Chunk;//为块链分配内存
j = 0;
//当字符串还没有结束以及j<CHUNKSIZE的时候,进行赋值
while (j < CHUNKSIZE && str[i])
{
temChunk->ch[j] = str[i * CHUNKSIZE + j];//将对应字符赋值
j++;
}
// 尾插法
//将当前已经完成的这个块,连接在上一个块的后面
lastChunk->next = temChunk;
lastChunk = temChunk;
}
//如果最后一个块没填满,通常用'#'补上
if (mod != 0)
{
int temp = CHUNKSIZE - mod;
for (size_t q = 0; q < temp; q++)
{
lastChunk->ch[mod + q] = '#';//最后一个块没填满,用'#'补上
}
}
lastChunk->next = NULL;
return h;
}
void print_Chunk(Chunk* h)
{
Chunk* temChunk;
temChunk = h->next;
int i;
while (temChunk)//打印输出时,按块输出
{
for (i = 0; i < CHUNKSIZE; i++)
{
if (temChunk->ch[i] == NULL)
{
break;
}
else
{
cout << temChunk->ch[i] ;
}
}
temChunk = temChunk->next;//一个块输出完毕,继续输出下一个块
cout << " ";//为了使直观的反映内部存储结构,输出时块与块之间,以空格间隔
}
cout << endl;
}
void contract(Chunk* t, Chunk* s)
{
while (t->next)
{
t = t->next;
}
t->next = s->next;//直接把s连在t之后
}
如图,第一个串的最后一块可能不满,显然有些不合理,造成了空间浪费。
为了便于观察,他是4个一块,第一个串最后一块不满的用#补上
//为了使直观的反映内部存储结构,输出时块与块之间,以空格间隔
第二个串最后一块不满的用#补上
思路(2)先连接,再分块存储
//先把两个串连接
//然后再按照块链的方式存储
#include <iostream>
#include<string>
#include<cstring>
#define CHUNKSIZE 5 //用户自定义块的大小
using namespace std;
typedef struct Chunk {//块链存储结构体
char ch[CHUNKSIZE];
struct Chunk* next;
}Chunk;
char s_s[200];
Chunk* initChunk(char[]);//初始化一个块链
void print_Chunk(Chunk* h);//打印Chunk块
void StrConcat(char s1[], char s2[]);
int main()
{
Chunk* s, * t;
char s_str[100];
char t_str[100];
cout << "请输入t串:";
cin >> t_str;
cout << "请输入s串:";
cin >> s_str;
StrConcat(t_str, s_str);//先把两个串连接
t = initChunk(s_s);//然后再按照块链的方式存储
print_Chunk(t);//打印连接后的块链
return 0;
}
Chunk* initChunk(char str[])
{
Chunk* h, * temChunk, * lastChunk;
h = new Chunk;//为头结点分配内存, 头结点不储存信息
lastChunk = h;
int len = strlen(str);//串的总长度
int num = strlen(str) / CHUNKSIZE;//计算出所需块链的个数
int mod = strlen(str) % CHUNKSIZE;
if (mod != 0) num++;//如果不是这整数个块,则块数num+1
int i, j;
//对每一个块链赋值
for (i = 0; i < num; i++)//创建num个块,组成一个块链
{
temChunk = new Chunk;//为块链分配内存
j = 0;
//创建单个块,当这个块填满或者字符串结束时,退出本次while循环
//当字符串还没有结束以及j<CHUNKSIZE的时候,进行赋值
while (str[i * CHUNKSIZE + j] != '\0' && j < CHUNKSIZE)
{
temChunk->ch[j] = str[i * CHUNKSIZE + j];//将对应字符赋值
j++;
}
// 尾插法
//将当前已经完成的这个块,连接在上一个块的后面
lastChunk->next = temChunk;
lastChunk = temChunk;
//如果最后一个块没填满,通常用'#'补上
//当字符串已经结束,并且最后一个块没满,则执行if语句
if ((i * CHUNKSIZE + j) == (len) && (CHUNKSIZE - mod!=0))
{
int temp = CHUNKSIZE - mod;
for (size_t q = 0; q < temp; q++)
{
temChunk->ch[j + q] = '#';//最后一个块没填满,用'#'补上
}
}
}
lastChunk->next = NULL;//块链完成,最后加上NULL结束
cout << endl << "当前块的大小为:" << CHUNKSIZE << endl;
cout << "该块链共有" << num << "个块组成" << endl << endl;
return h;
}
void print_Chunk(Chunk* h)
{
Chunk* temChunk;
temChunk = h->next;
int i;
//输出连接后的串
cout << "连接后的串为:";
for (i = 0; i < strlen(s_s); i++) {
cout << s_s[i];
}
cout << endl;
//便于观察块链的内部真实存储结构,输出块链的每一个块的内容
cout << "块链的存储结构为:";
while (temChunk)
{
for (i = 0; i < CHUNKSIZE; i++)
{
if (temChunk->ch[i] == NULL)
{
break;
}
else
{
cout << temChunk->ch[i];
}
}
temChunk = temChunk->next;
cout << " ";//当一个块输出完毕,输出空格,即每个块之间用空格间隔,还是便于观察
}
cout << endl;
}
void StrConcat(char s1[], char s2[])
{
int i = 0;
for (; i < strlen(s1); i++)
{
s_s[i] = s1[i];
}
for (; i < strlen(s1) + strlen(s2); i++)
{
s_s[i] = s2[i - strlen(s1)];
}
}
#define CHUNKSIZE 5 //用户自定义块的大小
当CHUNKSIZE= 5时,
第二个块只有3个字符,后面两个空缺部分用#填补
//为了使直观的反映内部存储结构,输出时块与块之间,以空格间隔
当CHUNKSIZE= 4时,
存储满两个完整的块
思路(3)在思路(1)上改进,第一个串的最后一块不满,用第二个串前移补上。但程序是实现上较为复杂,感兴趣的自己研究。