#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h> // 断言库
#pragma warning(disable: 4996)
#define pause system("pause")
// 定义String类型
typedef struct STRING{
char* data; // 数据
int length; // 实际存储长度
int capacity; // 容量 = 长度 + 1 字符串有'\0'
} Str;
// malloc calloc realloc
/*
当传递给函数的 data 指针 NULL 时,它会分配一个长度为2的字符串 ""
代替长度为1的字符 '\0'。
这是因为需要一个表示空字符串的字节,
还需要存储表示空字符的 '\0' 字节
*/
// 初始化Str对象数据 initStrByData // 栈区创建Str结构体变量,data数据在堆区
void initStrByData(Str* str, const char* data) {
if (NULL == str)
{
printf("str is NULL.\n"); // 显示返回
return;
}
if (NULL != str->data && str->capacity >= str->length && str->capacity >= 1 && str->length >= 0) // 如果已经存在数据,就清空释放掉
{
free(str->data);
str->capacity = str->length = 0;
}
if (NULL != data)
{
str->length = strlen(data);
str->capacity = str->length + 1;
str->data = (char*)malloc((str->capacity) * sizeof(char));
memset(str->data, 0, str->capacity);
strncpy(str->data, data, str->length);
str->data[str->length] = '\0'; // 强制设置终止字符,以确保字符串有效 ,其实前面已经清0了
}
else
{
str->length = 0; //无数据,就当成一个空串""
str->capacity = 2;
str->data = (char*)malloc(2 * sizeof(char));
str->data[0] = '\0';
}
}
// 初始化Str对象容量 initStrByCapacity
void initStrByCapacity(Str* str, int capacity) {
if (NULL == str)
{
printf("str is NULL.");
return;
} // 有点问题 二次分配data 空间,对于已经有内容的如何处理?
if (NULL != str->data && str->capacity >= str->length && str->capacity >= 1 && str->length >= 0) // 如果已经存在数据,就清空释放掉
{
free(str->data);
str->capacity = str->length = 0;
}
if (capacity <= 0)
{
str->capacity = 1;
str->length = 0; // 实际存储长度
str->data = (char*)calloc(str->capacity, sizeof(char));
}
else
{
str->capacity = capacity;
str->length = 0;
str->data = (char*)calloc(str->capacity, sizeof(char));
}
}
// 初始化Str对象容量 //创建于栈区的String createStrByCapacity
Str* createStrByCapacity(Str** str, int capacity) {
*str = (Str*)malloc(1 * sizeof(Str)); //申请空间
initStrByCapacity(*str, capacity);
return *str;
}
// 初始化Str对象数据 //创建于栈区的String createStrByData
Str* createStrByData(Str** str, const char* data) {
*str = (Str*)malloc(1 * sizeof(Str));
initStrByData(*str, data);
return *str;
}
// 释放Str对象的data占用的内存
void freeStr(Str* str) {
if (NULL != str)
{
if (NULL != str->data)
{
free(str->data);
str->data = NULL;
}
str->capacity = str->length = 0;
}
}
// 释放Str对象占用的内存
void deleteStr(Str** str) {
if (NULL != str)
{
if (NULL != *str)
{
if (NULL != (*str)->data)
{
free((*str)->data);
(*str)->data = NULL;
}
(*str)->capacity = (*str)->length = 0;
*str = NULL;
}
}
}
// 获取Str对象的长度
int getStrLength(const Str* str) {
if (NULL != str){
return str->length;
}
return -1;
}
// 获取Str对象的容量
int getStrCapacity(const Str* str) {
if (NULL != str){
return str->capacity;
}
return -1;
}
//注意,返回值应当判断,否则,返回值为-1时,以字符形式输出,其显示可能时未知的。
// 获取Str对象指定位置的字符
char getCharAtIndex(const Str* str, int index) {
if (NULL != str)
{
if (index >= 0 && index < str->length) {
return str->data[index];
}
}
return -1; // 字符不存在
}
// 输出Str对象的内容
void printStr(const Str* str) {
if (NULL != str){
if (NULL != str->data)
{
//printf("%s\n", str->data);
puts(str->data);
}
}
else
{
printf("%s\n","\"NULL\"");
}
}
// 拼接两个Str对象
Str* concatStr(Str* str1, const Str* str2) {
if(NULL != str1)
{
if (NULL != str2)
{
int len1 = str1->length;
int len2 = str2->length;
if ((len1 - len2) < (len2 + 1))
{
char* tmp = (char*)realloc(str1->data, len1 + len2 + 1);
if (NULL == tmp) {
return NULL; // 内存分配失败
}
// 重新分配存储空间
str1->data = tmp;
}
strcat(str1->data, str2->data);
str1->length = len1 + len2; // 更新实际长度
str1->capacity = str1->length + 1; // 更新容量
str1->data[len1 + len2] = '\0'; // 防止打印越界
}
return str1;
}
return NULL;
}
// 比较两个Str对象是否相等
int compareStr(const Str* str1, const Str* str2) {
if (NULL != str1 && NULL != str2)
{
if (NULL != str1->data && NULL != str2->data)
{
// 为了避免strcmp的返回值造成混淆,定义其返回结果
int cmp = strcmp(str1->data, str2->data);
if (cmp == 0) return 0; // str1 == str2
else if (cmp < 0) return -1; // str1 < str2
else return 1; // str1 > str2
}
}
return -2; // 无法比较
}
// 截取Str对象的子字符串
Str* subStr(const Str* str, int startIndex, int length) { // && 具有短路效果,条件必须全部满足
if (NULL != str && str->data != NULL && startIndex >= 0 && length >= 0)
{
// 因为 当str->data,没有正确的初始化,strlen函数的结果可能时未知的
// 好在初始化的时候就已经设置为'\0'结尾,还需判断str->data != NULL就行了
int strLength = strlen(str->data); // 和str->length一个效果
if (startIndex <= strLength && length <= strLength - startIndex)
{
// 可截取长度 length <= strLength - startIndex
Str* result = (Str*)malloc(1 * sizeof(Str));
initStrByCapacity(result, length + 1); // 为新字符串分配空间
strncpy(result->data, (str->data) + startIndex, length); // 复制数据
result->length = strlen(result->data); // 更新实际长度
return result;
}
}
return NULL;
}
// 查找字符串中第一次出现指定子字符串的位置
int findSubStr(const Str* str, const char* subStr) {
if (NULL != str && NULL != subStr)
{
// subStr的长度是否大于str,不做校验,因为使用strlen可能会出问题
char* found = strstr(str->data, subStr);
if (NULL != found) {
return found - str->data;
}
}
return -1; // 不存在返回-1
}
// 替换字符串中的指定子字符串
Str* replaceSubStr(Str* str, const char* oldSubStr, const char* newSubStr) {
if (NULL != str && NULL != oldSubStr && NULL != newSubStr)
{
int oldSubStrLen = strlen(oldSubStr);
int newSubStrLen = strlen(newSubStr);
// 先搜索old字串是否存在
char* found = strstr(str->data, oldSubStr);
if (NULL != found) {
// todo
// 首先计算新字符串的长度
int newStrLen = str->length - oldSubStrLen + newSubStrLen;
// 然后为新字符串分配内存空间
char* newStrData = (char*)malloc(newStrLen + 1);
if (NULL == newStrData) {
// 内存空间不足
return NULL;
}
// 将基础字符串str前found的部分拷贝至新字符串中
memcpy(newStrData, str->data, found - str->data);
// 将newSubStr拷贝到新字符串中
memcpy(newStrData + (found - str->data), newSubStr, newSubStrLen);
// 将基础字符串str中oldSubStr末尾的部分拷贝到新字符串中
memcpy(newStrData + (found - str->data) + newSubStrLen, found + oldSubStrLen, str->length - (found - str->data) - oldSubStrLen);
// 为新字符串结尾添加'\0'
newStrData[newStrLen] = '\0';
// 在释放原字符串内存的前提下,更新str的data,length和capacity
free(str->data);
str->data = newStrData;
str->length = newStrLen;
str->capacity = newStrLen + 1;
// 返回更新后的字符串str
return str;
}
}
return NULL;
}
// 替换字符串中的指定字符
Str* replaceChar(Str* str, char oldChar, char newChar) {
if (NULL != str)
{
// 循环控制条件为长度,而不是容量,因为处理的是有内容的字符
for (int i = 0; i < str->length; i++) {
if (str->data[i] == oldChar) {
str->data[i] = newChar;
}
}
str->data[str->capacity] = '\0'; // 添加'\0'到新字符串的末尾
return str;
}
return NULL;
}
// 使用 isalpha()函数来检查输入的字符是否为字母,
// 是,使用 toupper() 函数,如果不是,不变。
// 将字符串全部转换为大写
Str* toUpperCase(Str* str) {
for (int i = 0; i < str->length; i++) {
if (isalpha(str->data[i])) {
str->data[i] = toupper(str->data[i]);
}
}
//str->data[str->length] = '\0';
return str;
}
// 将字符串转换为小写
Str* toLowerCase(Str* str) {
for (int i = 0; i < str->length; i++) {
if (isalpha(str->data[i])) {
str->data[i] = tolower(str->data[i]);
}
}
str->data[str->length] = '\0';
return str;
}
// 测试用例
int main(int argc, char* argv[]) {
// 测试创建Str对象
Str* str = createStrByData(&str,"Hello World!");
assert(str != NULL); // 断言
printf("str addr 0x%p\n", str);
// 测试计算字符串长度
assert(getStrLength(str) == 12);
assert(strcmp(str->data, "Hello World!") == 0);
// 测试删除Str对象
deleteStr(&str);
assert(str == NULL);
// 测试字符串子串截取
str = createStrByData(&str, "Hello World!");
Str* substr = subStr(str, 6, 5);
assert(substr != NULL);
assert(substr->length == 5);
assert(strcmp(substr->data, "World") == 0);
deleteStr(&substr);
// 测试字符串子串截取(越界)
substr = subStr(str, 13, 1);
assert(substr == NULL);
// 测试字符串子串截取(!)
substr = subStr(str, 11, 1);
assert(substr != NULL);
assert(substr->length != 0);
deleteStr(&substr);
// 测试查找子字符串
int index = findSubStr(str, "World");
assert(index == 6);
// 测试查找子字符串(不存在)
index = findSubStr(str, "Goodbye");
assert(index == -1);
// 测试查找子字符串(空字符串)
index = findSubStr(str, "");
assert(index == 0);
// 测试将字符串转换为小写
initStrByData(str, "HELLO WORLD!"); // 再次初始化str
toLowerCase(str);
assert(strcmp(str->data, "hello world!") == 0);
// 测试替换字符
replaceChar(str, 'o', 'x');
assert(strcmp(str->data, "hellx wxrld!") == 0);
// 测试释放Str对象内存
deleteStr(&str); // bug,程序卡死
printf("del:str add 0x%p\n", str);
pause;
return 0;
}
使用c实现String类型的部分功能
于 2023-06-01 11:54:59 首次发布