C/C++ 小结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/xiaoma_bk/article/details/89471837


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;

猜你喜欢

转载自blog.csdn.net/xiaoma_bk/article/details/89471837
今日推荐