C++模板基础和STL之string

泛型编程之模板

使用实际调用的函数不是同一个,因为不同类型参数,函数栈帧中开辟的空间不一样。参数不一样,所以调用的函数也不一样。使用模板速度必重载速度更快,因为是编译器直接生成。而STL就叫标准模板库。

template<class T>

void Swap(T& x1, T& x2) // 本意是用引用&
{
    T x = x1;
    x1 = x2;
    x2 = x;
}
  • 特别场景:当函数像求和一样,两个都用模板的T类型,那么T不可以同时为doble和int,如何解决这种问题?
      答:显示实例化。这种方式是指定模板参数T的具体类型。

  • 对于模板参数T:

可以做参数、也可能做返回值。
typedef中可以使用参数T使得某个类或某类数据结构支持尽可能多的参数类型,而只是用typedef int datatype不能同时满足两种或以上的数据结构存储的数据类型。实际上,面对不同参数,编译器会自动生成不一样的代码,不必自己写出来重复度极高的相似类或结构体的代码,工作都交给了编译器。如下写法:

stack<int> st1;
stack<double>st2;
stack<int*>st4;

以上,编译器实例化出几份代码分别是存int、double的等几个存不同元素类型的栈。

  • 注意点:
  1. 如果在类外使用类模板写函数,还要重声明使用了类模板T。不然如下第二行的T识别不出来。
    请添加图片描述
  2. 使用类模板后,类型变成了class_name,class_name只是类名。
  • 模板实例化:
    隐式实例化和显示实例化:
      答:隐式是直接传参,让编译器自己判断,显示是函数名后:<>显示实例化。模板函数和非模板函数(平时常用的)可以同时存在,调用时候,如果显示给类型,会优先调用非模板函数。隐式调用,优先调用模板函数。此外,如果模板可以产生更匹配的函数,就选择模板函数。模板不允许自动类型转换,但是普通函数可以自动类型转换。
  • 类模板:
    比如一个项目中,我们如果定义栈的类,内部类型使用了class T。那么在同一项目中,可以给stack存各种类型数值,而不用类模板,就肯定不行,只能存单一类型数值。

关于模板的疑问点:

  1. 下面有关C++中为什么用模板类的原因,描述错误的是? ( )
    A.可用来创建动态增长和减小的数据结构
    B.它是类型无关的,因此具有很高的可复用性
    C.它运行时检查数据类型,保证了类型安全
    D.它是平台无关的,可移植性
  • 问题:D对吗?C对吗?
    模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换。
    只要支持模板语法,模板的代码就是可移植的,总之它不安全,且如果支持模板语法,就可移植。
  1. 在下列对fun的调用中,错误的是( )
    template

T fun(T x,T y){

return xx+yy;

}

A.fun(1, 2)
B.fun(1.0, 2)
C.fun(2.0, 1.0)
D.fun(1, 2.0)

  • B、D哪个错了?
    显示实例化,改变的是传入的参数类型, 而不是说确定了模板中T类型,所以D会都变float,D正确了。
  1. 下列关于模板的说法正确的是( )
    A.模板的实参在任何时候都可以省略
    B.类模板与模板类所指的是同一概念
    C.类模板的参数必须是虚拟类型的
    D.类模板中的成员函数全是模板函数
  • 我全不懂
    A 不一定有时候需要指定
    B 模板类是通过类模板实例化的具体类,不一样。也就是说:模板类更小,是个具体的。类模板是说你在使用类模板
    C 错误,不是虚拟,也可以直接传,如2题的D
    D 对,因为数据成员是模板类型,所以函数都是模板函数。
  1. 下列的模板声明中,其中几个是正确的( )

1)template

2)template<T1,T2>

3)template<class T1,T2>

4)template<class T1,class T2>

5)template<typename T1,T2>

6)template<typename T1,typename T2>

7)template<class T1,typename T2>

8)<typename T1,class T2>

  • 2、4、6 7
    class可以用typename代替。
  1. 下列描述错误的是( )
    A.编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
    B.函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具
    C.模板分为函数模板和类模板
    D. 模板类跟普通类以一样的,编译器对它的处理时一样的
    D错误,肯定不一样。

STL

  STL是C++标准模板库(standard template libaray)。此外STL版本一般一致,常见有PJ、SGI版本等。
核心:容器、算法

string

  1. 基本知识点:
    必须导入包,因为<<、>>对string类型操作需要重载。
    构造函数7、成员函数上百,只需要掌握常用即可。
    请添加图片描述
  2. 常见函数:
  • size()和length()结果一样,但是推荐size。它俩的计算都不包含\0。此外由于其它STL容器中习惯用size,因为只有字符串习惯说length长度,多用size()为了保持统一,方便记忆用size。
  1. 对capacity()的了解:查看string容量的函数
      答:利用s += ‘char’ 或其它函数会使得字符串增容,字符串变量长度发生改变后会自动增容。大小包括**\0**。
  2. 当字符串长度变化后如何增容的?
      答:如果字符串变量长度改变后,开始会再malloc()一个32的空间,此后如果长度再变化,会成1.5倍地做扩容。
  3. 访问之operator[]
  • std::string::operator[]
      使得string能直接通过s[i]得到下标为i的字符值。返回类型是引用,出了范围还存在,所以可以直接返回私有这个成员
    请添加图片描述
      原理:重载[],通过【pos】直接返回字符指针_str[pos]。以底层的私有指针变量为一维数组返回下标为pos的_str。即使是私有成员变量也可以返回,可以返回它的地址,也可以返回它的拷贝值。
  • 此外,at()功能也类似[],但是它抛出异常,而不是断言。
  • 字符串拼接:
      声明string s而不做初始化也可以任意拼接字符串,但是建议像做leetcode,结果字符串还是以""初始化吧。

遍历string之迭代器

  1. 理解:
      暂时认为是指针
  2. 使用代码:
    需要写++it。
    请添加图片描述
  3. 迭代器变量的操作:通过it可以访问string某个位置的值,也可以通过it改字符串中字符。*it += 1;
  4. 反向迭代器:rbegin()、rend()
      与之前正向迭代器操作一样,需要加上r。结果方向相反
      它的rbegin()指向’\0’的前一个。rend()指向第一个字符。
    请添加图片描述
  • 既然有了下标搭配[]的用法,为什么还要迭代器呢?
      因为其它STL也需要遍历,不能通过[]方式,为了保持统一

  • 这里发现:创建正、反迭代器类型it、rit都很费劲,右值是.begin()或.end(),用auto可以自动识别右值类型做转换。

  • 当迭代器遇上const常字符串的引用:不能修改,只能*it读。

遍历string之范围for

  1. 范围for运行效果如上述迭代器。
for(auto e: s1)
	cout<<e<<endl;
  1. 使用:
  • 可以读写:
  1. auto e只是对s1中的值做拷贝。如果要修改字符串,应该使用引用。
  2. 规定&必须放在e前面。
  3. 如下的auto可换char,但是不如auto好用。
  1. 代码:
for(auto& e: s1)
	cout<<e<<endl;

reserve():专门的字符串扩容函数,以’\0’填充

  • 作用:
  1. 开空间: 申请1000个空间,一次多开,会让string减少增容次数,提高效率。
    s.reserve(1000);
  2. 开空间加初始化:reserve(100, ‘x’);
    开100个空间,每个位置填’x’。
    此外,reserve扩容并不会影响原来的string长度。
    常使用reserve()而不是resize()

resize()

  可以扩容,也可以缩容。缩容可能让字符串丢失。

c_str()

  1. 解释:
    获取C形式的string,以’\0’结束。
    通过字符串变量可以直接调到。
  2. 用法:
    可以使用在字符串读文件上:
      如下第一种不可编译,因为fopen()第一个参数是const char*,而这里的file是string。使用file_str()是const char*。
string file("test.txt");
FILE* fout = fopen(file, "W");
-------------下面可以的-----------------
string file2(test.txt);
FILE* fout = fopen(file_str, "W");

substr()

截取字符串,返回的结果是深拷贝。

find()

  • 功能:
      默认从参数size_t pos=0开始找。如果找不到,给整型最大值。 string::npos,npos是string中的静态变量。
  • 使用技巧:
      用find(‘str’)求字符位置,再搭配substr()截取子串。substr(0, pos-0),技巧就是用find()结果减某个位置

rfind()

  • 情景:文件名是:.txt.zip,我们找.zip,如果寻找策略是从第一个".“到文件末尾寻找”.zip",那么只能得到".txt.zip",只需要".zip"这需要从右往左找。用rfind(“.”)得到靠后的"."位置,然后用substr(pos)得到从这个位置到最末尾的子串。
    请添加图片描述

url.substr():寻找从某个位置起往后的n个。

s.substr(x, n):从下标为x起找n个。

erase():删除

param1:起始位置
param2:删个数。如果不给,就删完。

  • 删头:
    uri.erase(0, 1);
  • 删尾:如果删除位置是末尾,则倒着删
    uri.erase(uri.size() - 1, 1);
  • 使用erase()对字符串去头尾:
      搭配find_first_not_of()、find_last_not_of(" “),后面这个意思是最后一个不是空格,所以还应该+1,才是末尾空格位置。以下” "或’ '都可以用
// 不空才去做
// find_first_not_of
void trimSpace(string& s)
{
    if (!s.empty())
    {
        s.erase(0, s.find_first_not_of(" "));
        s.erase(s.find_last_not_of(" ") + 1);

    }
}

sto()系列和to_string()

数字变字符串、字符串变数字:
stoi()转为int类型,stold转为double类型。
请添加图片描述

demo:

  1. 解析url:
int main()
{
    // s1 末尾默认有\0
    string url = "https://www.baidu.com/?tn=62095104_31_oem_dg/";
    size_t pos1 = url.find(":");
    string protocol = url.substr(0, pos1 - 0);
    cout << "协议:"<<protocol<< endl;
    // 找第二个'/',find()和rfind()直接用都拿不到。
    // 这就可以用find的其它用法:从某个位置开始找,我们根据url的规律,可以从w开始,pos1+3
    size_t pos2 = url.find('/', pos1+3);
    // cout <<"第二个/位置" << pos2 << endl;  pos2 = 21
    string domain = url.substr(pos1+3, pos2 - (pos1+3));
    cout << "域名:"<<domain<<endl;
    string uri = url.substr(pos2 + 1);
    cout << "区分段:" << uri << endl;
    // 删头
    uri.erase(0, 1);
    cout << "区分段:" << uri << endl;
    // 删尾:从位置起删,如果是最后位置,则反着删
    uri.erase(uri.size() - 1, 1);
    cout << "区分段:" << uri << endl;

    return 0;
}

  1. 读空格后单词长度
#include <iostream>
#include<string>
#include<string>
using namespace std;

int main() {
    string s;
    //char ch = getchar();
    // 单个字符接收 以'\n'为结尾 会忽视空格
    /*while (ch != '\n')
    {
        s += ch;
        ch = getchar();
    }*/
    getline(cin, s);
    int pos = s.rfind(' ');
    string res = s.substr(pos + 1);
    cout << res.size() << endl;
    return 0;
}

字符串的接收:

cin>>s,
cin和scanf字符串默认以空格和换行结束
当要输入字符串以空格间隔,只接收第一个。

  • 两种接收带空格字符串的方式:
  1. 循环getchar()方式
string s;
char ch = getchar();
// 单个字符接收 以'\n'为结尾 会忽视空格
    while (ch != '\n')
    {
        s += ch;
        ch = getchar();
    }
  1. getline()
getline(cin, s);

练习

  1. leetcode344. 反转字符串
  • 思路:
       利用左右变量begin、end,双重循环,依次交换头尾。外圈要符合begin<end,内部判断左右两端是字母就停下做交换。不用担心不够两个的情况,如果最后指向同一个位置,那么交换自己之后就结束,且记得交换完,begin++,end–

  • 注意:

  1. C++不用写swap,库中提供了。
  2. 像这种都是正数的变量如下标begin、end且也较小,用size_t无符号整形即可。
bool isLetter(char ch)
{
    if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
        return true;
    else
        return false;
}

class Solution {
public:
    string reverseOnlyLetters(string s) {
        size_t begin = 0;
        size_t end = s.size() - 1;
        // 左到右判断一遍 遇到字母就停下 都是字母才交换。
        // 注意换完还得往后走
        while (begin < end)
        {
            while (begin < end && !isLetter(s[begin]))
            {
                begin++;
            }
            while (begin < end && !isLetter(s[end]))
            {
                end--;
            }1
            swap(s[begin], s[end]);
            begin++;
            end--;
        }
        return s;
        }
};
  1. leetcode 387. 字符串中的第一个唯一字符
  • 思路:
      利用计数排序思想,字母范围比较集中,st[i] - ‘a’ 结果为0~27,最后遍历寻找为1的值。

  • 报错:
    请添加图片描述
    一直报错无返回值,仔细看因为左边提示:会有-1的结果。所以没有认真看题,不是所有都符合规范

  • 代码

class Solution {
public:
    int firstUniqChar(string s) {
        int countArr[26] = { 0 };
        // 统计次数
        for (size_t i = 0; i < s.size(); ++i)
            countArr[s[i] - 'a']++;
        for (int j = 0;j < s.size(); j++)
            if (countArr[s[j] - 'a'] == 1)
                return j;
        return -1;
    }
};
  • 巧妙点和习惯:
  1.   要返回原始字符串中的位置, s[j] - ‘a’ 得到字母表中第几个值。其中j,就是该字母在原始string中位置的记录。
  2. 会使用 size_t:当数字范围较小且都是正数,尽管使用。
  3. string习惯使用size()求长度。

  1. 回文串
    这个题让看字母类和数字类是否相同,不考虑其他特殊字符。
  • 思路:
  1. 双指针法:begin、end同时挪动,是
    为什么是两层:移动过程可能会停下做操作,做完操作后,因为外层循环使得继续去移动。所以两层循环搭配双指针非常合适。
    2. 巧妙用法:大小写转换:对数字做tolower()和对字母做tolower()效果一样,不会影响数字,且比较字母更加方便。
  2. 注意事项:当前都是字母后,比较完应该挪动指针
class Solution {
public:
    bool isLetterOrNum(char ch)
    {
        if (ch >= '0' && ch <= '9')
            return true;
        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
            return true;
        return false;
    }
    bool isPalindrome(string s) {
        int begin = 0, end = s.size() - 1;
        /*
    
        */
        while (begin < end)
        {

            while (begin < end && !isLetterOrNum(s[begin]))
            {
                begin++;
            }
            while (begin < end && !isLetterOrNum(s[end]))
            {
                end--;
            }
            if (tolower(s[begin])!=tolower(s[end]))
            {
                cout << "这里不对" << endl;
                return false;
            }

            else {
                end--;
                begin++;
            }
        }
        return true;
    }
};
  1. leetcode415. 字符串相加
    主要是长测试用例的相加:
  • 对于leetcode类测试:
    如下代码:
    上面的可以,为什么下面不可以?
Solution s1 = Solution();
-----------上面的可以,为什么下面不可以----------------
Solution s1 = new Solution();
-----------下面的又可以----------------
Solution* s1 = new Solution();

因为new在申请空间,返回地址,所以用类指针类型接收。
习惯上,操纵类喜欢用指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值