目录
1. 模板初阶
1.1 泛型编程
1.2 函数模板
1.3 类模板
2. auto关键字
3. 范围for的使用(略提)
4. String类部分接口的使用
4.1 String构造函数的使用
4.2 string类begin和end的使用
4.3 string类的rbegin和rend的使用
4.4 string类的size和length的使用
4.5 string类的resize和capacity的使用
4.6 string类的shrink_to_fit的使用
4.7 string类的reserve的使用
4.8 string类[]的使用
4.9 string类的at的使用
4.10 string类的front和back的使用
4.11 string类operator+=、append、push_back的使用
4.12 string类的c_str、find和npos、和rfind的使用
1. 模板初阶
1.1 泛型编程
- 起源: 在平常如果说我们想写Swap来交换两个变量,那么如果说每次都传入不同的变量那么就需要通过重载函数来实现,例如我们要交换两个int变量,那就需要写一个SwapInt()函数,如果要交换两个char变量,那就需要些一个SwapChar()函数,这样太麻烦了,为了解决这个问题C++就提出了泛型编程。
- 泛型编程:泛型编程是一种编写与类型无关的代码,是代码复用的一种手段,例如提供一个图案的模板给你然后你可以印出不同颜色但图案是一样的图形;而模板就是泛型编程的基础如下图所示:
1.2 函数模板
- 函数模板的概念:函数模板就代表了上图的一个图案的模板,你可以传入不同的类型的值来产生特定类型的版本;
- 函数模板的语法:template<class T1, class T2, ......class Tn>或者template<typename T1, typename T2, ....... typename Tn>;class和typename都可以使用。
- 函数模板的原理:函数模板并不是一个函数,它就像一张给编译器创建不同类型函数的蓝图,你需要int就传入int,需要char就传入char,通俗点来说就是让编译器当🐮🐴,不需要程序员当牛马了;如下图所示: 在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应 类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演, 将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
- 函数模板的实例化:用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化 和显式实例化。
- 隐式实例化:传入实参,让推演模板参数的实际类型;如下代码所示:
template<typename T> void Swap(T& left, T& right) { T temp = left; left = right; right = temp; } int main() { //实际上调用的是两个函数 int a = 1, b = 2; cout << "a is >" << a << endl; cout << "b is >" << b << endl; Swap(a, b); cout << "After Swap a is >" << a << endl; cout << "After Swap b is >" << b << endl; double c = 1.6, d = 2.0; cout << "c is >" << c << endl; cout << "d is >" << d << endl; Swap(c, d); cout << "After Swap c is >" << c << endl; cout << "After Swap d is >" << d << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
-
显示实例化:意思就是需要手动给予类型,语法:函数名<类型>(参数);为什么要加入这个语法呢?因为如果说一个函数的参数没有T类型的话那么编译器就无法推导出T的类型,那时候我们就需要显示实例化的使用;如下代码所示:
#include<iostream> using namespace std; template<typename T> T* func(size_t n) { return new T[n]; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; //显示实例化 //意思就是明确给明了T的类型就是<>里面的类型; //必须显示实例化的场景 //这里由于参数没有T的类型,所以我们传参数时 //编译器无法推出T是什么类型 那么这个时候我们就 //需要显示实例化的使用 //func(10); func<int>(10); func<double>(29); return 0; }
-
模板参数的匹配原则:如果说同时出现两个Add,一个是模板,一个是int类型的时候,我们在下面调用它Add(1 , 3);并不会出现两个都调用的情况,而是调用原有的Add函数;这其实就像是你在家里肚子饿了的时候有两个选择,第一个是点外卖(调用已有的Add),第二个是自己做(调用模板),那肯定会选择点外卖,那如果说家长指定(显示实例化)让你要自己做(调用模板),那就得自己去做饭(调用模板);如下代码所示:
//模板参数的匹配机制 #include<iostream> using namespace std; int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } int main() { //有的人就说会不会这两个会有冲突,因为两个都是Add(int left, int right) //但实际上用模板生成的int类型的函数和Add是不一样的 Add(1, 2);//这里肯定是调用Add函数,有现成的不会调用模板 Add<int>(2, 3);//指定<>了的话就会调用模板 Add(1.3, 1.9);//两个同类型的会先调用同类型的template<class T> Add(1, 1.9);//template<class T1, class T2> return 0; }
-
注意:模板生成的int函数和你创建的int的函数不一样,这个需要通过汇编才能看到;如下图所示:call那里调用的是不同的函数
1.3 类模板
- 类模板的定义格式:
template<class T1, class T2, ..., class Tn> class 类模板名 { // 类内成员定义 };
//typedef 和template的区别 #include<iostream> using namespace std; //本来说可以用typedef 来代替类模板,但是其实不是的 //如果说我们想同时创建多个不同类型的类的时候用typedef是 //无法实现的,但是我们可以用template类模板实现,例如 Stack<int> st1; Stack<double> st2; //但是typedef 无法实现 using STDatetype = int; //这个可以平替typedef typedef int STDatetype; //注意Stack是类名 // Stack<int>才是类型 // 类模版 template<typename T> class Stack { public: Stack(size_t capacity = 4) { _array = new T[capacity]; _capacity = capacity; _size = 0; } void Push(const T& data); private: T* _array; size_t _capacity; size_t _size; }; //类模板类外声明需要这样; //声明和定义分离的写法 template<class T> void Stack<T>::Push(const T& data) { } int main() { //这里类模板必须是用显式实例化 //实例化生成对应的类,这里是两个不同类型 Stack<int> st1; Stack<double> st2; return 0; }
- 注意:类模板的实例化,类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
2. auto关键字
- auto是可以根据初始化的值来推导类型,那样的话就代表auto类型在创建变量的时候必须初始化,如果不初始化编译后就不知道他是什么类型;如下代码所示:
void TheUseAuto() { //auto 关键字的使用,auto是可以就是根据初始化的值来推导类型 int a = 10; double b = 3.1415; auto c = a; auto d = b; cout << "c is:>" << c <<endl; cout << "d is:>" << d <<endl; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
-
用auto声明指针类型的时候,用auto和auto*没有任何区别,但用auto声明引用类型的时候必须加&;
-
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
- auto不能直接用来声明数如下代码所示:
// 可以做返回值,但是建议谨慎使用 auto func1() { return 3; } 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; 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 C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型 auto array[] = { 4, 5, 6 }; return 0; }
-
在这里看不出auto的魅力,auto实际上就是一块语法糖;当我们学到string类后,string类中有一个 叫迭代器(iterator)其实就先当做是一个指针,首先先用string类实例化一个对象s1,那每次定义一个迭代器的时候就需要写:---string :: iterator lt1 = s1.begin();但我们可以使用auto来解决这点,直接auto lt1 = s1.begin();
string s3("hello world"); string::iterator lt3 = s3.begin(); cout << "Befor -1, s3 is:> " << s3 << endl; while (lt3 != s3.end()) { //这里是让hello world的ascii码值全部-1 (*lt3)--; lt3++; } cout << "After -1, s3 is:> " << s3 << endl; cout << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
3. 范围for的使用(略提)
直接看代码和里面注释;😊
void TheUseFor()
{
//现在来讲的是For范围的使用
//对于一个有范围的集合而言要是让程序员自己说明循环范围的时候如果出现一些粗心则会发生错误
//为了避免这个情况C++11引入了For范围的使用
//使用语法For(类型: 变量)如下
//而且这也是传入起始位置然后他就会自行遍历
string str("i love sleeping");
for (auto temp : str)
{
cout << temp << " ";
}
cout << endl;
//使用for范围遍历对str2进行修改
string str2("hello world");
for (auto& temp : str2)
{
temp += 2;
}
for (auto temp : str2)
cout << temp << " ";
cout << endl << endl;
//这里说明了其实for循环和for范围是一样的
//但for范围其实也就是一种语法糖,主打一个方便
int a[] = { 1,2,3,4,5,6 };
for (size_t i = 0; i < sizeof(a)/sizeof(int); i++)
{
cout << a[i] << " ";
}
cout << endl;
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4. String类部分接口的使用
4.1 String构造函数的使用
具体链接:string::string - C++ Reference (cplusplus.com)
#include<string>
using namespace std;
int main()
{
//string 类构造使用
//copy
string s1 = "leoweitao1222";
string s2(s1);
cout << "The use of copy:> " << s2 << endl;
//substring(取子串)
string s3 = "Congratulation!!!";
string s4(s3, 4, 6);
cout << "The use of substring:> " << s4 << endl;
//c-string
char arr[] = "klasjdklafj";
string s5(arr);
cout << "The use of c-string:> " << s5 << endl;
return 0;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.2 string类begin和end的使用
- begin这是一个返回首元素地址,需要用迭代器接收 具体链接:string::begin - C++ Reference (cplusplus.com)
//begin
string s1("hello mynameislwt");
//配合迭代器使用
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
it1++;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
- end是一个返回尾部元素地址的,需要用迭代器接收;具体链接:string::end - C++ Reference (cplusplus.com)
string str("Test string");
for (auto it = str.begin(); it != str.end(); ++it)
{
cout << *it << " ";
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.3 string类的rbegin和rend的使用
rbegin和rend就是倒过来的意思,本来begin是返回首元素的迭代器,但是rbegin就是从后面开始算起,颠倒过来了,rbegin的迭代器指向的其实就是end指向的前一个元素;(注意:end指向的是尾部元素的后一个位置)
具体链接:string::rbegin - C++ Reference (cplusplus.com)
//逆序rbegin其实就是尾部,需要通过++到达rend处
string s1("hello leowhytool");
string::reverse_iterator it1 = s1.rbegin();
while (it1 != s1.rend())
{
cout << *it1 << " ";
it1++;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
!!!rend也一样就不重复说了;
4.4 string类的size和length的使用
size 和length的作用效果是一样的,实际上一个是stl里的一个是string里面的,length是string里自带的,但是这个只在物理内存连续的情况下适用例如数组的长度等,但是不能用在树上,因为树没有长度,只有大小;
具体链接:string::size - C++ Reference (cplusplus.com)
//size 和length的作用效果是一样的,实际上一个是stl里的
//一个是string里面的,length是string里自带的,但是这个只在物理内存连续的情况下适用
//例如数组的长度等,但是不能用在树上,因为树没有长度,只有大小;
string s1("hello leowhytool");
cout << s1.size();
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.5 string类的resize和capacity的使用
resize就是修改size的大小,capacity就是返回他那个空间的容量,如果超过capacity的大小的话,那就会进行扩容;注意!:size是返回string的长度;
具体链接:string::resize - C++ Reference (cplusplus.com)
string s1("hello leowhytool12333");
s1.resize(10);
cout << "s1.size is :> " << s1.size() << endl;
cout << s1 << endl;
cout << "s1.capacity is:> " << s1.capacity() << endl;
s1.resize(20, 'a');
cout << "s1.size is :> " << s1.size() << endl;
cout << s1 << endl;
cout << "s1.capacity is:> " << s1.capacity() << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.6 string类的shrink_to_fit的使用
shrink_to_fit就是让capacity的空间适配size的空间,例如capacity开了100个空间,但只使用了10个,size就为10,然后我们使用shrink_to_fit就可以让capacity最大程度接近10,而减少空间浪费;
具体链接:string::shrink_to_fit - C++ Reference (cplusplus.com)
//shiring_to_fit的使用:其实我们给s1增容的时候会多加空间的,具体就看capacity
//和size的大小就可以知道
string s1(100,'q');
cout << "The size of s1 is:> " << s1.size() << endl;
cout << "The capacity of s1 is:> " << s1.capacity() << endl;
//如果我们不用shrink_to_fit的话当我们使用了resize后capacity不会因此而改变
//那么如果说我们resize了20后,capacity还有100个那么就会造成80个空间浪费,那么
//我们就可以使用shrink_to_fit 让 capacity适配size
s1.resize(20);
s1.shrink_to_fit();
cout << "After shrink,the size of s1 is:> " << s1.size() << endl;
cout << "After shrink,the capacity of s1 is:> " << s1.capacity() << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.7 string类的reserve的使用
reserve的中文意思是保留,而在C++里其实就是提前为字符串预留出空间来,可以减少动态申请内存时对性能损耗的情况,这个是当你在知道需要多少空间的时候可以使用,可以不用进行动态申请内存;而一般使用reserve的话,当你要申请100个空间的时候他可能会给你申请111个,这都是正常的;打个比方,当找父母要95块钱时大多数父母都会直接凑整给个100;
string s1("leowhytool");
cout << "The capacity of s1 is:> " << s1.capacity() << endl;
s1.reserve(100);
cout << "After the reserve,the capacity of s1 is:> " << s1.capacity() << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.8 string类[]的使用
string类里已经重载了[],使得我们可以通过[]访问指定的元素,就类似数组通过下标访问指定位置的元素一样,非常方便;
具体链接:string::operator[] - C++ Reference (cplusplus.com)
string s1("leowhytool");
cout << s1[1] << endl;
cout << endl;
for (int i = 0; i < s1.length(); ++i)
{
cout << s1[i];
}
cout << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.9 string类的at的使用
at就是返回指定位置的字母,我们通过at来修改指定位置的字符,因为at的返回值是引用;
具体链接:string::at - C++ Reference (cplusplus.com)
//at的使用,就是取到指定位置的字母,我们可以修改他,因为返回值是引用
string s1("hello, leowhytool");
cout << s1.at(3) << endl;
s1.at(3) = 'q';
cout << s1 << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.10 string类的front和back的使用
front顾名思义就是取string的首位元素,而back就是取string的最后一位元素;我们都可以通过他俩来修改string的首位元素和最后一位元素,因为他俩的返回值类型都是引用;
back具体链接:string::back - C++ Reference (cplusplus.com)
front具体链接:string::front - C++ Reference (cplusplus.com)
//back()的使用:就是取到最后一个,因为返回值是引用所以我们可以对其进行修改
//front就是取最前面字母
string s1("hello,lewowhytool");
cout << s1.back() << endl;
s1.back() = 'e';
cout << s1 << endl;
cout << s1.front() << endl;
s1.front() = 'q';
cout << s1 << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.11 string类operator+=、append、push_back的使用
- operator+=:string类重载了+=,可以很好的实现尾插一串字符串和尾插一个字符;
具体链接:string::operator+= - C++ Reference (cplusplus.com)
//operator+=
string s1("i love ");
s1 += 'a';
cout << s1 << endl;
s1 += " hamburger";
cout << s1 << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
- append:其实和operator+=一样,都是在后面尾插一段字符串;但append的用法很多,具体的看文案使用,这里就演示两种;
具体链接:string::append - C++ Reference (cplusplus.com)
//append 在尾部插入一段
string s1("i love ");
s1.append("hamburger");
cout << s1 << endl;
//这个是插入str2的一部分字符串
string str("i love ");
string str2("hamburger");
cout << str.append(str2, 0,3) << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:;
- push_back尾插操作,这个尾插只是单纯尾插一个字符;
具体链接:string::push_back - C++ Reference (cplusplus.com)
//push_back 尾插操作
string str3("i love ");
str3.push_back('g');
cout << str3 << endl;
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
4.12 string类的c_str、find和npos、和rfind的使用
- c_str的作用是直接返回一个指向字符串的指针
具体链接:string::c_str - C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/string/string/c_str/
string s1("i love hamburger");
const char* str1 = s1.c_str();
for (int i = 0; i < strlen(str1); i++)
{
cout << str1[i] << " ";
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
- find和npos:find和npos一起使用,npos其实就是string的最大容量,但是在编译器中是无法生成这个大小的空间的,所以我们可以通过find和npos的使用达到从头遍历到尾的效果
具体链接:string::npos - C++ Reference (cplusplus.com)
具体链接:string::find - C++ Reference (cplusplus.com)
void TestFind()
{
string s1("qqqqqqqqqqqqqqqqqqqqqqqqq");
cout << "s1's npos is:> " << s1.npos << endl;
string str1 = "i love hamburger, but i dont love apple";
string str2 = "love";
size_t Found = str1.find(str2);
if (Found != str1.npos)
{
cout << "第一次是在:" << Found << "出现" << endl;
}
//这里其实就是让他跳过第一个找到love的l的位置去到o,然后又接着去找后面再次出现love的位置
//所以这里需要Found+1
Found = str1.find(str2, Found + 1);
if (Found != str1.npos)
{
cout << "第二是在:" << Found << "出现" << endl;
}
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
- rfind就是找到最后一次出现的那个字符;
具体链接:string::rfind - C++ Reference (cplusplus.com)
void TestRfind()
{
string s1("我想吃汉堡,我不想吃苹果");
string s2("想");
size_t find = s1.rfind(s2);
if (find != s1.npos)
{
s1.replace(find, s2.size(), " ");
}
cout << s1 << endl;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:;
END!