c++知识点整理(下)

又花了好长时间整理了c++下部分知识,终于结束了…累成狗

面向对象程序设计语言的特性: 封装,继承,多态

  • 封装是基础
  • 继承是关键
  • 多态是补充

多态

  • 什么是多态?
多态存在于继承之中,是继承的进一步扩展多态的含义就是多种形态cpp语言的多态性是指同一个函数名具有不同的实现,同一个运算符具有不同的功能。这分别是函数的重载或重写和运算符的重载或重写
  • 多态性的体现
①子类和基类的同名成员函数发生重载
②子类重写了基类的普通成员函数
③子类重写了基类的虚函数
  • cpp支持两种多态性
一种是编译时的多态性,一种是运行时的多态性。
前一种又称为静态编译,后一种又称为动态编译。
编译时的多态性是通过重载来实现的,运行时的多态性是通过继承和虚函数来实现的
  • 多态的作用是什么
封装:可以使的代码的模块化
继承:可以扩展已存在的代码,都为了实现代码的重用
多态:目的是为了实现接口的重用。即函数重用。不管传来的就是是哪个类的对象,函数都能通过同一个接口调用到适应各自对象的成员方法。
  • 多态的实现
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类的对象
调用相应的虚函数,可以根据指向子类的不同而实现不同的方法,如果没
有使用虚函数的话,可以说没有用到多态的精髓,不用虚函数,利用基类
的指针调用相应的函数的时候,会被限制在基类函数的本身,而无法调用
子类中被重写的函数。

运算符重载

对已有的运算符重新定义,是它的功能得到拓展
  • 运算符重载的若干问题
除了. * :: ?: sizeof 这几个运算符之外都可以重载

运算符的重载不是建立新的运算符,而是对现有的运算符进行重载,使它再用于类的对象的时候具有新类型的定义。使得cpp具有更好的可拓展性。

运算符的重载是通过编写函数的定义来实现的,通常使用的函数多为成员函数或者友元函数,在极少数情况下可以定义为一般函数。运算符的重载时使用的函数的名字由关键字operator与要重载的运算符符号组成
如:operator+,函数体指出了重载后的运算符功能
class A
A a1,a2,a3
a3=a1+a2;//对象之间的运算
  • 重载后的运算符与原运算符比较
a 优先级不变
b 结合性不变
c 操作数个数不变
通常改变操作数的类型,有内置类型变成自定义的对象
  • 用于类的对象的运算符必须重载?
由于类是用户定义的,原则上说,用于类的对象运算符都必须重载。但是:

a  在不提供对象赋值运算符时,系统提供一个默认的赋值运算符,其功能是逐个复制类的数据成员。但是,在有些情况下我们也会重载赋值运算符
b  对于任何类的对象通常无需重载地址运算符&
  • 编译系统如何选择重载的运算符?
运算符的重载实际上是通过定义一个函数实现的,运算符重载实质上就是函数的重载,编译系统选择重载函数是根据操作数个数和类型。这与重载函数的选择一样
  • 运算符的重载的形式
①成员函数形式
②友元函数
①成员函数形式运算符重载函数的格式
类型 operator运算符 (参数表)
{
   ...
}

其中operator是关键字,运算符是被重载的运算符的符号,参数表中的参数的个数与重载运算符操作数的个数有关。

一般来说:

单目运算符采用成员函数形式重载,无参数;
双目运算符采用成员函数形式,一个参数。
引用该成员函数的对象作为第一操作数,参数表中的参数作为第二操作数

注意:

重载运算符不能重载内置的数据类型,重载函数里面至少要有个类对象
class A{
    private:
        double i;
        double j;
    public:
        A()
        {
            i=0;
            j=0;
        }
        A(double x,double y)
        {
            i=x;
            j=y;
        }
        A operator+(const A &a)
        {
            return A(i+a.i,j+a.j);
        }
        A operator-(const A &a)
        {
            return A(i-a.i,i-a.j);
        }
        A operator*(const A &a)
        {
            A b;
            b.i=(i*a.i-j*a.j);
            b.j=(i*a.j+j*a.i);
            return b;
        }
        A operator/(const A &a)
        {
            A b;
            b.i=(i*a.i+j*a.j)/(a.i*a.i+a.j*a.j);
            b.j=(j*a.i-i*a.j)/(a.i*a.i+a.j*a.j);
            return b;
        }
        void Print()
        {
            if(j>0)
                cout<<i<<"+"<<j<<"i";
            else
                cout<<i<<j<<"i";
        }
};
int main()
{
    A a(1,2),b(3,4),c;
    c=a+b;
    a.Print();
    cout<<" + ";
    b.Print();
    cout<<" = ";
    c.Print();
    cout<<endl;
    return 0;
}

友元函数形式运算符重载函数的格式

friend 类型 operator 运算符(参数表)
{...}
其中friend 关键字说明该函数为友元函数。一般讲,对双目运算符以友元函数的形式重载时,参数表有两个参数,对单目运算符以友元函数的形式重载时,参数表内只有一个参数

注:一般情况下单目运算符重载常使用成员函数形式,而双目运算符的重载常用选用友元函数的形式

[ ]  ->  = 用成员函数

联编

联编是指一个程序自身彼此关联的过程,具体讲函数联编就是指将一个函数的调用与其相应函数体代码相连接。

c++语言支持两种函数联编,静态联编和动态联编。

静态联编

  • 静态联编是在调用同名函数时,编译器将根据调用时所使用的实参在编译时就确定下来应该调用的是哪一个函数体,优点是速度快,但是在一些情况下,程序员难以预测到调用某一个函数时使用什么对象,所以灵活性不够
  • 静态联编:编译时的多态性,根据编译时的对象来选择同名函数中相应的函数体

动态联编

  • 动态联编是指在运行时根据当时的情况来确定调用的同名函数是哪一个函数体。它是通过虚函数来实现的。在继承的情况下,不同类仲可以具有相同的名字的虚函数,他们对应不同的实现。动态联编就是在运行时更具有当时的对象来选择相应的函数体,所以灵活性很强,但效率低。
  • 说明:动态联编时运行时的多态性,在不同类中吸纳共同名的函数实现的选择是在运行时进行的。为此需要将同名函数定义为虚函数,这样才能进行动态联编。
virtual 类型 函数名(参数表)
{。。。}
其中virtual是说明定义虚函数的关键字
class A{
    public:
        virtual void fun()
        {
            cout<<"class A.\n";
        }
        void func1()
        {
            cout<<"class a.\n";
        }
};
class B:public A
{
    public:
        void fun()
        {
            cout<<"class B.\n";
        }
        void func1()
        {
            cout<<"class b.\n";
        }
};
class C:public B
{
    public:
        virtual void fun()
        {
            cout<<"class C.\n";
        }
        void func1()
        {
            cout<<"class c.\n";
        }
};
int main()
{
    C c;
    c.A::fun();
    c.B::fun();
    c.fun();
    A *pa=&c;
    pa->fun();//调用虚函数实现动态联编,pa指向的是给它赋的真实的对象类型
    pa->func1();//实现静态联编,pa指向的是子类对象的基类部分
    B &rb=c;
    rb.fun();//实现静态联编,rb绑定的是给它赋的真实的对象
    return 0;
}
调用虚函数实现动态联编,pa指向的是给它赋的真实的对象类型
实现静态联编,pa指向的是子类对象的基类部分
实现静态联编,rb绑定的是给它赋的真实的对象

虚函数

  • 虚函数具有下述特点:
虚函数是使用关键字virtual来说明的
基类中说明的了虚函数在他的派生类中与基类中相同说明的虚函数,一定是虚函数,可以省略关键字virtual

实现动态联编的条件:
①公有继承
②调用虚函数
③通过基类对象指针或基类对象的引用调用虚函数

一个静态的成员函数,不能说明为虚函数
通过成员函数调用虚函数采取什么联编?
class A{
    public:
        virtual void f1()
        {
            cout<<"A::f1() called.\n";
        }
        void f2()
        {
            f1();
        }
};
class B:public A
{
    public:
        void f1()
        {
            cout<<"B::f1() called.\n";
        }
        void f3()
        {
            A::f1();
        }

};
int main()
{
    B b;
    b.f2();//动态联编
    b.f3();//
    A &ra=b;
    ra.f2();//动态联编
    A a=b;
    a.f2();//静态联编
    return 0;
}

构造函数和析构函数中调用虚函数实现什么联编?
通过构造函数和析构函数调用虚函数发生静态联编
成员函数调用虚函数发生动态联编
class A{
    public:
        A()
        {
            cout<<"A() called.\n";
            f1();
        }
        virtual void f1()
        {
            cout<<"A::f1() called.\n"<<endl;
        }
        ~A()
        {
            cout<<"~A() called.\n";
            f1();
        }
};
class B:public A
{
    public:
        B()
        {
            cout<<"B() called.\n";
            f1();
        }
        void f1()
        {
            cout<<"B::f1() called.\n"<<endl;
        }
        ~B()
        {
            cout<<"~B called.\n";
            f1();
        }
};
int main()
{
    B b;
    return 0;
}

静态联编:调用的虚函数是自身或者基类的
动态联编:调用的虚函数是它的派生类的

class A{
    public:
        virtual void f1()
        {
            cout<<"A::f1() called.\n";
        }
        virtual void f2()
        {
            cout<<"A::f2() called.\n";
        }
};
class B:public A
{
    public:
        B()
        {
            f1();
        }
        void gf1()
        {
            f1();
        }
        ~B()
        {
            f2();
        }
};
class C:public B
{
    public:
        void f1()
        {
            cout<<"C::f1() called.\n";
        }
        ~C()
        {
            f2();
        }
        void f2()
        {
            cout<<"C::f2() called.\n";
        }
};
int main()
{
    C c;
    c.gf1();
    return 0;
}

纯虚函数

  • 定义:
一种特殊的虚函数,纯虚函数是没有具体的实现的一种函数。在定义他的基类中不给他具体的实现,而在其派生类中给出函数的实现。
纯虚函数的一般形式如下:
class 类名
{
virtual 类型 函数名(参数表)=0;
}
class A{
    public:
        virtual void fun()
        {
            cout<<"A::fun() called.\n";
        }
};
class B:public A
{
    public:
        virtual void fun()
        {
            cout<<"B::fun() called.\n";
        }
};
class C:public B
{
    public:
        void fun()//空虚函数
        {}
};
void Print(A *a)
{
    (*a).fun();
}
int main()
{
    A *pa=new A;
    B *pb=new B;
    C *pc=new C;
    Print(pa);
    Print(pb);
    Print(pc);
    return 0;
}
其中,函数名是纯虚函数名,他是虚函数的一种,需要用关键字virtual说明。该函数没有具体的实现,即用赋值为0来表示,他的具体的实现在其派生类中
class A{
    private:
        int x0,y0;
    public:
        A(int i=0,int j=0)
        {
            x0=i;
            y0=j;
        }
        virtual void Draw()=0;
};

class B:public A
{
    private:
        int x1,y1;
    public:
        B(int i=0,int j=0):A(i,j)
        {
            x1=i;
            y1=j;
        }
        void Draw()
        {
            cout<<"B::Draw()\n";
        }
};
class C:public B
{
    private:
        int x2,y2;
    public:
        C():B(0,0)
        {
            x2=0;
            y2=0;
        }
        void Draw()
        {
            cout<<"C::Draw() called.\n";
        }
};
void Drawobj(A *p)
{
    p->Draw();
}
int main()
{
    B *b=new B;
    C *c=new C;
    Drawobj(b);
    Drawobj(c);
    return 0;
}
在基类里有一个虚函数,并不打算调用它,所以他没有任何具体的实现,只是为了让它的子类继承它,要调用的是它子类中的这个函数,所以我们可以定义它在基类里面为虚函数。

抽象类

  • 定义:
包含纯虚函数的类,抽象类不能用来定义对象,它只是为了方便类的设计提出来的。通常一个抽象类作为基类,即它里面有一个纯虚函数,如果在他的子类里面,这个纯虚函数并没有实现定义,那么它的子类也是个抽象类。

模版

  • 什么是模版
模版是一种工具使用它程序员可以建立具有通用类型的函数库和类库
模版有两种不同形式:函数模版和类模版
  • 模板分类:函数模板类模板

  • 为什么引进模版

函数模板实际上是一个通用的函数,它可以适应某个范围的不同类型的对象操作。这样做可以避免程序员的重复劳动,也可以增加程序的灵活性和安全性。在有些情况下,函数模板可以替代函数重载

函数模板

  • 函数模板的定义格式
template<参数化类型名表>
类型 函数名(参数表)
{函数体}
其中template是定义模版的关键字。参数化类型名表又称模版参数表,该表中有多个表项时用逗号分隔,每个表项称为一个模版参数,该表通常使用的格式如下
class 标识符1class 标识符2...
这里class不是定义类的关键字,其含义表示其后为参数化的类型名
一个函数模板的例子
template <class SwapType>
void Swap(SwapType &a,SwapType &b)
{
SwapType temp;
temp=a;
a=b;
b=temp;
}
  • 函数模板与模版函数

    函数模版是对一组函数的描述,而模版函数是某个函数模版的一个试函数
    编译系统对于程序中定义的函数模版并不产生可执行代码!!当编译系统在程序中发生由于函数模版的形参表中相匹配的函数调用的时候,便生成一个模版函数,该函数和的函数体与函数模板的函数体相同,编译系统对模版函数的生成可执行代码

//用int型来替代模版参数SwapType,将生成一个模版函数
void Swap(int &a,int &b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}

编写求两个数中的最小值的函数模板,并计算几个函数模板的值

#include <iostream>
using namespace std;
template <class T>
T Min(T &a,T &b)
{
    return a<b?a:b;
}
int main()
{
    int x1=5,y1=8;
    cout<<"Min(x1,y1)="<<Min(x1,y1)<<endl;
    char x2='c',y2='d';
    cout<<"Min(x2,y2)="<<Min(x2,y2)<<endl;
    return 0;
}

//a,b的类型可一致,可不一致;返回值的类型与a的类型一致

#include <iostream>
using namespace std;
template <class T>
T MUN(T &a,T &b)//a,b的类型可一致,可不一致;返回值的类型与a的类型一致
{
    return a+b;
}
int main()
{
    int x1=5,y1=8;
    cout<<"MUN(x1,y1)="<<MUN(x1,y1)<<endl;
    char x2='1',y2='A';
    cout<<"MUN(x2,y2)="<<MUN(x2,y2)<<endl;
    return 0;
}

编写一个使用冒泡排序法进行操作的函数模板。

#include <iostream>
#include <string.h>
using namespace std;
template <class T>
void Blue(T *a,int l)
{
    for(int i=0;i<l-1;i++)
    {
        for(int j=i+1;j<l;j++)
        {
            if(a[i]>a[j])
            {
                T temp;
                temp=a[i];
                a[i]=a[j];
                a[j]=temp;
            }
        }
    }
}
int main()
{
    float a[5]={4.3,1.4,3.5,2.55,3.7};
    Blue(a,5);
    for(int i=0;i<5;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
    char str[]="sbafehfhge";
    Blue(str,strlen(str));
    cout<<str<<endl;
    return 0;
}

编写一个进行两个数比较的操作,并求出较大数的函数模板。
定义特点模版函数,替换函数模版生成的相应的模版函数

#include <iostream>
#include <string.h>
using namespace std;
template <class T>
T Max(T a,T b)
{
    return a>b?a:b;
}
/*char *Max(char *s1,char *s2)
{
    return strcmp(s1,s2)>=0?s1:s2;
}
*/
int main()
{
    double x1=5.3,y1=8.4;
    cout<<"Max(x1,y1)="<<Max(x1,y1)<<endl;
    string x2="abd",y2="abc";
    cout<<"Max(x2,y2)="<<Max(x2,y2)<<endl;
    return 0;
}

类模版

  • 类模版的引进
Node是链表的节点类,List类是链表类
class Node
{...};
class List
{
private:
public:
List();
~List();
void Add(Node &);
void Remove(Node &);
Node *Find(Node &);
}
链表类List只是对节点为Node类链表定义,如果对于另一个节点,则链表类需要重新定义。这种只因借点类型不同而类的成员没有改变的情况重新定义链表将会带来重复,为了解决这个问题,所以就引进了类模版。对链表类中节点类型参数化,即通过参数T来代替Node,将得到一个类模版
class List
{
private:
public:
List();
~List();
void Add(T &);
void Remove(T &);
Node *Find(T &);
}

可见引进类模版在于减少程序员的重复劳动,提高程序的可重用性。

  • 怎么定义一个类模版
template <参数化类型名表>
class 类名
{
类体说明;
};
  • 类模版对象的定义

    类模版是对类中数据类型进一步抽象的类,初始话类模版时,使用具体的数据类型来替代参数化的类型后便产生一个模板类,
    格式如下

类模版名<具体类型> 对象名(初始化参数)
例:
array<int>intob(10);
array<double>doubleob(10);
例:定义一个类模版TA 格式如下
template<>
class TA
{
private:
T a,b;
public:
TA(T i,T j):a(i),b(j)
{}
T geta()
{return a;}
T getb()
{return b;}
};
int型替换模版参数T,生成一个int的模版类,定义该类的对象,格式如下:
TA<int> intobject(5,6);
这里,用int替换模版参数T,生成模版 名为TA<int>
定义该类对象名为intobject,并调用2个参数的构造函数进行初始化。
TA<double> doubleobject(4.5,6.7);

定义一个数组类模版Array,用这个类模版实现的模板类定义的对象能够在堆上开辟一段连续的数组空间,该数组名就是对象名。能够直接通过下标方式访问该数组中的任何元素(模拟实现一个数组的功能)

#include <iostream>
#include <stdlib.h>
using namespace std;
template<class T>
class Array
{
    private:
        T *a;
        int length;
    public:
        Array(int i)
        {
            length=i;
            a=new T[i];
            if(!a)
            {
                cout<<"make array failed"<<endl;
                exit(1);
            }
        }
        ~Array()
        {
            delete a;
        }
    T &operator [](int i)//intobj[i]<==>intobj.operator[](i)<==>intobj[i]=a[i]
    {
        if(i<0||i>length-1)
        {
            cout<<"out of bard"<<endl;
            exit(1);
        }
        return a[i];
    }
};
int main()
{
    int a[10];
    Array<int> intobj(10);
    for(int i=0;i<10;i++)
        intobj[i]=i;
    for(int i=0;i<10;i++)
        cout<<intobj[i]<<" ";
    cout<<endl;
    return 0;
}

STL:标准模版库(standard template library):

顺序容器之一vector

标准库类型vector表示对象的集合,其中所有的对象的类型都相同。集合中的每个对象都有一个与之对应的索引,下标号,用来访问对象。因为vector容纳着其他对象,所以被称为一种容器
cpp既有类模版也有函数模版,其中vector是一个类模版。对于类模版来说,我们通过定义提供一些额外的信息来指定模版到底实例化成什么样的类。

定义和初始化vector 对象

vector<T> v1 v1是一个空的vector对象,它的元素的类型是T类型
vector <int> v1

vector <T> v2(v1) v2中包含v1中所有元素
vector <int> v2(v1) v1元素拷贝给v2

vector <T> v3(n,val) v3中包含n个重复元素,每个元素的值都是val
vector <int> v3(10,-1)  10int类型的元素,每个元素都为-1

vector <T> v4(n) v4中包含n个重复执行了值初始化的对象
vector <T> v4(10) 10元素  ,每个都为0

vector <T> v5(begin,end) 创建容器v5,并使用另一个容器中的[begin,end)之间的元素初始化容器。

向vector对象添加元素
利用vector的成员函数push_back()向其中添加元素,push_back()负责吧一个值当成是一个vector对象的尾元素 “压到(push)”vector对象的“尾端(back)”;
#include <iostream>
#include <stdlib.h>
#include <vector>
using namespace std;

int main()
{
    vector<int> v2;
    for(int i=0;i!=100;i++)
        v2.push_back(i);
    for(int i=0;i!=100;i++)
        cout<<v2[i]<<" ";
    cout<<endl;
    return 0;
}

从标准输入中读入整数,将其作为vector对象的元素存储
int main()
{
    vector<int> v2;
    int data;
    while(cin>>data)
        v2.push_back(data);
    for(int i=0;i!=v2.size();i++)
        cout<<v2[i]<<" ";
    cout<<endl;
    return 0;
}
除了使用下标访问vector对象的元素之外,标准库还提供了另外一种检测元素的方法:迭代器(iterator)

迭代器是一种允许程序检查容器内元素,并实现元素遍历的数据类型。

迭代器提供了比下标操作更一般的方法:所有的标准库类型都定义了相应的迭代器类型,而少数容器支持下标操作,所有c++程序更倾向于用迭代器来访问容器内元素,包括对vector的访问

迭代器

迭代器是面向对象版本的指针,指针可以指向一个内存的地址吗,迭代器可以指向容器的一个位置,STL 的每一个容器类模版中都定义了一组对应的迭代器类型
  • 定义迭代器:
vector<int>::iterator iter;
定义了一个名为iter的变量,它的数据类型由vector<int> 定义的iterator类型 即一个迭代器
begin和end操作
如果容器中有元素,有begin返回迭代器指向容器里面的第一个元素:
vector<int> vec;
vector<int>::iterator iter=vec.begin();
iter指向vec容器的第一个元素

由end()操作返回的迭代器执行vector的末端元素的下一个位置,通常称为超出末端迭代器,表明他指向一个不存在的元素
如果vector是空的,begin返回的迭代器和end返回的迭代器相同

迭代器iter
*iter 表示iter所指向的位置的元素
iter++ 自增
int main()
{
    vector<int> text;
    int data;
    while(cin>>data)
        text.push_back(data);
    for(vector<int>::iterator iter=text.begin();iter!=text.end();iter++)
        cout<<*iter<<" ";
    cout<<endl;
    return 0;
}
  • 容器的反向遍历:反向迭代器:reverse_iterator
vector<int>::reverse_iterator ri;
rbegin(),rend();
反向遍历使用rbegin(),redn()来定位
反向遍历迭代器的使用和普通迭代器一样,可以使用++来位移迭代器,使用*来取值
rbegin()指向容器里面最后一个元素的位置,rend()指向容器里面第一个元素里面的前一个位置
int main()
{
    vector<int> text;
    int data;
    while(cin>>data)
        text.push_back(data);
    for(vector<int>::reverse_iterator riter=text.rbegin();riter!=text.rend();riter++)
        cout<<*riter<<" ";
    cout<<endl;
    return 0;
}

元素的插入

vec.push_back(elem);将elem插入到vec的末尾
vec.insert(position,elem);将elem插入到指定的position位置
vec.insert(position,n,elem);将elem的n个拷贝插入到指定的position位置
vec.insert(position,beg,end);将迭代器beg至end-1之间的元素插入到vec指定的position位置

元素的删除

vec.clear();清空容器中的所有元素
vec.erase(position);删除position指定位置元素
vec.erase(beg,end);删除beg至end-1之间元素
vec.pop_back();删除做后一个元素

vector<int> num;
vector<int>::iterator iter=num.begin();
num.pop_back();
num.erase(iter);
num.erase(iter,iter+2);删除前两个
其他常用函数
vec.empty();如果vec不含任何函数,返回真。
vec.size();返回vec中的元素的个数
vec[n];返回vec中第n+1位置上元素的引用

顺序容器之一list(链表)

初始化

初始化list容器的方法
list<T> L; 构造了空容器L
list<T> L(n,elem); 构造了一个大小为n的对象L,并使用elem元素进行初始化
list<T> L(list1); 构造了容器L,并使用已创建容器list1进行初始化
list<T> L(beg,end); 构造了容器L,并使用[beg,end)之间的元素进行初始化

元素的插入

L.push_back(elem);尾插
L.push_front(elem);头插
L.insert(position,elem);向容器的position位置插入元素elem
L.insert(position,n,elem);向容器的position位置插入n个元素elem
L.insert(position,beg,end);将迭代器beg至end-1之间的内容到position位置插入
list1.insert(list1.begin(),list2.begin(),list2.end());将list2元素插到list
1的头部
L.splice(position,list);将链表容器list中的元素插入到position位置,并清空list
L.splice(position,list,pos);将链表容器list中pos位置上的元素插入到position位置,并清空pos位置上的元素从list中移除
L.splice(position,list,beg,end);将链表容器list中的beg至end-1之间的元素插入到position位置,并清空beg至end-1的元素

元素的删除

L.pop_back();尾删
L.pop_front();头删
L.clear();清空容器
L.erase(position);删除position位置元素
L.erase(beg,end);删除beg至end-1之间的元素
L.remove(elem);移除与元素elem相等的元素

新建一个链表容器,并插入一个值,输出整条链表,删除某个值并输出整条链表

#include <iostream>
#include <stdlib.h>
#include <list>
using namespace std;

int main()
{
    list<int> L;
    for(int i=0;i<10;i++)
        L.push_front(i);
    L.insert(L.end(),10);
    list<int>::iterator p=L.begin();
    while(p!=L.end())
    {
        cout<<*p<<" ";
        p++;
    }
    cout<<endl;
    L.remove(9);
    p=L.begin();
    while(p!=L.end())
    {
        cout<<*p<<" ";
        p++;
    }
    cout<<endl;

    return 0;
}

建立两条链表分别输出他们的值,并把其中一条链表的值清空插入到另一条链表的头部,并输出两条链表

int main()
{
    list<int> L;
    for(int i=0;i<10;i++)
        L.push_front(i);
    list<int>::iterator p=L.begin();
    while(p!=L.end())
    {
        cout<<*p<<" ";
        p++;
    }
    cout<<endl;
    list<int> N;
    for(int i=0;i<10;i++)
        N.push_front(i);
    list<int>::iterator q=N.begin();
    while(q!=N.end())
    {
        cout<<*q<<" ";
        q++;
    }
    cout<<endl;

    L.splice(L.begin(),N);
    p=L.begin();
    while(p!=L.end())
    {
        cout<<*p<<" ";
        p++;
    }
    cout<<endl;

    q=N.begin();
    while(q!=N.end())
    {
        cout<<*q<<" ";
        q++;
    }
    cout<<endl;
    return 0;
}

vector和list的区别

  • vector可以通过下标快速访问任何一个元素,但是删除除了首元素和尾元素之外的元素效率很低。
  • list可以快速高效的删除任何一个元素,但是访问元素效率低,只能通过迭代器遍历来访问。

简单的标准库类型— pair类型

pair是一种模版类型。定义在头文件utility中,没给pair可以存储2个值。这2种值没有限制。first,second分别表示第一第二

  • 如何生成一个pair类型的对象
pair<int ,int> p(2,3);
pair<int ,int> p1=make_pair(3,4); make_pair()函数,不需要写出型别,就可以生成一个pair对象
pair<string,double> p("xx",5.4);
pair<string,double> p1=make_pair("xx",5.4);
pair<string,double>("xx",5.4);无名对象
  • 获取pair的值
#include <iostream>
#include <stdlib.h>
#include <utility>
using namespace std;
int main()
{
    pair<string,double> p("xx",5.4);
    cout<<p.first<<" "<<p.second<<endl;
    return 0;
}

关联容器之一map(映射)

关联容器与顺序容器的本质差别在于:

关联容器通过键(key)存储和读取元素,而顺序容器是通过元素在容器中的位置顺序存储和访问元素

ST提供了4个关联容器:map,set,multimap,multiset

map是STL的一个关联容器,它提供一对一(第一个称为关键字,每个关键字只能在map中出现一次,第二个称为该关键字对应的值)的数据处理能力
两个基本的关联容器的类型:mapsetmap容器中的元素以"键(key)-值(value)"对的形式组织,"键"用作对map容器中的元素的索引,而值则表示所存储或读取的数据。

set仅仅包含一个键,有效的支持关于某个关键字是否存在这种查询

setmap类型的对象所包含的元素都具有不同的键,即关键字都互不相同,即不能允许为同一个键添加第二个值
map1-a
2-b
3-c
4-d
map:左边的为关键字,右边的为关键字对应的值,关键字互不相同
set:
1
2
3
4
set:只有关键字,关键字互不相同

如果一个键必须对应多个值
mulitmap:  一对多
1-a
2-b
1-d
1-c
mulitset: 允许多个键相同

map的构造函数

map<k,v> m;创建一个名为m的空对象,其键和值的类型分别为k,v
map<k,v> m(m2);创建一个m2的副本m,其键和值的类型相同
map<k,v> m(b,e);创建map类型m对象,存储迭代器b,e之间的所有元素。元素的类型必须为pair<k,v>或者能够转换成pair<k,v>。

map类定义的类型

map<k,v>::key_type  在map容器中,关键字的类型
map<k,v>::mapped_type  在map容器中,键所关联的值的类型
map<k,v>::value_type  在map容器中,元素的类型即一个pair类型,所以说它的first元素具有const map<k,v>::key_type类型,second元素则为map<k,v>::mapped_type类型。

注:

map<k,v>类型容器中存放的元素是pair<k,v>类型的对象,所以说通过map容器中的迭代器进行解引用运算(*iter),得到一个pair<k,v>类型的对象
利用pair<k,v>插入数据
用insert函数插入value_type数据

用数组的形式插入数据

#include <iostream>
#include <map>
#include <utility>
using namespace std;

int main()
{
    map<int,string> mapStu;
    mapStu.insert(pair<int,string>(1,"stu_one"));
    mapStu.insert(pair<int,string>(map<int,string>::value_type(2,"stu_two")));
    mapStu[3]="stu_three";
    mapStu[3]="stu_threessss";
    map<int,string>::iterator iter;
    for(iter=mapStu.begin();iter!=mapStu.end();iter++)
        cout<<iter->first<<" - "<<iter->second<<endl;
    return 0;
}

注:

以上三种方法,虽然都可以实现数据的插入,但是他们是有区别的,第一种,第二种效果一样,但是再插入数据时涉及到关键字的唯一性,如果有相同关键字则插不进出。第三种,可以插进但是会覆盖之前相同关键字所对应的值。
#include <iostream>
#include <map>
#include <utility>
using namespace std;
int main()
{
    map<int,string> m1,m2;
    map<int,string>::iterator m1_iter;
    m1.insert(pair<int,int>(1,10));
    m1.insert(pair<int,int>(2,20));
    m1.insert(pair<int,int>(3,30));
    m2.insert(pair<int,int>(10,100));
    m2.insert(pair<int,int>(20,200));
    m2.insert(pair<int,int>(30,300));
    cout<<"m1:"<<endl;
    for(m1_iter=m1.begin();m1_iter!=m1.end();m1_iter++)
        cout<<m1_iter->first<<" - "<<m1_iter->second<<endl;
    m1.swap(m2);
    cout<<"after swap m1,m2"<<endl;
    cout<<"m1:"<<endl;
    for(m1_iter=m1.begin();m1_iter!=m1.end();m1_iter++)
        cout<<m1_iter->first<<" - "<<m1_iter->second<<endl;
    cout<<"m2:"<<endl;
    for(m1_iter=m2.begin();m1_iter!=m2.end();m1_iter++)
        cout<<m1_iter->first<<" - "<<m1_iter->second<<endl;
    return 0;
}
#include <iostream>
#include <map>
#include <utility>
using namespace std;
int main()
{
    map<int,int> m1;
    map<int,int>::iterator m1_iter;
    m1.insert(pair<int,int>(1,10));
    m1.insert(pair<int,int>(4,40));
    m1.insert(pair<int,int>(3,30));
    m1.insert(pair<int,int>(6,60));
    m1.insert(pair<int,int>(7,70));
    m1.insert(pair<int,int>(2,20));
    for(m1_iter=m1.begin();m1_iter!=m1.end();m1_iter++)
        cout<<m1_iter->first<<" - "<<m1_iter->second<<endl;
    return 0;
}
map的大小
int n=mapStu.size();
数据的遍历
前向迭代器
    map<int,string>::iterator iter;
    for(iter=mapStu.begin();iter!=mapStu.end();iter++)
        cout<<iter->first<<" - "<<iter->second<<endl;
反向迭代器   
    map<int,string>::reverse_iterator riter;
    for(riter=mapStu.rbegin();riter!=mapStu.rend();riter++)
        cout<<riter->first<<" - "<<riter->second<<endl;
用数组的写法遍历
int n=mapStu.size();
for(int i=1;i<n;i++)
    cout<<mapStu[i]<<endl;

数据的查找

 判断这个关键字是否在map中出现
  • 第一种:用count函数来关键字是否出现,无法知道数据位置,出现返回1,否则返回0;
    if(1==mapStu.count(1))
    cout<<”find”;
  • 第二种用find函数来定位数据出现的位置,它返回一个迭代器,当数据出现时,返回数据所在位置的迭代器,如果map中没有要查找的数据,它的返回迭代器等于end()函数返回的迭代器。
map<int,string>::iterator iter;
iter=mapStu.find(1);
if(iter!=mapStu.end())
cout<<"find";
数据的清空和判断是否为空清空map数据用clear()。判断是否为空用empty(),真空返回ture,假的返回false
mapStu.clear();
if(mapStu.empty())
cout<<"is empty"<<endl;

数据的删除

  • 如果要删除1,用迭代器删
map<int,string>::iterator iter;
iter=mapStu.find(1);
mapStu.erase(iter);
  • 直接根据关键字删
mapStu.erase();//删了返回1,否则返回0
  • 用迭代器,成片的删
mapStu.erase(mapStu.begin(),mapStu.end());
map中swap的用法:交换两个容器的元素
map的排序问题
map中的元素是自动按关键字key升序排列,所以不用对map排序

string类

string类:可以把string看作一个特殊的容器,里面装的都是字符

定义和构造初始化

string str1="hello hahaha";
string str2("world hahaha");
string str3(str1,6);
string str4(str1,6,3);
char ch[]={"roly-poly"};
string str5=ch;
string str6(ch);
string str7(ch,4);
string str8(10,'i');
string str9(ch+5,ch+9);
hello hahaha
world hahaha
hahaha
hah
roly-poly
roly-poly
roly
iiiiiiiiii
poly

赋值拼接字符串

string重载了= + +=等多重运算符,让字符串能够进行简单的算术运算
拼接
追加字符
重新赋值
指定位置插入
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
int main()
{
    string str1="hello hahaha";
    string str2("world hahaha");
    string str3(str1+str2);
    cout<<str3<<endl;
    str1.push_back('!');
    cout<<str1<<endl;
    str1.append("-cpp");
    cout<<str1<<endl;
    str1.assign("hello world");
    cout<<str1<<endl;
    str1.insert(6," hahaha");
    cout<<str1<<endl;
    cout<<str1[0]<<endl;
    cout<<str1.at(0)<<endl;
    string stuff;
    getline(cin,stuff);
    cout<<"stuff="<<stuff<<endl;
    getline(cin,stuff,'!');
    cout<<"stuff="<<stuff<<endl;
    char buff[20];
    cin.getline(buff,20);
    cout<<"buff="<<buff<<endl;
    cin.getline(buff,20,'!');
    cout<<"buff="<<buff<<endl;
    return 0;
}

访问字符操作

string可以按数组方式,以下标来访问。还可以用at()函数访问指定的字符
从键盘获取一行字符,赋值给stuff
从键盘获取一行字符,赋值给stuff,以!结束
从标准输入获取至多20个字符,最后一个空格结束

可以使用STL接口,把string理解为一个容器
排序,升序

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
    string str;
    str.push_back('q');
    str.push_back('w');
    str.push_back('e');
    cout<<str<<endl;
    string::iterator itstr=str.begin();
    for(;itstr!=str.end();itstr++)
        cout<<*itstr<<" ";
    cout<<endl;
    sort(str.begin(),str.end());
    for(itstr=str.begin();itstr!=str.end();itstr++)
        cout<<*itstr<<" ";
    return 0;
}

string中的字符替换删除操作

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string str="standard lover";
    string str2="gates of down";
    str.swap(str2);
    cout<<str<<endl;
    string strtemp="0f";
    str.replace(6,strtemp.length(),strtemp);
    cout<<str<<endl;
    str.erase(2,4);//删除下标号为2及后4个
    cout<<str<<endl;
    str.erase();//删除整个字符串
    cout<<str<<endl;
    str2.clear();//清空
    cout<<str2<<endl;
    return 0;
}

实现一个string类(构造,拷贝,析构,赋值,输出,比较,字符串加,长度,子串)

#include <string.h>
#include <iostream>
using namespace std;

class String
{
    private:
        char *saddr;
    public:
        String(const char * str=NULL)
        {
            if(str==NULL)
            {
                saddr=new char[1];//存放\0
                saddr[0]='\0';
                return ;
            }

            saddr = new char[strlen(str)+1];
            strcpy(saddr,str);

        }
        String(const String &str)
        {
            saddr = new char[strlen(str.saddr)+1];
            //开辟s2的空间
            strcpy(saddr,str.saddr);//装s2到s3
        }
        ~String()
        {
            delete [] saddr;
        }
        String operator+(const String &str)//一个操作数
        {
            String temp;
            delete[] temp.saddr;//释放
temp.saddr=new char[strlen(saddr)+strlen(str.saddr)+1];
            strcpy(temp.saddr,saddr);
            strcpy(temp.saddr+strlen(saddr),str.saddr);
            //将s3拷贝到s2后面
            return temp;
        }
        String operator=(const String &str)
        {
            if(saddr==str.saddr)//自己拷贝
            {
                return *this;
            }
            delete [] saddr;//先释放再重新开辟
            saddr = new char [strlen(str.saddr)+1];
            strcpy(saddr,str.saddr);
            return *this;
        }
    friend  ostream &operator<<(ostream &o,const String &str);
    bool operator==(const String &str)
    {
        return strcmp(saddr,str.saddr)==0;
    }
    int length()
    {
        return strlen(saddr);
    }
};
ostream &operator<<(ostream &o,const String &str)
{
            o<<str.saddr;
            return o;
}
int main()
{
    String s1;
    String s2("hello");
    String s3("world");
    String s4(s2);
    s1=s2+s3;
    cout<<s1<<" "<<s2<<" "<<s3<<endl;
    if(s2==s3)
    {
        cout<<"s2==s3"<<endl;
    }
    else
    {
        cout<<"s2!=s3"<<endl;
    }
    cout<<"s2的长度"<<s2.length()<<endl;

    return 0;
}

文件操作

打卡文件

打开文件: 要通过一个流对象打开一个文件,我们使用他的成员函数open()

void open(const char *filename,openmode);
这里的filename是一个字符串,代表要打开的文件名,mode 是下列标识符的组合:

ios:in 为输入(读)方式打开
ios:out 为输出(写)方式打开
ios:app所有的输出附加在文件末尾
ios:trunc如果文件存在则先删除文件
ios:binary 二进制方式打开

这里写标识符可以组合使用,中间用(|)操作符间隔,例如:
我们要想以二进制方式打开文件1.txt来写入数据,我们可以
通过一下方式调用成员函数open()来实现:

ofstream file
file.open("1.txt",ios::out|ios::app|ios::binary);

ofstream,ifstream和fstream所有这些类的成员函数open都包含一个默
认打开文件方式,这三个类的默认方式各不相同:

  • ofstream 默认打开方式:ios::out|ios::trunc
  • ifstream 默认打开方式:ios::in
  • fstream 默认打开方式 ios::in|ios::out

只有当函数被调用没有声明方式参数的情况下,默认值才会被调
用,如果函数被调用是声明了任何参数,默认值就会被改写

文件的输出操作:想要程序中的数据输出到文件中,一共需要5个步骤
  • 1.包含fstream头文件 #include
  • 2.建立ofstream对象 ofstream ofcout;
  • 3.将对象与文件关联 ofcout.open(“test.txt”);
  • 4.使用该对象将数据输出到文件test中 ofcout<<”hello world”
  • 5.关闭与文件的连接 ofcout.close();
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
//  ofstream ofcout;
//  ofcout.open("test.txt");
    ofstream ofcout("test.txt");//调用ofstream类中的构造函数来创建这个文本文件
    ofout<<"hello world";
    ofout.close();//完成这个操作后要用close关闭,否则什么都不会被保存下来。
    return 0;
}

读取文件的数据

打开文件读取数据的方法和输出数据到文件的方法基本一致5步骤
  • 1.包含fstream头文件 #include
  • 2.建立ifstream对象 ifstream ifcin;
  • 3.将对象与文件关联 ifcin.open(“test.txt”);
  • 4.使用该对象读取文件test中的数据到数组temp中 ifcin>>temp
  • 5.关闭与文件的连接 ifcin.close();
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    ifstream ifcin;
    ifcin.open("test.txt");
    char temp[100];
    ifcin>>temp;//从文件中读取数据时遇到空格结束读取
    cout<<temp<<endl;
    return 0;
}

如何读取空格和空格后面的字符?

#include<iostream>
#include <fstream>
using namespace std;
int main()
{
    ifstream ifcin;
    ifcin.open("test.txt");
    char temp[100];
    ifcin.getline(temp,100);
    cout<<temp<<endl;
    return 0;
}

关闭文件


c++的基本内容已经全在这了,总体分为上下部分,希望对也有帮助

发布了30 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/fzl_blog/article/details/68958387
今日推荐