✨✨小新课堂开课了,欢迎欢迎~✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:C++:由浅入深篇
小新的主页:编程版小新-CSDN博客
1.为什么会有string类
C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列的库函数,但是要实现对字符串的增删查改等操作还是很麻烦的,而且稍不留神可能还会越界访问,所以我们封装了一个string类来处理字符串的各种问题。
2.标准库里的string类
2.1什么是string类
string类的特性:我们可以动态地存储和操作字符串,无需担心内存管理问题,因为它会自动处理字符串的存储和释放,并且支持各种字符串操作,如拼接,比较,查找,替换等。
在使用string类时,必须包含#include头文件以及using namespace std;
下面我们就通过代码的示例了解一些string类的常见用法。
2.2string类的常用接口
1.string类对象的常见构造
void string1()
{
string s1; //构建一个空的string对象
cout << s1 << endl;
string s2("hello world");//直接用常量字符串来构造
cout << s2 << endl;
string s3(5,'x');//用5(n)个字符来构造string对象
cout << s3 << endl;
string s4(s2);
cout << s4 << endl;//拷贝构造
}
运行结果:
2.string类对象的容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间** |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
void string2()
{
string s1("hello world");
//string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
cout << "s1:" << s1 << endl;
cout <<"size:" << s1.size() << endl;
cout << "length:" << s1.length() << endl;
//size和length没有什么区别,只是size更具有通用性,更推荐使用
cout << "capacity:" << s1.capacity() << endl;
//class string
//{
//private:
// char _buff[16];
// char* _str;
//
// size_t _size;
// size_t _capacity;
//};
//capcacity就是我们之前所熟知的容量
//这里补充说明一点:当容量不够时会自动扩容,通常是按1.5倍扩,这个和环境有关
//在VS中,还特地设有buff存储数据当大于buff时就会将数据拷贝给str,这个过程是按2倍扩的,自后的都是按1.5倍扩
cout <<"empty:"<< s1.empty() << endl;//0表示非空,1表示空
s1.clear(); //清空字符串,不改变底层空间大小
cout << "s1:" << s1 << endl;
s1.reserve(100); //制定开一定的空间(一般都会多开)
cout << "capacity:" << s1.capacity() << endl;
s1.resize(5, 'x');
cout << "s1:" << s1 << endl;//j将有效字符的个数改成5(n)个,多出来的空间,用x填充
cout << "size:" << s1.size() << endl;
s1.resize(20, 'k');
cout << "s1:" << s1 << endl;//j将有效字符的个数改成20(n)个,多出来的空间,用k填充
cout << "size:" << s1.size() << endl;
}
运行结果:
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
小扩展:
我们再来扩展一下与capacity相关的小细节,上面我们即提到1.5倍扩,又提到了2倍扩。具体情况是这样的。
buff的空间开的是16个字节,有一个是‘\0’的位置,我们只能存15个有效字符,当我们要储存的数据小于15字符,该字符串会存到buff里面,而不是str里。
当要储存的字符串较大时,就会以buff的容量大小为基础进行扩容,首次是扩容2倍扩,如果空间还是不够,就会1.5倍的进行扩容直至开够足够的空间。
3. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位 置的迭代器 |
rbegin + rend | rbegin获取容器最后一个字符 + rend指向容器“反向的开头”,在实际的容器范围之外,它在第一个元素之前。 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
void string3()
{
string s1("hello world");
//三种遍历方法
//1.下表+[]
//2.迭代器(正向迭代器 反向迭代器)
//3.范围for(看起来很高级,底层还是用迭代器实现的)
//1.下表+[]
for (int i = 0; i < s1.size(); i++)
{
//s[i]+=2;
cout << s1[i] << ' ';
}
//[]能直接访问,修改字符串的内容,把他当成我们所熟知的[]使用即可。
cout << endl;
//2.正向迭代器
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
//3.反向迭代器
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << ' ';
rit++;
}
cout << endl;
//范围for-自动赋值,自动迭代,自动判断结束
for (auto& ch : s1)
{
cout << ch << ' ';
}
cout << endl;
}
运行结果:
在这里补充2个C++11的小语法。
auto关键字
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。(auto的功能简而言之就是能够自动推导出类型)
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&,当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组
int func1()
{
return 10;
}
//不能做参数
//void func2(auto a)
//{} error
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
void test()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
//auto e; 推断不出来e的类型 error
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(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
//auto cc = 3, dd = 4.0; error
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
//auto array[] = { 4, 5, 6 }; error
}
运行结果:
使用场景举例:
auto在一个变量的类型名很长的时候,就发挥了很大的作用,很便捷,但是也有弊端。
auto自动推断类型
map<string, string> dict;
map<string, string>::iterator mit = dict.begin();
auto mit = dict.begin();//自动推导出mit的类型
使用起来更加便捷,但是代码的可读性会降低
范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历,范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
void test2()
{
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]<<" ";
}
cout << endl;
// C++11的遍历
//自动迭代,自动取数据,自动判断结束
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " " ;
cout << endl;
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
}
运行结果:
4.string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
void string4()
{
string s1("hello world");
s1.push_back('x');
cout << "push_back后:";
cout << s1 << endl; //尾插一个字符c
s1.append("abc");
cout << "append后:";
cout << s1 << endl;//尾插一个字符串
s1.operator+=("hello");
cout << "operator+=后:";
cout << s1<<endl;//尾插一个字符串
cout << "c_str:";
const char* str = s1.c_str();
cout << str << endl;//返回C格式字符串
//c_str主要是用来将string对象转化为C风格的字符串指针,用于传给const char*类型参数的函数
cout << "find:";
//size_t find (char c, size_t pos = 0) const;
int num1 = s1.find('e');
cout << num1 << endl;//pos位置开始往后找字符c,返回该字符在字符串中的位置(正着找)
cout << "rfind:";
//ize_t rfind (char c, size_t pos = npos) const;
//npos static const size_t npos = -1;
//his constant is defined with a value of - 1, which because size_t is an unsigned integral type,
// it is the largest possible representable value for this type.
//总结:npos是一个很大数,该类型的最大值
int num2 = s1.rfind('e');
cout << num2 << endl;//pos位置开始往前找字符c,返回该字符在字符串中的位置(倒着找)
cout << "s1:" << s1 << endl;
cout << "substr:";
//string substr (size_t pos = 0, size_t len = npos) const;
string tmp = s1.substr(2, 5);//从pos位置开始,截取n和字符,然后返回
cout << tmp << endl;
}
运行结果:
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5.string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
void string5()
{
string s1("hello world");
string s2("xiaoxin");
//string operator+ (const string & lhs, const string & rhs);
cout << "operator+:";
cout << operator+(s1, s2) << endl;//涉及深拷贝,效率低下,尽量少用
cout << "operator+>>";//输入运算符重载
string s3;
operator>>(cin, s3);
cout << s3 << endl;
cout << "operator<<";
operator<<(cout,s1) << endl;
//getline 获取一行字符
//getline与比如getchar,scanf之类的区别在于,后者的两个一般都是以空格,换行符分界
//如果输入带空格的字符串,后者就会将其当成两个字符串,从而读不到空格
//getline就能读到中间的空格,将其当成一个字符串
//istream& getline(istream & is, string & str, char delim);
// 第一种,delim默认是遇到换行符截止
//istream& getline(istream & is, string & str);
//这一种是遇到str截止
//relational operators
//这个函数库里包含了各种比较大小的函数,函数类型为bool类型
//知道是这样的就可以
std::string foo = "alpha";
std::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";
}
运行结果:
3.总结
以上介绍的都是string类的主要接口,还有一些string的接口不常用,感兴趣的可以可以自己学一下,下面是我自己整理的,还比较全面,希望有所帮助。下一篇我们就来介绍string类的模拟实现。
感谢各位的观看~
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//class string
//{
//private:
// char _buff[16];
// char* _str;
// size_t size;
// size_t capacity;
//
//};
//构造
void string1()
{
string s1;
string s2("hello world");
string s3(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s2 << endl;
//string (const string& str, size_t pos, size_t len = npos);
//有多少拷贝多少
string s4(s2, 6, 15);
cout << s4 << endl;
//npos是有缺省值的,可以不传
string s5(s2, 6);
cout << s5 << endl;
//拷贝前n个字符
string s6("hello world", 5);
cout << s6 << endl;
string s7(10, 'x');
cout << s7 << endl;
}
//三种遍历方式
void string2()
{
string s1("hello world");
//小标+[]
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << ' ';
}
cout << endl;
//迭代器
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
//范围for-自动赋值,自动迭代,自动判断结束
//底层其实就是迭代器
for (auto& ch : s1)
{
cout << ch << ' ';
}
cout << endl;
//auto自动推断类型
//map<string, string> dict;
//map<string, string>::iterator mit = dict.begin();
//auto mit = dict.begin();
//使用起来更加便捷,但是代码的可读性会降低
}
//迭代器
void string3()
{
//iterator begin();
string s2("hello world");
string::iterator it = s2.begin();
while (it != s2.end())
{
*it += 2;
cout << *it << ' ';
it++;
}
cout << endl;
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << ' ';
rit++;
}
cout << endl;
//const_iterator begin() const;
const string s3("hello world");
string::const_iterator cit = s3.begin();
while (cit != s3.end())
{
//*cit += 2;
cout << *cit << " ";
++cit;
}
cout << endl;
string::const_reverse_iterator rcit= s3.rbegin();
while (rcit != s3.rend())
{
// *rcit += 2;
cout << *rcit << " ";
++rcit;
}
cout << endl;
//只读不写
//const_iterator cbegin() const noexcept;
string::const_iterator csit = s3.cbegin();
while (csit != s3.cend())
{
cout << *csit << " ";
++csit;
}
cout << endl;
string::const_reverse_iterator csrit = s3.crbegin();
while (csrit != s3.crend())
{
cout << *csrit << " ";
++csrit;
}
cout << endl;
//begin()返回一个普通的迭代器,可读可写;cbegin()返回一个常量迭代器
//只读不写
}
void TestPushBack()
{
// reverse 反转 逆置
// reserve 保留、预留
string s;
// 提前开空间,避免扩容,提高效率
s.reserve(10);
size_t sz = s.capacity();//15
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
//31 47 70 105
//刚开始是二倍扩,后面大概是1.5倍扩
//因为刚开始是有个buff存在的,小于buff空间大小就存在buff里面
//当大于buff的容量时,就会存到_str里面,地方发生转变的这次是2倍扩,后面在堆上的时候就
//大概是1.5倍扩了
}
void string4()
{
string s2("hello world");
//size和length没有区别,只是size更具有通用性,更推荐使用
cout << s2.size() << endl;
cout << s2.length() << endl;
//都不包含\0
cout << s2.max_size() << endl;
cout << s2.capacity() << endl;
//resize:将字符串的长度调整为n个字符
//void resize(size_t n);
//void resize(size_t n, char c);
s2.resize(5);
//多的部分用X填充
s2.resize(12, 'x');
cout << s2 << endl;
string s1("hello worldxxxxxxxxxxxxx");
cout << s1.size() << endl;//24
//不够会扩容,一般是以1.5倍进行扩容
cout << s1.capacity() << endl << endl;//31
//在VS下一般不会缩容,但是不同的平台不一样,例如g++就会缩容
s1.reserve(20);
cout << s1.size() << endl;//24
cout << s1.capacity() << endl << endl;//31
//这里就没有缩容
//缩容也有规定,缩容不会缩的比size小
s1.reserve(28);
cout << s1.size() << endl;//24
cout << s1.capacity() << endl << endl;//31
//扩容
s1.reserve(40);
cout << s1.size() << endl;//24
cout << s1.capacity() << endl << endl;//47
s1.clear();//清空字符串,但是容量不变
cout << s1.size() << endl;//0
cout << s1.capacity() << endl << endl;//47
//判断字符串是否为空
std::string str1 = "";
if (str1.empty()) {
std::cout << "字符串为空" << std::endl;
}
else {
std::cout << "字符串不为空" << std::endl;
}
//std::string::shrink_to_fit
//与reserve相比,这个更有约束力一些,该函数会请求减少string占用的空间
//以适用于当前的内容大小,但是也不一定
string s4("hello");
s4.shrink_to_fit();
cout << s4.size() << endl;//5
cout << s4.capacity() << endl << endl;//15
//原本大小就小于15的,这里就没有缩容
std::string str(100, 'x');
std::cout << "1. capacity of str: " << str.capacity() << '\n';//111
str.resize(10);
std::cout << "2. capacity of str: " << str.capacity() << '\n';//111
str.shrink_to_fit();
std::cout << "3. capacity of str: " << str.capacity() << '\n';//15
//这里就缩容了
}
//元素访问
void string5()
{
//operator[]和at用法上没有区别
//oparator[]里面是断言,越界会报错;但是at越界会抛异常,抛out_of_range的异常
//back()函数用于获取字符串的最后一个字符
//front用于获取字符串的第一个字符
}
//调节器modifier
void string6()
{
//operator+=
string s("hello world");
s.operator+=('x');
s.operator+=("hxxxxxxx");
s.operator+=(s);
s += 'y';
s += "3333333";
s += s;
cout << s << endl;
cout << endl << endl;
//append
//append除了和operator+=上面相同的用法外,还有下面的用法
//1.string& append (const string& str, size_t subpos, size_t sublen);
//用于将一个字符串str,从指定的subpos位置开始,长度为sublen追加到当前字符串的末尾
string s1("hello world");
s1.append(s1, 2, 4);
cout << s1 << endl;
//string& append (const char* s, size_t n);
s1.append("xxxxx", 3);
cout << s1 << endl;
//string& append (size_t n, char c);
s1.append(3,'h');
cout << s1 << endl;
//template <class InputIterator>
//string& append(InputIterator first, InputIterator last);
std::vector<char> vec = { 'w','r','o','l','d' };
s1.append(vec.begin(), vec.end());
cout << s1 << endl;
//补充一点:first和last不一定就是起始位置和终止位置;也可以是自己指定位置
//但是string类里只给了begin和end,这就需要自定义迭代器,现在我还不会
//void push_back (char c);
s1.push_back('l');
//assign - 用来给字符串赋予新的值
//string& assign(const string & str);
string s3("hello world");
s3.assign(s1);
cout << s3 << endl;
//string& assign(const string & str, size_t subpos, size_t sublen);
s3.assign(s1, 5, 7);
cout << s3 << endl;
//string & assign(const char* s);
s3.assign("xxxxx");
cout << s3 << endl;//xxxxx
//string& assign(const char* s, size_t n);
s3.assign("hello world", 5);
cout << s3 << endl;
//string& assign(size_t n, char c);
s3.assign(5, 's');
cout << s3 << endl;
//template <class InputIterator>
//string& assign(InputIterator first, InputIterator last);
std::vector<char> charvec = { 'h','e','l','l','o' };
s3.assign(charvec.begin(), charvec.end());
cout << s3 << endl;
//insert
//用法总结:在指定位置插入单个字符、字符串,另一个字符串的一部分
//使用迭代器进行插入操作
//replace
//用于替换字符串里的一部分内容
//erase
//string& erase (size_t pos = 0, size_t len = npos);
//有缺省值可以不给
s3.erase(0, 3);//从第几个位置删除几个字符
cout << s3 << endl;
s3.erase(s3.begin());//删除第一个字符
cout << s3 << endl;
s3.erase(--s3.end());//尾删
cout << s3 << endl;
//end()并不是指向最后一个字符,而是最后一个字符的下一个位置
//swap
//void swap(string & str);
s3.swap(s1);//交换s1与s3的内容
cout << s3 << endl;
cout << s1 << endl;
//pop_back
//删除最后一个字符
}
void string7()
{
//c_str-返回字符串的指针
//data也是返回一个字符串的指针
//copy和substr都可用于拷贝字符或者字符串
//size_t copy (char* s, size_t len, size_t pos = 0) const;
//copy是将string里的内容拷贝到外部的字符串数组s中;
// string substr (size_t pos = 0, size_t len = npos) const;
//而substr是将string里提取子字符串返回一个新的string对象
//find是找一个字符,字符串,string对象;rfind是倒着找
// 找到返回索引;找不到返回string::npos
//find_first_of
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::cout << str << '\n';
std::size_t found = str.find_first_of("abcdef");//找到任意一个都返回索引
while (found != std::string::npos)
{
str[found] = '*';
found = str.find_first_not_of("abcdef", found + 1);
}
//find_last_of是倒着找
//find_first_not_of是找不到返回索引;find_last_not_of是倒着找,找不到返回索引
//cpmpare不参与,用的比较多的运算符重载
//get_allocator还不会
}
void string8()
{
//oparator+ 用于字符串的拼接
// 可以连接两个string对象;string对象和字符串字面量;字符串字面量和string对象
//这里解释一下要实现字符串字符串字面量+string对象的底层;该函数不是成员函数,或者将其设为全局函数
//举个例子,双目操作符的左操作数默认为类对象,字符串字面量+string对象这个就行不通
string s1("hello");
string s2 = s1 + "world";
cout << s2 << endl;
string s3 = "world" + s1;
cout << s3 << endl;
//swap
//这个和上面的一个的使用方式不同,两个参数
//void swap (string& x, string& y);
//getline
//getline与比如getchar,scanf之类的区别在于,后者的两个一般都是以空格,换行符分界
//如果输入带空格的字符串,后者就会将其当成两个字符串,从而读不到空格
//getline就能读到中间的空格,将其当成一个字符串
//istream& getline(istream & is, string & str, char delim);
// 第一种,delim默认是遇到换行符截止
//istream& getline(istream & is, string & str);
//这一种是遇到str截止
}
int main()
{
string8();
//TestPushBack();
return 0;
}
///
// 范围for和auto的扩展
//auto不能做参数,但是可以做返回值,不过不建议使用
//范围for适用于容器和数组
//int func1()
//{
// return 10;
//}
//
不能做参数
void func0(auto a = 0)
{}
//但是能做返回值,不过不建议使用
//auto func2()
//{
// //...
// return func1();
//}
//
//auto func3()
//{
// //...
// return func2();
//}
//int main()
//{
// int a = 10;
// 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;
//
// auto不能用于数组
// //auto array[] = { 4, 5, 6 };
//
// auto ret = func3();
//
// 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;
// }
//
// // yyds
// // 范围for适用于容器 和 数组
// // C++11的遍历
// for (auto& e : array)
// e *= 2;
//
// for (auto e : array)
// cout << e << " " << endl;
//
// return 0;
//}