STL和C++11

1 补充知识点

c与c++区别
c语言和C++不能说一个面向过程,一个面向对象

  • 面向过程和面向对象只是一种编程思想,不是针对某一种语言的。Linux内核中的C语言就是运用面向对象的思想编写的。
  • 只是说,在实现面向对象编程时,C++提供的语法实现起来比C语言更方便;仅此而已。

g++编码选项

  • -std=c++11 表示使用c++11的标准来编译。因为C++11基本上是行业标准
  • -ansi 关闭gnu的特性,使代码全面符合ansi要求,从而具有高移植性。避免包含一些只有gnu独有的语法

c/c++ extern关键字

声明函数或变量。函数可以省略,但变量省略就成定义了,而不是声明。声明不会分配内存,而定义会占用内存空间

c++ const关键字与c语言差别:

  • C++ 对 const 的特性做了调整,C++ 规定,全局 const 变量的作用域仍然是当前文件,在其他文件中是不可见的,这和添加了static关键字的效果类似
  • 使用的是 GCC,那么可以通过添加 extern 关键字来增大 C++ 全局 const 变量的可见范围,其他文件使用 extern 声明后就可以使用了。

c++ new 和 delete

C++ 中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:

  • 用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++中我们非常鼓励使用 new 和 delete。

对指针的理解

  • 基本类型变量仅仅是地址的注记符而已,这个变量就是数据的内存首地址,假如数据类型为int,则从首地址开始的连续四个内存地址,都用来存储这个int类型的数据。
  • 编译时,会将变量名替换为首地址
  • 而指针变量,它注记指代的内存块,存储的不是数据,而是另一片内存地址,而这另一片内存地址才是真实的数据。
  • 所以:使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高
    在这里插入图片描述

2 STL(standard Template Libray)

模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程
STL是基于泛型编程思想,利用模板技术。包含了计算机科学领域常用的基本数据结构和基本算法

2.1 顺序容器

顺序容器(也称"序列式容器")将一组具有相同类型的元素****以严格的线性形式组织起来。主要分为三类: vector、deque、list

vector:

  • 内部实现实际上是动态数组,存取任何元素都能在常数时间内完成,在尾端增删元素具有较佳的性能;头部插入要整体移动所有数据,效率低
  • vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的,在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,在将原先空间中的元素复制到新的空间中,性能消耗较大。
  • 随机访问效率高

deque:

  • 类似vector的结构,随机访问效率高。
  • 不同之处:vector还维护首地址,双端插入的效率都很高;vector按块存储,在扩展空间的时候新增一个块,不用复制,效率较高

list

  • 双向链表结构
  • 随机访问效率低,都要遍历
  • 高效的随机插入/删除操作;尤其在首尾 插入,效率很高,只需要改变元素的指针。

2.2 关联容器之set、multiset、map、multimap

关联容器内的元素是排序的,插入任何元素,都能按照相应的排序准则来确定位置,特点是在查找时具有非常好的性能,通常以平衡二叉树方式实现,插入和检索的时间都是O(logn)

set和multiset

  • set是一种关联性容器,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高
  • set中的元素都是唯一的,而且默认情况下会对元素进行升序排列
  • set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同

map和multimap:

  • map容器提供一个键值对(key/value)容器,map与multimap差别仅仅在于multiple允许一个键对应多个值
  • map内部自建一棵红黑树(一种自平衡二叉树),这棵树具有数据自动排序的功能,所以在map内部所有的数据都是有序的,以二叉树的形式进行组织

2.3 容器适配器adapter

适配器(Adaptors)是标准库中的一个通用概念,容器、迭代器和函数****都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器(Container adaptors)在一种已有的容器类型上面进行包装,使其行为看起来像一种不同的类型

标准库定义了三个序列容器适配器

  • stack: 栈,先进后出;底层使用deque
  • queue:队列,先进先出;底层使用deque
  • priority_queue: 优先级队列:优先级大的先出队,底层数据结构默认是大根堆。优先级队列底层默认把数据组成一个大根堆结构,而大根堆的构建就需要在一个内存连续的数组上(堆中结点和它左右孩子的关系是通过下标计算的),vector动态数组底层是绝对连续的,而deque是分段连续的,所以用vector

迭代器

迭代器用于遍历对象集合的元素,从实现角度来看,**迭代器是一种将***operator、operator->、operator++、operator–**等指针操作予以重载的class template

2.4 函数对象

如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象。函数对象是一个对象,但是使用的形式看起来像函数调用,实际上也执行了函数调用,因而得名。


3 C++11

C++11标准,就是新增的特性

1 auto关键字

auto关键字和static关键字区别:
1、static变量存放在静态存储区,在程序整个运行期间都不释放;而auto变量存放在动态存储区,随着生命周期的结束而立即释放。
2、static变量只赋值一次,以后就不用赋值;而auto变量在函数每调用一次都要赋初值。
3、如果用户不对static变量赋初值,则默认为0或’\0’;而auto变量为不确定值。

c++默认就是auto,所以auto关键字写和不写没区别。

C++11给auto关键字赋予了新的含义:

  • 使用它来做自动类型推导。也就是说,使用了 auto 关键字以后,编译器会在编译期间自动推导出变量的类型,这样我们就不用手动指明变量的数据类型了

nullptr关键字

用nullptr表示空指针

更优雅的初始化方法

C++11之前,只有数组能使用初始化列表:int arr[3] = {1, 2, 3}
c++11开始,各种数据类型都可以用初始化列表来初始化:map(int, string){{1, "hello"}, {2, "hh"}};

基于范围的for循环

void foo() {
    vector<int> v{1, 2, 3};
    for (auto x:v) {
        cout << x << endl;
    }
}

编译断言:static_assert

编译期间断言:

// c++ 11 compile-time assert
static_assert ( bool_constexpr , string ) 

// bool_constexpr: 常量表达式(不能包含变量)string: 
// 如果bool_constexpr表达式为false, 这个string就是编译时候报的错误

// run-time assert
assert(ptr != NULL)

lambda表达式

格式

[capture list] (params list)mutable exception-> return type {  
    function body
}

除了捕获列表[capture list],其他项参数列表、mutable、异常、返回类型都可以省略

捕获列表的捕获方式和捕获范围
在这里插入图片描述

function对象包装器

可以降不同的可调用对象,包装成一个类模板,从一个入口统一调用:functon<return type<typename, ...>>

bind

类似python的偏函数,将可调用对象的部分函数固定起来。

auto x = [](int a, int b) {
    return a+b;
}

auto y = bind(x, 1, 2);
cout << y() << endl;

智能指针

智能指针在栈区创建,指向堆区的对象。

函数结束的时候,会调用智能指针的析构函数,析构函数在调用的时候,会释放堆区管理的对象。

  • auto_ptr:c++11废弃:因为auto_ptr基于所有权转移的语义,即将一个旧的auto_ptr赋值给另外一个新的auto_ptr时,旧的那一个就不再拥有该指针的控制权,内部指针被赋值为null。
  • unique_ptr:推荐的智能指针。遵循独占语义的智能指针,在任何时间点,资源只能唯一地被一个unique_ptr所占有,当其离开作用域时自动析构。资源所有权的转移只能通过std::move()而不能通过赋值
  • shared_ptr:**多个智能指针可以指向相同对象,**该对象和其相关资源会在“最后一个引用被销毁”时候释放。
    • shared_ptr智能指针实现的时候,它内部使用了引用计数技术。
    • 构造函数将引用计数初始化为1;
    • 拷贝构造函数、赋值运算符重载函数将对象的引用计数加1;
    • 析构函数调用的是release函数,release将引用计数减1,当减到0时,释放。
  • weak_ptr:配合shared_ptr,解决了shared_ptr互相引用,导致的内存泄漏问题
#include <iostream>
#include <memory>
#include <string>

/*实现智能指针
shared_ptr:内部维护了一个引用计数。当引用计数为0的时候,释放堆区资源

拷贝构造函数:增加引用计数
析构函数:调用release函数
构造函数:引用计数+1
运算符重载=:引用计数+1

*/
using namespace std;



template<typename T>
class MySharedPtr {
public:
    // 构造函数
    explicit MySharedPtr(T *ptr=nullptr): m_ptr(ptr), m_counter(new long(1)) {
        cout << "con init" << endl;
    }

    // 拷贝构造函数
    MySharedPtr(const MySharedPtr<T> &other) {
        this->m_ptr = other.m_ptr;
        this->m_counter = other.m_counter;
        (*(this->m_counter))++;
        cout << "copy init" << endl;
    }

    // 赋值运算符重载;返回不是引用的话,会发现return语句又调用了一次构造函数:其实return了一个局部对象,这个局部对象赋值给外层变量,相当于一次拷贝构造
    MySharedPtr<T> & operator=(MySharedPtr<T> &other) {
        if (this != &other) {
            this->release();
            
            this->m_ptr = other.m_ptr;
            this->m_counter = other.m_counter;
            (*(this->m_counter))++;
        }
        cout << "= init" << endl;
        return *this;
    }

    T & operator*(void) {
        return *(this->m_ptr);
    }

    T *operator->(void) {
        return m_ptr;
    }

    int get_counter(void) {
        return *m_counter;
    }

    // 析构函数
    ~MySharedPtr(){
        release();
        cout << "m_counter: " << *m_counter << endl;
    }

    void show() {
        cout << "m_counter: " << *m_counter << endl;
    }

private:
    T *m_ptr;
    long *m_counter;

protected:
    void release() {
        (*(m_counter))--;
        if ((*m_counter) == 0) {
            if (m_ptr != nullptr) {
                delete m_ptr;
            }
            delete m_counter;
        }
    }

};

int main(int argc, char **argv)
{
    const string *ptr = new string {"hello 222"};

    MySharedPtr<const string> p1(ptr);
    p1.show();

    MySharedPtr<const string> p2(p1);
    p2.show();

    cout << "---------" << endl;
    MySharedPtr<const string> p3;
    p3 = p1;
    p3.show();
    cout << "------+++---" << endl;

    MySharedPtr<const string> &p4 = p3; // 引用必须在定义的同时初始化,引用不会创建新的对象,再次体现引用的本质是指针,指针仅仅是一个指针变量,并不会创建对象
    p4.show();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个两个四个三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值