7. C/C++常用标准库及String类实现指南

一、前言

在正常的开发过程中,使用标准库是一件非常常见的事情,如果不会使用标准库,程序开发效率将大幅下降。

为了后面做一些有趣的小项目,所以本节将讲一些C/C++中常用的标准库。

但这也只是为了给大家留下一个印象,更多的还是需要大家自己学习。

最主要的原因是,如果不经常用,即使现在会用了,过段时间也会忘了,所以如果后面的项目中遇到我觉得大家可能不怎么熟悉的地方,也会稍微讲一讲。

二、String

C/C++的标准库众多,所以我们就先挑一个稍微简单一点且非常常用的标准库:string

不过由于C/C++标准库里面的源码到处使用宏、自定义结构体和模板,十分晦涩难懂,我们这里只是简单实现一下它的基础功能即可。

老规矩,不知不觉已经来到第七章了,那就新建一个项目day7,将其设为启动项目、并添加源文件main.cpp

image-20231202105137599

看名字string就知道,这是一个字符串,其本质就是封装了对char*的操作。

那么让我们先来使用一下它,看它有多方便吧,添加头文件string

#include<iostream>
#include<string>
using std::string;
using std::cout;
using std::endl;
int main() {
	string s1="https://www.kucoding.com";
	s1 += " 学习C/C++";
	cout << s1 << endl;
}

同样的,string也是在标准命名空间std中,避免写着麻烦,我们就使用了using std::string

这里用到了一个写法 +=,这其实也是一个运算符,等效于 s1=s1+" 学习C/C++"; ,其实intdouble等基本数据类型都可以这样写。

上面的几句话,如果换为char* ,使用C语言提供的字符串操作函数,就是下面这种写法:

#include<iostream>
using std::cout;
using std::endl;
int main() {
	char buf[100]="https://www.kucoding.com";
	strcat_s(buf,100," 学习C/C++");
	cout << buf<<endl;
}

strcat_s为函数strcat的安全函数,用于拼接字符串。

这么一看,好像string标准库也没什么优势,但如果继续下去,你就会发现string的优越性。

比如:

  • 我这里给缓存区buf给了100个空间,这是我估计出来的,但string不用我来估计。
  • 只要电脑内存够大,string几乎可以无限自动追加,且这一过程是自动的,虽然我们用char* ,通过new动态分配内存的方法也可以实现,但全程需要我们手动操作。
  • string 只需要像符合我们常规思路的使用即可比如用+=追加字符串,但使用char*,我们得自己记大量的字符串操作函数。

所以接下来,我们就试着实现一个自己的string类,来实现一个常规的字符串类的基本功能。

首先,创建一个类String,它需要有三个成员变量:

  • buf:存放数据的缓存区

  • size:当前字符串的长度(不含末尾0结束字符)

其目的是方便成员函数使用,比如增、删、改、查等操作都需要字符串的长度才能进行,与其每次都调用一下strlen函数来计算,不如直接记录下来。

  • capacity:缓存区容量,即缓存区的大小,方便时刻关注当前操作是否有可能超出缓存区的大小。

完成之后如下:

class String {
private:
	char* buf; //存放数据的缓存区
	int size; //当前使用了多少缓存区
	int capacity; //缓存区的容量
public:
};

有了成员变量,我们就可以开始写它的构造函数了:

	String() {
		buf = new char[15];
		capacity = 15;
		size = 0;
	}

	String(const char* str) {
		size=strlen(str);
		capacity = size * 2;
		buf = new char[capacity];
		strcpy_s(buf, size+1, str);
	}

这里只写了两个构造函数,分别为无参构造函数与带一个常量字符串参数的构造函数。

  • 无参构造函数:给缓存区申请默认大小15个字节,并给成员变量赋值。
  • 带参构造函数:用于将传入的字符串赋值给本类中的缓存区,并初始化sizecapacitycapacity之所以要比size大两倍,是为了方便后面追加字符串。

这里用到了C语言的函数strlen,作用是用于求字符串的长度,注意它得到的长度不包含末尾\0

还有strcpy_sstrcpy的安全函数,用于复制字符串,将第三个参数的字符串,复制到第一个所指定的缓存区中,中间的大小要包含最后结尾字符 \0,所以要加一。

写完构造函数,再把析构函数写好:

	~String()
	{
		if (buf != NULL) {
			delete[] buf;
		}
	}

开始先进行了判断,如果buf等于NULL,就说明它没有被分配内存,就不进行delete[] 操作。

这里使用delete[]而不是delete的原因是删除的是数组,一般来说,两者是匹配使用的,不理解的可以参考文章:内存分配

然后是写赋值函数:

	void operator=(const char* str) {
		if (buf != NULL) {
			delete[] buf;
			buf = NULL;
		}
		size = strlen(str);
		capacity = size * 2;
		buf = new char[capacity];
		strcpy_s(buf, size+1, str);
	}

这又是一个新知识点,叫做重载运算符,通过关键字operator进行,具体如何写,取决于紧跟其后的操作符号。

比如这里是等于号=,等于号有左右两个操作数,但由于这是在类里面,类本身占了一个左操作数,所以还需要一个参数作为右操作数。

各个运算符用法不一定相同,请结合具体情况。

之所以参数为const char *,仅仅只是因为我们需要的是赋值字符串,如果你有其它需求,完全可以改。

毕竟是赋值动作,所以得先判断以前是不是有缓存区,如果有就先删除了,然后重新分配空间,拷贝过来。

至于返回值为什么为void,仅仅只是不想弄的太复杂,当然你也可以添加。

之所以C++之中能使用这种连等的语法:a=b=c=d,就是依靠的重载等于运算符,然后返回一个返回值,不过我这里就不深入了,可以自己尝试一下。

接下来再看一看我们上面使用的 +=,这同样是一个操作符号:

	void operator+=(const char* str) {
		int tLen = strlen(str);
		if (buf == NULL) {
			size = tLen;
			capacity = size * 2;
			buf = new char[capacity];
			strcpy_s(buf, size+1, str);
		}
		else if(tLen+size+1<capacity) {
			size += tLen;
			strcat_s(buf, size+1, str);
		}
		else {
			capacity = (size + tLen) * 2;
			char* tBuf = new char[capacity];
			tBuf[0] = '\0';
			strcat_s(tBuf, size+1, buf);
			strcat_s(tBuf, size + tLen+1, str);
			size += tLen;
			delete[] buf;
			buf = tBuf;
		}
	}

这个就稍微复杂一些,它的用法和前面的=符号是一样的。

函数中先是得到字符串长度tLen,然后判断缓存区是否存在,如果不存在,就和前面的那个带参构造函数中代码相同。

如果已经存在,就再看已用的空间加上追加的字符串长度,是否小于容量。

如果小于,我们就可以直接用函数strcat_s来进行追加字符串。

strcat_s函数为strcat函数安全函数,只是多了第二个参数,指定追加完之后字符串的长度,注意要加1,因为有结尾 \0

否则,就是容量不够了,那就重新开辟一个更大的空间,然后将两个字符串都追加到新的空间中,删除原有空间,将新空间赋值给buf使用。

这里注意我将新空间的第一个元素赋值为 \0,这是因为strcat_s函数是通过结尾字符\0开始追加的。

char* tBuf = new char[capacity];
tBuf[0] = '\0';

最后,还需要一个获取大小,容量的函数:

	int Size() {
		return size;
	}

	int Capacity() {
		return capacity;
	}
作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux