4.3 字符串
字符串(String)是由零个或多个字符(char)顺序排列组成的有限序列,简称为串。例如 “good morning”就是由12个字符构成的一个字符串。一般把字符串记作:
其中S是串名,引号中的字符序列是串值。字符个数是串的长度,长度为0的串被称为空串,因为它不包含任何字符。需要注意的是,空格字符(" ")并不是空串,因为它包含一个字符——空格。 若把某个串称为主串,则主串中任意个连续的字符组成的子序列被称为子串。子串在主串中第一次出现时,其首字符在主串中的序号被称为该子串在主串中的位置。
4.3.1 字符串的定义与存储
字符串在许多非数值计算问题中扮演着重要的角色,并在模式匹配、程序编译和数据处理等领域得到广泛应用。 在高级程序设计语言中,字符串通常被定义为以特殊字符’ ’(称为空字符或字符串结束符)结尾的字符序列。这个约定使得在处理字符串时可以方便地确定字符串的结束位置。
关于字符串的存储方式,主要有两种常见的方式:
- 顺序存储:字符串的字符按照顺序依次存储在连续的内存空间中。这种方式使得字符串的访问和操作效率较高,可以通过索引直接访问任意位置的字符。在顺序存储方式中,字符串的长度可以通过计算字符个数或者遇到’ ’结束符来确定。
- 链式存储:字符串的字符通过链表的方式进行存储。每个节点包含一个字符和指向下一个节点的指针。链式存储方式可以动态地分配内存,适用于长度可变的字符串。但是相比于顺序存储,链式存储方式需要更多的内存空间,并且访问字符需要遍历链表。
选择何种存储方式取决于具体的应用场景和需求。顺序存储适合于需要频繁访问和操作字符串的情况,而链式存储适合于长度可变的字符串或者对内存空间要求较高的情况。关于字符串的基础知识亦可参考前文: 【重拾C语言】六、批量数据组织(三)数组初值;字符串、字符数组、字符串数组;类型定义 typedef 【重拾C语言】七、指针(三)指针与字符串(字符串与字符串数组;指针与字符串的遍历、拷贝、比较;反转字符串)
1. 顺序存储
串的顺序存储是把一个串所包含的字符序列相继存入连续的字节中,通常用数组实现。例如字符串S="student", 顺序存储在数组A[10]中,则A[0]=‘s’,A[1]=‘t’,…,A[6]=‘t’ .
2. 链式存储
串的链式存储是通过将可用的存储空间划分为一系列大小相同的节点来实现的。每个节点包含两个部分:一个存储字符的数据域和一个指向下一个节点的指针域。 例如,假设我们有一个字符串S = “student”,我们可以使用链式存储方式将其表示为一个节点序列。每个节点包含一个字符和一个指向下一个节点的指针。 链式存储的节点结构可以如下表示:
代码语言:javascript复制struct Node {
char data; // 存储字符的数据域
Node* next; // 指向下一个节点的指针域
};
对于字符串S = “student”,可以创建以下节点序列:
代码语言:javascript复制Node 1: data = 's', next -> Node 2
Node 2: data = 't', next -> Node 3
Node 3: data = 'u', next -> Node 4
Node 4: data = 'd', next -> Node 5
Node 5: data = 'e', next -> Node 6
Node 6: data = 'n', next -> Node 7
Node 7: data = 't', next -> NULL
其中,每个节点的data域存储一个字符,next指针指向下一个节点。最后一个节点的next指针为空(NULL),表示链表的结束。
链式存储方式可以动态地分配内存空间,适用于长度可变的字符串。通过遍历链表,我们可以访问和操作字符串中的字符。然而,相对于顺序存储方式,链式存储需要额外的指针空间,并且访问字符的效率较低。
3. C语言实现顺序存储
代码语言:javascript复制#include <stdio.h>
int main() {
char S[] = "student";
printf("String S: %sn", S);
return 0;
}
输出结果为:
代码语言:javascript复制String S: student
使用字符数组S
来存储字符串"student"。该字符串被存储在数组中的连续内存空间中,每个字符占据一个数组元素的位置。
4. C语言实现链式存储
接下来,让我们使用C语言实现字符串的链式存储:我们将使用一个结构体来表示链表的节点,每个节点包含一个字符和一个指向下一个节点的指针。
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
struct Node {
char data;
struct Node* next;
};
void printString(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
printf("%c", current->data);
current = current->next;
}
printf("n");
}
int main() {
struct Node* head = (struct Node*)malloc(sizeof(struct Node));
struct Node* second = (struct Node*)malloc(sizeof(struct Node));
struct Node* third = (struct Node*)malloc(sizeof(struct Node));
struct Node* fourth = (struct Node*)malloc(sizeof(struct Node));
struct Node* fifth = (struct Node*)malloc(sizeof(struct Node));
struct Node* sixth = (struct Node*)malloc(sizeof(struct Node));
struct Node* seventh = (struct Node*)malloc(sizeof(struct Node));
head->data = 's';
head->next = second;
second->data = 't';
second->next = third;
third->data = 'u';
third->next = fourth;
fourth->data = 'd';
fourth->next = fifth;
fifth->data = 'e';
fifth->next = sixth;
sixth->data = 'n';
sixth->next = seventh;
seventh->data = 't';
seventh->next = NULL;
printf("String S: ");
printString(head);
return 0;
}
输出结果为:
代码语言:javascript复制String S: student
创建一个链表来表示字符串"student"。每个节点都包含一个字符和一个指向下一个节点的指针。通过遍历链表,我们可以打印出链表中存储的字符,从而得到字符串的内容。注意,在实际应用中,我们应该在使用完链表后释放分配的内存,以避免内存泄漏。
代码优化
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
struct Node {
char data;
struct Node* next;
};
void printString(struct Node* node) {
while (node != NULL) {
printf("%c", node->data);
node = node->next;
}
}
int main() {
struct Node* head = NULL;
struct Node* tail = NULL;
char characters[] = {'s', 't', 'u', 'd', 'e', 'n', 't'};
int length = sizeof(characters) / sizeof(characters[0]);
for (int i = 0; i < length; i ) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = characters[i];
newNode->next = NULL;
if (head == NULL) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
}
printf("String S: ");
printString(head);
printf("n");
// 释放链表节点的内存
struct Node* current = head;
while (current != NULL) {
struct Node* temp = current;
current = current->next;
free(temp);
}
return 0;
}