模板初阶以及string类使用
- 模板的简单认识
- 1.泛型编程
- 2.函数模板
- 模板的原理图
- 函数模板格式
- 函数模板实例化
- 非模板函数和模板函数的匹配原则
- 3.类模板
- 类模板的定义格式
- 类模板的实例化
- string
- 1.string简介
- 2.string常用的接口
- 题目练习
- 1.字符串相加
- 2.字符串里面最后一个单词的长度
- 3.翻转字符串区间
模板的简单认识
1.泛型编程
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
实现加法:
//不使用模板,只使用函数重载,每一个需要的类型都需要写一份函数
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
int main()
{
int a1 = 0;
int b1 = 0;
int c1 = add(a1, b1);
double a2 = 0.1;
double b2 = 0.5;
double c2 = add(a2, b2);
return 0;
}
两个函数只有类型上的不同,逻辑完全一致,能不能写一份表示逻辑的模板,让编译器帮我们生成函数呢?
2.函数模板
模板的原理图
函数模板格式
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
用函数模板来实现加法:
//模板不是具体的函数,真正的函数是由编译器去生成的
template<typename T>
T add(T a,T b)
{
return a + b;
}
//typename是用来定义模板参数的关键字,也可以用class
int main()
{
int a1 = 0;
int b1 = 0;
int c1 = add(a1, b1);
double a2 = 0.1;
double b2 = 0.5;
double c2 = add(a2, b2);
return 0;
}
函数模板实例化
- 隐式实例化:由编译器依据实参来自行生成。
template<typename T>
T add(T a,T b)
{
return a + b;
}
int main()
{
int a1 = 0;
int b1 = 0;
int c1 = add(a1, b1);//编译器生成int add(int a, int b);
double a2 = 0.1;
double b2 = 0.5;
double c2 = add(a2, b2);//编译器生成double add(double a, double b);
return 0;
}
- 显示实例化:在函数名后的<>中指定模板参数的实际类型
下面这个例子不显示实例化就会报错:
template<typename T>
T add(T a,T b)
{
return a + b;
}
int main()
{
int a1 = 0;
double b1 = 0.5;
int c1 = add(a1, b1);
//模板中两个参数类型是一致的,这里一个int,一个double,没有明确指定编译器无法生成函数
return 0;
}
利用显示实例化解决:
template<typename T>
T add(T a,T b)
{
return a + b;
}
int main()
{
int a1 = 0;
double b1 = 0.5;
double c1 = add<double>(a1, b1);
//指定生成double add(double a,double b)并调用,和正常函数一样这里会进行隐式类型转换
return 0;
}
非模板函数和模板函数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//专门处理int
int add(int a, int b)
{
return a + b;
}
//通用的函数模板
template<typename T>
T add(T a,T b)
{
return a + b;
}
int main()
{
int a1 = 0;
int b1 = 5;
int c = add(a1, b1);//与非模板函数完全匹配,编译器不会生成
c = add<int>(a1, b1); //指定编译器生成并调用
//两个函数同时存在不冲突
return 0;
}
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
//专门处理int
int add(int a, int b)
{
return a + b;
}
// 通用加法函数
template<class T1, class T2>
T1 add(T1 left, T2 right)
{
return left + right;
}
int main()
{
add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
return 0;
}
3.类模板
类模板的定义格式
template<class T1, class T2, …, class Tn>
class 类模板名
{
// 类内成员定义
};
//和函数模板一样,这里并不是真正的类
template<class T>
class A
{
public:
A(T a)
:_a(a)
{}
//演示一下定义声明分离
T get();
private:
T _a;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
T A<T>::get()
{
return _a;
}
类模板的实例化
类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
int main()
{
//A是类名,A<int>才是类型
A<int> a(5);
A<double> b(5.22);
return 0;
}
string
1.string简介
- string是表示字符串的字符串类
- string类是basic_string模板类的一个实例,即typedef basic_string<char> string
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string是一个顺序容器,会自动扩容。
2.string常用的接口
string类中有一个静态成员npos:static const size_t npos = -1;
本文只讲使用,不讲底层原理
- string类对象的常见构造
函数名称 | 功能说明 |
---|---|
string() (常用) | 构造空的string类对象,即空字符串 |
string(const char* s) (常用) | 用C字符串来构造对象 |
string(size_t n, char c) | string类对象中前n个字符初始化为C |
string(const string&s) (常用) | 拷贝构造 |
int main()
{
string s1; // 构造空的string类对象s1
string s2("hello"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
return 0;
}
- string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size_t size() (常用) | 返回有效字符数('\0’不算有效字符) |
size_t length() | 和size()功能一致 |
size_t capacity() | 返回可存储有效字符的空间总大小,'\0’不算 |
bool empty() (常用) | 检查是否为空串,是返回true,否则返回false |
void clear() (常用) | 清空字符串,变成空串 |
void reserve (size_t n = 0) (常用) | 预留空间,n大于容量时扩容,不然什么都不做 |
void resize (size_t n, char c) | n>有效字符数时从从字符串尾开始填充c直到字符串满;n>容量就先扩容再填充;n<有效字符数时缩短字符串,保留前n个字符。 |
int main()
{
string s2("hello");
cout << s2.empty() << endl;
s2.clear();//调用clear清理
cout << s2.empty() << endl;
cout << "capacity:" << s2.capacity() << endl;
s2.reserve(100);//调用reserve预留空间
cout << "reserve_capacity:" << s2.capacity() << endl;
s2.resize(50, 'c');//填充
cout << "s2:" << s2 << endl;
s2.resize(10);//缩短到10字符
cout << "s2:" << s2 << endl;
return 0;
}
- string类对象的访问及遍历操作
函数名称 | 功能说明 |
---|---|
(const)char& operator () | 运算符重载,可以像数组一样通过下标访问字符 |
(const_)iterator begin()和end() | begin取第一个字符位置的迭代器,end取最后一个字符的下一个位置的迭代器(大家可以把迭代器想成指针,后面使用大家就明白了) |
(const_)reverse_iterator rbegin()和rend() | 反向迭代器,使用和begin()一致,方向相反 |
范围for | C++11支持,底层是迭代器 |
int main()
{
string s2("hello");
//利用[]运算符重载遍历
for (int i = 0; i < s2.size(); i++)
cout << s2[i];
cout << endl;
//利用迭代器进行遍历,利用auto自动推导类型
//这里是std::string::iterator
auto it = s2.begin();
while (it != s2.end())
{
cout << *it;
it++;
}
cout << endl;
//反向迭代器遍历
//这里是std::string::reverse_iterator
auto rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit ;
rit++;
}
cout << endl;
//范围for遍历
for (auto ch : s2)
cout << ch ;
cout << endl;
//修改
for (auto& ch : s2)
cout << ch ;
return 0;
}
- string类对象的修改操作
函数名称 | 功能说明 |
---|---|
void push_back(char c) | 尾插一个字符c |
string& append (const char* s) | 尾插一个字符串 |
string& operator+=() (常用) | 既可尾插字符串,也可单个字符,非常好用 |
const char* c_str() (常用) | 返回C格式字符串首地址 |
size_t find (char c,size_t pos = 0) (常用) | 从pos位置开始寻找第一个出现c的位置并返回,否则返回npos |
size_t rfind (char c,size_t pos = npos) | 从pos位置开始向前寻找第一次出现c的位置并返回,否则返回npos;当pos大于等于长度时,从尾开始向前搜索。(一般就是用这个函数搜索单个字符最后出现的位置) |
string substr (size_t pos = 0, size_t len = npos) | 从pos位置开始截取len个字符,如果截取长度大于后面长度,就截取到字符串尾部结束,然后返回 |
1.string& insert (size_t pos, size_t n, char c) 2.string& insert (size_t pos, const char* s) | 1.在pos位置插入n个c 2.在pos位置插入C格式字符串 |
int main()
{
string s2("hello ");
s2.push_back('w');
s2.append("or");
s2 += 'l';
s2 += "dd!";
cout << s2.c_str() << endl;
cout << "pos(find):" << s2.find('w') << endl;
cout << "pos(rfind):" << s2.rfind('d') << endl;
if (s2.find('k') == std::string::npos)
cout << "没找到" << endl;
string sub = s2.substr(6, 6);
cout << sub.c_str() << endl;
//这里相当于头插'c'
s2.insert(0,1,'c');
s2.insert(2,"hello ");
return 0;
}
- string类非成员函数
函数 | 功能说明 |
---|---|
string operator+() | 先拷贝一份,实现尾插后返回,不改变原对象 |
operartor>>() (常用) | 输入运算符重载 |
operator<<() (常用) | 输出运算符重载 |
istream& getline (istream& is, string& str) | 读取一行字符,遇到’\n’才结束 |
bool operator>() (<,<=,>=,==,!=) | 进行字符串比较 |
int main()
{
string s2("hello ");
string s3 = s2 + "world";
cout << "s3:" << s3 << endl;
cin >> s3;
cout << "s3:" << s3 << endl;
getchar();
getline(cin, s3);//可以读取空格
cout << "s3:" << s3 << endl;
string s("hello");
string s1("hellO");
if (s == s1)
cout << "s == s1" << endl;
else if (s > s1)
cout << "s > s1" << endl;
else if (s < s1)
cout << "s < s1" << endl;
return 0;
}
题目练习
1.字符串相加
链接 :字符串相加
题目要求:
题解:
class Solution {
public:
string addStrings(string num1, string num2)
{
//用来遍历
int end1 = num1.size()-1;
int end2 = num2.size()-1;
//用来记录进位
int flag = 0;
int sum = 0;
string str;
while(end1 >= 0 || end2 >= 0)
{
//只有不越界的情况才给值,否则给0
int x1 = end1>=0?num1[end1]-'0':0;
int x2 = end2>=0?num2[end2]-'0':0;
sum = flag + x1 + x2;
//这里使用尾插,头插效率比较慢
str += ('0'+sum%10);
flag = sum/10;
end1--;
end2--;
}
//还需要处理一种特殊情况,比如"9"+"1",同时结束刚好进位,需要补1
if(flag == 1)
str += '1';
//数据是尾插的,需要逆置
//reverse属于stl中的算法
//只需要传入迭代器区间就能实现逆置
reverse(str.begin(),str.end());
return str;
}
};
2.字符串里面最后一个单词的长度
链接:字符串里面最后一个单词的长度
题目要求:
题解:
#include <iostream>
#include<string>
using namespace std;
int main()
{
string str;
getline(cin,str);
//直接调用rfind找最后一个空格的位置
//没有找到说明只有一个单词,直接返回长度
int pos = str.rfind(' ');
if(pos == std::string::npos)
cout << (str.size()) << endl;
else
cout << (str.size()-(pos+1));
}
3.翻转字符串区间
链接:翻转字符串区间
题目要求:
题解:
class Solution
{
public:
string reverseStr(string s, int k)
{
int index1 = 0;
int index2 = k-1;
//每2k个字符中反转前k个字符
//"abcdefg" ->前4个反转前2个,"abcd"->"bacd","efg"->"feg"
while(index2 <= s.size()-1)
{
reverse(s.begin()+index1,s.begin()+index2+1);
index1 += 2*k;
index2 += 2*k;
}
//最后一段小于k的全部反转
reverse(s.begin()+index1,s.end());
return s;
}
};