【C++】基础3——正则表达式,静态多态(函数重载和模板),异常处理

文章目录

  • [1. 正则表达式](#1. 正则表达式)
    • [1.1 regex_search](#1.1 regex_search)
    • [1.2 regex_match](#1.2 regex_match)
    • [1.3 regex_replace](#1.3 regex_replace)
    • [1.4 正则表达式补充](#1.4 正则表达式补充)
  • [2. 多态](#2. 多态)
  • [3. 重载](#3. 重载)
  • [4. 模板](#4. 模板)
    • [4.1 函数模板](#4.1 函数模板)
    • [4.2 类模板](#4.2 类模板)
    • [4.3 模板显式实例化](#4.3 模板显式实例化)
    • [4.4 模板特化](#4.4 模板特化)
      • [4.4.1 全模板](#4.4.1 全模板)
      • [4.4.2 偏模板](#4.4.2 偏模板)
  • [5. 异常处理](#5. 异常处理)
    • [5.1 捕获异常例子:vector的边界检查](#5.1 捕获异常例子:vector的边界检查)
    • [5.2 抛出异常+捕获异常例子:除法抛出除零异常,调用方捕获它](#5.2 抛出异常+捕获异常例子:除法抛出除零异常,调用方捕获它)
  • [6. 零散话题](#6. 零散话题)
    • [6.1 class和struc的异同](#6.1 class和struc的异同)
      • [6.1.1 相同点](#6.1.1 相同点)
      • [6.1.2 不同点](#6.1.2 不同点)
    • [6.2 什么是移动构造函数吗,拷贝构造函数](#6.2 什么是移动构造函数吗,拷贝构造函数)

1. 正则表达式

找字符串中匹配模式的子串。从字符串开始位置搜索,直到找到第一个匹配项(如果需要拿到所有匹配项,需要使用regex_iterator)就停止继续搜索并返回true,未能找到匹配项则返回false。

c++ 复制代码
/*
regex_search(text, pat, matchResult)
text: string, 待匹配字符串
pat: regex, 正则表达式
matchResult(可选): smatch, 存放匹配结果。结果子串matchResult.str(),结果子串在原字符串中的位置matchResult.position()。
返回: bool 
*/

#include <iostream>
#include <regex>
#include <string>

void NonIterativeMtach(std::string text, std::regex pattern){
    std::smatch match;  // 用smatch保存匹配结果
	// 查找第一个匹配项: John Doe, 25 years old
    if (std::regex_search(text, match, pattern)) {
        std::cout << "First match found:" << std::endl;
        std::cout << "Whole match: " << match.str(0) << std::endl;
        // 遍历各个捕获组
        for (size_t i = 1; i < match.size(); ++i) {
            std::cout << "Capture group " << i << ": " << match.str(i) << std::endl;
        }

        // 继续查找后续匹配项: Jane Smith, 30 years old
        std::string remainingText = match.suffix().str();
        while (std::regex_search(remainingText, match, pattern)) {
            std::cout << "\nNext match found:" << std::endl;
            std::cout << "Whole match: " << match.str(0) << std::endl;
            for (size_t i = 1; i < match.size(); ++i) {
                std::cout << "Capture group " << i << ": " << match.str(i) << std::endl;
            }
            remainingText = match.suffix().str();
        }
    } else {
        std::cout << "No match found." << std::endl;
    }
}

// 使用迭代器遍历匹配结果
void IterativeMatch(std::string text, std::regex pattern){
    std::smatch match;
    std::sregex_iterator it(text.begin(), text.end(), pattern);  // 指向第一个匹配项的迭代器
    std::sregex_iterator end;  // 结束迭代器
    if(it == end){
        std::cout << "No match found." << std::endl;
        return;
    }
    for(; it!= end; it++){
        match = *it;
       std::cout << "Whole match: " << match.str(0) << std::endl;
        for (size_t i = 1; i < match.size(); ++i) {
            std::cout << "Capture group " << i << ": " << match.str(i) << std::endl;
        }
        std::cout << std::endl;
    }
}

int main() {
    std::string text = "John Doe, 25 years old. Jane Smith, 30 years old.";
    std::regex pattern("(\\w+) (\\w+), (\\d+) years old");

    // 仅判断否匹配:std::regex_search(text, pattern)
    if(std::regex_search(text, pattern)){
        std::cout << "matched!" << std::endl;
    }
    NonIterativeMtach(text, pattern);
    std::cout << std::endl << std::endl;
    IterativeMatch(text, pattern);

    return 0;
}

1.2 regex_match

检查整个输入字符串是否完全匹配模式,如果匹配则返回true, 否则返 嘻嘻嘻尺寸小星星 false。

c++ 复制代码
 /* 
regex_match(text, pat, matchResult): 
text: string, 待匹配字符串
pat: regex, 正则表达式
matchResult(可选): smatch, 存放匹配结果。结果子串matchResult.str(),结果子串在原字符串中的位置matchResult.position()。
返回: bool
*/

#include <iostream>
#include <regex>


int main(){
    std::string text1 = "John Doe, 25 years old";
    std::regex pattern("(\\w+) (\\w+), (\\d+) years old");
    std::smatch matches;
    if(std::regex_match(text1, matches, pattern)){
        std::cout << "text1 matched!" << std::endl;
        for(size_t i = 0; i < matches.size(); i++){
            std::cout << "matches[" << i << "]: " << matches[i] << std::endl;
        }
    }else{
        std::cout << "text1 not matched!" << std::endl;
    }
    
    // 整个输入字符串匹配才返回true
    std::string text2 = "John Doe, 25 years old. Jane Smith, 30 years old.";
	if(std::regex_match(text1, pattern)){
        std::cout << "text2 matched!" << std::endl;
    }else{
        std::cout << "text2 not matched!" << std::endl;
    }
}

1.3 regex_replace

所有匹配项都被替换为subs后返回结果字符串。

c++ 复制代码
/*
regex_replace(text, pat, subs)
text: string, 待匹配字符串
pat: regex, 正则表达式
subs: 替换字符串
flags(可选):控制匹配行为的匹配标志(大小写等)
返回: 匹配替换后的字符串
*/
#include <iostream>
#include <regex>
#include <string>

int main() {
    std::string text = "John Doe, 25 years old. Jane Smith, 30 years old."; 
    // 简单替换
    std::regex pattern1(",");
    std::string replacement1 = " is";
    std::string result1 = std::regex_replace(text, pattern1, replacement1);

	// 使用捕获组替换
    std::regex pattern2("(\\w+) (\\w+), (\\d+) years old");
    std::string replacement2 = "$2 $1 is $3 years old";
    std::string result2 = std::regex_replace(text, pattern2, replacement2);

    std::cout << "Original text: " << text << std::endl;
    std::cout << "Replaced text1: " << result1 << std::endl;
    std::cout << "Replaced text2: " << result2 << std::endl;

    return 0;
}

1.4 正则表达式补充

  • 常用正则表达式元字符:

    • .:任意一个除换行符外的字符。
    • *:量词(贪婪),匹配0次或者多次。
    • +:量词(贪婪),匹配1次或多次。
    • ?:作量词时,匹配0次或1次;作非贪婪开关时,使贪婪量词转为非贪婪量词,如.*?.+?
  • 正则表达式的转义序列(C++): \\d 匹配数字,\\w 匹配单词,\\b 单词边界

  • 常用的正则表达式

    • 密码校验:^(?=.*[a-zA-Z])(?=.*\\d).+$
  • 零宽断言:正则表达式中,只判断不纳入匹配结果。

    • 正向先行断言:"apple(?=\\d+)"中括号内的表达式子是正向先行断言,表示匹配apple时,如果apple前是数字,则匹配成功,否则匹配失败。匹配结果是apple而不是apple加数字。
    • 正向后行断言:"(?<=¥)\\d+"括号内的表达式是正向后行断言,表示匹配数字时,如果数字前是符号,则匹配成功,否则匹配失败,匹配结果是数字而不是加数字。
    • 负向先行断言:"apple(?!\\d+)"如果apple后不是数字,匹配成功,否则匹配失败。
    • 负向后行断言:"(?<!¥)\\d+"如果数字前不是,匹配成功,否则匹配失败。
  • 性能考虑:编译正则表达式;减少使用非贪婪量词。

2. 多态

C++多态机制:静态多态(编译时多态)和动态多态(运行时多态)。

静态多态:函数重载;模板。ref:本文档第3章和第4章。

动态多态:虚函数和继承(运行时才确定对象的行为:派生类重写基类的虚函数f(),基类指针在运行时指向派生类对象,则将调用派生类对象的重写的f())。ref:https://blog.csdn.net/backlash321/article/details/146068181

3. 重载

函数签名:函数名,参数列表

函数头:返回类型,函数名,参数列表

函数声明:函数头

函数定义:函数头+函数体

重载的函数要求:函数名相同,函数参数列表不同,即函数签名相同。如果两个函数仅仅函数返回类型不同,那么这两个函数不构成重载(会报错)。

3.1 普通函数重载

c++ 复制代码
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

3.2 运算符重载

重载运算符要求:至少有一个参数是自定义类型;只能对已有运算符重载(+-*/) ,不能重载部分运算符( .::?:sizeof

3.2.1 重载二元运算符

重载 "<"
c++ 复制代码
// 作为成员函数来重载二元运算符, String对象y、z,y < z解析为y.operator< (z)
class String{
public:
    bool operator<( const String & ) const;
    ...
};

// 作为非成员函数的二元重载运算符,y < z将被解析为operator(y, z)
bool operator<(const String &, const String & );
重载"<<"和">>"
c++ 复制代码
#ifndef PHONENUMBER_H
#define PHONENUMBER_H

#include <iostream>
#include <string>

// define
class PhoneNumber
{
    friend std::ostream &operator<<( std::ostream &, const PhoneNumber & );
    friend std::istream &operator>>( std::istream &, PhoneNumber & );
private:
    std::string areaCode;
    std::string exchange;
    std::string line;    
};
#endif

3.2.2 重载一元运算法

重载"!"
c++ 复制代码
// 成员函数形式
class String{
public:
    bool operator!() const;
    ...
};

// 非成员函数形式
bool operator!( const String & );
重载"++"
前置++
c++ 复制代码
// 成员函数形式, 编译器解析为d1.operator++()
class Date{
public:
    Date &operator++();
    ...
}

// 非成员函数形式,编译器解析为operator++( d1 )
Date &operator++( Date & );
后置++
c++ 复制代码
// 成员函数形式,编译器解析为d1.operator++(0),参数0仅仅是为了编译器区分前置/后置
class {
public:
    Date opereator++( int );  // 需要占位参数
    ...
}

// 非成员函数形式,编译器解析为operator++( d1, 0 ),参数0仅仅是为了编译器区分前置/后置
Date operator++( Date &, int );

4. 模板

4.1 函数模板

c++ 复制代码
// 单个类型模板
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// 多个类型模板
template <typename T1, typename T2>
void display(const T1& a, const T2& b) {
    cout << a << endl;
    cout << b << endl;
}

4.2 类模板

c++ 复制代码
template <typename T>
class Stack {
private:
    T* data;
    int size;
    int capacity;

public:
    Stack(int cap) : capacity(cap), size(0) {
        data = new T[capacity];
    }

    ~Stack() {
        delete[] data;
    }

    void push(T value) {
        if (size < capacity) {
            data[size++] = value;
        }
    }

    T pop() {
        if (size > 0) {
            return data[--size];
        }
        return T();
    }
};

4.3 模板显式实例化

在代码中明确指定模板参数的具体类型,让编译器在特定位置生成该类型的模板实例,而不是等到模板被实际使用时才进行实例化。这样可以提升编译效率,避免连接错误。

c++ 复制代码
#include <iostream>

// 函数模板
template <typename T>
T add(T a, T b) {
    return a + b;
}

// 类模板
template <typename T>
class Container {
public:
    Container(T value) : data(value) {}
    T getData() const {
        return data;
    }
private:
    T data;
};

// 函数模板显式实例化
template int add<int>(int, int);
template float add<float>(float, float);

// 类模板显式实例化
template class Container<double>;

int main() {
    int result1 = add(1, 2);
    float ret = add(1.2, 1.3);
    std::cout << "Result of add(1, 2): " << result1 << std::endl;
    std::cout << "Result of add(1.2, 1.3): " << ret << std::endl;

    Container<double> c(3.14);
    std::cout << "Data in Container: " << c.getData() << std::endl;

    return 0;
}

4.4 模板特化

通用模板有一套实现逻辑,但是希望对某些具体的类型走其他逻辑而不是同一的通用模板逻辑。这时可以使用模板特化。可以理解为重写父模板。模板特化分为全特化(子模板将父模板的全部模板参数都指定成具体类型)和偏特化(子模板将父模板的部分模板参数指定为具体类型,其他模板参数扔指定为模板参数)

4.4.1 全模板

c++ 复制代码
#include <iostream>

// 函数模板
template <typename T1, typename T2>
T1 max(T2 a, T2 b) {
    return (a > b) ? a : b;
}

// 函数模板全特化
template <>
bool max<bool, const char*>(const char* a, const char* b) {
    return (std::strcmp(a, b) > 0) ? a : b;
}


int main() {
    int result1 = max<int, int>(1, 2);  // 调用时指定使用通用模板
    std::cout << "Max of 1 and 2: " << result1 << std::endl;
    bool result2 = max<bool, const char *>("ab", "bb");  // 调用时指定使用全特化模板
    std::cout << "Max of 'ab' and 'bb': " << result2 << std::endl;

    return 0;
}

4.4.2 偏模板

c++ 复制代码
#include <iostream>

// 类模板
template <typename T1, typename T2>
class Pair {
public:
    Pair(T1 first, T2 second) : first(first), second(second) {}
    void print() const {
        std::cout << "Generic Pair: " << first << ", " << second << std::endl;
    }
private:
    T1 first;
    T2 second;
};

// 类模板偏特化
template <typename T>
class Pair<T, int> {
public:
    Pair(T first, int second) : first(first), second(second) {}
    void print() const {
        std::cout << "Partial Specialization Pair: " << first << ", " << second << std::endl;
    }
private:
    T first;
    int second;
};

int main() {
    Pair<double, char> p1(3.14, 'A');  // 使用父模板
    p1.print();

    Pair<double, int> p2(3.14, 10);  // 使用特化的子模板
    p2.print();

    return 0;
}

5. 异常处理

  • 异常处理需要完成两件工作:抛出异常(throw),捕获异常(try-catch)。

  • 关于异常在调用栈中的传递:f1()调用f2(),f2()调用f3(),f3()抛出异常,选则在f1()还是f2()中捕获它?

    • 在f2()中处理异常的理由:如果 f2() 能够根据异常信息进行局部的错误恢复或处理,则不需要将异常向上传递(如f2() 可以尝试重新调用 f3() 或者使用备用方案来完成任务),这样可以保持较好的封装性。

    • 在f1()中处理异常的理由:如果f2()不足以处理异常,则应该向上传递给f1()。要求在一个较为宏观的层面全面而通用地处理异常和记录异常,则在f1()处理异常更合适(如记录日志、进行资源清理或者通知用户)。

5.1 捕获异常例子:vector的边界检查

c++ 复制代码
#include < stdexcept >

// vector对象的at函数提供边界检查和抛出异常的功能,at(ind)中ind超出vector现有元素范围,则抛出out_of_range异常(定义在<stdexcept>中)
try{
    cout << v.at( 8 ) << endl; 
}
catch (out_of_range &ex){
    cout << ex.what() <<endl;  // 显示存储在异常对象ex中存储的错误信息
}

5.2 抛出异常+捕获异常例子:除法抛出除零异常,调用方捕获它

c++ 复制代码
#include <iostream>

// 定义一个除法函数,可能会抛出除零异常
double divide(double a, double b) {
    if (b == 0) {
        // 抛出一个字符串类型的异常
        throw "Division by zero!";
    }
    return a / b;
}

int main() {
    double num1 = 10.0;
    double num2 = 0.0;

    try {
        double result = divide(num1, num2);
        std::cout << "Result: " << result << std::endl;
    }
    catch (const char* error) {
        std::cerr << "Error: " << error << std::endl;
    }
    std::cout << "After exception" << std::endl;

    return 0;
}
/*
Error: Division by zero!
After exception
*/

6. 零散话题

6.1 class和struc的异同

6.1.1 相同点

都可以用来定义数据类型,内部可以定义数据成员和函数成员。

c++ 复制代码
// struct的基本用法
struct MyStruct{  // 命名结构体
    int a;
    int b;
    void display(){
        cout << "display: ";
        cout << "a = " << a << ", b = " << b << endl;
    }
    MyStruct(int a, int b): a(a), b(b){}
};

struct {  // 匿名结构体
    int a;
    int b;
    void display(){
        cout << "display: ";
        cout << "a = " << a << ", b = " << b << endl;
    }
} anonymousVar;  // 使用匿名结构体声明的变量

// class的基本用法
class SqList{
public:
    SqList(int capacity=100){
        this->len = 0;
        this->capacity = 0;
        this->dataPtr = new int[capacity];
        if(this->dataPtr != NULL){
	        this->capacity = capacity;
            // init
            for(size_t i=0; i<capacity; i++){
                dataPtr[i] = 0;
            }
        }
    }
    ~SqList(){
        len = 0;
        capacity = 0;
        delete[] dataPtr;
    }
    void traverse(){
        for(size_t i=0; i<len; i++){
            cout << dataPtr[i] << ' ';
        }
        cout << endl;
    }
    void display(){
        cout << "display: ";
        for(size_t i=0; i<capacity; i++){
            cout << dataPtr[i] << ' ';
        }
        cout << endl;
    }
private:
    int len;
    int capacity;
    int * dataPtr;
};

int main() {
    SqList mySqList(10);
    MyStruct myStructInstance(1,2);
    mySqList.display();
    myStructInstance.display();
}

6.1.2 不同点

  • 默认成员变量权限不同,struct默认成员变量为public成员变量,class默认成员变量为private成员变量。
c++ 复制代码
struct MyStruct{
    int a;
    int b;
    MyStruct(int a, int b): a(a), b(b){}
};

class MyClass{
    int a;
    int b;
    MyClass(int a, int b): a(a), b(b){}
};

int main(){
    MyStruct myStructInstance(1, 2);
    cout << "a = " << myStructInstance.a << ", b = " << myStructInstance.b << endl;
    // MyClass myClassInstance(1, 2);  // 出错,构造函数是private的
    // cout << "a = " << myClassInstance.a << ", b = " << myClassInstance.b << endl;  // 出错,成员变量是private的
    return 0;
}
  • 默认继承方式不同,struct默认public继承,class默认praivate继承
c++ 复制代码
struct BaseStruct{
    int a;
    BaseStruct(int a):a(a){}
};

struct DerivedStruct: BaseStruct{
    int b;
    DerivedStruct(int a, int b):BaseStruct(a), b(b){}
};

class BaseClass{
    int a;
    BaseClass(int a): a(a){}
};

class DerivedClass: BaseClass{
    int b;
    // DerivedClass(int a, int b): BaseClass(a), b(b){}  // 基类构造函数为private,派生类无法访问
};

int main(){
    DerivedStruct myDerivedStructInstance(1, 2);
    cout << "a = " << myDerivedStructInstance.a << ", b = " << myDerivedStructInstance.b << endl;
    // MyClass myClassInstance(1, 2);  // 出错,构造函数是private的
    // cout << "a = " << myClassInstance.a << ", b = " << myClassInstance.b << endl;  // 出错,成员变量是private的
    return 0;
}

6.2 什么是移动构造函数吗,拷贝构造函数

拷贝构造函数:创建对象的副本时使用。拷贝构造函数被调用的时机:用一个对象初始化另一个同类型对象时;函数传参时;函数返回时;以某类型对象为元素的容器在插入对象时,会调用该类型的拷贝构造函数。

移动构造函数:定义移构造函数,函数返回值时不再需要拷贝临时对象(右值)控制的资源,而只需要将临时对象(右值)的资源控制权交给新对象即可。返回值对象就是临时对象(右值)。

c++ 复制代码
class MyClass{
public:
    // 自定义构造函数
    MyClass(int cap){
        this->len = 0;
        this->cap = cap;
        this->data = new(std::nothrow) int[cap];  // 分配内存失败不会返回bad_alloc,而是返回nullptr
        if(this->data != nullptr){
            for(size_t i=0; i < cap; i++){
                this->data[i] = 0;
            }
        }
    }
    // 拷贝构造函数
    MyClass(MyClass &other){
        cout << "Copy Constructor is called!" << endl;
        len = other.len;
        cap = other.cap;
        // 深拷贝
        data = new(std::nothrow) int[other.cap];
        if(data != nullptr){
            for(size_t i=0; i < cap; i++){
                data[i] = other.data[i];
            }
        }
    }
    // 移动构造函数
    MyClass(MyClass &&other){
        cout << "Move Constructor is called!" << endl;
        len = other.len;
        cap = other.cap;
        // 浅拷贝
        data = other.data;
        other.data = nullptr;  // 避免右值对象被销毁时释放data
    }
    // 析构函数
    ~MyClass(){
        delete[] data;
    }
    void display(){
        for(size_t i=0; i<cap; i++){
            cout << data[i] << ' ';
        }
        cout << endl;
    }
private:
    int len;
    int cap;
    int *data;
};

MyClass f(MyClass a){  // 发生拷贝
    cout << "just enter f()" << endl;
    cout << "before return f()" << endl;
    return a;
}

int main(){
    MyClass myClassIns(10);
    myClassIns.display();
    MyClass b = f(myClassIns);  // 传参调用拷贝构造函数;返回调用移动构造函数(返回值是临时对象,作为右值,将资源控制权交给左值b对象,导致调用移动构造函数)
    return 0;
}

/*
0 0 0 0 0 0 0 0 0 0 
Copy Constructor is called!
just enter f()
before return f()
Move Constructor is called!
*/
相关推荐
nqqcat~9 分钟前
STL常用算法
开发语言·c++·算法
_GR19 分钟前
2022年蓝桥杯第十三届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·蓝桥杯·动态规划
Matlab光学33 分钟前
MATLAB仿真:Ince-Gaussian光束和Ince-Gaussian矢量光束
开发语言·算法·matlab
虾球xz1 小时前
游戏引擎学习第195天
c++·学习·游戏引擎
珹洺1 小时前
Java-servlet(十)使用过滤器,请求调度程序和Servlet线程(附带图谱表格更好对比理解)
java·开发语言·前端·hive·hadoop·servlet·html
勘察加熊人1 小时前
c#使用forms实现helloworld和login登录
开发语言·c#
熙曦Sakura1 小时前
【C++】map
前端·c++
Stardep1 小时前
算法学习11——滑动窗口——最大连续1的个数
数据结构·c++·学习·算法·leetcode·动态规划·牛客网
双叶8361 小时前
(C语言)学生信息表(学生管理系统)(基于通讯录改版)(正式版)(C语言项目)
c语言·开发语言·c++·算法·microsoft
IT从业者张某某2 小时前
Python数据可视化-第4章-图表样式的美化
开发语言·python·信息可视化