Linux嵌入式开发与应用

欢迎来到Linux嵌入式开发与应用>>   | 首页 资源中心 | Database | Linux | 生活趣谈 | 嵌入式开发 | 一般分类 | ITPUB论坛

数据源:以编程方式配置 ODBC 数据源

发表人:upczap | 发表时间: 2007年十二月08日, 20:39

数据源:以编程方式配置 ODBC 数据源

本主题说明如何用编程方式配置开放式数据库连接 (ODBC) 数据源名称。这增加了访问数据的灵活性,不必强制用户显式地使用 ODBC 管理器或其他程序来指定数据源名称。

通常,如果关联的数据库管理系统 (DBMS) 支持用 ODBC 管理器来创建数据源,则用户运行 ODBC 管理器来创建数据源。

用 ODBC 管理器创建 Microsoft Access ODBC 数据源时,有两个选择:可以选择现有的 .mdb 文件,或者创建新的 .mdb 文件。没有从 MFC ODBC 应用程序创建 .mdb 文件的编程方式。因此,如果您的应用程序要求将数据放到 Microsoft Access 数据源(.mdb 文件)中,最可行的办法是有一个空的 .mdb 文件,需要时可以使用或复制它。

但是,许多 DBMS 允许以编程的方式创建数据源。一些数据源维护数据库的目录规范。也就是说,目录是一个数据源,数据源中的每个表都存储在单独的文件中(对于 dBASE,每个表都是一个 .dbf 文件)。其他 ODBC 数据库的驱动程序,如 Microsoft Access 和 SQL Server,要求在满足一些特定的条件之后才能建立数据源。例如,使用 SQL Server ODBC 驱动程序时,需要建立了一台 SQL Server 计算机。

SQLConfigDataSource 实例

下面的示例用 ::SQLConfigDataSource ODBC API 函数创建一个新的称为“New Excel Data Source”的 Excel 数据源:

SQLConfigDataSource(NULL,ODBC_ADD_DSN, "Excel Files (*.xls)", 
                   "DSN=New Excel Data Source" 
                   "Description=New Excel Data Source" 
                   "FileType=Excel" 
                   "DataDirectory=C:EXCELDIR" 
                   "MaxScanRows=20");

注意,数据源实际上是一个目录 (C:EXCELDIR),这个目录必须存在。Excel 驱动程序使用目录作为其数据源,使用文件作为单独的表(一个表就是一个 .xls 文件)。

有关创建表的更多信息,请参见 数据源:以编程方式在 ODBC 数据源中创建表

下面的内容讨论需要传递给 ::SQLConfigDataSource ODBC API 函数的参数。若要使用 ::SQLConfigDataSource,必须包括 Odbcinst.h 头文件并使用 Odbcinst.lib 导入库。另外,Odbccp32.dll(对于 16 位系统为 Odbcinst.dll)必须在运行时路径中。

您可以使用 ODBC 管理器或类似的实用程序来创建 ODBC 数据源名称。但是,有时从应用程序直接创建数据源名称获得访问权,而不需要用户运行单独的实用程序的做法更可取。

ODBC 管理器(通常安装在“控制面板”中)将一些项放在 Windows 注册表(对于 16 位系统,放在 Odbc.ini 文件)中,从而新建数据源。ODBC 驱动程序管理器将查询这个文件,以获得有关数据源的所需信息。了解哪些信息要放在注册表中很重要,因为您需要通过调用 ::SQLConfigDataSource 来提供这些信息。

尽管不用 ::SQLConfigDataSource 也可以将这些信息直接写到注册表,但是任何这样做的应用程序都依赖于驱动程序管理器当前用来维护其数据的技术。如果 ODBC 驱动程序管理器以后的修订版使用不同的方法来维护有关数据源的记录,使用这项技术的所有应用程序都将会出现问题。通常建议您在提供了 API 函数的情况下使用 API 函数。例如,如果使用 ::SQLConfigDataSource 函数,可以将代码从 16 位系统移植到 32 位系统,因为该函数可以正确地写入 Odbc.ini 文件或注册表。

SQLConfigDataSource 参数

下面解释 ::SQLConfigDataSource 函数的参数。其中大部分内容摘自随 Visual C++ 1.5 版本和更高版本一同提供的“ODBC API 程序员参考”

函数原型

BOOL SQLConfigDataSource(HWND hwndParent,UINT fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);

备注

参数及其用法

hwndParent

指定为任何对话框的所有者的窗口。ODBC 驱动程序管理器或者特定 ODBC 驱动程序将创建这些对话框以从用户处获得有关新数据源的附加信息。如果 lpszAttributes 参数没有提供足够的信息,则显示一个对话框。hwndParent 参数可以为 NULL

lpszDriver

驱动程序的描述。这是显示给用户的名称,不是物理驱动器的名称 (DLL)。

lpszAttributes

格式为“键名=值”的属性列表。这些字符串用 Null 结束符分开,列表结尾有 2 个连续的 Null 结束符。这些属性主要是要为新数据源填入到注册表中的默认驱动程序特定项。ODBC API 参考中有关该函数没有提到的重要的一项是指定新数据源名称的“DSN”(“数据源名称”)。其他项都是针对新数据源驱动程序的。因为驱动程序会用对话框提示用户提供新值,所以通常没有必要提供所有的项。(将 hwndParent 设置成 NULL 将导致这种情况。)您也可以显式地提供默认值以便不提示用户进行输入。

使用 ODBC 管理器确定 lpszDriver 参数的驱动程序说明

  1. 运行 ODBC 管理器。

  2. 单击“添加”

这会提供已安装的驱动程序及其说明的列表。将此说明作为 lpszDriver 参数使用。请注意,应使用整个说明,包括说明中的文件扩展名和括号(如果有),如“Excel Files (*.xls)”。

另一种方法是检查注册表(对于 16 位系统,为 Odbcinst.ini 文件),在注册表项“ODBC Drivers”(或在 Odbcinst.ini 的 [ODBC Drivers] 节)下有一个所有驱动程序项和说明的列表。

一种查找 lpszAttributes 参数的键名和值的方法是:查看已配置的数据源(多半是由 ODBC 管理器配置的数据源)的 Odbc.ini 文件。

查找 lpszAttributes 参数的键名及值

  1. 运行 Windows 注册表编辑器(或者对于 16 位系统,打开 Odbc.ini 文件)。

  2. 使用以下方法之一查找 ODBC 数据源的信息:

    • 对于 32 位系统,在左窗格中查找键 HKEY_CURRENT_USERSoftwareODBCODBC.INIODBC Data Sources

      右边窗格列出了形式为“pub: REG_SZ:<data source name>”的项,这里 <data source name> 是已用您要使用的驱动程序的所需设置配置好的数据源。选择要用的数据源,例如 SQL Server。字符串“pub:”后的项依次为在 lpszAttributes 参数中要使用的键名和值。

    • 对于 16 位系统,在 Odbc.ini 文件中查找带 [<data source name>] 标记的节。

      该行之后的各行的格式为“键名=值”。这些正是在 lpszAttributes 参数中要用的项。

您也许想查看要使用的特定驱动程序的文档。可以在驱动程序的联机帮助中找到有用的信息。运行 ODBC 管理器可访问驱动程序的联机帮助。在 Windows NT、Windows 3.1 或 Windows 95 上,这些帮助文件通常位于 WINDOWSSYSTEM 目录下。

获得 ODBC 驱动程序的联机帮助

  1. 运行 ODBC 管理器。

  2. 单击“添加”

  3. 选择驱动程序名称。

  4. 单击“确定”。

在 ODBC 管理器显示为该特定驱动程序创建新数据源的信息时,单击“帮助”。这就打开了该特定驱动程序的帮助文件,该帮助文件一般包含有关使用驱动程序的重要信息。

有关更多信息,请参见 Installer DLL 函数参考


VC中用于调试程序的几个宏的使用技巧

发表人:upczap | 发表时间: 2007年十二月08日, 20:36

VC中用于调试程序的几个宏的使用技巧

一、TRACE
当选择了Debug目标,并且afxTraceEnabled变量被置为TRUE时,TRACE宏也就随之被激活了。但在程序的Release版本中,它们是被完全禁止的。下面是一个典型的TRACE语句:
int nCount =9;
CString strDesc("total");
TRACE("Count =%d,Description =%sn",nCount,strDesc);
可以看到,TRACE语句的工作方式有点像C语言中的printf语句,TRACE宏参数的个数是可变的,因此使用起来非常容易。如果查看MFC的源代码,你根本找不到TRACE宏,而只能看到TRACE0TRACE1TRACE2TRACE3宏,它们的参数分别为0123
二、ASSERT
如果你设计了一个函数,该函数需要一个指向文档对象的指针做参数,但是你却错误地用一个视图指针调用了这个函数。这个假的地址将导致视数据的破坏。现在,这种类型的问题可以被完全避免,只要在该函数的开始处实现一个ASSERT测试,用来检测该指针是否真正指向一个文档对象。一般来讲,编程者在每个函数的开始处均应例行公事地使用assertionASSERT宏将会判断表达式,如果一个表达式为真,执行将继续,否则,程序将显示一条消息并且暂停,你可以选择忽视这条错误并继续、终止这个程序或者是跳到Debug器中。下面一例演示了如何使用一个ASSERT宏去验证一个语句。
void foo( char p, int size )
{
ASSERT( p != 0 ); //确认缓冲区的指针是有效的
ASSERT( ( size >= 100 ); //确认缓冲区至少有100个字节
// Do the foo calculation
}
这些语句不产生任何代码,除非—DEBUG处理器标志被设置。Visual C++只在Debug版本设置这些标志,而在Release版本不定义这些标志。当—DEBUG被定义时,两个assertions将产生如下代码:
//ASSERT( p != 0 );
do{
if( !(p != 0) && AfxAssertFailedLine(FILE,LINE) )
AfxDebugBreak();
}while(0);
//ASSERT((size = 100);
do{
if(!(size = 100) &&AfxAssertFailedLine(FILE,LINE))
AfxDebugBreak();
}while(0);
Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。
Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。
AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。
AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。
三、VERIFY
因为assertion只能在程序的Debug版本中起作用,在表达式中不可以包含赋值语句、增加语句(++)或者是减少语句(--),因为,这些语句实际改变数据。可有时你可能想要验证一个能动的表达式,使用一个赋值语句。那么就到了用VERIFY宏来替代ASSERT。例如:
void foo(char p, int size )
{
char q;
VERIFY(q = p);
ASSERT((size = 100);
// Do the foo calculation
// Do the foo calculation
}
Debug模式下,ASSERTVERIFY是一回事,但是在Release模式下,VERIFY宏仍然测试表达式而assertion却不起任何作用。可以说,在Release模式下,ASSERT语句被删除了。
请注意,如果你在一个ASSERT语句中错误地使用了一个能动的表达式,编译器将不做任何警告地忽略它。在Release模式下,该表达式就会被无声息地删除掉,这将会导致程序的错误运行。由于Release版的程序通常不包含Debug信息,这类错误将很难被发现。

QT开发

发表人:upczap | 发表时间: 2007年六月24日, 22:19

QT开发群 42164870 ,欢迎QT开发爱好者加入该群!!

有经验大家共同学习!


自制一小型的linux系统mylinux

发表人:upczap | 发表时间: 2007年六月02日, 18:57

通过grub引导程序引导编译后的内核文件bzImage,把制作的文件系统变成ramdisk镜像文件,通过内存加载镜像文件完成文件系统的注入;其中的命令通过编译busybox生成bin,sbin文件;注意:

内核文件+文件系统镜像文件+grub引导程序<=1440K

详细步骤

1. 软盘上安装引导器(grub

具体操作如下:

# mke2fs /dev/fd0

创建了 ext2 文件

# mount /dev/fd0 /mnt/floppy

现在,创建一些目录,并将一些关键文件复制到软盘:

# mkdir /mnt/floppy/boot

# mkdir /mnt/floppy/boot/grub

# cp /boot/grub/stage1 /mnt/floppy/boot/grub

# cp /boot/grub/stage2 /mnt/floppy/boot/grub

运行grub命令

grub> 提示符处,输入:

grub> root (fd0)

grub> setup (fd0)

grub> quit

引导盘完成。

2 配置busybox

新建一个目录存放资料:

#mkdir /floppylinux

make menuconfig配置busybox

#cp busybox-1.00.tar.gz /floppylinux

#cd /floppylinux

#tar xvfz busybox-1.00.tar.gz

#cd busybox-1.00

#make menuconfig

下面是需要编译进busybox的功能选项,

General Configuration应该选的选项

Show verbose applet usage messages

Runtime SUID/SGID configuration via /etc/busybox.conf

Build Options

Build BusyBox as a static binary (no shared libs)

Installation Options

Don't use /usr

其他选项都是一些linux基本命令选项默认

配置好后退出并保存.

编译并安装busybox

#make

#make install

编译好后在busybox目录下生成子目录_install,里面的内容:

drwxr-xr-x 2 root root 4096 11 24 15:28 bin

lrwxrwxrwx 1 root root 11 11 24 15:28 linuxrc -> bin/busybox

drwxr-xr-x 2 root root 4096 11 24 15:28 sbin

其中可执行文件busyboxbin目录下,其他的都是指向他的符号链接.

3 制作根文件系统

建立临时目录,该目录为软盘的文件系统

#mkdir //tmp/floppy-linux

busybox下的_install目录下的文件复制过来:

#cp ./_install /tmp/floppy-linux –r

#cd /tmp/floppy-linux

# mkdir dev etc etc/init.d proc mnt tmp var

# chmod 755 dev etc etc/init.d bin mnt tmp var

# chmod 555 proc

# cd dev

# mknod tty c 5 0

# mknod console c 5 1

# chmod 666 tty console

# mknod tty0 c 4 0

# chmod 666 tty0

# mknod ram0 b 1 0

# chmod 600 ram0

# mknod fd0 b 2 0

# chmod 600 fd0

# mknod null c 1 3

# chmod 666 null

建启动配置文件:

/etc/inittab,

/etc/init.d/rcS,

/etc/fstab

/boot/grub.conf

/boot/menu.lst

其中

#ln –s grub.conf menu.lst

initab:

::sysinit:/etc/init.d/rcS

::askfirst:/bin/sh

rcS:

#!/bin/sh

mount –a

# chmod 755 rc.sysinit

fstab:

proc /proc proc defaults 0 0

grub.conf:

timeout 10

default 0

title FloppyLinux   root (fd0)  kernel /boot/bzImage  initrd /initrd.img.gz

4 制作Ramdisk的镜像文件:

# dd if=/dev/zero of=/dev/ram1

dd: 正在写入 /dev/ram1’: 设备上没有空间

读入了 8193+0 个块

输出了 8192+0 个块

#mke2fs -m0 /dev/ram1

#mkdir /mnt/ram

#mount /dev/ram1 /mnt/ram

将先前做好的floppylinux根文件系统拷贝到ram1.

#cp -R /tmp/floppy-linux /* /mnt/ram

#umount /dev/ram1

# dd if=/dev/ram1 of=/tmp/floppy-linux /initrd.img

# file initrd.img

initrd.img: Linux rev 1.0 ext2 filesystem data

loop设备来把他重新挂装到文件系统里:

# mount -o loop initrd.img /mnt/ram/

查看/mnt/ram下的内容,/tmp/floppy-linux /下的一模一样

# ls /mnt/ram

bin dev etc lost+found mnt proc sbin tmp var

#umount /mnt/ram

压缩initrd.img印象文件

# gzip -v9 initrd.img

initrd.img: 90.1% -- replaced with initrd.img.gz

查看压缩后的大小:

# ls -lh initrd.img.gz

只有406K

5编译linux系统内核

#cp linux-2.4.20.bz2 /usr/src/

#cd /usr/src

#tar xfvj linux-2.4.20.bz2

#ln -s linux-2.4.20 linux

进入linux源代码目录:

#cd linux

清理源代码树:

#make mrproper

运行配置程序:

#make menuconfig

code maturity level options

先选择N,当我们配置好常规的东西,要加入framebuffer支持时再将这一项选择Y,如果不在code maturity level options选择为Y,将不能配置framebuffer.

Loadable module support

选择N,为了简化系统的制作,我在这个项目中不选择可加载内核模块的支持.

processor type and features

processor family 中选择386CPU

其他选项都选择N.

General setup

networking support 选择Y

PCI support 选择Y

System V ipc 选择Y

systrl support选择Y

kernel support for ELF 选择Y

其余内容都可以选择N,

chnology devices (MTD)

Parallel port support

Plug and Play configuration

以上三个大项中的所有内容选择N

block devices

Normal floppy disk support

Loopback device support

RAM disk support

initial RAM disk (initrd) support

Per partition statics in /proc/partitions

以上几项选择Y,其余全部选择N.

Multi-device support (RAID and LVM)

Cryptography support (CryptoAPI)

这两个大项全部选择N

Networking options

这一大项中,只需要把下列项目编译进内核:

Packet socket :mmapped IO

TCP/IP networking

Telephony Support 选择N

ATA/IDE/MFM/RLL support

选择Y,然后下面的'IDE,ATA and ATAPI Block Devices'按钮就被激活

下面几项选择Y,其余都可以是N.

Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support

Include IDE/ATA-2 DISK support

Auto-Geometry Resizing support

Include IDE/ATA CDROM support

SCSI support

Fusion MPT device support

IEEE 1394(FireWire) support

I2O device support

全部选择N

Network device support

选择Y

然后点Ethernet(10 or 100 Mbit)按钮选择网卡驱动(Reltek8139)

Amateur Radio support

IrDA (infrared) support

ISDN subsystem

Old CD-ROM drivers (not SCSI,not IDE)

Input core support

全部选择N

Charcter devices

除了Virtual terminalSupport for console on terminal两项,其他全选N

Multimedia devices

Crypto Hardware support

全部选择N.

File sytems

选择其中有三个:

/proc file system support.

Second extended fs support BabyLinux的基本文件系统.

ISO 9660 CDROM filesytem support用光盘.

Console drivers

.前面三个全部选择Y,

Frame-buffer support按钮是灰色的不能选,别急,回到第一个大选项:

Code maturity level options 选择Y,就可以激活这个按钮了.

下面几个选项需要选择Y:

Support for framebuffer devices

VESA VGA graphics console

Support only 8 pixels wide fonts

剩下的几个大项全部选N

保存后退出,配置程序会自动生成一个隐藏的配置文件.config

#make dep

#make bzImage

bzImage654K

6 整合启动盘

#cp bzImage /mnt/floppy/boot

#cp /tmp/floppy-linux/initrd.img.gz /mnt/floppy

全部文件(文件夹)如下:

/lost+found/

/boot/

/boot/grub/

/boot/grub/stage1

/boot/grub/stage2

/boot/grub/menu.lst

/boot/grub/grub.conf

/boot/bzImage

/initrd.img.gz

五实验遇到问题与总结

1,在制作启动盘的时候,运行grub命令后,进入grub界面后,运行:

grub>setup (fd0)

结果有时可能会出错,可能是软盘出问题,换张软盘

2,软盘制作完成后,重启机器,在grub开始进行引导时可能出现错误:

Error 16: Inconsistent filesystem strutwre

这个也是软盘的问题

3,在编译的内核时,有时可能不通过,在Redhet9.0(kernel-2.4.20)下用gcc-3.3.3就能编译通过2.4.20版本的内核,而在Fedora Core 2下用gcc-3.4.2就不行,可能是gcc的版本问题。


Linux下的多线程编程

发表人:upczap | 发表时间: 2007年五月28日, 08:39

1 引言
  线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
  为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
  下面我们先来尝试编写一个简单的多线程程序。
2 简单的多线程编程
  Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1.c。

/* example.c*/
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.n");
}

int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.n");
pthread_join(id,NULL);
return (0);
}

我们编译此程序:
gcc example1.c -lpthread -o example1
运行example1,我们得到如下结果:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
再次运行,我们可能得到如下结果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,  pthread_create和pthread_join,并声明了一个pthread_t型的变量。
  pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
  typedef unsigned long int pthread_t;
  它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
  函数pthread_join用来等待一个线程的结束。函数原型为:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
  在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

3 修改线程的属性
  在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
  属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
  另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, &param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
  
4 线程的数据处理
  和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。

4.1 线程数据
  在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
  和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
  创建键的函数原型为:
  extern int pthread_key_create __P ((pthread_key_t *__key,
  void (*__destr_function) (void *)));
  第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

  在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey,创建键*/
pthread_once ( & once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函数 createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函数 freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
}

  这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t __key));
  这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。

4.2 互斥锁
  互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
  我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
  这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
  pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
  上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

4.3 条件变量  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 
  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
  函数pthread_cond_signal()的原型为:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}

increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
  count值为0
时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
  函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。

4.4 信号量  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t *sem)用来释放信号量sem。
  下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据,每读一次,信号量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%dn",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%dn",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 防止程序过早退出,让它在此无限期等待*/
pthread_join(t1,NULL);
}

  在Linux下,我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。

5 小结
  多线程编程是一个很有意思也很有用的技术,使用多线程技术的网络蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。


写给25岁以上的单身男性的100条忠告

发表人:upczap | 发表时间: 2007年四月11日, 21:14

写给25岁以上的单身男性的100条忠告

1你可以不拥有任何东西,除了对生活的激情。
2然后是钱。
3努力赚钱。
4有了钱,可以拥有很多女人的身体。
5没有钱,要拥有很多女人的心。
6如果身体和心你都无法得到,那只能说明你的无能。
7英雄和浪子,取长补短。
8该英雄时要英雄,该浪子时得浪子。
9喝酒,抽烟。一个都不能上瘾。
10赌,女人,权力。追求,但不能沉沦。
11热爱体育。如果你不热爱你就不是男人。
12即使你爱体育爱得发疯。也不能抱着体育生活一辈子。
13善待你的家人。
14网络游戏,浅尝辄止。
15有分寸,懂得控制自己。
16厨艺精湛,但不轻易下厨。
17如果厨艺不佳,刀功一定要好。
18得到女人的心,先满足她的胃。
19再给她可以倚靠的肩膀。
20依赖。女人的致命处。
21想让女人嫁给你,给她安全感。经济上和精神上的双重安全。
22不要以为每个女人都很傻。
23如果不打算骗女人一生一世。就不要轻易骗她。
24如果是善意的谎言,要设法让她明白你的良苦用心。
25如果不爱她,就不要娶她。除非你能保证对她好。
26如果背叛了她,就尽量补偿她。
27女人的寿命比男人长,她会恨男人一直恨到死。
28最好不要得罪女人。
29补偿最好的方式是钱。
30连钱都不要的女人,要小心。
31追到一个女人很容易,可是摆脱掉却很难。追求之前请三思。
32不要跟玩不起的人玩。
33就算是尝试一夜情,也不要选择去P C。
34对象的选择和客户一样,慎重。
35不要轻易给女人承诺。说者无心,听者有意。
36尊重女人的最好方式就是坦诚对她。
37即使你是个流氓,也要流氓得坦坦荡荡。
38没有一个女人,能比你的母亲重要。
39小时糊涂,大事不糊涂。
40身材魁梧,动作温柔。
41找一个理解你的人做妻子。
42找一个了解你的女人做知已。
43作为成熟男人,应该清楚得不到的才是最好的。
所以,该舍时要舍得。
44不要打女人。有这种冲动前先看看《妇女权益保障法》。
45不要谈太多次恋爱。否则你自己都会厌倦不堪。
46不要见一个爱一个,爱得太多,你的爱就要贬值。
47“我爱你”这三个字不必挂在嘴边,用行动来证明吧。
48“我爱你”堵住所有废话的最后一击。话要用在刀刃上。
49拥抱或者吻。肢体语言永远比语言更有力度。
50对上司,可以服从,可以抗议。就是不能卑躬屈膝。
51买一栋房子。即使是按揭。只要你不打算当无根浪子。
52再买一栋。即使是借钱。用于投资。
53炒股。不为赚钱,只为证明自己的判断力。
54即使是输掉一栋房子,也要笑着自嘲:我不是一个输不起的人。
55买车。先买二手车。如果你技术很好或者有很多钱则另当别论。
56哪儿摔倒,哪儿站起来再摔几次。
57没有才华,也要有气魄。
58不要西装革履,也不必着装休闲。把衬衫扣子解掉三颗,就是一种随意。
59神采飞扬,不修边幅。
60穿西装时记着把商标撕掉。
61笑,在任何人面前。
62让你周围的人因为你的存在而感到快乐。
63怕硬可以,欺软不行。
64选择婚姻,而不是婚姻选择你。
65着装整洁,不必非得把皮鞋擦得锃亮。
66随意。不必往头发上喷发胶或者定型水。
67看一个女人虚荣不虚荣,就剃上光头衣冠不整地跟她一起走在大街上。注意她的表情。
68一支玫瑰,或者一句甜言蜜语。关键时候,派上用场。女人是很容易满足的。
69因为需要才喜欢,还是因为喜欢才需要。分清楚。
70想跟女人分手时,设法让她先提出来。
71大智若愚。傻与不傻,先看你会不会装傻。
72做一个出色的男人,先学会哄。
73哄上司,哄同事,哄老人,哄女人,哄孩子。
74如果以上你通通都能哄到,那么恭喜你,你的人生一定值得你炫耀。
75魅力源于实力。
76能让别人跟着你走,是你的魅力所在。
77脾气温和,性格好斗。
78内敛。该出手时再出手。
79同流,但不合污。
80异想天开。没有异想,何来天开?
81沉稳。不乏可爱。
82如果实在长相严肃,至少可以带点儿冷幽默。
83有自己的立场和观点。不一定要表明出来。
84说到做到。做到再说。
85旅游。不必期待艳遇。
86沉默。有些话是不必说出来的。
87在女人面前,永远不要解释,只有认错。
88邀请不熟的女士吃饭,要找些天时地利的借口。
89像个君子,即使你不是。
90先赚钱,再谈爱。
91同居。如果你享受这种习惯。
92享受眼前的同时,想想以后。
93冷静和超然,即使是在危难时刻。装也要装出来。
94得之淡然,失之泰然。一切皆顺其自然。
95顺然自然,并不代表不去争取。
96不要跟一般人一般见识,即使是特殊人也一样。
97不要骂脏话。
98侮辱一个人最好的方式就是置之不理。侮辱,而不是责骂。
99认真。但不顽固。
100好好生活。你的生命是一次性的。


15所名校计算机专业之特色分析

发表人:upczap | 发表时间: 2007年四月11日, 20:41

15所名校计算机专业之特色分析

作者:◎ 清华大学 倪铭辰

随着IT时代的到来,几乎所有院校都开设了计算机系。计算机专业领域很多,虽然对于本科生来说,基本上是宽领域的全面培养,但是由于有各个学校的特色存在,同学们往往会在强势领域有更多的收获。

清华大学:在拥有多位院士的清华大学差不多在所有方向都有相应的强人撑着,相比之下,应用和系统结构更强些,某些方向已经处于世界领先水平。优势领域包括多媒体、网络技术等。同时拥有模式识别与智能控制国家重点学科。

北京大学:在软件方面尤其在中文视觉听觉信息处理方面有优势,由王选和杨芙清两位院士曾领导的方正和青鸟证实了北大在中国当前软件界的实力。硬件方面有一位院士王阳元。

上海交通大学:上海交通大学近年来在走上坡路,软件方向的发展很快,模式识别与智能控制也是国家重点学科。优势领域包括系统软件、网络信息获取与处理、并行与分布理论、数据库等。

南京大学:如果说20世纪80年代的软件专业是并蒂莲花的话,那么现在的南大完全可以说是一枝独秀,由孙钟秀院士领导的软件学科甚至已经涉及到大型操作系统的设计。优势领域包括软件自动化与形式化方法、分布计算与并行处理、系统软件及其信息安全、多媒体技术、人工智能与知识工程、数据库技术、语言信息处理等。拥有软件新技术国家重点实验室。

哈尔滨工业大学:深处寒冷北方的哈工大能保持几十年的超强实力可以说是个奇迹,作为当年唯一的计算机应用重点学科,哈工大培养出了大量的人才。现在他们与许多学校和研究机构搞学科并建,吸纳了很多外来人才,增强了哈工大的整体实力。目前哈工大承担的项目经费已超亿元。他们的优势领域包括:智能机器人、CIMS与系统集成、智能化中文信息处理机器翻译技术、计算机网络及系统安全防护技术等。

东北大学:建有软件工程国家级工程研究中心,它的软件方向大多与数据库有关,东软集团的发展证实了东大软件的实力。张嗣瀛教授是中科院院士。优势领域:智能控制与智能机器人系统、诊断与容错控制、CIMS中生产计划与存储管理的建模、宽带计算机网络、分布式多媒体信息处理技术、数据库及其应用技术、嵌入式系统技术等。

国防科技大学:这是一所不太为人所了解的学校,就像一个淡泊名利的世外高人,我们在感叹她的高大的同时,也会有一种难以接近的感觉。由3位院士领导的系统结构方向曾经研制出了让中国人骄傲的银河系列大型计算机。国防科技大学的优势领域大多和硬件相关。设有网络技术与信息安全研究所及并行与分布处理国家重点实验室。

浙江大学:前任校长潘云鹤是浙大计算机系唯一一位院士(现为中国工程院副院长),他所领衔创建的计算机图形学、智能CAD在国内无其右者,而且浙大开放式的校风,吸纳贤才的远见,使其越来越受到人们的认可。浙大建有计算机辅助设计与图形学国家重点实验室。优势领域还有CAD/CIMS、虚拟现实、网络与多媒体、产品创新设计、智能信息与人机交互技术等。

东南大学:中国工程院院士顾冠群教授为首。计算机网络、数据库及信息系统软件技术与人工智能CIMS技术是东南大学的优势领域。建有国家863/CIMS网络和数据库实验室、教育部网络和信息集成技术实验室、中国教育和科研计算机网华东(北)地区网络中心。

北京航天航空大学:是发展势头最猛的学校之一,在人工智能、软件工程和体系结构、虚拟现实与多媒体技术方向有优势。建有由李未院士领导的软件开发环境国家重点实验室,教育部虚拟现实新技术重点实验室、教育部软件网上联合研发中心。

有一些学校的计算机专业是在基础学科如数学、电子(通信)的基础上发展起来的,也就是随着计算机发展的狂热而转型来的。数学带动软件发展,电子通信则对硬件的发展奠定了良好的基础。
以数学专业发展软件专业的学校有复旦大学、南开大学、中国科技大学等。以电子通讯专业发展硬件专业的学校有西安电子科技大学、西安交通大学、北京邮电大学等。


选择排序讲解

发表人:upczap | 发表时间: 2007年四月07日, 22:07

选择排序讲解

////////////////////////////////////////////////////////////////////

#i nclude<stdio.h>

void main(){

void sort(int x[],int n);

int *p,i,a[10];

p=a;

for(i=0;i<10;i++)scanf("%d",p++);

p=a;

sort(p,10);

for(p=a,i=0;i<10;i++){

printf("%5d",*p);

p++;

}

}

void sort(int x[],int n){

int i,j,k,t;

for(i=0;i<n-1;i++){

k=i;

for(j=i+1;j<n;j++)

if(x[j]>x[k])k=j;

if(k!=i){

t=x[i];

x[i]=x[k];

x[k]=t;

}

}

}

//////////////////////Li同学发送给我的源程序有若干笔误,已修改

mail中该同学提到:

这个是课本指针那一章的,是用选择法对10个数从大到小排序的.我看不懂后面的那个被调函数尤其是那个if(k!=i),这个条件不是必然成立的吗?老师具体给我说下这个程序吧.

下面专门讲解一下这个程序。

选择排序方法是基本的排序算法之一。其算法思路简单,容易构建,缺点是执行效率不如冒泡法、希尔排序法等。

选择排序法的核心思想是从待排序数据序列中选择最大(小)的那个,将其放在最前。比如对于10个数据(编号为array[0]-array[9]),如下:

12 56 -9 456 69 321 -987 52 63 10

首先从array[0]-array[9]中选择最大的那个,将其放在array[0]

其次从array[1]-array[9]中选择最大的那个,将其放在array[1]

再次从array[2]-array[9]中选择最大的那个,将其放在array[2]

……

最后从array[8]-array[9] 中选择最大的那个,将其放在array[8]

很显然,经过9轮选择,整个数组就会按降序排列,这就是选择排序法的算法流程。

这需要用一个二重循环来实现。内层循环的本质是寻找特定范围数据的最大者,并将其放在约定位置。

将该算法以更一般的形式描述:

n个数组元素需要从大到小排序(编号为array[0]-array[n-1])。则需要n-1轮选择。

首先从array[0]-array[n-1]中选择最大的那个,将其放在array[0]

其次从array[1]-array[n-1]中选择最大的那个,将其放在array[1]

再次从array[2]-array[n-1]中选择最大的那个,将其放在array[2]

……

最后从array[n-2]-array[n-1]中选择最大的那个,将其放在array[8]

可以看出,这n-1次循环的规律是一样的。选择排序算法可以描述为:

void sort(int array[],int n){

int i;

for(i=0;i<=n-2;i++)

{array[i]-array[n-2]中选择最大的放在array[i];}

}

那么下面的任务就是实现{array[i]-array[n-2]中选择最大的放在array[i];}了。略。

注释讲解如下:

///////////////////////////////////////////////////////////////////

#i nclude<stdio.h>

void main(){

void sort(int x[],int n);

int *p,i,a[10];//主函数中声明指针变量p,循环变量i,数组a

p=a;//p指向a的首地址即a[0]

for(i=0;i<10;i++)scanf("%d",p++);//通过指针p间接访问数组元素,接受值放在数组中

p=a;//重新使指针变量指向数组首地址,非常重要

sort(p,10);//调用选择法排序函数sort(int*,int)

for(p=a,i=0;i<10;i++){//for循环用来打印已排序的数组元素,p=a没有必要

printf("%5d",*p);

p++;

}

}

void sort(int x[],int n){

int i,j,k,t;//k用来指向最大的元素

for(i=0;i<n-1;i++){//n个数组元素,i=0~n-2,n-1轮,在x[i]-x[n-2]中选择最大的放在x[i];

k=i;//默认x[i]为最大,k指向x[i]

for(j=i+1;j<n;j++)

if(x[j]>x[k])k=j;//若有元素大于x[k],则更新k,使k始终指向最大的元素

if(k!=i){//k最初指向i,若k值没有变化(k==i),说明x[i]就是x[i]-x[n-2]中的最大值,不用再交换;若k!=i,说明k值有更新就不再等于i,需要将x[k]即最大值交换到x[i]

t=x[i];

x[i]=x[k];

x[k]=t;

}

}

}

/////////////////////////////////////////////////////////////

至于Li同学所说“k!=i必然成立”是不正确的。k==i是有可能的,如array[i]-array[n-1]的最大值恰好是array[i],则k==i。这时候就不必要(而非不能)交换,因为array[k]就是array[i]

sort函数执行实例:

28 356 53 -9 93 815 -815 8 9 15降序排列(array[0]-array[9])。

//////////////////第一轮(i==0),从array[i]-array[9]中选择最大的,放在a[i]

i==0,k==5,array[i]<==>array[k]

第一轮过后:

815 356 53 -9 93 28 -815 8 9 15

//////////////////第二轮(i==1),从array[i]-array[9]中选择最大的,放在a[i]

i==1,k==1,不必交换

第二轮过后:

815 356 53 -9 93 28 -815 8 9 15

//////////////////第三轮(i==2),从array[i]-array[9]中选择最大的,放在a[i]

i==2,k==4,array[i]<==>array[k]

第三轮过后:

815 356 93 -9 53 28 -815 8 9 15

//////////////////第四轮(i==3),从array[i]-array[9]中选择最大的,放在a[i]

i==3,k==4,array[i]<==>array[k]

第四轮过后:

815 356 93 53 -9 28 -815 8 9 15

//////////////////第五轮(i==4),从array[i]-array[9]中选择最大的,放在a[i]

i==4,k==5,array[i]<==>array[k]

第五轮过后:

815 356 93 53 28 -9 -815 8 9 15

//////////////////第六轮(i==5),从array[i]-array[9]中选择最大的,放在a[i]

i==5,k==9,array[i]<==>array[k]

第六轮过后:

815 356 93 53 28 15 -815 8 9 -9

//////////////////第七轮(i==6),从array[i]-array[9]中选择最大的,放在a[i]

i==6,k==8,array[i]<==>array[k]

第七轮过后:

815 356 93 53 28 15 9 8 -815 -9

//////////////////第八轮(i==7),从array[i]-array[9]中选择最大的,放在a[i]

i==7,k==7,不必交换

第八轮过后:

815 356 93 53 28 15 9 8 -815 -9

//////////////////第九轮(i==8),从array[i]-array[9]中选择最大的,放在a[i]

i==8,k==9, array[i]<==>array[k]

第九轮过后: