QTableWidget基本功能总结

7 5 月, 2011 by edsionte 2 comments »

QTableWidget类提供了一种基于条目(item)的表格视图模型,在该部件中数据以item为基本单位,每条数据(item)对应一个QTableWidgetItem类的对象,所有数据形成的item组成整个表格。接下来我们创建一个用来显示学生信息的表格,以此为例说明TableWidget的一些常用功能。

1.创建QTableWidget

首先创建studentInfo类,然后在Qt Desinger模式下创建一个QTableWidget部件,对其命名为stuTableWidget。通过在该部件上点击右键创建列项表头,创建完毕后也就同时指定了该表格的列项数。如下:

2.初始化

除了直接在设计模式下创建行数和列数外,我们还可以通过调用相应的方法来设定。比如我们通过setRowCount方法在studentInfo类的构造方法中即可指定行项数。

ui->stuTableWidget->setRowCount(30);

该方法在创建行的同时会自动创建一个用来显示行号的列项,如下:

如果我们的表格只用来显示信息,并不涉及对相应信息的修改,则可以通过下述方法将表格设置为只读模式:

 ui->stuTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);

setEditTriggers()是QAbstractItemView类中的一个方法,通过向该方法传递相应的参数以设置item的一些属性,比如NoEditTriggers参数可将item设置为只读,DoubleClicked代表双击时item可修改。而QTableWidget继承了QAbstractItemView方法,因此它可以使用该函数。

3.信息显示

表格视图中数据的获取随用途的不同而不同。如果使用于C/S模型的客户端,那么表格中的信息需要从服务器端发送到本地,再相应解析;如果使用在数据库中,则需要从数据库中获取相应信息。这里假定数据已经到达本地,我们通过下面的方法来显示数据信息。

void studentInfo::showInfo()
{
    QTableWidgetItem *tmpItem;

    tmpItem = new QTableWidgetItem(QString("04065061"));
    ui->stuTableWidget->setItem(0, 0, tmpItem);

    tmpItem = new QTableWidgetItem(QString("edsionte"));
    ui->stuTableWidget->setItem(0, 1, tmpItem);

    tmpItem = new QTableWidgetItem(QString("1988.01.28"));
    ui->stuTableWidget->setItem(0, 2, tmpItem);

    tmpItem = new QTableWidgetItem(QString("male"));
    ui->stuTableWidget->setItem(0, 3, tmpItem);

    tmpItem = new QTableWidgetItem(QString("Xi'an Institute of Posts and Telecommunications"));
    ui->stuTableWidget->setItem(0, 4, tmpItem);
}

上述的showInfo方法为第一行设定了相应信息,我们可以看到表格的一行中每个具体的列项都对应一个QTableWidgetItem对象,并通过在setItem方法中指定行号和列号将该item对象设置到表格的具体位置。在上述的showInfo方法中,我们分别通过该方法创建了第一行的第一到第五列的数据(行列下表从0开始)。

4.为表格数据添加右键菜单

有时候我们想通过点击鼠标右键对表格数据进行一些其他操作,比如复制、查看详情等,我们可以按照下面的方法来实现。为了实现点击右键弹出菜单这个功能,我们必须在类studentInfo类中声明一个菜单变量popMenu和一个菜单选项变量action。

class studentInfo : public QMainWindow
{
…………
private:
    Ui::studentInfo *ui;
    QMenu *popMenu;
    QAction *action;

private slots:
    void on_stuTableWidget_customContextMenuRequested(QPoint pos);
…………
};

声明完毕后,我们在studentInfo类的构造函数中对其进行初始化,如下:

    ui->stuTableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    popMenu = new QMenu(ui->stuTableWidget);
    action = new QAction("Copy", this);

setContextMenuPolicy方法用来设置widget菜单项的显示方法,而CustomContextMenu是唯一与邮件菜单有关的参数,因此这里我们将菜单显示方法设置为该类型。如果widget设置为CustomContextMenu时,当在数据上点击右键时就会发送customContextMenuRequested ( const QPoint & pos )信号,该信号还会捕捉到点击右键的位置,并用pos参数来存储。与此信号关联的槽函数我们定义如下:

void studentInfo::on_stuTableWidget_customContextMenuRequested(QPoint pos)
{
    popMenu->addAction(action);
    popMenu->exec(QCursor::pos());
}

我们首先将菜单选项action添加到邮件弹出菜单popMenu中,再通过exec方法在pos()位置显示该邮件菜单,pos()返回的位置即为点击鼠标的位置。

现在,如果点击右键菜单选项并不会发生任何动作,这是因为我们并没有关联相应的槽函数。由于具体的菜单选项不同,其函数的实现也不同,这里我们只给出框架,如下:

void studentInfo::rightClickedOperation()
{
    //do something
}

定义好槽函数,最关键的是与相应的信号连接。对于上述两个槽函数,我们可以使用两种方法进行信号和槽的关联:在Qt Desinger模式下添加或手动进行connect关联。对于customContextMenuRequested信号,我们使用前种方法实现信号和槽的关联;对于右键菜单选项的功能实现,我们可以通过connect函数实现,如下:

connect(action, SIGNAL(triggered()), this, SLOT(rightClickedOperation()));

分治算法之快速排序

4 5 月, 2011 by edsionte 无评论 »

快速排序算法也是基于分治思想的一种排序算法,它的基本操作即为比较-交换。

快速排序算法的基本思想是从待排序的序列中选取一个比较标准K(通常选取第一个元素),然后将其余元素依次跟K进行比较。在比较的过程中将大于K的元素移到K的后面,将小于K的元素移到K的前面,最后的结果是将原始序列分为两个子序列,而K元素则恰好位于两个子列中间。上述过程称为一趟快速排序,接下来依次为两个子序列进行快速排序,依次递归。当子序列的长度小于1时,递归停止。此时,原始序列已经成为一个有序的序列了。

根据上面的思想,快速排序算法的代码实现如下所示。quickSort函数对原始序列递归进行快速排序,每次排序时先通过partiton函数得到序列中p到r元素间的分界点q,然后再分别对两个子序列p到q-1和q+1到r进行快速排序。

void quickSort(int *a, int p, int r)
{
	if (p < r) {
		int q = partition(a, p, r);
		quickSort(a, p, q - 1);
		quickSort(a, q + 1, r);
	}
}

partition函数是快速排序算法的关键。该函数选取待排序序列的第一个元素作为基准,通过反复的比较-交换将p到r之间的元素分成两组子序列,一组子序列的元素全部小于x,另一组子序列的元素全部大于x。

在具体的比较-交换过程中,设置两个记录点low和high,并在初始时将基准保存到x中。然后不断进行下面两种扫描:

1.将high从右至左扫描,直到a[high] < x为止,由于此时的a[high]是第一个小于基准x的元素,因此将a[high]和x交换。

2.将low从左至右扫描,直到a[low] >= x为止,由于此时的a[low]是第一个不小于基准x的元素,因此将a[low]和x交换。

当low小于high时会一直持续上述两种扫描,否则称其完成了一次划分过程。每一次的划分过程就会得到分界位置,返回为quickSort函数。

int partition(int *a, int p, int r)
{
	int x, low, high;

	x = a[p];
	low = p;
	high = r;

	while (low < high) {
		while (low < high && a[high] >= x)
			high--;
		if (low < high) {
			a[low] = a[high];
			a[high] = x;
			low++;
		}
		output_data(a, n);

		while (low < high && a[low] < x)
			low++;
		if (low < high) {
			a[high] = a[low];
			a[low] = x;
			high--;
		}
		output_data(a, n);
	}
	a[low] = x;
	return low;
}

在partition函数中,选择第一个元素p作为基准可以保证该函数正常退出。如果选取最后一个元素r作为基准,而该元素又恰好是最大元素,那么partition函数就会返回r,这使得quickSort无限递归下去。完整的代码可在这里下载

动手实践字符设备驱动

25 4 月, 2011 by edsionte 7 comments »

今年带软件08级童鞋LinuxOS试验的时候,导师让我写一个最简单的字符设备驱动,以便让从未接触过的初学者快速入门。代码参考如下。

字符设备驱动程序:

#include < linux/init.h >
#include < linux/module.h >
#include < linux/types.h >
#include < linux/fs.h >
#include < asm/uaccess.h >
#include < linux/cdev.h >

MODULE_AUTHOR("Edsionte Wu");
MODULE_LICENSE("GPL");

#define MYCDEV_MAJOR 231 /*the predefined mycdev's major devno*/
#define MYCDEV_SIZE 100

static int mycdev_open(struct inode *inode, struct file *fp)
{
	return 0;
}

static int mycdev_release(struct inode *inode, struct file *fp)
{
	return 0;
}

static ssize_t mycdev_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
	unsigned long p = *pos;
	unsigned int count = size;
	int i;
	char kernel_buf[MYCDEV_SIZE] = "This is mycdev!";

	if(p >= MYCDEV_SIZE)
		return -1;
	if(count > MYCDEV_SIZE)
		count = MYCDEV_SIZE - p;

	if (copy_to_user(buf, kernel_buf, count) != 0) {
		printk("read error!\n");
		return -1;
	}

	/*
	for (i = 0; i < count; i++) {
		__put_user(i, buf);//write 'i' from kernel space to user space's buf;
		buf++;
	}
	*/

	printk("edsionte's reader: %d bytes was read...\n", count);
	return count;

}

static ssize_t mycdev_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
	return size;
}

/*filling the mycdev's file operation interface in the struct file_operations*/
static const struct file_operations mycdev_fops =
{
	.owner = THIS_MODULE,
	.read = mycdev_read,
	.write = mycdev_write,
	.open = mycdev_open,
	.release = mycdev_release,
};

/*module loading function*/
static int __init mycdev_init(void)
{
	int ret;

	printk("mycdev module is staring..\n");

	ret=register_chrdev(MYCDEV_MAJOR,"edsionte_cdev",&mycdev_fops);
	if(ret<0)
	{
		printk("register failed..\n");
		return 0;
	}
	else
	{
		printk("register success..\n");
	}

	return 0;
}

/*module unloading function*/
static void __exit mycdev_exit(void)
{
	printk("mycdev module is leaving..\n");
	unregister_chrdev(MYCDEV_MAJOR,"edsionte_cdev");
}

module_init(mycdev_init);
module_exit(mycdev_exit);

Makefile文件:

obj-m:=mycdev.o
PWD:=$(shell pwd)
CUR_PATH:=$(shell uname -r)
KERNEL_PATH:=/usr/src/linux-headers-$(CUR_PATH)

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

用户态测试程序:

#include < stdio.h >
#include < sys/types.h >
#include < sys/stat.h >
#include < fcntl.h >
#include < stdlib.h >

int main()
{
	int testdev;
	int i, ret;
	char buf[15];

	testdev = open("/dev/mycdev", O_RDWR);

	if (-1 == testdev) {
		printf("cannot open file.\n");
		exit(1);
	}

	if (ret = read(testdev, buf, 15) < 15) {
		printf("read error!\n");
		exit(1);
	}

	printf("%s\n", buf);

	close(testdev);

	return 0;
}

使用方法:

1.make编译mycdev.c文件,并插入到内核;
2.通过cat /proc/devices 查看系统中未使用的字符设备主设备号,比如当前231未使用;
3.创建设备文件结点:sudo mknod /dev/mycdev c 231 0;具体使用方法通过man mknod命令查看;
4.修改设备文件权限:sudo chmod 777 /dev/mycdev;
5.以上成功完成后,编译本用户态测试程序;运行该程序查看结果;
6.通过dmesg查看日志信息;

C/S模型的健壮性-为僵死进程收尸

21 4 月, 2011 by edsionte 无评论 »

并发服务器可以同时处理多个客户端的请求,服务器对客户端网络请求的处理是通过fork子服务进程来完成的。当有多个客户请求时,主服务器进程就会产生多个子服务器进程。当这些子服务器进程处理完客户请求时,就终止运行了。由于主服务器进程(也就是这些子服务器进程的父亲)仍然在运行,因此这些终止的子服务器进程在主服务器退出之前就成为了僵死进程。通常服务器进程都长时间处于运行状态,所以这些僵死进程就会一直存在于系统内。如果不及时清除这些僵死进程,他们将占用内核的空间,最终可能会耗尽系统资源。本文将描述如何清理这些僵死进程。

1. 僵死进程的产生

下面将通过一个简单的C/S模型回射程序来演示僵死进程的产生。我们首先将服务器置于后台运行,然后再查看当前终端(pts/3)下的进程状态:

edsionte@edsionte-desktop:~/echoCS$ ps -o pid,ppid,args,stat,wchan
  PID  PPID COMMAND                     STAT WCHAN
 5071  2305 bash                        Ss   wait
 6146  5071 ./server                    S    inet_csk_wait_for_connect
 6149  5071 ps -o pid,ppid,args,stat,wc R+   -

由上可以看出,服务器进程处于等待连接的状态,此刻它正在监听客户请求。接着运行客户端程序,连接成功后输入任意的字符串,可以看到服务器和客户端通信正常。

edsionte@edsionte-desktop:~/echoCS$ ./client 127.0.0.1
received a connection from:127.0.0.1
ps
ps

我们在另一个终端下,查看位于终端pts/3下客户端和服务器进程的状态。

edsionte@edsionte-desktop:~/echoCS$ ps -t pts/3 -o pid,ppid,args,stat,wchan
  PID  PPID COMMAND                     STAT WCHAN
 5071  2305 bash                        Ss   wait
 6146  5071 ./server                    S    inet_csk_wait_for_connect
 6150  5071 ./client 127.0.0.1          S+   n_tty_read
 6151  6146 ./server                    S    sk_wait_data

由于客户进程发来连接请求,因此服务器进程fork了一个子服务器进程,从pid和ppid可以看到两个服务器进程之间的父子关系。父子进程此刻都处于等待状态,不过他们等待的目标不同:父进程监听客户请求,子进程则阻塞于read函数等待套接字数据的来临。

接下来通过发信号SIGINT(通过键盘上的ctrl+c可发出此信号)使客户进程终止。

edsionte@edsionte-desktop:~/echoCS$ ./client 127.0.0.1
received a connection from:127.0.0.1
ps
ps
^C

我们在另一个终端查看pty/3终端当前进程的状态:

edsionte@edsionte-desktop:~/echoCS$ ps -t pts/3 -o pid,ppid,args,stat,wchan
  PID  PPID COMMAND                     STAT WCHAN
 5071  2305 bash                        Ss+  n_tty_read
 6146  5071 ./server                    S    inet_csk_wait_for_connect
 6151  6146 [server]           Z    exit

可以看到,子服务器进程现在处于僵死状态。目前我们只有运行了一次客户端程序,如果一个服务器的网络请求繁忙,则会产生很多僵死进程。

2.处理僵死进程

当子进程终止时会给父进程发送SIGCHLD信号,因此我们可以利用信号处理函数捕获这个信号并对僵死进程进行处理。我们知道在父进程中调用wait函数可以防止先于父进程终止的子进程编程僵死进程,因此我们的信号捕获函数如下所示:

void sig_zchild(int signo)
{
	pid_t pid;
	int stat;

	pid = wait(&stat);
        printf("child %d terminated\n", pid);
	return;
}

并且我们需要适当的修改服务器程序,在accept函数调用之前调用signal函数:

	if(listen(sockfd, BACKLOG) == -1) {

		printf("listen error!\n");
		exit(1);
	}

	if (signal(SIGCHLD, sig_zchild) == SIG_ERR) {
		printf("signal error!\n");
		exit(1);
	}

	while (1) {

		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr,
&sin_size)) == -1) {

			printf("accept error!\n");
			continue;
		}
		…… ……
	}//while

我们下面进行测试,我们首先在终端后台运行服务器程序,然后同时在其他多个终端分别运行客户端程序。当前终端的进程状态如下:

edsionte@edsionte-desktop:~/echoCS$ ps
  PID TTY          TIME CMD
 6699 pts/0    00:00:00 bash
 6926 pts/0    00:00:00 server
 6947 pts/0    00:00:00 server
 6966 pts/0    00:00:00 server
 6985 pts/0    00:00:00 server
 6986 pts/0    00:00:00 ps

然后我们分别在运行客户程序的终端杀死客户端进程。再次查看当前终端下的进程状态,可以发现并没有僵死的子服务进程。因为每个子进程在终止时发送SIGCHLD信号都会被父进程中的信号处理函数捕获。

3.僵死进程处理的加强版

上述的客户端进程每次向服务器端只发出一个连接请求,当客户端终止时子服务器进程将终止;如果一个客户端向服务器发出多个连接请求,当该客户端终止时多个子服务器进程将同时终止,也就是说父服务器进程将面临同时处理多个SIGCHLD信号的情况。

按照上述的特殊客户端,我们将回射C/S模型中的客户端适当修改,使运行一次客户端就连接服务器5次,这样就会产生相应的5个子服务器进程。

	int i;
	for (i = 0; i < 5; i++) {
		if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
              		printf("socket error!\n");
         		exit(1);
         	}
	
		serv_addr.sin_family = AF_INET;
          	serv_addr.sin_port = htons(SERV_PORT);
         	serv_addr.sin_addr = *((struct in_addr *)host->h_addr);

            	if(connect(sockfd, (struct sockaddr *)&serv_addr,
					sizeof(struct sockaddr)) == -1) {
			printf("connect error!\n");
	          	exit(1);
            	}
	}

	str_cli(stdin, sockfd);

运行服务器程序和刚刚修改过的客户端程序,我们作如下测试:

edsionte@edsionte-desktop:~/echoCS$ ps -t pts/0 -o pid,ppid,args,stat
  PID  PPID COMMAND                     STAT
 2651  2649 bash                        Ss
 2861  2651 ./server                    S
 2865  2651 ./mclient 127.0.0.1         S+
 2866  2861 ./server                    S
 2867  2861 ./server                    S
 2868  2861 ./server                    S
 2869  2861 ./server                    S
 2870  2861 ./server                    S
edsionte@edsionte-desktop:~/echoCS$ kill 2865
edsionte@edsionte-desktop:~/echoCS$ ps -t pts/0 -o pid,ppid,args,stat
  PID  PPID COMMAND                     STAT
 2651  2649 bash                        Ss+
 2861  2651 ./server                    S
 2867  2861 [server]           Z
 2868  2861 [server]           Z
 2869  2861 [server]           Z
 2870  2861 [server]           Z

当杀死客户端进程后,只有一个子服务器进程被SIGCHLD信号处理函数捕获处理,其他四个子服务器进程仍然处于僵死状态。出现这种现象的原因是,主服务器进程中的信号处理函数使用了wait函数,该函数会一直阻塞到出现第一个终止的子进程。也就是说,该函数只等待第一个终止的子进程并返回。

本客户端程序终止后,将同时产生5个SIGCHLD信号,而wait函数只处理其中一个,解决这个问题的办法是使用waitpid函数。该函数将周期性的检查是否有子进程终止,也就是说可以等待所有的终止子进程。修改后的服务器信号处理函数如下:

void sig_zchild(int signo)
{
	pid_t pid;
	int stat;

	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);

	return;
}

按此修改后SIGCHLD信号处理函数后,就可以避免留下僵死进程。

C/S模型中的并发服务器

20 4 月, 2011 by edsionte 无评论 »

网络通信程序中最经典的模型就是客户端/服务器(C/S)模型。该模型中的服务器程序通常处于长时间运行的状态,当客户端程序主动对服务器程序发出网络请求时,服务器程序才做出具体的回应。也就是说,服务器大多数处于等待状态,只有收到客户端的网络请求才被唤醒,当处理完客户端的请求时候,又将处于等待状态。

上述同时只能处理一个客户端请求的服务器被称为迭代服务器(iterative server),这样的C/S模型只能应对那些简单的应用。大多数情况下我们希望服务器可以同时服务多个客户端程序,即服务器程序既能处理客户端发送来的请求同时又能接收其他客户端发送的请求。这样的服务器称为并发服务器(concurrent server)。

Linux中实现并发服务器的方法很简单:每当客户端对服务器有网络请求时,服务器程序就fork一个子服务器进程来处理这个客户的请求,而主服务器程序仍处于等待其他客户端网络请求的状态。基于这个原理,并发服务器的基本模型如下:

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		my_error("socket", errno, __LINE__);
	}

	if (bind(sockfd, (struct sockaddr *)&my_addr,
				sizeof(struct sockaddr_in)) == -1) {
		my_error("bind", errno, __LINE__);
	}

	if (listen(sockfd, BACKLOG) == -1) {
		my_error("listen", errno, __LINE__);
	}

	while (1) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
			my_error("accept", errno, __LINE__);
			continue;
		}

		if ((pid = fork()) == 0) {
			close(sockfd);
			process_client_request(client_fd);
			close(client_fd);
			exit(0);
		} else if (pid > 0)
			close(client_fd);
		else
			my_error("fork", errno, __LINE__);
	}

每当accept()接收到一个TCP连接时,主服务器进程就fork一个子服务器进程。子服务器进程调用相应的函数,通过client_fd(连接套接字)对客户端发来的网络请求进程处理;由于客户端的请求已被子服务进程处理,那么主服务器进程就什么也不做,通过sockfd(监听套接字)继续循环等待新的网络请求。

这里我们需要特别强调的是,在父子进程中需要关闭相应的套接字描述符。从上述代码中可以看到,子服务进程在处理客户端的网络请求之前关闭了监听套接字,主服务进程则关闭了连接套接字,子服务进程在处理完客户端请求后关闭了连接套接字。最后一种关闭套接字的情形比较合乎常理,这里我们重点关注前两种关闭套接字的情况。

我们的问题是:子服务进程关闭了监听套接字,服务器是否还能监听其他连接请求;父服务进程关闭了连接套接字,服务器是否还能够处理客户端请求。

如果理解了文件的的引用计数,这个问题就会迎刃而解。每个文件都有一个引用计数,该引用计数表示当前系统内的所有进程打开该文件描述符的个数。套接字是一种特殊的文件,当然也有引用计数。

在并发服务器这个例子当中,在accept函数之前,sockfd的引用计数为1;在fork函数执行之前,sockfd和client_fd的引用计数分别为1;当fork执行后,由于子进程复制了父进程的资源,所以子进程也拥有这两个套接字描述符,则此时sockfd和client_fd的引用计数都为2。只有当子进程处理完客户请求时,client_fd的引用计数才由于close函数而变为0。由于父服务器进程的主要任务是监听客户请求,则它关闭了连接套接字client_fd;而子进程的主要任务是处理客户请求,它不必监听其他客户请求,因此子进程关闭了sockfd。由上可知,父子进程关闭相应的套接字并不会影响其所负责的通信功能。

我们可以通过下面的两幅示意图更进一步了解fork函数执行前后的客户端和服务器之间的状态。

父子进程关闭相应套接字后客户端和服务器端的状态如下:

这就是监听套接字和连接套接字两者的最终状态,子进程处理客户请求,父进程监听其他客户请求。这样的并发服务器看似完美,但是会产生许多僵死进程,这是为什么?下文将会分析。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key