c++入门

阅读前需了解基本语法知识(任意编程语言都可,本文主要对比c语言)

【本文为个人薄见,仅供参考。若有错误请指出】

参考:

小甲鱼c++快速入门

C++ Primer 中文版 第5版

一、面向对象:

可以理解为c语言中的结构体。面向对象编程更倾向于使用对象(即结构体-c或类-java)的变量或方法。

二、基本语法:

1.基本结构

#include <iostream>
using namespace std;
 
// main() 是程序开始执行的地方
 
int main()
{
   return 0;
}
  • #include<iostream>

    • 声明输入输出流

  • using namespace std;

    • 将std命名空间中的所有标识符引入到当前的命名空间中,以便在代码中直接使用标准库中的函数、类和对象,而无需在每个标识符前面加上std::前缀。

宏定义 define和const

C中const 能定义真正意义上的常量吗?C++中的const呢?

不能。c中的const仅仅是从编译层来限定,不允许对const 变量进行赋值操作,在运行期是无效的,所以并非是真正的常量(比如通过指针对const变量是可以修改值的),但是c++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。 补充:

1)c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。 2)这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。 3)c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。 4)c语言中只有enum可以实现真正的常量。 5)c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。 6)c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。

2.输入输出

c++中的的“>>”和“<<”都为重载运算符,不同于c语言中的左移右移运算符。

"<<":用于输出数据到流(stream)对象,特别是在输出到标准输出流(std::cout)时经常使用。

//输入一些整数计算和,回车键作为结束标志符。空格作为分隔符(空格可多个)
int main(){
	int sum=0,a;
	while(cin >> a){
		sum+=a; 
		while(cin.peek() == ' '){  //观测,指针停留在当前位置并不后移。
			cin.get();  //从输入流中获取,并指向下一个 
		}
		if (cin.peek() == '\n'){
			break;
		}
	}
	cout << sum;
} 
int main(){
	int sum=0;
	int i,a=0;
	char c;
	while(scanf("%d",&a) == 1){
		sum+=a;
		
		while((c = getchar()) == ' ');

		if (c == '\n'){
			break;	
		}	
		ungetc(c,stdin); //push the char of number to the io stream
	}
	printf("%d",sum);
}

        cin对象 cin >>  ;

                cin.ignore() 忽略(删除)
                cin.getline() 获取   “当输入字符串包含空格时:std::getline(std::cin,string)”
                cin.get() 读取
                cin.peek() 观测
                cin.read() 读取
                cin.write() 输出
                cin.gcount() 计数

        cout对象 cout <<  ;

                cout.precision() 精度
                cout.width() 输出宽度

3.文件I/O

C------------------------------

        int main( int argc , char *argv[] ) 

                argc是命令行总的参数个数,argv[]是参数字符串,argv[x] 第x个参数字符串

        FILE 文件类型 File 类

fprintf(stderr,"error:%s\n",strerror(errno)); 把错误信息定向到stderr标准流,再由stderr打印到屏幕(如果没有对stderr做过重定向)。

        EOF == end of file

                getc() :返回值为int

                putc():

        if( ferror(fp) ):判断读取文件是否有错误

C++-----------------------------

        ifstream:文件读取类

                ifstream in(char* filename, int open_mode); open_mode:文件打开方式“ios::app”

        ofstream:写入文件类

                ofstream out(char* filename, int open_mode)

                ofstream / ifstream.open:打开

                ofstream / ifstream.close : 关闭

        打开模式ios

                ios::in        打开文件

                ios::app       写入文件末尾

                ios::out        打开文件

                ios::binary        二进制打开文件

                ios::trunk        删除文件已有内容

                ios::nocreate        

                ios::noreplece

                ios::beg        使得文件指针指向文件头

                ios::end        与ios::beg相反

多种模式可用     ios::in | ios::out

文件流对象:两个成员函数(seekp 和 seekg)

它们可以用于将读写位置移动到文件中的任何字节。

        1.seekp 可用于将信息 put(放入 写入)到文件中

        2. seekg 则可用于从文件中 get(获取)信息。

        3. tellg()函数不需要带参数,它返回当前定位指针的位置,也代表着输入流的大小。

4.函数重载

相同函数名不同参数和相同用途

功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同

方便对不同数据类型进行相同处理

5.数据类型

数组

        声明:type arrayName [ arraySize ]; double balance[10];

        初始化数组:double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

特点:

        放在一块连续的内存空间中

        数组中每个元素都是相同数据类型

        c语言中字符串实际存储在一个字符数组中,c++中使用std::string。

        string的底层实现

string继承自basic_string,其实是对char*进行了封装,封装的string包含了char*数组,容量,长度等等属性。

string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2*n),然后将原字符串拷贝过去,并加上新增的内容。

滑动窗口
        
面试
  • C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);
  • 在释放对象数组时没有使用delete[],使用了delete。可能造成内存泄漏。
  • 数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。
        动态数组(STL中vector)如何实现:
  • 动态数组

数组和指针紧密相关,数组名代表数组内存地址,即数组中第一个元素的内存地址。

静态变量和结构体

  • STL中的vector

STL中的vector是封装了动态数组的顺序容器。不过与动态数组不同的是,vector可以根据需要自动扩大容器的大小。具体策略是每次容量不够用时重新申请一块大小为原来容量两倍的内存,将原容器的元素拷贝至新容器,并释放原空间,返回新空间的指针。

在原来空间不够存储新值时,每次调用push_back方法都会重新分配新的空间以满足新数据的添加操作。如果在程序中频繁进行这种操作,还是比较消耗性能的。


指针

c++中变量类型会根据自然边界进行对齐

指针存储的是变量的内存地址

取址操作符:&

指针指向的内存中的地址不保存指向的数据本身。

多个指针可以指向相同地址。

*的用法:①创建指针 ②对指针进行解引用

什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?

用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。

1). 使用的时候要记得指针的长度. 2). malloc的时候得确定在那里free. 3). 对指针赋值的时候应该注意被赋值指针需要不需要释放. 4). 动态分配内存的指针最好不要再次赋值. 5). 在C++中应该优先考虑使用智能指针.

C/C++引用和指针的区别?

指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。

引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)

有多级指针,但是没有多级引用,只能有一级引用。

指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)

sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。

引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

使用指针前最好做类型检查,防止野指针的出现;

引用底层是通过指针实现的;

作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。

C++中的指针参数传递和引用参数传递

指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。

引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。

从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。


野指针是什么?如何检测内存泄漏?(void *p)

野指针:指向内存被释放的内存或者没有访问权限的内存的指针。

“野指针”的成因主要有3种:

指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如

char *p = NULL;

char *str = new char(100);

指针p被free或者delete之后,没有置为NULL;

指针操作超越了变量的作用范围。


如何避免野指针?

对指针进行初始化

①将指针初始化为NULL。

char * p = NULL;

②用malloc分配内存

char * p = (char * )malloc(sizeof(char));

③用已有合法的可访问的内存地址对指针初始化

char num[ 30] = {0};

char *p = num;

指针用完后释放内存,将指针赋NULL。

delete(p);

p = NULL;

结构

定义:
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
......
} object_names;
访问结构成员

使用成员访问运算符(.)

object_names.member_name1

6.与或非

逻辑(OR):|| 【一真则真】
当两个条件中有一个或全部满足某个要求时,则表达式的值为真

逻辑(AND)运算符:&& 【一假则假】

两个条件为真时,则表达式的值为真(条件:两个都要为真   结果:则为真)
备注:&&运算符允许建立一系列的if else if else 语句,进行设置取值范围

以逻辑运算符 && 为例,对于 A && B 这个表达式,如果 A 表达式返回 False,那么 A && B 已经确定为 False,此时不会去执行表达式 B。同理,对于逻辑运算符 ||, 对于 A || B 这个表达式,如果 A 表达式返回 True ,那么 A || B 已经确定为 True ,此时不会去执行表达式 B。

三、STL

1.vector

和数组相似,称为单端数组。

数组是静态空间,而vector可以动态扩展

动态扩展

并不是在原空间后续接新空间,而是找更大的内存空间,将原数据拷贝到新空间,并释放原空间。

构造函数

vector<T> v;                  //采用模板实现类实现,默认构造函数

//赋值
vector(v.begin(),v.end());    //将v[begin,end]区间中的元素拷贝给本身
vector(n,elem);               //构造函数将n个elem拷贝给本身
vector(const vector &vec);    //拷贝构造函数

//容量和大小
v.size();                     //返回vector元素个数
capacity();                     //容器的容量
resize(int num);                //重新指定容器的长度num,若容器变长,则以默认值填充新位置
                                //若容器变短,则末尾超出容器长度的元素被删除
resize(int num,elem);           //重新指定容器的长度num,若容器变长,则以elem值填充新位置
                                //若容器变短,则末尾超出容器长度的元素被删除

//插入和删除
v.push_back();                //末端插入数据
pop_back();                    //删除最后一个元素
insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele
insert(const_iterator pos, int count, ele);       //迭代器指向位置pos插入count个元素ele
erase(const_iterator pos);     //删除迭代器指向的元素
erase(const_iterator start,const_iterator end); //删除指定区间元素
clear();                    //删除所有元素

//数据存取
at(int idx);        //返回索引idx所指数据
operator[];            //返回索引idx所指数据
front();                //返回第一个数据元素
back();                //返回最后一个数据元素
count(nums.begin(), nums.end(), target) //查找target元素

//容器互换
swap(vec);            //两个vector的元素进行互换

//预留空间,减少vector在动态扩展时的次数
reserve(int len);         //预留len个元素长度,预留位置不初始化,元素不可访问

默认构造:

vector<int> nums;//默认构造,无参构造

for (auto x : nums) == for (vector< int >::iterator iter = nums.begin(); iter != nums.end(); iter++)

2.deque

vector对于头部的插入删除效率低,数据量越大,效率越低

3.map

  • map中的所有元素都是pair
  • pair中的第一元素为key(索引),第二个为value
  • 所有元素会根据元素的键值自动排序

                本质:

属于关联式容器,底层结构是二叉树

map和multimap的区别

map不允许容器中有重复key值元素

multimap允许容器中有重复key值元素

 构造和赋值

//构造
map<T1,T2> mp;         //默认构造
map(const map &mp);     //拷贝构造

//赋值
map& operator = (const map &mp)

4.string

char*是一个指针,string是一个类。string内部封装了char*管理字符串,是一个char*型容器。

        构造函数

string();        //创建一个空的字符串

string(const char* s);        //使用字符串s初始化

string(const string& str);        //使用一个string初始另一个

string(int n,char c);        //使用n个c字符初始化

        赋值:

string str = " ... ";

string str2 = str1;

string str='c';

string str.assign(" ");

string str.assign(" ",n);        //把字符串前n个字符赋给str

string str2.assign(str1);        

string str.assign(n,'c');        //n个c字符

        拼接:

重载-+

append

str.append(s,n)        //把字符串s前n个字符追加到当前字符串尾部

str.append(s,start,end)        //把字符串s的start至end部分的字符追加到当前字符串尾部

        查找和替换:

find:

str.find(s,n);        //从n开始查询str中是否存在s,n默认为0

rfind:

rfind是从右向左查找,find是从左向右查找

replace:

str.replace(start,end,s)         //当字符串长度大于截取长度时,将从start位置完全替换为s

        比较:

str1.compare(str2) == 0;

        存取:

(str.length() == str.size())

str[ ] == str.at( )

        插入和删除:

str.insert(pos,s);

str.insert(pos,n,c);        //pos位置插入n个c

str.erase(pos,n);

        子串:

str.substr(start,end);

四、项目

1.通讯录管理系统

系统需求:

添加、显示、删除、查找、修改联系人等功能。

显示菜单:

void showMenu(){
    cout << "1.添加联系人" << endl;
    cout << "2.显示联系人" << endl;
    cout << "3.删除联系人" << endl;
    cout << "4.查找联系人" << endl;
    cout << "5.修改联系人" << endl;
    cout << "6.清空联系人" << endl;
    cout << "0.退出系统" << endl;
}

联系人:

信息包括:姓名,性别,年龄,联系电话,家庭住址

struct people
{
    string name;
    string sex;
    int age;
    string phone;
    string address;
};

*性别值只可在[男,女]中选择:使用枚举enum SEX

*电话限制为11位数字:使用<regex>库

std::regex phonePattern("^1[3-9]\\d{9}$");

std::regex_match(phone, phonePattern);
添加:
void add_p(BOOKS *b){
    if(b->id >= MAX){
        cout << "通讯录已满" << endl;
    }else{
        b->id ;
        string name;
        cout << "请输入姓名" << endl;
        cin >> name;
        b->p[b->id].name=name;

        string sex;
        cout << "请输入性别:男,女" << endl;
        startsex:
        cin >> sex;
        if(sex == "男" || sex == "女"){
            b->p[b->id].sex=sex;
        }else{
            cout << "性别输入错误,请重新输入" << endl;
            goto startsex;
        }
            
        int age;
        cout << "请输入年龄" << endl;
        cin >> age;
        b->p[b->id].age=age;

        string phone;
        cout << "请输入手机号" << endl;
        startphone:
        cin >> phone;
        std::regex phonePattern("^1[3-9]\\d{9}$");
        if(std::regex_match(phone, phonePattern)){
            b->p[b->id].phone=phone;
        }else{
            cout << "电话号输入错误,请重新输入" << endl;
            goto startphone;
        }

        string address;
        cout << "请输入地址" << endl;
        cin >> address;
        b->p[b->id].address=address;

        cout << "添加完成" << endl;
        b->id++;
        system("pause");
        system("cls"); //清屏
    }
}

换行输出: std::endl 可以用于在输出文本后添加一个换行符,以确保下一行内容从新的一行开始。

当你操作指向对象的指针时,使用 -> 来代替 .

三目运算符: x == num ?x1:x2         若x=num则输出x1,否则输出x2。

删除:

数据前移

int findPeople(BOOKS *b,string name){
    for(int i=0; i<b->id ; i++){
        if (b->p[i].name == name){
            return i;
        }
    }
    return -1;
}

void delPeople(BOOKS *b){
    cout << "请输入需要删除的联系人姓名" << endl;
    string name;
    cin >> name ;
    int p = findPeople(b,name);
    if(p==-1){
        cout << "查无此人,请重新输入" << endl;
    }else{
        for(int i=p;i<b->id;i++){
            b->p[i].name = b->p[i+1].name;
            b->p[i].sex = b->p[i+1].sex;
            b->p[i].age = b->p[i+1].age;
            b->p[i].phone = b->p[i+1].phone;
            b->p[i].address = b->p[i+1].address;
        }
        b->id--;
        cout << "删除成功" << endl;
    }
    system("pause");
    system("cls"); //清屏
}

查找:
void showPeople(BOOKS *b){
    cout << "请输入需要查找的联系人姓名" << endl;
    string name;
    cin >> name ;
    int i = findPeople(b,name);
    if(i==-1){
        cout << "查无此人,请重新输入" << endl;
    }else{
        cout << "id:        " << i << endl;
        cout << "name:      " << b->p[i].name << endl;
        cout << "sex:       " << b->p[i].sex << endl;
        cout << "age:       " << b->p[i].age << endl;
        cout << "phone:     " << b->p[i].phone << endl;
        cout << "address:   " << b->p[i].address << endl;
        cout << "查询完成" << endl;
    }
    system("pause");
    system("cls"); //清屏
}
修改:
void chagePeople(BOOKS *b){
    cout << "请输入需要修改的联系人姓名" << endl;
    string name;
    cin >> name ;
    int i = findPeople(b,name);
    if(i==-1){
        cout << "查无此人,请重新输入" << endl;
    }else{
        string name;
        cout << "原姓名:" << b->p[i].name << " 请输入需要修改后的联系人姓名" << endl;
        cin >> name;
        b->p[i].name=name;

// **************sex**********************
        string sex;
        cout << "原:" << b->p[i].sex << " 请输入性别:男,女" << endl;
        startsex:
        cin >> sex;
        if(sex == "男" || sex == "女"){
            b->p[i].sex=sex;
        }else{
            cout << "性别输入错误,请重新输入" << endl;
            goto startsex;
        }

// **************age**********************            
        int age;
        cout << "原:" << b->p[i].age << " 请输入年龄:" << endl;
        cin >> age;
        b->p[i].age=age;

// **************phone**********************
        string phone;
        cout << "原:" << b->p[i].phone << " 请输入手机号" << endl;
        startphone:
        cin >> phone;
        std::regex phonePattern("^1[3-9]\\d{9}$");
        if(std::regex_match(phone, phonePattern)){
            b->p[i].phone=phone;
        }else{
            cout << "电话号输入错误,请重新输入" << endl;
            goto startphone;
        }

// **************address**********************
        string address;
        cout << "原:" << b->p[i].address << " 请输入地址" << endl;
        cin >> address;
        b->p[i].address=address;

// **************finish**********************
        cout << "修改完成" << endl;
    }
    system("pause");
    system("cls"); //清屏
}
清空:
void clean(BOOKS *b){
    b->id = 0 ;
    cout << "通讯录已清空" << endl;
    system("pause");
    system("cls"); //清屏
}

主函数:

int main(){
    int select;
    BOOKS book;
    book.id = 0;

    while(1){
        showMenu();
        cin >> select;
        switch (select)
        {
        case 0:
            cout << "即将退出,欢迎下次使用" << endl;
            system("pause");
            return 0;
        case 1: //添加联系人
            add_p(&book); //使用地址传参,避免了复制整个对象的开销,节省了内存和时间。
            break;
        case 2: //显示联系人
            showAll(&book);
            break;
        case 3: //删除
            delPeople(&book);
            break;
        case 4: //查找
            showPeople(&book);
            break;
        case 5: //修改
            chagePeople(&book);
            break;
        case 6: //清空
            break;
        default:
            cout << "输入错误,请重新输入" << endl;
            break;
        }
    }
    return 0;
}

2.职工管理系统

(1)需求分析:

面向角色:普通员工、经理和老板;

主要功能:

  • 增加员工信息、
  • 显示员工信息、
  • 删除离职员工信息、
  • 修改员工信息、
  • 查找员工信息、
  • 按照编号排序、
  • 清空所有文档
  • 退出管理程序;

(2)管理类

  • 与用户交互界面
  • 对职工增删改查的操作
  • 与文件的读写交互

3.webServer

信号量 Semaphore:

信号:简单来说就是消息,是由用户、系统或者进程发送给目标进程的信息,用来通知目标进程某个状态的改变或系统异常,对应的是异步(任务可挂起)的场景。

信号量:首先是一个变量,其次是计数器。它是多线程环境下使用的一种设施,信号量在创建时需要设置一个初始值,表示同时可以有几个任务(线程)可以访问某一块共享资源。(当前可用资源量)

  • 一个任务要想访问共享资源,前提是信号量大于0,当该任务成功获得资源后,将信号量的值减 1;
  • 若当前信号量的值小于 0,表明无法获得信号量,该任务必须被挂起,等待信号量恢复为正值的那一刻;
  • 当任务执行完之后,必须释放信号量,对应操作就是信号量的值加 1。

另外,对信号量的操作(加、减)都是原子的。互斥锁(Mutex)就是信号量初始值为 1 时的特殊情形,即同时只能有一个任务可以访问共享资源区。

信号量直到 C++20 才被支持,那 C++11 通过互斥锁和条件变量实现。

互斥锁mutex:

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

同一时刻,只能有一个线程持有该锁

pthread_mutex_init()          //功能:初始化一个互斥锁

pthread_mutex_destroy()   //功能:销毁一个互斥锁

pthread_mutex_lock()       //功能:加锁

pthread_mutex_trylock()  //功能:尝试加锁

pthread_mutex_unlock()    //功能:解锁

以上5个函数的返回值都是:成功返回0, 失败返回错误号。

 pthread_mutex_t 类型,互斥锁

pthread_mutex_t   mutex; 变量mutex只有两种取值1、0。

条件变量

  • 条件变量是线程可用的另一种同步机制
  • 条件变量给多个线程提供了一个会合的场所
  • 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
  • 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样

五、算法

1.链表:

分为三部分:即前向指针,数据,后项指针

2.动态规划:

记忆化搜索、空间换时间、带备忘录的递归、递归树的剪枝

leetcode:剑指 Offer 19. 正则表达式匹配

初始化:

需要先初始化 dp 矩阵首行,以避免状态转移时索引越界。

dp[0][0] = true: 代表两个空字符串能够匹配。

//匹配到*号的时候代码逻辑
if(p.charAt(i) === '*'){ //即dp第i+1列为*
  dp[0][i+1] = dp[0][i-1]
}

建立二维数组

假设s=abbcddd,p=a*b*.dd*,因此先建立一张对应二维数组的表:

当s为"":

.可以为任何字符,但是不能为空,所以不匹配。

    dp[0][0] = true;
    for (let i = 1; i < p.length; i++) {
        if (p.charAt(i) === "*") { //即dp第i+1列为*
            dp[0][i + 1] = dp[0][i - 1];
        }
    }

匹配状态转移

p[j]为普通字符时:如果s[i]===p[j],那么通过dp[i+1][j+1]=dp[i][j]判断

p[j]为.时: 可以把p[j]看成任意但不为空的字符,也是通过dp[i+1][j+1]=dp[i][j]判断

p[j]为*时: 1.如果p[j-1]!==s[i]&&p[j-1]!=='.'时,和上面的dp[0][j+1]=dp[0][j-1]的逻辑一样,把前一个*的状态值转移过来。即匹配零次。

p[j-1]==s[i]时,对*可以匹配成功的情况可以分为三种:假设p[j-1]=b,出现的个数为n

  • n=0时,复用上面的逻辑,即dp[i+1][j+1]=dp[i+1][j-1]
  • n=1时,dp[i+1][j+1]只需要等于dp[i+1][j]的值即可
  • n>=2时,这个时候dp[i+1][j]和dp[i+1][j-1]都有可能为false。那么在出现多个b后dp[i+1][j+1]可以根据dp[i][j+1]判断

p.charAt(j) === '*'时

  if (p.charAt(j - 1) !== s.charAt(i) && p.charAt(j - 1) !== '.') {
      dp[i + 1][j + 1] = dp[i + 1][j - 1]
  } else {
      dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1] || dp[i + 1][j - 1])
  }

 代码仅供理解使用:

#define str(x) (x-1) //字符串和dp矩阵下标转换
class Solution {
public:
    bool isMatch(string s, string p) {
        int s_size = s.size()+1;
        int p_size = p.size()+1;
        vector<vector<bool>> dp(s_size,vector<bool>(p_size,false));
        //初始化
        dp[0][0] = true;
        for(int i=1;i<p_size;i++){
            dp[0][i] = dp[0][i-2] && p[str(i)] == '*';
        }
        //状态转换
        for(int i=1;i<s_size;i++){
            for(int j=1;j<p_size;j++){
                if(p[str(i)] == '*'){
                    //1.匹配0次
                    if(dp[i][j-2]) dp[i][j]=true;
                    //2.匹配多次(s[str(i)]与p[str(i)]匹配,或s[str(i)]与.匹配)
                          //dp[i - 1][j]为true则代表匹配0次情况已经出现过
                    else if(dp[i - 1][j] && p[str(i-1)]==s[str(i-1)]) dp[i][j]=true;  
                    else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true;
                }else{
                     //dp[i - 1][j - 1]代表前ij个字符是匹配的
                    if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true;
                    else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true;
                }
            }
        }

        return dp[s_size - 1][p_size - 1];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值