C++项目代码学习笔记

1. BGL: Boost Graph Library增强图

(1条消息) Boost Graph Library 快速入门_seamanj的博客-CSDN博客_boost graph

Boost Graph Library-BGL学习笔记1_熊猫鹏-梓潼的博客-CSDN博客

//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// [<http://www.boost.org/LICENSE_1_0.txt>](<http://www.boost.org/LICENSE_1_0.txt>))
//=======================================================================
#include <[boost/config.hpp](<https://www.boost.org/doc/libs/1_78_0/boost/config.hpp>)>
#include <iostream> // for std::cout
#include <utility> // for std::pair
#include <algorithm> // for std::for_each
#include <[boost/utility.hpp](<https://www.boost.org/doc/libs/1_78_0/boost/utility.hpp>)> // for boost::tie
#include <[boost/graph/adjacency_list.hpp](<https://www.boost.org/doc/libs/1_78_0/boost/graph/adjacency_list.hpp>)>
#include <[boost/graph/graphviz.hpp](<https://www.boost.org/doc/libs/1_78_0/boost/graph/graphviz.hpp>)>
using namespace boost;
template < class Graph > struct exercise_vertex
{
    exercise_vertex(Graph& g_, const char name_[]) : g(g_), name(name_) {}
    typedef typename graph_traits< Graph >::vertex_descriptor Vertex;
    void operator()(const Vertex& v) const
    {
        using namespace boost;
        typename property_map< Graph, vertex_index_t >::type vertex_id
            = get(vertex_index, g);
        std::cout << "vertex: " << name[get(vertex_id, v)] << std::endl;
        // Write out the outgoing edges
        std::cout << "\\tout-edges: ";
        typename graph_traits< Graph >::out_edge_iterator out_i, out_end;
        typename graph_traits< Graph >::edge_descriptor e;
        for (boost::tie(out_i, out_end) = out_edges(v, g); out_i != out_end;
             ++out_i)
        {
            e = *out_i;
            Vertex src = source(e, g), targ = target(e, g);
            std::cout << "(" << name[get(vertex_id, src)] << ","
                      << name[get(vertex_id, targ)] << ") ";
        }
        std::cout << std::endl;
        // Write out the incoming edges
        std::cout << "\\tin-edges: ";
        typename graph_traits< Graph >::in_edge_iterator in_i, in_end;
        for (boost::tie(in_i, in_end) = in_edges(v, g); in_i != in_end; ++in_i)
        {
            e = *in_i;
            Vertex src = source(e, g), targ = target(e, g);
            std::cout << "(" << name[get(vertex_id, src)] << ","
                      << name[get(vertex_id, targ)] << ") ";
        }
        std::cout << std::endl;
        // Write out all adjacent vertices
        std::cout << "\\tadjacent vertices: ";
        typename graph_traits< Graph >::adjacency_iterator ai, ai_end;
        for (boost::tie(ai, ai_end) = adjacent_vertices(v, g); ai != ai_end;
             ++ai)
            std::cout << name[get(vertex_id, *ai)] << " ";
        std::cout << std::endl;
    }
    Graph& g;
    const char* name;
};
int main(int, char*[])
{
    // create a typedef for the Graph type
    typedef adjacency_list< vecS, vecS, bidirectionalS, no_property,
        property< edge_weight_t, float > >
        Graph;
    // Make convenient labels for the vertices
    enum
    {
        A,
        B,
        C,
        D,
        E,
        N
    };
    const int num_vertices = N;
    const char name[] = "ABCDE";
    // writing out the edges in the graph
    typedef std::pair< int, int > Edge;
    Edge edge_array[] = {
        Edge(A, B),
        Edge(A, D),
        Edge(C, A),
        Edge(D, C),
        Edge(C, E),
        Edge(B, D),
        Edge(D, E),
    };
    const int num_edges = sizeof(edge_array) / sizeof(edge_array[0]);
    // average transmission delay (in milliseconds) for each connection
    float transmission_delay[] = { 1.2, 4.5, 2.6, 0.4, 5.2, 1.8, 3.3, 9.1 };
    // declare a graph object, adding the edges and edge properties
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
    // VC++ can't handle the iterator constructor
    Graph g(num_vertices);
    property_map< Graph, edge_weight_t >::type weightmap = get(edge_weight, g);
    for (std::size_t j = 0; j < num_edges; ++j)
    {
        graph_traits< Graph >::edge_descriptor e;
        bool inserted;
        boost::tie(e, inserted)
            = add_edge(edge_array[j].first, edge_array[j].second, g);
        weightmap[e] = transmission_delay[j];
    }
#else
    Graph g(
        edge_array, edge_array + num_edges, transmission_delay, num_vertices);
#endif
    boost::property_map< Graph, vertex_index_t >::type vertex_id
        = get(vertex_index, g);
    boost::property_map< Graph, edge_weight_t >::type trans_delay
        = get(edge_weight, g);
    std::cout << "vertices(g) = ";
    typedef graph_traits< Graph >::vertex_iterator vertex_iter;
    std::pair< vertex_iter, vertex_iter > vp;
    for (vp = vertices(g); vp.first != vp.second; ++vp.first)
        std::cout << name[get(vertex_id, *vp.first)] << " ";
    std::cout << std::endl;
    std::cout << "edges(g) = ";
    graph_traits< Graph >::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
        std::cout << "(" << name[get(vertex_id, source(*ei, g))] << ","
                  << name[get(vertex_id, target(*ei, g))] << ") ";
    std::cout << std::endl;
    std::for_each(vertices(g).first, vertices(g).second,
        exercise_vertex< Graph >(g, name));
    std::map< std::string, std::string > graph_attr, vertex_attr, edge_attr;
    graph_attr["size"] = "3,3";
    graph_attr["rankdir"] = "LR";
    graph_attr["ratio"] = "fill";
    vertex_attr["shape"] = "circle";
    boost::write_graphviz(std::cout, g, make_label_writer(name),
        make_label_writer(trans_delay),
        make_graph_attributes_writer(graph_attr, vertex_attr, edge_attr));
    return 0;
}

2. Namespace path_planning

所谓分离式实现就是指“声明”和“定义”分别保存在不同的文件中,一般讲声明保存在.h文件、定义保存在.cpp文件中。如此放置便可达到分离式方式。

那么将声明和定义分离有什么意义吗?首先从非分离式(声明的同时给出定义)看,其内容一般保存在.h文件中,以供多个源文件引用。但是将定义放在头文件,那么当多个源文件使用#include命令包含此类的头文件便会在链接阶段出现“multiple definition”链接错误!那么想让多个文件使用此头文件,又不引发链接的“multiple definition”错误该怎么办呢?

分离式的实现便可以解决这个问题。因为.h文件中只包含声明,即使被多个源文件引用也不会导致“multiple definition”链接错误。所以分离式实现增强了命名空间的实用性。以下举例说明。(2015-04-11 更新)

//Two.h 定义
#ifndef TWO_H
#define TWO_H
#include <string>
using namespace std;
namespace MyNameSpace
{
   void Say();
}
namespace MyPrintSpace
{
   void Say();
}
#endif
//Two.cpp 实现
#include <iostream>
#include <string>
#include "Two.h"
using namespace std;
void MyNameSpace::Say()
{
   cout << "MyNameSpace::Say()" << endl;
}
void MyPrintSpace::Say()
{
  cout << "MyPrintSpace::Say()" << endl;
}

#include "Two.h"预编译指令只引入了namespace的定义到当前文件中,此时还必须使用‘namespace_name::Identifier’的方式访问名称。

  1. 若想直接使用某个标识符,应该使用如下方法引入标识符:

using 命名空间标识符::标识符;

  1. 将namespace下面的所有标识符引入,可使用:

using namespace 命名空间标识符;

将自定义的namespace的实现和定义分别放在.cpp和.h文件中,达到分离式实现。Two.h文件用于命名空间的声明,Two.cpp文件用于命名空间中函数、类等的实现。

I. 自定义namespace的使用

// One.cpp 测试文件
#include <iostream>
#include <string>
#include "Two.h"
using namespace std;
using namespace MyNameSpace;
using namespace MyPrintSpace;
void Say()
{
   cout << "Galobel::NameSpace" << endl;
}
int main()
{
   ::Say(); //全局命名空间::
   MyNameSpace::Say();
   MyPrintSpace::Say();
   return 0;
}

#编写Makefile文件 CC=g++ OBJS=One.o Two.o NameSpace : ${OBJS} ${CC} -o NameSpace ${OBJS} One.o : One.cpp Two.h ${CC} -c -o One.o One.cpp Two.o : Two.cpp Two.h ${CC} -c -o Two.o Two.cpp clean: rm NameSpace One.o Two.o #Terminal中编译 $ make g++ -c -o One.o One.cpp g++ -c -o Two.o Two.cpp g++ -o NameSpace One.o Two.o

运行结果:

$ ./NameSpace Galobel::NameSpace MyNameSpace::Say() MyPrintSpace::Say()

II. namespace浅析

A. using声明/using编译指令

using std::cin; // using声明使特定的标识符可用

using namespace std; // using编译指令使整个名称空间可用

B. using编译指令和using声明的比较

使用using编译指令导入一个名称空间中所有的名称与使用多个using声明是不一样的,而更像是大量使用作用域解析运算符。使用using声明时,就好像声明了相应的名称一样。

如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身小声明区域中声明了名称一样。

如果使用using执行导入一个已经声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。

不过仍然可以像下面的示例那样使用作用域解析运算符:

namespace Jill
{
    double buket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch; // gloabl namespace,全局空间
int main( )
{
   using namespace Jill; // 导入命名空间所有名称
   Hill Thrill;
   double water = bucktet(2); // use Jill:bucket( )
   double fetch;  // 隐藏Jill::fetch
   cin >> fetch;  // local fetch
   cin >> ::fetch; // 全局fetch
   cin >> Jill::fetch; // Jill::fetch
}
int foom( )
{
   Hill top;  //错误
   Jill::Hill crest; //valid
}
//假设名称空间和声明区域定义了相同的名称。如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。如果是会用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
//
//注:以上内容摘录自《C++ Primer Plus(第6版)中文版》第9章 内存模型和名称空间 第328页

一般说来,使用using声明比使用using编译指令更安全,这是由于它(using declaration)只导入指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using编译指令导入所有名称,包括可能不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

// in file One.h
namespace **seek**{
    void Func();
}
 end One.h /
// in file Two.h
namespace **seek**{
    void Add(double a, double b);
}
 end Two.h /
//在文件One.h和Two.h中都有命名空间seek的声明,这种写法是正确的。

3. explicit keyword

explicit关键字 C++ explicit 关键字 - 知乎 (zhihu.com)

C++ 参考手册如下解释

  1. 指定构造函数或转换函数 (C++11起)为显式, 即它不能用于隐式转换复制初始化.
  2. explicit 指定符可以与常量表达式一同使用. 函数若且唯若该常量表达式求值为 true 才为显式. (C++20起)

这篇文章我们关注的就是第一点. 构造函数被explicit修饰后, 就不能再被隐式调用了. 也就是说, 之前的代码, 在Point(int x = 0, int y = 0)前加了explicit修饰, 就无法通过编译了.

4. 结构体struct

C++ 结构体(struct)最全详解 - 简书 (jianshu.com)

I. 三种结构体初始化方法:

  • 1.利用结构体自带的默认构造函数
  • 2.利用带参数的构造函数
  • 3.利用默认无参的构造函数

**要点:**什么都不写就是使用的结构体自带的默认构造函数,如果自己重写了带参数的构造函数,初始化结构体时如果不传入参数会出现错误。在建立结构体数组时,如果只写了带参数的构造函数将会出现数组无法初始化的错误!!!下面是一个比较安全的带构造的结构体示例

struct node{
    int data;
    string str;
    char x;
    //注意构造函数最后这里没有分号哦!
  node() :x(), str(), data(){} //无参数的构造函数数组初始化时调用
  node(int a, string b, char c) :data(a), str(b), x(c){}//有参构造
};
//结构体数组声明和定义
struct node{
    int data;
    string str;
    char x;
    //注意构造函数最后这里没有分号哦!
  node() :x(), str(), data(){} //无参数的构造函数数组初始化时调用
  node(int a, string b, char c) :data(a), str(b), x(c){}//初始化列表进行有参构造
}N[10];

II. 结构体嵌套

正如一个类的对象可以嵌套在另一个类中一样,一个结构体的实例也可以嵌套在另一个结构体中。例如,来看以下声明:

struct Costs
{
    double wholesale;
    double retail;
};
struct Item
{
    string partNum;
    string description;
    Costs pricing;
}widget;

Costs 结构体有两个 double 类型成员,wholesale 和 retail。Item 结构体有 3 个成员,前 2 个是 partNum 和 description,它们都是 string 对象。第 3 个是 pricing,它是一个嵌套的 Costs 结构体。如果定义了一个名为 widget的 Item 结构体,则图 3 说明了其成员。


 

嵌套结构体访问的方式:

widget.partnum = "123A";
widget.description = "iron widget";
widget.pricing.wholesale = 100.0;
widget.pricing.retail = 150.0;

还有一点很重要,不能在结构体声明中初始化结构体成员,因为结构体声明只是创建一个新的数据类型,还不存在这种类型的变量。例如,以下声明是非法的:

  //非法结构体声明
struct Date
{
    int day = 23,
    month = 8,
    year = 1983;
};

因为结构体声明只声明一个结构体“看起来是什么样子的”,所以不会在内存中创建成员变量。只有通过定义该结构体类型的变量来实例化结构体,才有地方存储初始值。

  • 访问

定义结构体:

struct MyTree{
    MyTree*left;
    MyTree*right;
    int val;
    MyTree(){}
    MyTree(int val):left(NULL),right(NULL),val(val){}
};

一般结构体变量的访问方式:

int main(){ MyTree t; t.val = 1; cout<<t.val; return 0;}

可见,结构体中的变量,可以直接通过"."操作符来访问。

而对于结构体指针而言:必须通过"->"符号来访问指针所指结构体的变量。

int main(){
    MyTree *t1 = new MyTree(1);
    MyTree *t2 ;
    t2->val = 2;
    cout<<t1->val<<" "<<t2->val;  //输出:1 2
    t2.val = 3;  //error: request for member 'val' in 't2', whitch is of pointer type 'MyTree*' (maybe you meant to use '->' ?)
    cout<<t2.val;  //error: request for member 'val' in 't2', which is of pointer type 'MyTree*' (maybe you mean to use '->' ?
    return 0;
}

5. std::size_t

C/C++中size_t 的用法_chaoruizhe123的博客-CSDN博客_c++ size_t

size_t在C语言中就有了。它是一种“整型”类型,里面保存的是一个整数,就像int, long那样。这种整数用来记录一个大小(size)。size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”。

通常我们用sizeof(XXX)操作,这个操作所得到的结果就是size_t类型。因为size_t类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为int并赋值给int类型的变量。

类似的还有wchar_t, ptrdiff_t。

wchar_t就是wide char type,“一种用来记录一个宽字符的数据类型”。

ptrdiff_t就是pointer difference type,“一种用来记录两个指针之间的距离的数据类型”。

//示例代码:
int i;                   // 定义一个int类型的变量i
size_t size = sizeof(i); // 用sizeof操作得到变量i的大小,这是一个size_t类型的值
                         // 可以用来对一个size_t类型的变量做初始化
i = (int)size;           // size_t类型的值可以转化为int类型的值
char c = 'a';       // c保存了字符a,占一个字节
wchar_t wc = L'a';  // wc保存了宽字符a,占两个字节。注意'a'表示字符a,L'a'表示宽字符a
int arr[] = {1, 2, 3, 4, 5}; // 定义一个数组
int* p1 = &arr[0];           // 取得数组中元素的地址,赋值给指针
int* p2 = &arr[3];
ptrdiff_t diff = p2 - p1;    // 指针的减法可以计算两个指针之间相隔的元素个数
                             // 所得结果是一个ptrdiff_t类型
i = (int)diff;               // ptrdiff_t类型的值可以转化为int类型的值

In Action:

#include<iostream>
int main()
{
        std::cout << "Test Start:" << std::endl;
        long int i;
        long int i_after;
        size_t size = sizeof(i);
        std::cout << "size_t i's sizeof value is:" << size << std::endl;
        i_after = (int)size;
        std::cout << "i_after is:" << i_after << std::endl;
        char c = 'a';
        char temp;
        wchar_t wc = L'a';
        temp = wc;
        i_after = (int)wc;
        std::cout << "i_after for wchar_t is:" << i_after << std::endl;
        std::cout << "char for wchar_t is:" << temp << std::endl;
        return 0;
}


 

6. Const

C++ const 关键字小结 | 菜鸟教程 (runoob.com)

const 是 constant 的缩写,本意是不变的,不易改变的意思。在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数。

C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。

I. const修饰普通类型的变量:不能改变

对于 const 变量 a,我们取变量的地址并转换赋值给 指向 int 的指针,然后利用 *p = 8; 重新对变量 a 地址内的值赋值,然后输出查看 a 的值。

从下面的调试窗口看到 a 的值被改变为 8,但是输出的结果仍然是 7。

 

从结果中我们可以看到,编译器然后认为 a 的值为一开始定义的 7,所以对 const a 的操作就会产生上面的情况。所以千万不要轻易对 const 变量设法赋值,这会产生意想不到的行为。

如果不想让编译器察觉到上面到对 const 的操作,我们可以在 const 前面加上 volatile 关键字。

Volatile 关键字跟 const 对应相反,是易变的,容易改变的意思。所以不会被编译器优化,编译器也就不会改变对 a 变量的操作。

volatile const int a = 1;
a = 2;

#include<iostream>
 
using namespace std;
 
int main(void)
{
    volatile const int  a = 7;
    int  *p = (int*)&a;
    *p = 8;
    cout<<a;
    system("pause");
    return 0;
}

 输出结果如我们期望的是 8。

II. const 修饰指针变量

const 修饰指针变量有以下三种情况。

  • A: const 修饰指针指向的内容,则内容为不可变量。
  • B: const 修饰指针,则指针为不可变量。
  • C: const 修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。

对于 A:

const int *p = 8;

则指针指向的内容 8 不可改变。简称左定值,因为 const 位于 * 号的左边。

对于 B:

int a = 8;
int* const p = &a;
*p = 9; // 正确
int  b = 7;
p = &b; // 错误

对于 const 指针 p 其指向的内存地址不能够被改变,但其内容可以改变。简称,右定向。因为 const 位于 * 号的右边。

对于 C: 则是 A 和 B的合并

int a = 8;
const int * const  p = &a;

这时,const p 的指向的内容和指向的内存地址都已固定,不可改变。

对于 A,B,C 三种情况,根据 const 位于 * 号的位置不同,我总结三句话便于记忆的话:"左定值,右定向,const修饰不变量"

III. const参数传递和函数返回值。

对于 const 修饰函数参数可以分为三种情况。

A:值传递的 const 修饰传递,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。

#include<iostream>
 
using namespace std;
 
void Cpf(const int a)
{
    cout<<a;
    // ++a;  是错误的,a 不能被改变
}
 
int main(void)
 
{
    Cpf(8);
    system("pause");
    return 0;
}

B:当 const 参数为指针时,可以防止指针被意外篡改。

#include<iostream>
 
using namespace std;
 
void Cpf(int *const a)
{
    cout<<*a<<" ";
    *a = 9;
}
 
int main(void)
{
    int a = 8;
    Cpf(&a);
    cout<<a; // a 为 9
    system("pause");
    return 0;
}

C:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。并且对于一般的 int、double 等内置类型,我们不采用引用的传递方式。

#include<iostream>
 
using namespace std;
 
class Test
{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm()const
    {
       return _cm;
    }
 
private:
    int _cm;
};
 
 
 
void Cmf(const Test& _tt)
{
    cout<<_tt.get_cm();
}
 
int main(void)
{
    Test t(8);
    Cmf(t);
    system("pause");
    return 0;
}

结果输出 8

**对于 const 修饰函数的返回值。**Const 修饰返回值分三种情况。

A:const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。

#include<iostream>
 
using namespace std;
 
const int Cmf()
{
    return 1;
}
 
int Cpf()
{
    return 0;
}
 
int main(void)
{
    int _m = Cmf();
    int _n = Cpf();
 
    cout<<_m<<" "<<_n;
    system("pause");
    return 0;
}

B: const 修饰自定义类型作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。

C: const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。

IV. const修饰类成员函数

const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。

**注意:**const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this 指针,即不能实例化,const 成员函数必须具体到某一实例。

下面的 get_cm()const; 函数用到了 const 成员函数:

#include<iostream>
 
using namespace std;
 
class Test
{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm()const
    {
       return _cm;
    }
 
private:
    int _cm;
};
 
 
 
void Cmf(const Test& _tt)
{
    cout<<_tt.get_cm();
}
 
int main(void)
{
    Test t(8);
    Cmf(t);
    system("pause");
    return 0;
}

如果 get_cm() 去掉 const 修饰,则 Cmf 传递的 const _tt 即使没有改变对象的值,编译器也认为函数会改变对象的值,所以我们尽量按照要求将所有的不需要改变对象内容的函数都作为 const 成员函数。

如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用 mutable 关键字修饰这个成员,mutable 的意思也是易变的,容易改变的意思,被 mutable 关键字修饰的成员可以处于不断变化中,如下面的例子。

#include<iostream>
using namespace std;
class Test
{
public:
    Test(int _m,int _t):_cm(_m),_ct(_t){}
    void Kf()const
    {
        ++_cm; // 错误
        ++_ct; // 正确
    }
private:
    int _cm;
    mutable int _ct;
};
 
int main(void)
{
    Test t(8,7);
    return 0;
}

这里我们在 Kf()const 中通过 ++_ct; 修改 _ct 的值,但是通过 ++_cm 修改 _cm 则会报错。因为 ++_cm 没有用 mutable 修饰。

7. std::map<BlockPaitKey, BlockPairValue>

map<BlockPairKey, BlockPairValue> block_pairs_logger, temp_block_pairs_logger;

定义于头文件


template< class Key, class T, class Compare = <Key>, class Allocator = <<const Key, T> >> class map;


namespace pmr {  template <class Key, class T, class Compare = <Key>>  using map = std::map<Key, T, Compare, <<const Key,T>>>}

std::map 是有序键值对容器,它的元素的键是唯一的。用比较函数 Compare 排序键。搜索、移除和插入操作拥有对数复杂度。 map 通常实现为红黑树

在每个标准库使用*比较* (Compare) 概念的位置,以等价关系检验唯一性。不精确而言,若二个对象 ab 互相比较不小于对方 : !comp(a, b) && !comp(b, a) ,则认为它们等价(非唯一)。

std::map 满足*容器* (Container)知分配器容器* (AllocatorAwareContainer)关联容器* (AssociativeContainer) 和*可逆容器* (ReversibleContainer) 的要求。

8. auto

c++ auto基本用法_lwgkzl的博客-CSDN博客_auto c++

总述:

auto的原理就是根据后面的值,来自己推测前面的类型是什么。

auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

注意点:

1.用auto声明的变量必须初始化(auto是根据后面的值来推测这个变量的类型,如果后面没有值,自然会报错)

2.函数和模板参数不能被声明为auto(原因同上)

3.因为auto是一个占位符,并不是一个他自己的类型,因此不能用于类型转换或其他一些操作,如sizeof和typeid

4.定义在一个auto序列的变量必须始终推导成同一类型

auto x1 = 5, x2 = 5.0, x3='r'; *// This is too much....we cannot combine like this*

示例:

std::vector<std::string> ve;
std::vector<std::string>::iterator it = ve.begin();
auto it = ve.begin();//我们可以用atuo来代替那个初始化类型:
//此外,如果是可用迭代器的对象的话,还可以这样操作:
int main(){
    vector<int>v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    for(auto i : v){
        cout<<i<<" ";
    }
    cout<<endl;
    return 0;
}
// auto在这里就是简单的替换了int类型。

9. Override

C++ override使用详解_Geek.Fan的博客-CSDN博客_c++ override

在派生类中,重写 (override) 继承自基类成员函数的实现 (implementation) 时,要满足如下条件:

一虚:基类中,成员函数声明为虚拟的 (virtual)

二容:基类和派生类中,成员函数的返回类型和异常规格 (exception specification) 必须兼容

四同:基类和派生类中,成员函数名、形参类型、常量属性 (constness) 和 引用限定符 (reference qualifier) 必须完全相同

如此多的限制条件,导致了虚函数重写如上述代码,极容易因为一个不小心而出错

C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。

  1. 公有继承

纯虚函数 => 继承的是:接口 (interface)

普通虚函数 => 继承的是:接口 + 缺省实现 (default implementation)

非虚成员函数 =>继承的是:接口 + 强制实现 (mandatory implementation)

  1. 不要重新定义一个继承自基类的非虚函数 (never redefine an inherited non-virtual function

  2. 在声明需要重写的函数后,加关键字 override

这样,即使不小心漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误。

在使用中需要注意以下几点:

(1).覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;

(2).覆盖的方法的返回值必须和被覆盖的方法的返回一致;

(3).覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

(4).被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

10. using

using RobotType = std::string;

11. C++17 std::variant

一种变体类

12. emplace_back()

C++11 新增加特性,效率优于puch_back(), 不需要复制拷贝,但是注意版本适配问题。

C++ STL vector添加元素(push_back()和emplace_back())详解 (biancheng.net)

13. shared_ptr

(1条消息) C++智能指针:shared_ptr用法详解_Tonson_的博客-CSDN博客_shared_ptr

shared_ptr里的reset()函数用于返回该对象的引用计数

14. assert()

C/C++ assert()函数用法总结 - 白菜菜白 - 博客园 (cnblogs.com)

assert();判断条件为false时,会直接调用abort()终止程序,慎用!

已放弃使用assert()的原因是,频繁的调用会极大的影响程序的性能,增加额外的开销。在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:

#include <stdio.h>
#define NDEBUG
#include <assert.h>

1)在函数开始处检验传入参数的合法性如:

int resetBufferSize(int nNewSize)
{
  //功能:改变缓冲区大小,
  //参数:nNewSize 缓冲区新长度
  //返回值:缓冲区当前长度 
  //说明:保持原信息内容不变     nNewSize<=0表示清除缓冲区
  assert(nNewSize >= 0);
  assert(nNewSize <= MAX_BUFFER_SIZE);
  ...
}

2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败,如:

3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题

4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感。

5)有的地方,assert不能代替条件过滤。

assert是用来避免显而易见的错误的,而不是处理异常的。错误和异常是不一样的,错误是不应该出现的,异常是不可避免的。c语言异常可以通过条件判断来处理,其它语言有各自的异常处理机制。

一个非常简单的使用assert的规律就是,在方法或者函数的最开始使用,如果在方法的中间使用则需要慎重考虑是否是应该的。方法的最开始还没开始一个功能过程,在一个功能过程执行中出现的问题几乎都是异常。

15. [[nodiscard]]

nodiscard是c++17引入的一种标记符,其语法一般为[[nodiscard]]或[nodiscard("string")],含义可以理解为“不应舍弃”。nodiscard一般用于标记函数的返回值或者某个类,当使用某个弃值表达式而不是cast to void 来调用相关函数时,编译器会发出相关warning。 nodiscard介绍 C++_qq_38617319的博客-CSDN博客_放弃具有nodiscard属性的函数的返回值

16. static_cast

在学习关键字explicit的时候,注意到另外一个关键字也不太熟悉,就是static_cast

struct A
{
    A(int) { }      // 转换构造函数
    A(int, int) { } // 转换构造函数(C++11)
    operator bool() const { return true; }
};
 
struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};
 
int main()
{
    A a1 = 1;      // OK:复制初始化选择 A::A(int)
    A a2(2);       // OK:直接初始化选择 A::A(int)
    A a3 {4, 5};   // OK:直接列表初始化选择 A::A(int, int)
    A a4 = {4, 5}; // OK:复制列表初始化选择 A::A(int, int)
    A a5 = (A)1;   // OK:显式转型进行 static_cast
    if (a1) ;      // OK:A::operator bool()
    bool na1 = a1; // OK:复制初始化选择 A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化==================================
 
//  B b1 = 1;      // 错误:复制初始化不考虑 B::B(int)
    B b2(2);       // OK:直接初始化选择 B::B(int)
    B b3 {4, 5};   // OK:直接列表初始化选择 B::B(int, int)
//  B b4 = {4, 5}; // 错误:复制列表初始化不考虑 B::B(int,int)
    B b5 = (B)1;   // OK:显式转型进行 static_cast
    if (b2) ;      // OK:B::operator bool()
//  bool nb1 = b2; // 错误:复制初始化不考虑 B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK:static_cast 进行直接初始化
}

C++static_cast用法_风拔萝卜的博客-CSDN博客_c++ static_cast

在C++中强制类型转换存在四种方式,分别是static_cast、const_cast、rinterpret_cast和dynamic_cast。前三种对应这在c语言中旧式的强制类型转换,这篇文章讲解一下static_cast static_cast关键字主要用于以下几种情况: 1)基本类型之间的转换,但不能用于基本类型指针之间的类型转换(void指针和基本类型指针之间可以)。例如:

double d=0;
int i=static_cast<int>(d);

2)用于有继承关系的子类与父类之间的指针或引用的转换

3)空类型指针转换为任意基本类型的指针

第三条是这里面很容易出错,因为有可能出现未知的转换结果,要保证转换的正确性就必须保证转换后所得的类型就是指针原先的类型。

先看一个转换正确的例子

代码如下:

int i=0;
void *vp=&i;
int *p=static_cast<int*>(vp);
*p=3;
cout<<i<<endl;

输出结果是 3

我们分析一下: i的初始类型为int 然后我们将i的地址转换为void*,然后void指针vp转换为int指针,此时int型指针 p和vp指向的i的原始类型int一致,所以转换结果正确。

接下来再看几个错误的例子

例1:

代码如下:

int  i=3;
void *vp=&i;
char *p=static_cast<char*>(vp);
*p=4
cout<<i<<endl;

输出结果是 4

我们分析一下:初看来我们的结果是正确的,但是这里实际是有错误的,或者说是不安全的转换。考虑如下代码:

int  i=256;
void *vp=&i;
char *p=static_cast<char*>(vp);
*p=4;
cout<<i<<endl;

输出结果是 260 这是为什么呢,让我们来分析一下。上面的两个例子都是错误的,原因就在于,最后转换的类型与数据的初始类型是不一样的,但是为什么输出结果一个正确,一个却是错误的呢。原因就在于不同的数据类型占用的字节数是不一样的,在我的机器里char是一个字节也就是8位,int是4字节,也就是32位,在第一个例子里3的二进制是0...0 0000 0011,那么在转换为char指针时实际指向低8位数据空间,也就是00000011所在位置,然后经过p=4以后,变为00000100,因此数据i的二进制就变成了0...0 0000 0100 ,也就是4,虽然结果没错,但是是不安全的,而第二个例子就体现了不安全。 同样对第二个例子进行分析。256的二进制是0000 0000 0000 0000 0000 0001 0000 0000,转换后char指针仍然指向低8位所在的数据空间,也就是0000 0000所在位置,然后经过p=4以后,变为00000100,因此数据i的二进制就变成了0000 0000 0000 0000 0000 0001 0000 0100,转换成十进制就是260。 接下来再看一个例子

int  i=4294967294;
void *vp=&i;
double *p=static_cast<double*>(vp);
*p=4;
cout<<i<<endl;

输出结果是0

这里出现错误同样是和上面的两个例子一样,之所以结果是0,我个人的理解是由于浮点型表示形式不同所导致的。

以上是本人对static_cast用法的一些理解,如有错误,请批评指正,谢谢

17 std::move()

左值 右值概念

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

int main(int argc, char** argv)
{
	int&& i = 123;
	int&& j = std::move(i);
	int&& k = i;//编译不过,这里i是一个左值,右值引用只能引用右值
	int& k = i;//编译通过,这里需要理解int&&的含义

	cout << "i,j and k are:" << i << " " << j << " " << k << " " << endl;

	return 0;
}

 

为什么需要右值引用

C++引入右值引用之后,可以通过右值引用,充分使用临时变量,或者即将不使用的变量即右值的资源,减少不必要的拷贝,提高效率。如下代码,均会产生临时变量:


class RValue {
};

RValue get() {
    return RValue();
}

void put(RValue){}

为了充分利用右值的资源,减少不必要的拷贝,C++11引入了右值引用(&&),移动构造函数,移动复制运算符以及std::move。

右值引用(&&),移动构造函数,移动复制运算符以及std::move

将上面的类定义补充完整:

2、std::move的原理

std::move的定义:

 

这里,T&&是通用引用,需要注意和右值引用(比如int&&)区分。通过move定义可以看出,move并没有”移动“什么内容,只是将传入的值转换为右值 ,此外没有其他动作。std::move+移动构造函数或者移动赋值运算符,才能充分起到减少不必要拷贝的意义。

3、std::move的使用场景

在之前的项目中看到有的同事到处使用std::move,好像觉得使用了std::move就能移动资源,提升性能一样,在我看来,std::move主要使用在以下场景:

  • 使用前提:1 定义的类使用了资源并定义了移动构造函数和移动赋值运算符,2 该变量即将不再使用
  • 使用场景
RValue a, b;

//对a,b坐一系列操作之后,不再使用a,b,但需要保存到智能指针或者容器之中
unique_ptr<RValue> up(new RValue(std::move(a)));
vector<RValue*> vr;
vr.push_back(new RValue(std::move(b)));

//临时容器中保存的大量的元素需要复制到目标容器之中
vector<RValue> vrs_temp;
vrs_temp.push_back(RValue());
vrs_temp.push_back(RValue());
vrs_temp.push_back(RValue());
vector<RValue> vrs(std::move(vrs_temp));

RValue c;
put(std::move(c));

在没有右值引用之前,为了使用临时变量,通常定义const的左值引用,比如const string&,在有了右值引用之后,为了使用右值语义,不要把参数定义为常量左值引用,否则,传递右值时调用的时拷贝构造函数

18. gRPC

​​​​​​​(2条消息) 详解分布式RPC开源框架-gRPC【linux服务器开发】_linux大本营的博客-CSDN博客_linux rpc框架

19. Std::optional<>

C++17 新特性之 std::optional(上) - 知乎 (zhihu.com)

20. map.count()

C++ map count()用法及代码示例 - 纯净天空 (vimsky.com)

21. C++中#ifdef/#ifndef/#else/#endif的用法详解

C++中#ifdef/#ifndef/#else/#endif的用法详解_yp_zang的博客-CSDN博客_#ifdef、#else、#endif和#ifndef

22.std::chrono::high_resolution_clock::now()

C++11 std::chrono库详解 - mjwk - 博客园 (cnblogs.com)

声明:本文所有整理版权都属于原作者,本人只是收集整理,若有冒犯,请联系删除,不当之处,还请见谅~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

熊猫鹏-梓潼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值