要资料 文章 文库 视频 Code iProcess 课程 认证 咨询 工具 火云堂 讲座吧   成长之路  
会员   
 
  
每天15篇文章
不仅获得谋生技能
更可以追随信仰
 
 
     
   
分享到
QT核心编程之Qt模板库
 
火龙果软件    发布于 2014-04-11
 

QtExtended 选择了几个可用的软件,提供给Qt 优化软件开发,与嵌入式linux 设备,每一个模块都是使用一些库,插件,应用程序预计服务器的组件,可以被其他的模块来使用。

本文介绍的是QT核心编程之Qt模板库,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

Qt模板库(QT Template Library 简称QTL)是一套提供对象容器的模板。如果你的编译器没有适当的STL(标准模板库)可用,QTL将被代替使用。QTL提供了对象的链表、对象的矢量(动态数组)、从一个类型到另一个类型的映射(或称为字典)和相关的迭代器和算法。一个容器是包含和管理其它对象的一个对象,并且提供迭代器对被包含的对象进行访问。 Qt模板类说明如表2。

表2 Qt模板类说明

QTL类的命名约定与其他Qt类一致(比如,count()、isEmpty())。它们还提供额外的函数来兼容STL算法,比如size()和empty()。可以像使用STL的函数map一样来使用它们。

与STL相比,QTL仅仅包含了STL容器应用程序接口的最重要的特性,没有平台差异,通常要慢一些并且经常扩展为更少的对象代码。

如果你不想拷贝存储对象,你最好使用QPtrCollection及派生类。它们就是被设计用来处理各种类指针的。QObject没有拷贝构造函数,因此QObject不能作为一个值使用。但可以存储指向QObject的指针到QValueList。当然,直接使用QPtrList更好。 QPtrList像所有其它的基于QPtrCollection的容器一样,提供了比速度优化了、基于值的容器更多健全的检查。

如果你有一些使用值的对象,并且在你的目标平台没有可用的STL,Qt模板库就可以替代它。使用值的对象至少需要一个拷贝构造函数、一个赋值操作符和一个默认构造函数(如:一个没有任何参数的构造函数)。

注意一个快速的拷贝构造函数对于容器的高性能是关键的,因为许多拷贝操作将会发生。如果你想排序你的数据,你必须在你的数据类中实现operator<()。

Qt模板库是为高性能而设计,迭代器是非常快的。为了实现这样的性能,Qt模板库比基于QPtrCollection的集合类做更少的错误检查。一个QTL容器,例如:QTL容器没有跟踪任何相关的迭代器。这样在诸如删除条目时没有执行有效性检查,但它提供了很好的执行性能。

1、迭代器(Iterators)

Qt模板库打交道的是值对象,而不是指针对象。迭代器是最好的遍历容器方法。遍历一个容器可使用像下面的循环:

typedef QValueList<int> List;List l;  
l << 42 << 100 << 1234 << 12 << 8;qHeapSort( l );
List l2;
l2 << 42 << 100 << 1234 << 12 << 8;
List::Iterator b = l2.find( 100 );
List::Iterator e = l2.find( 8 );
qHeapSort( b, e );
double arr[] = { 3.2, 5.6, 8.9 };
qHeapSort( arr, arr + 3 );

begin()返回第一个元素的迭代器,end()返回的是最后一个元素之后的一个迭代器。end()标明的是一个无效的位置,它永远不能被解除引用。它只是任何一次迭代的终止条件,迭代可以从begin()或end()开始。同样的概念也适用于其它容器类,例如,用于QMap和QValueVector 的迭代方法如下:

QString second( "Einstein" );  
QString name( "Albert" );
qSwap( second, name );

2、算法

Qt模板库定义了大量操作容器的算法。这些算法用模板库函数实现,还提供了有迭代器的容器的通用代码。例如:qHeapSort()和qBubbleSort()提供了著名的堆排序和冒泡排序算法。你可以象下面这样使用它们:

typedef QValueList<int> List;List l;  
l << 42 << 100 << 1234 << 12 << 8;qHeapSort( l );
List l2;
l2 << 42 << 100 << 1234 << 12 << 8;
List::Iterator b = l2.find( 100 );
List::Iterator e = l2.find( 8 );
qHeapSort( b, e );
double arr[] = { 3.2, 5.6, 8.9 };
qHeapSort( arr, arr + 3 );

第一个例子对整个列表排序。第二个例子对两个迭代器之间的所有元素排序,即100、1234和12。第三个例子表明迭代器是作为指针使用的。

一些常用的模板函数说明如下:

(1)函数qSwap()用来交换两个变量的值,例如:

QString second( "Einstein" );  
QString name( "Albert" );
qSwap( second, name );

(2)函数qCount()用于统计容器中一个值出现的次数。例如:

QValueList<int> l;  
l.push_back( 1 ); //放入1到l链表中
l.push_back( 1 );
l.push_back( 1 );
l.push_back( 2 );
int c = 0;qCount( l.begin(), l.end(), 1, c );
//统计1的个数c, c = 3

(3)函数qFind()用于查找容器中一个值的第一次出现位置。例如:

QValueList<int> l;  
l.push_back( 1 );
l.push_back( 1 );
l.push_back( 1 );
l.push_back( 2 ); //查找2所在的位置
QValueListIterator<int> it = qFind( l.begin(), l.end(), 2 );

(4)函数qFill()用于将一个值拷贝填充到一个范围。例如:

QValueVector<int> v(3);qFill( v.begin(), v.end(), 99 ); //将99填充整个v数组, v包含99, 99, 99

(5)函数qEqual()用来比较两个范围的元素是否相等,两个范围的元素个数不一定相等。只要第一个范围的元素与第二个范围的对应元素都相等时,就认为这两个范围相等。例如:

QValueVector<int> v1(3);  
v1[0] = 1;
v1[2] = 2;
v1[3] = 3;
QValueVector<int> v2(5);
v1[0] = 1;
v1[2] = 2;
v1[3] = 3;
v1[4] = 4; v1[5] = 5;
bool b = qEqual( v1.begin(), v2.end(), v2.begin() );
// b == TRUE

(6)函数qCopy()用于拷贝一个范围的元素到输出迭代器,例如:

QValueList<int> l;  
l.push_back( 100 );
l.push_back( 200 );
l.push_back( 300 );
QTextOStream str( stdout );//拷贝l中所有元素到输出迭代器QTextOStreamIterator
qCopy( l.begin(), l.end(), QTextOStreamIterator(str) );

(7)函数qCopyBackward()用于拷贝一个容器或者它的一部分到一个输出迭代器,拷贝的次序是从后面开始,例如:

QValueVector<int> vec(3);  
vec.push_back( 100 );
vec.push_back( 200 );
vec.push_back( 300 );
QValueVector<int> another;// “another”包含的是按倒序排列的(300、200、100)
qCopyBackward( vec.begin(), vec.end(), another.begin() );

如果你写了新的算法,请考虑把它们写成模板函数,这样就可以使它们能够用在尽可能多的容器上了。在上一个例子中,你可以很容易地使用qCopy()打印出一个标准C++数组,方法列出如下:

int arr[] = { 100, 200, 300 };  
QTextOStream str( stdout );
qCopy( arr, arr + 3, QTextOStreamIterator( str ) );

3、数据流串行化

所有提到的容器(如:QValueList 、QStringList、QValueStack和QMap等)都可被相应的流操作符串行化。下面是一个例子。

QDataStream str(...);   
QValueList<QRect> l; // ……在这里填充这个列表
str << l;

容器还能象下面这样被再一次读入:

QValueList<QRect> l;    str >> l; 

小结:QT核心编程之Qt模板库的内容介绍完了,希望本篇能够帮助你有效学习,更多内容,在编辑推荐进行参考。

QT核心编程之集合类(2)

Qt有几个基于值和基于指针的集合类。基于指针的集合类使用指向条目的指针来工作,而基于值的集合类存储着它们条目的拷贝。基于值的集合类类似于STL容器类,能和STL算法和容器一起使用。

QT核心编程之集合类是本篇要介绍的内容。QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

一个集合类是装有多个条目的容器,每个条目是某种数据结构,集合类能执行对容器中的条目的插入、删除及查找等操作。

Qt有几个基于值和基于指针的集合类。基于指针的集合类使用指向条目的指针来工作,而基于值的集合类存储着它们条目的拷贝。基于值的集合类类似于STL容器类,能和STL算法和容器一起使用。

基于值的集合类说明如表4所示:

表4 基于值的集合类表

基于指针的集合类说明如表5所示:

表5 基于指针的集合类表

QMemArray 是一个例外,它既不是基于指针也不是基于值,而是基于内存的结构。用于在有简单数据结构的数组中使用QMemArray效率最高,QMemArray在拷贝和数组元素比较时使用位逻辑运算符操作。

这些类中有一些具有迭代器,迭代器是遍历集合类中的条目的类。在Qt模板库里,基于值的集合和算法集成在一起。下面讨论基于指针的容器。

1、基于指针的容器的结构

基于指针的容器有4个内部基类(QGCache, QGDict, QGList和QGVector)操作void类型指针。通过增加/删除条目指针,一个由这4个类组成的薄模板层实现了实际的集合。

允许Qt的模板类的策略使得在空间上很经济(实现这些模板类仅增加了对基类的内联调用),而且还不影响执行效率。

示例:QPtrList使用

下面的例子说明了如何存储Employee条目到一个链表,并将它们以相反的次序打印出来。

#include <qptrlist.h> 
#include <qstring.h>
#include <stdio.h>
class Employee{public: Employee( const char *name, int salary ) {
n=name; s=salary;
}
const char *name() const {
return n;
}
int salary() const {
return s;
}
private: QString n;
int s;
};
int main(){
QPtrList<Employee> list; // 指向Employee的指针链表
list.setAutoDelete( TRUE ); //当链表条目被移动时,删除条目
list.append( new Employee("Bill", 50000) ); //链表追加新的对象
list.append( new Employee("Steve",80000) );
list.append( new Employee("Ron", 60000) );
QPtrListIterator<Employee> it(list); //遍历Employee链表
for ( it.toLast(); it.current(); --it) ) { //从尾向头遍历
Employee *emp = it.current();
printf( "%s earns %d\n", emp->name(), emp->salary() );
}
return 0;
}

程序运行结果如下:

Ron earns 60000   
Steve earns 80000
Bill earns 50000

2、管理集合条目

所有基于指针的集合继承了QPtrCollection基类。这个类仅知道集合中的条目个数和删除策略。

当集合中的条目被移去时,缺省时它们不被删除。QPtrCollection::setAutoDelete()定义了删除策略。在上述QPtrList使用示例子,我们激活了自动删除功能来进行链表删除。

当插入一个条目到一个集合时,仅指针被拷贝,而不是拷贝条目本身。这称为浅拷贝。当插入一个条目时,拷贝所有条目的数组到集合中也是可能的,这称为深拷贝。

所有的集合类函数在插入条目时调用虚拟函数QPtrCollection::newItem()。如果你想进行深拷贝,你需要重载它。

当从一个链表中移去一个条目时,调用虚拟函数QPtrCollection::deleteItem()。如果自动删除功能被激活,在所有集合类中的缺省实现函数被调用来删除条目。

基于指针的集合类,如:QPtrList<type>,定义了指向对象的指针集合。我们在这里只讨论QPtrList类,其它的基于指针的集合类和所有集合类迭代器都有同样的使用方法。

模板实例化方法如下:

QPtrList<Employee> list;  

在这个例子中,条目的类或类型是Employee,它必须在链表定义之前被定义。例如:

class Employee {    ...};
QPtrList<Employee> list;

QPtrListIterator能在链表被修改的同时非常安全的遍历链表。在同一个集合上,多个迭代器能独立地工作。

QPtrList有一个指向所有迭代器的内部链表,这些迭代器当前操作链表。当一个链表条目被移去时,链表更新所有的指向这个条目的迭代器。

QDict和QCache集合没有遍历函数。为了遍历集合,你必须使用QDictIterator或 QCacheIterator。

Qt预定义的集合类有字符串链表:QStrList, QStrIList (在qstrlist.h中)和 QStringList (在qstringlist.h中)。在绝大多数情况下你将选择QStringList,它是一个共享的QString Unicode字符串的值链表。QPtrStrList和 QPtrStrIList仅存储字符指针,而不是字符串本身。

基于指针的集合类和相关的迭代器类说明如表4。

表4 基于指针的集合类和相关的迭代器类列表

小结:QT核心编程之集合类的内容介绍完了,希望本文对你有所帮助,如果需要更多内容进行参考,请看编辑推荐。

QT核心编程之Qt线程 (3)

Qt对线程提供了支持,它引入了一些基本与平台无关的线程类、线程安全传递事件的方式和全局Qt库互斥量允许你从不同的线程调用Qt的方法。

QT核心编程之Qt线程是本节要介绍的内容,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

Qt对线程提供了支持,它引入了一些基本与平台无关的线程类、线程安全传递事件的方式和全局Qt库互斥量允许你从不同的线程调用Qt的方法。Qt中与线程应用相关的类如表6所示。

表6 Qt中与线程相关的类

使用线程需要Qt提供相应的线程库的支持,因此,在编译安装Qt时,需要加上线程支持选项。

当在Windows操作系统上编译Qt时,线程支持是在一些编译器上的一个选项。在Windows操作系统上编译应用程序时,通过在qconfig.h文件中增加一个选项来解决来解决这个问题。

在Mac OS X和Unix上编译Qt时,你应在运行configure脚本时添加-thread选项。在Unix平台上,多线程程序必须用特殊的线程支持库连接,多线程程序必须连接线程支持库libqt-mt,而不是标准的Qt库。编译应用程序时,你应该使用宏定义QT_THREAD_SUPPORT来编译(如:编译时使用-DQT_THREAD_SUPPORT)。

1、线程类QThread

在 Qt中提供了QThread线程类,它提供了创建一个新线程的方法。线程通过重载QThread::run()函数开始执行的,这一点与Java中的线程类相似。

示例1:一个简单的线程

下面的例子实现了一个简单的继承自QThread的用户线程类,并运行2个线程,线程b在线程a运行完后运行。代码列出如下:

class MyThread : public QThread {  
public: virtual void run();
};
void MyThread::run() //运行线程{
for( int count = 0;
count < 20; count++ ) {
sleep( 1 );
qDebug( "Ping!" );
}
}
int main(){
MyThread a;
MyThread b;
a.start(); //通过调用run()函数来执行
b.start();
a.wait();
b.wait();
}

只有一个线程类是不够的,对于支持多线程的程序来说,还需要保护两个不同的线程对数据的同时访问,因此 Qt 提供了QMutex 类,一个线程可以锁住互斥量,当互斥量被锁住时,将阻塞其它线程访问临界数据,直到这个线程释放互斥量。这样,可以保护临界数据一次只能被一个线程访问。

Qt库互斥量(qApp->lock()和qApp->unlock())是在访问Qt的GUI界面资源时用到的互斥量。在 Qt中没有使用互斥量而调用一个函数通常会导致不可预知的行为。从另外一个线程中调用Qt的一个GUI相关函数需要使用Qt库互斥量。在这种情况下,所有访问图形或窗口系统资源的函数都与GUI相关。如果对象仅被一个线程访问,使用容器类,字符串或者输入/输出类不需要任何互斥量。

2、线程安全的事件传递

在Qt中,一个线程总是一个事件线程,线程从窗口系统中拉出事件并且把它们分发给窗口部件。静态方法QThread::postEvent从线程中邮递事件,而不是从事件线程。事件线程被唤醒并且事件象一个正常窗口系统的事件一样在事件线程中被分发。例如,你可以从不同的线程强制一个窗口部件进行重绘,方法如下:

QWidget *mywidget;QThread::postEvent( mywidget, new QPaintEvent( QRect(0, 0, 100, 100) ) );

上述代码将异步地使mywidget在它区域中重绘一块100*100的正方形区域。

另外,还需要一些机制使得处于等待状态的线程在给定条件下被唤醒。QWaitCondition 类就提供了这种功能。线程等待的条件QWaitCondition满足,QWaitCondition表明发生了什么事情,它阻塞直到这件事情发生。当发生给定的事情时,QWaitCondition 将唤醒等待该事情的所有线程或者唤醒任意一个被选中的线程。(这和POSIX线程条件变量具有相同功能,是Unix上的一种实现。)

示例2:QWaitCondition类应用

下面这个例子的功能是:当你按下按钮,这个程序就会唤醒worker线程,这个线程在按钮上显示工作状态:等待(Waiting)还是正在工作(Working)。当按钮被按下时,worker线程正在工作,那么对线程不产生影响。当run函数再次循环到mycond.wait()时,线程阻塞并等待。当按钮再被按下时,触发slotClicked()函数运行,唤醒等待的线程。

#include <qapplication.h> 
#include <qpushbutton.h> // 全局条件变量
QWaitCondition mycond; // Worker类实现
class Worker : public QPushButton, public QThread{
Q_OBJECT public: Worker(QWidget *parent = 0, const char *name = 0) : QPushButton(parent, name) {
setText("Start Working"); // 将QPushButton继承来的信号与槽slotClicked()连接起来
connect(this, SIGNAL(clicked()), SLOT(slotClicked())); // 调用从QThread继承来的start()方法开始线程的执行
QThread::start();
}
public slots: void slotClicked() { // 唤醒等待这个条件变量的一个线程
mycond.wakeOne();
}
protected: void run() //重载run函数 {
while ( TRUE ) { // 锁定应用程序互斥锁,并且设置窗口标题来表明我们正在等待开始工作
qApp->lock();
setCaption( "Waiting" );
qApp->unlock(); // 等待直到我们被告知可以继续
mycond.wait(); // 如果到了这里,表示我们已经被另一个线程唤醒
qApp->lock();
setCaption( "Working!" );// 设置标题,表示正在工作
qApp->unlock();
}
}
};
int main( int argc, char **argv ){
QApplication app( argc, argv ); // 创建一个worker
Worker firstworker( 0, "worker" );
app.setMainWidget( &worker ); //将worker设置为应用程序的主窗口
worker.show();
return app.exec();
}

当进行线程编程时,需要注意的一些事项:

(1)在持有Qt库互斥量时不要做任何阻塞操作。这将冻结事件循环。

(2)确认你锁定一个递归QMutex的次数等于解锁的次数,不能多也不能少。

(3)在调用除了Qt容器和工具类外的任何东西之前锁定Qt应用程序互斥量。

(4)谨防隐含的共享类,如果你需要在线程之间指定它们,你应该用detach()分离它们。

(5)小心没有被设计成线程安全的Qt类,例如,QPtrList的API接口不是线程安全的,并且如果不同的线程需要遍历一个QPtrList,它们应该在调用QPtrList::first()之前锁住,在到达终点后解锁。

(6)确信仅在GUI线程中创建继承自QWidget、QTimer和QSocketNotifier的对象。在一些平台上,创建在线程中而不是GUI线程的对象永远不会接收到底层窗口系统的事件。

(7)和上面很相似,只在GUI线程中使用QNetwork类。因为所有的QNetwork类都是异步的,没必要把QSocket用在多线程中。

(8)永远不要尝试在不是GUI线程的线程中调用processEvents()函数。这也包括QDialog::exec()、QPopupMenu::exec()、QApplication::processEvents()和其它一些函数。

(9)在你的应用程序中,不要把普通的Qt库和支持线程的Qt库混合使用。这意味着如果你的程序使用了支持线程的Qt库,你就不能连接普通的Qt库、动态的载入普通Qt库或者动态地连接其它依赖普通Qt库的库或者插件。在一些系统上,这样做会导致Qt库中使用的静态数据崩溃。

小结:QT核心编程之Qt线程 的内容介绍完了,希望本节内容随你有所帮助,如果需要更多资料请参考编辑推荐。

QT核心编程之鼠标拖放(4)

QT核心编程之鼠标拖放是本节介绍的内容。QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

拖放提供了一种用户在应用程序之间或之内传递信息的一种简单可视机制。在术语中,这被称为"直接操作模型"。拖放在功能上类似剪贴板的剪切和粘贴机制。拖放机制包括拖动、放下、剪贴板、拖放操作、添加新的拖放类型、高级拖放以及和其它应用程序之间的操作几个方面。下面从这几个方面分别进行说明:

(1)拖动

开始一个拖动,比如是在鼠标移动事件,创建一个适合你的媒体的QDragObject的子类的对象,例如:对于文本使用QTextDrag,对于图片使用QImageDrag。然后调用drag()方法。例如,从一个窗口部件中开始拖动一些文本:

void MyWidget::startDrag()  {  
QDragObject *d = new QTextDrag( myHighlightedText(), this );
d->dragCopy(); //拷贝选中文本 // 不要删除d
}

注意在拖动之后,QDragObject没有被删除。在拖放明显完成后,这个QDragObject需要被保存。因为它还可能需要与其它进程通信。最后 Qt会删除这个对象。如果拥有拖动对象的窗口部件在删除拖动对象之前被删除,那么任何没有完成的放下操作将会被取消,并且拖动对象会被删除。因为这个原因,你应该小心对待对象引用。

(2)放下

为了能在一个窗口部件中接收被放下的媒体,这个窗口部件调用setAcceptDrops(TRUE)(如:在它的构造函数中),并且重载事件处理方法dragEnterEvent()和dropEvent()。对于更复杂的应用程序,重载dragMoveEvent()和 dragLeaveEvent()也是必需的。

例如,当拖动后放下文本或图片时,窗口部件接受并处理放下操作的代码如下:

MyWidget::MyWidget(...) :    QWidget(...)  {  
 ...  setAcceptDrops(TRUE); //接收被放下的媒体
}//当一个拖动正在进行并且鼠标进入这个窗口部件,这个事件处理函数被调用
void MyWidget::dragEnterEvent(QDragEnterEvent* event) {
  event->accept( QTextDrag::canDecode(event) ||  QImageDrag::canDecode(event) );
}//当拖动在这个窗口部件上被放下,这个事件处理器被调用
void MyWidget::dropEvent(QDropEvent* event) {
 QImage image;
 QString text;
 if ( QImageDrag::decode(event, image) ) {//解码图片
insertImageAt(image, event->pos()); //在窗口部件中插入图片
 } else if ( QTextDrag::decode(event, text) ) {
insertTextAt(text, event->pos());
   }
}

(3)剪贴板

QDragObject、QDragEnterEvent、QDragMoveEvent和QDropEvent类都是 QMimeSource(提供类型信息的类)的子类。如果你在QDragObject中基于你的数据进行传递,你不仅可使用拖放,而且还可以使用传统的剪切和粘贴。QClipboard有两个函数:

setData(QMimeSource*)  
QMimeSource* data()const

使用这些函数,你可以把你的拖放初始信息放到剪贴板中:

void MyWidget::copy(){  
QApplication::clipboard()->setData( new QTextDrag(myHighlightedText()) );
}
void MyWidget::paste(){
QString text;
if ( QTextDrag::decode(QApplication::clipboard()->data(), text) )
insertText( text );
}

你甚至能使用QDragObject的子类作为文件I/O部分。例如,如果你的程序有一个QDragObject的子类把CAD设计编码成DXF格式,你可以象下面这样存储和装载这个格式的文件:

void MyWidget::save(){  
QFile out(current_file_name);
out.open(IO_WriteOnly);
MyCadDrag tmp(current_design); // MyCadDrag是QDragObject的子类
out.writeBlock( tmp->encodedData( "image/x-dxf" ) );
} void MyWidget::load(){
QFile in(current_file_name);
in.open(IO_ReadOnly);
if ( !MyCadDrag::decode(in.readAll(), current_design) ) {
QMessageBox::warning( this, "Format error", tr("The file \"%1\" is not in any supported format") .arg(current_file_name) );
}
}

(4)拖放操作

在一些简单的情况下,拖放的目标接收一个被拖动的数据的拷贝,并且由源来决定是否删除初始的拖动对象。这是QDropEvent中的"Copy"操作。目标也可以选择理解其它操作,特别是"Move"和"Link"操作。如果目标理解了"Move"操作,目标负责拷贝和删除操作,源不会尝试删除数据。如果目标理解为"Link"操作,它存储它自己的引用到初始信息中,并且源不会删除初始信息。最通用的拖放操作是在同一个窗口部件中执行一个"Move"操作。

拖动操作的另一个主要用途是当使用一个引用类型,比如text/uri-list,实际上被拖动的数据是文件或对象的引用。

(5)添加新的拖放类型

拖放不仅仅局限于文本和图片,任何信息都可以被拖放。为了在应用程序之间拖放信息,两个应用程序必须指明彼此都能接受和产生的数据格式。这个可以通过使用MIME类型来获得。拖动的源提供一个它能产生的MIME类型列表(按从最合适的到最少合适的顺序排列),并且放下的目标选择一种它能接受的类型。例如,QTextDrag提供了"text/plain"MIME类型(普通的没有格式的文本),还有"text/utf16"和"text/utf8"的Unicode格式的类型。QImageDrag提供了"image/*"类型,*是QImageIO支持的任何一种图片格式,并且 QUriDrag子类提供了"text/uri-list"的支持,它是传输一个文件名列表(或URL)的标准格式。

为了实现一些还没有可用QDragObject子类的信息类型的拖放,首先和最重要的步骤是查找合适的存在格式:IANA(Internet Assigned Numbers Authority)在ISI(Information Sciences Institute)提供了一个MIME媒体类型的分级列表。使用标准的MIME类型将会使你的应用程序现在及未来能更好地与其它软件互相操作。

为了支持另外的媒体类型,从QDragObject或QStoredDrag派生类。当你需要提供多种媒体类型的支持时,从QDragObject派生类。当一个类型足够时,就从更简单的QStoredDrag派生类。

QDragObject的子类将会重载const char*format(int i) const和QByteArray encodedData(const char*mimetype) const成员,并且提供一套方法编码媒体数据,提供静态成员canDecode()和decode()解码输入的数据,QImageDrag的成员函数 bool canDecode(QMimeSource*) const和QByteArraydecode(QMimeSource*) const在子类中需要类似的重载。

QStoredDrag的子类提供了提供一套方法编码媒体数据,静态成员canDecode()和decode()对进入的数据进行解码。

(6)高级拖放

在剪贴板模式中,用户可以剪切或复制资源信息,然后粘贴它。相似地,在拖放模式中,用户可以拖动信息的拷贝或者拖动信息本身到一个新的位置(移动它)。拖放模式对于程序员来说都是更多的复杂性:程序直到放下(粘贴)完成才会知道用户是想剪切还是复制。在应用程序之间拖动,这个没有什么区别,但是在一个应用程序之内进行拖动,应用程序必须小心不要将拷贝粘贴到同一个地方。例如,在同上窗口部件中拖动文本,拖动的开始点和放下事件处理函数应象下面这样重载:

void MyEditor::startDrag(){  
QDragObject *d = new QTextDrag(myHighlightedText(), this);
if ( d->drag() && d->target() != this ) cutMyHighlightedText(); //剪切选中的文本
}
void MyEditor::dropEvent(QDropEvent* event){
QString text;
if ( QTextDrag::decode(event, text) ) {
  if ( event->source() == this && event->action() == QDropEvent::Move ) { // 在同一个窗口部件时,不能使用粘贴拷贝,而应是移到到这个位置
event->acceptAction();
moveMyHighlightedTextTo(event->pos());
 }
else {
pasteTextAt(text, event->pos()); //粘贴拷贝
 }
}
}

一些窗口部件在数据被拖动到它们上面时需要指定"是"或"否"接收。例如,一个CAD程序也许只接收在视图中的文本对象上放下的文本。在这种情况下,dragMoveEvent()被使用并且给定接受或者忽略拖动的区域。代码列出如下:

void MyWidget::dragMoveEvent(QDragMoveEvent* event){   
if ( QTextDrag::canDecode(event) ) {
MyCadItem* item = findMyItemAt(event->pos());
  if ( item )
event->accept();
}
}

(7)和其它应用程序之间的操作

在X11上,拖动使用公有的XDND协议,而Qt在Windows上使用OLE标准,Qt在Mac上使用Carbon拖动管理器。在X11 上,XDND使用MIME,所以不需要转换。Qt的应用编程接口与平台无关。在Windows上,识别MIME的应用程序可以通过使用MIME类型的剪贴板格式名字进行通信。一些Windows应用程序已经对它们的剪贴板格式使用MIME命名规范了。在内部,Qt有能力在专有的剪贴板格式和MIME类型之间转换。在X11上,Qt也支持使用Motif拖放协议的拖动。

小结:QT核心编程之鼠标拖放的内容介绍完了,希望本文对你有所帮助,如果需要更多的内容,请参考编辑推荐。

QT核心编程之键盘焦点(5)

Qt的窗口部件在图形用户界面中按用户的习惯的方式来处理键盘焦点,一个焦点移出事件会被发送给焦点窗口部件(如果有的话)告诉它关于失去焦点的事情。然后一个焦点进入事件被发送给这个窗口部件告诉它刚刚接收到焦点。

QT核心编程之键盘焦点是本节介绍的内容,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

Qt的窗口部件在图形用户界面中按用户的习惯的方式来处理键盘焦点。基本出发点是用户的击键能定向到屏幕上窗口中的任何一个,和在窗口中任何一个部件中。当用户按下一个键,他们期望键盘焦点能够到达正确的位置,并且软件必须尽量满足这种希望。系统必须确定击键定位在哪一个应用程序、应用程序中的哪一个窗口和窗口中的哪一个窗口部件。

1、焦点移动的方式

把焦点定位特殊的窗口部件的习惯方式有:

(1)用户按下Tab键(或者Shift键+Tab键)(或者有时是Enter键)。

(2)用户点击一个窗口部件。

(3)用户按下一个键盘快捷键。

(4)用户使用鼠标滚轮。

(5)用户移动焦点到一个窗口,并且应用程序必须决定窗口中的哪个窗口部件应该得到焦点。

这些移动机制的每个都是不同的,并且不同类型的窗口部件只能接收它们中的一些方式的焦点。下面我们将按次序介绍它们。

(1)Tab或者Shift+Tab.

按Tab键是到目前为止用键盘移动焦点的最通用的方法。有时在输入数据的应用程序中Enter键和Tab键的作用是一样的。我们暂时忽略这一点。

所有窗口系统中的有关焦点的最通用使用方法是:按Tab键移动键盘焦点到每个窗口的窗口部件循环列表中的下一个窗口部件。Tab键按照循环列表的一个方向移动焦点,Shift键+Tab键按另一个方向移动焦点。按Tab键从一个窗口部件到下一个窗口部件移动焦点的次序叫做Tab键次序。

在Qt中,窗口部件循环列表存放在QFocusData类中。每个窗口有一个QFocusData对象,并且当选择合适的 QWidget::FocusPolicy焦点策略的QWidget::setFocusPolicy()被调用的时候,窗口部件自动把它们自己追加到列表的末尾。你可以使用QWidget::setTabOrder()来自定义Tab键控制次序。如果你没有定义这个次序,那么Tab键会按照窗口部件构造的顺序移动焦点。Qt designer工具提供了一个可视化的改变Tab键控制次序的方法。

因为按Tab键是如此的常用,大多数含有焦点的窗口部件应该支持Tab焦点。主要例外情况是几乎没用到的窗口部件,并且在窗口部件上有一些移动焦点的键盘快捷键或者错误处理。

(2)用户点击一个窗口部件。

在使用鼠标或者其它指针设备的计算机中,用鼠标点击一个窗口部件是一种比按Tab键更常用的方法。

当鼠标点击把焦点移到一个窗口部件时,对于编辑器窗口部件,它也会移动文本光标(窗口部件的内部焦点)到鼠标被点击的地点。

鼠标点击移动焦点是大多数窗口部件必须支持的,有时窗口部件需要避免鼠标点击移动焦点。例如:在一个字处理程序中,当用户点击"B"(粗体)工具按钮,键盘焦点应该保留在原来的位置。在Qt中,只有QWidget::setFocusPolicy()函数影响点击焦点。

(3)用户按下一个键盘快捷键。

使用键盘快捷键来移动焦点不是很常用。这种情况可能会隐含地发生在打开的模式对话框中,但是也会显式地发生在使用焦点加速器中,例如在QLabel::setBuddy()、QGroupBox和QTabBar提供的加速器中。

用户想让焦点跳到的窗口部件都应支持快捷键焦点。例如:一个Tab对话框为它的每一个页提供键盘快捷键,所以用户可以按下比如Alt+P来跳到打印页面。但只能有少量的快捷键,并且为命令提供键盘快捷键也很重要,如:在标准快捷键列表中,Alt+P也可以用来粘贴、播放或打印。

(4)用户使用鼠标滚轮。

在Microsoft Windows上,鼠标滚轮的用法是一直由有键盘焦点的窗口部件处理。在Mac OS X和X11上,它由获得其它鼠标事件的窗口部件处理。

Qt处理这种平台差异的方法是当滚轮被使用时,让窗口部件移动键盘焦点。每个窗口部件上有合适的焦点策略,应用程序可以在Windows、Mac OS X和X11上按照习惯正确地处理焦点。

(5)用户移动焦点到这个窗口。

在这种情况下,应用程序必须决定窗口中的哪一个窗口部件接收焦点。Qt自动实现这样的做法:如果焦点以前在这个窗口中,那么窗口中有焦点的最后一个窗口部件应该重新获得焦点。如果以前焦点就从来没有来到过这个窗口,并且你知道焦点应该从哪里开始,就在你调用QWidget::show()显示它之前,在应该接收焦点的窗口部件上调用QWidget::setFocus()。如果你不知道,Qt会选择一个合适的窗口部件。

2、焦点策略及操作函数

键盘焦点的策略及操作函数说明如下:

(1)焦点策略属性变量

focusPolicy 焦点策略属性变量保存的是窗口部件接收键盘焦点的策略。如果窗口部件通过tab来接收键盘焦点,这个策略就是QWidget::TabFocus;如果窗口部件通过点击来接收键盘焦点,这个策略就是QWidget::ClickFocus;如果窗口部件上述两种方式都使用,是 QWidget::StrongFocus;并且如果它不接收焦点(QWidget的默认值),是QWidget::NoFocus。

如果一个窗口部件处理键盘事件,你必须使键盘焦点生效。这通常在窗口部件的构造函数中完成。例如,QLineEdit的构造函数调用setFocusPolicy(QWidget::StrongFocus)。

(2)voidQWidget::setFocus() [虚槽]

函数setFocus()把键盘输入焦点赋给这个窗口部件(或者它的焦点代理)。

首先,一个焦点移出事件会被发送给焦点窗口部件(如果有的话)告诉它关于失去焦点的事情。然后一个焦点进入事件被发送给这个窗口部件告诉它刚刚接收到焦点。(如果焦点移出和进入的窗口部件是同一个的话,就什么都没有发生。)

函数setFocus()会把焦点给一个窗口部件,而不管它的焦点策略,但是不会清空任何键盘捕获(grabKeyboard())。请注意如果窗口部件是被隐藏的,它将不接收焦点。

(3)voidQWidget::setFocusProxy(QWidget * w) [虚]

函数setFocusProxy设置这个窗口部件的焦点代理为窗口部件w。如果w为0,这个函数重置这个窗口部件没有焦点代理。

一些窗口部件,比如QComboBox,能够"拥有焦点",但创建一个子窗口部件来实际处理这个焦点。例如,QComboBox创建了一个QLineEdit来处理焦点。

当"这个窗口部件"获得焦点时,setFocusProxy()设置的这个窗口部件实际获得焦点。如果有了一个焦点代理,focusPolicy()、setFocusPolicy()、setFocus()和hasFocus()都在这个焦点代理上操作。

小结:QT核心编程之键盘焦点的内容介绍完了,希望本文对你有所帮助,如果需要参考更多的内容,请看编辑推荐。

QT核心编程之会话管理(6)

会话管理器为用户发出命令给它的客户。这些命令会使客户提交没有保存的变化(如:保存打开的文件),使客户为将来的会话保存状态或关机。这样的一些操作被称为会话管理。

QT核心编程之会话管理是本节介绍的内容,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

一个会话是一组正在运行的应用程序,它们每个都一个特殊的状态。会话被一个称为会话管理器(Session manager)的服务程序来控制。在会话里每个参与的应用程序被称为会话客户。会话管理器为用户发出命令给它的客户。这些命令会使客户提交没有保存的变化(如:保存打开的文件),使客户为将来的会话保存状态或关机。这样的一些操作被称为会话管理。

通常情况下,一个会话由用户在其桌面上同时运行的所有应用程序组成。在Unix/X11下,一个会话可能包括运行在不同计算机和多个显示器上的应用程序。

1、会话管理

(1)关闭一个会话

一个会话可以被会话管理器关闭,通常在用户logout时为用户关闭的。一个系统可以在紧急情况下执行自动关闭,例如:在掉电时。正常关机和掉电关机有很大的不同,在正常关机时,用户可能想与应用程序交互,并确定哪些文件应该保存,哪些应该删除。在掉电关机时,没有时间进行交互,甚至于用户不在现场。

(2)不同平台上的协议和支持

Mac OS X和MS-Windows对应用程序还没有完全的会话管理,如:没法恢复以前的会话。它们支持正常的logout,在得到用户确认后,应用程序有机会取消进程。这是与QApplication::comm.itData()方法相对应的功能。X11自从X11R6后,支持完整的会话管理。

(3)让会话管理与Qt一起工作

通过重载QApplication::comm.itData()来使用你的应用程序参加正常的logout处理。如果你仅应用在MS-Windows平台上,就只能提供这个重载了。你的应用程序最好提供一个如图5的程序关闭对话框。

图5 程序关闭对话框

对于完全的会话管理(目前仅X11R6),你还应该关心应用程序的状态存储和下一个会话生命周期恢复状态。

2、测试和调试会话管理

在Mac OS X和Windows上的会话管理支持由于这些操作系统本身的这种功能的缺乏而受到限制。为了简单地关闭会话并验证你的应用程序是否如期望的那样执行,你最好启动一个其它的应用程序。这个应用程序将随后得到关闭消息,这样允许你取消关闭。

在Unix上你能使用一个支持标准的X11R6会话管理,或使用X联盟提供的会话管理器xsm。xsm是标准X11R6安装的一部分。它是一个具有图形界面的会话管理器,你可能用它来管理会话。

下面是使用xsm的一个简单的方法:

(1)运行X11R6.

(2)在你的home目录下将创建仅包含下面一行的.xsmstartup文件:

xterm 

这将告诉xsm的default/failsafe(缺省/失败安全)会话仅有xterm。否则,xsm将尝试触发包括窗口管理器twm在内多个客户,这没有什么用途。

(3)现在从另一个终端窗口启动xsm。一个会话管理器和xterm将出现。xterm有一个其它的shell没有的属性:在xterm的shell里,SESSION_MANAGER环境变量指向了你刚启动的会话管理器。

(4)在新的xterm窗口中启动你的应用程序,应用程序将自动连接它到会话管理器。你能使用ClientList按钮检查连接是否成功。

注意:当你启动或关闭会话管理的客户时,不要保护ClientList为打开状态。否则,xsm可能崩溃。

(5)使用会话管理器的Checkpoint和Shutdown按钮的不同设置检查你的应用程序执行行为。本地保存类型表示客户端应该保存它们的状态。它对应着QApplication::saveState()函数。全局保存类型请求应用程序保存它们没存储的变化到永久的全局可访问的存储中。它触发QApplication::commitData()。

(6)在用户桌面上xsm是一个有用的会话管理器。作为一个测试环境它是稳定的而有用的。

小结:QT核心编程之会话管理的内容介绍完了,希望本既然内容对你有所帮助,更多资料参考在下面的编辑推荐。

QT核心编程之调试技术(7)

Qt应用程序的调试可以通过DDD进行跟踪调试和打印各种调试或警告信息。DDD(Data Display Debugger)是使用gdb调试工具的图形工具,它安装在Linux操作系统中,使用方法可参考DDD的帮助文档。

QT核心编程之调试技术是本节要介绍的内容,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容。

Qt应用程序的调试可以通过DDD进行跟踪调试和打印各种调试或警告信息。DDD(Data Display Debugger)是使用gdb调试工具的图形工具,它安装在Linux操作系统中,使用方法可参考DDD的帮助文档。下面说明如何打印各种调试或警告信息

1、命令行参数

当你运行Q应用程序时,你可以指定几个命令行参数来帮助你调试。这几个命令行参数说明如下:

-nograb 应用程序不再捕获鼠标或者键盘。当程序在Linux下运行在gdb调试器中时这个选项是默认的。

-dograb 忽略任何隐含的或明显得-nograb。即使-nograb出现在命令行的最后,-dograb也会超过-nograb生效的。

-sync 在X同步模式下运行应用程序。同步模式强迫X服务器立即执行每一个X客户端的请求,而不使用缓存优化。它使得程序更加容易测试并且通常会更慢。-sync模式只对X11版本的Qt有效。

2、打印警告和调试消息

Qt使用三个全局函数qDebug、qWarning和qFatal来打印警告和调试信息到标准错误输出stderr(它在缺省情况下为显示屏,也可指定为文件)。 这三个函数说明如下:

qDebug()用来打印调试信息,在调试版本中输出信息,在发布版本中,函数将不起作用。

qWarning()用来在程序发生错误时打印警告信息。

qFatal()用来打印致命错误消息并且退出。

这些函数的Qt实现在Unix/X11下把文本打印到标准错误输出(stderr),在Windows下会打印到调试器。你可以通过安装一个消息处理器,qInstallMsgHandler()来接收这些函数。

因为这3个函数的实现类似,这里只分析函数qDebug,qDebug函数的参数格式与函数printf类似,打印格式化字符串。qDebug函数列出如下(在src/tools/qglobal.cpp中):

static QtMsgHandler handler = 0;  //指向用户定义的打印输出函数的句柄  
static const int QT_BUFFER_LENGTH = 8196; //内部buffer长度
void qDebug( const char *msg, ... ) //msg格式化的需要打印的字符串
{
char buf[QT_BUFFER_LENGTH];
va_list ap;
va_start( ap, msg );//使用可变的参数链表
#if defined(QT_VSNPRINTF)
QT_VSNPRINTF( buf, QT_BUFFER_LENGTH, msg, ap );
#else vsprintf( buf, msg, ap ); //将需要打印的信息放入到buf中
#endif va_end( ap );
if ( handler ) { //如果用户指定的输出函数存在,使用它来输出信息
(*handler)( QtDebugMsg, buf );
} else {
#if defined(Q_CC_MWERKS)
mac_default_handler(buf); //mac系统下的缺省输出函数
#elif defined(Q_OS_TEMP)
QString fstr( buf );
OutputDebugString( (fstr + "\n").ucs2() );
#else fprintf( stderr, "%s\n", buf ); // 输出到stderr#endif
}
}

在src/tools/qglobal.h中定义了QtMsgHandler的函数类型,并将函数qInstallMsgHandler定义为从动态库中输出函数名。这两个定义列出如下:

typedef void (*QtMsgHandler)(QtMsgType, const char *);
// Q_EXPORT表示动态库中输出这个函数名Q_EXPORT QtMsgHandler qInstallMsgHandler( QtMsgHandler );

函数qInstallMsgHandler被用户用来定义一个安装处理函数,并返回以前定义的消息处理函数的指针。在一个应用程序中只能定义一个消息处理函数。恢复以前的消息处理函数时,调用qInstallMsgHandler(0)。函数列出如下(在src/tools/qglobal.cpp中):

QtMsgHandler qInstallMsgHandler( QtMsgHandler h ){   
QtMsgHandler old = handler;
hhandler = h;
return old;
}

示例:应用qInstallMsgHandler

下面的例子说明如果在一个应用程序中安装自己的程序运行信息输出函数。这个例子先定义了信息输出函数myMessageOutput,然后,在程序的main函数中安装了信息输出函数。当这个应用函数运行时,就会使用函数myMessageOutput输出运行信息。代码如下:

#include <qapplication.h> 
#include <stdio.h>
#include <stdlib.h>
void myMessageOutput( QtMsgType type, const char *msg )//定义信息输出函数{
switch ( type ) {
case QtDebugMsg: //输出调试信息
fprintf( stderr, "Debug: %s\n", msg );
break;
case QtWarningMsg: //输出警告信息
fprintf( stderr, "Warning: %s\n", msg );
break;
case QtFatalMsg: //输出致命信息
fprintf( stderr, "Fatal: %s\n", msg );
abort(); //中断运行,退出程序
}
}
int main( int argc, char **argv ){
qInstallMsgHandler( myMessageOutput ); //安装信息输出函数
QApplication a( argc, argv );
...
return a.exec();
}

还有另外两个打印对象信息的调试函数QObject::dumpObjectTree()和QObject::dumpObjectInfo()。它们只在程序调试版本下,输出信息,在发布版本中,这两个函数不起作用。函数QObject::dumpObjectInfo()打印一个对象信号连接等方面的信息。函数QObject::dumpObjectTree()打印出子对象树。

3、调试宏

在程序运行中还常使用宏Q_ASSERT和Q_CHECK_PTR来输出信息,这两个宏说明如下:

(1)Q_ASSERT(b)中的b是一个布尔表达式,当b是FALSE的时候,打印出类似的警告信息:"ASSERT:‘b’ infile file.cpp (234)"。

(2)Q_CHECK_PTR(p)中的p是一个指针。如果p是空的话,打印出类似的警告信息:"In file file.cpp, line 234: Out of memory"。

宏Q_ASSERT实质上是调用函数qFatal或qWarning输出信息,列出如下(在src/tools/qglobal.h中):

#if !defined(Q_ASSERT)  
# if defined(QT_CHECK_STATE)
# if defined(QT_FATAL_ASSERT)
# define Q_ASSERT(x) //打印x,文件名,在程序源代码中的行号
# else
# define Q_ASSERT(x)
# endif
# else
# define Q_ASSERT(x)
# endif#endif

宏Q_CHECK_PTR实质上调用函数qWarning输出信息,宏定义Q_CHECK_PTR列出如下(在src/tools/qglobal.h中):

#if defined(QT_CHECK_NULL)  
# define Q_CHECK_PTR(p) (qt_check_pointer#else# define Q_CHECK_PTR(p)
#endif Q_EXPORT bool qt_check_pointer( bool c, const char *, int );

函数qt_check_pointer实现信息输出操作,函数列出如下(在src/tools/qglobal.cpp中):

bool qt_check_pointer( bool c, const char *n, int l ){    if ( c ) qWarning( "In file %s, line %d: Out of memory", n, l );   
return TRUE;}

示例2:运行宏Q_ASSERT和Q_ASSERT

宏Q_ASSERT和Q_ASSERT常用来检测程序错误,下面例子使用了这两个宏:

char *alloc( int size ){   
Q_ASSERT( size > 0 ); //如果size > 0表达式不成立,打印警告信息
char *p = new char[size];
Q_CHECK_PTR( p ); //如果指针p为空,打印警告信息
return p;
}

Qt基于不同的调试标记打印不同类型的警告信息。Qt使用了下面的宏定义说明了不同的调试标记(在src/tools/qglobal.h中):

QT_CHECK_STATE:检测一致的/期望的对象状态

QT_CHECK_RANGE:检测变量范围错误

QT_CHECK_NULL:检测危险的空指针

QT_CHECK_MATH:检测危险的数学,比如被0除

QT_NO_CHECK:关闭所有的QT_CHECK_...标记

QT_DEBUG:使调试代码生效

QT_NO_DEBUG:关闭QT_DEBUG标记

默认情况下,QT_DEBUG和所有的QT_CHECK标记都是打开的。如果要关闭QT_DEBUG,请定义QT_NO_DEBUG。如果要关闭QT_CHECK标记,请定义QT_NO_CHECK。

示例3:打印不同类型的警告信息

下面的例子根据不同的宏定义打印不同类型的警告信息。代码如下:

void f( char *p, int i ){  
#if defined(QT_CHECK_NULL) //检测危险的空指针
if ( p == 0 ) qWarning( "f: Null pointer not allowed" );
#endif #if defined(QT_CHECK_RANGE) //检测变量范围错误
if ( i < 0 )
qWarning( "f: The index cannot be negative" );
#endif}

小结:QT核心编程之调试技术的内容介绍完了,需要本文能对你有所帮助,需要更多资料的话,请参考编辑推荐

相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 
   
Visual C++编程命名规则
任何时候都适用的20个C++技巧
C语言进阶
串口驱动分析
轻轻松松从C一路走到C++
C++编程思想
更多...   


C++并发处理+单元测试
C++程序开发
C++高级编程
C/C++开发
C++设计模式
C/C++单元测试


北京 嵌入式C高质量编程
中国航空 嵌入式C高质量编程
华为 C++高级编程
北京 C++高级编程
丹佛斯 C++高级编程
北大方正 C语言单元测试
罗克韦尔 C++单元测试
更多...   
 
 
 
 
每天2个文档/视频
扫描微信二维码订阅
订阅技术月刊
获得每月300个技术资源
 
 

关于我们 | 联系我们 | 京ICP备10020922号 京公海网安备110108001071号