C++的基础学习

C++主要学习

 

        C与C++的不同、C++的特性及专业术语、C++程序的编译

一、C到C++的转换,C与C++的区别

C++是C的增强

区别:

C++具有严格的数据类型检查

C++新增了命名空间、异常处理、面向对象编程、变量的权限和引用及函数的重载及运算符的重载等

C++的专业术语

类、对象、继承、多态、封装、组合、静态绑定和动态绑定,成员变量、成员函数等

二、C++的编译过程

与C语言的编译过程一致

预处理、编译、汇编、链接

文件在各阶段的过程 . cpp ---- .i -------> .s  ----> .o ----->  .exe、. out

注:源文件 .cpp

        临时文件 .i

        汇编文件 .s

        二进制文件 .o

        可执行文件 ( .exe 、.out ),其扩展名为 .exe 在 DOS 中为 .out,在 UNIX 操作系统中为 .out。

1、预处理

        主要进行添加源文件,进行宏替换和条件编译(.cpp ---> .i  )

2、编译

        主要进行检查代码是否符合C++的代码规范和将源代码编译为汇编语言( .i  -----> .s)

3、汇编

        将汇编语言翻译为二进制(  .s  ------>  .o )

4、链接

        生成可执行文件( .o  -------- .exe、. out)

三、命名空间的使用

定义:在全局的范围内,小区一小部分空间来进行命名

种类:

有名命名空间和无名命名空间

1、有名命名空间

        有名字的命名空间

//有名命名空间的使用

格式1:
#include 
unsing namespace std;
namespace famous
{
    int a;
}
famous ::a;
命名空间名 :: 需要访问的内容

格式2:
#include 
unsing namespace std;
namespace famous
{
    int a;
}
unsing famous ::a;

unsing  命名空间名 :: 需要访问的内容
格式3:
#include 
unsing namespace std;
namespace famous
{
    int a;
}
unsing namespace  famous ::a;
unsing namespace 命名空间名 :: 需要访问的内容

注:域作用符-------由两个冒号组成   ::

        C++有严格的大小区分,不能使用 大写(小写) 代替 小写 (大写)

        命名空间必须是全局的一部分

 2、无名命名空间

        没有名字的命名空间

格式
#include
unsing namespace std;
namespace 
{
    int a;
}
::a;
::需要访问的内容

注:访问命名空间就是访问命名空间里面的指定内容

        命名空间是可以发生嵌套

        命名空间可以分为有名的命名空间和无名的命名空间(实际开发中有名的命名空间用的比较多)

        命名空间是属于全局的一部分,只不过是取了全局的一块区域命了一个名称而已 (4)访问命名空间的时候需要用到域作用符号

 四、作用域

1、局部作用域

在函数体{},代码块{},for()循环,while()循环里面等等的区域,称为局部作用域

例如:
void fun(int a)//int a就只在fun函数里面才能使用
{
    
}
void fun()//int a就只在fun函数里面才能使用
{
    int a;
}
for(int i=0;i<10;i++0)
{
    //int i只有在for循环里面才能使用
}
while(int i)
{
    //int i只有在while循环里面才能使用
}

 C++的局部作用域里面用的最多的就是类作用域

类作用域:

在类{}里面的区域才能使用的作用域,在类之外的话就不能使用

class Student
{
    int age;
};
//年龄就只能在学生这个类里面使用

2、全局作用域

除局部作用域之外的地方就是全局作用域

例如:
int a=10;
int manin()
{
    
}
//定义了一个全局变量a

命名空间的作用域

        属于全局作用域的一部分,但只要超出命名空间就不能使用,只在命名空间里面可见,超出命名空间就会失效。

namespace Student
{
    int a;
};
//a只在Student中使用,超出这个区域就会失效

五、内存模型

动态内存

C使用 mallloc 和 free 两个函数来申请和释放堆区空间

C++使用 new 和 delete 两个函数来申请和释放内存空间

1、开辟变量地址空间

使用new来开辟空间,delete来释放空间

格式1:只开辟空间,未进行初始化
    数据类型 * 标识符名称 = new 数据类型

注:前后的数据类型必须一致
    在堆区开辟变量地址空间,用标识符来指向堆区空间,堆区空间的大小就是数据类型的大小
    申请空间之后记得释放空间

例如:
申请堆区空间:

    int * p = new int;
或 int * p; p =new int;

释放堆区空间:
    delete p;

格式2:开辟空间并进行初始化
数据类型 * 标识符名称 (初始化内容) = new 数据类型

//申请堆区空间并初始化
int *p = new int (50);
//开辟堆区空间时对该空间进行初始化,初始化为50

//释放堆区空间
delete p;

2、开辟数组地址空间

使用new来申请堆区空间,delete来释放堆区空间

格式1:开辟数组地址空间 ----- 一维数组
数据类型 * 标识符名称 = new 数据类型 [数组大小];

注:
   数据类型只能是基本数据类型和自定义的数据类型

//申请数组的堆区空间
int * p = new int [10];
//在堆区开辟一个具有10个元素的数组空间

//释放数组的堆区空间
delete [] p;

格式2:开辟数组地址空间时同时进行初始化
数据类型 * 标识符名称 = new 数据类型 [数组大小] {数组初始化的内容};

//申请数组的堆区空间
int * p = new int [10] {0,1,2,3,4,5,6,7,8,9};
//p[0] = 0;p[1] = 1; ···  p[9] = 9; 

//释放数组的堆区空间
delete [] p;

注:

        请说明一下malloc/free和new/delete的区别?

        1、malloc/free在C中是一个函数,而new/delete在C++中是一个操作符

        2、malloc/free开辟空间时不能进行初始化,而new/delete开辟空间时可以进行初始化

        3、malloc/free开辟空间时需要手动计算空间的大小,而new/delete开辟空间时会自动计算空间的大小(空间的大小与数据的类型有关)

        4、malloc/free开辟空间时需要强转,而new/delete开辟空间时不需要强转

六、 类和对象

        在C++中,通常使用类来进行事物的描述,类的实例化就称之为对象

1、类---经常在头文件中进行使用

类是用来描述现实世界中的事物,是一个抽象的概念,不一定存在,只是脑海里面的一个概念

格式:
class   类名
//class ---类的关键字
//pbulic: -----类的关键字
{
pbulic:
    //变量-----用于描述类的特征
    //函数-----用于描述类的行为
};

例如:
#include 
unsing namespace std;//标准的IO库,可以省略命名空间std的命名;
class Animal
{
public:
    char name;
    char age;
    void hail(char hail)
    {
        cout<<"毛发的长短:"<

2、对象-类的实例化

对象实例化的格式:

2.1 在栈区的实例化

注:

        在栈区进行实例化时,不需要用户手动的去释放内存,编译器会自动释放内存

格式:
    类名 对象名;

class people//定义一个类
{
public:
    int a;
};
people zhangsan;//类的实例化

2.2 在堆区的实例化

注:在堆区的实例化,需要用户手动的去释放内存,如果不释放内存,很容易造成内存泄漏

        当进行类的实例化时,实例化的对象就具备了类的特征和行为

格式:
    类 * 对象名 = new 类名;

例如:
    people *zhangsan = new people;

3、访问类中成员

成员:成员函数 + 成员变量

由于类的实例化的方式不同,访问的方式也不同

在栈区的实例化
访问方式
格式:
    对象名.成员(函数)

例如:
class People
{
public:
    int age;//成员变量
    void fun();//成员函数
    {
        //函数执行的东西
    }
};

int main()
{
    People people;
    people.fun();
    people.age;
}

在堆区的实例化
访问方式
格式:
    对象名->成员(函数)

例如:
class People
{
public:
    int age;//成员变量
    void fun();//成员函数
    {
        //函数执行的东西
    }
};

int main()
{
    people->fun();
    people->age;
}

 4、类成员的访问修饰符

作用:

        保护类的成员,共三种

public(公共):

        在public下的成员对于整个类都是可见的

        在public下的成员对于类体外也是可见的

        在public下的成员对于友元函数也是可见的

        在public下的成员对于派生类(子类)也是可见的

protected(保护):

        在protected下的成员对于整个类都是可见的

        在protected下的成员对于类的外部是不可见的

        在protected下的成员对于友元函数是可见的

        在protected下的成员对于派生类(子类)是可见的

private(私有):

        在private下的成员对于整个类都是可见的

        在private下的成员对于类的外部是不可见的

        在private下的成员对于友元函数是可见的

        在private下的成员对于派生类(子类)是不可见的

 注:

        如果在类里面没有写任何的类的成员访问修饰符,那么该类下的成员都是归于private属性下

        一般将成员变量放置到private属性下

 5、构造函数        

        用于初始化类的成员变量的函数

格式:
    类名  (参数列表)
    {
        //代码块-----用于初始化成员变量    
    }

例如:

    People (参数列表)
    {
        
    }

注:

        若用户没有写构造函数,那么编译器会自动生成一个默认的构造函数,反之则不会生成构造函数

 5.1 如何初始化类的成员变量

直接初始化类的成员变量

class Text
{
public:
    int a;
    Text()//构造函数
    {
        a = 10;//在构造函数中对a的初始化
    }
}

通过初始化列表进行类的成员函数初始化

格式:
    类名 (参数列表) :成员变量1(初始化值1),成员变量2(初始化值2), ------

例如:
    class Text//类名
    {
    public:
        int age;//年龄
        char sex;//性别
        const char * name;//姓名
        Text():age(20),sex("男"),name("李四")//构造函数
        {
            
        }
    }

注:

        构造函数的命名必须和类的名字一样

        构造函数的功能主要是在类的对象创建时定义初始化的状态

        构造函数没有返回值,不能用 void 来修饰

 5.2 如何调用构造函数

构造函数的显式调用

格式:
    类名 对象名 (构造函数参数列表);
    //这时候就显式的调用类的构造函数

例如:
    People people();

构造函数的隐式调用

格式:
    类名(构造函数参数列表);
//隐式的调用类的构造函数

例如:
    People();

注:

        显式调用时需要调用类的对象,隐式调用不需要调用类的对象

构造函数与普通函数的区别:
        不同点:

                1、普通函数前有返回值类型,而构造函数前没有返回值

                2、普通函数后面没有初始化列表,而构造函数后有初始化列表

                3、普通函数调用的时候以显式调用的方式,而构造函数不仅可以显示的调用还可以隐式的调用

        相同点:

                1、都可以带参数或者是不带参数

                2、都可以发生重载

6、 析构函数

        释放构造函数的空间

格式:
~类名()
{
    //代码块
}

例如:
class Text
{
public:
    Text()//构造函数
    {
        
    }
    ~Text()//析构函数
    {
    
    }
}

注:

        析构函数不能带参数

        类中只能有一个析构函数,就是说析构函数不能重载

        析构函数与构造函数一样,没有返回值

析构函数的调用:

        当类的对象使用完成之后,会调用一次析构函数

        当类的指针对象被销毁的时候,会调用一次析构函数

        当main函数体结束时会调用析构函数

 7、拷贝构造函数

        用户需要用一个对象去初始化另一个对象的时,就可以使用拷贝构造函数

格式:
    类名 (const 类名 & obj)
    {
    //拷贝构造函数的代码
    }

例如:
    class Text
    {
    public:
        double real, imag;
        Text(double r, double i) 
        {
            real= r; imag = i;
        }
    };
    int main()
    {
        Text cl(1, 2);//定义对象1
        Text c2 (cl);//定义对象2
        //用复制构造函数初始化c2
        cout<

7.1 浅拷贝

        浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

        相当于是把一个对象里的成员变量和它的值拷贝了一份儿给另外一个对象,如果类里面有指针对象并且在堆区开辟了空间,而且在析构函数里对指针对象进行了释放,这时候如果使用浅拷贝,造成堆区空间重复释放,而引起程序崩溃

7.2 深拷贝

        深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

        相当于是把一个对象里的成员变量拷贝了一份儿,但是变量的地址又重新开辟了一个空间,解决的问题就是堆区重复释放造成程序崩溃的问题

浅拷贝和深拷贝的区别:
        浅拷贝的拷贝对象与类的对象共用一个内存

        深拷贝的拷贝对象与类的对象未占用一个对象

8、this指针

        this 是 C++中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员

        this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的成员

原型:
    成员函数(类名 * const this)

格式:
    this->成员

例如:
    this->text();

注:

        this指针只能在成员函数中使用,全局函数、静态成员函数都不能使用this指针

        友元函数中也没有this指针

9、static

        static 是 C/C++ 中很常用的修饰符,它被用来控制变量的存储方式和可见性

        用static声明函数或者是变量为静态的,作用与C语言里面的static一致

9.1 静态成员函数

格式:
static 返回值类型  函数名(参数列表)
{
    //函数体
}

例如:
static void funcition()
{
    //函数体
}
//void是万能的,无返回值

静态成员函数的访问格式:

通过类的对象访问类的成员函数
格式1:
对象名.静态成员
对象名->静态成员

例如:

class Text//类
{
public:
    int age;
    char sex;
    void fun()
    {
        
    }
};
int main()
{
    Text text;//类的实例化
    text.age;
    text.sex;
    text.fun();
}

通过类加上域作用符来访问类的静态成员
格式2:
    类名::静态成员;

例如:
class Text//类
{
public:
    int age;
    char sex;
    void fun()
    {
        
    }
};
int main()
{
    
    Text::age;
    Text::sex;
    Text::fun();
}

注:

        静态成员函数中无this指针

        静态成员函数不能访问类的非静态成员函数

        类的非静态成员函数可以访问类的静态成员

9.2 静态成员变量

格式:
static 数据类型 变量名

例如:
static int a;

类外初始化的格式:
数据类型 类名::静态成员函数变量名 = 初始值;

例如:
int Text::a = 70;

注: 

        不能在类里面进行静态成员变量的初始化,在类外才能进行静态成员变量的初始化

 10、const

        const确定某一个值保持不变

10.1 const修饰成员变量

        作用跟C类似,有区别的是在类里声明的const成员变量可以不用声明的时候进行初始化,如果没有进行初始化就必须在构造函数的初始化列表中进行初始化的工作

在C里:
	const int a; //在声明a的同时必须对它进行初始化
在C++里:
	const int a; //可以在声明a的时候不用进行初始化, 那么必须在构造函数初始化列表中进行初始化

10.2 const修饰成员函数

        用const修饰的成员函数中不能去更改类的成员变量的值(即const修饰的成员函数里对类的成员变量是一个只读的行为)

格式:
返回值类型 函数名 (函数列表) const
{
    //函数体
}

例如:
void function () const
{
    //函数体
}

11、友元函数与友元类

目的:给类的外部提供一个访问类的私有成员的接口

关键字:friend,用关键字来声明友元函数和友元类

11.1 友元函数

C++提供了友元函数就可以通过友元函数访问到类的私有成员,C++规定是友元函数声明在类里,就表示该函数是该类的友元,那么就可以通过友元函数访问类的私有成员,友元函数的定义放置在类的外部。

声明格式(在类里声明)

friend 返回值类型 函数名(参数列表);//友元函数的声明

定义(在类外定义): ​ 返回值类型 函数名(参数列表) ​ { ​ //函数体 ​ }

11.2 友元类

​C++提供了友元类就可以通过友元类访问类的私有成员,C++规定是友元类声明在一个其他类里,就表示该友元类是其他类的友元,那么就可以通过友元类访问其他类的私有成员,友元类的定义放置在其他类的外部。

声明格式: ​ friend class 类名;//声明了一个友元类

定义(在其他类外定义): 

class Test 

{ 

        //类体

}

八、标准流

也自动包含了 

 流,数据的流动,数据的流动就像水一样从一个地方流向一个地方,C++形象的称为“流”

 输入流和输出流

(1)输入流 ​ 就是数据从输入设备(键盘、鼠标、磁盘等等)流向内存的过程,称之为“输入流”,为了不与输出流相冲突,编译器在内存开辟了一个缓冲区,该缓冲区用于存放数据

(2)输出流 ​ 数据从内存流向输出设备(终端显示器、控制台等等)的过程,称之为“输出流”,为了不与输入流相冲突,编译器在内存开辟了一个缓冲区,该缓冲区用于存放数据

 1、标准的输入与输出流类:iostream类

iostream类:

i-------input(输入)

 o-------output(输出)

stream-----流

istream---------input相关的内容

ostream---------output相关的内容

1.1 cout:标准的输出

console out :控制台输出 从内存里获取数据输出到控制台,中间是经过输出缓冲区

格式:

        cout(是ostream类的对象,并且是在iostream里进行实例化)<<基本的数据类型(不需要用户提前指定,编译器会自动识别数据的类型);

解释:将基本类型的数据从内存里取出来放置到输出缓冲区里,然后直接从输出缓冲区获取数据输出到控制台

输出多个数据: 

cout<<

数据1<<数据2<<数据3..............................; 

或 

cout <<数据1 

<<数据2 

<<数据3 

<<数据4 

............

符号:<<(输出符号)

        在C语言里“<<”代表移位运算符,并且该运算符只能用于基本类型的数据的移位操作,在C++里对该运算符进行了重载的工作,该运算符就可以用于其他类型的数据(例如自定义类型的数据),在输出这里,C++对“<<”进行了重载,就表示可以用它来输出数据

在内部的重载的格式:

ostream &operator<<(float);
//将float类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(double);
//将double类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(int);
//将int类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char);
//将char类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char *);
//将char *类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(const char*)
//;将const char*类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(string);
//将string类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)
........................

1.2 cerr:标准的错误输出流

从内存里获取错误的数据(程序在运行的时候,产生的错误)输出到控制台

cerr:console error

使用方式:跟cout一样

在内部的重载的格式:

ostream &operator<<(float);
//将float类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(double);
//将double类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(int);
//将int类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char);
//将char类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char *);
//将char *类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(const char*)
//;将const char*类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(string);
//将string类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

........................

1.3 clog:标准的日志的输出

        从内存里获取日志数据(程序文件的日志信息等等)输出到控制台

clog:console log

使用方式:跟cout一样

在内部的重载的格式:

ostream &operator<<(float);
//将float类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(double);
//将double类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(int);
//将int类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char);
//将char类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(char *);
//将char *类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(const char*)
//;将const char*类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

ostream &operator<<(string);
//将string类型的数据送到输出缓冲区里,然后直接输出缓冲区获取数据显示到控制台(终端显示器)

........................

1.4  cin:标准的输入流

(在iostream里实例化istream对象)

        从磁盘或者是终端设备获取数据输入到内存

符号:>>(输入符号)

cin:console input;
cin>>a>>b>>c>>d>>l;//定义了a,b,c,d,l变量;

使用方式:

        cin>>基本类型的变量;//从磁盘或者是终端设备获取数据存入到变量里

        在C语言里“>>”代表移位运算符,并且该运算符只能用于基本类型的数据的移位操作,在C++里对该运算符进行了重载的工作,该运算符就可以用于其他类型的数据(例如自定义类型的数据),在输入这里,C++对“>>”进行了重载,就表示可以用它来输入数据

在内部重载的格式:

istream  & operator>>基本类型的数据;
基本类型的数据:
int、float、double、char、char*、const char *、signed int等等

注:

        cout、cerr、clog、cin、endl、ends、flush等这些不是C++预留的关键字,它们都是流对象(类的实例化),这些对象都是在iostream里实例化(使用的时候需要包含iostream头文件)

 什么时候获取输出缓冲区里的值?

1、当程序执行完成时(main结束),会自动刷新输出缓冲区一次

2、当遇到endl(换行对象):endline,会自动刷新输出缓冲区

3、当输出缓冲区满的时候,会自动刷新输出缓冲区

注:iostream里提供了一个宏:BUFSIZ,查看缓冲区的大小---------512个字节

 1.5 输入/输出流的格式的输出

使用控制符进行输入/输出(包含iomanip)

进制形式输出:

十进制:dec
八进制:oct
十六进制:hex
 	int a,b;
    cin >> a>>b;
    cout << hex;//输出十六进制的结果
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << oct;//输出八进制的结果
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << dec;//输出十进制的结果
    cout <<"a = " << a << endl;
    cout << "b = " << b << endl;

浮点型输出 :

    setprecision(int n);
//告诉编译器后面的程序,数据是n个有效数据进行显示,有四舍五入的功能

    setiosflags(ios::statusflag);
    setiosflags(ios::fixed);
//定点输出,一般结合setprecision一起使用,setprecision(int n)里的n表示小数位数而不是有效的数据位数

//浮点型
    cout << setprecision(5) << setiosflags(ios::fixed) << 3.1415926 << endl;

设置输出域宽:

表示设置输出的长度,长度不够那么就补空格

setW(int n);
//表示输出数据的长度

cout << setw(10)<

设置输出域宽不足时填充的字符:

setfill(char c);
//表示域宽不足时填充的内容

cout << setw(10)<

设置显示的内容对齐方式:

setiosflags(ios::statusflag);
ios::left;//左对齐
ios::right;//右对齐

//设置对齐方式
    cout << setw(20) << setiosflags(ios::right)
/*设置右对齐*/ << "hello world!" << endl;

cout << setiosflags(ios::left) /*设置左对齐*/ << "hello world!" << endl;

使用流成员函数进行输入/输出(包含iomanip):

清除状态标志流成员函数unsetf
一般格式:long ios::unsetf(long flags)
调用格式:流对象.unsetf(ios::状态标志);

设置状态标志流成员函数setf
一般格式:long ios::setf(long flags)
调用格式:流对象.setf(ios::状态标志);

设置域宽流成员函数width
一般格式:int ios::width(int n)
调用格式:流对象.width(n);
注:它只对下一个流输出有效(默认右对齐),输出完成后,恢复默认值0

设置实数的精度流成员函数precision
一般格式:int ios::precision(int n)
调用格式:流对象.precision(n);
注:参数n在十进制小数输出时表示有效数字
标志fixed和scientific表示输出小数位数

填充字符流成员函数fill
一般格式:char ios::fill(char ch)
调用格式:流对象.fill(ch);
注:当输出值达不到域宽时用填充符来填充,默认填充符为空格,与width函数搭配使用

九、运算符重载

C++的基础学习_第1张图片

1、重载

 一般是发生在函数中,C++也支持运算符重载

解释: ​ C++里的函数名相同、函数的参数个数、类型、顺序不同,可以发生重载(函数重载)

例如:
void function()
{
}
void function(int)
{

}
void function(int , char)
{

}
void function(char , int)
{

}

2、 重写

        就是将原来函数重新复制一份(函数的返回值、函数名、参数个数、类型、顺序都相同),继承里面用的最多

例如:
void funiton()
{

}
void funiton()
{

}

3、隐藏(覆盖)

        就是将原来函数重新复制一份,就将原来的函数覆盖(隐藏),隐藏一般用的最多的是具有继承关系的两个类中

4、运算符重载

        C++里对基本的运算符重新赋予新的运算,例如,以前的加法(+)只能用于两个基本类型的数据进行相加,但是对于非基本类型的数据就不适用。C++对基本的运算符进行重载,重载之后就可以用于非基本类型的数据的运算。

在类中运算符重载实际上是给类定义运算(类的对象能进行基本运算符的运算操作),强制修饰为函数的调用
算术运算符:+、-、*、/、%、++、--

位操作运算符:&、|、~、^(位异或)、>(右移)

逻辑运算符:!、&&、||

比较运算符:<、>=、>等等

赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、>=

其他运算符:[]、()、->、,、new、delete、new[]、delete[]、*指针运算符

/*注意事项:
A、除
成员访问运算符“.”、
成员指针运算符“.*”和“->*”、
作用域运算符“::”、
sizeof运算符和三目运算符“?:”、
预处理符号“#”
以外,C++中的所有运算符都可以重载(其中“=”和“&”不必用户重载)
.  .*  ->*  ::  sizeof  ?:  #  (不能被重载)

B、重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符

C、运算符重载的实质是函数重载,遵循函数重载的选择原则

D、重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构

E、运算符重载不能改变该运算符用于内部类型对象的含义

F、运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符

G、重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数

H、重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则

I、运算符重载可以通过成员函数的形式,也可是通过友元函数,还可以是非成员函数的形式

5、普通成员函数的运算符重载

关键字:operator 


声明格式: 

        返回值类型(可以是基本的数据类型&自定义的数据类型)

        operator 被重载的运算符(参数列表);

定义格式: 

        返回值类型(可以是基本的数据类型&自定义的数据类型) operator 被重载的运算符 (参数列表) { //函数体 } 

例如:
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a - obj.a;
		test.b = this->b - obj.b;
		return test;
	}

主程序的撰写:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a - obj.a;
		test.b = this->b - obj.b;
		return test;
	}
};

int main()
{
	Test test1;
	Test test2;
	Test test3;
	test3 = test1 + test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	return 0;
}	

6、友元函数的运算符重载

关键字:friend、operator

声明格式:
	friend  返回值类型(基本的数据类&自定义的类型) operator   被重载的运算符  (参数列表);

定义:
	 返回值类型(基本的数据类&自定义的类型) operator   被重载的运算符  (参数列表)
	{
		//函数体
	}

例如:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a + obj.a;
		test.b = this->b + obj.b;
		return test;
	}
	friend Test operator -(const Test& obj1, const Test& obj2);
};//友元函数的重载

Test operator -(const Test& obj1,const Test &obj2)
{
	Test test;
	test.a = obj1.a - obj2.a;
	test.b = obj1.b - obj2.b;
	return test;
}

int main()
{
	Test test1;
	Test test2;
	Test test3;
	Test test4;
	test3 = test1 + test2;
	test4 = test1 - test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	cout << "test4.a=" << test4.a << endl;
	cout << "test4.b=" << test4.b << endl;
	return 0;
}

7、一般函数的成员运算符重载

关键字:operator
声明格式:
	函数返回值类型(自定义的类型&基本的数据类型) operator 被重载的运算符 (参数列表)
	{
		//函数体
	}

例如:

class Test
{
public:
	int a = 10;
	int b = 20;
	//实现两个对象中a相加并且b相加
	Test operator +(const Test& obj)
	{
		Test test;
		test.a = this->a + obj.a;
		test.b = this->b + obj.b;
		return test;
	}
	friend Test operator -(const Test& obj1, const Test& obj2);
};

Test operator -(const Test& obj1,const Test &obj2)
{
	Test test;
	test.a = obj1.a - obj2.a;
	test.b = obj1.b - obj2.b;
	return test;
}

Test operator *(const Test& obj1, const Test& obj2)
{
	Test test;
	test.a = obj1.a * obj2.a;
	test.b = obj1.b * obj2.b;
	return test;
}

int main()
{
	Test test1;
	Test test2;
	Test test3;
	Test test4;
	Test test5;
	test3 = test1 + test2;
	test4 = test1 - test2;
	test5 = test1 * test2;
	cout << "test3.a=" << test3.a << endl;
	cout << "test3.b=" << test3.b << endl;
	cout << "test4.a=" << test4.a << endl;
	cout << "test4.b=" << test4.b << endl;
	return 0;
}

十、类的继承与多态

1、继承

        一个类的对象共享了另一个类或其他多个类的成员变量和成员方法(子类还可以自己扩充自己的特征和行为),例如:儿子继承父亲(儿子具备父亲的特征和行为),该类和另一个类或其他类的关系,称之为“继承”,C++继承又分为单继承和多继承。

        单继承:​一个类的对象共享了另一个类的成员变量和成员方法

        多继承: ​一个类的对象共享了其他多个类的成员变量和成员方法

单继承的格式:

class  新的类名(子类)  :  继承的权限   已经存在的类(父类)
	{
		//类体
	};

//新的类我们称之为子类(派生类),已经存在的类我们称之为父类(基类)
//继承的权限:public、private、protected

例如:
class Father
{
public:
	int a = 50;
	void function()
	{
		cout << "这是父类的成员函数" << endl;
	}
};
class  Son : public Father //表示Son类继承自Father
{
public:
	void son_func()
	{
		cout << "a=" << a << endl;
	}
};
int main()
{
	Son zhangsan;
	zhangsan.son_func();
	zhangsan.function();
	return 0;
}

1、父类和子类的构造和析构函数的调用时机

        构造时,先调用父类的构造函数,再调用子类的构造函数,析构时,先调用子类的析构函数,再调用父类的析构函数

2、如果父类和子类有相同的特征或者行为?

        如果父类和子类有相同的变量或函数,那么子类就会屏蔽(隐藏)父类的变量或函数,执行子类的变量或函数,如果父类和子类没有相同的变量或函数,子类就会继承父类的变量或函数

 1.1 继承的权限

c++提供了继承的权限:public(共享)、private(私有)、protected(保护),目的是对继承加以限制

public继承:

(1)父类的public下的成员就全部继承到子类的public属性下

(2)父类的protected下的成员变量是继承到子类的protected属性下,父类的protected下的成员函数没有继承到子类

(3)父类的private下的成员是没有继承到子类的private属性下

private继承:

(1)父类的public下的成员就全部继承到子类的protected属性下

(2)父类的protected属性下的成员全部继承到子类的protected属性下

(3)父类的private下的成员是没有继承到子类的private属性下

protected继承:

(1)父类的public下的成员就全部继承到子类的private属性下

(2)父类的protected属性下的成员全部继承到子类的private属性下

(3)父类的private下的成员是没有继承到子类的private属性下

1.2 多继承 

        一个新的类继承多个已经存在的类,称为多继承,例如:儿子不仅继承父亲的一些特征和行为,并且儿子继承母亲的特征和行为

声明的格式:
	class   新的类(子类或者是派生类):继承的权限1  已经存在的类1(父类1), 继承的权限2  已经存在的类2(父类2),·····
	{
		//类体
	}

例如:

class Father1
{
public:
	int a = 60;
protected:
	int b = 70;
private:
	int c = 80;
};

class Father2
{
public:
	int sum = 60;
};

class Son :public Father1,private Father2 //多继承
{
public:
	void function()
	{
		cout << "b=" <<  b<< endl;
	}
protected:

};

2、多态

        按照字面的意思:

        多种形态(多种方法),C++里一般称为“一种接口,多种方法”,一种接口,指的是父类接口,多种方法表示父类中有方法并且子类中也有相同的方法(方法相同,实现的内容不同)

构成多态性:

(1)两个类具有继承关系

(2)子类中必须重写父类的方法(子类方法中的内容与父类中的方法可以不一样)

(3)父类中的方法必须是虚函数

a、虚函数 ​ 虚函数相当于是一个普通的成员函数,只不过在该函数前加了一个关键字“virtual”,那就表明该函数是一个虚函数

声明格式:

         ​ virtual 返回值类型 函数名 (参数列表); 

定义格式: ​

         virtual 返回值类型 函数名 (参数列表) ​ 

        { ​ 

                //函数体 ​

        } ​ 

例如:
    virtual void function()
	{
		//函数体
	}
    class Son1 :virtual public Father

(1)多态性的调用时机 当父类的指针或者是引用指向子类时 注: 具有继承关系的两个类,是支持父类和子类相关联(父类的指针或者是引用指向子类)

(2)静态绑定 函数的定义和调用在编译阶段就确定了,称之为静态绑定,在C里的函数就是如此,在C++里函数的重载也是如此 在类中: 静态绑定的时候是根据类的指针对象或者是引用的类型来确定函数的调用,而不是通过类的指针或者是引用指向的对象的类型来确定

(3)动态绑定 函数的定义和调用在运行阶段才确定,称之为动态绑定,例子就是:多态中的虚函数

3、抽象类

        java里声明抽象类关键字obstract,现实世界中有些事物是不能实例化对象的,我们将这些事物声明为抽象类

        例如:鬼、外星人,抽象类只是给子类提供一个基类,在基类里只是对事物的一些描述,而没有具体的实现

声明格式:
​	class  类名
​	{
​		virtual  返回值类型  函数名(参数列表)=0;
​	};
例如:
    class Student
	{
		virtual void funciton() = 0 ; 
    //该类只是描述事物具备一些特征和行为,但是没有具体实现行为
	}

纯虚函数

        在虚函数的声明后面加上“=0”,就表示该虚函数为纯虚函数,它是构成抽象类不可缺少的条件 声明格式:

virtual 返回值类型 函数名(参数列表)=0;

例如:

声明格式:
	virtual  返回值类型  函数名(参数列表)=0;

例如:

virtual void funciton() = 0 ;

注:

        1、抽象类只是给子类提供一个基类

        2、抽象类不能实例化为对象

        3、抽象类里只是描述了一些行为,但没有具体实现行为(纯虚函数),需要在子类中去重新实现抽象类的行为

虚析构函数

        虚-----virtual,就是在析构函数前加上一个关键字virtual,那就表明该析构函数为虚析构函数,当基类指针指向子类对象时,如果释放该基类指针,那么就不会释放子类的析构函数,那么这时候需要将基类的析构函数声明为虚析构函数,释放的时候才会调用子类的析构函数

格式:
​	virtual ~函数名()
​	{
​		//函数体
​	}

例如:
    virtual ~Student()
	{
		//函数体
	}

主函数:

//虚析构函数
#include
using namespace std;
class Father
{
    public:
    ~Father()
    {
        cout<<"这是一个父类虚析构函数"<
注:
    1、是将父类的析构函数声明虚析构函数
    2、两个类需要有继承关系
    3、基类指针指向子类的对象

限制构造函数(了解)

        构造函数不称限制构造函数。主要应用于一些特殊的场合。析构函数同理

情况1:私有private ,提供构造对象的友元函数接口,限定对象的生成区域。如 对象只允许生成在堆区。通过友元函数 调用构造器实现对象创建

情况2:保护protected, 限制构造函数或析构函数只能通过继承方式,使用子类的构造或析构函数实现基类构造或析构的调用

注:    

      1、此种方式一般用于抽象类,不允许直接创建对象,只能用于继承

      2、 使用继承时,应注意虚析构函数,基类指针delete的问题,即虚析构函数问题

      3、同时,若析构函数受保护,则不能通过基类指针来调用,只能使用友元函数的方式

 虚继承

        虚继承就是在继承权限前加上关键字virtual,为了解决多重继承(菱形继承)造成的二义性问题

格式:
​	class  类1  :virtual 继承权限  类2
​	{
​		//类体
​	};

例如:
    class Son :virtual public Student
	{
		//类体
	}

十一、异常

异常:是C++中一种容错机制,是一个错误处理系统,可以将问题的发现与问题的处理分离。

1、异常处理

一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道 怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。提供异常的基本目的就是为了处理上面的问题。

基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。

C++提供了三个关键字:throw 、try、catch

throw: 

抛出异常 

格式: 

throw 抛出异常的类型的数据; 

try:

检测代码是否产生了异常 

格式: 

try { //可以产生的异常的代码块 } 

catch: 

捕获异常 

格式: 

catch(异常的类型)

{ //处理产生的异常 }
//异常的使用
#include
#include
using namespace std;

//有一个计算除法的函数,如果用户使用除法的时候,输入了分母为0,就会抛出异常,同时进行处理
int division(int a, int b)//除法,a分子,b分母
{
    if (b == 0 || a == 0)
    {
        throw 15.6;//抛出异常
    }
    else
    {
        cout << "a/b = " << a/b << endl;
        return a / b;
    }
}
int main()
{
    try
    {
        try
        {
            division(20, 0);
        }
        catch (char)
        {

        }
    }//检测代码是否产生异常,可以进行嵌套使用
    catch (char *)//捕获异常
    {
        cout << "用户输入的分子、母不能为0 !" << endl;
    }
    /*cout << "a/b = " << division(12,3) << endl;*/
    catch (...)
    {
        cout << "捕获所有的异常!" << endl;
    }
    return 0;
}

2、异常的注意事项

        1、如果确定异常已经产生,但是用户没有去写捕获该异常的catch,那么程序的外层继续捕获该异常,如果还是没有捕获到,那么最后就调用windows的terminate来终止程序

       2、try模块后面必须紧跟着一个catch块或者是多个catch块

       3、产生的异常首先是匹配try后紧跟着的第一个catch块,如果没有匹配,继续往下一个catch进行匹配,如果还是没有捕获到,那么最后就调用windows的terminate来终止程序

        4、catch(...)它是用于捕获所有异常

        5、try...catch块可以发生嵌套

3、标准异常

std::exception    		//该异常是所有标准 C++ 异常的父类。
std::bad_alloc    		//该异常可以通过 new 抛出。
std::bad_cast     		//该异常可以通过 dynamic_cast 抛出。
std::bad_exception     	 //这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid  		//该异常可以通过 typeid 抛出。
std::logic_error 		//理论上可以通过读取代码来检测到的异常。
std::domain_error      	//当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 	//当使用了无效的参数时,会抛出该异常。
std::length_error      	//当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range      	//参数超出有效范围 
std::runtime_error     	//理论上不可以通过读取代码来检测到的异常。
std::overflow_error  	//当发生数学上溢时,会抛出该异常。
std::range_error     	//当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error  	//当发生数学下溢时,会抛出该异常。

4、自定义异常

        需要继承C++提供的所有异常的基类exception,可以在自定义的异常类重写what()方法,用于查看产生的异常的类型

格式:
    class  自定义的异常 :public  exception
	{
		//自定义异常类体
	}

例如:
#include"iostream"
using namespace std;
//自定义异常类---用于提示字符串的错误
class MyException :public exception
{
public:
	const char* str;
	MyException(const char* obj)
	{
		str = obj;
	}
	const char* what()
	{
		return str;
	}
};

int function(int a, int b)
{
	if (b == 0)
	{
		throw out_of_range("长度不能超出");//标准的异常
        //throw MyException("用户输入的分母不能为0");//自定义的异常
	}
	else
	{
		return a / b;
	}
}

int main()
{
	try
	{
		function(10, 0);
	}
	catch (int)
	{
    }
    catch (out_of_range& e)
    {
	    cout << "产生的异常:" << e.what() << endl;
    }
    catch (MyException& e)
    {
	    cout << "产生的异常:" << e.what() << endl;
    }
    return 0;
}

十二、模板

        模板就是提供一个可参考的对象,例如:字帖(模板),在程序中,例如:

        void funciton(int a , int b);

        //对参数的类型不必写死,泛型编程: 泛型:数据的类型是广泛的----泛型

1、函数模板

声明格式: 

关键字:
template template <模板形参1,模板形参2,.......> 函数的返回值 函数名(函数形参列表);
 
定义格式: 
template <模板形参1,模板形参2,.......> 函数的返回值 函数名(函数形参列表)
{ //函数体 } 

注意: 

(1)模板的形参是用关键字typename/class 
    格式: typename 标识符名称 或 class 标识符名称 

    例如: typename T1; 
    //表示T1是函数模板的模板形参,可以用T1来代替内置的基本的数据类型(int\char\string等等) 

(2)模板的模板形参要与函数形参一一对应 
    注意:如果传递多个函数形参的类型是一样的,那么模板形参可以只写一个 

(3)模板形参称之为非类型形参(不是基本的内置的类型) 

(4)<模板形参1,模板形参2,.......>,模板形参不是函数形参 

(5)模板函数可以发生重载 

(6)模板函数的返回值可以是模板形参类型

2、类模板

声明格式: 

template <模板形参> class 类名

{ 

        //类体

}; 

例如:

 template class Test
 {
 public:
     T1 a;
     Test(T1 b)
     {
         a = b;
         cout << "a=" << a << endl;
     }
 };

模板类的实例化: 

类名<数据类型>对象名(构造函数的参数列表); 

例如: Testtest(10); //表示模板形参的类型为int类型

十三、智能指针

        智能指针就是管理裸指针开辟的堆空间,换句话说就是将裸指针开辟的堆空间的管理权交给智能指针管理。

        智能:自动释放管理的堆空间,智能指针其实质是C++提供的一个类,具备裸指针的特性(->, .,*),C++提供了4种智能指针:

        auto_ptr(自动管理)、shared_ptr(共享指针)、weak_ptr(弱指针)、unique_ptr(唯一指针)

注:

        auto_ptr已经被弃用(C++11)

1、shared_ptr智能指针

        shared_ptr 是C++11提供的一种智能指针类,它足够智能,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。 ​

        它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。

1、每个 shared_ptr 对象在内部指向两个内存位置:

        (1)指向对象的指针。

        (2)用于控制引用计数数据的指针。

2、共享所有权如何在参考计数的帮助下工作:

        (1)当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。

        (2)当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它自动调用delete函数删除该内存。

3、引用计数器 ​

        shared_ptr 使用经典的 “引用计数” 的方法来管理对象资源。引用计数指的是,所有管理同一个裸指针( raw pointer )的 shared_ptr,都共享一个引用计数器,每当一个 shared_ptr 被赋值(或拷贝构造)给其它 shared_ptr 时,这个共享的引用计数器就加1,当一个 shared_ptr 析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个 shared_ptr 了,于是我们释放指针指向的资源,即在引用计数归零时,这个内部类型指针与 shared_ptr 管理的资源一起被释放。

4、shared_ptr成员函数:

(1)use_count() 返回引用计数的个数(即绑定的指针个数)

(2)unique() 返回是否是独占所有权( use_count 为 1)

(3)swap(shared_ptr对象) 交换两个 shared_ptr 对象(即交换所拥有的对象)

(4)reset() 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少 (5)get() 返回内部对象(指针)的地址, 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的

5、问题 ​

共享指针循环引用的时候会造成资源得不到释放

        weak_ptr(弱指针) ​

        解决共享指针循环引用的时候会造成资源得不到释放的问题,协助共享指针工作

弱指针:与共享指针一起使用,作为共享指针的备份,弱指针的存在,不影响共享指针的释放。

弱指针中的方法: ​

         lock(); 可以将弱指针转换为 共享指针使用,若其指向的空间已经释放,将返回NULL指针 ​                 expired(); 检查弱指针指向的空间存在否,若被释放 返回假,存在返回真;

         weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作(例如解决shared_ptr因循环引用不能释放资源的问题)。

        使用weak_ptr 来打破循环引用,它与一个 shared_ptr 绑定,但却不参与引用计数的计算,不论是否有 weak_ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。

        weak_ptr 像是 shared_ptr 的一个助手。同时,在需要时,它还能摇身一变,生成一个与它绑定的 shared_ptr 共享引用计数的新 shared_ptr。

        总而言之,weak_ptr 的作用就是:在需要时变出一个 shared_ptr,在其他时候不干扰 shared_ptr 的引用计数。

        (1)用shared_ptr进行初始化

        (2)成员函数expired()用于判断shared_ptr是否被释放

#include "iostream"
using namespace std;

class Son;
class Father
{
public:
	~Father()
	{
		cout << "Father释放资源" << endl;
	}
	weak_ptrson;//弱指针
};
class Son
{
public:
	~Son()
	{
		cout << "Son释放资源" << endl;
	}
	weak_ptrfather;//弱指针
};
int main()
{
	Father* father2 = new Father();
	Son* son2 = new Son();
	shared_ptrfather1(father2);
	shared_ptrson1(son2);
	father2->son = son1;
	son2->father = father1;
	cout << "与Father绑定的共享指针的个数:" << father1.use_count() << endl;
	cout << "与Son绑定的共享指针的个数:" << son1.use_count() << endl;
	return 0;
}

6、unique_ptr(唯一指针)

        唯一指针:与共享指针类似,是一种受限制的共享指针,绑定一个指针指向对象

        unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享《被管理对象指针》所有权的智能指针

        unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针

         unique_ptr具有->和*运算重载符,因此它可以像普通指针一样使用。

        格式:

                unique_ptr<类型>对象名(指针对象)

十四、C++标准模板库(STL)

        C++标准模板库是一种泛型编程,将算法抽象出来,独立于类型编程。

        C++标准库提供等容器主要有 数组,链表,队列等等, 可以实现 增删改查、排序等操作

        C++标准模板库的六大构成组件

        泛型容器(containers)

                特殊的数据结构,实现了数组、链表、队列、等等,实质是模板类

        迭代器(iterators)

                一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载

        算法(algorithms)

                读写容器对象的逻辑算法:排序、遍历、查找、等等,实质是模板函数

        空间配置器(allocator)

                容器的空间配置管理的模板类

        配接器(adapters)

                用来修饰容器、仿函数、迭代器接口

        仿函数(functors)

                 类似函数,通过重载()运算符来模拟函数行为的类 组件间的关系

         container(容器) 通过 allocator(配置器) 取得数据储存空间

        algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容

        functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化

        adapter(配接器) 可以修饰或套接 functor(仿函数)

         这些组件最终实现一些常见的数据结构的快速使用

1、vector(向量)

vector相当于一个动态数组

数组方式连续存储,可以使用[] 符号进行随机访问

 #include "vector"

(1)初始化vector对象的方式:

vectorv1 ;

//默认的初始化方式,内容为空

vector v2(v1) ;

//v2是v1的一个副本

vectorv3(n ,i) ;

//v3中包含了n个数值为i的元素

vectorv4(n) ;

//v4包含了n个元素,每个元素的值为0

(2)vector常用函数

empty():判断向量是否为空,为空返回真,否则为假

begin():返回向量(数组)的首元素地址

end(): 返回向量(数组)的末元素的下一个元素的地址

clear():清空向量

front():返回得到向量的第一个元素的数据

back():返回得到向量的最后一个元素的数据

size():返回得到向量中元素的个数

push_back(数据):将数据插入到向量的尾部

pop_back():删除向量尾部的数据

2、list(双向链表)

链表相对于vector向量来说的优点在于:

        1、动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,在次分配更大的空间,这样的话效率就比较低了。

         2、支持内部插入、头部插入和尾部插入

缺点:

        不能随机访问,不支持[]方式和vector.at()、占用的内存会多于vector(非有效数据占用的内存空间)

#include "list"

(1)初始化

list对象的方式

list L0; //空链表

list L1(3); //建一个含三个默认值是0的元素的链表

list L2(5,2); //建一个含五个元素的链表,值都是2

list L3(L2); //L3是L2的副本

list L4(L1.begin(),L1.end()); //c5含c1一个区域的元素[begin, end]。

(2)list常用函数

begin():返回list容器的第一个元素的地址

end():返回list容器的最后一个元素之后的地址

rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)

rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)

front():返回链表中第一个数据值

back():返回链表中最后一个数据值

empty():判断链表是否为空

size():返回链表容器的元素个数

clear():清除容器中所有元素

insert(pos,num):将数据num插入到pos位置处(pos是一个地址)

insert(pos,n,num):在pos位置处插入n个元素num

erase(pos):删除pos位置处的元素

push_back(num):在链表尾部插入数据num

pop_back():删除链表尾部的元素

push_front(num):在链表头部插入数据num

pop_front():删除链表头部的元素 sort():将链表排序,默认升序

3、deque(队列)

合并了 vector 和 list的特点,

优点 :

随机访问方便,即支持[]操作符和vector.at(n)

在内部方便的进行插入和删除操作

可在两端进行push、pop

缺点:

占用内存多

使用区别:

如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector

如果你需要大量的插入和删除,而不关心随机存取,则应使用list

如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque

#include "deque"

deque d1; //创建一个空的双端队列d

dequed2(n) ;//创建一个元素个数为n的队列

dequed3(n,num);//创建一个元素个数为n的队列,并且每个元素为num

成员函数:

push_back() //在队尾插入元素

push_front() //在队首插入元素

insert(d.begin()+1,9); //第一个元素之后插入9

size() //双端队列的大小

empty() //判断是否为空

begin() //队首的指针,指向队首元素

end() //队尾元素的下一位作为指针

rbegin() //以最后一个元素作为开始

rend() //以第一个元素的上一位作为指针

erase() //删除某一个元素

clear() //删除所有元素

pop_front() //删除队首元素

pop_back() //删除队尾元素

你可能感兴趣的:(c++)