C++核心编程(黑马程序员)

目录

C++核心编程

程序的内存模型:

        代码区:

        全局区:

        栈区:

        堆区:

new与delete运算符:

c++引用:引用的本质在c++内部实现是一个指针常量。

函数的默认参数:

函数占位参数:

函数重载:

类和对象:

封装:封装时C++面向对象三大特性之一

        封装的意义:

        struct和class的区别:

对象特性:

       构造函数和析构函数:

        拷贝构造函数:

        深拷贝和浅拷贝:

        初始化列表:C++提供了初始化列表语法,用来初始化属性。

        类对象作为类成员:

        静态成员:

        成员变量和成员函数分开存储:

        this指针:

        空指针访问成员函数:

        const修饰成员函数:

        友元:关键字:friend

运算符重载:

                加号运算符重载:

                左移运算符重载:

                递增运算符重载:

                赋值运算符重载:

                关系运算符重载:

                函数调用运算符重载:

继承:

                继承的基本语法:

                继承方式:

                继承中的对象模型:   

                继承中的构造和析构顺序:

                继承中同名成员处理方式:

                继承同名静态成员的处理方式:

              多继承语法:

                菱形继承:

多态:

                多态的原理剖析:

               纯虚函数与抽象类:

               虚析构和纯虚析构:

C++文件操作:

                文本文件:

                二进制文件:

C++提高编程:

模板:

                模板的概念:

                函数模板:

                        函数模板语法:

                函数模板注意事项:

                普通函数和函数模板的区别:

                普通函数与函数模板的调用规则:

                模板的局限性:

                类模板:

                类模板与函数模板的区别:

                类模板中成员函数的创建时机:

                类模板对象做函数参数:

                类模板与继承:

                类模板成员函数类外实现:

                类模板分文件编写:

                类模板与友元:

STL(Standard Template Library,标准模板库):

                string容器: 

                vector容器:

                deque容器:

                stack容器:

                queue容器:

                 list容器:

                set/multiset容器:

                pair对组:

                map/multimap容器:

                函数对象:

                谓词:

                内建函数对象:

                STL常用算法:

                1.常用遍历算法:

                for_each:

                transform:

                2.常用查找算法:

                find:

                find_if:

                adjacent_find:

                binary_search:

                count:

                count_if:

                3.常用排序算法:

                sort:

                random_shuffle:

                merge:

                reverse:

                4.常用拷贝和替换算法:

                copy:

                replace:

                replace_if:

                swap:

                5.常用算术生成算法:

                accumulate:

               fill:

                6.常用集合算法: 

                set_intersection:

                set_union:

                set_difference:


C++核心编程

  • 程序的内存模型:

           意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。       

 c++在程序运行前分为全局区和代码区。

        代码区:

        1.存放cpu执行的机器指令。

        2.代码区是共享的,共享的意义是对于频繁被执行的程序,只需要在内存中有一份代码即可。

        3.代码区是只读的,以防止程序意外的修改了它的指令。   

       

        全局区:

         1.全局变量和静态变量存放在此。

         2.全局区还包含了常量区,字符串常量和其他常量(const 修饰的全局变量)也存放在此。

         3.该区域的数据在程序结束后由操作系统释放。

        栈区:

         1.由编译器自动分配释放,存放函数的参数值(形参),局部变量等。

         2.注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。

        堆区:

         1.由程序员分配释放,若程序员不释放,程序结束时由操作系统回收。

         2.在c++中主要利用new在堆区开辟内存,由delete释放。

  • new与delete运算符:

int *a = new int(10);

delete a;//释放内存

int *arr = new int[10];

delete[] arr;
  • c++引用:引用的本质在c++内部实现是一个指针常量。

        1.引用必须初始化。int &b;//错误的

        2.引用在初始化后不能发生改变。

        3.注意:不要返回局部变量的引用。

        4.如果函数的返回值是引用,这个函数调用可以作为左值。

int a = 10;

int d = 30;

int &b = a;//引用

b=d;//赋值操作,而不是更改引用,此时b=30,a=30

int &c;//错误的




//引用作为函数参数
int swap(int& a,int& b)
{
    int temp=a;
    a=b;
    b=temp;
}

int& test()//不要这样做!!!
{
    int a=10;//局部变量存放在栈区
    return a;
}

int &ref = test();

cout<<ref;//第一次结果正确,因为编译器做了保留
cout<<ref;//第二次结果错误,因为a的内存已经释放

int& testt()//这样可以
{
    static int a = 10;//静态变量存放在全局区
    return a;
} 

int &ref2=testt();

cout<<ref2;
cout<<ref2;//均正确

testt() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值。

cout<<ref2;//输出结果为1000




int a = 10;

int& ref = a;//自动转换为int* const ref = &a; 指针常量指向不可改,这也说明了为什么引用不可更改。

ref = 20;//内部发现ref是引用,自动帮我们转换为:*ref = 20;  
//常量引用
//使用场景:用来修饰形参,防止误操作

int a = 10;

int& ref = 10;//错误的,引用必须引一块合法的内存空间。

const int& ref = 10;//正确的
//加上const以后,编译器内部将代码修改为 int temp = 10; const int& ref = temp; 

ref = 20;//错误的,加上const之后变为只读,不可修改


//防止误操作
void showVal(int& val)
{
    val=1000;//原本只想打印val,却不小心修改了数据。
    cout<<val;
}

void showVal(const int& val)
{
    val=1000;//const修饰之后,函数内无法修改val值。
    cout<<val;
}
  • 函数的默认参数:

       1.如果某个位置已经有了默认参数,那么从这个位置往后的参数也必须要有默认参数。

       2.如果函数声明有默认参数,函数实现就不能有默认参数,即声明与实现只能有一个有默认参数

int fuc(int a,int b,int c)
{
    return a+b+c;
}

int func_1(int a = 10, int b = 10, int c = 10)
{
    return a+b+c;
} 

//如果某个位置已经有了默认参数,那么从这个位置往后的参数也必须要有默认参数

int func_2(int a, int b = 10,int c)//不行
{
    return a+b+c;
}

int func_3(int a , int b = 10,int c = 10)//可以
{
    return a+b+c;
}

//如果函数声明有默认参数,函数实现就不能有默认参数,即声明与实现只能有一个有默认参数

int func_4(int a,int b,int c);


int func_4(int a , int b = 10,int c = 10)
{
    return a+b+c;
}

  • 函数占位参数:

        1.c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。

//占位参数

void func(int a)
{
    cout<<"this is func";
}

func(10);


void func_1(int a,int)//第二个参数即为占位参数
{
    cout<<"this is func_1";
}

func_1(1,2);//必须要有两个参数才能调用

//占位参数还可以有默认参数

void func_2(int a,int = 100)
{
    cout<<"this is func_2";
}

func_2(2);
func_2(2,3);//都可调用
  • 函数重载:

      函数名可以相同,提高复用性。

      函数重载满足条件:

        1.同一个作用域下。

        2.函数名称相同。

        3.函数参数类型不同 或者 个数不同 或者 顺序不同。

//函数重载:返回值必须相同

void func()
{
    cout<<"func 的调用";
}

void func(int a)
{
    cout<<"func 的调用";
}

void func(double a)
{
    cout<<"func 的调用";
}

void func(int a,double b)
{
    cout<<"func 的调用";
}

void func(double a,int b)
{
    cout<<"func 的调用";
}

        注意事项:

        1.引用作为重载条件

        2.函数重载碰到默认参数。

//函数重载的注意事项

//1.引用作为重载的条件

void func(int &a)
{
    cout<<"func 的调用";
}

void func(const int &a)
{
    cout<<"func 的调用";
}

func(a);//调用第一个
func(10);//调用第二个

//2/函数重载碰到默认参数

void func2(int a,int b = 10)
{
    cout<<"func 的调用";
}

void func2(int a)
{
    cout<<"func 的调用";
}

func2(10);//当函数重载碰到默认函数,出现二义性,报错,尽量避免

类和对象:

  • 封装:封装时C++面向对象三大特性之一

        封装的意义:

        1.将属性和行为作为一个整体,表现生活中的事物。

        2.将属性和行为加以权限控制。

        

//设计一个圆类,求圆的周长

# define pi 3.15


//类中的属性和行为 我们统一称为 成员
//属性  成员属性 成员变量
//行为 成员函数 成员方法 


class Circle
{

    //访问权限:
    //公共权限 public 类内可以访问,类外也可访问

    //保护权限 protected 类内可以访问,类外不可访问 子类可以访问父类中的保护内容

    //私有权限 private 类内可以访问,类外不可访问  子类不可访问父类中的私有内容

public:

    //行为:获取圆的周长
    
    double calculateZC()
    {
        return 2*pi*r;
    }
    //set方法
    void setR(double a)
    {
        r=a;
    }
    //get方法
    double getR()
    {
        return r;
    }


private:
        //属性:半径

    double r;

};


int main()
{
    Circle c;//实例化
    
}

        struct和class的区别:

        1.struct默认权限为公共

        2.class默认权限为私有

class c1
{
    int a;//默认为私有
};

struct c2
{
    int a2;//默认为公共
};
//封装:

point.h 头文件

#pragma once
#include<iostream>
using namespace std;

class Point
{
public:
    
    void setX(int a);//只需要声明,加分号补全
    
    int getX();
    ...
private:

    int x;
    int y;

};



point.cpp 源文件//源文件中只需要函数实现

#include "point.h"

void Point::setX(int a)//添加作用域
{
    x=a;
}

int Point::getX()
{
    return x;
}



//使用时:只需包含头文件

include<bits/stdc++.h>
include "point.h"

using namespace std;

...

       

  • 对象特性:

       构造函数和析构函数:

        1.构造函数:主要作用在于创建对象时为对象成员属性赋值。

        2.析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理操作。

        3.如果我们不提供,编译器会自动提供这两个函数。

        4.编译器提供的是空实现。

        

        创建一个类,C++编译器会给每个类都添加至少4个函数:

        1.默认构造函数(空实现)

        2.默认析构函数(空实现)

        3.默认拷贝构造(值拷贝)

        4.赋值运算符 operator= 对属性进行值拷贝

        构造函数调用规则:

        1.如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造。

        2.如果用户定义拷贝构造函数,c++不会再提供其他构造函数。

        拷贝构造函数:

        用处:

        1.把对象作为实参进行函数调用时,系统自动调用拷贝构造函数实现把对象值传递给形参对象。

        2.当函数的返回值为对象时,系统自动调用拷贝构造函数对返回对象值创建一个临时对象,然后再将这个临时对象值赋给接收函数返回值的对象。

class Point
{
public:

//构造函数:1.无返回值,无需写void . 2.函数名与类名相同。 3. 可以有参数,可以发生重载。 4.创建对象时,系统会自动调用一次.

    Point(){}
    Point(int ix,int iy)
    {
        x=ix;
        y=iy;
    }

//拷贝构造函数:
//1.名字与类名相同
//2.只有一个形参数,该参数是该类对象的引用

    Point(const Point& p)
    {
        x=p.x;
        y=p.y;
    }

//析构函数:1.无返回值,无需写void . 2.函数名与类名相同,在前面加 ~ 。 3. 不可以有参数,不可以发生重载。 4.销毁对象时,系统会自动调用一次.

~Point()
{
    cout<<"析构函数的调用";
}


private:
    int x;
    int y;
}


//构造调用的三种方法:

//1.括号法:

Point p1();//这样编译器会认为这是一个函数的声明

Point p2;//实例化对象


//2.显示法:

Point(2,3);//匿名对象                                           对象声明
                                                     //          ^        
Point(p2);//错误的                                               ||
//不要利用拷贝构造函数初始化匿名对象,编译器会认为 Point(p2) === Point p2 重定义了

Point p3 = Point(2,3);有参构造

Point p4 = Point(p3);//拷贝构造

//3.隐式转换法:

Point p5 = 10; 相当于 Point p5 = Point(10);

Point p6 = p5;拷贝构造 相当于 Point p6 = Point(p5);

        深拷贝和浅拷贝:

        浅拷贝:简单的赋值操作。

        深拷贝:在堆区重新申请空间,进行拷贝操作。

        如果属性有在堆区开辟的:

        1.在析构函数中我们要自己释放掉这块内存空间(delete),默认析构函数只会释放掉栈区的空间

        2.一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

a492b75458d747dbadf3c01063a4c937.png

class Person
{
    
public:

    Person(){}

    person(int age, int h)
    {
        m_Age=age;
        m_Height = new int(h);
    }
    
    //自己实现拷贝构造函数,解决浅拷贝带来的问题
    Person(const Person& p)
    {
        m_Age = p.m_Age;
        //m_Height = p.m_Height;//编译器默认实现的代码,会带来问题
        
        //深拷贝操作
        m_Height = new int(*p.m_Height);
    }
    
    
    
    ~Person()
    {
        if(m_Height!=nullptr)
        {
            delete m_Height;
            m_Height=nullptr;
        }

    }
    
    int m_Age;
    int *m_Height;
};


Person p1(18,180);

Person p2(p1);//此时如果调用浅拷贝会出现堆区内存重复释放的问题,因为浅拷贝会将p1在堆区存放身高数据的地址直接拷贝至p2,析构时p2会释放掉这块堆区的内存,那么等p1析构时其实这块内存已经在p2析构时被释放了,但p1并不知道,会再次释放这块内存,导致程序异常。

        

        初始化列表:C++提供了初始化列表语法,用来初始化属性。

        

        语法:构造函数():属性1(值1),属性2(值2)...{}

class Cla
{
public:
    
    //传统初始值操作
//    Cla(int a,int b)
//    {
//        m_a=a;
//        m_b=b;
//   }

    
    //初始化列表初始化属性

    Cla(int a,int b):m_a(a),m_b(b){}//可以
    
    Cla(int a,int b):m_a(a),m_b(b)//可以
    {
        //也可以写他的的东西    
    }

    int m_a;

    int m_b;


};

        类对象作为类成员:

        1.C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。

        2.构造的顺序是: 先调用对象成员的构造,再调用本类的构造        

        3.析构顺序与构造顺序相反。

class A{};

class B
{
public:

    A a;  

};

//先构造A对象,再构造B对象。
//析构相反。

        静态成员:

                静态成员变量:

                1.所有对象共享同一份数据。

                2.在编译阶段分配内存。

                3.类内声明,类外初始化。

class A
{
public:

    staric int x;    

private://静态成员变量也有访问权限
    
    static int y;

};

int A::x = 100;
int A::y = 500;

//A a1;
//cout<<a1.x;//100

//A a2;
//a2.x = 200;

//cout<<a1.x;//200

//静态成员变量 不属于某个对象,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式

//1.通过对象进行访问

A a;
cout<<a.x;//100


//2.通过类名进行访问

cout<<A::x;//100

cout<<A::y;//类外访问不到私有静态成员变量

                静态成员函数:

                1.所有对象共享同一个函数。

                2.静态成员函数只能访问静态成员变量。

               

class A
{
public:

    //静态成员函数
    static void func()
    {
        x = 300;//静态成员函数 可以访问 静态成员变量
        y = 10;//不行,静态成员函数 不能访问 非静态成员变量, 无法区分这个y属于哪个对象
    }

    //静态成员变量

    static int x;

    //非静态成员变量
    int y;
private:

    //静态成员函数也有访问权限
    
    static void func2(){}


};

int A::x = 100;


//访问:

//1.通过对象访问

A a;

a.func();


//2.通过类名访问

A::func();

A::func2();//错误的,类外访问不到私有静态成员函数

        C++对象模型和this指针:

        成员变量和成员函数分开存储:

        1.在C++中,类内的成员变量和成员函数分开存储。

        2.只有非静态成员变量采属于类的对象上。

        3.每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。

class A
{

};

A a;
//空对象占用的内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间, 是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout<<sizeof(a);//1


//成员变量 和 成员函数 是分开存储的
class B
{
    int x;//非静态成员变量, 属于类的对象上
    
    static int y;//静态成员变量, 不属于类的对象上

    void func(){} //非静态成员函数, 不属于类的对象上

    static void func2(){} //静态成员函数, 不属于类的对象上
    
};

int B::y = 5;

B b;
//占用4个字节,因为有个非静态成员变量,类型为int
cout<<sizeof(b);//4

        this指针:

        前言: 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是: 这一块代码如何区分哪个对象调用自己的呢?

        C++通过特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。

        1.this指针是隐含每一个非静态成员函数内的一种指针。

        2.this指针不需要定义,直接使用即可。

        this指针的用途:

        1.当形参和成员变量同名时,可用this指针来区分。

        2.在类的非静态成员函数中返回对象本身,可用 return *this;

//解决名称冲突
class A
{
public:

    A(int x)
    {
        x=x;
    }
    
    int x;        
   
};

A a(18);
cout<<a.x;//此时值不为18,而是一个乱值。

//********************************************

//解决名称冲突

class A
{
public:

    A(int x)
    {    //this指针指向被调用的成员函数所属的对象。
        this->x=x;
    }
    
    int x;        
   
};

A a(18);
cout<<a.x;//此时值为18

//返回对象本身用*this

class Person
{
public:

    Person(int age)
    {
         this->age=age;   
    }

    void PersonAddAge(Person& p)
    {
        this->age += p.age;
    }


    int age;

};

Person p1(10);

Person p2(20);

p2.PersonAddAge(p1);

cout<<p2.age;//30

//***********************************


class Person
{
public:

    Person(int age)
    {
         this->age=age;   
    }
        //如果是以值的方式返回,会调用拷贝构造函数创建一个新的对象
        //引用的方式返回:不会创建新的对象,而是返回该对象
    Person& PersonAddAge(Person& p)
    {
        this->age += p.age;
        
        return *this;//返回对象本身用*this
    }


    int age;

};

Person p1(10);

Person p2(20);

//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

//如果是以值的方式返回,结果为30
//以引用的方式返回,结果为50
cout<<p2.age;

        空指针访问成员函数:

        1.C++中空指针也是可以调用成员函数的,但是也要主义有没有用到this指针。

        2.如果用到this指针,需要加以判断保证代码的健壮性。

//空指针调用成员函数

class Person
{
public:

    void showClassName()
    {
        cout<<"this is Person class ";
    }


//    void showAge()
//    {    
//        //报错原因时因为传入的指针为nullptr,导致this->m_Age错误。
//        cout<<"age = "<<m_Age;
//    }

    void showAge()
    {    
        
        if(this == nullptr) return ;//提高代码的健壮性
        cout<<"age = "<<m_Age;
    }

    int m_Age;
};

Person* p = nullptr;

p->showClassName();//可以正常调用,没有问题

p->showAge();

        

        const修饰成员函数:

        常函数:

        1.成员函数加const后我们称这个函数为常函数。

        2.常函数内不可修改成员属性。

        3.成员属性声明时加关键字mutable后,在常函数中依然可以修改。

        常对象:

        1.声明对象前加const称该对象为常对象。

        2.常对象只能调用常函数。

//常函数

class Person
{
public:
    //this的本质 是指针常量 指针的指向是不能修改的
    //Person * const this


    //在成员函数后加const,修饰的是this指针, 让指针指向的值也不可修改
    void showPerson() const //相当于 const Person * const this
    {
        //this->m_A = 100 ;//函数后加const之后属性就无法修改了
        
        this->m_B = 100;
    }  

    void func(){}  

    int m_A;
    
    mutable int m_B;//特殊变量,即使在常函数中,也可修改这个值,加关键字mutable
 
};

//常对象

const Person p;//在对象前加const, 变为常对象

p.m_A = 100;//报错,不能修改

p.m_B = 100;//没错,可以修改。m_B是特殊值, 在常对象下也可以修改

//常对象只能调用常函数

p.showPerson();

p.func();//报错, 常对象不可以调用普通成员函数, 因为普通成员函数可以修改属性

        友元:关键字:friend

                友元的目的是让一个函数或者类访问另一个类中私有友元。

                友元的三种实现:

                1.全局函数做友元。

                2.类做友元。

                3.成员函数做友元。

        

//全局函数做友元

class Building
{
    
    //goodGay全局函数是Building类的友元,可以访问Building中的私有成员。
    friend void goodGay(Building *building);

public:
    
    Building()
    {
        m_SittingRoom = "客厅";
        
        m_BedRoom = "卧室";
    }       

    string m_SittingRoom;//客厅

private:

    string m_BedRoom;//卧室

};

//全局函数:
void goodGay(Building *building)
{
    cout<<building->string m_SittingRoom;

    cout<<building->string m_SittingRoom;
}
//类做友元

class Building;

///
class GoodGay
{
public:

    GoodGay();
    
    void visit();//参观函数, 访问Building中的属性
    
    Building* building;    

};

//类外写成员函数
GoodGay::GoodGay()
{
    //创建建筑物对象
    building = new Building;
}

void GoodGay::visit()
{
    cout<<building->m_SittingRoom;//GoodGay类访问 客厅

    cout<<building->m_BedRoom;GoodGay类访问 卧室
}

///
class Building
{
    
    //GoodGay类Building类的友元,可以访问Building中的私有成员。
    friend class GoodGay;

public:
    
    Building();

    string m_SittingRoom;//客厅

private:

    string m_BedRoom;//卧室

};


//类外写成员函数
Building::Building()
{
    m_SittingRoom = "客厅";
        
    m_BedRoom = "卧室";
}       



GoodGay gg;
gg.visit();
//成员函数做友元

class Building;

///
class GoodGay
{
public:

    GoodGay();
    
    void visit();//让visit函数 可以访问B uilding中私有成员
    void visit2();//让visit2函数 不可以访问 Building中私有成员
    
    Building* building;    

};

//类外写成员函数
GoodGay::GoodGay()
{
    //创建建筑物对象
    building = new Building;
}

void GoodGay::visit()
{
    cout<<building->m_SittingRoom;//visit() 可以 访问 客厅

    cout<<building->m_BedRoom;//visit() 可以 访问 卧室
}

void GoodGay::visit2()
{
    cout<<building->m_SittingRoom;//visit2() 可以 访问 客厅

    //cout<<building->m_BedRoom;//visit2() 不可以 访问 卧室
}

///
class Building
{
    
    //GoodGay类中的成员函数visit() 为Building类的友元,可以访问Building中的私有成员。
    friend void GoodGay::visit();

public:
    
    Building();

    string m_SittingRoom;//客厅

private:

    string m_BedRoom;//卧室

};
/
Building::Building()
{
    m_SittingRoom = "客厅";
        
    m_BedRoom = "卧室";
}       

  • 运算符重载:

                概念:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型。

                注意:

                1.对于内置的数据类型的表达式的运算符是不可能改变的,即你不能通过运算符重载让 1 + 1 = 0。

                2.不要滥用运算符重载。

                   

                加号运算符重载:

                        实现两个自定义数据类型相加的运算。

                   

class Person
{
public:


   //1.成员函数重载 + 号
   Person operator+(Person &p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }


   Person operator+(int num)
    {
        Person temp;
        temp.m_A = this->m_A + num;
        temp.m_B = this->m_B + num;
        return temp;
    }


    int m_A;
    
    int m_B;    

};

//2.全局函数重载 + 号
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}

Person p1;

p1.m_A = 10;

p1.m_B = 10;


Person p2;

p2.m_A = 10;

p2.m_B = 10;

Person p3 = p1 + p2;

//成员函数重载本质调用
Person p3 = p1.operator+(p2);

//全局函数重载本质调用
Person p3 = operator+(p1,p2);


//运算符重载 也可以发生函数重载

Person p4 = p1 + 10;

        

                左移运算符重载:

                        作用:可以输出自定义数据类型

//左移运算符重载

class Person
{

friend ostream& operator<<( ostream &cout ,Person &p );

public:


   //1.利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p<<cout
    //不会利用成员函数重载 << 运算符,因为无法实现 cout 在左侧
//   void operator<<( cout )
//    {
//
//    }


    int m_A;
    
    int m_B;    

};

//只能利用全局运算符重载左移运算符
ostream& operator<<( ostream &cout ,Person &p )//本质 operator<<(cout,p) 简化 cout<<p;
{
    cout<<"m_A = "<<p.m_A<<", m_B = "<<p.m_B;
    return cout;
}

Person p;

p.m_A = 10;

p.m_B = 20;

cout<<p;

        

                递增运算符重载:

                        作用:通过重载递增运算符,实现自己的整型数据。   

//重载递增运算符

//自定义整型
class MyInteger
{
    friend ostream& operator<<(ostream& cout, MyInteger& myint);
    // friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
    MyInteger()
    {
        m_Num = 0;
    }

    //重载前置++运算符
    MyInteger& operator++()
    {
        m_Num++;//先自增运算
        return *this;//在返回
    }

    //重载后置++运算符
    MyInteger* operator++(int)
    {
        MyInteger* temp = new MyInteger(*this);
        m_Num++;
        return temp;
    }

    // MyInteger operator++(int)
    // {
    //     MyInteger temp = *this;
    //     m_Num++;
    //     return temp;
    // }

private:
    int m_Num;

};

//重载左移运算符
ostream& operator<<(ostream& cout, MyInteger& myint)
{
    cout << myint.m_Num;
    return cout;
}

// ostream& operator<<(ostream& cout, MyInteger myint)
// {
//     cout << myint.m_Num;
//     return cout;
// }

    MyInteger myint;

    cout << ++myint << endl;

    cout << *(myint++) << endl;
//  cout << myint++ << endl;


    cout << myint;


            

                赋值运算符重载:

                        如果类中有属性指向堆区,做赋值操作时也会出现浅拷贝问题。

class Person
{
public:

    Person(int age)
    {
        m_Age = new int(age);
    }

    ~Person()
    {
        if (m_Age != nullptr)
        {
            delete m_Age;
            m_Age = nullptr;
        }
    }

        //重载 赋值运算符
    Person& operator=(Person& p)
    {
        //编译器是提供浅拷贝
        //m_Age = p.m_Age;//浅拷贝析构时会发生堆区重复释放的问题

        //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
        if (m_Age != nullptr)
        {
            delete m_Age;
            m_Age = nullptr;
        }
        //深拷贝
        m_Age = new int(*p.m_Age);
        return *this;
    }

    int* m_Age;
    
};

Person p1(18);

Person p2(20);

Person p3(30);

p3 = p2 = p1;

cout << *p1.m_Age << endl;//18
cout << *p2.m_Age << endl;//18
cout << *p3.m_Age;//18

                关系运算符重载:

                        作用:重载关系运算符,可以让两个自定义类型进行对此操作。

class Person
{
public:
    Person(string name, int age)
    {
        m_Name = name;
        m_Age = age;
    }

    //重载 == 号
    bool operator==(Person& p)
    {
        if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
        {
            return true;
        }
        return false;
    }

    //重载 != 号
    bool operator!=(Person& p)
    {
        if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
        {
            return false;
        }
        return true;
    }
    
    string m_Name;
    int m_Age;
    
};


Person p1("tom", 18);

Person p2("mary", 18);

if (p1 == p2)
{
    cout << "相等";
}
else
{
    cout << "不等";
}

if (p1 != p2)
{
    cout << "不等";
}
else
{
    cout << "相等";
}

                函数调用运算符重载:

                        1.函数调用运算符()也可以重载。

                        2.由于重载后使用的方式非常像函数的调用,因此被称为伪函数。

                        3.仿函数没有固定写法,非常灵活。

//函数调用运算符重载

//打印输出类
class MyPrint
{
public:
    //函数调用运算符重载
    void operator()(string test)
    {
        cout << test << endl;
    }
};

void FuncPrint(string test)
{
    cout << test << endl;
}

//仿函数非常灵活,没有固定的写法
//加法类

class MyAdd
{
public:
    int operator()(int num1, int num2)
    {
        return num1 + num2;
    }
};

void test01()
{
    MyPrint myprint;
    
    myprint("hello world");//由于使用起来非常类似于函数调用,因此被称为仿函数。

    FuncPrint("hello world");   
}

void test02()
{
    MyAdd myadd;
    int ret = myadd(100, 200);
    cout << "ret = " << ret << endl;

    //匿名函数对象

    cout << MyAdd()(100, 100) << endl;
    
}

  • 继承:

        继承是面向对象三大特性之一

        有些类与类之间存在特殊关系,例如下图中:

bef9e01895854bf1b56eba4623ab0b12.png

         我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

         这个时候我们就可以考虑利用继承的技术,减少重复代码。

                继承的基本语法:

                

               语法: class 子类 : 继承方式 父类

               子类 也称为 派生类
               父类 也称为 基类       

                

                派生类中的成员,包含两大部分:

                1.一类是从父类继承过来的,一类是自己增加的成员。

                2.从父类继承过来的表现其共性,而新增的成员体现了其个性。

        

        普通实现页面如下:无意义的重复的代码太多

//普通实现页面

//Java页面

class Java
{
public:

    void header()
    {
        cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
    }
    
    void footer()
    {
        cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
    }

    void left()
    {
        cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
    }

    void content()
    {
        cout<<"Java 学科视频"<<endl;
    }

};

//Python页面

class Python
{
public:

    void header()
    {
        cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
    }
    
    void footer()
    {
        cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
    }

    void left()
    {
        cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
    }

    void content()
    {
        cout<<"Python 学科视频"<<endl;
    }

};

//c++页面

class CPP
{
public:

    void header()
    {
        cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
    }
    
    void footer()
    {
        cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
    }

    void left()
    {
        cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
    }

    void content()
    {
        cout<<"C++ 学科视频"<<endl;
    }

};

void test01()
{
    cout<<"Java 下载视频页面如下: "<<endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();

cout<<"****************************************"<<endl;

    cout<<"Python 下载视频页面如下: "<<endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();   

cout<<"****************************************"<<endl;

    cout<<"C++ 下载视频页面如下: "<<endl;
    Cpp cpp;
    cpp.header();
    cpp.footer();
    cpp.left();
    cpp.content();   
}

        继承实现页面如下:

//继承实现页面

//继承的好处: 减少重复代码

//语法: class 子类 : 继承方式 父类

//子类 也称为 派生类
//父类 也称为 基类

class BasePage
{
public:

    void header()
    {
        cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
    }
    
    void footer()
    {
        cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
    }

    void left()
    {
        cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
    }

    void content()
    {
        cout<<"Java 学科视频"<<endl;
    }
};

//java页面

class Java : public BasePage
{
public:

    void content()
    {
        cout<<"Java 学科视频"<<endl;
    }
    
};

//python页面

class Python : public BasePage
{
public:

    void content()
    {
        cout<<"Python 学科视频"<<endl;
    }
    
};

//C++页面

class CPP : public BasePage
{
public:

    void content()
    {
        cout<<"C++ 学科视频"<<endl;
    }
    
};

                继承方式:

                继承方式一共有三种:

                1.公共继承。

                2.保护继承。

                3.私有继承。

aca0d58579964728aa788c4317c5b72e.png

        

//继承方式

//父类
class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

//公共继承

class Son1 : public Base
{
public:
    void func()
    {
        m_A = 10;//父类中的 公共权限成员 到子类中依然是 公共权限

        m_B = 10;//父类中的 保护权限成员 到子类中依然是 保护权限

      //m_C = 10;//父类中的 私有权限成员 子类访问不到

    }
    
};

void test01()
{
    Son s1;
    s1.m_A = 100;
  //s1.m_B = 100;//到Son1中 m_B 是 保护权限 类外访问不到
}

//保护继承

class Son2 : protected Base
{
public:
    void func()
    {
        m_A = 10;//父类中的 公共权限成员 到子类中变为了 保护权限

        m_B = 10;//父类中的 保护权限成员 到子类中变为了 保护权限

      //m_C = 10;//父类中的 私有权限成员 子类访问不到

    }
    
};

void test02()
{
    Son s1;
  //s1.m_A = 100;//到Son2中 m_B 是 保护权限 类外访问不到
  //s1.m_B = 100;//到Son2中 m_B 是 保护权限 类外访问不到
}

//私有继承

class Son3 : protected Base
{
public:
    void func()
    {
        m_A = 10;//父类中的 公共权限成员 到子类中变为了 私有权限成员

        m_B = 10;//父类中的 保护权限成员 到子类中变为了 私有权限成员

      //m_C = 10;//父类中的 私有权限成员 子类访问不到

    }
    
};

class GrandSon3 : protected Son3
{
public:
    void func()
    {
    //  m_A = 10;//祖父类Base中的 公共权限成员, 到了以私有方式继承父类Son3中变为了私有成员,孙子类访问不到。

     // m_B = 10;//祖父类Base中的 保护权限成员, 到了以私有方式继承父类Son3中变为了私有成员,孙子类访问不到。

      //m_C = 10;//祖父类Base中的 私有权限成员, 子类 与 孙子类 都访问不到。

    }
    
};

void test03()
{
    Son s1;
  //s1.m_A = 100;//到Son2中 m_B 是 私有权限 类外访问不到
  //s1.m_B = 100;//到Son2中 m_B 是 私有权限 类外访问不到
}

     

                继承中的对象模型:   

           问题:从父类继承过来的成员, 哪些属于子类对象中

//继承中的对象模型

//父类
class Base
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;//私有成员只是被隐藏了, 但是还是会继承下去
};

class Son : public Base
{
public:

    int m_D; 
};

void test01()
{
    //父类中所有非静态成员属性都会被子类继承下去
    //父类中私有成员属性 是被编译器给隐藏了 因此访问不到, 但确实被继承下去
    cout<<"size of Son = "<<sizeof(Son)<<endl;//16
}

//利用开发人员命令提示工具查看对象模型
//跳转盘符 D:
//跳转文件路径 cd 具体路径下
//dir
//查看命令  cl /d1 reportSingleClassLayout 类名 文件名    (文件名可按tab键自动补齐)


//黑马程序员 C++ P129

        

                继承中的构造和析构顺序:

                子类继承父类后,当创建子类对象时,也会调用父类的构造函数。

                问题:父类与子类的构造和析构顺序是谁前谁后?

                            1.先构造父类,再构造子类。
                            2.析构与构造顺序相反。

//继承中的构造和析构顺序

class Base
{
public:
    Base()
    {
        cout<<"Base构造函数"<<endl;
    }

    ~Base()
    {
        cout<<"Base析构函数"<<endl;
    }
};

class Son : public Base
{
public:
    Son()
    {
        cout<<"Son构造函数"<<endl;
    }

    ~Son()
    {
        cout<<"Son析构函数"<<endl;
    }    
}

void test01()
{
    //Base b;

    //先构造父类,再构造子类
    //析构与构造顺序相反
    Son s;
}

                继承中同名成员处理方式:

                        问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

                        1.访问子类同名对象,直接访问即可。

                        2.访问父类同名对象,需要加作用域。

                        3.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数。

//继承同名成员处理方式

class Base
{
public:
    Base()
    {
        m_A = 100;
    }
    
    void func()
    {
        cout<<"Base - func() 调用"<<endl;
    }
    
    void func(int a)
    {
        cout<<"Base - func(int a) 调用"<<endl;
    }

    int m_A;
}; 

class Son : public Base
{
public:
    Son()
    {
        m_A = 200;
    }

    void func()
    {
        cout<<"Son - func() 调用"<<endl;
    }  
    
    int m_A; 

};

//同名成员属性处理方式
void test01()
{
    Son s;
    cout<<"Son 下 m_A = "<<s.m_A;
    //如果通过子类对象 访问到父类中同名成员, 需要加作用域
    cout<<"Base 下 m_A = "<<s.Base::m_A;
}

//同名成员函数处理方式
void test02()
{
    Son s;
    s.func();//直接调用 ,如果子类中没有同名成员函数,则直接调用父类中的函数,如果子类中有,调用的是子类中的同名成员
    s.Base::func();
    
    //如果 子类 中出现和 父类 同名的 成员函数,子类的同名成员会隐藏掉 父类中所有同名成员函数(包括重载的)
    //如果想访问到父类中被隐藏的同名成员函数,需要加作用域
    s.func(100);//报错
    s.Base::func(100);//正确的
    
}

        

                继承同名静态成员的处理方式:

        问题:继承中同名的静态成员在子类对象上如何进行访问?

                静态成员和非静态成员出现同名,处理方式一致

                        1.访问子类同名对象,直接访问即可。

                        2.访问父类同名对象,需要加作用域。

                        3.同名静态成员处理方式和非静态成员处理方式一致,只不过有两种访问的方式(通过对象 和类名)

               

//继承同名静态成员处理方式

class Base
{
public:
    Base()
    {
        m_A = 100;
    }
    
    static void func()
    {
        cout<<"Base - static void func() 调用"<<endl;
    }

    static void func(int a)
    {
        cout<<"Base - static void func(int a) 调用"<<endl;
    }

    static int m_A;
}; 

int Base::n_A = 100;

class Son : public Base
{
public:
    Son()
    {
        m_A = 200;
    }

    static void func()
    {
        cout<<"Son - static void func() 调用"<<endl;
    } 

    static int m_A;

};

int Son::n_A = 200;

//同名静态成员属性处理方式
void test01()
{
    //1.通过对象访问
    Son s;
    cout<<"Son 下 m_A = "<<s.m_A;
    //如果通过子类对象 访问到父类中静态同名成员, 需要加作用域
    cout<<"Base 下 m_A = "<<s.Base::m_A;

    //2.通过类名访问
    
    cout<<"Son 下 m_A = "<<Son::m_A;
     
    cout<<"Base 下 m_A = "<<Base::m_A;//直接通过父类访问
    
    cout<<"Base 下 m_A = "<<Son::Base::m_A; //第一个:: 代表通过类名方式访问, 第二个:: 代表访问父类作用域下   

}

//同名成员函数处理方式
void test02()
{
    //1.通过对象访问
    Son s;
    s.func();
    s.Base::func();
    
    //2.通过类名访问
    
    Son::func();
    
    Son::Base::func();

    //如果子类中出现和父类同名的静态成员函数,也会隐藏掉父类中所有同名成员函数
    //如果想访问父类中被隐藏的静态同名成员函数,需要加作用域
    Son::Base::func(100);
}

        

              多继承语法:

                语法:class 子类 : 继承方式 父类1 , 继承方式 父类2...

                多继承可能会引发父类中有同名成员出现,需要加作用域区分。

                C++实际开发中不建议使用多继承。

//多继承语法

class Base1
{
public:
    
    Base1()
    {
        m_A = 100;
    }        

    int m_A;
};

class Base2
{
public:
    
    Base2()
    {
        m_A = 100;
      //m_B = 100;
    }
        
    int m_A;
  //int m_B;
};

//子类 需要继承Base1 和 Base2
//语法:class 子类 : 继承方式 父类1 , 继承方式 父类2...
class Son : public Base1, public Base2
{
public:
    Son()
    {
        m_C = 300;
        m_D = 400;
    }

    
    int m_C;
    int m_D;
};

void test01()
{
    Son s;
    
    cout<<sizeof(s);//16

    //cout<<s.Base2::m_B; 

    //当父类中出现同名成员, 需要加作用域区分
    cout<<s.Base1::m_A;
    cout<<s.Base2::m_A;    
}

                菱形继承:

                        概念:

                        1.两个派生类继承同一个基类。

                        2.又有某个类同时继承两个派生类。

                        3.这种继承被称为菱形继承,又或者钻石继承。

                        4.利用虚继承可以解决菱形继承问题。

a702150518f540c9b19c9aebe3bb70a2.png

                菱形继承问题:

                1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。

                2.羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

                3.利用虚继承可以解决菱形继承问题。

f872e3eac2674c759b9b22c601cc06fc.png

//动物类
class Animal
{
public:
    int m_Age;
};

//利用虚继承 解决菱形继承的问题
//继承之前,加上关键字 virtual 变为虚继承
//Animal 类被称为 虚基类

//羊类

class Sheep : virtual public Animal{};

//驼类

class Tuo : virtual public Animal{};

//羊驼类

class SheepTuo : public Sheep, public Tuo
{};

void test01()
{
    SheepTuo st;

    
    //当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
    st.Sheep::m_Age = 18;

    st.Tuo::m_Age = 28;

    //这份数据我们只需要一份就可以,菱形继承导致数据有两份,浪费资源。
}


//当使用虚继承之后,st.Sheep::m_Age,st.Tuo::m_Age,st.m_Age三个都是同一份数据。
void test02()
{
    st.Sheep::m_Age = 18;

    st.Tuo::m_Age = 28;

    
    cout<<st.Sheep::m_Age;//28
    
    cout<<st.Tuo::m_Age;//28

    cout<<st.m_Age;//28
}

      

  • 多态:

                多态是C++面向对象三大特性之一。

                多态分为两类:

                1.静态多态:函数重载和运算符重载属于静态多态,复用函数名。

                2.动态多态:派生类和虚函数实现运行时多态。

                静态多态和动态多态的区别:

                1.静态多态的函数地址早绑定 - 编译阶段确定函数地址。

                2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址。

//多态

//动物类

class Animal
{
public:
    //虚函数 函数前面加virtual
    virtual void speak()
    {
        cout<<"动物在说话";
    }
};


//猫类
class Cat : public Animal
{
public:
    //函数重写 函数返回值类型 函数名 参数列表 完全相同
    void speak()
    {
        cout<<"小猫在说话";
    }

};


//狗类
class Dog:public Animal
{
public:
    void speak()
    {
        cout<<"小狗在说话";
    }
};

//执行说话的函数
//地址早绑定, 在编译阶段确定函数地址
//如果想执行小猫在说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1.有继承关系
//2.子类要重写父类的虚函数,子类重写的函数前virtual关键字可写可不写

//动态多态的使用
//父类的指针或者引用 指向子类对象

void doSpeak(Animal &animal)
{
    animal.speak();
}

void test01()
{
    Cat cat;

    //Animal & animal = cat;
    doSpeak(cat);//在Animal类的speak()函数前面加virtual之前,执行结果为动物在说话

    Dog dog;

    doSpeak(dog);//在Animal类的speak()函数前面加virtual之后,执行结果为小狗在说话
}

        

                多态的原理剖析:

5239638fefc6432497064b5b7fbc7529.png

9a594837473a4adda9951ec2c60deb08.png

//多态

//动物类

class Animal
{
public:
    //虚函数 函数前面加virtual
    virtual void speak()
    {
        cout<<"动物在说话";
    }
};


//猫类
class Cat : public Animal
{
public:
    //函数重写 函数返回值类型 函数名 参数列表 完全相同
    void speak()
    {
        cout<<"小猫在说话";
    }

};


//狗类
class Dog:public Animal
{
public:
    void speak()
    {
        cout<<"小狗在说话";
    }
};



void doSpeak(Animal &animal)
{
    animal.speak();
}

void test01()//speak()没加virtual之前
{
    cout<<sizeof(Animal);//1
}

void test02()//speak()加virtual之后
{
    cout<<sizeof(Animal);//4 (32位) 8(64位) 说明类的内部多了个指针(虚函数(表)指针 )
}

                                                 多态的优点:

                                                 1.代码组织结构清晰。

                                                 2.可读性强。

                                                 3.利于前期和后期的扩展以及维护。

                                                 4.C++开发提倡利用多态设计程序框架,因为多态优点很多。

               纯虚函数与抽象类:

                在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写函数,因此可以将虚函数改为纯虚函数。

                纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

                当类中有了纯虚函数,这个类也被称为抽象类。

                抽象类特点:

                1.无法实例化对象。

                2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

                

class Base
{
public:
    //纯虚函数。
    //只要有一个纯虚函数,那么这个类就被称为抽象类。
    //抽象类特点:
    //无法实例化对象。
    //抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类 
    virtual void func() = 0;

};

class Son:public Base
{
public:
    virtual void func()
    {
        cout<<"func() 函数调用 ";
    }
};

void test01()
{
    //Base b;//抽象类无法实例化对象
    //new Base;//抽象类无法实例化对象

    //Son s;//当子类中没有重写父类中的纯虚函数时,也无法实例化对象。

    Son s;//当子类中 重写了 父类中的纯虚函数时,可以正常实例化对象了。

    Base* base = new Son;
    base->func();
}

               虚析构和纯虚析构:

                多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。

                解决方式: 将父类中的析构函数改为虚析构或者纯虚析构。

                虚析构和纯虚析构共性;

                1.可以解决父类指针释放子类对象。

                2.都需要有具体的函数实现。

                虚析构和纯虚析构区别:

                1.如果是纯虚析构,该类属于抽象类,无法实例化对象。

                虚析构语法: virtual ~类名(){ }

                纯虚析构语法:virtual ~类名() = 0;类外代码实现:类名::~类名(){ }

                

                

//虚析构和纯虚析构
class Animal
{
public:
    Animal(){}
    
      //虚析构
//    virtual ~Animal()
      {
         cout<<"Animal 虚析构函数调用";
      }

      //纯虚析构 需要声明也需要实现
        //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
     virtual ~Animal() = 0;
    
    //纯虚函数
    virtual void speak() = 0;
};

Animal::~Animal()
{
    cout<<"Animal 纯虚析构函数调用";
}

class Cat:public Animal
{
public:
    
    Cat(string name)
    {
        m_Name = new stirng(name);
    }

    ~Cat()
    {
        if(m_Name != nullptr)
        {
            delete m_Name;
            m_Name = nullptr;
        }
    }
    
    void speak()
    {
        cout<<*m_Name<<"在猫叫"<<endl;
    }

    string *m_Name;
    
};

void test01()
{
    Animal* a = new Cat("Tom");
    a->speak();                                    
    //父类指针在析构时 不会调用子类中析构函数,导致子类如果有堆区属性时,会发生堆区内存泄漏
    delete a;                                //解决方法,在父类析构函数前加virtual,改为虚析构           
}

                总结:

                1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。

                2.如果子类中没有堆区数据,可以不写虚析构或纯虚析构。

                3.拥有纯虚析构的类也属于抽象类。

  • C++文件操作:

                程序运行时产生的数据都属于临时数据,程序一旦结束都会被释放。通过文件可以将数据持久化。

                C++中文件操作需要包含头文件<fstream>

                文件类型分为两种:

                1.文本文件 - 文件以文本的ASCII码形式存储在计算机中。

                2.二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们。

                操作文件的三大类:

                1.ofstream:写操作

                2.ifstream:读操作

                3.fstream:读写操作

        

                文本文件:

                        写文件:

                        写文件步骤如下:

                        1.包含头文件:        #include<fstream>

                        2.创建流对象:        ofstream ofs;

                        3.打开文件   :        ofs.open("文件路径",打开方式);

                        4.写数据      :        ofs<<"写入的数据";

                        5.关闭文件  :        ofs.close();

                        文件打开方式:

df3608eef8b346e595ddd459b66bee83.png

                 注意:文件打开方式可以配合使用,利用|操作符。

                 例如:用二进制方式写文件:  ios::binary | ios::out  

        

//文本文件 写文件

//1.包含头文件 fstream

void test01()
{
    //2.创建流对象
    ofstream ofs;
    
    //3.指定打开方式
    ofs.open("文件路径",ios::out);

    //4.写内容
    ofs<<"姓名:张三"<<endl;
    ofs<<"性别:男"<<endl;
    ofs<<"年龄:18"<<endl;
    
    //5.关闭文件
    ofs.close();
}

                        读文件:

                        读文件步骤如下:

                        1.包含头文件:        #include<fstream>

                        2.创建流对象:        ifstream ifs;

                        3.打开文件并判断文件是否打开成功:   ifs.open("文件路径",打开方式);

                        4.读数据      :        四种方式读取

                        5.关闭文件  :        ifs.close();

//文本文件 读文件

void test01()
{
    //1.包含头文件
    
    
    //2.创建文件流
    ifstream ifs;

    //3.打开文件 并且判断是否打开成功
    ifs.open("D:\\cppvscode\\study\\in.txt",ios::in);
    
    if(!ifs.is_open())
    {
        cout<<"文件打开失败"<<endl;
        return;
    }

    //4.读数据
    
    //四种方法
    //第一种:
    
    // char buf[1024] = {0};
    // while(ifs>>buf)
    // {
    //     cout<<buf<<endl;
    // }

    //第二种
    // char buf[1024] = { 0 };
    // while (ifs.getline(buf, sizeof(buf)))
    // {
    //     cout << buf << endl;
    // }

    //第三种

    string buf;
    while (getline(ifs, buf))
    {
        cout << buf << endl;
    }

    //第四种 不推荐用
    // char c;
    // while ((c = ifs.get()) != EOF)//EOF end of file
    // {
    //     cout << c;
    // }


    //5.关闭文件
    ifs.close();
}

        

                二进制文件:

                以二进制的方式对文件进行读写操作

                打开方式要指定为 ios::binary

                

                写文件:

                二进制方式写文件主要利用流对象调用成员函数write

                函数原型:ostream& write(const char * buffer,int len);

                参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数。

//二进制文件 写文件
class Person
{
public:

    //写字符串的时候,最好不要用C++的string,因为会出现一些问题,对文件直进行操作时,直接用C风格的字符串,因为底层使用C写的
    char m_Name[64];//姓名
    int m_Age;//年龄
};

//1.包含头文件 fstream

void test01()
{
    //2.创建流对象
    //ofstream ofs("person.txt", ios::out | ios::binary);
    ofstream ofs;
    //3.打开文件
    ofs.open("person.txt", ios::out | ios::binary);

    //4.写文件
    Person p = { "张三",18 };
    ofs.write((const char*)&p, sizeof(Person));

    //5.关闭文件
    ofs.close();
}

                

                读文件:

                二进制方式读文件主要利用流对象调用成员函数read

                函数原型:istream& read(char* buffer,int len);

                参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。

//二进制 读文件

class Person
{
public:
    char m_Name[64];//姓名
    int m_Age;//年龄
};

void test01()
{
    //1.包含头文件

    //2.创建流对象

    ifstream ifs;
    
    //3.打开文件 并判断是否成功打开

    ifs.open("person.txt", ios::in | ios::binary);

    if (!ifs.is_open())
    {
        cout << "文件打开失败";
        return;
    }
    //4.读文件

    Person p;

    ifs.read((char*)&p, sizeof(Person));

    cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age;
    //5.关闭文件
    ifs.close();
}

C++提高编程:

                本阶段主要针对C++泛型编程与STL技术。

  • 模板:

                模板的概念:

                        模板就是建立通用的模具,大大提高复用性。

                        模板的特点:

                        1.模板并不能直接使用,它只是一个框架。

                        2.模板的通用并不是万能的。

                函数模板:

                        C++另一种编程思想成为泛型编程,主要利用的技术就是模板。

                        C++提供两种模板机制:函数模板类模板

                        函数模板语法:

                        函数模板作用:

                        建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

                        语法:

template<typename T>//template - 声明创建模板
函数声明或定义        //typename - 表明其后面的符号是一种数据类型,可以用class代替
                    //T - 通用的数据类型,名称可以替换,通常为大写字母

                        

//函数模板

//交换两个整型函数
void swapInt(int &a,int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

//交换两个浮点型函数
void swapFloat(float &a,float &b)
{
    float temp = a;
    a = b;
    b = temp;
}

//函数模板 
template<typename T>//声明一个模板,告诉编译器后面代码中紧跟着的 T 不要报错,T 是一个通用数据类型
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
void test01()
{
    int a = 10;
    int b = 20;

    //swapInt(a,b);
    //利用函数模板交换
    //两种方式使用函数模板

    1.自动类型推导
    mySwap(a,b);

    //2.显示制定类型
    mySwap<int>(a,b);
}

                总结:

                1.函数模板利用关键字template

                2.使用函数模板有两种方式:自动类型推导、显示指定类型。

                3.模板的目的是为了提高复用性,将类型参数化。

        

                函数模板注意事项:

                注意事项:

                1.自动类型推导,必须推到住一致的数据类型T,才可以使用。

                2.模板必须确定出T的数据类型,才可以使用。

//函数模板注意事项

template<class T>//typename可以替换成class
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

void test01()
{
    int a = 10;
    int b = 20;

    char c = 'x';
    
    //1.自动类型推导,必须推到住一致的数据类型T,才可以使用。
    mySwap(a,b);//正确的
    mySwap(a,c);//错误的
}

//2.模板必须确定出T的数据类型,才可以使用。
template<typename T>
void func()//函数体中没有用到T时,编译器无法推导出T的数据类型
{
    cout<<"func 调用";
}

void test02()
{
    func();//错误的,函数体中没有用到T时,编译器无法推导出T的数据类型
    func<int>();//正确的,此时随便给他一个数据类型,让它可以确定T的数据类型。
}

                普通函数和函数模板的区别:

                1.普通函数调用时可以发生自动类型转换(隐式类型转换)。

                2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。

                3.如果利用显示指定类型的方式,可以发生隐式类型转换。

//普通函数和函数模板的区别:

//1.普通函数调用时可以发生自动类型转换(隐式类型转换)。

//2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。

//3.如果利用显示指定类型的方式,可以发生隐式类型转换。

//普通函数

int myAdd01(int a,int b)
{
    return a + b;
}

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

void test01()
{
    int a = 10;
    int b = 20;
    char c = 'c';//a - 97 c - 99
    cout<<myAdd01(a,b)<<endl;//109

    //自动类型推导
    cout<<myAdd02(a,c)<<endl;//错位的 利用自动类型推导,不会发生隐式类型转换。
    

    //显式指定类型
    cout<<myAdd02<int>(a,c)<<endl;//109 利用显示指定类型的方式,可以发生隐式类型转换。
}

                总结:建议使用显式指定类型的方式调用函数模板,因为可以自己确定通用类型T,以减少错误和不确定的结果。

                

                普通函数与函数模板的调用规则:

                   调用规则如下:

                   1.如果函数模板和普通函数都可以实现,优先使用普通函数。

                   2.可以通过空模板参数列表来强制调用函数模板。

                   3.函数模板也可以发生重载。

                   4.如果函数模板可以产生更好的匹配,优先调用函数模板。

//普通函数与函数模板的调用规则:

//1.如果函数模板和普通函数都可以实现,优先使用普通函数。

//2.可以通过空模板参数列表来强制调用函数模板。

//3.函数模板也可以发生重载。

//4.如果函数模板可以产生更好的匹配,优先调用函数模板。

void myPrint(int a, int b)
{
    cout<<"调用普通函数";
}

template<typename T>
void myPrint(T a, T b)
{
    cout<<"调用函数模板";
}

template<typename T>
void myPrint(T a,T b,T c)
{
    cout<<"调用重载的函数模板";
}

void test01()
{
    int a = 10;
    int b = 20;

    myPrint(a,b);//调用普通函数,如果此时普通函数只有声明没有实现,依然会调用普通函数,只是没找到普通函数的实现,编译器会报错。

    //通过空模板参数列表来强制调用函数模板
    myPrint<>(a,b);//调用函数模板

    myPrint(a,b,100);//调用重载的函数模板

    //如果函数模板可以产生更好的匹配,优先调用函数模板
    char c1 = 'a';
    char c2 = 'b';

    myPrint(c1,c2);//调用函数模板
}

                总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。

                模板的局限性:

                局限性:模板的通用性并不是万能的。

//模板的局限性:
//模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。

class Person
{
public:
    string m_Name;
    int m_Age;

    Person(stirng name,int age)
    {
        m_Name = name;
        m_Age = age;
    }
};

//对比两个数据是否相等
template<typename T>
bool myCompare(T &a,T &b)
{
    if(a==b)
    {
        return true;
    }
    return false;
}

//利用具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person &a,Person &b)
{
    if(a.m_Name == b.m_Name && a.m_Age == b.m_Age)
    {
        return true;
    }
    return false;
}

void test01()
{
    int a=10;
    int b=20;
    cout<<myCompare(a,b);//false
}

void test02()
{
    Person p1("Tom",10);
    Person p2("Tom",10);
    cout<<myCompare(p1,p2);//异常, 利用具体化之后可以运行正确
}

                总结:

                1.利用具体化的模板,可以解决自定义类型的通用化。       

                2.学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板。

                类模板:

                类模板的作用:

                建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

                语法:

template<typename T>//template - 声明创建模板
类                  //typename - 表明其后面的符号是一种数据类型,可以用class 代替
                    //T - 通用的数据类型,名称可以替换,通常为大写字母。
//类模板

template<class NameType,class AgeType>
class Person
{
public:
    NameType m_Name;
    AgeType m_Age;

    Person(NameType name,AgeType age)
    {
        m_Name = name;
        m_Age = age;
    }

    void showInfo()
    {
        cout<<"m_Name="<<m_Name<<",m_Age"<<m_Age;
    }
};

void test01()
{
    Person<string,int> p1("孙悟空",20);
    p1.showInfo();
}

                总结:类模板与函数模板语法相似,在声明替template后面加类,此类称为类模板。

                类模板与函数模板的区别:

                类模板与函数模板区别主要有两点:

                1.类模板没有自动类型推导。

                2.类模板在模板参数列表中可以有默认参数。

//类模板与函数模板的区别:
template<class NameType,class AgeType = int>
class Person
{
public:
    NameType m_Name;
    AgeType m_Age;

    Person(NameType name,AgeType age)
    {
        m_Name = name;
        m_Age = age;
    }

    void showInfo()
    {
        cout<<"name:"<<m_Name<<",age:"<<m_Age;
    }
};



//1.类模板没有自动类型推导的使用方式。

void test01()
{
    //Person p("孙悟空",1000);//错误的,无法用自动类型推导
    Person<string,int>p("孙悟空",1000);//正确的,只能用显示指定类型

    p.showInfo();
}

//2.类模板在模板参数列表中可以有默认参数。
void test02()
{
    //template<class NameType,class AgeType = int>
    Person<string> p1("猪八戒",999);

    p1.showInfo();
}

                总结:

                1.类模板使用只能用显式指定类型方式。

                2.类模板中的模板参数列表中可以有默认参数。

                类模板中成员函数的创建时机:

                类模板中 成员函数 和 普通类中的 成员函数 创建时机 是有区别的:

                1.普通类中的 成员函数  一开始 就可以创建。

                2.类模板中的 成员函数 在调用时 才创建。

//类模板中 成员函数 和 普通类中的 成员函数 创建时机 是有区别的:

//1.普通类中的 成员函数  一开始 就可以创建。

//2.类模板中的 成员函数 在调用时 才创建。

//类模板中的成员函数: 只要没调用就不会创建
//类模板中的成员函数: 只要没调用就不会创建
//类模板中的成员函数: 只要没调用就不会创建

class Person1
{
public:
    void showPerson1()
    {
        cout<<"Person1 show";
    }
};

class Person2
{
public:
    void showPerson2()
    {
        cout<<"Person2 show";
    }
};

template<class T>
class MyClass
{
public:

    T obj;
        
    //类模板中的成员函数: 只要没调用就不会创建
    void func1()
    {
        obj.showPerson1();
    }

    void func2()
    {
        obj.showPerson2();
    }

};

void test01()
{
    MyClass<Person1>m;
    m.func1();
    m.func2();//错误,showPerson2 不是 Person1 的成员函数


}

                总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建。

                类模板对象做函数参数:

                学习目标:

                类模板实例化出的对象,像函数传参的方式。

                一共有三种传入方式:

                1.指定传入的类型 --- 直接显示对象的数据类型。

                2.参数模板化        --- 将对象中的参数变为模板进行传递。

                3.整个类模板化     --- 将这个对象类型 模板化进行传递。

//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
    
    T1 m_Name;
    T2 m_Age;    

    Person(T1 name, T2 age)
    {
        m_Name = name;
        m_Age = age;
    }

    void showInfo()
    {
        cout<<"name:"<<m_Name<<",age:"<<m_Age;
    }
    
};

//1.指定传入的类型 --- 直接显示对象的数据类型。
void printPerson1(Person<string,int>&p)
{
    p.showInfo();
}

void test01()
{
    Person<string,int> p("孙悟空",100);
    printPerson1(p);
}

//2.参数模板化        --- 将对象中的参数变为模板进行传递。
template<class T1,class T2>//这里的T1与T2与 class Person 中的T1与T2没有关联
void printPerson2(Person<T1,T2>&p)
{
    p.showInfo();
    cout<<"T1的类型为:"<<typeid(T1).name<<endl;
    cout<<"T2的类型为:"<<typeid(T2).name<<endl;
}


void test02()
{
    Person<string,int> p1("猪八戒",90);
    printPerson1(p1);
}

//3.整个类模板化     --- 将这个对象类型 模板化进行传递。
template<class T>
void printPerson3(T &p)
{
    p.showInfo();
    //查看T的数据类型名称
    cout<<"T的数据类型为:"<<typeid(T).name<<endl;
}

void test03()
{
    Person<string,int> p3("唐僧",30);
    printPerson1(p3);
}

                        总结:

                        1.通过类模板创建的对象,可以有三种方式向函数中进行传参。

                        2.使用比较广泛的是第一种:指定传入的类型。

                

                类模板与继承:

                当类模板碰到继承时,需要注意以下几点:

                1.当子类继承的父类是一个类模板时,子类在声明的时候,需要指出父类中T的类型。

                2.如果不指定,编译器无法给子类分配内存。

                3.如果想灵活制定出父类中T的类型,子类也需要变为类模板。

//类模板与继承
template<class T>
class Base
{
    T m;
};

//class Son:punlic Base//错误,必须要知道父类中T的数据类型,才能继承给子类。因为不知道T的类型,无法计算要占多少内存空间
class Son:punlic Base<int>//正确的
{
public:
    
};

void test01()
{
    Son s1;
}

//如果想灵活制定出父类中T的类型,子类也需要变为类模板。
template<class T1,class T2>//在这里 T1 为子类自己的成员变量 obj 的数据类型
class Son2:public Base<T2>//T2 为从父类中成员变量 m 的数据类型
{
public:
    T1 obj;
    Son2()
    {
        cout<<"T1的类型为:"<<typeid(T1).name()<<endl;
        cout<<"T2的类型为:"<<typeid(T2).name()<<endl;
    }
};

void test01()
{
    Son2<int,char> s2;
}

                        总结:如果父类是类模板,子类需要指定出父类中T的数据类型。

                类模板成员函数类外实现:

                学习目标:能够掌握类模板中的成员函数类外实现

//类模板成员函数类外实现
template<class T1,class T2>
class Person
{
public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name,T2 age);
//    {
//        m_Name = name;
//        m_Age = age;
 //   }
    
    void showInfo();
//    {
//        cout<<"name:"<<m_Name<<",age:"<<m_Age;
//    }
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
    m_Name = name;
    m_Age = age;    
}

//成员函数的类外实现
template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
    cout<<"name:"<<m_Name<<",age:"<<m_Age;
}

void test01()
{
    Person<string,int>p("tom",20);
    p.showInfo();
}

                        总结: 类模板中成员函数类外实现时,需要加上模板参数列表。

                类模板分文件编写:

                学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式。

                问题:

                类模板中成员函数创建时机是在调用阶段,导致份文件编写时链接不到。

                解决:

                解决方式1:直接包含cpp源文件。

                解决方式2:将声明和实现写到同一文件中,并更改后缀名为.hpp,hpp时约定的名称,并不是强制。

//类模板分文件编写问题及解决
/
//person.h 文件

#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person
{
public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name,T2 age);
    
    void showInfo();
};
///

///
//person.cpp 文件

#include"person.h"

template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
    m_Name = name;
    m_Age = age;    
}

template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
    cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
///

///
//main 文件
#include<iostream>
using namespace std;

//#include"person.h"//法一:直接包含源文件 即改为#include"person.cpp"
#include"person.cpp" //法二:将.h和.cpp写到一起,并将后缀名更改为.hpp
void test01()
{
    Person<string,int>p("tom",20);
    p.showInfo();
}

///

///

#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person
{
public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name,T2 age);
    
    void showInfo();
};

template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
    m_Name = name;
    m_Age = age;    
}

template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
    cout<<"name:"<<m_Name<<",age:"<<m_Age;
}

///

///
//main 文件
#include<iostream>
using namespace std;

#include"person.hpp"//法一:直接包含源文件 即改为#include"person.cpp"
                    //法二:将.h和.cpp写到一起,并将后缀名更改为.hpp
void test01()
{
    Person<string,int>p("tom",20);
    p.showInfo();
}

///

                总结:主流的解决方法是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

                

                类模板与友元:

                学习目标:

                掌握类模板配合友元函数的类内和类外实现。

                1.全局函数内实现 - 直接在类内声明即可。

                 2.全局函数外实现 - 需要提前让编译器知道全局函数的存在。

//类模板与友元
//1.全局函数内实现 - 直接在类内声明即可。
//2.全局函数外实现 - 需要提前让编译器知道全局函数的存在。

//通过全局函数 打印Person信息

//提前让编译器知道Person类存在,且为模板类
template<class T1,class T2>
class Person;

//类外实现 需要让编译器提前知道这个函数的存在
template<class T1,class T2>
void showInfo2(Person<T1,T2> p)
{
    cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
}


template<class T1,class T2>
class Person
{
    //全局函数 类内实现
    friend void showInfo(Person<T1,T2> p)
    {
        cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
    }

    //全局函数  类外实现
    //加空模板参数列表 <>
    //如果全局函数 是类外实现,需要让编译器提前知道这个函数的存在
    friend void showInfo2<>(Person<T1,T2> p);

public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name,T2 age);
    {
        m_Name = name;
        m_Age = age;
    }
};

//类外实现 需要在上方,需要让编译器提前知道这个函数的存在
//template<class T1,class T2>
//void showInfo2(Person<T1,T2> p)
//{
//    cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
//}

//类内实现测试
void test01()
{
    Person<string,int>p("tom",20);
    showInfo(p);
}

//类外实现测试 
void test02()
{
    Person<string,int>p2("jerry",20);
    showInfo2(p2);
}

                        总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。

  • STL(Standard Template Library,标准模板库):

                诞生:长久以来,软件界一直希望建立一种可重复利用的东西->c++面向对象和泛型编程思想,目的就是复用性的提升->大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作->为了建立数据结构和算法的一套标准,诞生了STL。

                基本概念:

                1. STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)

                2.容器算法之间通过迭代器进行无缝连接

                3.STL几乎所有的代码都采用了模板类或模板函数

               

                STL六大组件:

                大体分为六大组件:分别为:容器、算法、迭代器、仿函数、适配器、空间配置器

                1.容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据

                2.算法:各种常用的算法,如sort、find、copy、for_each等

                3.迭代器:扮演了容器与算法之间的胶合剂

                4.仿函数:行为类似函数,可作为算法的某种策略

                5.适配器:一种用来修饰容器或者仿函数或迭代器接口的东西

                6.空间配置器:负责空间的配置与管理

                STL中容器、算法、迭代器:

                容器:置物之所也

                STL容器就是将运用最广泛的一些数据结构实现出来

                常用的数据结构:数组、链表、树、栈、队列、集合、映射表 等

                这些容器分为序列式容器关联式容器两种:

                序列式容器:强调值的排序,每个元素均有固定的位置

                关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

                算法:问题之解法也

                有限的步骤,解决逻辑或数学上的问题

                算法分为:质变算法非质变算法

                质变算法:运算过程中会更改区间内的元素的内容。例如拷贝、替换、删除 等

                非质变算法:运算过程中不会更改区间内的元素的内容。例如查找、计数、遍历、寻找极值 等

                迭代器:容器和算法之间粘合剂

                提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式

                每个容器都有自己专属的迭代器

                迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针

                迭代器种类:

                常用的迭代器种类为双向迭代器随机访问迭代器 

                容器算法迭代器初识:

                容器:vector        算法:for_each        迭代器:vector<int>::iterator

#include<vector>
#include<algorithm>//标准算法头文件

//vector容器存放内置数据类型

void test01()
{
    vector<int> v;//创建vector容器,数组
    
    v.push_back(10);//向容器内尾插数据
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);

    vector<int>::iterator it_begin = v.begin();//起始迭代器,指向容器内第一个元素

    vector<int>::iterator it_end = v.end();//结束迭代器,指向容器中最后一个元素的下一个位置

    //第一种遍历方式

    while(it_begin != it_end)
    {
        cout<<*it_begin<<endl;
        it_begin++;
    }
    //

    //第二种遍历方式
    
    for(vector<int>::iterator it = v.begin(); it != v.end(); ++it)
    {
        cout<<*it<<endl;
    }


    //第三种遍历方式,利用STL中的遍历算法

    void func(int val)
    {
        cout<<val<<endl;
    }

    for_each(v.begin(), v.end(), func);

}



//vector容器存放自定义数据类型


class Person
{
public:

    Person(string name, int age)
    {
        this->m_name = name;
        this->m_age = age;
    }    

    string m_name;
    int m_age;
};

void test02()
{

    vector<Person> vp;

    Person p1("zs", 12);
    Person p2("za", 13);
    Person p3("zb", 14);
    Person p4("zc", 15);
    Person p4("zd", 16);

    //向容器中添加数据

    vp.push_back(p1);
    vp.push_back(p2);
    vp.push_back(p3);
    vp.push_back(p4);
    vp.push_back(p5);

    //遍历容器
    for(vector<Person>::iterator it = vp.begin(); it != vp.end(); ++it)
    {
        cout<<  (*it).m_name  <<" "<<  (*it).m_age  <<endl;//法一

        cout<<    it->m_name  <<" "<<    it->m_age  <<endl;//法二
    }

}
///

//存放自定义数据类型 指针

void test03()
{

    vector<Person*> vp;

    Person p1("zs", 12);
    Person p2("za", 13);
    Person p3("zb", 14);
    Person p4("zc", 15);
    Person p4("zd", 16);

    //向容器中添加数据

    vp.push_back(&p1);
    vp.push_back(&p2);
    vp.push_back(&p3);
    vp.push_back(&p4);
    vp.push_back(&p5);

    //遍历容器
    for(vector<Person*>::iterator it = vp.begin(); it != vp.end(); ++it)
    {
        cout<<  (*it)->m_name  <<" "<<  (*it)->m_age  <<endl;//法一
    }

}

                容器嵌套容器:

//容器嵌套容器

void test01()
{

    vector<vector<int>> v;

    //创建小容器
    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    vector<int> v4;

    //向小容器中添加数据
    for(int i = 1; i < 4; ++i)
    {
        v1.push_back(i);
        v2.push_back(i*10);
        v3.push_back(i*20);
        v4.push_back(i*30);
    }

    //将小容器插入到大容器中
    v.push_back(v1);
    v.push_back(v2);
    v.push_back(v3);
    v.push_back(v4);

    //通过大容器遍历所有数据
    for( vector<vector<int>>::iterator itv = v.begin(); itv != v.end(); ++itv)
    {
        //(*itv)------>容器
        for(vector<int>::iterator it = (*itv).begin(); it != (*itv).end(); ++it)
        {
            cout<<*it<<endl;
        }
    }

}

                string容器: 

                1.string基本概念:

                本质:string是C++风格的字符串,本质是一个类

                string和char*的区别:

                char*是一个指针

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

                特点:

                1.string类内部封装了很多成员方法,如查找find、拷贝copy、删除delete、替换replace、插入insert

                2.string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

                2.string构造函数:

                构造函数原型:

//构造函数原型:

string();                     //创建一个新的字符串,如string str;

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

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

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

                3.string赋值操作:

                功能描述:给string字符串进行赋值。

                赋值的函数原型:

//赋值的函数原型:

string& operator=(const char* s);        //char*类型字符串 赋值给当前的字符串

string& operator=(const string& s);      //把字符串s赋给当前的字符串  

string& operator=(char c);               //字符赋给当前的字符串

string& assign(const char* s);           //把字符串s赋给当前的字符串 

string& assign(const char* s, int n);    //把字符串s的前n个字符赋给当前的字符串 

string& assign(const string& s);         //把字符串s赋给当前的字符串 

string& assign(int n, char c);           //用n个字符c赋给当前的字符串

                样例:

void test01()
{

    string str1;
    str1 = "hello world";//1
    
    string str2;
    str2 = str1;//2

    string str3;
    str3 = 'a';//3

    string str4;
    str4.assign("hello C++");//4

    string str5;
    str5.assign("hello c++",7);//5 结果输出 hello c

    string str6;
    str6.assign(str5);//6

    string str7;
    str7.assign(10,'w');//7
}

                总结:一般用 = 方式。

                4.string字符串拼接:

                功能描述:实现在字符串末尾拼接字符串

                函数原型:

//函数原型:

string& operator+=(const char* str);               //重载+=操作符

string& operator+=(const char c);                  //重载+=操作符

string& operator+=(const string& str);             //重载+=操作符

string& append(const char* s);                     //把字符串s连接到当前字符串末尾

string& append(const char* str, int n);            //把字符串s的前n个字符连接到当前字符串末尾

string& append(const string& s);                   //同operator+=(const string& str)

string& append(const string& s, int pos, int n);   //把字符串s从pos开始的n个字符串连接到当前字符串末尾

                样例:

void test01()
{
    string str1 = "我";
    str1 += "爱玩游戏";//1  我爱玩游戏

    str1 += ':';//2 我爱玩游戏:

    string str2 = " LOL DNF";
    str1 += str2;//3 我爱玩游戏: LOL DNF
    
    string str3 = "I";
    str3.append(" love");//4 I love

    str3.append(" game abcde", 5);//5 I love game

    str3.append(str2);//6 I love game LOL DNF

    string str4 = "I love game";
    str4.append(str2, 5, 3);//7 I love gameDNF
}

                5.string查找和替换:

                功能描述:

                查找:查找指定字符串是否存在

                替换:在指定位置替换字符串

                函数原型:

//函数原型:

int find(const string& str, int pos = 0) const;        //查找str第一次出现位置,从pos开始找

int find(const char* s, int pos = 0) const;            //查找s第一次出现位置,从pos开始找

int find(const char* s, int pos, int n) const;         //从pos位置查找s的前n个字符第一次出现位置

int find(const char c, int pos = 0) const;             //查找字符c第一次出现位置,从pos开始找

int rfind(const string& str, int pos = npos) const;    //查找str最后一次位置,从pos开始找

int rfind(const char* s, int pos = npos) const;        //查找s最后一次出现位置,从pos开始找

int rfind(const char* s, int pos, int n) const;        //从pos位置查找s的前n个字符最后一次位置

int rfind(const char c, int pos = 0) const;            //查找字符c最后一次出现位置,从pos开始找

string& replace(int pos, int n, const string& str);    //替换从pos开始的n个字符为str

string& replace(int pos, int n, const char* s);        //替换从pos开始的n个字符为s

                样例:

//字符串的查找和替换

//查找
void test01()
{
    string str1 = "abcdefgde";
    
    //find
    int pos = str1.find("de");//    3
    pos = str1.find("df");    //    -1   

    //rfind
    //find是从左往右查找,rfind是从右往左查找 
    pos = str1.rfind("de");   //    7 返回值依然是左边第一个字符的位置下标

}

//替换
void test02()
{
    string str1 = "abcdefg";
    
    //从 1号位置起 3个字符 替换为 “1111”
    str1.replace(1,3,"1111");//    a1111efg
}

                6.字符串比较:

                功能描述:字符串之间的比较

                比较方式:

                字符串比较是以字符的ASCII码进行对比

                > 返回 1        = 返回 0        < 返回 -1

                函数原型:

//函数原型:

int compare(const string& s) const;    //与字符串s比较

int compare(const char* s) const;      //与字符串s比较

                样例:

//字符串比较

void test01()
{
    string str1 = "hello";

    string str2 = "hello";

    string str3 = "xello";  

    int res = str1.compare(str2);//    0

    res = str3.compare(str2);//    1

    res = str2.compare(str3);//    -1

}

                总结:compare主要用于比较两个字符串是否相等,比较谁大谁小意义不是很大。

                7.string字符存取:

                string中单个字符存取方式有两种

char& operator[](int n);    //通过[]方式取字符

char& at[](int n);          //通过at方法获取字符

                样例:

//string 字符存取

void test01()
{
    string str = "hello";

    //通过[]方式访问单个字符
    for(int i = 0; i < str.size(); ++i)
    {
        cout<<str[i]<<" ";
    }

    //通过at方式访问单个字符
    for(int i = 0; i < str.size(); ++i)
    {
        cout<<str.at(i)<<" ";
    }

    //修改
    str[0] = 'x';//    xello

    str.at(1) = 'x';//    xxllo

}

                8.string插入和删除:

                功能描述:对string字符串进行插入和删除字符操作

                函数原型:

//函数原型:

string& insert(int pos, const char* s);    //在指定位置插入字符串s

string& insert(int pos, const string& s);  //在指定位置插入字符串s

string& insert(int pos, int n, char c);    //在指定位置插入n个字符c

string& erase(int pos, int n = npos);      //删除从pos开始的n个字符

                样例:

//string的插入和删除

void test01()
{
    string str = "hello";
    
    //插入
    str.insert(1,"111");//    h111ello

    //删除
    str.erase(1,3);//    hello
}

                总结:插入和删除起始下标都是从0开始

                9.string子串:

                功能描述:从字符串中获取想要的子串

                函数原型:

//函数原型:

string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串

                样例:

//string子串

void test01()
{
    string str = "abcdefg";

    string s1 = str.substr(1, 3);//    bcd
}

                vector容器:

                1.vector基本概念:

                功能:vector的数据结构与数组非常相似,也称为单端数组

                vector与普通数组区别:

                不同之处在于数组是静态空间,而vector可以动态扩展

                动态扩展:

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

                vector的迭代器是支持随机访问的迭代器 。

                2.vector构造函数:

                功能和描述:创建vector容器。

                函数原型:

//函数原型

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

vector(v.begin(), v.end());     //将 v中的 [ begin(), end() ) 区间中的元素拷贝给本身,区间前闭后开

vector(n, elem);                //构造函数将n个elem拷贝给本身

vector(const vector &vec);      //拷贝构造函数

                样例:

//vector容器构造

void p_v(vector<int> &v)
{
    for(vector<int>::iterator it = v.begin(); it != v.end(); ++it)
        cout << *it << " ";
}

void test01()
{
    vector<int> v1;//默认构造 无参构造

    for(int i = 0; i < 10; ++i)
        v1.push_back(i);
    p_v(v1);
    
    //通过区间方式进行构造
    vector<int> v2(v1.begin(), v1.end());
    p_v(v2);
    
    //n个elem方式
    vector<int> v3(10,1);//10个1
    p_v(v3);
    
    //拷贝构造
    vector<int> v4(v3);
    p_v(v4);
}

                3.vector容器赋值操作:

                功能描述:给vector容器进行赋值

                函数原型:

//函数原型

vector& operator=(const vector &vec);    //重载 = 操作符

assign(v.begin(), v.end());              //将[ v.begin(), v.end() ) 区间中的值赋值给本身

assign(n, elem);                         //将n个elem拷贝赋值给本身

                样例:

//vector赋值

void test01()
{
    vector<int> v1;
    
    for(int i = 0; i < 10; ++i)
        v1.push_back(i);
    
    //赋值
    
    vector<int> v2 = v1;//等号赋值

    //assign

    vector<int> v3;
    v3.assign(v1.begin(), v1.end());

    vector<int> v4;
    v4.assign(10,1);//10个1
}

                4.vector容器的容量和大小:

                函数原型:

//函数原型

empty();                //判断是否为空

capacity();             //容器的容量

size();                 //返回容器中元素的个数

resize(int num);        //重新指定容器的长度为num, 若容器变长,则以默认值填充新位置
                        //若变短,则末尾超出容器长度的元素被删除

resize(int num, elem);  //重新指定容器的长度为num, 若容器变长,则以elem填充新位置
                        //若变短,则末尾超出容器长度的元素被删除

                样例:

//vector容器容量和大小

void test01()
{
    vector<int> v1;
    for(int i = 0; i < 10; ++i)
        v1.push_back(i);

    bool res = v1.empty();//    false

    int cap = v1.capacity();//    

    int sz = v1.size();//    10

    //重新指定大小
    v1.resize(15);//如果第二个参数缺省,默认用0来填充(重新指定大小与比原来大)
    v1.resize(15,100);//可以指定填充值

    v1.resize(5);//重新指定大小与比原来小,超出部分被删除
}

                5.vector插入和删除:

                功能描述:对vector进行插入和删除操作

                函数原型:

//函数原型

push_back(elem);                                    //尾部插入元素elem

pop_back();                                         //删除最后一个元素

insert(const_iterator pos, elem);                   //迭代器指向位置pos插入元素elem

insert(const_iterator pos, int count, elem);        //迭代器指向位置pos插入count个元素elem

erase(const_iterator pos);                          //删除迭代器指向的元素

erase(const_iterator start, const_iterator end);    //删除迭代器从start到end之间的元素

clear();                                            //删除容器中所有元素

                样例:

//vector的插入和删除

void test01()
{
    vector<int> v1;
    
    //尾插
    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);
    
    //尾删
    v1.pop_back();//删除末尾元素30

    //插入
    v1.insert(v1.begin(), 100);在头部插入100

    v1.insert(v1.begin(), 2, 1000);//在头部插入2个1000

    //删除
    v1.erase(v1.begin());删除头部元素1000

    v1.erase(v1.begin(), v1.end());//删除了全部元素

    //清空
    v1.clear();
}

                6.vector数据存取:

                功能描述:对vector容器中的数据进行存取操作

                函数原型:

//函数原型

at(int idx);    //返回索引index所指的数据

operator[idx];  //返回索引index所指的数据

front();        //返回容器中的第一个数据

back();         //返回容器中的最后一个数据

                样例:

//vector数据存取

void test01()
{
    vector<int> v1;
    for(int i = 0; i < 10; ++i)
        v1.push_back(i);

    //利用[]方式访问数组中元素
    for(int i = 0; i < v1.size(); ++i)
        cout<< v1[i] <<" ";

    //利用at方式访问数组中元素
    for(int i = 0; i < v1.size(); ++i)
        cout<< v1.at(i) <<" ";  

    //获取第一个元素
    cout<< v1.front() <<endl;

    //获取最后一个元素
    cout<< v1.back() <<endl;
}

                7.vector互换容器:

                功能描述:实现两个容器内元素进行互换

                函数原型:

//函数原型

swap(vec); //将vec与本身元素进行互换

                样例:

//vector互换容器


//1.基本使用
void test01()
{
    vector<int> v1;
    for(int i = 0; i < 10; ++i)
        v1.push_back(i);// 0到9
    
    vector<int>v2;
    for(int i = 10; i; --i)
       v2.push_back(i);// 10到1  
        
    v1.swap(v2);//互换容器内元素
}

//2.实际用途
//巧用swap可以收缩内存空间
void test02()
{
    vector<int> v;
    for(int i = 0; i < 100000; ++i)
        v1.push_back(i);   

    cout<<"容量"<<v.capacity()<<endl;

    cout<<"大小"<<v.size()<<endl;

    v.resize(3);//重新指定大小,容量没变,大小减小到3

    cout<<"容量"<<v.capacity()<<endl;//不变

    cout<<"大小"<<v.size()<<endl;//变为 3

    //巧用swap收缩内存
    vector<int> (v).swap(v);

    cout<<"容量"<<v.capacity()<<endl;//变为3

    cout<<"大小"<<v.size()<<endl;//变为3
}

                8. vector预留空间:

                功能描述:减少vector在动态扩展容量时的扩展次数

                函数原型:

//函数原型

reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问

                样例:

//vector预留空间

void test01()
{
    vector<int> v;

    //利用reserve预留空间
    v.reserve(100000);
    
    int cnt = 0;//统计开辟次数

    int *p = nullptr;    

    for(int i = 0; i < 100000; ++i)
    {
        v1.push_back(i); 
        if(p != &v[0])
        {
            p = &v[0];
            cnt++;
        }
    }
    cout<<cnt<<endl;//    = 30 ,预留空间后 = 1
}

                deque容器:

                1.deque容器基本概念:

                功能:双端数组,可以对两端进行插入删除操作。

                deque与vector的区别:

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

                2.deque相对vector而言,对头部的插入删除效率更高

                3.vector访问元素的会比deque效率高,这与二者的内部实现有关

                deque内部工作原理:

                1.deque内部有个中控器,维护每段缓冲区的内容,缓冲区中存放真实数据

                2.中控器维护的是每段缓冲区的地址, 使得使用deque时一片连续的内存空间。

                deque容器的迭代器也是支持随机访问的。 

                2.deque构造函数:

                函数原型:

//函数原型:

deque<T> deqT;                //默认构造形式

deque(d.begin(), d.end());    //构造函数将 [ begin(), end() ) 区间内的元素拷贝给本身

deque(int n, elem);           //构造函数将n个elem拷贝给本身

deque(const deque& deq);      //拷贝构造函数

                样例:

//deque构造函数

//额外知识:只读迭代器:const_iterator

void test01()
{
    deque<int> d1;
    for(int i = 0; i < 10; ++i)
        d1.push_back(i);   
    
    deque<int> d2(d1.begin(),d1.end());

    deque<int> d3(10,1);

    deque<int> d4(d3);
}

                3.deque赋值操作:

                函数原型:

//函数原型:

deque& operator=(const deque& deq);    //重载等号操作符

assign(begin(), end());                //将 [ begin(), end() ) 区间的数据拷贝赋值到本身

assign(int n, elem);                   //将n个elem拷贝赋值到本身

                样例:

//deque容器赋值操作

void test01()
{
    deque<int> d1;
    for(int i = 0; i < 10; ++i)
        d1.push_back(i);   
    
    deque<int> d2 = d1;

    deque<int> d3;
    d3.assgin(d1.begin(), d1.end());
    
    deque<int> d4;
    d4.assgin(10,1);
}

                4.deque容器大小操作:

                函数原型:

//函数原型:

deque.empty();                //判断是否为空

deque.size();                 //返回容器中元素的个数

deque.resize(num);            //重新指定容器的长度为num, 若容器变长,则以默认值填充新位置
                              //若变短,则末尾超出容器长度的元素被删除
    
deque.resize(num, elem);      //重新指定容器的长度为num, 若容器变长,则以elem填充新位置
                              //若变短,则末尾超出容器长度的元素被删除

                样例:

//deque容器大小操作

void test01()
{
    deque<int> d1;
    for(int i = 0; i < 10; ++i)
        v1.push_back(i);   

    bool b = di.empty();

    int sz = d1.size();

    //deque容器中没有容量的概念,可以一直装,只需在中控器中加上新开辟的缓冲区首地址就行

    //重新指定大小
    d1.resize(15);

    d1.resize(15,100);

    d1.resize(5);
}

                5.deque容器插入和删除:插入和删除提供的位置pos为迭代器

                函数原型:

//函数原型

//两端插入操作                

push_back(elem);            //在容器尾部添加一个数据

push_front(elem);           //在容器头部插入一个数据

pop_back();                 //删除容器最后一个数据

pop_front();                //删除容器第一个数据

//指定位置操作:插入和删除提供的位置pos为迭代器

insert(pos,elem);           //在pos位置插入一个elem元素的拷贝,返回新数据的位置

insert(pos,n,elem);         //在pos位置插入n个elem数据,无返回值

insert(pos,beg,end);        //在pos位置插入[beg,end)区间的数据,无返回值

clear();                    //清空容器的所有数据

erase(beg,end);             //删除[beg,end)区间的数据,返回下一个数据的位置

erase(pos);                 //删除pos位置的数据,返回下一个数据的位置

                样例:

//deque容器插入和删除

void printDeque(const deque<int>& d) 
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
//两端操作
void test01()
{
	deque<int> d;

	//尾插
	d.push_back(10);
	d.push_back(20);

	//头插
	d.push_front(100);
	d.push_front(200);
 
	printDeque(d);
 
	//尾删
	d.pop_back();

	//头删
	d.pop_front();

	printDeque(d);
}
 
//插入
void test02()
{
	deque<int> d;

	d.push_back(10);
	d.push_back(20);

	d.push_front(100);
	d.push_front(200);

	printDeque(d);
 
	d.insert(d.begin(), 1000);
	printDeque(d);
 
	d.insert(d.begin(), 2,10000);
	printDeque(d);
 
    //按区间进行插入
	deque<int>d2;

	d2.push_back(1);
	d2.push_back(2);
	d2.push_back(3);
 
	d.insert(d.begin(), d2.begin(), d2.end());

	printDeque(d);
 
}
 
//删除
void test03()
{
	deque<int> d;

	d.push_back(10);
	d.push_back(20);

	d.push_front(100);
	d.push_front(200);

	printDeque(d);
 
	d.erase(d.begin());
	printDeque(d);
 
	d.erase(d.begin(), d.end());

	d.clear();

	printDeque(d);

                总结:插入和删除提供的位置pos为迭代器

                6.deque数据存储:

                函数原型:

//函数原型

at(int idx); //返回索引idx所指的数据

operator[];  //返回索引idx所指的数据

front();     //返回容器中第一个数据元素

back();      //返回容器中最后一个数据元素

                样例:

//deque数据存取

void test01()
{
 
	deque<int> d;

	d.push_back(10);
	d.push_back(20);

	d.push_front(100);
	d.push_front(200);
 

	for (int i = 0; i < d.size(); i++) {
		cout << d[i] << " ";
	}
	cout << endl;

	
    for (int i = 0; i < d.size(); i++) {
		cout << d.at(i) << " ";
	}
	cout << endl;
 
	cout << "front:" << d.front() << endl;
 
	cout << "back:" << d.back() << endl;
}

                7.deque排序:

                算法:

sort(iterator beg, iterator end) //对beg和end区间内元素进行排序

                样例:

//deque排序

void printDeque(const deque<int>& d) 
{
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

void test01() {

	deque<int>d1;

	d1.push_back(10);
	d1.push_back(200);

	d1.push_back(5);
	d1.push_back(30);
	d1.push_back(0);

	cout << "排序前:" << endl;
	printDeque(d1);

	// 注意不是d1.sort
	// sort函数不属于任何容器,它是STL标准模板库提供的

	sort(d1.begin(), d1.end());

	cout << "排序后:" << endl;
	printDeque(d1);
}

                stack容器:

                1.stack基本概念:

                概念:stack(栈)是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个进出口,即栈顶。所以栈不能进行遍历操作

                

                 入栈--数据放入栈中 push

                出栈--数据从栈顶取出 pop

                stack常用接口:

//常用接口

//构造函数:

stack<T> stk;                         //stack采用模板类实现,默认构造 stack<int> s;

stack(const stack &stk);              //拷贝构造函数

//赋值操作:

stack& operator=(const stack &stk);   //重载等号操作符

//数据存取:

push(elem);                           //向栈顶添加元素

pop();                                //从栈顶移除第一个元素

top();                                //返回栈顶元素

//大小操作

empty();                              //判断堆栈是否为空

size();                               //返回栈的大小

                样例:

#include <stack>
//栈容器常用接口
void teste1()
{
    //创建栈容器 特点:符合先进后出
    stack<int> s;
 
    //向栈中添加元素,叫做压栈入栈
    s. push(10);
    s. push(20);    
    s. push(30);
 
    //只要栈不为空,查看栈顶,并且执行出栈操作
    while (!s. empty()) 
    {
        //输出栈顶元素
        cout << "栈顶元素为:"<< s.top() << endl;

        //弹出栈顶元素
        s.pop();
    }
    cout <<“栈的大小为:”<< s.size() << endl;
}

                queue容器:

                概念:

                1.Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口:队头和队尾

                2.从一端新增元素,只能另一端移除元素。

                3.只有队头和队尾才可以被外界访问,因此队列不允许有遍历行为。

                 queue常见操作:

//queue常见操作

//构造函数:
queue<T> que;                       //默认构造函数

queue(const queue &que);            //拷贝构造函数

//赋值操作:

queue& operator=(const queue &que); //重载等号操作符

数据存取:

push(elem);                         //往队尾添加元素

pop();                              //从队头移除第一个元素

back();                             //返回最后一个元素

front();                            //返回第一个元素

//大小操作:

empty();                            //判断堆栈是否为空

size();                             //返回栈的大小

                 list容器:

                1.list基本概念:

                功能:将数据进行链式存储

                链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

                链表的组成:链表由一系列结点组成

                结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

                STL中的链表是一个双向循环链表

                由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,不支持跳跃式访问。属于双向迭代器 

                list的优点:

                1.采用动态存储分配,不会造成内存浪费和溢出

                2.链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素

                list的缺点:

                1.链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

                List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

                2.list构造函数:

                功能描述:创建list容器

                函数原型:

//函数原型

list<T> lst;           //list采用采用模板类实现,对象的默认构造形式。

list(beg,end);         //构造函数将[beg, end)区间中的元素拷贝给本身。

list(n,elem);          //构造函数将n个elem拷贝给本身。

list(const list &lst); //拷贝构造函数。

                样例:

//list构造函数

 // 遍历函数
void printList(const list<int>& L) {
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
		cout << *it << "    ";
	}
	cout << endl;
}

void test01() {
	//1.第一种方式,默认构造
	list<int> lis1;
    // 添加数据
	lis1.push_back(10); 
	lis1.push_back(20);
	lis1.push_back(30);
	lis1.push_back(40);

	 //遍历容器
	printList(lis1); 
    cout << endl;

	// 2.第二种方式,n个元素
	list<int> lis2(10, 20);

	printList(lis2); 
    cout << endl;

	// 3.第三种方式,利用区间
	list<int> lis3(lis2.begin(), lis2.end());

	printList(lis3); 
    cout << endl;

	// 4.第四种方式,拷贝构造
	list<int> lis4(list2);

	printList(lis4); 
    cout << endl;
}

                3.list赋值和交换:

                功能描述:给list容器进行赋值,以及交换list容器

                函数原型:

//函数原型

assign(beg, end);                 //将[beg, end)区间中的数据拷贝赋值给本身。

assign(n, elem);                  //将n个elem拷贝赋值给本身。

list& operator=(const list &lst); //重载等号操作符

swap(lst);                        //将lst与本身的元素互换。

                样例:

//list赋值和交换

// 打印函数
void PrintList(const list<int>& lis) 
{

	for (list<int>::const_iterator it = lis.begin(); it != lis.end(); it++) 
    {
		cout << *it << "    ";
	}
	cout << endl;
}


void test01()
{

	list<int> lis1;
    
    list1.push_back(10);
    list1.push_back(20);
    list1.push_back(30);
    list1.push_back(40);

	PrintList(lis1); 

	// 第一种方式,利用assign函数将10个100赋给容器
	list<int> lis2;
	lis2.assign(10, 100);

	PrintList(lis2);


	// 第二种方式,利用assign函数将一个区间的值赋给容器
	lis2.assign(lis1.begin(), lis1.end());
	PrintList(lis2);


	// 第三种方式,利用重载等号赋值操作
	list<int> lis3 = lis1;
	PrintList(lis3);
}

// 交换函数
void test02()
{

	list<int> lis1;
    
    list1.push_back(10);
    list1.push_back(20);
    list1.push_back(30);
    list1.push_back(40);

	lis2.assign(10, 100);

	cout << "lis2容器交换前的元素值为:" << endl;
	PrintList(lis2);

	cout << "lis1容器交换前的元素值为:" << endl;
	PrintList(lis1);

	lis2.swap(lis1);

	cout << "lis2容器与lis1容器交换后的元素值为:" << endl;
	PrintList(lis2);

	cout << "lis1容器与lis2容器交换后的元素值为:" << endl;
	PrintList(lis1);
}

                4.list 大小操作:

                函数原型:

//函数原型

size();              //返回容器中元素的个数

empty();             //判断容器是否为空

resize(num);         //重新指定容器的长度为num,若容器变长,则以默认值0来填充新位置。
​                     //如果容器变短,则末尾超出容器长度的元素被删除。

resize(num, elem);   //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
                     //如果容器变短,则末尾超出容器长度的元素被删除

                样例:

//list 大小操作

void test01() 
{
	list<int> lis1;

    list1.push_back(10);
    list1.push_back(20);
    list1.push_back(30);
    list1.push_back(40);

    bool is_emp = list1.empty();

    int sz = list1.size();
	  	
	// 重新指定大小 

	lis1.resize(10, 100);

	lis1.resize(2);
}

                5.list 插入和删除:

                函数原型:

//函数原型

//注意插入函数中的pos得用迭代器,而且list容器的迭代器不支持跳跃式访问

push_back(elem);        //在容器尾部加入一个元素

pop_back();             //删除容器中最后一个元素

push_front(elem);       //在容器开头插入一个元素

pop_front();            //从容器开头移除第一个元素

insert(pos,elem);       //在pos位置插elem元素的拷贝,返回新数据的位置。

insert(pos,n,elem);     //在pos位置插入n个elem数据,无返回值。

insert(pos,beg,end);    //在pos位置插入[beg,end)区间的数据,无返回值。

clear();                //移除容器的所有数据

erase(beg,end);         //删除[beg,end)区间的数据,返回下一个数据的位置。

erase(pos);             //删除pos位置的数据,返回下一个数据的位置。

remove(elem);           //删除容器中所有与elem值匹配的元素。

                样例:

//list 插入和删除

void test01()
{
    
    list<int> list1;
    
    //尾插
    list1.push_back(10);
    list1.push_back(20);
    list1.push_back(30);

    //头插
    list1.push_front(100);
    list1.push_front(200);
    list1.push_front(300);

    //尾删
    list1.pop_back();

    //头删
    list1.pop_front();

    //insert插入
    list<int>::iterator it = list1.begin();
    list1.insert(++it, 1000);

    //删除
    it = list1.begin();
    list1.erase(++it);

    //移除 按值删除 删除list中所有与传入的参数相同的值
    list1.push_back(10000);
    list1.push_back(10000);
    list1.push_back(10000);
    list1.remove(10000);

    //清空
    list1.clear();
}

                6.list 数据存取:

                函数原型:

//函数原型

front(); //返回第一个元素。

back();  //返回最后一个元素。

                样例:

//list 数据存取

void test01()
{

    list<int> list1;
    
    list1.push_back(10);
    list1.push_back(20);
    list1.push_back(30);
    
    int fro_data = list1.front();

    int bac_data = list1.back();

    //迭代器不支持随机访问
    list<int>::iterator = list1.begin();

    it++;//没问题
    //it = it + 1;//报错,因为list的迭代器不支持随机访问
    it--;//没问题,因为是双向迭代器
}

                总结:list容器中不可以通过[]或者at方式访问数据,因为数据结构为链表。

                7.list 反转和排序:

                注意:

                1.List不支持algorithm库中的排序算法,因为List容器的迭代器不能跳跃式访问

                2.list容器的公共接口里有排序函数

                3.所有不支持随机访问迭代器的容器,不可以用标准算法,不支持随机访问迭代器的容器,内部会提供对应一些算法

                功能描述:将容器中的元素反转,以及将容器中的数据进行排序

                函数原型:

//函数原型

list.reverse();  //反转链表

list.sort();     //链表排序,默认排序规则从小到大,升序

                样例:

//list反转和排序

bool cmp(const int a, const int b)
{
    return a > b;
}

void test01()
{
    list<int> lst;

    lst.push_back(50);
    lst.push_back(20);
    lst.push_back(40);
    lst.push_back(10);
    lst.push_back(30);
    
    //反转
    lst.reverse();

    //排序

    //所有不支持随机访问迭代器的容器,不可以用标准算法
    //sort(lst.begin(), lst.end());//发生异常

    //不支持随机访问迭代器的容器,内部会提供对应一些算法
    //如果是自定义数据类型,使用排序算法时一定要提供自定义排序方式cmp
    lst.sort();//默认升序
    lst.sort(cmp);//自定义排序方式cmp, 此例中是 降序 
}

                set/multiset容器:

                1.概念:

                简介:插入数据的同时自动排好顺序

                本质:set/multiset属于关联式容器。底层结构是二叉树实现。

                set和multiset区别:

                1.set不允许容器中有重复的元素。

                2.multiset允许容器中有重复的元素。

                2.set构造和赋值

                函数原型:

//函数原型

//构造:
set<T> st;                      //默认构造函数:set<int>s3;

set(const set &st);             //拷贝构造函数set<int>s2(s1);

//赋值:

set& operator=(const set &st);  //重载等号操作符s3 = s2;

//插入数据:

insert(elem);                   //插入数据只有insert方法 set.insert(10);

                3.set大小和交换:

                函数原型:

//函数原型

//无法重新指定大小,没有resize()方法

size();   //返回容器中元素的数目

empty();  //判断容器是否为空

swap(st); //交换两个集合容器

                4.插入和删除:

                函数原型:

//函数原型:

insert(elem);     //在容器中插入元素。

clear();          //清除所有元素

erase(pos);       //删除pos迭代器所指的元素,返回下一个元素的迭代器。

erase(beg, end);  //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。

erase(elem);      //删除容器中值为elem的元素。

                5.查找和统计:

                函数原型:

//函数原型

find(key);  //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end()

count(key); //统计key的元素个数,set不允许重复,结果只能为0或1

                6.set和multiset的区别:

                区别:

                1.set不可以插入重复数据,而multiset可以

                2.set插入数据的同时会返回插入结果,表示插入是否成功

                3.multiset不会检测数据,因此可以插入重复数据

//

set<int> s;


//set.insert()返回的类型为 pair< set<int>::iterator , bool > ,前值表示插入的位置,是一个迭代器类型;后值表示插入是否成功
pair<set<int>::iterator, bool> res = s.insert(10);


//multiset.insert() 返回的类型为 multiset<int>::iterator
multiset<int> ms;
multiset<int>::iterator m_res = ms.insert(10);

                set排序:

                规则:默认从小到大

                主要技术点:利用仿函数,可以改变排序规则

                内置数据类型:

//内置数据类型

class my_cmp
{
public:

    bool operator()(int v1, int v2)
    {
        return v1 > v2;
    }
};

void test01()
{
    set<int> s1;

    s1.insert(10);
    s1.insert(50);
    s1.insert(60);
    s1.insert(30);
    s1.insert(20);

    //打印
    print_set(s1);

    //指定排序规则 为 从大到小
    //指定排序规则需要在插入数据之前
    set<int, my_cmp> s2;

    s2.insert(10);
    s2.insert(50);
    s2.insert(60);
    s2.insert(30);
    s2.insert(20);
    
    //遍历时 迭代器也要变为 set<int, my_cmp>::iterator 输出数据仍然为 cout<<*it<<" ";

}

                自定义数据类型:

//自定义数据类型

class Person
{
public:
    Person(string name, int age)
    {
        p_name = name;
        p_age = age;
    }

    stirng p_name;
    int p_age;
};

class my_cmp
{
public:
    bool operator()(const Person& p1, const Person& p2)
    {
        //按年龄升序
        return p1.p_age < p2.p_age;
    }
};

void test01()
{
    //自定义数据类型,都要定义排序规则
    set<Person, my_cmp> s;

    Person p1("刘备",20);
    Person p2("关羽",28);
    Person p3("张飞",26);
    Person p4("赵云",21);

    s.insert(p1);
    s.insert(p2);
    s.insert(p3);
    s.insert(p4);
    
    //未定义排序规则之前会导致异常,无法输出
    for(set<Person, my_cmp>::iterator it = s.begin(); s != s.end(); it++)
        cout<<"姓名:"<<(*it).p_name<<"\t"<<"年龄:"<<(*it).p_age<<endl;
}

                pair对组:

                函数原型:

//函数原型:

//创建
pair<type, type> p ( value1, value2 );             //默认构造 value1 value2赋初值

pair<type, type> p = make_pair( value1, value2 );  //利用make_pair

//访问

p.first 和 p.second;                               //如cout<<p.first;

//赋值:

pair<type, type> p = p1;                           //operator=

                map/multimap容器:

                1.基本概念:

                简介:

                1.map中所有元素都是pair,数据成对出现

                2.pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)

                3.所有元素都会根据元素的键值自动排序

                本质:

                map/multimap属于关联式容器,底层结构是用二叉树实现。

                优点:

                可以根据key值快速找到value值

                map与multimap区别:

                map与multimap使用方法一致,但是map和multimap也有区别

                1.map不允许容器中有重复key值元素,但是value可以重复

                2.multimap允许容器中有重复key值元素,也允许value值重复

                map/multimap容器的迭代器也不支持随机访问

                2.map构造和赋值:

                函数原型:

//函数原型

//构造:

map<T1, T2> mp;                 //map默认构造函数:

map(const map &mp);             //拷贝构造函数

//赋值:

map& operator=(const map &mp);  //重载等号操作符

                样例:

//map构造和赋值

// 打印函数
void printMap(map<int,int> &m) 
{
	for(map<int,int>::iterator it=m.begin();it!=m.end();it++)
    {
		cout << "Key值:" << it->first << "    " << "数值:" << it->second << endl;
	}
    cout<<endl;
}

void test01() {

	// 默认构造
	map<int, int> m1;

    // 注意插入的时候一定要用对组
    //map容器不允许有重复的Key值元素!和set容器一样编译器并不会报错,但最后还是相当于没插这个数.
	m1.insert(pair<int, int>(1, 10));
	m1.insert(pair<int, int>(4, 40));
	m1.insert(pair<int, int>(2, 20));
	m1.insert(pair<int, int>(3, 30));
	printMap(m1);

	// 拷贝构造
	map<int, int> m2(m1);
	printMap(m2); 


	// 等号赋值
	map<int, int>m3;
    m3 = m2;
	printMap(m3);
}

                3.map大小和交换:

                函数原型:

//函数原型

size();   //返回容器中元素的数目

empty();  //判断容器是否为空

swap(st); //交换两个集合容器

                4.map插入和删除:

                函数原型:

//函数原型:

insert(elem);         //在容器中插入元素。

clear();              //清除所有元素

erase(pos);           //删除pos迭代器所指的元素,返回下一个元素的迭代器。

erase(beg, end);      //删除迭代器区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
    
erase(key);           //删除容器中键值为key的元素。

                样例:

//map插入和删除

void test01() 
{
	map<int, int> m;


	// 插入方法,第一种,利用对组
	m.insert(pair<int, int>(1, 10));

	// 第二种插入方法,make_pair
	m.insert(make_pair(2, 20));


	// 第三种插入方法,::value_type,不推荐
	m.insert(map<int, int>::value_type(3, 30));

	// 第四种
	m[4] = 40;

	// 直接打印cout<<m[5],会为容器创建一个键值为5 value为0的元素
	cout<<m[5]<<endl;

	// 删除
	m1.erase(6);//按key值来删除

	m1.erase(m1.begin());//迭代器

	m1.erase(m1.begin(),m1.end());//迭代器

	// 清空
	m2.clear();
}

                5.map查找和统计:

                函数原型:

//函数原型

find(key);  //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
            //map<int, int>::iterator pos = map.find(key);
            //key: (*pos).first,  value: (*pos).second

count(key); //统计key的元素个数,对于map容器。元素个数要么为0,要么为1

                6.map容器排序:

                因为map是根据key值进行排序的,所以更改的排序规则针对的是key值。必须在容器插入数据前就给出排序规则。

                主要技术点:利用仿函数,可以改变排序规则

                样例:

//map排序

class my_cmp
{
public:

	// 修改内置数据类型排序为降序
	bool operator()(int v1, int v2)
	{
		return v1 > v2;
	}
};

void test01()
{
	// 创建map容器
	map<int, int, my_cmp> m;

	m.insert( make_pair(1, 10));
	m.insert( make_pair(2, 20));
	m.insert( make_pair(3, 30));
	m.insert( make_pair(4, 40));

}

                函数对象:

                1.函数对象概念:

                概念:

                1.重载函数调用操作符的类,其对象也称为函数对象

                2.函数对象使用重载()时,行为类似函数调用,也叫仿函数

                本质:函数对象(仿函数)是一个类,不是一个函数。

                2.函数对象使用:

                1.函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值

                2.函数对象超出普通函数的概念,函数对象可以有自己的状态

                3.函数对象可以作为参数传递

                 样例:

// 函数对象(仿函数)

class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

//1.函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
void test01()
{
	MyAdd myadd;
	cout << myadd(10, 10) << endl;
}


//2.函数对象超出普通函数的概念,函数对象可以有自己的状态
class MyPrint
{
public:
	MyPrint()
	{
		this->count = 0;
	}
	void operator()(string test)
	{
		cout << test << endl; 
		this->count++;
	}

	int count;//内部自己状态
};

void test02()
{
	MyPrint myprint;
	myprint("hello world");
	myprint("hello world");
	myprint("hello world");
	myprint("hello world");
	myprint("hello world");
	myprint("hello world");

	cout << "MyPrint调用次数为:" << myprint.count << endl;
}


//3.函数对象可以作为参数传递
void doPrint(MyPrint& mp, string test)
{
	mp(test);
}

void test03()
{
	MyPrint myPrint;
	doPrint(myPrint, "hello c++");
}

                谓词:

                谓词概念:

                1.返回bool类型的仿函数称为谓词

                2.如果operator()接受一个参数,那么叫做一元谓词

                3.如果operator()接收两个参数,那么叫做二元谓词

                一元谓词:

//一元谓词

class GreaterFive
{
public:
	bool operator()(int val)
	{
		return val > 5;
	}
};

void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	//查找容器中,有没有大于5的数字 
	                                             //GreaterFive()匿名函数对象
	vector<int>::iterator pos = find_if(v.begin(), v.end(), CreaterFive());

	if (pos == v.end())
	{
		cout << "未找到" << endl;
	}
	else
	{
		cout << "找到了,大于5的数字为:" << *pos << endl;
	}
}  

                二元谓词:

//二元谓词

class MyCompare
{
public:
	bool operator()(int val1,int val2)
	{
		return val1 > val2;
	}
};

void test01()
{
	vector<int>v;

	v.push_back(10);
	v.push_back(40);
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);
	
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;

	//改变为降序
	sort(v.begin(), v.end(),MyCompare());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

                内建函数对象:

                1.内建函数对象意义:

                概念:STL内建了一些函数对象

                分类

                1.算数仿函数

                2.关系仿函数

                3.逻辑仿函数

                用法

                1.这些仿函数所产生的对象,用法和一般函数完全相同

                2.使用内建函数对象,需要引入头文件#include< functional>

                2.算数仿函数:

                功能描述

                1.实现四则运算

                2.其中negate是一元运算,其它都是二元运算

                仿函数原型:

//仿函数原型

template<class T> T plus<T>        // 加法仿函数

template<class T> T minus<T>       // 减法仿函数

template<class T> T multiplies<T>  // 乘法仿函数

template<class T> T divides<T>     // 除法仿函数

template<class T>T modulus<T>      // 取模仿函数

template<class T>T negate<T>       // 取反仿函数

                样例:

//算数仿函数

void test01()
{
	// negate 一元仿函数 取反函数
	negate<int> n;
	cout << n(50) << endl;//    -50

 
	// plus 二元仿函数 加法
	plus<int> p;
	cout << p(10, 20) << endl;//    30
}

                3.关系仿函数:

                功能描述:实现关系对比

                仿函数原型:

//仿函数原型

template<class T> bool equal_ to<T>       // 等于

template<class T> bool not_ equal_ to<T>  // 不等于

template<class T> bool greater<T>         // 大于

template<class T> bool greater_ equal<T>  // 大于等于

template<class T> bool less<T>            // 小于

template<class T> bool less_ equal<T>     // 小于等于

                样例:

//关系仿函数
//大于


class MyCompare
{
public:
	bool operator()(int val1, int val2) 
	{
		return val1 > val2;
	}
};

void test01()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(40);
	v.push_back(20);
	v.push_back(50);
 
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl << endl;
    
    //降序
	// sort(v.begin(), v.end(), MyCompare());
    //利用 greater<type>() 内建函数对象 大于仿函数
	sort(v.begin(), v.end(), greater<int>());
 
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

                4.逻辑仿函数:

                功能描述:实现逻辑运算

                仿函数原型:

//仿函数原型

template<class T> bool logical_ and<T>    // 逻辑与 

template<class T> bool logical_or<T>      // 逻辑或

template<class T> bool logical_not<T>     // 逻辑非

                样例:

//逻辑仿函数
逻辑非 logical_not

void test01()
{
	vector<bool> v;

	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	v.push_back(false); 
 
	for (vector<bool>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl << endl;
 
	// 利用逻辑非 将容器 v 搬运到 v2 中,并且执行取反操作
	vector<bool> v2;

	v2.resize(v.size());
    
    //transform 搬运函数,应用时目标容器一定要有足够的空间,前两个参数为原容器的迭代器,第三个参数为目标容器的迭代器,最后一个参数为仿函数,即需要执行的操作。
	transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
 
	for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

                STL常用算法:

                概述:

                1.算法主要是由头文件< algorithm >< functional > < numeric >组成

                2.< algorithm >是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等。

                3.< functional >定义了一些模板类,用以声明函数对象

                4.< numeric >体积很小,只包括几个在序列上面进行简单数据运算的模板函数

                1.常用遍历算法:

                算法简写:

//常用遍历算法

for_each    //遍历容器

transform   //搬运容器中的数据至另一个容器中

                for_each:

                功能描述:实现遍历容器

                函数原型:

//函数原型:

for_each(iterator beg, iterator end, _func);

//beg:开始迭代器

//end:结束迭代器

//_func:普通函数或者函数对象(仿函数)

                样例:

//for_each


//普通函数
void Print01(int val)
{
	cout << val<<" ";
}

//仿函数
class Print02
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int>v;

	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	for_each(v.begin(), v.end(),Print01);//普通函数
	cout << endl;

	for_each(v.begin(), v.end(), Print02());//仿函数
	cout << endl;
}

                transform:

                功能描述:搬运容器中的数据至另一个容器中

                函数原型:

//函数原型:

transform(iterator beg1, iterator end1, iterator beg2, _func);

//beg1:源容器开始迭代器

//end1:源容器结束迭代器

//beg2:目标容器开始迭代器

//_func:函数或者函数对象,在搬运过程中对数据做的操作。

                样例:

//transform

//普通函数,将元素搬运过去并+100
int Transform(int val)
{
	return val + 100;
}

//仿函数
class print02
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	for_each(v.begin(), v.end(), print02());
	cout << endl;


	vector<int> v2;//目标容器

	v2.resize(v.size()); //目标容器必须先开辟空间

	transform(v.begin(), v.end(), v2.begin(), Transform);

	for_each(v2.begin(), v2.end(), print02());
	cout << endl;
}

                2.常用查找算法:

                学习目标:掌握常用的查找算法

                算法简介:

//算法简介:

find             //查找元素

find_if          //按条件查找元素

adjacent_find    //查找相邻重复元素

binary_search    //二分查找法

count            //统计元素个数

count_if         //按条件统计元素个数

                find:

                功能描述: 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

                函数原型:

//函数原型

find(iterator beg, iterator end, value);    
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg:开始迭代器

//end:结束迭代器

//value:查找的元素

                样例:

//find


//使用find查找内置数据类型
void test01()
{
	vector<int> v1;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
	}

    //查找容器中 5 是否存在
	vector<int>::iterator it = find(v1.begin(), v1.end(), 5);
	if (it == v1.end())
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到 值为:" << *it << endl;
	}
}

//使用find查找自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
    //重载 == ,让底层find知道如何对比Person数据类型  
	bool operator==(const Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
			return true;
		else
			return false;	
	}

	int m_Age;
	string m_Name;
};

void test01()
{
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

	vector<Person> v;
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

    //若为自定义数据类型,必须重载 == 让底层find知道如何对比自定义的数据类型  
	vector<Person>::iterator it = find(v.begin(), v.end(), p3);

	if (it == v.end())
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到了 姓名:" << it->m_Name << " 年龄:" << it->m_Age << endl;
	}
}

                find_if:

                功能描述: 按条件查找元素

                函数原型:

//函数原型:

find_if(iterator beg, iterator end, _Pred);
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg:开始迭代器

//end:结束迭代器

//_Pred:函数或者谓词(返回bool类型的仿函数)

                样例:

//find_if
//测试内置数据类型和自定义数据类型的find_if功能


class GreaterFive
{
public:
	bool operator()(int val)
	{
		return val > 5;
	}
};

//内置数据类型
void test01()
{
	vector<int>v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end())
	{
		cout << "没找到" << endl;	
	}
	else
	{
		cout << "找到了" << *it << endl;
	}
}


//自定义数据类型
class Person
{
public:
	Person(string name,int age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}
	string m_Name;
	int m_Age;
};
class Greater20
{
public:
	bool operator()(Person &p)
	{
		return p.m_Age > 20;
	}
};
void test02()
{
	vector<Person>V;
	Person p1("1",11);
	Person p2("2",22);
	Person p3("3",33);
	Person p4("4",44);

	V.push_back(p1);
	V.push_back(p2);
	V.push_back(p3);
	V.push_back(p4);

	vector<Person>::iterator it = find_if(V.begin(), V.end(), Greater20());
	if (it == V.end())
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到了" << it->m_Name<<" "<<it->m_Age << endl;
	}
}

                adjacent_find:

                功能描述: 查找相邻重复元素

                函数原型:

//函数原型:

adjacent_find(iterator beg, iterator end); //查找相邻重复元素,返回相邻元素的第一个位置的迭代器

//beg:开始迭代器

//end:结束迭代器

                样例:

//adjancent_find

void test01()
{
	vector<int> v1;
	v1.push_back(0);
	v1.push_back(1);
	v1.push_back(3);
	v1.push_back(0);
	v1.push_back(4);
	v1.push_back(4);
	v1.push_back(6);
	v1.push_back(7);
	vector<int>::iterator it = adjacent_find(v1.begin(), v1.end());
	if (it == v1.end())
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到 元素为:" << *it << endl;
	}
}

                binary_search:

                功能描述: 查找指定元素是否存在

                函数原型:

//函数原型
//注意: 在无序序列中不可用

bool binary_search(iterator beg, iterator end, value); 
//查找指定的元素,查到 返回true 否则false

//beg:开始迭代器

//end:结束迭代器

//value:查找的元素

                样例:

// binary_search

void test01()
{
	vector<int> v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	bool ret = binary_search(v.begin(), v.end(), 2);
	if (ret == 0)
	{
		cout << "没找到" << endl;
	}
	else
	{
		cout << "找到" <<  endl;
	}
}

                count:

                功能描述: 统计元素个数

                函数原型:

//函数原型:

count(iterator beg, iterator end, value);    //统计元素出现次数

//beg:开始迭代器

//end:结束迭代器

//value:统计的元素

                样例:

//count

//内置数据类型
void test01()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);

	int num = count(v.begin(), v.end(), 4);

	cout << "4的个数为: " << num << endl;
}

//自定义数据类型
//统计自定义数据类型时需要重载 == 号
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	bool operator==(const Person& p)
	{
		if (this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	string m_Name;
	int m_Age;
};

void test02()
{
	vector<Person> v;

	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 30);
	Person p5("曹操", 25);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	Person p("诸葛亮", 35);

	int num = count(v.begin(), v.end(), p);
	cout << "和诸葛亮同岁的人数为:" << num << endl;
}

                count_if:

                功能描述: 按条件统计元素个数

                函数原型:

//函数原型:

count_if(iterator beg, iterator end, _Pred);  //按条件统计元素出现次数

//beg:开始迭代器

//end:结束迭代器

//_Pred:谓词

                样例:

//count_if

class Greater4
{
public:
	bool operator()(int val)
	{
		return val >= 4;
	}
};

//内置数据类型
void test01()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);

	int num = count_if(v.begin(), v.end(), Greater4());

	cout << "大于4的个数为: " << num << endl;
}

//自定义数据类型
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

class AgeLess35
{
public:
	bool operator()(const Person& p)
	{
		return p.m_Age < 35;
	}
};
void test02()
{
	vector<Person> v;

	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 30);
	Person p5("曹操", 25);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	int num = count_if(v.begin(), v.end(), AgeLess35());
	cout << "小于35岁的个数:" << num << endl;
}

                3.常用排序算法:

                学习目标: 掌握常用的排序算法

                算法简介:

//算法简介:

sort()             //对容器内元素进行排序

random_shuffle()   //洗牌 指定范围内的元素随机调整次序

merge()            // 容器元素合并,并存储到另一容器中

reverse()          // 反转指定范围的元素

                sort:

                功能描述: 对容器内元素进行排序

                函数原型:

//函数原型:

sort(iterator beg, iterator end, _Pred);
//对容器内数据进行排序,第三个参数缺省时默认为升序

//beg:开始迭代器

//end:结束迭代器

//_Pred:谓词

                样例:

//sort

void myPrint(int val)
{
	cout << val << " ";
}

void test01() {
	vector<int> v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

	//sort默认从小到大排序
	sort(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	//从大到小排序
	sort(v.begin(), v.end(), greater<int>());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}

                random_shuffle:

                功能描述: 洗牌 指定范围内的元素随机调整次序

                函数原型:

//函数原型:

random_shuffle(iterator beg, iterator end);  //指定范围内的元素随机调整次序

//beg:开始迭代器

//end:结束迭代器

                样例:

//random_shuffle:

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{

//加一个随机数种子,每次都能产生不同的随机值。
	srand((unsigned int)time(NULL));

	vector<int> v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	//打乱顺序
	random_shuffle(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

                merge:

                功能描述: 两个容器元素合并,并存储到另一容器中。

                函数原型:

//函数原型:

merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//容器元素合并,并存储到另一容器中
//注意: 两个容器必须是有序的

//beg1:容器1开始迭代器

//end1:容器1结束迭代器

//beg2:容器2开始迭代器

//end2:容器2结束迭代器

//dest:目标容器开始迭代器

                样例:

//merge:
//merge合并的两个容器必须是有序序列

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v1;
	vector<int> v2;

	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i + 1);
	}

	vector<int> vtarget;

	//目标容器需要提前开辟空间
	vtarget.resize(v1.size() + v2.size());

	//合并  需要两个有序序列
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin());

	for_each(vtarget.begin(), vtarget.end(), myPrint());
	cout << endl;
}

                总结:merge合并的两个容器必须是有序序列

                reverse:

                功能描述: 将容器内元素进行反转
                函数原型:

//函数原型:

reverse(iterator beg, iterator end);    //反转指定范围的元素

//beg:开始迭代器

//end:结束迭代器

                样例:

//reserve

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

	cout << "反转前: " << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	cout << "反转后: " << endl;

	reverse(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

                4.常用拷贝和替换算法:

                学习目标: 掌握常用的拷贝和替换算法

                算法简介:

//算法简介:

copy()         // 容器内指定范围的元素拷贝到另一容器中

replace()      // 将容器内指定范围的旧元素修改为新元素

replace_if()   // 容器内指定范围满足条件的元素替换为新元素

swap()         // 互换两个容器的元素

                copy:

                功能描述: 将容器内指定范围的元素拷贝到另一容器中

                函数原型:

//函数原型:
copy(iterator beg, iterator end, iterator dest);
//将容器内指定范围的元素拷贝到另一容器中

//beg:开始迭代器

//end:结束迭代器

//dest:目标起始迭代器

                样例:

//copy

class myPrints
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v1;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i + 1);
	}
	vector<int> v2;
	v2.resize(v1.size());
	copy(v1.begin(), v1.end(), v2.begin());

	for_each(v2.begin(), v2.end(), myPrints());
	cout << endl;
}

                replace:

                功能描述: 将容器内指定范围的旧元素修改为新元素

                函数原型:

//函数原型

replace(iterator beg, iterator end, oldvalue, newvalue);
//将区间内旧元素 替换成 新元素

//beg 开始迭代器

//end结束迭代器

//oldvalue旧元素

//newvalue新元素

                样例:

//replace

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);
	v.push_back(70);

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

    //替换
	replace(v.begin(), v.end(), 20, 2000);

	for_each(v.begin(), v.end(), myPrint());
}

                replace_if:

                功能描述: 将区间内满足条件的元素,替换成指定元素

                函数原型:

//函数原型:

replace_if(iterator beg, iterator end, _pred, newvalue);
//按条件替换元素,满足条件的替换成指定元素

//beg:开始迭代器

//end:结束迭代器

//_pred:谓词

//newvalue:替换的新元素

                样例:

//replace_if:

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

class Greater30
{
public:
	bool operator()(int val)
	{
		return val >= 30;
	}
};

void test01()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);
	v.push_back(70);

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	//把大于等于30的替换为3000
	replace_if(v.begin(), v.end(), Greater30(), 3000);

	for_each(v.begin(), v.end(), myPrint());
}

                swap:

                功能描述: 互换两个容器的元素

                函数原型:

//函数原型:

swap(container c1, container c2);    //互换两个容器的元素

//c1:容器1

//c2:容器2

                样例:

//swap:

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++) {
		v1.push_back(i);
		v2.push_back(i + 100);
	}

	cout << "交换前: " << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;

	cout << "交换后: " << endl;
	swap(v1, v2);
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;
}

                5.常用算术生成算法:

                学习目标: 掌握常用的算术生成算法

                注意: 算术生成算法属于小型算法,使用时包含的头文件为#include<numeric>

                算法简介:

//算法简介:

accumulate();   // 计算容器元素累计总和

fill();         // 向容器中添加元素

                accumulate:

                功能描述: 计算区间内容器元素累计总和

                函数原型:

//函数原型:

accumulate(iterator beg, iterator end, value);    //计算容器元素累计总和

//beg:开始迭代器

//end:结束迭代器

//value:起始值(起始的累加值,将后续所有的值都加到这里)

                样例:

//accumulate:

void test01()
{
	vector<int> v;
	for (int i = 0; i <= 100; i++) {
		v.push_back(i);
	}

	int a = accumulate(v.begin(), v.end(), 0);

    //参数3:起始累加值
	int b = accumulate(v.begin(), v.end(), 1000);

	cout << "a = " << a << ", b = " << b << endl;
}

               fill:

                功能描述: 向容器中填充指定的元素 

                函数原型:

//函数原型

fill(iterator beg, iterator end, value);    //向容器中填充元素

//beg:开始迭代器

//end:结束迭代器

//value:填充的值

                样例:

//fill

class myPrint
{
public:
	void operator()(int val)
	{
		cout << val << " ";
	}
};

void test01()
{

	vector<int> v;
	v.resize(10);

	//填充
	fill(v.begin(), v.end(), 100);

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

                6.常用集合算法: 

                学习目标: 掌握常用的集合算法

                算法简介:

//算法简介:

set_intersection();     // 求两个容器的交集

set_union();            // 求两个容器的并集

set_difference();       // 求两个容器的差集

                set_intersection:

                功能描述: 求两个容器的交集

                函数原型:

//函数原型:

set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//求两个集合的交集。注意,两个集合必须是有序序列

//beg1:容器1开始迭代器

//end1:容器1结束迭代器

//beg2:容器2开始迭代器

//end2:容器2结束迭代器

//dest:目标容器开始迭代器

                样例:

//set_intersection:


void myPrint(int val)
{
	cout << val << " ";
}

void test01()
{
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i+5);
	}

	for_each(v1.begin(), v1.end(), myPrint);
	cout << endl;

	for_each(v2.begin(), v2.end(), myPrint);
	cout << endl;

	vector<int> v3;

	//容器大小为这两个容器中最小的那个
	v3.resize(min(v1.size(), v2.size()));
    
    //获取交集 返回值为交集中最后一个元素的位置
	vector<int>::iterator End = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

    //注意第二个参数为 End,因为目标容器的 size() 很有可能比交集大,如果为 v3.end(),后面会输出0
	for_each(v3.begin(), End, myPrint);
	cout << endl;
}

                set_union:

                功能描述: 求两个集合的并集

                函数原型:

//函数原型:

set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//求两个集合的并集,注意:两个集合必须是有序序列。

//beg1:容器1开始迭代器

//end1:容器1结束迭代器

//beg2:容器2开始迭代器

//end2:容器2结束迭代器

//dest:目标容器开始迭代器

                样例:

void myPrint(int val)
{
	cout << val << " ";
}

void test01()
{
	vector<int> v1;
	vector<int> v2;

	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i+5);
	}

	for_each(v1.begin(), v1.end(), myPrint);
	cout << endl;

	for_each(v2.begin(), v2.end(), myPrint);
	cout << endl;

	vector<int> v3;

	//新容器大小为之前两个容器大小之和
	v3.resize(v1.size() + v2.size());

	vector<int>::iterator End = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

	for_each(v3.begin(), End, myPrint);
	cout << endl;
}

                set_difference:

                功能描述: 求两个集合的差集

                函数原型:

//函数原型:

//求两个集合的差集,注意:两个集合必须是有序序列。
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//得到的结果为 容器1 - 容器2 , 即容器1中有而容器2中没有的数据

//beg1:容器1开始迭代器

//end1:容器1结束迭代器

//beg2:容器2开始迭代器

//end2:容器2结束迭代器

//dest:目标容器开始迭代器

                样例:

//set_difference:

void myPrint(int val)
{
	cout << val << " ";
}

void test01()
{
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
		v1.push_back(i);
		v2.push_back(i+5);
	}
	for_each(v1.begin(), v1.end(), myPrint);
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint);
	cout << endl;

	vector<int> v3;
	v3.resize(max(v1.size(),v2.size()));
	//v1和v2的差集
	vector<int>::iterator End = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
	for_each(v3.begin(), End, myPrint);
	cout << endl;
	//v2和v1的差集
	End = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), v3.begin());
	for_each(v3.begin(), End, myPrint);
	cout << endl;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值