目录

一.auto和范围for

auto

范围for

二.string类的常用接口介绍

1.成员函数(Member functions)

a.构造(constructor)

b.析构(destructor)

c.赋值运算符重载(operator=)

 2.迭代器(iterators)

a.begin和end

b.rbegin和rend

3.容量(capacity)

a.size

b.capacity

c.rsize

d.reserve

e.clear

4.元素访问(element access)

a.oparetor[ ]

5.修改(modifiers)

a.operator+=

b.append

c.push_back

d.insert

e.erase

f.replace

6.字符串操作(string operations)

a.c_str

b.find

c.substr

7.非成员功能重载(non-member function overloads)

a.operator+

b.relation operators

c.operator<<

d.operator>>

e.getline


一.auto和范围for

在了解string类之前,我们先以学会auto和范围for的使用作为铺垫。

auto

1.在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

2.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。

3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

4.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用。

5.auto不能直接用来声明数组。

int func1()
{
	return 10;
}

int main()
{
	int a = 0;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
	//auto e;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	int x = 10;
	auto y = &x;
	auto* z = &x;
	auto& m = x;
	cout << typeid(y).name() << endl;
	cout << typeid(z).name() << endl;
	cout << typeid(m).name() << endl;
	auto aa = 1, bb = 2;
	cout << typeid(aa).name() << endl;
	cout << typeid(bb).name() << endl;
	// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
	//auto cc = 3, dd = 4.0;
	// 
	// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
	//auto array[] = { 4, 5, 6 };
	return 0;
}

范围for

1.对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号”:”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被选代的范围,自动迭代,自动取数据,自动判断结束。

2.范围for可以作用到数组和容器对象上进行遍历。

3.范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include<iostream>
#include <string>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << endl;
	}
	// C++11的遍历
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " " << endl;
	string str("hello world");
	for (auto ch : str)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

范围for语句冒号之后的值会自动赋值给前面的变量,所以冒号前面的值的改变不会改变要遍历的容器的值。当然如果想要改变所要遍历的值,也有办法,就是在auto后面加一个引用就可以对其进行改变。

二.string类的常用接口介绍

1.成员函数(Member functions)

a.构造(constructor)

在C++98中一共提供了7个接口。

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("hello world");
	string s2(s1);
	string s3(s1, 1, 4);
	string s4("hehe");
	string s5("hehe", 2);
	string s6(10, 'x');
	string s7(s1.begin(), s1.end());
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
	return 0;
}

 在substring接口中,参数len的缺省值是npos。

那么npos是什么意思呢?

参考文档,我们可以知道npos是size_t的最大值。

npos是一个静态成员常量值,对于 size_t 类型的元素具有最大可能的值。

当此值用作字符串成员函数中 len (或 sublen) 参数的值时,表示"直到字符串的未尾”。作为返回值,它通常用于指示没有匹配项。

此常量定义为值 -1,因为 size_t是无符号整型,所以它是此类型的最大可能可表示值。

b.析构(destructor)

c.赋值运算符重载(operator=)

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1, s2, s3;
	string s0("hello world");
	s1 = s0;
	s2 = "hehe";
	s3 = '#';
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	return 0;
}

 2.迭代器(iterators)

a.begin和end

这是正向迭代器,有不含const和含const的两种用法,不含const可以对string实例化出来的对象进行改变,而含const则就只能对对象进行遍历。

begin返回一个指向字符串的第一个字符的位置,end返回字符串最后一个字符后一个字符的位置。

具体用法:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("01234");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;
	it = s1.begin();
	while (it != s1.end())
	{
		*it += 5;
		cout << *it;
		it++;
	}
	cout << endl;
	return 0;
}
b.rbegin和rend

这是反向迭代器,同样有不含const和含const的两种用法,不含const可以对string实例化出来的对象进行改变,而含const则就只能对对象进行遍历。

begin返回字符串中最后一个字符的位置,end返回字符串中第一个字符的前一个字符的位置。

具体用法:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("01234");
	string::reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
		cout << *it;
		it++;
	}
	cout << endl;
	it = s1.rbegin();
	while (it != s1.rend())
	{
		*it += 5;
		cout << *it;
		it++;
	}
	cout << endl;
	return 0;
}

3.容量(capacity)

a.size

返回字符串的长度

b.capacity

返回字符串的容量空间

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("hello");
	cout << "s1.size:" << s1.size() << endl;
	cout << "s1.capacity:" << s1.capacity() << endl;
	cout << "s1实例化是所开辟的空间的大小:" << sizeof(s1) << endl;
	string s2;
	int old = s2.capacity();
	cout << endl;
	cout << "string的扩容:" << endl;
	for (int i = 0; i < 100; i++)
	{
		s2 += 'x';
		if (old != s2.capacity())
		{
			old = s2.capacity();
			cout << s2.capacity() << endl;
		}
	}
	return 0;
}

观察上面代码的扩容的问题,我们发现在VS编译器中除了第一次扩容是以二倍扩容以外,之后的步骤都是以差不多1.5倍扩容的,这是VS编译器独有的。

同时,我们还可以观察到,s2字符串就算没有给任何的初始值,编译器也会自动给s2字符串分配15个字节的空间,这是为什么呢?

其实,编译器为了做一些优化,会在string类成员变量里面多加一个16字节的buff数组,

如果字符串的大小是小于16个字节的话,它就会自动存在buff数组里面,同时,_str指针会指向一个空地址。

如果字符串的大小大于16个字节的话,他就会让_str的字符指针指向字符串在堆上所开辟的空间,同时使buff数组失效。

这样的设计就避免了在刚开始初始化字符串的时候,字符串大小太小而需要多次扩容导致的扩容效变低。

我们可以在调试的时候观察到buff数组的存储情况。

在VS监视窗口中观察s1字符串在原始视图的下面会有一个_buf的数组,我们还可以看到数组的类型是一个16字节的字符数组。

我们还要注意s1实例化所开辟空间的大小是28个字节,这又是为什么呢?

还是与buff数组有关系,在x86的环境中,指针的大小为4个字节,也就是_str指针的大小为4个字节。_size和_capacity两个整形的大小都为4个字节。再加上buff数组的16个字节的大小正好就是28个字节。

c.rsize

 resize可以改变字符串的size值,如果n小于当前的字符串长度,size值会缩短至第一个n字符,然后移除掉第n个字符之后的内容。

如果n大于当前的字符串长度,当前的内容会直接延伸到第n个字符的位置,同时将c的值填充进去,如果没有指定c的值,会填充初始化值的字符,也就是'\0'。

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("hello world");
	cout << s1 << endl;
	cout << s1.size() << endl;
	s1.resize(20, '#');
	cout << s1 << endl;
	cout << s1.size() << endl;
	return 0;
}

d.reserve

 reserve起到了一个预留容量的作用。如果一个字符串的长度比较长,他就会不断的去扩容,这样就会造成开辟一块新的空间,在将原来空间的值拷贝给新的空间,最后再将原来的空间给释放掉,就会造成不必要的消耗。

reserve就可以解决这个问题。如果我们提前就知道要开辟的字符串的大小,那么我们就可以提前将字符串的大小预留出来,这样就减少了扩容的次数。

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1;
	s1.reserve(200);
	cout << s1.capacity() << endl;
	return 0;
}

假如说 ,我们想要开辟一块200字节的空间,reserve就会为我们开辟一块大于等于200字节的空间。

e.clear

消除掉字符串中的内容,并将size置为0. 

4.元素访问(element access)

a.oparetor[ ]

下标访问运算符的重载,分为带const(不能修改)以及不带const(可以修改)的两种接口。使用这个重载后的运算符可以直接访问字符串中指定下标的元素。

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string str("hello world");
    for (int i = 0; i < str.length(); ++i)
    {
        std::cout << str[i];
    }
    return 0;
}

5.修改(modifiers)

a.operator+=

在字符串的后面增加内容,参数可以是string类型的对象,字符指针的变量以及字符。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string name("John");
	string family("Smith");
	name += " K. ";         // c-string
	name += family;         // string
	name += '\n';           // character

	cout << name;
	return 0;
}

 

b.append

同样是一个尾插字符串的功能,支持迭代器。 

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string str;
	string str2 = "Writing ";
	string str3 = "print 10 and then 5 more";

	// used in the same order as described above:
	str.append(str2);                       // "Writing "
	str.append(str3, 6, 3);                   // "10 "
	str.append("dots are cool", 5);          // "dots "
	str.append("here: ");                   // "here: "
	str.append(10, '.');                    // ".........."
	str.append(str3.begin() + 8, str3.end());  // " and then 5 more"

	cout << str << '\n';
	return 0;
}

 

c.push_back

 尾插一个字符。

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	s1.push_back('#');
	cout << s1 << endl;
	return 0;
}
d.insert

在指定位置插入字符串。 

e.erase

删除指定位置的字符串。 

f.replace

在一个字符串中的指定位置用另一个字符串取代其内容。 不建议多用,因为涉及到挪动数据,造成效率变低。

6.字符串操作(string operations)

a.c_str

返回成员变量c_str的地址。 

这里的c_str的作用是为了能和C语言保持兼容。

例如我们想用C语言中的文件操作,fopen接口的第一个参数就是一个字符指针类型。那么这时候我们想要将string类中的_str成员变量传进去就需要用到c_str()这个接口。

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1 = ("test.cpp");

	FILE* pf = fopen(s1.c_str(), "r");
	char ch = fgetc(pf);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(pf);
	}
	return 0;
}

 

b.find

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world dlx abcdefg");
	cout << s1 << endl;
	size_t i = s1.find(" ");
	while (i != string::npos)
	{
		s1.replace(i, 1, "##");
		i = s1.find(" ", i);
	}
	cout << s1 << endl;
	return 0;
}

 

c.substr

在str中从pos位置开始,截取n个字符,然后将其返回。

7.非成员功能重载(non-member function overloads)

a.operator+

参考operator+=,这里参数里的string不会改变,因为是传值传参。尽量少用,因为传值返回,导致深拷贝效率低。 

b.relation operators

依次比较两个字符串的ASCII值、

int main()
{
	string foo = "alpha";
	string bar = "beta";

	if (foo == bar) std::cout << "foo and bar are equal\n";
	if (foo != bar) std::cout << "foo and bar are not equal\n";
	if (foo < bar) std::cout << "foo is less than bar\n";
	if (foo > bar) std::cout << "foo is greater than bar\n";
	if (foo <= bar) std::cout << "foo is less than or equal to bar\n";
	if (foo >= bar) std::cout << "foo is greater than or equal to bar\n";

	return 0;
}
c.operator<<

d.operator>>

e.getline

从 is 中提取字符并将其存储到 str 中,直到找到分隔符 delim(或换行符 '\n',对于 (2))。

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1;
	getline(cin, s1,'#');
	cout << s1 << endl;

	return 0;
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部