由于多年没有参加过应聘这个角色了,平常开发时候用的是原先自己积累的一些开源库,现在已经基本上把基础忘记完了,今天凑热闹去面试,结果可想而知,碰了一鼻子灰,这个是注定的结果,毋庸置疑! !!!! 哈哈 接下来谈下今天面试,自己被提问的问题吧。
1.首先是做一个简单的自我介绍~~~略@@@
2.一般介绍肯定是从工作项目介绍开始,在介绍时由于紧张所有犯了一个简单的错误,就是说话不利索,而且思维开始混乱,逻辑不清晰,因为担心问道那个不会的问题上,实际这些东西之间做过,但是时间长没有去了解它的原理,所以----不知道
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数.
实现方式: 重写/重载/重定义
(1)在基类用virtual声明成员函数为虚函数。
(2)在派生类中重新定义此函数,要求函数名,函数类型,函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
c++规定,当一个成员函数被声明为虚函数后,其派生类的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每层声明该函数时都加上virtual,使程序更加清晰。如果再派生类中没有对基类的虚函数重新定义,则派生类简单的继承起基类的虚函数。
(3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
(4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
指针最难
C++特性,即使面向对象,又是面向过程.
构造函数用于解决类中的对象初始化的问题
构造函数是一类特殊的函数,它的名字和类名相同,没有返回值,与其他的成员函数不同的是构造函数构造函数不需要用户来调用它,而是建立对象的时候自动的执行
引用在定义时就被初始化,之后无法改变;指针可以发生改变
表示形式不同引用是&, 指针是*;
引用不可以为空,但指针可以为空
对引用使用“sizeof”得到的是变量的大小,对指针使用“sizeof”得到的是变量的地址的大小
理论上指针的级数没有限制,但引用只有一级
++引用与++指针的效果不一样(一个是变量++,一个是地址++)
引用底层是用指针实现的.
原理:
创建一个信号,其中创建信号需要一些规则。
当需要调用外部函数时,发送一个信号,
此时与该信号相关联的槽便会被调用,槽其实就是一个函数,当然要使函数成为槽是有一定规则的。槽与信号的关联需要由程序员来完成。
在Qt中,信号和槽都需要位于类之中。
(1)一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
(2)多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。
(3)一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
(4)槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
(5)使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
信号和槽使用QObject类中的成员函数connect进行关联,该函数有多个重载版本。
(1)发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
(2)使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
(3)槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
(4)使用 emit 在恰当的位置发送信号;
(5)使用QObject::connect()函数连接信号和槽;
(6)任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。
通过connect函数的第五个参数connectType来控制。
connect用于连接qt的信号和槽,在qt编程过程中不可或缺。它其实有第五个参数,只是一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。
Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
**Qt::DirectConnection:**槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
**Qt::QueuedConnection:**槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
**Qt::BlockingQueuedConnection:**槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
**Qt::UniqueConnection:**这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此
它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间
进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新
申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。
list就是数据结构中的双向链表(根据sgi stl源代码),因此它的内存空间可以是不连续
的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它
没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除
和插入。
1.。常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2.。偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。 对于特定的环境,偶发性的...
3.。一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块...
4.。隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说...
在同一个作用域下,函数名相同,返回值类型/参数个数/参数类型不同.多态中在程序中静态调用的.
有时候函数处理的过程很相似,但是处理的类型却不一样这样就需要重载函数了,这样可以使用类似的函数调用使用于很多的场合
布局有几种: 栅格布局,水平布局,垂直布局,分裂时水平布局,分裂式垂直布局,在窗体布局中布局。
重写:在不同作用域下(一般是一个是父类,一个是子类),在多态中是在程序中运行时动态调用的.
象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
作用:通常在编程语句中用 abstract 修饰的类是抽象类。在C++中,含有纯虚拟函数的类称为抽象类,它不能生成对象;在java中,含有抽象方法的类称为抽象类,同样不能生成对象。
抽象类里面可以有非抽象方法但接口里只能有抽象方法 声明方法的存在而不去实现它的类被叫做抽像类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。
不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽像类,并让它指向具体子类的一个实例。不能有抽像构造函数或抽像静态方法。Abstract 类的子类为它们父类中的所有抽像方法提供实现,否则它们也是抽像类为。
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0:
在不同作用域下,只要函数名相同,且不是重写就是重定义.
explicit 禁止单参构造函数被自动型别转换
拷贝构造;拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。(主要是要使用当前类,类引用)
赋值函数:当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数
拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。(这些要点会在下面的String实现代码中体现)
C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。
const 修饰指针变量有以下三种情况。
被 static 修饰的变量、被 static 修饰的方法统一属于类的静态资源,是类实例之间共享的,换言之,一处变、处处变。定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见
在 C++ 中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
静态成员的定义或声明要加个关键 static。静态成员可以通过双冒号来使用即 <类名>::<静态成员名>。
不能通过类名来调用类的非静态成员函数。
类的对象可以使用静态成员函数和非静态成员函数。
静态成员函数中不能引用非静态成员。
类的非静态成员函数可以调用用静态成员函数,但反之不能。
类的静态成员变量必须先初始化再使用。
const规定变量在全局中是只读的变量,并且它的值无法被修改
const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。
static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化。
const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
cosnt成员函数主要目的是防止成员函数修改对象的内容。即const成员函数不能修改成员变量的值,但可以访问成员变量。当方法成员函数时,该函数只能是const成员函数。
static成员函数主要目的是作为类作用域的全局函数。不能访问类的非静态数据成员。类的静态成员函数没有this指针,这导致:1、不能直接存取类的非静态成员变量,调用非静态成员函数2、不能被声明为virtual
栈区:由编译器自动分配释放,像局部变量,函数参数,都是在栈区。会随着作用于退出而释放空间。
堆区:程序员分配并释放的区域,像malloc(c),new(c++)
全局数据区(静态区):全局变量和静态便令的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束释放。
4.代码区
C ++库以auto_ptr,unique_ptr,shared_ptr和weak_ptr的形式提供智能指针的实现,
3 策略模式
最容易理解的查找算法,顺序查找法
说明:顺序查找适合于存储结构为顺序存储或链接存储的线性表。
基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。
复杂度分析:
查找成功时的平均查找长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查找不成功时,需要n+1次比较,时间复杂度为O(n);
所以,顺序查找的时间复杂度为O(n)。
(2).二分查找
说明:元素必须是有序的,如果是无序的则要先进行排序操作。
基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
复杂度分析:最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
数表查找
5.1 最简单的树表查找算法——二叉树查找算法。
5.2 平衡查找树之2-3查找树(2-3 Tree)
平衡查找树之红黑树(Red-Black Tree)
5.4 B树和B+树(B Tree/B+ Tree)
快速排序
冒泡排序
选择排序
插入排序
归并排序
堆排序
希尔排序
基数排序
MSD(高位到低位排序)
vector与数组类似,拥有一段连续的内存空间,并且起始地址不变。便于随机访问,时间复杂度为O(1),但因为内存空间是连续的,所以在进入插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。
此外,当数组内存空间不足,会采取扩容,通过重新申请一块更大的内存空间进行内存拷贝。
list底层是由双向链表实现的,因此内存空间不是连续的。根据链表的实现原理,List查询效率较低,时间复杂度为O(n),但插入和删除效率较高。只需要在插入的地方更改指针的指向即可,不用移动数据。
为使用信号和槽,
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
第一种方法:
1. 创建一个类从QThread类派生
2. 在子线程类中重写 run 函数, 将处理操作写入该函数中
3. 在主线程中创建子线程对象, 启动子线程, 调用start()函数
第二种方法:
1. 将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
2. 在主线程中创建一QThread类对象
3. 在主线程中创建一个业务类对象
4. 将业务类对象移动到子线程中
5. 在主线程中启动子线程
6. 通过信号槽的方式, 执行业务类中的业务处理函数
多线程使用注意事项:
* 1. 业务对象, 构造的时候不能指定父对象
* 2. 子线程中不能处理ui窗口(ui相关的类)
* 3. 子线程中只能处理一些数据相关的操作, 不能涉及窗口
一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。这种情况下,单例模式是最恰当的解决办法。它有很多种实现方式,各自的特性不相同,使用的情形也不相同。今天要实现的是常用的三种,分别是饿汉式、懒汉式和多线程式。
通过单例模式, 可以做到:
1. 确保一个类只有一个实例被建立
2. 提供了一个对对象的全局访问指针
3. 在不影响单例类的客户端的情况下允许将来有多个实例
懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的时候才会加载。。。。。。懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象。好处是如果被调用就不会占用内存。
饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。饿汉式有两种常见的写法,写法1和写法2
在懒汉式的单例类中,其实有两个状态,单例未初始化和单例已经初始化。假设单例还未初始化,有两个线程同时调用GetInstance方法,这时执行 m_pInstance == NULL 肯定为真,然后两个线程都初始化一个单例,最后得到的指针并不是指向同一个地方,不满足单例类的定义了,所以懒汉式的写法会出现线程安全的问题!在多线程环境下,要对其进行修改。
使用double-check来保证thread safety.但是如果处理大量数据时,该锁才成为严重的性能瓶颈。
可以看到,获取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这是个最基础版本的单例实现,他有哪些问题呢?
m_instance_ptr
是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr
还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁
你需要系统中只有唯一一个实例存在的类的全局变量的时候才使用单例。越小越好,越简单越好,线程安全,内存不泄露
析构函数(destructor)是成员函数的一种,它的名字与类名相同,但前面要加~
,没有参数和返回值。
一个类有且仅有一个析构函数。如果定义类时没写析构函数,则编译器生成默认析构函数。如果定义了析构函数,则编译器不生成默认析构函数。
析构函数在对象消亡时即自动被调用。可以定义析构函数在对象消亡前做善后工作。例如,对象如果在生存期间用 new 运算符动态分配了内存,则在各处写 delete 语句以确保程序的每条执行路径都能释放这片内存是比较麻烦的事情。有了析构函数,只要在析构函数中调用 delete 语句,就能确保对象运行中用 new 运算符分配的空间在对象消亡时被释放。
QMap
两种之间的区别是:
QHash查找速度上显著于QMap
QHash以任意的方式进行存储,而QMap则是以key顺序进行存储
Qhash 的键类型必须提供operator==()和一个全局的qHash(key)函数。而QMap的键类型key必须提供operator<()函数