C/C++ 小結
- 1.多重定義問題
- 2.內存分配 malloc與calloc
- 3.C++中map、hash_map、unordered_map、unordered_set的區別
- 4.boost::filesystem 功能
- 5. c++ 讀寫txt
- 6.set list map
- 7.std :: tie、std :: make_tuple
- 8.c++ yaml
- 9.unordered_set 模型
- 10.指針學習總結
- 11.四捨五入
- 12 std::unique_ptr
- 13. 【C++ 異常】jump to case label [-fpermissive]
- 13 undef
- 14.c++14's std::make_unique
- 15 泛型模板函數 undefined reference to
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 問題
一類是鏈接的問題
- 鏈接時缺失了相關目標文件(.o)
- 鏈接時缺少相關的庫文件(.a/.so)
- 鏈接的庫文件中又使用了另一個庫文件
- 多個庫文件鏈接順序問題
- 定義與實現不一致
- 在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"