C/C++ 知识小结


https://en.cppreference.com/w/cpp/algorithm

1.多重定义问题

1.多重问题

例如一个头文件headfile.h这样写

#pragma once
void Func (){}
  • 在这个头文件被多个地方包含的时候就会出问题,链接时报错: (FuncB报重定义)
  • 原因是,在headfile.h中定义了函数及其实现,如果被包含时,则会把函数实现放入包含的位置,被包含多次时,则会被放入多次,从而导致Func重定义。
  • 那怕是在头文件中使用了 #pragma once或是#ifdef __xxx /#define __xxx/#endif也不能解决这种问题。原因是每个.cpp的编译都是多独立的,对于每个cpp来说,都是包含了Func的声明和实现,所以在链接时就不清楚到底是链接哪一个同名函数了。

2.解决方法:

2.1.添加inline标识,

添加完inline标识后,函数的内容代码被直接解释到调用处了,链接时相当于不存在这个函数,也就不存函数重定义的情况。

  • inline void Func (){}

2.2.添加static标识

static void Func (){}

2.3.放入到类中

class ClassA{
public: 
void Func () {}
}

对于静态函数和类,无论有多少文件包含它,也只会维护一份,链接时也就只能找到这一份,所以也是没有问题。


2.内存分配 malloc与calloc,以及relloc

1.简介:

malloc()与calloc() 功能都是用于动态内存的分配,两者略有差别。

malloc()用于一个大小的内存分配,void *malloc( size_t size );
calloc()用于多个对象的内存分配,void *calloc( size_t numElements, size_t sizeOfElement );

2.说明

1.分配内存空间函数malloc

  • 调用形式: (类型说明符*) malloc (size)
  • 功能: 在内存的动态存储区中分配一块长度为"size", 字节的连续区域。函数的返回值为该区域的首地址。
    “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。 “size”是一个无符号数。
  • 例如: pc=(char *) malloc (100);表示分配100个字节的内存空间,并强制转换为字符数组类型, 函数的返回值为指向该字符数组的指针, 把该指针赋予指针变量pc。

2.分配内存空间函数 calloc

  • 调用形式: (类型说明符*)calloc(n,size)
  • 功能: 在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。
    (类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。
  • 例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。

3.区别

  1. malloc申请后空间的值是随机的,并没有进行初始化,而calloc却在申请后,对空间逐一进行初始化,并设置值为0;
  2. calloc函数由于给每一个空间都要初始化值,那必然效率较malloc要低,并且现实世界,很多情况的空间申请是不需要初始值的,这也就是为什么许多初学者更多的接触malloc函数的原因。

3.relloc

realloc函数和上面两个有本质的区别,其原型void realloc(void *ptr, size_t new_Size)

  • **用于对动态内存进行扩容(**及已申请的动态空间不够使用,需要进行空间扩容操作),ptr为指向原来空间基址的指针, new_size为接下来需要扩充容量的大小。
  • 如果size较小,原来申请的动态内存后面还有空余内存,系统将直接在原内存空间后面扩容,并返回原动态空间基地址;如果size较大,原来申请的空间后面没有足够大的空间扩容,系统将重新申请一块(20+size)*sizeof(int)的内存,并把原来空间的内容拷贝过去,原来空间free;如果size非常大,系统内存申请失败,返回NULL,原来的内存不会释放。
  • 注意:如果扩容后的内存空间较原空间小,将会出现数据丢失,如果直接realloc(p, 0);相当于free( p).

3. 案例

1.malloc 与 calloc 对比

int *p = (int *)malloc(20*sizeof(int));
    int *pp = (int *)calloc(20, sizeof(int));
    int i;
    printf("malloc申请的空间值:\n\n");
    for ( i=0 ; i < 20; i++)
        printf("%d ", *p++);
    printf("\n\n");
    printf("calloc申请的空间的值:\n\n");
    for ( i=0 ; i < 20; i++)
        printf("%d ", *pp++);
    printf("\n");

上面值随意输出,下面值 0

2.calloc 使用

  pf_kdtree_node_t **queue, *node;      //queue每个元素都是一个node
  queue = calloc(self->node_count, sizeof(queue[0]));

3.C++中map、hash_map、unordered_map、unordered_set的区别

3.1 hash_map、unordered_map

这两个的内部结构都是采用哈希表来实现,区别在哪里?

  • unordered_map在C++11的时候被引入标准库了,而hash_map没有,所以建议还是使用unordered_map比较好。
  • 哈希表的好处是什么?- 查询平均时间是O(1)。

顾名思义,unordered,就是无序了,数据是按散列函数插入到槽里面去的,数据之间无顺序可言,但是有些时候我只要访问而不需要顺序,因此可以选择哈希表。

3.2 unordered_map与map

虽然都是map,但是内部结构大大的不同

  • map的内部结构是R-B-tree来实现的,所以保证了一个稳定的动态操作时间,查询、插入、删除都是O(logN),最坏和平均都是
  • unordered_map如前所述,是哈希表。顺便提一下,哈希表的查询时间虽然是O(1),但是并不是unordered_map查询时间一定比map短,因为实际情况中还要考虑到数据量,而且unordered_map的hash函数的构造速度也没那么快,所以不能一概而论,应该具体情况具体分析。

3.3 unordered_map与unordered_set

  • unordered_set就是在哈希表插入value,而这个value就是它自己的key,而不是像之前的unordered_map那样有键-值对,这里单纯就是为了方便查询这些值。
  • 举个大家好懂的例子,给你A,B两组数,由整数组成,然后把B中在A中出现的数字取出来,要求用线性时间完成。很明显,这道题应该将A的数放到一个表格,然后线性扫描B,发现存在的就取出。可是A的范围太大,你不可能做一个包含所有整数的表,因为这个域太大了,所以我们就用unordered_set来存放A的数,具体实现库函数已经帮你搞定了。

3.4 简易使用

  template <typename DataType>
  bool AddSensorData(const DataType& sensor_data){
    const std::string & frame_id = sensor_data.header.frame_id;
    if(map_sub_topics_check_.count(frame_id) == 0){
      map_sub_topics_check_.emplace(frame_id,SensorCheck());
      map_sub_topics_check_.at(frame_id).SaveReportPath(true,save_check_result_path_);
    }
    CHECK_NE(map_sub_topics_check_.count(frame_id),0);
    return map_sub_topics_check_.at(frame_id).AddSensorData(sensor_data);
  }

map 遍历

反向遍历

  • 可以使用反向迭代器reverse_iterator反向遍历map映照容器中的数据,它需要rbegin()和rend()方法指出反向遍历的起始位置和终止位置。
     for(map<int,char>::reverse_iterator rit=m.rbegin();rit!=m.rend();rit++) 
     	cout<<(*rit).first<<","<<(*rit).second<<endl;    
    

查找

  • 元素搜索: 使用find()方法搜索某个键值,如果搜索到了,则返回该键值所在的迭代起位置否则,返回end()迭代器位置,由于map采用黑白树搜索,所以搜索速度极快。当然也可以用count()方法,但是需要如果想直接使用的话再使用键值搜索,需要两次查找,这时候就不如find功能好。

4.boost::filesystem 功能

1.boost库文档

boost库文档

2.boost::filesystem

  • boost::filesystem库提供了两个头文件,一个是<boost/filesystem.hpp>,这个头文件包括基本的库内容。它提供了对文件系统的重要操作。同一时候它定义了一个类path。正如大家所想的。这个是一个可移植的路径表示方法,它是filesystem库的基础。
  • 一个是<boost/filesystem/fstream.hpp>。是对std::fstream的一个补充,使用能够使用类boost::path作为參数。从而使得filesystem库与标准库的关系更亲热。
  • 由于文件系统对于大多数系统来说都是共享的,所以不同的进程能够同一时候操作同一个对象,因此filesysetm不提供这方面的特性保证。当然这样的保证也是不可能的。或者至少昂贵的。
  • filesystem在不论什么时候,仅仅要不能完毕对应的任务。它都可能抛出 basic_filesystem_error异常。当然并不是总会抛出异常。由于在库编译的时候能够关闭这个功能。同一时候有两个函数提供了无异常版本号。这是由于在任务不能完毕时并不是是异常。
  • filesystem库的全部内容定义在boost名字空间的一个下级名字空间里,它叫boost::filesytem。在使用boost.filesytem之后,链接时须要加“-lboost_filesystem-mt”选项,由于这个须要额外的链接,并不是一个纯头文件的库。

3.基本功能

boost::filesystem::+下面命令

路径是否存在和是否为路径 在路径后面加’/'没有区别。

  1. system_complete(path); 返回完整路径(相对路径 + 当前路径)
  2. exists(path); 目录是否存在 文件、文件夹都可以
  3. is_directory(path);
  4. is_directory(file_status); 是否是路径 检测路径
  5. is_empty(path); 文件夹是否为空,必须保证路径存在,否则抛异常
  6. is_regular_file(path);
  7. is_regular_file(file_status); 是否是普通文件 普通文件,非文件夹
  8. is_symlink(path);
  9. is_symlink(file_status); 是否是一个链接文件
  10. file_status status(path); 返回路径名对应的状态
  11. initial_path(); 得到程序运行时的系统当前路径
  12. current_path(); 得到系统当前路径
  13. current_path(const Path& p); 改变当前路径
  14. space_info space(const Path& p); 得到指定路径下的空间信息,
    space_info 有capacity, free 和 available三个成员变量,分别表示容量,剩余空间和可用空间。
  15. last_write_time(const Path& p); 最后修改时间
  16. last_write_time(const Path& p, const std::time_t new_time); 修改最后修改时间
  17. bool create_directory(const Path& dp); 建立路径 建立文件夹
  18. create_hard_link(const Path1& to_p, const Path2& from_p);
  19. error_code create_hard_link(const Path1& to_p, const Path2& from_p, error_code& ec); 建立硬链接
  20. create_symlink(const Path1& to_p, const Path2& from_p);
  21. create_symlink(const Path1& to_p, const Path2& from_p, error_code& ec); 建立软链接
  22. remove(const Path& p, system::error_code & ec = singular); 删除空文件
  23. remove_all(const Path& p); 删除文件以及以下的所有内容
  24. rename(const Path1& from_p, const Path2& to_p); 重命名
  25. copy_file(const Path1& from_fp, const Path2& to_fp); 拷贝文件
  26. omplete(const Path& p, const Path& base = initial_path ()); 以base以基,p作为相对路径,返回其完整路径
  27. create_directories(const Path & p); 建立路径 建立文件夹

4.基本案例

案例1

#include <boost/filesystem.hpp>
#include <string>
 
int main()
{
    boost::filesystem::path path("/test/test1");   //初始化
    boost::filesystem::path old_cpath = boost::filesystem::current_path(); //取得当前程序所在文件夹
    boost::filesystem::path parent_path = old_cpath.parent_path();//取old_cpath的上一层父文件夹路径
    boost::filesystem::path file_path = old_cpath / "file"; //path支持重载/运算符
    if(boost::filesystem::exists(file_path)){  //推断文件存在性
        std::string strPath = file_path.string();
        int x = 1;
    } else {
        //文件夹不存在;
        boost::filesystem::create_directory(file_path);  //文件夹不存在。创建
    }
    bool bIsDirectory = boost::filesystem::is_directory(file_path); //推断file_path是否为文件夹
    boost::filesystem::recursive_directory_iterator beg_iter(file_path);
    boost::filesystem::recursive_directory_iterator end_iter;
    for (; beg_iter != end_iter; ++beg_iter) {
        if (boost::filesystem::is_directory(*beg_iter)){
             continue;
        }else{
            std::string strPath = beg_iter->path().string();  //遍历出来的文件名称
            int x=1;
        }
    }
    boost::filesystem::path new_file_path = file_path / "test.txt";
    if(boost::filesystem::is_regular_file(new_file_path)){	//推断是否为普通文件
        UINT sizefile = boost::filesystem::file_size(new_file_path);  //文件大小(字节)
        int x =1;
    }
    boost::filesystem::remove(new_file_path);//删除文件new_file_path
}
案例2
  std::string map_path1 = "/home/megvii/1carto_work/src/map_manager/maps/test";
  std::cout<<"map_path1:"<<map_path1<<std::endl;
  boost::filesystem::path filePath(map_path1);
  if(boost::filesystem::exists(filePath)){
    std::cout<<"目录存在:"<<std::endl;
 
    /*返回删除的文件夹和文件数,包括路径本身
     * remove_all  删除文件夹及其下全部文件
     * remove  删除文件或空文件夹
     */
    uintmax_t test = boost::filesystem::remove_all(map_path1);
    std::cout<<"删除该目录:"<<std::endl;
 
  }else{
    std::cout<<"目录不存在:"<<std::endl;
    /*文件夹不存在。创建*/
    boost::filesystem::create_directory(map_path1);
    std::cout<<"创建该目录:"<<std::endl;
  }

5. c++ 读写txt

1.小结

​​​​​​在这里插入图片描述
在这里插入图片描述

2.正常的读写

案例1

#include <fstream>
#include <ios>
#include <string>
  std::ofstream of("/home/mayun16/gtest/readme.txt",std::ios::app);
  int num =0;
  while (num<10) {
    num++;
    of<<123<<" "<<num<<" "<<"aaa"<<std::endl;
  }
  of.close();

  std::ifstream istm("/home/mayun16/gtest/readme.txt");

  char *cchar = new char[100];

  num = 0;
  while (num<10) {
    num++;
    istm.getline(cchar,20,'\n');
    std::cout<<num<<": "<<cchar<<std::endl;
    std::string sss(cchar);
    std::cout<<num<<" string: "<<sss<<std::endl;
  }

案例2

std::ofstream CSMfile_("/home/poses_saver/txt/CSM_pose.txt",std::ios::out);

void poseCSMCallback(const geometry_msgs::PoseStamped::ConstPtr& msg)
{
  CSMfile_.precision(9);
  CSMfile_ << msg->header.stamp <<" ";
  CSMfile_.precision(5);
  CSMfile_ << msg->pose.position.x<< " "
  << msg->pose.position.y << " "
  << 0 <<" "
  << msg->pose.orientation.x<<" "
  << msg->pose.orientation.y<<" "
  << msg->pose.orientation.z<<" "
  << msg->pose.orientation.w<< std::endl;
}

案例3

      std::string mark1_mark_read_path = loadMarkInMark1Path_;
      std::ifstream mark1_mark_read(mark1_mark_read_path);
      if(mark1_mark_read){
        while (!mark1_mark_read.eof()) {
          std::string str;
          std::getline(mark1_mark_read,str);
          if(!str.empty()){
            std::stringstream ss;
            ss << str;
            int id,key;
            double x,y,theta;
            ss>>id; ss>>key;
            ss>>x;ss>>y;ss>>theta;
            if(key==777){
              cartographer_ros_msgs::LandmarkEntry landmark_entry;
              landmark_entry.id = std::to_string(id);
              tf::Transform transform(tf::createQuaternionFromYaw(theta),tf::Vector3(x,y,0));
              landmark_entry.tracking_from_landmark_transform = TfTransformToGeometryPose(transform);
              landmark_list_mark1_.landmarks.push_back(landmark_entry);
            }
          }
        }
        mark_mark_read.close();


6.set list map

1.介绍

List:

  1. 可以允许重复的对象。
  2. 可以插入多个null元素。
  3. 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
  4. 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

Set:

  • 不允许重复对象

  • 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable维护了一个排序顺序。

  • 只允许一个 null 元素 Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及
    TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此
    TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

  • add(value):添加某个值,返回Set结构本身。 delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    has(value):返回一个布尔值,表示该值是否为Set的成员。 clear():清除所有成员,没有返回值

Map:

  • 不是collection的子接口或者实现类。Map是一个接口。
  • Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
  • TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
  • Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
  • Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

2.什么场景下使用list,set,map呢?

  • 如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList。
  • 如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
  • 如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。LinkedHashSet 也按照元素的插入顺序对它们进行存储。
  • 如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。你可以根据你的后续需要从 Hashtable、HashMap、TreeMap 中进行选择。

7.std :: tie、std :: make_tuple

1.两者比较

  • std::tie( x, y, z )
  • std::make_tuple( std::ref(x), std::ref(y), std::ref(z) )

两个表达式之间没有功能上的差异。tie()只是更短,而make_tuple()更通用。

  • make_tuple

template<class… Types> constexpr tuple<VTypes…> make_tuple(Types&&… t);
我们Ui可以decay_t为每个Ti类型研究。
然后,每个Vi中VTypes是X&如果Ui等号reference_wrapper,否则Vi是Ui。
因此std::make_tuple( std::ref(x), std::ref(y), std::ref(z) )产生了 std::tuple<X&, Y&, Z&>

#include <iostream>
#include <string>
#include <tuple>
 
int main()
{
    auto t = std::make_tuple(1, "Foo", 3.14);
    // index-based access
    std::cout << "(" << std::get<0>(t) << ", " << std::get<1>(t)
              << ", " << std::get<2>(t) << ")\n";
    std::get<0>(t) = 2// type-based access
    std::cout << "(" << std::get<int>(t) << ", " << std::get<const char*>(t)
              << ", " << std::get<double>(t) << ")\n";
    // Note: std::tie and structured binding may also be used to decompose a tuple
}

2.另一方面,tie

template<class… Types> constexpr tuple<Types&…> tie(Types&… t) noexcept;

返回: tuple<Types&…>(t…)。当在一个参数t是ignore,分配的任何值到相应的元组元素没有任何效果。

因此,std::tie(x, y, z)也产生一个std::tuple<X&, Y&, Z&>。

#include <tuple>
#include <functional>

void f(std::reference_wrapper<int> x, int y, int z)
{
    std::tie(x,y,z); // type is std::tuple<std::reference_wrapper<int>&, int&, int&>
    std::make_tuple(std::ref(x),std::ref(y),std::ref(z)); // type is std::tuple<int&, int&, int&>
}

8.c++ yaml

1.CMakeList.txt中添加:

find_package(PkgConfig REQUIRED)
pkg_check_modules(YAML REQUIRED yaml-cpp)
message(STATUS "YAML=${YAML_INCLUDE_DIRS} ${YAML_LIBRARIES}")

catkin_package(
  DEPENDS
   YAML
)

include_directories( ${YAML_INCLUDE_DIRS})

target_link_libraries(${PROJECT_NAME}_node  ${YAML_LIBRARIES})

2.文件中:

#include <yaml-cpp/yaml.h>

  YAML::Node read_yaml_test;

  read_yaml_test = YAML::LoadFile("/home/mayun16/Desktop/MultiModalRobotParam/system/user_config.yaml");
  int map_id_default = read_yaml_test["map_id_default"].as<int>();
  cout<<"map_id_default: "<<map_id_default <<endl;

3.具体使用:

  YAML::Node config = YAML::LoadFile(yaml_path.string());
  try{
    id_ = config["id"].as<int>();
    resolution_ = config["resolution"].as<double>();
    origin_x_ = config["origin"][0].as<double>();
    origin_y_ = config["origin"][1].as<double>();
    origin_th_ = config["origin"][2].as<double>();
    negate_ = config["negate"].as<int>();
    occupied_thresh_ = config["occupied_thresh"].as<double>();
    free_thresh_ = config["free_thresh"].as<double>();
    floor_ = config["floor"].as<int>();
    name_ = config["name"].as<std::string>();
  }catch(YAML::Exception& e){
    ROS_ERROR("There are wrong parameters,%s",info_yaml_path.c_str());
    return false;
  }

  YAML::Node config = YAML::LoadFile(yaml_path.string());
  try{
    id_ = config["id"].as<int>();
    resolution_ = config["resolution"].as<double>();
    origin_x_ = config["origin"][0].as<double>();
    origin_y_ = config["origin"][1].as<double>();
    origin_th_ = config["origin"][2].as<double>();
    negate_ = config["negate"].as<int>();
    occupied_thresh_ = config["occupied_thresh"].as<double>();
    free_thresh_ = config["free_thresh"].as<double>();
    floor_ = config["floor"].as<int>();
    name_ = config["name"].as<std::string>();
  }catch(YAML::Exception& e){
    ROS_ERROR("There are wrong parameters,%s",info_yaml_path.c_str());
    return false;
  }

9.unordered_set 模型

  • unordered_set是一些容器,它们以不特定的顺序存储唯一的元素,并允许根据各自的值快速检索各个元素。

1.模板原型:

template < class Key,                        // unordered_set::key_type/value_type
           class Hash = hash<Key>,           // unordered_set::hasher
           class Pred = equal_to<Key>,       // unordered_set::key_equal
           class Alloc = allocator<Key>      // unordered_set::allocator_type
           > class unordered_set;
  • 在unordered_set中,元素的值同时是其关键字,用于唯一标识它。 键是不可变的,因此,unordered_set中的元素不能在容器中修改一次,但可以插入和删除它们。
  • 在内部,unordered_set中的元素没有按照任何特定的顺序排序,而是根据它们的散列值组织成桶,以允许通过它们的值直接快速访问各个元素(平均具有恒定的平均时间复杂度)。
  • unordered_set容器的速度比设置容器的速度快,以通过它们的键访问各个元素,尽管它们对于通过元素子集的范围迭代通常效率较低。

2.容器属性

  • 联想 关联容器中的元素由它们的键引用,而不是它们在容器中的绝对位置。
  • 无序 无序容器使用散列表来组织它们的元素,这些散列表允许通过键快速访问元素。
  • 元素的值也是识别它的关键。
  • 独特的钥匙 容器中没有两个元素可以具有相同的键。
  • 分配器感知 容器使用分配器对象来动态处理其存储需求。

Capacity 容量: empyt size max_size
Iterators迭代器:begin end cbegin cend
Element lookup元素查找:find count equal_range
Modifiers修饰符:emplace emplace_hint insert erase clear swap
Buckets水桶:bucket_cout max_bucket_count bucket_size bucket
Hash policy哈希策略:hash_function max_load_factor rehash reserve
Observers:hash_function key_eq get_allocator

  • 非成员函数重载: operators(unordered_set) swap(unordered_set)

容器中的迭代器至少是前向迭代器。C++ 11中对unordered_set描述大体如下:无序集合容器(unordered_set)是一个存储唯一(unique,即无重复)的关联容器(Associative container),容器中的元素无特别的秩序关系,该容器允许基于值的快速元素检索,同时也支持正向迭代。在一个unordered_set内部,元素不会按任何顺序排序,而是通过元素值的hash值将元素分组放置到各个槽(Bucker,也可以译为“桶”),这样就能通过元素值快速访问各个对应的元素(均摊耗时为O(1))。原型中的Key代表要存储的类型,而hash也就是你的hash函数,equal_to用来判断两个元素是否相等,allocator是内存的分配策略。一般情况下,我们只关心hash和equal_to参数。

hash

  • hash通过相应的hash函数,将传入的参数转换为一个size_t类型值,然后用该值对当前hashtable的bucket取模算得其对应的hash值。

而C++标准库,为我们提供了基本数据类型的hash函数:

整型值:bool、char、unsigned char、wchar_t、char16_t、char32_t、short、int、long、long long、unsigned short、unsigned int、unsigned long、unsigned long long。上述的基本数据类型,其标准库提供的hash函数只是简单将其值转换为一个size_t类型值,具体可以参考标准库functional_hash.h头文件,如下所示:

/// Primary class template hash.  
  template<typename _Tp>  
    struct hash;  
  
  /// Partial specializations for pointer types.  
  template<typename _Tp>  
    struct hash<_Tp*> : public __hash_base<size_t, _Tp*>  
    {  
      size_t  
      operator()(_Tp* __p) const noexcept  
      { return reinterpret_cast<size_t>(__p); }  
    };  
  
  // Explicit specializations for integer types.  
#define _Cxx_hashtable_define_trivial_hash(_Tp)     \  
  template<>                      \  
    struct hash<_Tp> : public __hash_base<size_t, _Tp>  \  
    {                                                   \  
      size_t                                            \  
      operator()(_Tp __val) const noexcept              \  
      { return static_cast<size_t>(__val); }            \  
    };  
  
  /// Explicit specialization for bool.  
  _Cxx_hashtable_define_trivial_hash(bool)  
  
  /// Explicit specialization for char.  
  _Cxx_hashtable_define_trivial_hash(char)  
  
  /// Explicit specialization for signed char.  
  _Cxx_hashtable_define_trivial_hash(signed char)  
  
  /// Explicit specialization for unsigned char.  
  _Cxx_hashtable_define_trivial_hash(unsigned char)  
  
  /// Explicit specialization for wchar_t.  
  _Cxx_hashtable_define_trivial_hash(wchar_t)  
  
  /// Explicit specialization for char16_t.  
  _Cxx_hashtable_define_trivial_hash(char16_t)  
  
  /// Explicit specialization for char32_t.  
  _Cxx_hashtable_define_trivial_hash(char32_t)  
  
  /// Explicit specialization for short.  
  _Cxx_hashtable_define_trivial_hash(short)  
  
  /// Explicit specialization for int.  
  _Cxx_hashtable_define_trivial_hash(int)  
  
  /// Explicit specialization for long.  
  _Cxx_hashtable_define_trivial_hash(long)  
  
  /// Explicit specialization for long long.  
  _Cxx_hashtable_define_trivial_hash(long long)  
  
  /// Explicit specialization for unsigned short.  
  _Cxx_hashtable_define_trivial_hash(unsigned short)  
  
  /// Explicit specialization for unsigned int.  
  _Cxx_hashtable_define_trivial_hash(unsigned int)  
  
  /// Explicit specialization for unsigned long.  
  _Cxx_hashtable_define_trivial_hash(unsigned long)  
  
  /// Explicit specialization for unsigned long long.  
  _Cxx_hashtable_define_trivial_hash(unsigned long long)  
  • 对于指针类型,标准库只是单一将地址转换为一个size_t值作为hash值,这里特别需要注意的是char *类型的指针,其标准库提供的hash函数只是将指针所指地址转换为一个sieze_t值,如果,你需要用char *所指的内容做hash,那么,你需要自己写hash函数或者调用系统提供的hash。

  • 2.标准库为string类型对象提供了一个hash函数,即:Murmur hash,。对于float、double、long double标准库也有相应的hash函数,这里,不做过多的解释,相应的可以参看functional_hash.h头文件。

上述只是介绍了基本数据类型,而在实际应用中,有时,我们需要使用自己写的hash函数,那怎么自定义hash函数?参考标准库基本数据类型的hash函数,我们会发现这些hash函数有个共同的特点:通过定义函数对象,实现相应的hash函数,这也就意味我们可以通过自定义相应的函数对象,来实现自定义hash函数。比如:已知平面上有N,每个点的x轴、y轴范围为[0,100],现在需要统计有多少个不同点?hash函数设计为:将每个点的x、y值看成是101进制,如下所示:

#include<bits\stdc++.h>  
using namespace std;  
struct myHash   
{  
    size_t operator()(pair<int, int> __val) const  
    {  
        return static_cast<size_t>(__val.first * 101 + __val.second);  
    }  
};  
int main()  
{  
    unordered_set<pair<int, int>, myHash> S;  
    int x, y;  
    while (cin >> x >> y)  
        S.insert(make_pair(x, y));  
    for (auto it = S.begin(); it != S.end(); ++it)  
        cout << it->first << " " << it->second << endl;  
    return 0;  
}

equal_to

  • 该参数用于实现比较两个关键字是否相等,至于为什么需要这个参数?这里做点解释,前面我们说过,当不同关键字,通过hash函数,可能会得到相同的关键字值,每当我们在unordered_set里面做数据插入、删除时,由于unordered_set关键字唯一性,所以我们得确保唯一性。标准库定义了基本类型的比较函数,而对于自定义的数据类型,我们需要自定义比较函数。这里有两种方法:重载==操作符和使用函数对象,下面是STL中实现equal_to的源代码:
template<typename _Arg, typename _Result>  
    struct unary_function  
    {  
      /// @c argument_type is the type of the argument  
      typedef _Arg  argument_type;     
  
      /// @c result_type is the return type  
      typedef _Result   result_type;    
    };  
template<typename _Tp>  
    struct equal_to : public binary_function<_Tp, _Tp, bool>  
    {  
      bool  
      operator()(const _Tp& __x, const _Tp& __y) const  
      { return __x == __y; }  
    };  

3.扩容与缩容

  • 在vector中,每当我们插入一个新元素时,如果当前的容量(capacity)已不足,需要向系统申请一个更大的空间,然后将原始数据拷贝到新空间中。这种现象在unordered_set中也存在,比如当前的表长为100,而真实存在表中的数据已经大于1000个元素,此时,每个bucker均摊有10个元素,这样就会影响到unordered_set的存取效率,而标准库通过采用某种策略来对当前空间进行扩容,以此来提高存取效率。
  • 当然,这里也存在缩容,原理和扩容类似,不过,需要注意的是,每当unordered_set内部进行一次扩容或者缩容,都需要对表中的数据重新计算,也就是说,扩容或者缩容的时间复杂度至少为O(n)。
    code:
/ unordered_set::find  
#include <iostream>  
#include <string>  
#include <unordered_set>  
  
int main ()  
{  
  std::unordered_set<std::string> myset = { "red","green","blue" };  
  
  std::string input;  
  std::cout << "color? ";  
  getline (std::cin,input);  
  
  std::unordered_set<std::string>::const_iterator got = myset.find (input);  
  
  if ( got == myset.end() )  
    std::cout << "not found in myset";  
  else  
    std::cout << *got << " is in myset";  
  
  std::cout << std::endl;  
  
  return 0;  
}  

10.指针学习总结

指针的值是一个地址,用来指向某个变量、数组、常量的首地址。

/*首先是指针的定义*/
int p;//p是一个整型变量。
int *p;//p是一个指针变量,指向整型变量。
int *p[];/*p是一个数组,数组里存储的是指向整型变量的指针*/
int (*p)[];/*p是一个指针,指向整型数组的首地址。*/
int *p();/*p是一个函数,返回值是整型指针*/
int (*p)();/*p是一个指针,指向一个返回值为整型的函数*/
  
/*指针的赋值*/
int a,b[10];
int fun();
int *p=&a;//等价于 int *p;p=&a;
 
p=b//p指向b[0],p+n指向b[n];
 
int (*ptr)();
ptr=fun;//ptr 指向fun()的入口地址

11.四舍五入

round, roundf, roundl - 四舍五入取整,求最接近 x 的整数
函数原型:

double round(double x);	 	
float roundf(float x);	 		 
long double roundl(long double x);		 
float round(float x);	 	 	 	
long double round(long double x);
  • 头文件:#include <cmath>
    命名空间:std
    参数:x:浮点数
    取整之后的浮点数,是一个最接近 x 的整数值,从坐标轴上看,返回值是与 x 重合或在 x 的两侧与 x 距离最近的整数,
    如果有两个整数和 x 的距离相等,即 x 的小数部分正好等于 0.5 的时候,取绝对值大的那个整数,
    从字面上看,就是 “四舍五入”,只要小数点后面第一位数字是 5 或者大于 5 就进位,小于 5 就舍去。

  • std::round 与 System::Math::RoundTo 函数的区别,在有两个整数和 x 的距离相等的时候:
    round 取绝对值大的整数,即 “四舍五入”;
    RoundTo 取偶数,即 “四舍六入五成双”,称为 Banker’s Rounding 规则 (五后有数入、五后无数凑偶数);

  • round, roundf, roundl 是一组 C++ 11 新标准函数,在 C++ Builder 里面可以使用 System::Math::RoundTo 函数替代,虽然有细微的差别,而且在实际应用当中,更推荐使用 “四舍六入五成双” 的 Banker’s Rounding 规则的 System::Math::RoundTo 函数。

12 std::unique_ptr、shared_ptr

class a;
std::unique_ptr<A> (new A());

简易使用

//申明
std::unique_ptr<RangeDataCollatorNew> range_data_collator_ptr_;  ///< 激光数据同步对象
std::shared_ptr<PoseExtrapolatorSimple>  pose_extrapolator_ptr_;    ///< 位姿推算器对象

//初始化
range_data_collator_ptr_ = std::unique_ptr<RangeDataCollatorNew>(new RangeDataCollatorNew());
pose_extrapolator_ptr_ = std::make_shared<PoseExtrapolatorSimple>(kPoseQueueDuration, kGravityTimeConstant);

13. 【C++ 异常】jump to case label [-fpermissive]

13.1 问题代码

int main()
{
  int test = 2;
  switch(test)
  {
    case 1:
      int i = 1;  // i初始化后,一直存在,直到switch结束
      cout << i;
      break;
    case 2:
      cout << i;  // i未被初始化
      break;
    default:
      cout << "error" << endl;
  }
}
#报错信息如下
//test.cpp: In function 'int main()':
//test.cpp: error: jump to case label [-fpermissive]
//   case 2:
//        ^
//test.cpp: error:   crosses initialization of 'int i'
//    int b = 1;
//test.cpp: error: jump to case label [-fpermissive]
//   default:
//   ^
//test.cpp:11:8: error:   crosses initialization of 'int i'
//    int b = 1;

13. 2 说明

  • 从上面的代码中可以看出,因为switch中没有单独的区域块来限定变量i的声明周期,所以变量的作用域是初始化点到switch的结尾处。这里由于我们无法确定其他case中是否会使用到这种变量,使用之前变量是否被初始化,所以编译器会报错。例如:test值为2,直接执行case 2的话,未定义变量就会出异常。这也是编译器报错crosses initialization的原因。
  • 经过检验发现,无论其他分支是否包含定义的变量,只要case中带变量不带括号,编译器都会报错。

13.3修改方法

1、【缩小作用域】将case 1的代码用{ }括起来,设定清楚变量i的作用域,避免其他case访问
2、【扩大作用域】将变量i放到switch外部,switch中的每个case都可以访问

13.4 深入了解

switch语句是goto语句的一种,所以goto具备相同的性质,下述的goto语句不会被执行,变量i一定会被定义,但是会报跟上面一样的错误。这说明goto与标签之间,不能出现变量。变量必须出现在goto之前或标签之后。

13 undef

修改已经宏定义的符号常量的值:

#include <stdio.h>
int main( void )
{
#define MAX 200
    printf("MAX= %d\n",MAX);
#undef MAX
#define MAX 300
    printf("MAX= %d\n",MAX);
    return 0;
}

#undef  #undef 是在后面取消以前定义的宏定义
该指令的形式为 :
  #undef 标识符

  • 其中,标识符是一个宏名称。如果标识符当前没有被定义成一个宏名称,那么就会忽略该指令。
  • 一旦定义预处理器标识符,它将保持已定义状态且在作用域内,直到程序结束或者使用#undef 指令取消定义。

14.c++14 std::make_unique

template <class T>
struct _Unique_if {
  typedef std::unique_ptr<T> _Single_object;
};

template <class T>
struct _Unique_if<T[]> {
  typedef std::unique_ptr<T[]> _Unknown_bound;
};

template <class T, size_t N>
struct _Unique_if<T[N]> {
  typedef void _Known_bound;
};

template <class T, class... Args>
typename _Unique_if<T>::_Single_object make_unique(Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <class T>
typename _Unique_if<T>::_Unknown_bound make_unique(size_t n) {
  typedef typename std::remove_extent<T>::type U;
  return std::unique_ptr<T>(new U[n]());
}

template <class T, class... Args>
typename _Unique_if<T>::_Known_bound make_unique(Args&&...) = delete;

15 泛型模板函数 undefined reference to

15.1 问题

一类是链接的问题

  1. 链接时缺失了相关目标文件(.o)
  2. 链接时缺少相关的库文件(.a/.so)
  3. 链接的库文件中又使用了另一个库文件
  4. 多个库文件链接顺序问题
  5. 定义与实现不一致
  6. 在C++代码中链接C语言的库

另一类是C++的name mangling技术的问题

15.2 解决

1. 把实现方法直接写在头文件里
2. 将实现方法全写在另一个文件里,然后在头文件的结尾包含该文件
3. 在头文件对应的实现文件里添加具体类型的函数声明,

15.3 案例

在.cc中申明

template bool SensorChecks::AddSensorData(const nav_msgs::Odometry& sensor_data);
template bool SensorChecks::AddSensorData(const sensor_msgs::Imu& sensor_data);
template bool SensorChecks::AddSensorData(const sensor_msgs::LaserScan& sensor_data);

template <typename DataType>
bool SensorChecks::AddSensorData(const DataType& sensor_data){
  const std::string & frame_id = sensor_data.header.frame_id;
  if(map_sub_topics_check_.count(frame_id) == 0){
    map_sub_topics_check_.emplace(frame_id,SensorCheck());
    map_sub_topics_check_.at(frame_id).SaveReportPath(true,save_check_result_path_);
  }
  CHECK_NE(map_sub_topics_check_.count(frame_id),0);
  return map_sub_topics_check_.at(frame_id).AddSensorData(sensor_data);
}

直接写在头文件 .h中

template <typename DataType>
bool SensorChecks::AddSensorData(const DataType& sensor_data){
  const std::string & frame_id = sensor_data.header.frame_id;
  if(map_sub_topics_check_.count(frame_id) == 0){
    map_sub_topics_check_.emplace(frame_id,SensorCheck());
    map_sub_topics_check_.at(frame_id).SaveReportPath(true,save_check_result_path_);
  }
  CHECK_NE(map_sub_topics_check_.count(frame_id),0);
  return map_sub_topics_check_.at(frame_id).AddSensorData(sensor_data);
}

将文件引入头文件

/*实现方法写在其它文件里,然后包含在头文件的结尾 */
template<typename T>
void AddSensorData(T &a, T &b);
#include "AddSensorData.cc"

16. vector

16.1 vector 简介

  • 作用:它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。

  • vector在C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。

容器特点

  • 序列容器中的元素按严格的线性顺序排序。各个元素按其在此序列中的位置进行访问。
  • 甚至可以通过指针算术直接访问序列中的任何元素,并在序列末尾提供相对快速的元素添加/删除。
  • 容器使用分配器对象动态处理其存储需求。

16.2问题

赋值
将一个vector的内容赋值给另一个vector

  • 1、 声明
    vector v1(v2);
  • 2、将两个容器内的元素交换, 需要构建临时对象,一个拷贝构造,两次赋值操作。
    vector v1();v1.swap(v2);
  • 3、 将v2赋值给v1 但会清除掉v1以前的内容
    v1.assign(v2.begin(), v2.end());
  • 4、声明迭代器
    vector::iterator it;
    for(it = v2.begin();it!=v2.end();++it){//遍历v2,赋值给v1 效率慢
    v1.push_back(it); }
  • 5、 等号赋值,最简单
    v1 = v2
  • 6、追加,将一个容器中的内容追加到另一个容器的后面:
    std::vector v1, v2 ;
    v1.insert(v1.end(), v2.begin(), v2.end());

2.2 删除

  • iterator erase(iterator it):删除向量中迭代器指向元素
  • iterator erase(iterator first,iterator last):删除向量中[first,last)中元素
  • void pop_back():删除向量中最后一个元素
  • void clear():清空向量中所有元素
// erasing from vector
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  // set some values (from 1 to 10)
  for (int i=1; i<=10; i++) myvector.push_back(i);

  // erase the 6th element
  myvector.erase (myvector.begin()+5);
  
  // erase the first 3 elements:
  myvector.erase (myvector.begin(),myvector.begin()+3);
  std::cout << "myvector contains:";
  for (unsigned i=0; i<myvector.size(); ++i)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';
  return 0;
}

2.3 技巧

  1. 如果你要表示的向量长度较长(需要为向量内部保存很多数),容易导致内存泄漏,而且效率会很低;
  2. Vector作为函数的参数或者返回值时,需要注意它的写法:
    double Distance(vector&a, vector&b) 其中的“&”绝对不能少!!!

释放vector的内存

  • 是的,对于数据量很小的vector,完全没必要自己进行主动的释放,因为其对程序的效率几乎没有影响。但是当vector中存入大量的数据且都数据进行了一些操作,比如删除后,如果我们能积极主动的去释放内存,那么是非常有意义的。
  • std::vector::clear()
    • Removes all elements from the vector (which are destroyed), leaving the container with a size of 0.
    • 英文中提到的是size=0,而非capacity。clear后,size变为了0,capacity没有变化。再读一读clear的英文描述:
    • `A reallocation is not guaranteed to happen, and the vector capacity is not guaranteed to change due to calling this function. A typical alternative that forces a reallocation is to use swap:
  • vector().swap(x); // clear x reallocating
    • vector().swap(v);
    • swap之后,不仅仅是size变化了,capacity也是变化了。
  • shrink_to_fit()
    • 如果clear后在调用shrink_to_fit()也一样
    • 所以 不用以为只有swap替代clear才能正确释放vector的内存,C++11推出了shrink_to_fit方法,也可以达到目的。

17 STL set,deque,queue,map

17.1 std::set

  • 集合是按照特定顺序存储唯一元素的容器。
  • 在一个集合中,元素的值还可以标识它(该值本身是T类型的键),并且每个值都必须是唯一的。 集合中元素的值不能一次在容器中修改(元素始终为const),但可以将其插入容器或从容器中删除。
  • 在内部,集合中的元素始终按照其内部比较对象(类型为Compare)指示的特定严格弱排序标准进行排序。
  • set 容器通常比unordered_set容器通过键访问单个元素的速度慢,但是它们允许基于子集的顺序直接迭代子集。
  • set 通常实现为二进制搜索树。

容器属性

  • Associative:关联容器中的元素由其键而不是其在容器中的绝对位置来引用。
  • Ordered:容器中的元素始终严格遵守顺序。 所有插入的元素均按此顺序分配位置。
  • set:元素的值也是用于标识它的键。
  • Unique keys:容器中没有两个元素可以具有等效键。
  • Allocator-aware:容器使用分配器对象动态处理其存储需求。

1.2 成员函数

操作相关
operator=复制容器内容(公共成员函数)
Iterators:
beginReturn iterator to beginning (public member function )
endReturn iterator to end (public member function )
rbeginReturn reverse iterator to reverse beginning (public member function )
rendReturn reverse iterator to reverse end (public member function )
cbeginReturn const_iterator to beginning (public member function )
cendReturn const_iterator to end (public member function )
crbeginReturn const_reverse_iterator to reverse beginning (public member function )
crendReturn const_reverse_iterator to reverse end (public member function )
Capacity:
emptyTest whether container is empty (public member function )
sizeReturn container size (public member function )
max_sizeReturn maximum size (public member function )
Modifiers:
insertInsert element (public member function )
eraseErase elements (public member function )
swapSwap content (public member function )
clearClear content (public member function )
emplaceConstruct and insert element (public member function )
emplace_hintConstruct and insert element with hint (public member function )
Observers:
key_compReturn comparison object (public member function )
value_compReturn comparison object (public member function )
Operations:
findGet iterator to element (public member function )
count)因为集合容器中的所有元素都是唯一的,所以该函数只能返回1(如果找到了元素)或零(否则)
lower_boundReturn iterator to lower bound (public member function )
upper_boundReturn iterator to upper bound (public member function )
equal_rangeGet range of equal elements (public member function )

unordered_set

  • 无序集合是不按特定顺序存储唯一元素的容器,并允许根据其值快速检索单个元素。
  • 在unordered_set中,元素的值同时是其键,可以唯一地标识它。 键是不可变的,因此,不能在容器中一次修改unordered_set中的元素-尽管可以将它们插入和删除。
  • 在内部,unordered_set中的元素未按任何特定顺序排序,而是根据其哈希值组织到存储桶中,以允许直接通过其值快速访问各个元素(平均时间常数平均保持不变)。
  • 尽管unordered_set容器通过它们的键访问单个元素的速度要比set容器快,但是通过它们的元素子集进行范围迭代通常效率较低。

unordered_set与set差异函数

函数说明
count用特定键计数元素,不止0,1

17.2 deque函数:

单向队列(一端操作):Queue
双向队列(两端操作):Deque

  • deque容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与vector不同,deque支持高效插入和删除容器的头部元素,因此也叫做双端队列。deque类常用的函数如下。

构造函数

  • deque():创建一个空deque
  • deque(int nSize):创建一个deque,元素个数为nSize
  • deque(int nSize,const T& t):创建一个deque,元素个数为nSize,且值均为t
  • deque(const deque &):复制构造函数

增加函数

  • void push_front(const T& x):双端队列头部增加一个元素X
  • void push_back(const T& x):双端队列尾部增加一个元素x
  • iterator insert(iterator it,const T& x):双端队列中某一元素前增加一个元素x
  • void insert(iterator it,int n,const T& x):双端队列中某一元素前增加n个相同的元素x
  • void insert(iterator it,const_iterator first,const_iteratorlast):双端队列中某一元素前插入另一个相同类型向量的[forst,last)间的数据

删除函数

  • Iterator erase(iterator it):删除双端队列中的某一个元素
  • Iterator erase(iterator first,iterator last):删除双端队列中[first,last)中的元素
  • void pop_front():删除双端队列中最前一个元素
  • void pop_back():删除双端队列中最后一个元素
  • void clear():清空双端队列中最后一个元素

遍历函数

  • reference at(int pos):返回pos位置元素的引用
  • reference front():返回首元素的引用
  • reference back():返回尾元素的引用
  • iterator begin():返回向量头指针,指向第一个元素
  • iterator end():返回指向向量中最后一个元素下一个元素的指针(不包含在向量中
  • reverse_iterator rbegin():反向迭代器,指向最后一个元素
  • reverse_iterator rend():反向迭代器,指向第一个元素的前一个元素

判断函数

  • bool empty() const:向量是否为空,若true,则向量中无元素

大小函数

  • Int size() const:返回向量中元素的个数
  • int max_size() const:返回最大可允许的双端对了元素数量值

其他函数
void swap(deque&):交换两个同类型向量的数据
void assign(int n,const T& x):向量中第n个元素的值设置为x

17.3. map、unordered_map

map
std::map 类模板:

template < class Key,                                     // map::key_type
           class T,                                       // map::mapped_type
           class Compare = less<Key>,                     // map::key_compare
           class Alloc = allocator<pair<const Key,T> >    // map::allocator_type
           > class map;

在 std::map 中,Key 和 T 一起组成了 std::map 的 value_type:

typedef pair<const Key, T> value_type;
类型成员定义
key_type第一个模板参数(Key)
mapped_type第二个模板参数(T)
value_typepair<const key_type,mapped_type>
key_compare第三个模板参数(Compare)
  • 关联性:std::map 是一个关联容器,其中的元素根据键来引用,而不是根据索引来引用。
  • 有序性:在内部,std::map 中的元素总是按照其内部的比较器(比较器类型由Compare类型参数指定)指示的特定严格弱序标准按其键排序。
  • 唯一性:std::map 中的元素的键是唯一的。
  • std::map 通常由二叉搜索树实现。

构造案例

// constructing maps
#include <iostream>
#include <map>

// 比较器1
bool fncomp (char lhs, char rhs) { 
	return lhs < rhs; 
}

// 比较器2
struct classcomp {
	bool operator() (const char& lhs, const char& rhs) const {
		return lhs<rhs;
	}
};

int main () {
	// 默认构造,构造一个空的map
	std::map<char,int> first;
	
	first['a']=10;
	first['b']=30;
	first['c']=50;
	first['d']=70;
	
	// 由一对范围迭代器指定输入
	std::map<char,int> second (first.begin(), first.end());
	
	// 复制构造
	std::map<char,int> third (second);
	
	// 指定比较器:使用类
	std::map<char, int, classcomp> fourth;                 // class as Compare
	
	// 指定比较器:使用函数指针
	bool(*fn_pt)(char, char) = fncomp;
	std::map<char, int, bool(*)(char,char)> fifth (fn_pt); // function pointer as Compare
	
	return 0;
}

unordered_map

  • 关联性:通过key去检索value,而不是通过绝对地址(和顺序容器不同)
  • 无序性:使用hash表存储,内部无序
  • Map : 每个值对应一个键值
  • 键唯一性:不存在两个元素的键一样
  • 动态内存管理:使用内存管理模型来动态管理所需要的内存空间

unordered_map<const Key, T> map;

迭代器

unordered_map<Key,T>::iterator it;
(*it).first;             // the key value (of type Key)
(*it).second;            // the mapped value (of type T)
(*it);                   // the "element value" (of type pair<const Key,T>) 

17 fill ,fill_n

填充”功能为范围[begin,end]中的所有元素分配值“ val”,其中“ begin”是初始位置,“ end”是最后位置。

17.1 fill

头文件

  • algorithm

声明

template< class ForwardIt, class T >
void fill( ForwardIt first, ForwardIt last, const T& value );
  • ForwardIt 是一种迭代器,支持的数据类型包括数组、vector等。此外,对于超出范围的输入,是一种未定义的操作,结果视编译器而异。

参数

  • first, last :所需要修改元素的范围
  • value: 所需要修改的值

函数实现

template< class ForwardIt, class T >
void fill(ForwardIt first, ForwardIt last, const T& value)
{
    for (; first != last; ++first) {
        *first = value;
    }
}

案例

/*
* @Author:Jason.Lee
* @Date: 2018-7-3 
*/ 

#include<algorithm>
#include<iostream>
#include<iterator>

using namespace std;

int main(){
    vector<int> vect(8); 
    // calling fill to initialize values in the 
    // range to 4 
    fill(vect.begin() + 2, vect.end(), 4); 
    copy(vect.begin(),vect.end(),ostream_iterator<int>(cout," "));
    return 0;
}

17.2. fill_n

  • fill_n函数与fill在功能上并没有太大不同,应用场景有所不同。
  • fill是将整块数据“从头到尾”进行赋值,而fill_n是将数据的n个元素进行赋值,两者其实都能实现相同的效果。

头文件

  • Defined in header

声明

template< class OutputIt, class Size, class T >
void fill_n( OutputIt first, Size count, const T& value );

template< class OutputIt, class Size, class T >
OutputIt fill_n( OutputIt first, Size count, const T& value );

template< class OutputIt, class Size, class T >
constexpr OutputIt fill_n( OutputIt first, Size count, const T& value );

template< class ExecutionPolicy, class ForwardIt, class Size, class T >
ForwardIt fill_n( ExecutionPolicy&& policy, ForwardIt first, Size count, const T& value );

参数

  • first - 要修改的元素范围的开始
  • count - 要修改的元素数
  • value - 要分配的值
  • policy - 要使用的执行策略。 有关详细信息,请参见执行策略

返回值
如果count> 0,则迭代最后分配的元素之后的迭代器,否则迭代第一个。

异常
具有名为ExecutionPolicy的模板参数的重载报告错误,如下所示:

  • 如果作为算法一部分调用的函数的执行引发异常,并且ExecutionPolicy是标准策略之一,则将调用std :: terminate。 对于任何其他ExecutionPolicy,该行为都是实现定义的。
    如果算法无法分配内存,则会抛出std :: bad_alloc。

案例

#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
 
int main()
{
    std::vector<int> v1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
 
    std::fill_n(v1.begin(), 5, -1);
 
    std::copy(begin(v1), end(v1), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n";
}

18 queue vs deque

二者差异:

  • queue可以访问两端但是只能修改队头
  • deque可以访问两端并且可以在队首和队尾删除和插入元素

queue

  • queue 从队首弹出,先入先出
  • 并且queue只能从 队首删除元素,但是两端都能访问。
    queue<int>q;  //创建一个int型空队列q
    q.empty();  // 判断队列是否为空,为空返回true
    q.push(s);  // 将变量s从队尾入队
    q.pop();    // 将队头元素弹出
    q.front();  // 只返回队头元素
    q.back();  // 只返回队尾元素
    q.size()  //返回队列中元素个数
    

deque

  • deque双端队列的操作(可以在队头队尾进行入队出队操作)
    deque<int> dq; // 创建一个数双端队列dq
    dq.empty(); // 判断队列是否为空,为空返回true
    dq.push_front(s); // 将s从队头入队
    dq.push_back(s); // 将s从队尾入队,和普通队列方式一样
    dq.front(); // 只返回队头元素
    dq.back(); // 只返回队尾元素
    dq.pop_front(); // 将队头元素弹出
    dq.pop_back; // 将队尾元素弹出
    dq.clear(); // 将队列清空
    

19 遍历

  • 深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search, 简称 BFS))是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中。

深度、广度概念

  • 深度优先遍历 DFS

    • 主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
    • 即:我们选择一条支路,尽可能不断地深入,如果遇到死路就往回退,回退过程中如果遇到没探索过的支路,就进入该支路继续深入。
    • 像这样先深入探索,走到头再回退寻找其他出路的遍历方式,就叫做深度优先遍历(DFS)。
  • 广度优先遍历 BFS

    • 首先把起点相邻的几个顶点遍历完,然后去遍历稍微远的(隔一层)的顶点,然后再去玩遍历更远一些(隔两层)的顶点…
    • 像这样一层一层由内而外的遍历方式,就叫做广度优先遍历(BFS)。

深度、广度 实现

  • 深度优先遍历的关键在于 回溯,广度优先遍历的关键在于重放
  • 深度优先遍历的实现:
    • 回溯是什么意思呢?回溯顾名思义, 就是自后向前,追溯曾经走过的路径
    • 要想实现回溯,可以利用栈的先入后出特性,也可以采用递归的方式(因为递归本身就是基于方法调用栈来实现)。
  • 广度优先遍历的实现
    • 回溯与重放是完全相反的过程。
    • 把遍历过的顶点按照之前的遍历顺序重新回顾,就叫做重放。同样的,要实现重放也需要额外的存储空间,可以利用队列的先入先出特性来实现。

二者对比

  • 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列
  • 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历。
  • 广度优先遍历:又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。
  • 深度优先搜素算法:不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。—— 储存空间小,运行慢
  • 广度优先搜索算法:保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。—— 存储空间大,运行快

经验:

  • 通常深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。

    • 深度优先搜索通常用于模拟游戏(以及现实世界中类似游戏的情况)。 在典型的游戏中,您可以选择几种可能的操作之一。 每种选择都会导致进一步的选择,每种选择都会导致进一步的选择,以此类推,形成一种不断扩展的树形可能性图。
  • 广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些。

    • 广度优先搜索可用于在像对等网络中找到邻居节点,如BitTorrent,用于查找附近位置的GPS系统,用于查找指定距离内的人的社交网站以及类似的东西。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值