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

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.區別
malloc申請後空間的值是隨機的,並沒有進行初始化,而calloc卻在申請後,對空間逐一進行初始化,並設置值爲0;
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的區別

  • 標題中提到的四種容器,對於概念不清的人來說,經常容易弄混淆。這裏我不去把庫裏面複雜的原碼拿出剖析,這個如果有興趣其實完全可以查C++Reference,網上的原碼是最權威和細緻的了,而且我覺得有耐心直接認真看原碼的人,也不需要我這篇速記博文了,所以我這裏還是講的通俗一些,把它們區分的七七八八。

3.1 hash_map、unordered_map

這兩個的內部結構都是採用哈希表來實現。區別在哪裏?

  • unordered_map在C++11的時候被引入標準庫了,而hash_map沒有,所以建議還是使用unordered_map比較好。
    哈希表的好處是什麼?
  • 查詢平均時間是O(1)。

顧名思義,unordered,就是無序了,數據是按散列函數插入到槽裏面去的,數據之間無順序可言,但是有些時候我只要訪問而不需要順序,因此可以選擇哈希表。舉個最好的例子,就是我的LeetCode的博文裏------Longest Consecutive Sequence這一題,我的方法二就是用的unordered_map來實現“空間彌補時間”這樣的做法。

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

後者就是在哈希表插入value,而這個value就是它自己的key,而不是像之前的unordered_map那樣有鍵-值對,這裏單純就是爲了方便查詢這些值。我在Longest Consecutive Sequence這一題,我的方法一就是用了unordered_set,同樣是一個將空間彌補時間的方法。再舉個大家好懂的例子,給你A,B兩組數,由整數組成,然後把B中在A中出現的數字取出來,要求用線性時間完成。很明顯,這道題應該將A的數放到一個表格,然後線性掃描B,發現存在的就取出。可是A的範圍太大,你不可能做一個包含所有整數的表,因爲這個域太大了,所以我們就用unordered_set來存放A的數,具體實現庫函數已經幫你搞定了,如果對於具體怎麼去散列的同學又興趣可以查看《算法導論》的第11章或者《數據結構與算法分析》的第五章,如果要看《算法導論》的同學我給個建議,完全散列你第一遍的時候可以暫時跳過不看,確實有點複雜。

轉自:https://blog.csdn.net/u013195320/article/details/23046305


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);
  }

4.boost::filesystem 功能

1.boost庫文檔

https://www.boost.org/doc/

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::+下面命令

路徑是否存在和是否爲路徑 在路徑後面加’/'沒有區別。

system_complete(path); 返回完整路徑(相對路徑 + 當前路徑)
exists(path); 目錄是否存在 文件、文件夾都可以
is_directory(path);
is_directory(file_status); 是否是路徑 檢測路徑
is_empty(path); 文件夾是否爲空,必須保證路徑存在,否則拋異常
is_regular_file(path);
is_regular_file(file_status); 是否是普通文件 普通文件,非文件夾
is_symlink(path);
is_symlink(file_status); 是否是一個鏈接文件
file_status status(path); 返回路徑名對應的狀態
initial_path(); 得到程序運行時的系統當前路徑
current_path(); 得到系統當前路徑
current_path(const Path& p); 改變當前路徑
space_info space(const Path& p); 得到指定路徑下的空間信息,space_info 有capacity, free 和 available三個成員變量,分別表示容量,剩餘空間和可用空間。
last_write_time(const Path& p); 最後修改時間
last_write_time(const Path& p, const std::time_t new_time); 修改最後修改時間
bool create_directory(const Path& dp); 建立路徑 建立文件夾
create_hard_link(const Path1& to_p, const Path2& from_p);
error_code create_hard_link(const Path1& to_p, const Path2& from_p, error_code& ec); 建立硬鏈接
create_symlink(const Path1& to_p, const Path2& from_p);
create_symlink(const Path1& to_p, const Path2& from_p, error_code& ec); 建立軟鏈接
remove(const Path& p, system::error_code & ec = singular); 刪除空文件
remove_all(const Path& p); 刪除文件以及以下的所有內容
rename(const Path1& from_p, const Path2& to_p); 重命名
copy_file(const Path1& from_fp, const Path2& to_fp); 拷貝文件
omplete(const Path& p, const Path& base = initial_path()); 以base以基,p作爲相對路徑,返回其完整路徑
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:

可以允許重複的對象。
可以插入多個null元素。
是一個有序容器,保持了每個元素的插入順序,輸出的順序就是插入的順序。
常用的實現類有 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

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’s 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"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章