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
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
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";
// 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 (new A());
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;