c string头文件 C++简介(六)字符串

 2024-03-21 01:17:48  阅读 0

1、为什么要学习班? C语言中的字符串

在C语言中,字符串是以'\0'结尾的字符的集合。 为了操作方便,C标准库提供了一些str系列库函数,但这些库函数与字符串分离,不是很具体。 它符合OOP的思想,底层空间需要用户自己管理。 如果你不小心,你可能会越界访问。

算法题的运用

在OJ中,关于字符串的问题基本都是以类的形式出现,而在平时的工作中,为了简单、方便、快捷,基本都是使用类。 很少有人使用C库中的字符串操作函数。

2.标准库中的类

我们可以使用这个网站-C++来了解它在对方库中是如何定义的。

文档介绍

总结:

使用类时,必须包含#和using std;

底层介绍

我们从文档介绍中知道的类是一个类模板,大致如下:

typedef basic_string string;
template
class basic_string
{
private:
	T* str;
    //...
};

我们从来没有想过为什么类型是T,类型不就是char类型吗? 还有其他类型吗?

这个地方和有关。 计算机是美国人发明的。 早期,计算机只显示英文。 显示英文比较简单。 只需要26个英文字母即可组合。 算上大小写字母和标点符号,总共只有128个,足以代表常见的英语。 计算机通过建立数值与相应符号之间的映射关系来存储英文(仅二进制)代码。 这种映射关系称为编码表。 英语的编码表是ASCII表,用来表示英语。

ASCII码值的含义是:你所代表的一个字符存储在内存中时对应的整数值(以二进制存储)

例如1:大写字母A的ASCII码表示它以数字65的二进制形式存储在机器内存中。

eg2:对于这个字符串数组,实际上存储的是这些字符对应的值(语言规定字符串以\0结尾)

int main()
{
	char str[] = "hello";
	return 0;
}

头文件string的作用_头文件string.h的作用_c string头文件

后来为了在全世界普及计算机,就不能只适用于英语了。 于是,出现了一个全局的文本编码表,包括ASCII、utf-8、utf-16、utf-32等。

中文编码

在英语中,每个字母对应一个值,一个字节有8位,有2^8(256)种状态,所以英语很容易表达。 如果用一个字符来表示一个汉字,最多只能表示256个汉字,这是远远不够的。 一个字节不够,所以两个字节一起使用。 两个字节有2^16种状态,可以表示65000多个汉字。 但是,如果表示一个汉字的位数越多,就意味着该汉字所占用的空间。 越大,这就越糟糕。 UTF-8的意思是用2个字节来写一些常见的汉字,用3或4个字节来写一些生僻的字符,并规定了一系列的规则。 不同的值对应不同的汉字...,Linux下默认是utf-8。

eg:存储的是对应的值。 如果要显示的话,用这些值去对应的表中查一下即可。

int main()
{
	char str[] = "喝水";
	return 0;
}

c string头文件_头文件string的作用_头文件string.h的作用

另外一个有趣的事情是,当我们使用++时,文本的内容会发生变化。

c string头文件_头文件string.h的作用_头文件string的作用

所有编译器都允许您选择编码。 如果编译器的编码与你写的不对应,就会出现乱码。 国内读者较多,所以我定制了中文的编码表--gdk,默认编码是gdk,

由于一些编码的原因,有些字符串会用两个字符来表示,所以有; 如果有,建议使用(匹配)

c string头文件_头文件string的作用_头文件string.h的作用

头文件

头文件是#. 使用时记得添加。

如果不包含头文件的话,这样写是可以编译通过的。

c string头文件_头文件string的作用_头文件string.h的作用

但是使用流插入和流提取时会报错()

c string头文件_头文件string的作用_头文件string.h的作用

但不是

c string头文件_头文件string.h的作用_头文件string的作用

博主还是建议包含这个头文件,总比没有好。

3.类和构造函数的常用接口描述

给星的很重要,其他的都可以理解

头文件string.h的作用_c string头文件_头文件string的作用

(1) 无参数构造、带参数构造、复制构造

int main()
{
	string s1;  //无参构造
	string s2("hello world"); //带参的
	string s3(s2); //拷贝构造
 
	cin >> s1;
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
}

头文件string.h的作用_c string头文件_头文件string的作用

(2)

string (const string& str, size_t pos, size_t len = npos);

pos 表示从该位置开始。 Len 代表副本的长度。 这个len给出了一个默认值npos,表示取从该位置开始的所有后续字符,直到结束。 ps:npos的值为-1,对于来说-1就是整数的最大值(-1的补码都是1,对于来说就是整数的最大值)

int main()
{
	string s2("hello world"); //带参的
 
	string s4(s2, 2, 6);
	cout << s4 << endl;
}

头文件string.h的作用_c string头文件_头文件string的作用

s4:从s2的第二个位置开始,复制以下6个字符。

例如2:

int main()
{
	string s2("hello world"); //带参的
 
	string s5(s2, 2, 100);
	cout << s5 << endl;
 
	string s6(s2, 2);
	cout << s6 << endl;
}

c string头文件_头文件string.h的作用_头文件string的作用

↑s5:从s2的第二个位置开始,复制接下来的100个字符。

s6:从s2的第二个位置开始,复制所有后续字符(证明默认值npos)。

(3)

string (const char* s, size_t n);

int main()
{
	string s2("hello world"); //带参的
 
	string s7("hello world",3);
	cout << s7 << endl;
}

头文件string.h的作用_头文件string的作用_c string头文件

s7:初始化hello world的前三个字母,

(4)

string (size_t n, char c);

int main()
{
	string s8(10, '!'); //看起来有用实际没用
	cout << s8 << endl;
}

头文件string的作用_c string头文件_头文件string.h的作用

s8: 使用10!初始化类对象的容量操作

头文件string.h的作用_头文件string的作用_c string头文件

(1)计算物体的长度尺寸和长度

int main()
{
	string s1;
	cin >> s1;
 
	//不包含最后作为标识符的\0,算的是有效字符的长度
	cout << s1.size() << endl;  //重要
	cout << s1.length() << endl; //了解
}

头文件string的作用_头文件string.h的作用_c string头文件

(2)

功能:告诉我们这个字符串可以有多长(与内存有关)

(3)

功能:告诉我们这个字符串的容量有多大(即最多可以存储多少个字符)

头文件string的作用_头文件string.h的作用_c string头文件

虽然显示为15,但实际空间是16,因为有一个\0,但容量是指可以存储多少个有效字符。

(4)清除

功能:清除所有有效数据并预留空间

c string头文件_头文件string.h的作用_头文件string的作用

(5)中的容量增加如下。 以下是自动扩容的程序。 从结果可以得出,产能增幅一般会是1.5倍。

void TestPushBcak()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
 
}
int main()
{
	TestPushBcak();
}

头文件string的作用_c string头文件_头文件string.h的作用

但每一次容量的增加都是有代价的,而且这个代价还不小。 如果您已经知道需要多少空间,则可以利用降低容量增加的成本。

它只打开空间并影响容量,容量是可以更改的。

void TestPushBcak()
{
	string s;
	s.reserve(1000); //申请至少能存储1000个字符的空间
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
}
int main()
{
	TestPushBcak();
}

头文件string.h的作用_头文件string的作用_c string头文件

它将字符串中的有效字符数更改为 n。 当字符数增加时,会给额外的空间赋予一个初始值,用于初始化。 默认初始值为/0;

一开始,s1中的大小为0和15

c string头文件_头文件string.h的作用_头文件string的作用

使用后大小扩展为100,由于大小变化而变化,这100个大小用/0填充。

c string头文件_头文件string.h的作用_头文件string的作用

还可以自定义初始值

头文件string.h的作用_头文件string的作用_c string头文件

与 rsize 的异同

它只是简单地改变容量。 rsize 改变有效字符数,从而改变容量,并初始化改变后的有效字符数。

void Test()
{
	string s1;
	s1.reserve(100);
 
	string s2;
	s2.resize(100);
}
int main()
{
	Test();
}

头文件string.h的作用_c string头文件_头文件string的作用

使用扩容,不会影响之前的数据

头文件string的作用_c string头文件_头文件string.h的作用

但如果减小大小,数据就会被删除。

头文件string的作用_头文件string.h的作用_c string头文件

更有用的是。

(7) 空

功能:判断字符串是否为空

头文件string的作用_头文件string.h的作用_c string头文件

类对象的访问和遍历操作

(1) [ ] 遍历字符串

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

eg1:遍历字符

int main()
{
	string s1("hello world");
	for (size_t i = 0; i < s1.size(); i++) //读每个位置的字符
	{
		//cout << s1.operator[](i) << " ";//本质调用
		cout << s1[i] << " ";
	}
	cout << endl;
}

头文件string.h的作用_c string头文件_头文件string的作用

eg2:修改字符,每个字符+1后改变

int main()
{
	string s1("hello world");
	for (size_t i = 0; i < s1.size(); i++) //读每个位置的字符
	{
		//cout << s1.operator[](i) << " ";//本质调用
		cout << s1[i] << " ";
	}
	cout << endl;
	for (size_t i = 0; i < s1.size(); i++) //写每个位置的字符,operator[]的返回值是这个地方的引用
	{
		s1[i] += 1;
	}
	cout << endl;
	cout << s1 << endl;
}

为什么可以修改呢?

因为[]的返回值是char类型的引用。

//实际底层实现
char& operator[](size_t pos)
{
   //...	
   return _str[pos];
}
//底层是一个字符串数组,如果想修改第i个位置的字符,就返回这第[i]个位置的引用

编译器会将其转换为s1.[](i),然后这个函数调用就会有一个返回值。 该返回值是对第 i 个位置处的字符的引用。 如果添加它,则赋值可以修改第 i 个字符。 value,所以这里参考的是修改这个地方的值。 当作用域超出作用域时,对象还在,因为这个数组是在堆上打开的,超出作用域时堆不受影响。

这里的引用返回不是为了减少副本,而是为了支持修改对象

(2)迭代器遍历

使用方法及说明:

eg:修改和遍历字符

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
 
	it = s1.begin();
	while (it != s1.end())
	{
		*it += 1;
		++it;
	}
	cout << endl;
 
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
 
}

头文件string的作用_c string头文件_头文件string.h的作用

它有点像指针,但不一定是指针。 这里可以认为是一个指针。 该迭代器是嵌入类型,并且是在类中定义的,因此必须指定类域。

将迭代器视为类似指针的类型

头文件string.h的作用_头文件string的作用_c string头文件

PS:第二次调用的时候,就不用再写::了,也不需要定义了,但是还是要重新初始化一次。

头文件string.h的作用_头文件string的作用_c string头文件

(3)Range for自动向后迭代并自动判断结束(仅C++11中支持)eg1:遍历字符

int main()
{
	string s1("hello world");
	for (auto e : s1)
	{
		cout << e << " ";
	}
}

头文件string的作用_c string头文件_头文件string.h的作用

eg2:修改字符

int main()
{
	string s1("hello world");
	for (auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : s1)
	{
		e += 1;
	}
	for (auto e : s1)
	{
		cout << e << " ";
	}
}

c string头文件_头文件string的作用_头文件string.h的作用

(4)反向迭代器向后遍历

int main()
{
	string s1("hello world");
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
}

c string头文件_头文件string的作用_头文件string.h的作用

头文件string.h的作用_c string头文件_头文件string的作用

ps: 也可以使用auto自动推出它是一个迭代器。

头文件string.h的作用_c string头文件_头文件string的作用

头文件string.h的作用_c string头文件_头文件string的作用

问题1:迭代器遍历的意义是什么?

因为,无论向前还是向后遍历,下标+[]都好用,为什么还需要迭代器呢? 为此,使用下标和 [ ] 就足够了,并且确实可以不使用迭代器。 但是其他容器(数据结构)呢?

迭代器的意义在于所有容器都可以使用迭代器来修改访问。 而如果你知道一个容器的迭代器,你几乎就可以知道其他容器的迭代器。 所以对于 ,你必须能够使用迭代器,但是对于 ,一般我们还是喜欢下标+[ ]。

例如:list、map/set,不支持下标+[]遍历

c string头文件_头文件string的作用_头文件string.h的作用

问题2:使用迭代器时,可以将!=改为<或>吗?

答:对于 来说是可以的,因为它的底层是数组,但是对于其他容器来说是不可能的,所以建议使用 != ,不建议使用 > 或

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码