c++面试常见问题汇总与解析
c++面试常见问题汇总与解析
- 1.指针和引用的区别
- 2.堆和栈的区别
- 3.new和delete是如何实现的,new 与 malloc的异同处
- 4.C和C++的区别
- 5.C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制)
- 6.Struct和class的区别
- 7. define 和const的区别(编译阶段、安全性、内存占用等)
- 8. 在C++中const和static的用法(定义,用途)
- 9. C++中的const类成员函数(用法和意义),以及和非const成员函数的区别
- 10. C++的顶层const和底层const
- 11. final和override关键字
- 12. 拷贝初始化和直接初始化
- 13. 初始化和赋值的区别
- 14. extern "C"的用法
- 15. 模板函数和模板类的特例化
- 16. C++的STL源码(这个系列也很重要,建议侯捷老师的STL源码剖析书籍与视频),其中包括内存池机制,各种容器的底层实现机制,算法的实现原理等)
- 17. STL源码中的hashtable的实现
- 18. STL中unordered_map和map的区别和应用场景
- 19. STL中vector的实现
- 20. STL容器的几种迭代器以及对应的容器(输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器)
- 21. STL中的traits技法
- 22. vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。
- 23. C++中的重载和重写的区别
- 24. C++内存管理,内存池技术(热门问题),与csapp中几种内存分配方式对比学习加深理解
- 25. 介绍面向对象的三大特性,并且举例说明每一个
- 26. C++多态的实现
- 27. C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(包括单一继承,多重继承等)(拓展问题:为什么基类指针指向派生类对象时可以调用派生类成员函数,基类的虚函数存放在内存的什么区,虚函数表指针vptr的初始化时间)
- 28. C++中类的数据成员和成员函数内存分布情况
- 29. this指针
- 30. 析构函数一般写成虚函数的原因
- 31. 构造函数、拷贝构造函数和赋值操作符的区别
- 32. 构造函数声明为explicit
- 33. 构造函数为什么一般不定义为虚函数
- 34. 构造函数的几种关键字(default delete 0)
- 35. 构造函数或者析构函数中调用虚函数会怎样
- 36. 纯虚函数
- 37. 静态类型和动态类型,静态绑定和动态绑定的介绍
- 38. 引用是否能实现动态绑定,为什么引用可以实现
- 39. 深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
- 40. 对象复用的了解,零拷贝的了解
- 41. 介绍C++所有的构造函数
- 42. 什么情况下会调用拷贝构造函数(三种情况)
- 43. 结构体内存对齐方式和为什么要进行内存对齐?
- 44. 内存泄露的定义,如何检测与避免?
- 45. 手写智能指针的实现(shared_ptr和weak_ptr实现的区别)
- 46. 智能指针的循环引用
- 47. 遇到coredump要怎么调试
- 48. 内存检查工具的了解
- 49. 模板的用法与适用场景
- 50. 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?
- 51. 用过C++ 11吗,知道C++ 11哪些新特性?
- 52. C++的调用惯例(简单一点C++函数调用的压栈过程)
- 53. C++的四种强制转换
- 54. C++中将临时变量作为返回值的时候的处理过程(栈上的内存分配、拷贝过程)
- 55. C++的异常处理
- 56. volatile关键字
- 57. 优化程序的几种方法
- 58. public,protected和private访问权限和继承
- 60. decltype()和auto
- 61. inline和宏定义的区别
- 62. C++和C的类型安全
- 63. 参考链接
1.指针和引用的区别
(1)指针有自己的一块空间,而引用只是一个别名;
(2)使用 sizeof 看一个指针的大小为4字节(32位,如果要是64位的话指针为8字节),而引用则是被引用对象的大小。
(3)指针可以被初始化为 NULL,而引用必须被初始化且必须是一个已有对象的引用。
(4)作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象。
(5)指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能被改变。
(6)指针可以是多级,而引用没有分级
(7)如果返回动态分配内存的对象或者内存,必须使用指针,引用可能引起内存泄漏。
2.堆和栈的区别
(1)堆栈空间分配区别:
- 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
- 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
(2)堆栈的缓存方式区别
- 栈:是内存中存储值类型的,大小为2M(window,linux下默认为8M,可以更改),超出则会报错,内存溢出
- 堆:内存中,存储的是引用数据类型,引用数据类型无法确定大小,堆实际上是一个在内存中使用到内存中零散空间的链表结构的存储空间,堆的大小由引用类型的大小直接决定,引用类型的大小的变化直接影响到堆的变化
(3)堆栈数据结构上的区别
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。
3.new和delete是如何实现的,new 与 malloc的异同处
3.1. new操作针对数据类型的处理,分为两种情况:
(1) 简单数据类型(包括基本数据类型和不需要构造函数的类型)
- 简单类型直接调用 operator new 分配内存;
- 可以通过new_handler 来处理 new 失败的情况;
- new 分配失败的时候不像 malloc 那样返回
NULL,它直接抛出异常(bad_alloc)。要判断是否分配成功应该用异常捕获的机制;
(2)复杂数据类型(需要由构造函数初始化对象)
- new 复杂数据类型的时候先调用operator new,然后在分配的内存上调用构造函数。
3.2. delete也分为两种情况:
(1) 简单数据类型(包括基本数据类型和不需要析构函数的类型)
- delete简单数据类型默认只是调用free函数。
(2)复杂数据类型(需要由析构函数销毁对象)
- delete复杂数据类型先调用析构函数再调用operator delete。 从原理上来分析可以看看这篇博客:C++
new和delete的实现原理
3.3. new和delete与 malloc 和 free 的区别:
(1)属性上:new / delete 是c++关键字,需要编译器支持。 malloc/free是库函数,需要c的头文件支持。
(2)参数:使用new操作符申请内存分配时无须制定内存块的大小,编译器会根据类型信息自行计算。而mallco则需要显式地指出所需内存的尺寸。
(3)返回类型:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,故new是符合类型安全性的操作符。而malloc内存成功分配返回的是void *,需要通过类型转换将其转换为我们需要的类型。
(4)分配失败时:new内存分配失败时抛出bad_alloc异常;malloc分配内存失败时返回 NULL。
(5)自定义类型:new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。 malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
(6)重载:C++允许重载 new/delete 操作符。而malloc为库函数不允许重载。
(7)内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。其中自由存储区为:C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
4.C和C++的区别
C 是面向过程的一门编程语言,C++ 可以很好地进行面向对象的程序设计。C++ 虽然主要是以 C 的基础发展起来的一门新语言,但它不是 C 的替代品,它们是兄弟关系。面向对象和面向过程不是矛盾的,而是各有用途、互为补充的。
C++ 对 C 的增强,表现在六个方面:
- 增强了类型检查机制
- 增加了面向对象的机制
- 增加了泛型编程的机制(template)
- 增加了异常处理
- 增加了重载的机制
增加了标准模板库(STL)
(1)类型检查
C/C++ 是静态数据类型语言,类型检查发生在编译时,因此编译器知道程序中每一个变量对应的数据类型。C++ 的类型检查相对更严格一些。
很多时候需要一种能够实际表示多种类型的数据类型。传统上 C 使用 void* 指针指向不同对象,使用时强制转换回原始类型或兼容类型。这样做的缺陷是绕过了编译器的类型检查,如果错误转换了类型并使用,会造成程序崩溃等严重问题。
C++ 通过使用基类指针或引用来代替 void* 的使用,避免了这个问题(其实也是体现了类继承的多态性)。
面向对象
C 的结构体传递的是一种数据结构,我们只是在主函数里面对这种数据类型做某种调用。主函数的架构依然是基于函数、函数族的处理过程,即面向过程。
C++ 中最大的区别就是允许在结构体中封装函数,而在其他的地方直接调用这个函数。这个封装好的可直接调用的模块有个新名词——对象;并且也把结构体换一个名字——类。这就是面向对象的思想。在构建对象的时候,把对象的一些操作全部定义好并且给出接口的方式,对于外部使用者而言,可以不需要知道函数的处理过程,只需要知道调用方式、传递参数、返回值、处理结果。
泛型编程(template)
所谓泛型编程,简而言之就是不同的类型采用相同的方式来操作。在 C++ 的使用过程中,直接 template 用的不多,但是用 template 写的库是不可能不用的。因此需要对泛型有比较深入的了解,才可以更好地使用这些库。
C++ 里面的模版技术具有比类、函数更高的抽象水平,因为模版能够生成出(实例化)类和函数。可以用来:
异常处理
C 语言不提供对错误处理的直接支持,但它以返回值的形式允许程序员访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。可以在 errno.h 头文件中找到各种各样的错误代码。
所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把 errno 设置为 0(表示没有错误),这是一种良好的编程习惯。
C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。
函数重载 & 运算符重载
C++ 可以实现函数重载,条件是:函数名必须相同,返回值类型也必须相同,但参数的个数、类型或顺序至少有其一不同。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。大多数的重载运算符可被定义为普通的非成员函数(func(a, b) 形式调用)或者被定义为类成员函数(a.func(b) 形式调用)。
标准模板库(STL)
5.C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制)
通常,我们聊到Java,第一印象“面向对象”,“没有指针,编写效率高,执行效率较低”。更深入、专业一点就谈论 “java内存自动回收(GC垃圾回收机制),多线程编程”。**
java的三大特性是封装、继承和多态。**
总结如下:
(1) JAVA的应用在高层,C++在中间件和底层
(2) JAVA离不开业务逻辑,而C++可以离开业务为JAVA们服务
(3) java语言给开发人员提供了更为简洁的语法;取消了指针带来更高的代码质量;完全面向对象,独特的运行机制是其具有天然的可移植性。
(4) java 是运行在JVM上的,之所以说它的可移植性强,是因为jvm可以安装到任何的系统
(5) c++不是不能在其他系统运行,而是c++在不同的系统上运行,需要不同的编码(这一点不如java,只编写一次代码,到处运行)。java程序一般都是生成字节码,在JVM里面运行得到结果。
(6) java 在web 应用上具有c++ 无可比拟的优势
(7) java在桌面程序上不如c++实用,C++可以直接编译成exe文件,指针是c++的优势,可以直接对内存的操作,但同时具有危险性 。(操作内存的确是一项非常危险的事情,一旦指针指向的位置发生错误,或者误删除了内存中某个地址单元存放的重要数据,后果是可想而知的)。
(8) 垃圾回收机制的区别。c++用析构函数回收垃圾,java自动回收(GC算法),写C和C++程序时一定要注意内存的申请和释放。
(9) java 丰富的插件是java 发展如此迅速的原因
(10)java 很大的沿袭了c++的一些实用结构
(11)对于底层程序的编程以及控制方面的编程,c++很灵活,因为有句柄的存在。Java并不仅仅是C++语言的一个变种,它们在某些本质问题上有根本的不同:
- Java比C++程序可靠性更高。有人曾估计每50行C++程序中至少有一个BUG。姑且不去讨论这个数字是否夸张,但是任何一个C++程序员都不得不承认C++语言在提供强大的功能的同时也提高了程序含BUG的可能性。Java语言通过改变语言的特性大大提高了程序的可靠性。
- Java语言不需要程序对内存进行分配和回收。Java丢弃了C++
中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java语言不使用指针,并提供了自动的废料收集,在Java语言中,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题。 - Java语言中没有指针的概念,引入了真正的数组。不同于C++中利用指针实现的“伪数组”,Java引入了真正的数组,同时将容易造成麻烦的指针从语言中去掉,这将有利于防止在c++程序中常见的因为数组操作越界等指针操作而对系统数据进行非法读写带来的不安全问题。
- Java用接口(Interface)技术取代C++程序中的多继承性。接口与多继承有同样的功能,但是省却了多继承在实现和维护上的复杂性。
6.Struct和class的区别
(1)首先说一下C中的结构体和C++中的结构体的异同:
(2)C++中 struct 与 class 的区别:
- 内部成员变量及成员函数的默认访问属性:struct 默认防控属性是 public 的,而 class 默认的访问属性是private的
- 继承关系中默认访问属性的区别:在继承关系,struct 默认是 public 的,而 class 是 private
- class这个关键字还可用于定义模板参数,就等同于 typename;而strcut不用与定义模板参数
7. define 和const的区别(编译阶段、安全性、内存占用等)
(1)起作用的阶段: #define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
(2)作用的方式:const常量有数据类型,而宏常量没有数据类型,只是简单的字符串替换。编译器可以对前者进行类型安全检查。而对后者没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
(3)存储的方式:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份,const比较节省空间,避免不必要的内存分配,提高效率。
8. 在C++中const和static的用法(定义,用途)
(1)static:
- 修饰全局变量:存储在静态存储区;未经初始化的全局静态变量自动初始化为 0;作用域为整个文件之内。
- 修饰局部变量:存储在静态存储;未经初始化的局部静态变量会被初始化为0;作用域为局部作用域,但离开作用域不被销毁。
- 修饰静态函数:静态函数只能在声明的文件中可见,不能被其他文件引用
- 修饰类的静态成员:在类中,静态成员可以实现多个对象之间的数据共享,静态成员是类的所有对象中共享的成员,而不属于某一个对象;类中的静态成员必须进行显示的初始化
- 修饰类的静态函数:静态函数同类的静态成员变量一个用法,都是属于一个类的方法。而且静态函数中只可以使用类的静态变量。
(2)const:
- 修成类成员:在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数;
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。 - 修饰类函数:该函数中所有变量均不可改变。
9. C++中的const类成员函数(用法和意义),以及和非const成员函数的区别
(1):在赋值方面,const数据成员只能采用初始化列表方式,而非数据成员可以采用初始化列表和构造函数体内赋值两种方式。
class Test
{public:Test(int x,int y):m_y(y){m_x = x;//m_x也可以采用初始化列表方式,对于非内部数据类型最好采用初始化列表方式进行初始化 }private:int m_x;const int m_y;};
(2):在函数调用方面,const成员函数可以访问const数据成员(本身显示不能被修改)和const成员函数,可以访问非const数据成员,但是不能修改非const数据成员,且不能调用非const成员函数,而非const成员函数则没有限制。
- 对于每个对象的成员函数(这里不包括static成员函数,因为其不属于某个对象),其都有一个隐形的参数,也就是指向该类对象的一个指针,普通的成员函数,this指针类型是A(A类)const * this,其可以改变this所指向的值,但是不能修改this所保存的地址。而对于const成员函数,this指针类型是const A * const *this,其既不能改变this所指向的值,也不能修改this所保存的地址,因此上述就很好理解了。
10. C++的顶层const和底层const
- 指针实际定义了两个对象,指针本身和它所指的对象。这两个对象都可以用const进行限定。
- 底层const是代表对象本身是一个常量(不可改变);
const int* p2=&b; //-----可以改变p2的值,这是一个底层const
- 顶层const是代表指针的值是一个常量,而指针的值(即对象的地址)的内容可以改变(指向的不可改变);
int* const p1=&i; //-----不能改变p1的值,这是一个顶层const
11. final和override关键字
(1)final
- 用于限制某个类不能被继承,或者某个虚函数不能被重写,修饰函数,final只能修饰虚函数,并且要放到类或者函数的后面。
- final的用法
struct A
{//A::foo is final 限定该虚函数不能被重写virtual void foo() final;//Error: non-virtual function cannot be final,只能修改虚函数void bar() final;
};struct B final : A //struct B is final
{//Error: foo cannot be overridden as it's final in Avoid foo();
};struct C : B //Error: B is final
{};
(2)override
- 确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,还可以防止因疏忽把本来想重写基类的虚函数声明成重载。
- 保证重写虚函数的正确性,又提高代码的可读性。关键字要放到方法后面。确认自己目前在子类中正在重写一个来自父类的函数,那么我们最好是用override关键字来修饰该函数,override修饰的函数表示这个函数一定是父类(祖先)中传下来的,这样就帮助我们进行了函数的名称、参数的检查.
- 以后在子类中重写父类函数的时候,一定加上virtual和override关键字
- 代码示例
struct A
{virtual void func() {}
};struct D : A{//显式重写void func() override{}
};
12. 拷贝初始化和直接初始化
(1)什么是拷贝初始化(也称为复制初始化):将一个已有的对象拷贝到正在创建的对象,如果需要的话还需要进行类型转换。拷贝初始化发生在下列情况:
- 使用赋值运算符定义变量
- 将对象作为实参传递给一个非引用类型的形参
- 将一个返回类型为非引用类型的函数返回一个对象
(2)什么是直接初始化:在对象初始化时,通过括号给对象提供一定的参数,并且要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数
(3)直接初始化和拷贝初始化效率基本一样,因为在底层的实现基本一样,所以将拷贝初始化改为直接初始化效率提高不大。
(4) 例子
- ClassTest ct1(“ab”); 这条语句属于直接初始化,它不需要调用复制构造函数,直接调用构造函数ClassTest(const char *pc),所以当复制构造函数变为私有时,它还是能直接执行的。
- ClassTest ct2 = “ab”; 这条语句为复制初始化,它首先调用构造函数 ClassTest(const char* pc)
函数创建一个临时对象,然后调用复制构造函数,把这个临时对象作为参数,构造对象ct2;所以当复制构造函数变为私有时,该语句不能编译通过。 - ClassTest ct3 = ct1;这条语句为复制初始化,因为 ct1本来已经存在,所以不需要调用相关的构造函数,而直接调用复制构造函数,把它值复制给对象ct3;所以当复制构造函数变为私有时,该语句不能编译通过。
- ClassTest ct4(ct1);这条语句为直接初始化,因为 ct1 本来已经存在,直接调用复制构造函数,生成对象 ct3 的副本对象ct4。所以当复制构造函数变为私有时,该语句不能编译通过。
- 要点就是拷贝初始化和直接初始化调用的构造函数是不一样的,但是当类进行复制时,类会自动生成一个临时的对象,然后再进行拷贝初始化。
13. 初始化和赋值的区别
(1) 普通情况下,初始化和赋值好像没有什么特别去区分它的意义。
- int a=100;
- int a; a=100
(2)复杂数据类型,如类,情况不同;
- 默认构造函数是不传参,构建的对象默认的使用那些值赋值给成员变量;
- 而拷贝构造函数是接受一个相同类的另一个对象,使用该对象来逐成员的为自己的成员赋值;
- 构造函数的目的,是服务于类的初始化的,它并不服务于赋值。赋值是独立于初始化之后的操作。
#include <iostream>
using namespace std;
class Point
{
public: Point(int a=0, int b=0):x(a), y(b){}; ~Point(){}; Point& operator =(const Point &rhs); int x; int y;
};
Point& Point::operator =(const Point &rhs)
{ x = rhs.x+1; y = rhs.y+1; return *this;
}
int main(void)
{ Point p(1,1); Point p1 = p; //初始化操作 Point p2; p2 = p; //赋值操作 cout<<"p1.x = "<<p1.x<<" "<<"p1.y="<<p1.y<<endl; cout<<"p2.x = "<<p2.x<<" "<<"p2.y="<<p2.y<<endl; return 0; }
在p1中,Point p1=p;这个操作中,实际上是通过一种类似于拷贝构造函数中逐member的方式(但并没有生成一个拷贝构造函数,生成拷贝构造函数的四种情况见前面的随笔),并没有调用重载的"="运算符。所以最终结果是p1为(1,1)。
而在p2中,初始化与赋值是分开的,Point p2;就已经完成了初始化,这个初始化是通过定义的含参构造函数(但是以a=0,b=0的默认值完成的)。
然后在调用重载运算符,对p中成员均自加后赋值给p2的成员变量。
14. extern "C"的用法
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;
而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器不要修改你的函数名。
15. 模板函数和模板类的特例化
引入的原因:编写单一的模板,它能适应大众化,使每种类型都具有相同的功能,但对于某种特定类型,如果要实现其特有的功能,单一模板就无法做到,这时就需要模板特例化。
定义:是对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。
(1)函数模板特例化:必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。
template<typename T> //函数模板
int compare(const T &v1,const T &v2)
{if(v1 > v2) return -1;if(v2 > v1) return 1;return 0;
}//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template<>
int compare(const char* const &v1,const char* const &v2)
{return strcmp(p1,p2);
}
此处如果是compare(3,5),则调用普通的模板,若为compare(“hi”,”haha”)则调用特例化版本(因为这个cosnt char*相对于T,更匹配实参类型),注意,二者函数体的语句不一样了,实现不同功能。
(2)类模板的部分特例化:不必为所有模板参数提供实参,可以指定一部分而非所有模板参数,一个类模板的部分特例化本身仍是一个模板,使用它时还必须为其特例化版本中未指定的模板参数提供实参。此功能就用于STL源码剖析中的traits编程。详见C++primer 628页的例子。(特例化时类名一定要和原来的模板相同,只是参数类型不同,按最佳匹配原则,那个最匹配,就用相应的模板)
template<typename T>class Foo
{void Bar();void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{//进行int类型的特例化处理
}Foo<string> fs;
Foo<int> fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo<string>::Bar()
fi.Bar();//特例化版本,执行Foo<int>::Bar()
//Foo<string>::Bar()和Foo<int>::Bar()功能不同
16. C++的STL源码(这个系列也很重要,建议侯捷老师的STL源码剖析书籍与视频),其中包括内存池机制,各种容器的底层实现机制,算法的实现原理等)
17. STL源码中的hashtable的实现
hash_table是STL中hash_map 和 hash_set 的内部数据结构,hash_table的插入/删除/查找的时间复杂度都为O(1),是查找速度最快的一种数据结构,但是hash_table中的数据是无序的,一般也只有在数据不需要排序,只需要满足快速查找/插入/删除的时候使用hash_table。
18. STL中unordered_map和map的区别和应用场景
map是一种映射,这种映射是有序的,底层是使用红黑树来完成的,数据通过键值才存储,键是唯一的。
unordered_map,是一种无序的,底层是通过hash表来完成的。unordered库使用“桶”来存储元素,散列值相同的被存储在一个桶里。当散列容器中有大量数据时,同一个桶里的数据也会增多,造成访问冲突,降低性能。为了提高散列容器的性能,unordered库会在插入元素是自动增加桶的数量,不需要用户指定。每个桶都是用list来完成的。
(1)map
优点:
- 有序性: 其元素的有序性再很多应用中都会简化很多操作。
- 红黑树: 内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率很高。
缺点:
- 空间占用率高,每一个节点都需要额外保存父节点,孩子节点以及红黑性质,使得每一个节点都会占用大量的空间。
适用于:
- 对顺序有要求的问题。
(2)unordered_map
- 优点: 由于使用了哈希表,因此查找速度非常快。
- 缺点: 哈希表的建立比较耗费时间
- 适用于: 查找问题
19. STL中vector的实现
(1).vector有备用空间,当备用空间不够的时候,会重新开辟原空间两倍的空间进行重写分配。
(2).vector支持随机的存取,但是最好是选择从末尾插入,因为从中间插入会导致元素的移动,带来了性能的开销。
20. STL容器的几种迭代器以及对应的容器(输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器)
顺序容器:vector,deque是随机访问迭代器;list是双向迭代器
容器适配器:stack,queue,priority_queue没有迭代器
关联容器:set,map,multiset,multimap是双向迭代器
unordered_set,unordered_map,unordered_multiset,unordered_multimap是前向迭代器
21. STL中的traits技法
https://www.cnblogs.com/zhuwbox/p/3698083.html STL中的Traits编程技法
22. vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。
vector压入容器的对象都是拷贝操作,而且vector的数据存放都是连续存储的,所以在操作vector操作时,应该尽量避免对尾部操作之后的地方插入删除操作,因为这样会造成元素的移动,造成大量的开销。
频繁对vector调用push_back()会导致性能下降,这是由于系统每次给vector分配固定大小的空间,这个空间可能比用户想分配的空间大一些,但是频繁的使用push_back向容器中插入元素,会导致内存分配空间不够,会再次将整个对象的存储空间重新分配,将旧的元素移动到新的空间中,开销是非常大的。
23. C++中的重载和重写的区别
- 重载:是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
- 重写:指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。
24. C++内存管理,内存池技术(热门问题),与csapp中几种内存分配方式对比学习加深理解
c++的内存管理延续c语言的内存管理,但是也增加了其他的,例如智能指针,除了常见的堆栈的内存管理之外,c++支持智能指针,智能指针的对象进行赋值拷贝等操作的时候,每个智能指针都有一个关联的计数器,该计数器记录共享该对象的指针个数,当最后一个指针被销毁的时候,计数器为0,会自动调用析构函数来销毁函数。
常见的内存管理错误有:
a>内存分配未成功却使用了它,如果所用的操作符不是类型安全的话,请使用assert(p != NULL)或者if(p != NULL)来判断。 b>内存分配成功但未初始化 c>内存分配成功并已初始化,但是操作超过了内存的边界 d>忘记释放内存,造成内存泄露,每申请一块内存必须保证它被释放,释放内存后立即将指针置为NULL
数组与指针的对比,数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。
(1)内存池简介
C/C++下内存管理是让几乎每一个程序员头疼的问题,分配足够的内存、追踪内存的分配、在不需要的时候释放内存——这个任务相当复杂。而直接使用系统调用malloc/free、new/delete进行内存分配和释放,有以下弊端:
调用malloc/new,系统需要根据“最先匹配”、“最优匹配”或其他算法在内存空闲块表中查找一块空闲内存,调用free/delete,系统可能需要合并空闲内存块,这些会产生额外开销
频繁使用时会产生大量内存碎片,从而降低程序运行效率
容易造成内存泄漏
(2)内存池的优点
内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
(3)内存池的分类
从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更加广泛。
从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。
(4)经典内存池的设计
经典内存池实现过程
a.先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象;
b.每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间;
c.某个内存节点一旦分配出去,从空闲内存节点链表中去除;
d.一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表;
e.如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。
经典内存池的实现过程大致如上面所述,其形象化的过程如下图所示:
如上图所示,申请的内存块存放三个可供分配的空闲节点。空闲节点由空闲节点链表管理,如果分配出去,将其从空闲节点链表删除,如果释放,将其重新插入到链表的头部。如果内存块中的空闲节点不够用,则重新申请内存块,申请的内存块由内存块链表来管理。
注意,本文涉及到的内存块链表和空闲内存节点链表的插入,为了省去遍历链表查找尾节点,便于操作,新节点的插入均是插入到链表的头部,而非尾部。当然也可以插入到尾部,读者可自行实现。
经典内存池数据结构设计
按照上面的过程设计,内存池类模板有这样几个成员。
两个指针变量:
内存块链表头指针:pMemBlockHeader;
空闲节点链表头指针:pFreeNodeHeader;
空闲节点结构体:
struct FreeNode
{FreeNode* pNext;char data[ObjectSize];
};
内存块结构体:struct MemBlock
{MemBlock *pNext;FreeNode data[NumofObjects];
};
经典内存池的实现
根据以上经典内存池的设计,编码实现如下
#include <iostream>
using namespace std;template<int ObjectSize, int NumofObjects = 20>
class MemPool
{
private://空闲节点结构体struct FreeNode{FreeNode* pNext;char data[ObjectSize];};//内存块结构体struct MemBlock{MemBlock* pNext;FreeNode data[NumofObjects];};FreeNode* freeNodeHeader;MemBlock* memBlockHeader;public:MemPool(){freeNodeHeader = NULL;memBlockHeader = NULL;}~MemPool(){MemBlock* ptr;while (memBlockHeader){ptr = memBlockHeader->pNext;delete memBlockHeader;memBlockHeader = ptr;}}void* malloc();void free(void*);
};//分配空闲的节点
template<int ObjectSize, int NumofObjects>
void* MemPool<ObjectSize, NumofObjects>::malloc()
{//无空闲节点,申请新内存块if (freeNodeHeader == NULL){MemBlock* newBlock = new MemBlock;newBlock->pNext = NULL;freeNodeHeader=&newBlock->data[0]; //设置内存块的第一个节点为空闲节点链表的首节点//将内存块的其它节点串起来for (int i = 1; i < NumofObjects; ++i){newBlock->data[i - 1].pNext = &newBlock->data[i];}newBlock->data[NumofObjects - 1].pNext=NULL;//首次申请内存块if (memBlockHeader == NULL){memBlockHeader = newBlock;}else{//将新内存块加入到内存块链表newBlock->pNext = memBlockHeader;memBlockHeader = newBlock;}}//返回空节点闲链表的第一个节点void* freeNode = freeNodeHeader;freeNodeHeader = freeNodeHeader->pNext;return freeNode;
}//释放已经分配的节点
template<int ObjectSize, int NumofObjects>
void MemPool<ObjectSize, NumofObjects>::free(void* p)
{FreeNode* pNode = (FreeNode*)p;pNode->pNext = freeNodeHeader; //将释放的节点插入空闲节点头部freeNodeHeader = pNode;
}class ActualClass
{static int count;int No;public:ActualClass(){No = count;count++;}void print(){cout << this << ": ";cout << "the " << No << "th object" << endl;}void* operator new(size_t size);void operator delete(void* p);
};//定义内存池对象
MemPool<sizeof(ActualClass), 2> mp;void* ActualClass::operator new(size_t size)
{return mp.malloc();
}void ActualClass::operator delete(void* p)
{mp.free(p);
}int ActualClass::count = 0;int main()
{ActualClass* p1 = new ActualClass;p1->print();ActualClass* p2 = new ActualClass;p2->print();delete p1;p1 = new ActualClass;p1->print();ActualClass* p3 = new ActualClass;p3->print();delete p1;delete p2;delete p3;
}
程序运行结果:
004AA214: the 0th object
004AA21C: the 1th object
004AA214: the 2th object
004AB1A4: the 3th object
程序分析
阅读以上程序,应注意以下几点。
(1)对一种特定的类对象而言,内存池中内存块的大小是固定的,内存节点的大小也是固定的。内存块在申请之初就被划分为多个内存节点,每个Node的大小为ItemSize。刚开始,所有的内存节点都是空闲的,被串成链表。
(2)成员指针变量memBlockHeader是用来把所有申请的内存块连接成一个内存块链表,以便通过它可以释放所有申请的内存。freeNodeHeader变量则是把所有空闲内存节点串成一个链表。freeNodeHeader为空则表明没有可用的空闲内存节点,必须申请新的内存块。
(3)申请空间的过程如下。在空闲内存节点链表非空的情况下,malloc过程只是从链表中取下空闲内存节点链表的头一个节点,然后把链表头指针移动到下一个节点上去。否则,意味着需要一个新的内存块。这个过程需要申请新的内存块切割成多个内存节点,并把它们串起来,内存池技术的主要开销就在这里。
(4)释放对象的过程就是把被释放的内存节点重新插入到内存节点链表的开头。最后被释放的节点就是下一个即将被分配的节点。
(5)内存池技术申请/释放内存的速度很快,其内存分配过程多数情况下复杂度为O(1),主要开销在freeNodeHeader为空时需要生成新的内存块。内存节点释放过程复杂度为O(1)。
(6) 在上面的程序中,指针p1和p2连续两次申请空间,它们代表的地址之间的差值为8,正好为一个内存节点的大小(sizeof(FreeNode))。指针p1所指向的对象被释放后,再次申请空间,得到的地址与刚刚释放的地址正好相同。指针p3多代表的地址与前两个对象的地址相聚很远,原因是第一个内存块中的空闲内存节点已经分配完了,p3指向的对象位于第二个内存块中。
以上内存池方案并不完美,比如,只能单个单个申请对象空间,不能申请对象数组,内存池中内存块的个数只能增大不能减少,未考虑多线程安全等问题。现在,已经有很多改进的方案,请读者自行查阅相关资料。
注:与深入理解计算机系统(csapp)中几种内存分配方式对比学习加深理解
25. 介绍面向对象的三大特性,并且举例说明每一个
面向对象的三大特性:封装、继承、多态。
(1)封装:将很多有相似特性的内容封装在一个类中,例如学生的成绩学号、课程这些可以封装在同一个类中;
(2)继承:某些相似的特性,可以从一个类继承到另一个类,类似生活中的继承,例如有个所有的汽车都有4个轮子,那么我们在父类中定义4个轮子,通过继承获得4个轮子的功能,不用再类里面再去定义这4个轮子的功能。
(3)多态:多态指的相同的功能,不同的状态,多态在面向对象c++里面是通过重载和覆盖来完成的,覆盖在c++里面通过虚函数来完成的。例如鸭子的例子,所有的鸭子都有颜色,我们可以将这个颜色设置成为一个虚函数,通过继承子类对虚函数进行覆盖,不同子类中有各自的颜色,也就是有各自不同的鸭子颜色,这就是多态的典型表现之一。
26. C++多态的实现
多态用虚函数来实现,结合动态绑定。
引用/指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
27. C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(包括单一继承,多重继承等)(拓展问题:为什么基类指针指向派生类对象时可以调用派生类成员函数,基类的虚函数存放在内存的什么区,虚函数表指针vptr的初始化时间)
存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
注意 :
①每个虚表后面都有一个‘0’,它类似字符串的‘\0’,用来标识虚函数表的结尾。结束标识在不同的编译器下可能会有所不同。
②不难发现虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
多态实现利用到了虚函数表(虚表V-table)。它是一块虚函数的地址表,通过一块连续内存来存储虚函数的地址。这张表解决了继承、虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数函数。
为什么基类指针指向派生类对象时可以调用派生类成员函数
虚表指针一般放在首地址,如果父类有虚函数表,子类必定有;因为构造子类时先构造父类,所以使用父类的指针,编译器根据指针类型就能知道偏移多少就能找到父类的成员(包括虚函数指针),但是对于子类独有的成员,父类的指针无法提供偏移量,因此找不到。
基类的虚函数存放在内存的什么区
全局数据区(静态区)
虚表指针vptr的初始化时间
所有基类构造函数之后,但又在自身构造函数或初始化列表之前
编译器处理虚函数的方法是:
编译器为每个包含虚函数的类创建一个表,在表中编译器放置特定类的虚函数地址,在每个带有虚函数的类中,编译器为每个类对象放置一个指针(为每个类添加一个隐藏的成员),指向虚表。通过基类的指针或引用做虚函数调用时,编译器静态插入取得该指针,并在虚表中找到函数地址。注意基类和派生类的虚函数表是俩个东西,保存在不同的空间,但这俩个东西的内容可能一样。
虚函数实现原理(包括单一继承,多重继承等):虚函数表+虚表指针
每个虚函数都会有一个与之对应的虚函数表,该虚函数表的实质是一个指针数组,存放的是每一个对象的虚函数入口地址。对于一个派生类来说,他会继承基类的虚函数表同时增加自己的虚函数入口地址,如果派生类重写了基类的虚函数的话,那么继承过来的虚函数入口地址将被派生类的重写虚函数入口地址替代。那么在程序运行时会发生动态绑定,将父类指针绑定到实例化的对象实现多态。
单继承环境下的虚函数
假设存在下面的两个类Base和A,A类继承自Base类:
class Base
{
public:// 虚函数func1virtual void func1() { cout << "Base::func1()" << endl; }// 虚函数func2virtual void func2() { cout << "Base::func2()" << endl; }// 虚函数func3virtual void func3() { cout << "Base::func3()" << endl; }int a;
};class A : public Base
{
public:// 重写父类虚函数func1void func1() { cout << "A::func1()" << endl; }void func2() { cout << "A::func2()" << endl; }// 新增虚函数func4virtual void func4() { cout << "A::func3()" << endl; }
};
利用Visual Studio提供的命令行工具查看一下这两个类的内存布局。
类Base的内存布局图:
类A的内存布局图:
通过两幅图片的对比,我们可以看到:
在单继承中,A类覆盖了Base类中的同名虚函数,在虚函数表中体现为对应位置被A类中的新函数替换,而没有被覆盖的函数则没有发生变化。
对于子类自己的虚函数,直接添加到虚函数表后面。
另外,我们注意到,类A和类Base中都只有一个vfptr指针,前面我们说过,该指针指向虚函数表,我们分别输出类A和类Base的vfptr:
int main()
{typedef void(*pFunc)(void);cout << "virtual function testing:" << endl;Base b;cout << "Base虚函数表地址:" << (int *)(&b) << endl;A a;cout << "A类虚函数表地址:" << (int *)(&a) << endl;
}
输出信息如下:
我们可以看到,类A和类B分别拥有自己的虚函数表指针vptr和虚函数表vtbl。到这里,你是否已经明白为什么指向子类实例的基类指针可以调用子类(虚)函数?每一个实例对象中都存在一个vptr指针,编译器会先取出vptr的值,这个值就是虚函数表vtbl的地址,再根据这个值来到vtbl中调用目标函数。所以,只要vptr不同,指向的虚函数表vtbl就不同,而不同的虚函数表中存放着对应类的虚函数地址,这样就实现了多态的”效果“。
最后,我们用一幅图来表示单继承下的虚函数实现:
多继承环境下的虚函数
假设存在下面这样的四个类:
class Base
{
public:// 虚函数func1virtual void func1() { cout << "Base::func1()" << endl; }// 虚函数func2virtual void func2() { cout << "Base::func2()" << endl; }// 虚函数func3virtual void func3() { cout << "Base::func3()" << endl; }
};class A : public Base
{
public:// 重写父类虚函数func1void func1() { cout << "A::func1()" << endl; }void func2() { cout << "A::func2()" << endl; }
};class B : public Base
{
public:void func1() { cout << "B::func1()" << endl; }void func2() { cout << "B::func2()" << endl; }
};class C : public A, public B
{
public:void func1() { cout << "D::func1()" << endl; }void func2() { cout << "D::func2()" << endl; }
};
类A和类B分别继承自类Base,类C继承了类B和类A,我们查看一下类C的内存布局:
可以看到,类C中拥有两个虚函数表指针vptr。类C中覆盖了类A的两个同名函数,在虚函数表中体现为对应位置替换为C中新函数;类C中覆盖了类B中的两个同名函数,在虚函数表中体现为对应位置替换为C中新函数(注意,这里使用跳转语句,而不是重复定义)。
类C的内存布局可以归纳为下图:
多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。这些表按照派生的顺序依次排列,如果子类改写了父类的虚函数,那么就会用子类自己的虚函数覆盖虚函数表的相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。
28. C++中类的数据成员和成员函数内存分布情况
C++类成员所占内存总结:
(1)空类所占字节数为1
(2)类中的成员函数不占内存空间,虚函数除外;如果父类中如果有一个虚函数,则类所字节发生变化,如果是32位编译器,则占内存4个字节;如果是64位编译器,则占内存8个字节;
(3)和结构体一样,类中自身带有四字节对齐功能
(4)类中的static静态成员变量不占内存,静态成员变量存储在静态区
https://www.cnblogs.com/hnfxs/p/5395015.html C++类内存布局图(成员函数和成员变量分开讨论)
29. this指针
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间。按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分配存储单元,如下图所示。
我们可以看出这样不仅麻烦而且特别浪费空间,因此经过分析我们可以知道是按以下方式来储存的。
只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。如下图所示。
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间。
那么问题来了在不同对象但是调用的的代码又相同的情况下,编译器是如何分辨且准确的调用到各自的函数???
在c++中专门设立了一个this指针,用来指向不同的对象,当调用对象t1的成员函数display1时,this指针就指向display1,当调用t2的成员函数display2,this指针就指向display2。。。。。以此类推来分辨准确的调用
30. 析构函数一般写成虚函数的原因
#include <iostream>
using namespace std;
class Person
{
public:
virtual ~Person() //加了virtual,讲析构函数声明为虚函数
{cout << "Person::~Person()" << endl;
}
};class Student : public Person
{
public:
~Student() // virtual可加可不加
{cout << "Student::~Student()" << endl;
}
};int main()
{
Person *pt1 = new Person;
Person *pt2 = new Student; // 用基类的指针指向子类
// Student *pt3 = new Person; // 不能用子类指针指向基类,错误!
Student *pt4 = new Student;delete pt1;
cout << "*********" << endl;
delete pt2;
cout << "*********" << endl;
//delete pt3;
//cout << "*********" << endl;
delete pt4;
cout << "*********" << endl;
return 0;
}
-
运行结果: Person::~Person()
Student::~Student()
Person::~Person()
Student::~Student()
Person::~Person()
如果在基类中析构函数不加virtual,结果为:
Person::~Person()
Person::~Person()
Student::~Student()
Person::~Person()
可以看出:只有在用基类的指针指向派生类的时候虚函数发挥了动态的作用。
析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。
如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用delete销毁对象时,只调用了基类的析构函数,未调用派生类的析构函数。这样会造成销毁对象不完全,容易造成内存泄露。
31. 构造函数、拷贝构造函数和赋值操作符的区别
对象不存在,且没用别的对象来初始化,就是调用了构造函数;
对象不存在,且用别的对象来初始化,就是拷贝构造函数(上面说了三种用它的情况!)
对象存在,用别的对象来给它赋值,就是赋值函数。
32. 构造函数声明为explicit
C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。
- explicit构造函数是用来防止隐式转换的。
- 关键字explicit只对一个实参的构造函数有效
- 需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit的
- 只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复
33. 构造函数为什么一般不定义为虚函数
-
从C++之父Bjarne的回答我们应该知道C++为什么不支持构造函数是虚函数了,简单讲就是没有意义。虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用。
-
虚函数相应一个指向vtable虚函数表的指针,但是这个指向vtable的指针事实上是存储在对象的内存空间的。假设构造函数是虚的,就须要通过vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
34. 构造函数的几种关键字(default delete 0)
35. 构造函数或者析构函数中调用虚函数会怎样
总的来说,构造函数和析构函数调用虚函数并不能达到多态的效果,因为在析构和构造过程中,该对象变为一个基类对象,调用的方法都是基类的方法。
- 构造函数:在基类的构造过程中,虚函数调用从不会被传递到派生类中。代之的是,派生类对象表现出来的行为好象其本身就是基类型。不规范地说,在基类的构造过程中,虚函数并没有被"构造"。简单的说就是,在子类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的。
- 析构函数:一旦一个派生类的析构器运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。
36. 纯虚函数
纯虚函数不需要定义,我们不能够为纯虚函数提供函数体,同样的,包含纯虚函数的基类是抽象基类,抽象基类是不能创建对象的,只能通过继承,继承子类中覆盖纯虚函数,执行自己的功能,子类是可以创建对象的。
37. 静态类型和动态类型,静态绑定和动态绑定的介绍
静态类型和动态类型:
-
对象的静态类型:
对象在声明是采用的类型,在编译期确定; -
对象的动态类型:
当前对象所指的类型,在运行期决定,对象的动态类型可以更改,但静态类型无法更改。
静态绑定和动态绑定:
-
静态绑定:
绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。 -
动态绑定:
绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。
38. 引用是否能实现动态绑定,为什么引用可以实现
可以实现,因为动态绑定是发生在程序运行阶段的,c++中动态绑定是通过对基类的引用或者指针调用虚函数时发生。
因为引用或者指针的对象是可以在编译的时候不确定的,如果是直接传对象的话,在程序编译的阶段就会完成,对于引用,其实就是地址,在编译的时候可以不绑定对象,在实际运行的时候,在通过虚函数绑定对象即可。
39. 深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
深拷贝就是拷贝内容,浅拷贝就是拷贝指针。
浅拷贝拷贝指针,也就是说同一个对象,拷贝了两个指针,指向了同一个对象,那么当销毁的时候,可能两个指针销毁,就会导致内存泄漏的问题。
深拷贝不存在这个问题,因为是首先申请和拷贝数据一样大的内存空间,把数据复制过去。这样拷贝多少次,就有多少个不同的内存空间,干扰不到对方。
40. 对象复用的了解,零拷贝的了解
对象复用指得是设计模式,对象可以采用不同的设计模式达到复用的目的,最常见的就是继承和组合模式了。
零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。
零拷贝技术常见linux中,例如用户空间到内核空间的拷贝,这个是没有必要的,我们可以采用零拷贝技术,这个技术就是通过mmap,直接将内核空间的数据通过映射的方法映射到用户空间上,即物理上共用这段数据。
零拷贝介绍https://www.jianshu.com/p/fad3339e3448
41. 介绍C++所有的构造函数
默认构造函数、一般构造函数、拷贝构造函数
(1)默认构造函数(无参数):如果创建一个类你没有写任何构造函数,则系统会自动生成默认的构造函数,或者写了一个不带任何形参的构造函数。
(2)一般构造函数:一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)。
(3)拷贝构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中。参数(对象的引用)是不可变的(const类型)。此函数经常用在函数调用时用户定义类型的值传递及返回。
42. 什么情况下会调用拷贝构造函数(三种情况)
(1)用类的一个对象去初始化另一个对象时
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或引用时
43. 结构体内存对齐方式和为什么要进行内存对齐?
(1).前面的地址必须是后面的地址正数倍,不是就补齐
(2).整个Struct的地址必须是最大字节的整数倍
(3)为什么要?
- 空间换时间,加快cpu访问内存的效率,这是因为许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2、4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间接口的硬件设计
44. 内存泄露的定义,如何检测与避免?
(1)首先说到c++内存泄漏时要知道它的含义?
内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
(2)内存泄漏的后果?
最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。
(3)对于C和C++这种没有垃圾回收机制的语言来讲,我们主要关注两种类型的内存泄漏:
堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
(4)使用C/C++语言开发的软件在运行时,出现内存泄漏。可以使用以下两种方式,进行检查排除:
使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。
调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
(5)解决内存泄漏最有效的办法就是使用智能指针(Smart Pointer)。
使用智能指针就不用担心这个问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。
45. 手写智能指针的实现(shared_ptr和weak_ptr实现的区别)
shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。
shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。
weak_ptr用于解决“引用计数”模型循环依赖问题,weak_ptr指向一个对象,并不增减该对象的引用计数器
46. 智能指针的循环引用
C++11中引入了三种智能指针,分别是shared_ptr、weak_ptr和unique_ptr
智能指针的作用
智能指针可以帮助我们管理动态分配的堆内存,减少内存泄漏的可能性
手动管理堆内存有引起内存泄漏的可能,比如这段代码
try {int* p = new int;// Do somethingdelete p;
} catch(...) {// Catch exception
}
如果在执行Do something的时候发生了异常,那么程序就会直接跳到catch语句捕获异常,delete p这句代码不会被执行,发生了内存泄漏
我们把上面的程序改成
try {shared_ptr<int> p(new int);// Do something
} catch(...) {// Catch exception
}
当执行Do something的时候发生了异常,那么try块中的栈对象都会被析构。因此代码中p的析构函数会被调用,引用计数从1变成0,通过new分配的堆内存被释放,这样就避免了内存泄漏的问题
(1)循环引用问题
虽然智能指针会减少内存泄漏的可能性,但是如果使用智能指针的方式不对,一样会造成内存泄漏。比较典型的情况是循环引用问题,比如这段代码
class B; // 前置声明
class A {
public:shared_ptr<B> ptr;
};class B {
public:shared_ptr<A> ptr;
};int main()
{while(true) {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa -> ptr = pb;pb -> ptr = pa;}return 0;
}
这个程序中智能指针的引用情况如下图
上图中,class A和class B的对象各自被两个智能指针管理,也就是A object和B object引用计数都为2,为什么是2?
分析class A对象的引用情况,该对象被main函数中的pa和class B对象中的ptr管理,因此A object引用计数是2,B object同理。
在这种情况下,在main函数中一个while循环结束的时候,pa和pb的析构函数被调用,但是class A对象和class B对象仍然被一个智能指针管理,A object和B object引用计数变成1,于是这两个对象的内存无法被释放,造成内存泄漏,如下图所示
(2)解决方法
解决方法很简单,把class A或者class B中的shared_ptr改成weak_ptr即可,由于weak_ptr不会增加shared_ptr的引用计数,所以A object和B object中有一个的引用计数为1,在pa和pb析构时,会正确地释放掉内存
(3)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。
使用 weak_ptr 解决 shared_ptr 因循环引有不能释放资源的问题
- 使用 shared_ptr 时, shared_ptr 为强引用, 如果存在循环引用, 将导致内存泄露. 而 weak_ptr 为弱引用, 可以避免此问题, 其原理:
- 对于弱引用来说, 当引用的对象活着的时候弱引用不一定存在. 仅仅是当它存在的时候的一个引用, 弱引用并不修改该对象的引用计数, 这意味这弱引用它并不对对象的内存进行管理.
- weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。
注意: 虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出现了循环引用, 还是会造成内存泄漏.
47. 遇到coredump要怎么调试
内存泄漏的方法很多,可以用gdb打开core文件,确定出错的堆栈地点,从而判断程序出错的位置。
eg:
core dump又叫核心转储。当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump。
(1)ulimit -c unlimited命令设置coredump文件
(2)gdb a.out core命令运行程序(linux下)
(3)使用bt命令查看堆栈
48. 内存检查工具的了解
linux可以使用开源的Valgrind工具包,包含多个工具:Memcheck常用语检测malloc和new这类的问题,callgrind用来检查函数调用,cachegrind缓存使用,helgrind多线程程序中的竞争。除了valgrind还可以用mtrace这些工具
49. 模板的用法与适用场景
模板是C11里面添加的,使用与在不知道类型的情况下,编写一个泛型的程序,模板通过用一个指定的关键字来代替类型,进行泛型编程。
应用场景:应用场景很多,例如我们要编程一些和类型无关的代码时,STL里面的很多容器都是用到了模板,容器的功能都可以使用,但并没有确定容器里面一定要用指定的类型,可以是任何的类型。
50. 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?
成员初始化列表:
- 在类构造函数中,不在函数体内对变量赋值,而在参数列表后,跟一个冒号和初始化列表。
- 初始化和赋值对内置类型的成员没有什么大的区别,像上面的人一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。
但是有时候必须使用带初始化列表的构造函数:
- 成员类型是没有默认构造函数的类。若没有提供显示初始化,则类创建对象时会调用默认构造函数,如果没有默认构造函数,则必须显示初始化。
- const成员或者引用类型的成员。因为const对象或者引用类型只能初始化,不能赋值。 子类初始化父类的私有成员
为什么成员初始化列表效率更高?
- 因为对于非内置类型,少了一次调用默认构造函数的过程。
类对象的构造顺序是这样的:
(1)分配内存,调用构造函数时,隐式/显示的初始化各数据成员;
(2)进入构造函数后在构造函数中执行一般赋值与计算。
类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。
注意:构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。
51. 用过C++ 11吗,知道C++ 11哪些新特性?
例如:decltype,lambda表达式,智能指针, forward_list,tuple,正则表达式库,随机数
nullptr 专门代表空指针
auto 自动进行类型推导
引入了基于范围的迭代写法for(auto &i : arr)
初始化列表
引入了外部模板,能够显式的告诉编译器何时进行模板的实例化
可以指定模板的默认参数
引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数
提供了一个匿名函数的特性
52. C++的调用惯例(简单一点C++函数调用的压栈过程)
对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈
代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写
数据段:保存初始化的全局变量和静态变量,可读可写不可执行
BSS:未初始化的全局变量和静态变量
堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行
栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行
程序开始,从main开始,首先将参数压入栈,然后压入函数返回地址,进行函数调用,通过跳转指定进入函数,将函数内部的变量去堆栈上开辟空间,执行函数功能,执行完成,取回函数返回地址,进行下一个函数。
53. C++的四种强制转换
四种强制转换是static_cast、dynamic_cast、const_cast、reinterpret_cast。
static_cast:静态强制转换,类似传统c语言里面括号的强制转换
dynamic_cast:动态强制转换,主要应用于多态,父子类的类型转换,dynamic_cast和static_cast不同的是,它会检查类型转换是否正确,不能转换,则会返回null,所以不算是强制转换。
const_cast:取出const属性,比较简单,可以把const类型转换为非conse指针类型。
reinterpret_cast:一种非常随意的二进制转换,简单理解对一个二进制序列的重新解释。
54. C++中将临时变量作为返回值的时候的处理过程(栈上的内存分配、拷贝过程)
对于所调用的函数里面的临时变量,在函数调用过程中是被压到程序进程的栈中的,当函数退出时,临时变量出栈,即临时变量已经被销毁,临时变量占用的内存空间没有被清空,但是已经可以被分配给其他变量了,所以有可能在函数退出时,该内存已经被修改了,对于临时变量来说已经是没有意义的值了。
在C中,16bit程序中,返回值保存在ax寄存器中,32bit程序中,返回值保持在eax寄存器中,如果是64bit返回值,edx寄存器保存高32bit,eax寄存器保存低32bit。
综上,函数是可以将临时变量的值作为返回值的。
但是将一个指向局部变量的指针作为函数的返回值是有问题的。
由于指针指向局部变量,因此在函数返回时,临时变量被销毁,指针指向一块无意义的地址空间,所以一般不会有返回值。
如果得到正常的值,只能是幸运的,因为退出函数的时候,系统只是修改了栈顶的指针,并没有清内存;所以,是有可能正常访问到局部变量的内存的。但因为栈是系统自动管理的,所以该内存可能会可以被分配给其他函数,这样,该内存的内容就会被覆盖,不再是原来的值了。
常规程序中,函数返回的指针(函数指针,数组指针,结构体指针,联合体指针等)通常应该是:
(1)指向静态(static)变量;
(2)指向专门申请分配的(如用malloc)空间;
(3)指向常量区(如指向字符串"hello");
(4)指向全局变量;
(5)指向程序代码区(如指向函数的指针)。
除这5项以外,其它怪技巧不提倡。
函数内的变量,没有关键字static修饰的变量的生命周期只在本函数内,函数结束后变量自动销毁。当返回为指针的时候需要特别注意,因为函数结束后指针所指向的地址依然存在,但是该地址可以被其他程序修改,里面的内容就不确定了,有可能后面的操作会继续用到这块地址,有可能不会用到,所以会出现时对时错的情况,如果需要返回一个指针而又不出错的话只能调用内存申请函数
对于结构体和联合体来说,在作为函数的参数和返回值时,表现与C语言的内置类型(int,float, char等)是一样的,当为临时变量的时候,作为返回值时有效的。这个也是与指针不同的地方,所以一定要区分好,总是为当返回结构体或者联合体的时候,该怎么处理,原来直接返回就可以了…
55. C++的异常处理
https://www.cnblogs.com/nbk-zyc/p/12449331.html c++中的异常处理
http://c.biancheng.net/view/422.html C++异常处理(try catch throw)完全攻略
56. volatile关键字
禁止编译器优化,每次从内存中读取数据,有利于线程安全。
volatile用在如下的几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加volatile;
- 多任务环境下各任务间共享的标志应该加volatile;
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
57. 优化程序的几种方法
转载自:http://www.708luo.com/?p=36
冗余的变量拷贝
相对C而言,写C++代码经常一不小心就会引入一些临时变量,比如函数实参、函数返回值。在临时变量之外,也会有其他一些情况会带来一些冗余的变量拷贝。
之前针对冗余的变量拷贝问题写过一些帖子,详情请点击这里。
多重过滤
很多服务都会过滤的部分结果的需求,比如游戏交谈中过滤需要过滤掉敏感词。假设现在有两个过滤词典,一个词典A内容较少,另一个词典B内容较多,现在有1000个词需要验证合法性。
词落在词典A中的概率是1%,落在词典B中的概率是10%,而判断词是否落在词典A或B中的操作耗时差不多,记作N。
那么要判断词是否合法,有两种方式:
-
先判断词是否在A中,如果在返回非法;如果不在再判断是否在B中,如果在返回非法,否则返回合法。
-
和方式一类似,不过是先判断是否在B中。
现在我们来计算两种方式的耗时:
-
1000N+1000(1-1%)*N
-
1000N+1000(1-10%)*N
很明显,方式二的过滤操作排序优化方式一。
说得有些啰嗦,其实简单点说就是一句话:多重过滤中把强过滤前移;过滤强度差不多时,过滤消耗较小的前移。
如果有些过滤条件较强,但是过滤消耗也较大怎么办?该前移还是后移?个人到没遇到过这种情况,如果确实需要考虑,也可以用之前计算方式一、二整体耗时的方法也计算一遍。
字符数组的初始化
一些情况是:写代码时,很多人为了省事或者说安全起见,每次申请一段内存之后都先全部初始化为0。
另一些情况是:用了一些API,不了解底层实现,把申请的内存全部初始化为0了,比如char buf[1024]=""的方式,有篇帖子写得比较细,请看这里。
上面提到两种内存初始化为0的情况,其实有些时候并不是必须的。比如把char型数组作为string使用的时候只需要初始化第一个元素为0即可,或者把char型数组作为一个buffer使用的大部分时候根本不需要初始化。
频繁的内存申请、释放操作
曾经遇到过一个性能问题是:一个服务在启动了4-5小时之后,性能突然下降。
查看系统状态发现,这时候CPU的sys态比较高,同时又发现系统的minflt值迅速增加,于是怀疑是内存的申请、释放造成的性能下降。
最后定位到是服务的处理线程中,在处理请求时有大量申请和释放内存的操作。定位到原因之后就好办了,直接把临时申请的内存改为线程变量,性能一下子回升了。
能够迅速的怀疑到是临时的内存申请造成的性能下降,还亏之前看过这篇帖子。
至于为什么是4-5小时之后,性能突然下降,则怀疑是内存碎片的问题。
提前计算
这里需要提到的有两类问题:
-
局部的冗余计算:循环体内的计算提到循环体之前
-
全局的冗余计算
问题1很简单,大部分人应该都接触到过。有人会问编译器不是对此有对应的优化措施么?对,公共子表达式优化是可以解决一些这个问题。不过实测发现如果循环体内是调用的某个函数,即使这个函数是没有side effect的,编译器也无法针对这种情况进行优化。(我是用gcc 3.4.5测试的,不排除更高版本的gcc或者其他编译器可以针对这种情况进行优化)
对于问题2,我遇到的情况是:服务代码中定义了一个const变量,假设叫做MAX_X,处理请求是,会计算一个pow(MAX_X)用作过滤阈值,而性能分析发现,这个pow操作占了整体系统CPU占用的10%左右。对于这个问题,我的优化方式很简单,直接计算定义一个MAX_X_POW变量用作过滤即可。代码修改2行,性能提升10%。
空间换时间
这其实是老生常谈、在大学里就经常提到的问题了。
不过第一次深有体会的应用却是在前段时间刚遇到。简单来说是这样一个应用场景:系统内有一份词表和一份非法词表,原来的处理逻辑是根据请求中的数据查找到对应的词(很多),然后用非法词表过滤掉其中非法的部分。对系统做性能分析发现,依次判断查找出来的词是否在非法词表中的操作比较耗性能,能占整体系统消耗CPU的15-20%。后来的优化手段其实也不复杂,就是服务启动加载词表和非法词表的时候,再生成一张合法词表,请求再来的时候,直接在合法词表中查到结果即可。不直接用合法词表代替原来那份总的词表的原因是,总的词表还是其他用途。
内联频繁调用的短小函数
很多人知道这个问题,但是有时候会不太关注,个人揣测可能的原因有:
-
编译器会内联小函数
-
觉得函数调用的消耗也不是特别大
针对1,我的看法是,即使编译器会内联小函数,如果把函数定义写在cpp文件中并在另外一个cpp中调用该函数,这时编译器无法内联该调用。
针对2,我的实际经验是,内联了一个每个请求调用几百次的get操作之后,响应时间减少5%左右。
位运算代替乘除法
据说如果是常量的运算的话,编译器会自动优化选择最优的计算方式。这里的常量计算不仅仅是指"48"这样的操作,也可能是"ab"但编译的时候编译器已经可以知道a和b的值。
不过在编译阶段无法知道变量值的时候,将*、/、% 2的幂的运算改为位运算,对性能有时还是蛮有帮助的。
我遇到的一次优化经历是,将每个请求都会调用几十到数百次不等的函数中一个*8改为<<3和一个%8改为&7之后,服务器的响应时间减少了5%左右。
下面是我实测的一些数据:
%2的次方可以用位运算代替,a%8=a&7(两倍多效率提升)
/2的次方可以用移位运算代替,a/8=a>>3(两倍多效率提升)
2的次方可以用移位运算代替,a8=a<<3(小数值测试效率不明显,大数值1.5倍效率)
整数次方不要用pow,ii比pow(i,2)快8倍,ii*i比pow快40倍
strncpy, snprintf效率对比:目标串>>源串 strncpy效率低,源串>>目标串 snprintf效率低
编译优化
gcc编译的时候,很多服务都是采用O2的优化选项了。不过在使用公共库的时候,可能没注意到就使用了一个没开任何优化的产出了。我就遇到过至少3个服务因为打开了tcmalloc库的O2选项之后性能提升有10%以上的。
不过开O2优化,有些时候可能会遇到一些非预期的结果,比如这篇帖子提到的memory aliasing的问题。
58. public,protected和private访问权限和继承
(1) 访问权限
- public 这类型成员可以被类本身函数访问,也可以被外部创建的类对象调用。子类对象与子类内部可以访问
- protected类型成员,只能被类本身函数访问。外部创建的类对象没有访问权限。子类对象没有访问权限,子类内部可以访问
- private类型成员,只能被类本身函数访问,外部创建的类对象没有访问权限。子类对象和子类内部都没有访问权限
(2)继承关系的访问控制
- public继承,public继承使子类顺延父类的访问控制属性,即成员保持父类的控制属性,这样在子类中的成员访问控制同父类的一样
- protected继承,将父类public和protected属性的成员属性顺延到子类来后变成protected属性。protected属性是可以提供给子类在内部访问的。
- private继承。这种继承方式中断了后续子类对当前类的父类的所有访问权限,在该种继承方式下,会将父类public和protected属性顺延成private属性。这样,即使后面子类再次继承,都没有了对当前父类的成员的访问权限。
60. decltype()和auto
auto
1.编译器通过分析表达式的类型来确定变量的类型,所以auto定义的变量必须有初始值。
auto i=10; //ok,i为整型auto j; //error,定义时必须初始化。
j=2;
2.auto可以在一条语句中声明多个变量,但该语句中所有变量的初始值类型必须有一样。
auto i=0,*P=&i; //ok,i是整数,p是整型指针
auto a=2,b=3.14; //error,a和b类型不一致
3.auto会忽略掉顶层const,同时底层const则会保留下来
const int a=2,&b=a;
auto c=a; //c是int 型,而不是const int,即忽略了顶层const
auto d=&a; //d是一个指向const int 的指针,即保留了底层const
如果希望auto类型是一个顶层const ,需要明确指出:
const auto e=a; //e是const int 类型
4.当使用数组作为auto变量的初始值时,推断得到的变量类型是指针,而非数组
int a[10]={1,2,3,4,5,6,7,8,9,0}
auto b=a; //b是int *类型,指向数组的第一个元素int c[2][3]={1}
auto d=c; //d是int(*d)[3]类型的数组指针
for(auto e:c) //e是int*类型,而不是int(*)[3]
for(auto &f:c) //f是int(&f)[3]
//**************************************************
decltype (a) c; //c是由10个整型数构成的数组,c[10]
decltype
decltype和auto功能类型,但略有区别:
1.decltype根据表达式类型确定变量类型,但不要求定义时进行初始化
int a=2;
decltype (a) b; //b是int类型
b=3;
int &c=a;
decltype (c) d=a; //d为int &类型,因此定义时必须初始化
2.解引用指针操作将得到引用类型
int a=2,*b=a;
decltype (*b) c=a; //解引用,c是int &类型,因此必须初始化
3.decltype所用的表达式加()得到的是该类型的引用
int a=2;
decltype ((a)) b=a; //b是int&类型,而不是int类型,必须初始化
decltype (a) c; //c是int类型
4.decltype所用变量时数组时,得到的同类型的数组,而不是指针
int a[2]={1,2}
decltype (a) b={3,4} //int b[2]类型
5.decltype所用变量是函数时,得到的是函数类型,而不是函数指针
int fun(int a);
decltype(fun) *f(); //函数f返回的是 int(*)(int),即函数指针,而decltype(fun)是int(int)类型
61. inline和宏定义的区别
(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;
62. C++和C的类型安全
类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域。绝对类型安全的编程语言暂时还没有。
C语言的类型安全
C只在局部上下文中表现出类型安全,比如试图从一种结构体的指针转换成另一种结构体的指针时,编译器将会报告错误,除非使用显式类型转换。然而,C中相当多的操作是不安全的。
如果C++使用得当,它将远比C更有类型安全性。相比于C,C++提供了一些新的机制保障类型安全:
(1)操作符new返回的指针类型严格与对象匹配,而不是void *;
(2)C中很多以void*为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
(3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换;
(4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全;
(5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。即便如此,C++也不是绝对类型安全的编程语言。如果使用不得当,同样无法保证类型安全。
63. 参考链接
https://blog.csdn.net/ask233/article/details/99713381
https://liyiye012.github.io/2018/09/21/C++%E9%9D%A2%E8%AF%95%E9%AB%98%E9%A2%91%E9%A2%98/
https://blog.csdn.net/weixin_43819197/article/details/94407751
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- JQuery腾讯微博
JQuery部分 <script src="../../Scripts/jquery-1.11.3.js"></script><script type="text/javascript">$(function () {function textarea_txt() {if ($("#msgTxt").val().length > 140) {$(".countTxt").html(&…...
2024/4/30 18:01:58 - 汇总:不同格式文件怎么转换成PDF?
我们都知道目前来说PDF算是最稳定阅读也简单方便的一种文档格式,因而我们经常会将其他不同的文件转换成PDF,下面就分别说一说如何将常用办公文件格式转PDF的方法。 1、Office转PDF 日常使用最多的就是Word、Excel、PPT这些office办公文档格式了,转换PDF的方法其实也非常简单…...
2024/4/29 7:48:44 - 第四章——不确定情况下的最佳检测(非相干检测)
1. 背景 存在时延情况下的发送信号和信道模型接收到的带通信号的信号部分表达式:由于tdt_dtd很小,但是fctdf_ct_dfctd可能很大,因此只有sml(t−td)s_{ml}(t-t_d)sml(t−td)可以忽略其中的tdt_dtd项。 2.二进制信号的最佳相干检测 考虑传播时延和加性噪声得到接收…...
2024/4/29 7:48:32 - Hander
Handler作用:因为在Android中,主线程不建议做耗时的操作,子线程不建议更新UI,但是Android开发,其实就是搭建好页面,将服务器的数据展示到页面上,所以我网络请求使用会非常频繁,而网络请求属于耗时操作,需要放到子线程完成,但一般情况下也不会通过子线程更新UI,需要将…...
2024/4/30 23:02:42 - 红黑树(RBTree)
红黑树的概念 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。红黑树的性质每个结点不是红色就是黑色…...
2024/4/30 8:38:46 - TL6748-EVM开发板介绍
开发板简介基于TI TMS320C6748定点/浮点DSP C674x处理器,主频456MHz;集成uPP、EMIFA、SATA、USB 2.0 OTG等大数据接口,可与FPGA/CPLD配套使用;55mm*33mm,C6000系列DSP核心板,仅硬币大小;采用精密工业级B2B连接器,占用空间小,稳定性强,易插拔,防反插;通过高低温、振…...
2024/4/30 23:10:47 - Python语音合成指北(一)
文章来源: 饭饭的Python学习之路作者: 一粒米饭语音合成概述总的来说,语音合成是通过机械的、电子的方法产生人造语音的技术。其中TTS,是Text-To-Speech缩写,即“从文本到语音”,是人机对话的一部分。它属于语音合成,是将计算机自己产生的、或外部输入的文字信息转变为可…...
2024/4/29 7:48:16 - 如何入门 Python 爬虫?400集免费教程视频带你从0-1全面掌握
学习Python大致可以分为以下几个阶段:1.刚上手的时候肯定是先过一遍Python最基本的知识,比如说:变量、数据结构、语法等,基础过的很快,基本上1~2周时间就能过完了,我当时是在这儿看的基础:Python 简介 | 菜鸟教程2.看完基础后,就是做一些小项目巩固基础,比方说:做一个…...
2024/4/30 16:15:05 - 写csdn的初衷
先自我介绍下我是痴心,是一个在前端行业跌跌撞撞工作了两年的垃圾。为什么说自己是垃圾 ? 哈哈,因为自己是在是太菜了,在之前的工作中又没有养成整理问题的习惯。 只是当时解决了,现在回看的话,相信自己依然不会。有很多朋友跟我说,让我去写写csdn。当初我只是一笑而过,…...
2024/4/30 20:16:11 - 下载Android源码的正确姿势
Android源码阅读的好处有很多,可以加深我们对系统的了解,可以从根本上找出一些bug的原因,可以参考优雅的代码实现庆幸Android是开源的,所有的功能都可以看到实现。Android源码即AOSP(Android Open Source Project) ,有些人可能还不知道如何下载AOSP,先来看官网上的一段话…...
2024/4/29 2:47:22 - 前端学习随手记-vue框架
1.keyCode (获取敲击键盘的Unicode字符码) 对于 keypress 事件,该属性声明了被敲击的键生成的 Unicode 字符码。对于 keydown 和 keyup 事件,它指定了被敲击的键的虚拟键盘码。虚拟键盘码可能和使用的键盘的布局相关。 2.preventDefault() 方法 取消事件的默认动作。 语法 …...
2024/4/30 18:22:44 - 使用Openresty实现WAF防火墙功能
Openresty简介 OpenResty 是一个结合了 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。 OpenResty 通过汇聚各种设计精良的 Nginx 模块(主…...
2024/4/30 20:13:55 - 电商行业如此迅速发展???
社会竞争愈来愈激烈,飞速发展的现代生活更需要智能的参与,智能让生活变得更有趣和多元化。智能使得人们生活更加便捷,因为智能人们可以足不出户购买到任何所需之物,因为智能人们对于未知的世界更加了解,也因为智能使得人们解放了双手,使之替代简单机械的工作。荒林春雨足…...
2024/4/29 7:48:10 - 如何合理的选择一台服务器?
服务器对网站的影响都非常大,无论是SEO优化还是用户体验,而网站服务器配置的选择与网站的访问量密不可分。如果我们的网站大就需要选择配置高的云服务器,如果访问量大需要选择大带宽。那么具体的网站与服务器空间配置选择是什么? 服务器租用负载与网站访问 通常我们选择服务…...
2024/4/30 22:32:17 - 4.05周期信号波形对称性和谐波性质
1.f(t)f(t)f(t)为偶函数—对称于纵轴f(t)=f(−t)f(t)=f(-t)f(t)=f(−t) an=2T∫−T2T2f(t)cos(nΩt)dta_n=\frac{2}{T}\int _{-\frac {T}{2}}^\frac{T}2{}f(t)cos(n\Omega t)\rm dtan=T2∫−2T2Tf(t)cos(nΩt)dt bn=2T∫−T2T2f(t)sin(nΩt)dtb_n=\frac{2}{T}\int _{…...
2024/4/29 7:48:04 - linux【workerman】【GatewayWorker】聊天日常解决报错,无法启动, 分享
*** 本来增删改查写的好好的,领导突然来一个聊天项目,客户又想省钱,不想用第三方,没办法,只能自己做一个,但是作为一个小白,这算是一个新领域了,聊天的大门都还没进,就差点死在了门口。。。。*** ---------------------------------华丽的分割线-------------------…...
2024/4/29 7:47:51 - 【论文翻译】A New HEVC In-Loop Filter Based on Multi-channel Long-Short-term Dependency Residual Networks
A New HEVC In-Loop Filter Based on Multi-channel Long-Short-term Dependency Residual Networks Xiandong Meng∗, Chen Chen∗, Shuyuan Zhu†, and Bing Zeng† ∗The Hong Kong University of Science and Technology Clear Water Bay, Kowloon, Hong Kong, China †Uni…...
2024/4/29 7:47:51 - realvnc,realvnc软件是什么?软件介绍
RealVNC Enterprise 是工业标准 VNC 的一个增强版本,提供了核心的 Enterprise Edition 安全增强,包括 2048 位 RSA 服务器验证和 128 位 AES 会话加密术,加上为 Windows 网络量身定做的许多其他的特性。 但是我使用过一款也非常好的vnc工具,名字叫IIS7服务器管理工具。 这个…...
2024/4/29 7:47:49 - TTVNC,TTVNC软件是什么软件?使用教程
TTVNC 跨过防火墙和路由器的VNC 软件,ttcnc可以说是最易用的远程遥控软件了,只需要客户端和主控端验证码相同就可以了,跨越NAT和路由器,帮助不同内网的用户,最快3秒即可实现远程协助。免安装,可以在网吧运行,提供远程协助和无人值守模式。 但是我使用过一款也非常好的vn…...
2024/4/29 7:47:42 - 滴滴在 GitHub 开源的项目盘点
作为卓越的一站式移动出行和生活平台,滴滴在亚洲、拉美和澳洲为超过 5.5 亿用户提供出租车、快车、专车、豪华车、公交、代驾、企业级、共享单车、共享电单车、汽车服务、外卖、支付等多元化的服务。滴滴平台上,有数千万车主及司机获得灵活的工作和收入机会,年运送乘客超过 …...
2024/4/29 7:47:36
最新文章
- Redis(七) zset有序集合类型
文章目录 前言命令ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZPOPMAXZPOPMIN两个阻塞版本的POP命令BZPOPMAX BZPOPMINZRANKZREVRANKZSCOREZREMZREMRANGEBYRANKZREMRANGEBYSCOREZINCRBY集合间操作ZINTERSTOREZUNIONSTORE 命令小结 内部编码使用场景 前言 对于有序集合这个名…...
2024/5/1 1:32:11 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - WPS二次开发专题:如何获取应用签名SHA256值
作者持续关注WPS二次开发专题系列,持续为大家带来更多有价值的WPS开发技术细节,如果能够帮助到您,请帮忙来个一键三连,更多问题请联系我(QQ:250325397) 在申请WPS SDK授权版时候需要开发者提供应用包名和签…...
2024/4/30 4:38:48 - 【APUE】网络socket编程温度采集智能存储与上报项目技术------多路复用
作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…...
2024/4/30 7:26:52 - 416. 分割等和子集问题(动态规划)
题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义:dp[i][j]表示当背包容量为j,用前i个物品是否正好可以将背包填满ÿ…...
2024/4/30 9:36:27 - 【Java】ExcelWriter自适应宽度工具类(支持中文)
工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...
2024/4/30 0:57:52 - Spring cloud负载均衡@LoadBalanced LoadBalancerClient
LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon…...
2024/4/29 18:43:42 - TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案
一、背景需求分析 在工业产业园、化工园或生产制造园区中,周界防范意义重大,对园区的安全起到重要的作用。常规的安防方式是采用人员巡查,人力投入成本大而且效率低。周界一旦被破坏或入侵,会影响园区人员和资产安全,…...
2024/4/29 19:40:09 - VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...
2024/4/30 23:32:22 - 【Objective-C】Objective-C汇总
方法定义 参考:https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...
2024/4/30 23:16:16 - 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】🌏题目描述🌏输入格…...
2024/4/30 0:57:50 - 【ES6.0】- 扩展运算符(...)
【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数࿰…...
2024/4/29 21:25:29 - 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?
文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕,各大品牌纷纷晒出优异的成绩单,摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称,在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁,多个平台数据都表现出极度异常…...
2024/4/30 0:57:49 - Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
2024/4/30 14:53:47 - 用欧拉路径判断图同构推出reverse合法性:1116T4
http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b,我们在 a i a_i ai 和 a i 1 a_{i1} ai1 之间连边, b b b 同理,则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然࿰…...
2024/4/30 22:14:26 - 【NGINX--1】基础知识
1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息,并安装一些有助于配置官方 NGINX 软件包仓库的软件包: apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...
2024/4/29 7:41:18 - Hive默认分割符、存储格式与数据压缩
目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限(ROW FORMAT)配置标准HQL为: ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...
2024/4/30 22:57:18 - 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法
文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中,传感器和控制器产生大量周…...
2024/4/30 20:39:53 - --max-old-space-size=8192报错
vue项目运行时,如果经常运行慢,崩溃停止服务,报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中,通过JavaScript使用内存时只能使用部分内存(64位系统&…...
2024/4/30 0:57:46 - 基于深度学习的恶意软件检测
恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞,例如可以被劫持的合法软件(例如浏览器或 Web 应用程序插件)中的错误。 恶意软件渗透可能会造成灾难性的后果,包括数据被盗、勒索或网…...
2024/4/30 0:57:46 - JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...
2024/4/29 3:42:58 - C++中只能有一个实例的单例类
C中只能有一个实例的单例类 前面讨论的 President 类很不错,但存在一个缺陷:无法禁止通过实例化多个对象来创建多名总统: President One, Two, Three; 由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目…...
2024/4/29 19:56:39 - python django 小程序图书借阅源码
开发工具: PyCharm,mysql5.7,微信开发者工具 技术说明: python django html 小程序 功能介绍: 用户端: 登录注册(含授权登录) 首页显示搜索图书,轮播图࿰…...
2024/4/29 8:41:59 - 电子学会C/C++编程等级考试2022年03月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...
2024/4/30 20:52:33 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57