C++相关特性

C++相关特性

struct和class

  • C的struct与C++的class的区别:struct只是作为一种复杂数据类型定义,不能用于面向对象编程。

  • C++中的struct和class的区别:对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行。

关键字

auto和decltype
explicit

​ explicit关键字只能用于修饰只有一个参数的类构造函数,它的作用是表明该构造函数是显示的,而非隐式的,跟它相对应的另一个关键字是implicit,意思是隐藏的,类构造函数默认情况下即声明为implicit。

作用:禁止隐式调用类内的单参构造函数,主要包括以下三层意思:

  • 该关键字只能用来修饰类内部的构造函数;

  • 禁止隐式调用拷贝构造函数;

  • 禁止类对象之间的隐式转换;

  
  class CTest1 {
  public:
      CTest1(int n)
      {
          cout<<"Constructor of CTest1"<<endl;
      }
      CTest1(const CTest1&)
      {
          cout<<"Copy constructor of CTest1"<<endl;
      }
  };
  class CTest2 {
  public:
      explicit CTest2(int n)
      {
          cout<<"Constructor of CTest2"<<endl;
      }
      explicit CTest2(const CTest2&)
      {
          cout<<"Copy constructor of CTest2"<<endl;
      }
  };
  int main()
  {
      CTest1 a1(1);            //显示调用构造函数
      CTest1 b1 = 1;           //隐式调用构造函数
      CTest1 c1 = a1;          //隐式调用拷贝构造函数
      CTest1 d1(b1);           //显示调用拷贝构造函数
      CTest2 a2(2);            //显示调用构造函数
      CTest2 b2 = 2;           //隐式调用构造函数,编译错误
      CTest2 c2 = a2;          //隐式调用拷贝构造函数,编译错误
      CTest2 d2(b2);           //显示调用拷贝构造函数
      return 0;
  }

显式类型转换

c++primer(144)

C++的四种显式类型转换为:static_cast、const_cast、reinterpret_cast和dynamic_cast

类型转换的一般形式:cast-name<type>(expression);

static_cast

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast;

  
  //强制类型转换以便执行浮点数除法
  double slope=static_cast<double>(j)/i;
  
  注:
  
  顶层const:表示指针本身是个常量。如:int *const p;
  
  底层const:表示指针所指的对象是一个常量。如:int const *p;

const_cast

该运算符只能改变运算对象的底层const。

  
  const char *pc;
  char *p=const_cast<char*>(pc);

reinterpret_cast

通常为运算对象的位模式提供较低层次上的重新解释。

  
  int *ip;
  char *pc=reinterpret_cast(ip);

注:

1、在指针之间转换,将一个类型的指针转换为另一个类型的指针,无关类型;

2、将指针值转换为一个整型数,但不能用于非指针类型的转换。

dynamic_cast

运行时类型识别(以区别以上三个均在编译时识别),用于将基类的指针或引用安全地转换成派生类的指针或引用。

总 结

  • 去const属性用const_cast。

  • 基本类型转换用static_cast。

  • 多态类之间的类型转换用daynamic_cast。

  • 不同类型的指针类型转换用reinterpreter_cast

动态内存

智能指针

​ C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

shared_ptr

​ shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

  • 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的

  • 拷贝和赋值:拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。

  
  auto p=make_shared<int>(42); //p指向的对象只有p一个引用者
  auto q=(p);   //p和q指向同一个对象,此对象有两个引用者
  
  auto r=make_shared<int>(42);//p指向的对象只有r一个引用者
  r=p;     //给r赋值,令它指向另一个地址
           //递增q指向的对象的引用计数
          //递减r原来指向的对象的引用计数
          //r原来指向的对象已经没有引用者,会自动释放
  • get函数获取原始指针

  • 注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存

  • 注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。

  
  #include <iostream>
  #include <memory>
  int main() {  
          int a = 10;
          std::shared_ptr<int> ptra = std::make_shared<int>(a);
          std::shared_ptr<int> ptra2(ptra); //copy
          std::cout << ptra.use_count() << std::endl;
  
          int b = 20;
          int *pb = &a;
          //std::shared_ptr<int> ptrb = pb;  //error
          std::shared_ptr<int> ptrb = std::make_shared<int>(b);
          ptra2 = ptrb; //assign
          pb = ptrb.get(); //获取原始指针
  
          std::cout << ptra.use_count() << std::endl;
          std::cout << ptrb.use_count() << std::endl;    
  }
unique_ptr

​ unique_ptr“拥有”它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

​ 与shared_ptr不同,没有类似make_ptr的标准库函数返回一个unique_ptr。定义一个shared_ptr时,需要将其绑定到一个new返回的 指针上。

  
  unique_ptr<double> p1; //可以指向一个double的unique_ptr
  unique_ptr<int> p2(new int(42)); //p2指向一个值为42的int
  
  //由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作
  unique_ptr<string> p1(new string("sss"));
  unique_ptr<string> p2(p1);       //错误:unique_ptr不支持拷贝
  unique_ptr<string> p3;
  p3=p2                            //错误:unique_ptr不支持赋值

​ 虽然不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

  
  //将所有权从p1(接上p1,指向“sss”)转移给p2
  unique_ptr<string> p2(p1.release());  //release将p1置空
  unique_ptr<string> p3(new string("nnn"));
  //将所有权从p3转移到p2
  p2.reset(p3.release());//reset释放了p2原来指向的内存
weak_ptr

​ weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

动态数组

​ new和delete运算符一次分配/释放一个对象,但某些应用需要一次为很多对象分配内存的功能。例如,vector和string都在连续内存中保存它们的元素,因此,当容器需要重新分配内存时,必须一次性为很多元素分配内存。

​ 为支持这种需求,C++语言和标准库提供了两种一次分配一个对象数组的方法。C++语言定义了另一种new表达式语法,可以分配并初始化一个对象数组。标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。使用allocator通常会提供更好的性能和更灵活的内存管理能力。

new和数组

​ new分配一个对象数组:

  
  //调用get_size()确定分配多少个int
  int *pia=new int[get_size()]; //pia指向第一个int
  
  //也可以用一个表示数组类型的别名来分配一个数组
  typedef int arrT[42];//arrT表示42个int的数组类型
  int *p=new arrT;     //分配一个42个int的数组;p指向第一个int

​ 初始化动态分配对象的数组

  
  int *pia=new int[10];           //10个未初始化的int
  int *pia2=new int[10]();        //10个值初始化为0的int
  int *psa=new string[10];         //10个空string
  int *psa2=new string[10]();      //10个空string
  
  //10个int分别用列表中对应的初始化器初始化
  int *pia3=new int[10]{0,1,2,3,4,5,6,7,8,9};

​ 释放动态数组

  
  delete p;      //p必须指向一个动态分配的对象或为空
  delete [] pa;  //pa必须指向一个动态分配的数组或为空
allocator类

(见STL源码分析)

​ 当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在这种情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才执行对象创建操作。一般情况下,将内存分配和对象构造组合在一起可能导致不必要的浪费。

​ 标准库allocator类定义在memory头文件中,它将内存分配与对象构造分离开来,提供了一种类型感知的内存分配方式,它分配的内存是原始的、未构造的。

hashmap、hashtable

​ 总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。hash还有hash函数的耗时。当有100w条记录的时候,map也只需要20次的比较,200w也只需要21次的比较!所以并不一定常数就比log(n) 小!

​ hash_map对空间的要求要比map高很多,所以是以空间换时间的方法,而且,hash_map如果hash函数和hash因子选择不好的话,也许不会达到你要的效果,所以至于用map,还是hash_map,从3个方面来权衡:查找速度, 数据量, 内存使用。

java中hashmap与hashtable区别

1、HashMap是非线程安全的,HashTable是线程安全的。

2、HashMap的键和值都允许有null值存在,而HashTable则不行。

3、因为线程安全的问题,HashMap效率比HashTable的要高。

4、Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。

​ 一般现在不建议用HashTable, ①是HashTable是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。

extern “C”

  • 被extern限定的函数或变量是extern类型的

  • 被extern "C"修饰的变量和函数是按照C语言方式编译和连接的

extern "C" 的作用是让C++编译器将 extern "C" 声明的代码当作C语言代码处理,可以避免C++因符号修饰导致代码不能和C语言库中的符号进行链接的问题。

  
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  void *memset(void *, int, size_t);
  
  #ifdef __cplusplus
  }
  #endif

inline内联函数

特征:

  • 相当于把内联函数里面的内容写在调用内联函数处;

  • 相当于不用执行进入函数的步骤,直接执行函数体;

  • 相当于宏,却比宏多了类型检查,真正具有函数特性;

  • 不能包含循环、递归、switch等复杂操作;

  • 类中除了虚函数的其他函数都会自动隐式地当成内联函数。

使用:

  
  // 声明1(加inline,建议使用)
  inline int functionName(int first, int secend,...);
  // 声明2(不加inline)
  int functionName(int first, int secend,...);
  // 定义
  inline int functionName(int first, int secend,...) {/****/};

编译器对inline函数的处理步骤:

  1. 将inline函数体复制到inline函数调用点处;

  2. 为所用inline函数中的局部变量分配内存空间;

  3. 将inline函数的的输入参数和返回值映射到调用方法的局部变量空间中;

  4. 如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)。

优点:

  1. 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。

  2. 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。

  3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。

  4. 内联函数在运行时可调试,而宏定义不可以。

缺点

  1. 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

  2. inline函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像non-inline可以直接链接。

  3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

虚函数&内联函数

  • 内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。

  • 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

  • inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

  
  #include <iostream>  
  using namespace std;
  class Base
  {
  public:
      inline virtual void who()
      {
          cout << "I am Base\n";
      }
      virtual ~Base() {}
  };
  class Derived : public Base
  {
  public:
      inline void who()  // 不写inline时隐式内联
      {
          cout << "I am Derived\n";
      }
  };
  
  int main()
  {
      // 此处的虚函数who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
      Base b;
      b.who();
  
      // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
      Base *ptr = new Derived();
      ptr->who();
  
      // 因为Base有虚析构函数(virtual ~Base() {}),所以delete时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
      delete ptr;
      ptr = nullptr;
  
      system("pause");
      return 0;
  } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值