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的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。
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语言库中的符号进行链接的问题。
extern "C" { void *memset(void *, int, size_t); }
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函数的处理步骤:
将inline函数体复制到inline函数调用点处;
为所用inline函数中的局部变量分配内存空间;
将inline函数的的输入参数和返回值映射到调用方法的局部变量空间中;
如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)。
优点:
内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
内联函数在运行时可调试,而宏定义不可以。
缺点
代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
inline函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像non-inline可以直接链接。
是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。
虚函数&内联函数
内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual
唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
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; }