随笔 - 40, 文章 - 0, 评论 - 9, 引用 - 0
数据加载中……

UNIX CP命令

http://hi.baidu.com/xinghuali/blog/item/95e0d73399dd6f43ad4b5ff4.html
cp (复制档案或目录) 
[root@linux ~]# cp [-adfilprsu] 来源档(source) 目的檔(destination)
[root@linux ~]# cp [options] source1 source2 source3 .... directory
参数:
-a  :相当于 -pdr 的意思;
-d  :若来源文件为连结文件的属性(link file),则复制连结文件属性而非档案本身;
-f  :为强制 (force) 的意思,若有重复或其它疑问时,不会询问使用者,而强制复制;
-i  :若目的檔(destination)已经存在时,在覆盖时会先询问是否真的动作!
-l  :进行硬式连结 (hard link) 的连结档建立,而非复制档案本身;
-p  :连同档案的属性一起复制过去,而非使用预设属性;
-r  :递归持续复制,用于目录的复制行为;
-s  :复制成为符号连结文件 (symbolic link),亦即『快捷方式』档案;
-u  :若 destination 比 source 旧才更新 destination !
最后需要注意的,如果来源档有两个以上,则最后一个目的文件一定要是『目录』才行!
范例:
范例一:将家目录下的 .bashrc 复制到 /tmp 下,并更名为 bashrc
[root@linux ~]# cd /tmp
[root@linux tmp]# cp ~/.bashrc bashrc
[root@linux tmp]# cp -i ~/.bashrc bashrc
cp: overwrite `basrhc'? n
# 重复作两次动作,由于 /tmp 底下已经存在 bashrc 了,加上 -i 参数,
# 则在覆盖前会询问使用者是否确定!可以按下 n 或者 y 呢!
# 但是,反过来说,如果不想要询问时,则加上 -f 这个参数来强制直接覆盖!

范例二:将 /var/log/wtmp 复制到 /tmp 底下
[root@linux tmp]# cp /var/log/wtmp . <==想要复制到目前的目录,最后的 . 不要忘
[root@linux tmp]# ls -l /var/log/wtmp wtmp
-rw-rw-r--  1 root utmp 71808 Jul 18 12:46 /var/log/wtmp
-rw-r--r--  1 root root 71808 Jul 18 21:58 wtmp
# 注意到了吗?!在不加任何参数的情况下,档案的所属者会改变,连权限也跟着改变了~
# 这是个很重要的特性!要注意喔!还有,连档案建立的时间也不一样了!
# 如果您想要将档案的所有特性都一起复制过来,可以加上 -a 喔!
[root@linux tmp]# cp -a /var/log/wtmp wtmp_2
[root@linux tmp]# ls -l /var/log/wtmp wtmp_2
-rw-rw-r--  1 root utmp 71808 Jul 18 12:46 /var/log/wtmp
-rw-rw-r--  1 root utmp 71808 Jul 18 12:46 wtmp_2
# 瞭了吧!整个资料特性完全一模一样ㄟ!真是不赖~这就是 -a 的特性!

范例三:复制 /etc/ 这个目录下的所有内容到 /tmp 底下
[root@linux tmp]# cp /etc/ /tmp
cp: omitting directory `/etc'   <== 如果是目录,不能直接复制,要加上 -r 的参数
[root@linux tmp]# cp -r /etc/ /tmp
# 还是要再次的强调喔! -r 是可以复制目录,但是,档案与目录的权限会被改变~
# 所以,也可以利用 cp -a /etc /tmp 来下达指令喔!

范例四:将范例一复制的 bashrc 建立一个连结档 (symbolic link)
[root@linux tmp]# ls -l bashrc
-rw-r--r--  1 root root 395 Jul 18 22:08 bashrc
[root@linux tmp]# cp -s bashrc bashrc_slink
[root@linux tmp]# cp -l bashrc bashrc_hlink
[root@linux tmp]# ls -l bashrc*
-rw-r--r--  2 root root 395 Jul 18 22:08 bashrc
-rw-r--r--  2 root root 395 Jul 18 22:08 bashrc_hlink
lrwxrwxrwx  1 root root   6 Jul 18 22:31 bashrc_slink -> bashrc
# 那个 bashrc_slink 是由 -s 的参数造成的,建立的是一个『快捷方式』,
# 所以您会看到在档案的最右边,会显示这个档案是『连结』到哪里去的!
# 至于那个 bashrc_hlink 有趣了!建立了这个档案之后, bashrc 与 bashrc_hlink 
# 所有的参数都一样,只是,第二栏的 link 数改变成为 2 了~而不是原本的 1 喔!
# 这两种连结的方式的异同,我们会在下一章里面进行介绍的!

范例五:若 ~/.bashrc 比 /tmp/bashrc 新才复制过来
[root@linux tmp]# cp -u ~/.bashrc /tmp/bashrc
# 这个 -u 的特性,是在目标档案与来源档案有差异时,才会复制的。
# 所以,比较常被用于『备份』的工作当中喔! ^_^

范例六:将范例四造成的 bashrc_slink 复制成为 bashrc_slink_2
[root@linux tmp]# cp bashrc_slink bashrc_slink_2
[root@linux tmp]# ls -l bashrc_slink*
lrwxrwxrwx  1 root root   6 Jul 18 22:31 bashrc_slink -> bashrc
-rw-r--r--  1 root root 395 Jul 18 22:48 bashrc_slink_2
# 这个例子也是很有趣喔!原本复制的是连结档,但是却将连结档的实际档案复制过来了
# 也就是说,如果没有加上任何参数时,复制的是源文件,而非连结文件的属性!
# 若要复制连结文件的属性,就得要使用 -d 或者 -a 的参数了!

范例七:将家目录的 .bashrc 及 .bash_history 复制到 /tmp 底下
[root@linux tmp]# cp ~/.bashrc ~/.bash_history /tmp
# 可以将多个数据一次复制到同一个目录去!
这个 cp 的功能很多,而由于我们常常在进行一些数据的复制,所以也会常常用到这个指令的。 一般来说,我们如果去复制别人的数据 (当然,该档案您必须要有 read 的权限才行啊! ^_^) 时, 总是希望复制到的数据最后是我们自己的,所以,在预设的条件中, cp 的来源档与目的档的权限是不同的,目的档的拥有者通常会是指令操作者本身。举例来说, 上面的范例二中,由于我是 root 的身份,因此复制过来的档案拥有者与群组就改变成为 root 所有了! 这样说,可以明白吗?! ^_^

由于具有这个特性,因此,当我们在进行备份的时候,某些需要特别注意的特殊权限档案, 例如密码文件 (/etc/shadow) 以及一些设定档,就不能直接以 cp 来复制,而必须要加上 -a 或者是 -p 等等可以完整复制档案权限的参数才行!另外,如果您想要复制档案给其它的使用者, 也必须要注意到档案的权限(包含读、写、执行以及档案拥有者等等), 否则,其它人还是无法针对您给予的档案进行修订的动作喔!注意注意!

至于上面的范例当中,第四个范例是最有趣的,使用 -l 及 -s 都会建立所谓的连结档 (link file), 但是这两种连结档确有不一样的展现情况。这是怎么一回事啊? 那个 -l 就是所谓的 hard link ,至于 -s 则是 symbolic link ,鸟哥这里先不介绍, 因为这个涉及 i-node 的相关知识,我们还没有介绍到,下一章再来讨论这个 link 的问题喔! 总之,由于 cp 有种种的档案属性与权限的特性,所以,在复制时,您必须要清楚的了解到: 
• 是否需要完整的保留来源档案的信息? 
• 来源档案是否为连结档 (symbolic link file)? 
• 来源档是否为特殊的档案,例如 FIFO, socket 等? 
• 来源文件是否为目录?

posted @ 2008-01-02 11:56 茶 阅读(8427) | 评论 (0)编辑 收藏

SQL语言简介

     摘要: 1、           SQL概述 SQL是一种面向数据库的通用数据处理语言规范,能完成以下几类功能:提取查询数据,插入修改删除数据,生成修改和删除数据库对象,数据库安全控制,数据库完整性及数据保护控制。 数据库对象包括表、视图、索引、同义词、簇、触发器、函数、过程、包、数据库链、快照等(表空...  阅读全文

posted @ 2007-12-29 17:25 茶 阅读(240) | 评论 (0)编辑 收藏

用Pro*C开发多线程应用程序 (1)

(注:本文来自Pro*C/C++ Precompiler Programmer's Guide Release 8.1.5) 
  
  如果你的操作系统不支持线程,本文暂不适合你。本文包含以下几个部分: 
  
  n 什么是多线程? 
  
  n Pro*C中的运行时上下文 
  
  n 运行时上下文的使用模式 
  
  n 多线程应用程序的用户接口 
  
  n 多线程例子 
  
  一.什么是多线程? 
  一个多线程的应用程序中,线程运行在共享的地址空间里。线程是在进程内部执行的“轻量”级子进程,它们共享代码段和数据段,但是有自己的程序计数器、寄存器和堆栈。全局变量和静态变量在线程之间是共享的,因此通常需要在程序中使用某种互斥机制来管理线程对这些变量的访问,互斥体Mutexes就是用来保证数据完整性的同步装置。 
  
  有关互斥体的更多讨论,参看多线程编程方面的文章。 
  
  Proc*C编译器通过以下方式支持开发多线程的Oracle应用程序(在支持线程的平台上): 
  
  n 用一个命令行编译选项来产生线程安全的代码 
  
  n 用内嵌的SQL语句和指令支持多线程 
  
  n 线程安全的Lib库和其他客户端Lib库 
  
  注意:也许你的平台支持某个特殊的线程包,但还是需要查看Oracle有关平台的文档,看看Oracle是否支持它。 
  
  二.Pro*C中的运行时上下文 
  为了在线程和数据库连接之间建立松散的结合,Pro*C引入了一个概念runtime_context,我们称之为运行时上下文。一个运行时上下文包含了以下资源和信息: 
  
  n 与数据库服务器的连接 
  
  n 当前连接上使用的游标 
  
  n 内嵌的一些选项,如MODE,HOLD_CURSOR,RELEASE_CURSOR和 SELECT_ERROR 
  
  不仅仅是简单的支持线程和连接之间的松散结合,Pro*C编译器还允许开发人员在线程和运行时上下文之间建立松散的结合,Pro*C允许在程序里为运行时上下文定义一个句柄,通过这个句柄,运行时上下文可以在线程之间切换。 
  
  例如,一个交互式应用程序创建了线程T1,来执行一个查询,并且返回了前10条记录,然后T1终止。在用户输入了必须的数据之后,程序又创建了线程T2,并且把T1使用的运行时上下文传给T2,这样T2可以在同一个游标上获取接下来10条的记录。 
  
  三.运行时上下文的使用模式 
  下面是在多线程的Pro*C程序中使用运行时上下文的两种可能模式: 
  
  n 多线程共享单个运行时上下文 
  
  n 多线程使用互相独立的运行时上下文 
  
  不管采用哪种模式,不能在同一时刻多个线程共享同一个运行时上下文。如果两个或两个以上的线程在同一时刻试图使用同一个运行时上下文,将会出现以下错误:SQL-02131: Runtime context in use。 
  
  1.多线程共享单个运行时上下文 
  
  2.多线程使用互相独立的运行时上下文 
  
  四.多线程应用程序的用户接口 
  Pro*C编译器提供以下接口来支持多线程: 
  
  n 命令行选项,THREADS=YES|NO 
  
  n 内嵌SQL语句和指令 
  
  n 线程安全的公共库函数 
  
  1.THREADS选项 
  在proc预编译命令行上指定THREADS=YES,Pro*C编译器将保证产生的C代码是线程安全的。如果指定了THREADS=YES,Pro*C将会检查每个包含SQL执行语句的函数,是否指定了这些语句是在哪个运行时上下文中执行的,若没有发现这类指定标识,编译器就会返回错误。 
  
  2.内嵌SQL语句和指令 
  下列内嵌的SQL语句和指令用于支持多线程和运行时上下文的使用: 
  
  n EXEC SQL ENABLE THREADS; 
  
  n EXEC SQL CONTEXT ALLOCATE :context_var; 
  
  n EXEC SQL CONTEXT USE {:context_var/DEFAULT}; 
  
  n EXEC SQL CONTEXT FREE :context_var; 
  
  在以上SQL语句中,context_var是运行时上下文句柄,它必须被定义成sql_context类型:如sql_context context_var; 
  
  使用DEFAULT意味着接下来的SQL语句将使用默认的全局运行时上下文,直到另一条CONTEXT USE语句覆盖它。 
  
  n EXEC SQL ENABLE THREADS 
  
  这条可执行SQL语句初始化支持多线程的进程。它必须是程序中第一条可执行的SQL语句。 
  
  n EXEC SQL CONTEXT ALLOCATE 
  
  这条可执行SQL语句分配并初始化了一块用于指向一个新的运行时上下文的内存,并返回标识该上下文的句柄变量,该变量必须声明为sql_context类型。 
  
  n EXEC SQL CONTEXT USE 
  
  这条指令性语句告诉编译器接下去执行的SQL语句将使用指定的运行时上下文,这里的运行时上下文必须在此前已经用CONTEXT ALLOCATE分配并初始化。 
  
  n EXEC SQL CONTEXT FREE 
  
  这条语句释放了运行时上下文句柄指定的内存,并把它设置空值。 
  
  3.编程时要考虑的问题 
  尽管Oracle保证SQL库是线程安全的,但是你还是有责任保证你的Pro*C代码是为能在多线程下正确运行而设计的,例如,你必须考虑全局变量和静态变量的。 
  
  另外,多线程要求对以下问题进行考虑: 
  
  n 把sqlca结构定义成线程安全的。典型的做法是在每个函数开始定义一个同名的局部变量。 
  
  n sqlda结构也和sqlca结构一样处理。 
  
  n 把程序里的宿主变量定义成线程安全的。也就是说要小心处理程序里的全局变量和静态变量。 
  
  n 避免同一时刻不同线程使用同一个运行时上下文。 
  
  五.多线程例子 
  下面的例子运行在Red Hat9和Oracle9上。程序目的是用两个线程同时往一个表里插10000条记录,每个线程都拥有自己的运行时上下文。 
  
  #include 
  
  #include 
  
  #include /* Linux线程
  • 头文件 */ 
      
      #include "sqlca.h" /* Oracle头文件 */ 
      
      #define SQLCODE sqlca.sqlcode 
      
      static int insert_data( sql_context ); 
      
      static int start(); 
      
      int 
      
      main() 
      
      { 
      
      pthread_t tid1, tid2; 
      
      /* 创建两个线程 */ 
      
      if( pthread_create( &tid1,NULL,(void *)start,NULL ) ){ 
      
      printf( "创建线程失败!\n" ); 
      
      exit(1); 
      
      } 
      
      if( pthread_create( &tid2,NULL,(void *)start,NULL ) ){ 
      
      printf( "创建线程失败!\n" ); 
      
      exit(1); 
      
      } 
      
      /* 等待线程退出 */ 
      
      if( pthread_join( tid1,NULL ) ){ 
      
      printf( "等待线程结束失败!\n" ); 
      
      exit(1); 
      
      } 
      
      if( pthread_join( tid2,NULL ) ){ 
      
      printf( "等待线程结束失败!\n" ); 
      
      exit(1); 
      
      } 
      
      exit(0); 
      
      } 
      
      
      int 
      
      start() 
      
      { 
      
      sql_context context; 
      
      struct sqlca sqlca; /* 需要在此定义一个局部的sqlca */ 
      
      char uid[] = "dev/888888"; 
      /* 以下SQL语句的执行顺序不能更改 */ 
      
      EXEC SQL ENABLE THREADS; 
      
      EXEC SQL CONTEXT ALLOCATE :context; 
      
      EXEC SQL CONTEXT USE :context; 
      
      EXEC SQL CONNECT :uid; 
      
      if( SQLCODE < 0 ){ 
      
      printf( "创建数据库连接失败,%d:%s\n", SQLCODE,sqlca.sqlerrm.sqlerrmc); 
      
      return -1; 
      
      }insert_data( context ); 
      
      EXEC SQL COMMIT WORK RELEASE; 
      
      if( SQLCODE < 0 ){ 
      
      printf( "断开数据库连接失败!%d:%s\n", SQLCODE,sqlca.sqlerrm.sqlerrmc ); 
      
      return -1; 
      
      } 
      
      EXEC SQL CONTEXT FREE :context; 
      
      return 0; 
      
      } 
      
      static int 
      
      insert_data( context ) 
      
      sql_context context; 
      
      { 
      
      struct sqlca sqlca; /* 需要在此定义一个局部的sqlca */ 
      
      char name[11]; 
      
      int age; 
      
      int i; 
      
      strcpy( name, "test" ); 
      
      age = 20; 
      
      
      
      EXEC SQL CONTEXT USE :context; /* 指定执行SQL语句的上下文 */ 
      
      for( i=0; i
  • posted @ 2007-12-29 16:23 茶 阅读(287) | 评论 (0)编辑 收藏

    typedef

     
    typedef用法小结- -
    这两天在看程序的时候,发现很多地方都用到typedef,在结构体定义,还有一些数组等地方都大量的用到.但是有些地方还不是很清楚,今天下午,就想好好研究一下.上网搜了一下,有不少资料.归纳一下:
    来源一:Using typedef to Curb Miscreant Code
    Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。
    typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱。
    如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法?
    使用 typedefs 为现有类型创建同义字。
    定义易于记忆的类型名
      typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ''typedef'' 关键字右边。例如:
    typedef int size;
      此声明定义了一个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:
    void measure(size * psz);
    size array[4];
    size len = file.getlength();
    std::vector vs;
      typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:
    char line[81];
    char text[81];
    定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
    typedef char Line[81];
    Line text, secondline;
    getline(text);
    同样,可以象下面这样隐藏指针语法:
    typedef char * pstr;
    int mystrcmp(pstr, pstr);
      这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char *'类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():
    int mystrcmp(const pstr, const pstr);
      这是错误的,按照顺序,‘const pstr'被解释为‘char * const'(一个指向 char 的常量指针),而不是‘const char *'(指向常量 char 的指针)。这个问题很容易解决:
    typedef const char * cpstr;
    int mystrcmp(cpstr, cpstr); // 现在是正确的
    记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身是常量,而不是对象。
    代码简化
      上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:
    typedef int (*PF) (const char *, const char *);
      这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:
    PF Register(PF pf);
      Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
    int (*Register (int (*pf)(const char *, const char *)))
    (const char *, const char *);
      很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:"OK,有人还会写这样的代码吗?",快速浏览一下揭示 signal()函数的头文件 ,一个有同样接口的函数。
    typedef 和存储类关键字(storage class specifier)
      这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下面将带到第二个陷阱:
    typedef register int FAST_COUNTER; // 错误
      编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。
    促进跨平台开发
      typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:
    typedef long double REAL;
    在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
    typedef double REAL;
    并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:、
    typedef float REAL;
       你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string,allocator> 和 basic_ofstream>。
    作者简介
      Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。
    来源二:(http://www.ccfans.net/bbs/dispbbs.asp?boardid=30&;id=4455)
    C语言中typedef用法
    1. 基本解释
      typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。
      在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
      至于typedef有什么微妙之处,请你接着看下面对几个问题的具体阐述。
     2. typedef & 结构的问题
      当用下面的代码定义一个结构时,编译器报了一个错误,为什么呢?莫非C语言不允许在结构中包含指向它自己的指针吗?请你先猜想一下,然后看下文说明:
    typedef struct tagNode
    {
     char *pItem;
     pNode pNext;
    } *pNode;
      答案与分析:
      1、typedef的最简单使用
    typedef long byte_4;
      给已知数据类型long起个新名字,叫byte_4。
      2、 typedef与结构结合使用
    typedef struct tagMyStruct
    {
     int iNum;
     long lLength;
    } MyStruct;
      这语句实际上完成两个操作:
      1) 定义一个新的结构类型
    struct tagMyStruct
    {
     int iNum;
     long lLength;
    };
      分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct 关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。
      我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。
      2) typedef为这个新的结构起了一个名字,叫MyStruct。
    typedef struct tagMyStruct MyStruct;
      因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。
      答案与分析
      C语言当然允许在结构中包含指向它自己的指针,我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于typedef的应用。
      根据我们上面的阐述可以知道:新结构建立的过程中遇到了pNext域的声明,类型是pNode,要知道pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode。
      解决这个问题的方法有多种:
      1)、
    typedef struct tagNode
    {
     char *pItem;
     struct tagNode *pNext;
    } *pNode;
      2)、
    typedef struct tagNode *pNode;
    struct tagNode
    {
     char *pItem;
     pNode pNext;
    };
      注意:在这个例子中,你用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法。
      3)、规范做法:
    struct tagNode
    {
     char *pItem;
     struct tagNode *pNext;
    };
    typedef struct tagNode *pNode;
     3. typedef & #define的问题
      有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?
    typedef char *pStr;
    #define pStr char *;
      答案与分析:
      通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:
    typedef char *pStr1;
    #define pStr2 char *;
    pStr1 s1, s2;
    pStr2 s3, s4;
      在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。
      #define用法例子:
    #define f(x) x*x
    main( )
    {
     int a=6,b=2,c;
     c=f(a) / f(b);
     printf("%d \\n",c);
    }
      以下程序的输出结果是: 36。
      因为如此原因,在许多C语言编程规范中提到使用#define定义时,如果定义中包含表达式,必须使用括号,则上述定义应该如下定义才对:
    #define f(x) (x*x)
      当然,如果你使用typedef就没有这样的问题。
      4. typedef & #define的另一例
      下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?
    typedef char * pStr;
    char string[4] = "abc";
    const char *p1 = string;
    const pStr p2 = string;
    p1++;
    p2++;
      答案与分析:
      是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。
      #define与typedef引申谈
      1) #define宏定义有一个特别的长处:可以使用 #ifdef ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。
      2) typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。
      5. typedef & 复杂的变量声明
      在编程实践中,尤其是看别人代码的时候,常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值,比如:
      下面是三个变量的声明,我想使用typdef分别给它们定义一个别名,请问该如何做?
    >1:int *(*a[5])(int, char*);
    >2:void (*b[10]) (void (*)());
    >3. doube(*)() (*pa)[9];
      答案与分析:
      对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。
    >1:int *(*a[5])(int, char*);
    //pFun是我们建的一个类型别名
    typedef int *(*pFun)(int, char*);
    //使用定义的新类型来声明对象,等价于int* (*a[5])(int, char*);
    pFun a[5];
    >2:void (*b[10]) (void (*)());
    //首先为上面表达式蓝色部分声明一个新类型
    typedef void (*pFunParam)();
    //整体声明一个新类型
    typedef void (*pFun)(pFunParam);
    //使用定义的新类型来声明对象,等价于void (*b[10]) (void (*)());
    pFun b[10];
    >3. doube(*)() (*pa)[9];
    //首先为上面表达式蓝色部分声明一个新类型
    typedef double(*pFun)();
    //整体声明一个新类型
    typedef pFun (*pFunParam)[9];
    //使用定义的新类型来声明对象,等价于doube(*)() (*pa)[9];
    pFunParam pa;

    #define S(s) printf("%s\n", #s); s

     

    typedef struct _TS1{

        int x, y;

    } TS1, *PTS1, ***PPPTS1;  // TS1是结构体的名称,PTS1是结构体指针的名称

    // 也就是将结构体struct _TS1 命名为TS1,

    // struct _TS1 * 命名为 PTS1

    // struct _TS1 *** 命名为 PPPTS1

     

    typedef struct { // struct后面的结构体说明也可以去掉

        int x, y;

    } TS2, *PTS2;

     

    typedef PTS1 *PPTS1; // 定义PPTS1是指向PTS1的指针

     

    typedef struct _TTS1{

        typedef struct ITTS1 {

            int x, y;

        } iner;

        iner i;

        int x, y;

    } TTS1;

     

    //结构体内部的结构体也一样可以定义

    typedef TTS1::ITTS1 ITS1;

     

    void test_struct()

    {

        // 基本结构体重定义的使用

        TS1 ts1 = {100, 200};

        PTS1 pts1 = &ts1; // 完全等价于TS1* pts1 = &ts1;

        PPTS1 ppts1 = &pts1; // 完全等价于TS1** ppts1 = &pts1;

        PPPTS1 pppts1 = &ppts1; // 完全等价于 TS1*** pppts1 = &ppts1;

     

        TS2 ts2 = {99, 88};

        PTS2 pts2 = &ts2;   // 完全等价于 TS2* pts2 = &ts2;

     

        TTS1 itts1 = {{110, 220}, 10, 20};

        Its1* rits1 = &itts1.i;

        ITS1* &its1 = rits1; // 等价于 TTS1::ITTS1 *its1 = &(itts1.i);

     

        printf("ts1\t = (%d, %d)\n*pts1\t = (%d, %d)\n"

               "**ppts1\t = (%d, %d)\n***pppts1= (%d, %d)\n\n",

                ts1.x, ts1.y, pts1->x, pts1->y,

                (**ppts1).x, (**ppts1).y, (***pppts1).x, (***pppts1).y);

        printf("ts2\t = (%d, %d)\n*pts2\t = (%d, %d)\n\n",

            ts2.x, ts2.y, pts2->x, pts2->y);

        printf("itts1\t = [(%d, %d), %d, %d]\n*its1\t =  (%d, %d)\n\n",

            itts1.i.x, itts1.i.y, itts1.x, itts1.y, its1->x, its1->y);

     

        S(pts1->x = 119);

        S(pts2->y = 911);

        S(its1->x = 999);

     

        printf("ts1\t = (%d, %d)\n*pts1\t = (%d, %d)\n"

               "**ppts1\t = (%d, %d)\n***pppts1= (%d, %d)\n\n",

                ts1.x, ts1.y, pts1->x, pts1->y,

                (**ppts1).x, (**ppts1).y, (***pppts1).x, (***pppts1).y);

        printf("ts2\t = (%d, %d)\n*pts2\t = (%d, %d)\n\n",

            ts2.x, ts2.y, pts2->x, pts2->y);

        printf("itts1\t = [(%d, %d), %d, %d]\n*its1\t =  (%d, %d)\n\n",

            itts1.i.x, itts1.i.y, itts1.x, itts1.y, its1->x, its1->y);

     

        S((*ppts1)->y = -9999);

        printf("ts1\t = (%d, %d)\n**ppts1\t = (%d, %d)\n\n",

            ts1.x, ts1.y, (*ppts1)->x, (*ppts1)->y);

     

        S((**pppts1)->x = -12345);

        S((***pppts1).y = -67890);

        printf("ts1\t = (%d, %d)\n*pts1\t = (%d, %d)\n"

               "**ppts1\t = (%d, %d)\n***pppts1= (%d, %d)\n\n",

                ts1.x, ts1.y, pts1->x, pts1->y,

                (**ppts1).x, (**ppts1).y, (***pppts1).x, (***pppts1).y);

    }

    posted @ 2007-12-25 09:33 茶 阅读(433) | 评论 (0)编辑 收藏

    unix多进程2

    linux下的多进程编程(2007-5-21,11:17:55)
    什么是一个进程?进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。但和程序不同的是,在这个进程中,系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信,在了解这些之前,我们先要简单知道进程的结构。
    2.1 Linux下进程的结构
    Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段".其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。
    "代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序nokia s40 主题下载,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。
    2.2 Linux下的进程控制
    在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致,只在一些细节的地方有些区别手机和绚铃声,例如在Linux系统中调用vfork和fork完全相同,而在有些版本的Unix系统中,vfork调用有不同的功能。由于这些差别几乎不影响我们大多数的编程,在这里我们不予考虑。
    2.2.1 fork( )
    fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:
    void main(){
    int i;
    if ( fork() == 0 ) {
    /* 子进程程序 */
    for ( i = 1; i <1000; i ++ ) printf("This is child process ");
    }
    else {
    /* 父进程程序*/
    for ( i = 1; i <1000; i ++ ) printf("This is process process ");
    }
    }
    程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。 那么调用这个fork函数时发生了什么呢?fork函数启动一个新的进程,前面我们说过,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现,这将是我们下面的内容。既然它们如此相象,系统如何来区分它们呢?这是由函数的返回值来决定的。对于父进程,fork函数返回了子程序的进程号,而对于子程序中兴小灵通来电彩铃,fork函数则返回零。在操作系统中,我们用ps函数就可以看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数fork()下面的代码nokia 7650 铃声,而我们就是利用fork()函数对父子进程的不同返回值用if……else……语句来实现让父子进程完成不同的功能,正如我们上面举的例子一样。我们看到,上面例子执行时两条信息是交互无规则的打印出来的cect铃声下载,这是父子进程独立执行的结果,虽然我们的代码似乎和串行的代码没有什么区别。 读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一般CPU都是以"页"为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,象INTEL的CPU,其一页在通常情况下是4086字节大小,而无论是数据段还是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开。系统在空间上的开销就可以达到最小。
       下面演示一个足以"搞死"Linux的小程序,其源代码非常简单:
      void main()
       {
         for( ; ; ) fork();
       }
      这个程序什么也不做,就是死循环地fork,其结果是程序不断产生进程,而这些进程又不断产生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程"撑死了"。当然只要系统管理员预先给每个用户设置可运行的最大进程数,这个恶意的程序就完成不了企图了。
       2.2.2 exec( )函数族
      下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec函数族。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同,在Linux中,它们分别是:execl,execlp搞笑宝宝彩铃,execle,execv,execve和execvp,下面我只以execlp为例,其它函数究竟与execlp有何区别,请通过manexec命令来了解它们的具体情况。
      一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)
      那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:char command[256];
    void main()
    {
    int rtn; /*子进程的返回数值*/
    while(1) {
    /* 从终端读取要执行的命令 */
    printf( ">" );
    fgets( command, 256, stdin );
    command[strlen(command)-1] = 0;
    if ( fork() == 0 ) {
    /* 子进程执行此命令 */
    execlp( command, command );
    /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
    perror( command );
    exit( errorno );
    }
    else {
    /* 父进程, 等待子进程结束,并打印子进程的返回值 */
    wait ( &rtn );
    printf( " child process return %d ",. rtn );
    }
    }
    }
      此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也有exec类函数,其使用方法是类似的天津联通铃音下载,但DOS/WINDOWS还有spawn类函数,因为DOS是单任务的系统,它只能将"父进程"驻留在机器内再执行"子进程",这就是spawn类的函数。WIN32已经是多任务的系统了,但还保留了spawn类函数,WIN32中实现spawn函数的方法同前述UNIX中的方法差不多,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核心角度上讲不需要spawn类函数。
      在这一节里,我们还要讲讲system()和popen()函数。system()函数先调用fork(),然后再调用exec()来执行用户的登录shell,通过它来查找可执行文件的命令并分析参数,最后它么使用wait()函数族之一来等待子进程的结束。函数popen()和函数system()相似,不同的是它调用pipe()函数创建一个管道,通过它来完成程序的标准输入和标准输出。这两个函数是为那些不太勤快的程序员设计的,在效率和安全方面都有相当的缺陷,在可能的情况下,应该尽量避免。
    Linux联盟收集整理

    http://www.allegations.com.cn/baidu/8003/982.htm

    posted @ 2007-12-18 15:41 茶 阅读(505) | 评论 (0)编辑 收藏

    unix多进程

         摘要: 一.多进程程序的特点      由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在  实质上应该说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境  以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品". 以DOS  的概念...  阅读全文

    posted @ 2007-12-18 13:35 茶 阅读(436) | 评论 (0)编辑 收藏

    STL vector的使用

      http://www.cppreference.com/cppvector/index.html
    assign
    Syntax:
      #include <vector>
    void assign( size_type num, const TYPE& val );
    void assign( input_iterator start, input_iterator end );
    

    The assign() function either gives the current vector the values from start to end, or gives it num copies of val.

    This function will destroy the previous contents of the vector.

    For example, the following code uses assign() to put 10 copies of the integer 42 into a vector:

    vector<int> v;
     v
    .assign( 10, 42 );
     
    for( int i = 0; i < v.size(); i++ ) {
       cout
    << v[i] << " ";
     
    }
     cout
    << endl;            

    The above code displays the following output:

    42 42 42 42 42 42 42 42 42 42          

    The next example shows how assign() can be used to copy one vector to another:

    vector<int> v1;
     
    for( int i = 0; i < 10; i++ ) {
       v1
    .push_back( i );
     
    }              

     vector
    <int> v2;
     v2
    .assign( v1.begin(), v1.end() );            

     
    for( int i = 0; i < v2.size(); i++ ) {
       cout
    << v2[i] << " ";
     
    }
     cout
    << endl;            

    When run, the above code displays the following output:

    0 1 2 3 4 5 6 7 8 9     



    Vector constructors
    Syntax:
      #include <vector>
    vector();
    vector( const vector& c );
    vector( size_type num, const TYPE& val = TYPE() );
    vector( input_iterator start, input_iterator end );
    ~vector();
    

    The default vector constructor takes no arguments, creates a new instance of that vector.

    The second constructor is a default copy constructor that can be used to create a new vector that is a copy of the given vector c.

    The third constructor creates a vector with space for num objects. If val is specified, each of those objects will be given that value. For example, the following code creates a vector consisting of five copies of the integer 42:

    vector<int> v1( 5, 42 );         

    The last constructor creates a vector that is initialized to contain the elements between start and end. For example:

    // create a vector of random integers
     cout
    << "original vector: ";
     vector
    <int> v;
     
    for( int i = 0; i < 10; i++ ) {
       
    int num = (int) rand() % 10;
       cout
    << num << " ";
       v
    .push_back( num );
     
    }
     cout
    << endl;            

     
    // find the first element of v that is even
     vector
    <int>::iterator iter1 = v.begin();
     
    while( iter1 != v.end() && *iter1 % 2 != 0 ) {
       iter1
    ++;
     
    }              

     
    // find the last element of v that is even
     vector
    <int>::iterator iter2 = v.end();
     
    do {
       iter2
    --;
     
    } while( iter2 != v.begin() && *iter2 % 2 != 0 );              

     
    // only proceed if we find both numbers
     
    if( iter1 != v.end() && iter2 != v.begin() ) {
       cout
    << "first even number: " << *iter1 << ", last even number: " << *iter2 << endl;        

       cout
    << "new vector: ";
       vector
    <int> v2( iter1, iter2 );
       
    for( int i = 0; i < v2.size(); i++ ) {
         cout
    << v2[i] << " ";
       
    }
       cout
    << endl;
     
    }

    When run, this code displays the following output:

    original vector: 1 9 7 9 2 7 2 1 9 8
     first even number
    : 2, last even number: 8
     
    new vector: 2 7 2 1 9          

    All of these constructors run in linear time except the first, which runs in constant time.

    The default destructor is called when the vector should be destroyed.

     

    posted @ 2007-12-11 14:26 茶 阅读(4000) | 评论 (2)编辑 收藏

    网站记录

    http://stl.winterxy.com/ 号称最优秀的STL使用学习网站
    http://www.cppreference.com/ 英文STL使用资料参考网站
    http://www.uml.org.cn/oobject/Oobject-lt.asp  这玩意不通不行啊
    http://www.yesky.com/SoftChannel/72342371928702976/20031215/1753319.shtml com技术很nb的技术
    http://www.cnscn.org/

    http://www.jzxue.com/建站学

    http://www.bccn.net/Article/编程中国

    http://wenda.stx168.com/?site=yb 商问网

    http://topic.csdn.net/u/20081014/15/c69dd6c0-c0da-4ecf-a2c2-f0fa458bbc2a.html?seed=735903946
    失业七个月,面试六十家公司的深圳体验

    posted @ 2007-12-11 14:02 茶 阅读(246) | 评论 (0)编辑 收藏

    c++ STL 容器基础(一)

         摘要:   阅读全文

    posted @ 2007-12-11 14:00 茶 阅读(5578) | 评论 (2)编辑 收藏

    C++ STL编程入门基础【简摘】

         摘要:   阅读全文

    posted @ 2007-12-11 11:56 茶 阅读(630) | 评论 (0)编辑 收藏

    仅列出标题
    共4页: 1 2 3 4