前言
做开发岗,可能有些知识点在项目上根本用不到,但是还是要熟练,要多回顾,现总结一些常规知识点。
一、Qt基础知识点
1.信号槽
Qt信号和槽的本质是什么
回调函数
Qt信号槽机制的优势
(1)类型安全。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或几个参数。如果信号和槽的签名不符,编译器就会报错。
(2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的哪个槽需要接收它发出的信号,它只需在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道是哪个对象的哪个槽收到了信号。同样的,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除,应用程序也不会崩溃。
(3)信号和槽机制增强了对象间通信的灵活性。一个信号可以关联多个槽,也可以多个信号关联一个槽。
Qt信号槽机制的不足
同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:
(1)需要定位接收信号的对象;
(2)安全地遍历所有的关联(如一个信号关联多个槽的情况);
(3)编组/解组传递的参数;
(4)多线程的时候,信号可能需要排队等待。
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。
多线程下,信号槽分别在什么线程中执行,如何控制
可以通过connect的第五个参数进行控制信号槽执行时所在的线程
connect有几种连接方式
直接连接和队列连接、自动连接
直接连接:信号槽在信号发出者所在的线程中执行
队列连接:信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
自动连接:多线程时为队列连接函数,单线程时为直接连接函数
connect()第五个参数的作用
指定连接方式:默认,队列,直接
多线程时才有意义。默认的时候,如果是多线程,默认使用队列,如果是单线程, 使用直接方式。
队列: 槽函数所在的线程和接收者一样;直接:槽函数所在线程和发送者一样
如何使用C++模拟Qt信号和槽
Qt的信号和槽原理就是回调函数。所以,我们需要保存对象绑定的回调函数
//1.创建槽类Slot,该类的功能是保存对象和对象绑定的回调函数
template<class T>
class SlotBase
{
public:
virtual void Exec(T param1) = 0; //纯虚函数
virtual ~SlotBase(){}
};
/*
* func: 槽函数
* parm:
* return:
*/
template<class T, class T1>
class Slot : public SlotBase<T1>
{
public:
/* 定义Slot的时候,获取槽函数信息 */
Slot(T* pObj, void (T::*func)(T1))
{
m_pSlotBase = pObj;
m_Func = func;
}
/* signal触发时,调用 */
void Exec(T1 param1)
{
(m_pSlotBase->*m_Func)(param1);
}
private:
/* 槽函数信息 暂存 */
T* m_pSlotBase;
void (T::*m_Func)(T1);
};
/*
2. 创建signal类
重要阐述:
1.创建一个Signal 类,该类保主要是保存多个Slot对象,当一个信号发送时,会遍历这个表,对每一个slot绑定的回调函数进行调用。
2.重载运算符(), 遍历这个表,调用回调函数,即signal触发机制
3.写一个绑定函数Bind,用于将Slot对象添加到槽表中
*/
template<class T1>
class Signal
{
public:
/* 模板函数 -> Bind时获取槽函数指针 */
template<class T>
void Bind(T* pObj, void (T::*func)(T1))
{
m_pSlotSet.push_back(new Slot<T,T1>(pObj,func));
}
/* 重载操作符 -> signal触发机制 */
void operator()(T1 param1)
{
for(int i=0;i<(int)m_pSlotSet.size();i++)
{
m_pSlotSet[i]->Exec(param1);
}
}
~Signal()
{
for(int i=0;i<(int)m_pSlotSet.size();i++)
{
delete m_pSlotSet[i];
}
}
private:
vector<SlotBase<T1>*> m_pSlotSet; //这一句很重要,靠基类的指针来存储 信号槽指针
};
/*
3.测试类
测试类包含多个signal 当调用接口就将调用signal的()函数,从而调用slot
*/
class TestSignal
{
public:
TestSignal()
{
}
void setValue(int value)
{
emit ValueChanged(value);
}
void setfValue(int value)
{
emit ValueChanged_f(value);
}
public slots:
void FuncOfA(int parm)
{
printf("enter FuncOfA parm = %d\n", parm);
}
void FuncOfB(int parm)
{
printf("enter FuncOfB parm = %d\n", parm);
}
signals:
Signal<int> ValueChanged;
Signal<float> ValueChanged_f;
};
//4.定义一个链接函数
#define Connect(sender, signal, receiver, method) ((sender)->signal.Bind(receiver, method))
Qt元对象如何实现跨线程连接的
信号槽,第五个参数。同线程,用回调函数;不同线程,用队列。这样做是为了节省开销
moc是怎么作用的
Qt 将源代码交给标准 C++ 编译器,如 gcc 之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。
moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。
在命令行下输入moc yourfilename.h -o moc_youfilename.cpp生成不带Q_OBJECT的源文件。
2.UI设计
描述过程, 如何实现一个自定义按钮, 使其在光标进入,按下,离开三种状态下显示不同的图片.
创建一个类, 让其从QPushButton类派生, 重写该类中的事件处理器函数
方法一:
1>.enterEvent() – 光标进入
2>.leaveEvent() – 光标离开
3>.mousePressEvent() – 鼠标按下
4>.paintEvent() – 刷新背景图
方法二:
通过setstylesheet设置
qss中,自定义的属性,与原生的属性有什么不一样
qss没法手动添加属性,那些不是qproperty
3.流
描述QT中的文件流(QTextStream)和数据流(QDataStream)的区别, 他们都能帮助我们完成一些什么事情
QTextStream – 文本流, 操作轻量级数据(int, double, QString), 数据写入文件中之后以文本的方式呈现。
QDataStream – 数据流, 通过数据流可以操作各种数据类型, 包括类对象, 存储到文件中数据可以还原到内存(二进制)。
QTextStream, QDataStream可以操作磁盘文件, 也可以操作内存数据, 通过流对象可以将数据打包到内存, 进行数据的传输.
4.网络
描述Qt下Tcp通信的整个流程
服务器端:
1.创建用于监听的套接字
2.给套接字设置监听
3.如果有连接到来, 监听的套接字会发出信号newConnected
4.接收连接, 通过nextPendingConnection()函数, 返回一个QTcpSocket类型的套接字对象(用于通信)
5.使用用于通信的套接字对象通信
1>.发送数据: write
2>.接收数据: readAll/read
客户端:
1.创建用于通信的套接字
2.连接服务器: connectToHost
3.连接成功与服务器通信
1>.发送数据: write
2>.接收数据: readAll/read
描述QT下udp通信的整个流程
QT下udp通信服务器端和客户端的关系是对等的, 做的处理也是一样的
1.创建套接字对象
2.如果需要接收数据, 必须绑定端口
3.发送数据: writeDatagram
4.接收数据: readDatagram
5.多线程
描述QT下多线程的三种使用方法(QThread和QtConcurrent), 以及注意事项
方法一:
1.创建一个类从QThread类派生
2.在子线程类中重写 run 函数, 将处理操作写入该函数中
3.在主线程中创建子线程对象, 启动子线程, 调用start()函数
方法二:
1.将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
2.在主线程中创建一QThread类对象
3.在主线程中创建一个业务类对象
4.将业务类对象移动到子线程中
5.在主线程中启动子线程
6.通过信号槽的方式, 执行业务类中的业务处理函数
方法三:
QFuture<void> fut1 = QtConcurrent::run(processFun, command);
processFun为线程回调函数,这里还需要了解一下lambda表达式,下方实例传了两个参数filepath和data
线程池的使用(QThreadPool )
QThreadPool::globalInstace()->maxThreadCount(); //获取本机的最大线程数
多线程使用注意事项:
1.务对象, 构造的时候不能指定父对象
2.子线程中不能处理ui窗口(ui相关的类)
3.子线程中只能处理一些数据相关的操作, 不能涉及窗口
4.多线程使用中注意对使用公用资源的加锁(QMutex)否则线程是不安全的
假如想在子线程中使用主线程的对象,这时就要通过信号和槽来实现(发信号给主线程,让主线程来操作)
6.Qt类
QVariant使用
1.用户自定义需要先注册一个类型,即使用qRegisterMetaType,注册到QT的一个Vector中
2.QVariant里面会new一个用户自定义类型的内存,并调用拷贝构造函数,QVariant自身的赋值会使用共享内存管理。所以用户可以传入一个临时变量地址,如果用户传入的是一个指针,这个指针需要用户自己析构,改变这个指针的值,并不会改变QVariant,因为是两个不同的空间了。而如果QVariant a1=b1(b1是QVariant),改变b1的值会改变a1的,因为这样用的是shared指针。
初看2以为是对的,验证发现不准确,改变b1并没有改变a1的值,细看发现这里面有QT使用了个小技巧,要取b1的值然后改变时,会调用data函数
CVariantHelp* pBTemp = reinterpret_cast<CVariantHelp*>(b1.data());
pBTemp->j_ = 99;
//而data的实现会调用detach将shared分离
void* QVariant::data()
{
detach();
return const_cast<void *>(constData());
}
void QVariant::detach()
{
if (!d.is_shared || d.data.shared->ref == 1)
return;
Private dd;
dd.type = d.type;
handler->construct(&dd, constData());
if (!d.data.shared->ref.deref())
handler->clear(&d);
d.data.shared = dd.data.shared;
}
7.Qt指针
Qt中的智能指针
1.QPointer
特点:当其指向的对象(T必须是QObject及其派生类)被销毁时,它会被自动置NULL.
注意:它本身析构时不会自动销毁所guarded的对象
用途:当你需要保存其他人所拥有的QObject对象的指针时,这点非常有
2.QScopedPointer QScopedArraytPointer与 std::unique_ptr/scoped_ptr
这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。
无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象
3.QSharedPointer QSharedArrayPointer 与 std::shared_ptr
QSharedPointer 与 std::shared_ptr 行为最接近原始指针,是最像指针的"智能指针",应用范围比前面的提到的更广。
QSharedPointer 与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了std::auto_ptr 和 QScopedPointer 因为转移语义而不能把指针作为容器元素的缺陷
4.QWeakPointer 与 std::weak_ptr
强引用类型的QSharedPointer已经非常好用,为什么还要有弱引用的 QWeakPointer?
QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。
weak_ptr 主要是为了避免强引用形成环状。摘自msdn中一段话:
A cycle occurs when two or more resources controlled by shared_ptr objects hold mutually referencing shared_ptr objects. For example, a circular linked list with three elements has a head node N0; that node holds a shared_ptr object that owns the next node, N1; that node holds a shared_ptr object that owns the next node, N2; that node, in turn, holds a shared_ptr object that owns the head node, N0, closing the cycle. In this situation, none of the reference counts will ever become zero, and the nodes in the cycle will not be freed. To eliminate the cycle, the last node N2 should hold a weak_ptr object pointing to N0 instead of a shared_ptr object. Since the weak_ptr object does not own N0 it doesn’t affect N0’s reference count, and when the program’s last reference to the head node is destroyed the nodes in the list will also be destroyed.
在Qt中,对于QObject及其派生类对象,QWeakPointer有特殊处理。它可以作为QPointer的替代品。这种情况下,不需要QSharedPointer的存在
5.QSharedDataPointer
这是为配合 QSharedData 实现隐式共享(写时复制 copy-on-write))而提供的便利工具。
Qt中众多的类都使用了隐式共享技术,比如QPixmap、QByteArray、QString、…。而我们为自己的类实现隐式共享也很简单,比如要实现一个 Employee类:
定义一个只含有一个数据成员(QSharedDataPointer) 的 Employee 类
我们需要的所有数据成员放置于 派生自QSharedData的 EmployeeData类中。
6.QExplicitlySharedDataPointe
这是为配合 QSharedData 实现显式共享而提供的便利工具。QExplicitlySharedDataPointer 和 QSharedDataPointer 非常类似,但是它禁用了写时复制功能。这使得我们创建的对象更像一个指针。
二、C++基础知识点
1.基础
变量的声明和定义有什么区别
为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但只能在一个地方定义。加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
说明:很多时候一个变量,只是声明,不分配内存空间,知道具体使用时才初始化,分配内存空间,如外部变量。
头文件中的 ifndef/define/endif 干什么用
预处理,防止头文件被重复引用
#include和#include“filename.h”有什么区别
前者用来包含开发环境提供的库头文件,后者用来包含自己编写的头文件。
extern有什么作用
extern标识的变量或者函数声明其定义在别的文件中,提示编译器遇到此变量和函数时在其它模块中寻找其定义。
在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明?
函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。
sizeof和strlen的区别
sizeof和strlen有以下区别:
sizeof是一个操作符,strlen是库函数。
sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0‘的字符串作参数。编译器在编译时就计算出了sizeof的结果。而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。数组做sizeof的参数不退化,传递给strlen就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是sizeof。
说明:指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。其保存的数值是个整型数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。
写一个“标准”宏MIN
#define min(a,b) ((a)<=(b)?(a):(b))
宏定义的用法,看下面这个程序,求出结果
#include <stdio.h>
#define S(a,b) a*b
int main(void)
{
int n = 3;
int m = 5;
printf("%d",S(n+m,m+n));
return 0;
}
这道题容易出现的错误结果是64,得到这个结果肯定是这样理解的(3+5)(5+3)。其实并不是,大家要理解宏定义的概念,宏定义只是简单的符号替换,而不做其他处理,所以这里得到的结果是 3+55+3=31.
大家看看运行结果:
//view plain copy
fs@ubuntu:~/qiang/hanshu$ gcc -o 1 1.c
fs@ubuntu:~/qiang/hanshu$ ./1
31
要想得到正确结果,应该怎么样呢?应该这样改,define s(a,b) (a)*(b),这样才是正确结果;
//view plain copy
fs@ubuntu:~/qiang/hanshu$ gcc -o 1 1.c
fs@ubuntu:~/qiang/hanshu$ ./1
64
大家记住这句话,宏定义只是简单的符号替换!
c语言中,运算对象必须是整型数的运算符的有()
A、% B、/ C、%和/ D、*
答案:A。取余对象必须是整数型
一个指针可以是volatile吗?
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:子中断服务子程序修改一个指向一个buffer的指针时,必须用volatile来修饰这个指针。
typedef和define有什么区别
(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。
注意:typedef定义是语句,因为句尾要加上分号。而define不是语句,千万不能在句尾加分号
关键字const是什么
const用来定义一个只读的变量或对象。主要优点:便于类型检查、同宏定义一样可以方便地进行参数的修改和调整、节省空间,避免不必要的内存分配、可为函数重载提供参考。
说明:const修饰函数参数,是一种编程规范的要求,便于阅读,一看即知这个参数不能被改变,实现时不易出错。
static有什么作用
static在C中主要用于定义全局静态变量、定义局部静态变量、定义静态函数。在C++中新增了两种作用:定义静态数据成员、静态函数成员。
注意:因为static定义的变量分配在静态区,所以其定义的变量的默认值为0,普通变量的默认值为随机数,在定义指针变量时要特别注意。
static有什么用途?(请至少说明两种)
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用
全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
全局变量储存在静态数据库,局部变量在堆栈。
什么函数不能声明为虚函数?
构造函数不能声明为虚函数。
写出float x 与“零值”比较的if语句
if(x<0.000001&&x>-0.000001)
不能做switch()的参数类型是
switch的参数不能为实型
写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中 a的值
int a = 4;
(A) a += (a++);
(B) a+= (++a);
(C) (a++) += a;
(D) (++a) += (a++);
a = ?
C错误,左侧不是一个有效变量,不能赋值,可改为(++a) += a;改后答案依次为9,10,10,11
MFC中CString是类型安全类么?
不是,其它数据类型转换到CString可以使用CString的成员函数Format来转换
k最后的值是?
int i=10, j=10, k=3; k*=i+j;
60。此题考察优先级,实际写成: k*=(i+j);,赋值运算符优先级最低
2.数组和指针
a 和 &a 有什么区别,请写出以下代码的打印结果,主要目的是考察a和&a的区别。
//view plain copy
#include<stdio.h>
void main( void )
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return;
}
输出结果:2,5。
注意:数组名a可以作数组的首地址,而&a是数组的指针。思考,将原式的int *ptr=(int *)(&a+1);改为int *ptr=(int *)(a+1);时输出结果将是什么呢?
数组时连续分配一串单元,数目开始定义的时候就必须固定下来,看起来整洁,但是写的程序是死程序,容易浪费内存。
指针存放一个地址值,表示指向某一个单元,可以用指针来索引单元。数组可以完成栈,堆,树等等的操作,它在编程时候的好处是非常的灵活,在构建思路的时候有很大的灵活性。
简述指针常量与常量指针区别
指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。其实指针常量是唯一的,即NULL;常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。
指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。
如何避免“野指针”
“野指针”产生原因及解决办法如下:
(1)指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向NULL。
(2)指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向NULL。
(3)指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL。
注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性
引用与指针有什么区别?
1)引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。
某32位系统下, C++程序,请计算sizeof 的值
char str[] = “http://www.ibegroup.com/”
char *p = str ;
int n = 10;
sizeof (str ) = ?(1)
sizeof ( p ) = ?(2)
sizeof ( n ) = ?(3)
void Foo ( charstr[100])
{
sizeof( str ) = ?(4)
}
void *p = malloc(100 );
sizeof ( p ) = ?(5)
(1)25 (2)4 (3) 4 (4)4 (5)4
回答下面的问题
void GetMemory(char **p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str,100);
strcpy(str,"hello");
printf(str);
}
//请问运行Test 函数会有什么样的结果?
输出“hello”
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
//请问运行Test 函数会有什么样的结果?
输出“world”
char* GetMemory(void)
{
char p[] ="hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
//请问运行Test 函数会有什么样的结果?
无效的指针,输出不确定
3.内存
简述C、C++程序编译的内存分配情况
C、C++中内存分配方式可以分为三种:
(1)从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。
(2)在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配:
即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个C、C++程序编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
简述strcpy sprintf与memcpy的区别
三者主要有以下不同之处:
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。
说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。
链表与数组的区别
数组和链表有以下几点不同:
(1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
(2)数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
(3)数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4)越界问题:链表不存在越界问题,数组有越界问题。
说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。
简述队列和栈的异同
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是“后进先出”。
注意:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。
memcpy拷贝结构体、结构体数组到字符数组(字符串)
memcpy可以将结构体拷贝到字符数组中,但直接从字符数组中是不能取出想要的结果的。因为结构体中数据类型和字符类型是不一致的,
如果真要取出数据内容,有两种方法:1.再次使用memcpy进行解析 2.强制类型转换.
例1:
struct aaa {
int a;
int b;
}aa,bb;
char buffer[20];
aa.a=20;aa.b=30;
memcpy(buffer,&aa,sizeof(aaa)); //一般是作为发送方的处理方式
memcpy(&bb,buffer,sizeof(aaa)); // 一般是作为接受方的处理方式
cout<<bb.a<<bb.b<<endl;
例2:
memcpy(buffer,&aa,sizeof(aaa));
aaa *new = (aaa *)buffer;
cout<<new->a<<new->b<<endl;
同样,memcpy也可以将结构体数据拷贝到字符中,采用上述方式即可取出字符数组中的内容
aaa bb[2],aa[2];
memset(aa, 0, sizeof(bbb)*2);
memset(bb, 0, sizeof(bbb)*2);
char arr[20];
aa[0].a=40;
aa[0].b=50;
aa[1].a = 100;
aa[1].b = 200;
memcpy(arr,aa,sizeof(bbb)*2);
//bbb *cc = (bbb *)arr;
memcpy(bb, arr, sizeof(bbb)*2);
cout<<bb[0].a<<bb[0].b<<bb[1].a<<bb[1].b<<endl;//可以证明输出结果分别是:40,50,100,200
4.系统
描述实时系统的基本特性
在特定时间内完成特定的任务,实时性与可靠性。
堆栈溢出一般是由什么原因导致的
没有回收垃圾资源。
Linux有内核级线程么
线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两种类型:“用户级线程”和“内核级线程”。用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现,但线程的调度需要用户程序完成,这有些类似Windows 3.x 的协作式多任务。另外一种则需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部需求进行创建和撤销,这两种模型各有其好处和缺点。用户线程不需要额外的内核开支,并且用户态线程的实现方式可以被定制或修改以适应特殊应用的要求,但是当一个线程因 I/O 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会;而内核线程则没有各个限制,有利于发挥多处理器的并发优势,但却占用了更多的系统开支。 Windows NT和OS/2支持内核线程。Linux 支持内核级的多线程
程序什么时候应该使用线程,什么时候单线程效率高
1.耗时的操作使用线程,提高应用程序响应
2.并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。
3.多CPU系统中,使用线程提高CPU利用率
4.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
其他情况都使用单线程。
C++中什么数据分配在栈或堆中,New分配数据是在近堆还是远堆中
栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理堆: 程序运行时动态申请,new 和malloc申请的内存就在堆上。
使用线程是如何防止出现大的波峰
意思是如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提高调度效率和限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队等候。
CSingleLock是干什么的
同步多个线程对一个数据类的同时访问
NEWTEXTMETRIC 是什么
物理字体结构,用来设置字体的高宽大小
关于内存对齐的问题以及sizeof()的输出
编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
动态连接库的两种方式?
调用一个DLL中的函数有两种方法:
载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。
5.网络
Internet采用哪种网络协议?该协议的主要层次结构?
Tcp/Ip协议。主要层次结构为: 应用层/传输层/网络层/数据链路层/物理层。
Internet物理地址和IP地址转换采用什么协议?
ARP(Address Resolution Protocol)(地址解析协议)
IP地址的编码分为哪俩部分?
IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。
IP组播有那些好处?
Internet上产生的许多新的应用,特别是高带宽的多媒体应用,带来了带宽的急剧消耗和网络拥挤问题。组播是一种允许一个或多个发送者(组播源)发送单一的数据包到多个接收者(一次的,同时的)的网络技术。组播可以大大的节省网络带宽,因为无论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包。所以说组播技术的核心就是针对如何节约网络资源的前提下保证服务质量。
TCP/IP 建立连接的过程?(3-way shake)
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
ICMP是什么协议,处于哪一层?
Internet控制报文协议,处于网络层(IP层),ICMP报文是在IP数据报内部被传输的,通常被IP层或更高层(TCP/IP)使用。作用是:传递差错信息或其它需要注意的信息。
winsock建立连接的主要实现步骤?
服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接。
客户端:socker()建立套接字,连接(connect)服务器,连接上后使用send()和recv(),在套接字上写读数据,直至数据交换完毕,closesocket()关闭套接字。
服务器端:accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待连接。该新产生的套接字使用send()和recv()写读数据,直至数据交换完毕,closesocket()关闭套接字。
6.数据结构
什么是平衡二叉树
左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。
冒泡排序算法的时间复杂度是什么
时间复杂度是O(n^2)。
C++中为什么用模板类
(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
函数模板与类模板有什么区别
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
7.数据库
一般数据库若出现日志满了,会出现什么情况,是否还能使用?
只能执行查询等读操作,不能执行更改,备份等写操作,原因是任何写操作都要记录日志。也就是说基本上处于不能使用的状态。
SQL Server是否支持行级锁,有什么好处?
支持,设立封锁机制主要是为了对并发操作进行控制,对干扰进行封锁,保证数据的一致性和准确性,行级封锁确保在用户取得被更新的行到该行进行更新这段时间内不被其它用户所修改。因而行级锁即可保证数据的一致性又能提高数据操作的迸发性。
对数据库的一张表进行操作,同时要对另一张表进行操作,如何实现?
将操作多个表的操作放入到事务中进行处理
触发器怎么工作的?
触发器主要是通过事件进行触发而被执行的,当对某一表进行诸如UPDATE、 INSERT、DELETE 这些操作时,数据库就会自动执行触发器所定义的SQL 语句,从而确保对数据的处理必须符合由这些SQL 语句所定义的规则。
8.编程
用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序(约瑟夫环问题)。
循环链表,用取余操作做
编写strcat函数
已知strcat函数的原型是
char *strcat (char *strDest, const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串
(1)不调用C++/C 的字符串库函数,请编写函数 strcat
char * __cdeclstrcat (char * dst, const char * src)
{
char *cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return dst; /*return dst */
}
(2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值?
方便赋值给其他变量
总结
如果感觉有80%都会的话,说明基础还过关。继续加油吧!(持续更新中。。。)
参考链接:链接