<?xml version="1.0" encoding="utf-8" standalone="yes" ?><rss version='2.0' xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"  xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>编程联盟技术频道C/C++编程资料</title><link>http://www.bcbbs.net</link> <description>编程联盟技术频道C/C++编程资料</description> <copyright>Copyright  by  编程联盟 2007-2010</copyright> <item><title>ODBC来完成多平台数据库操作</title><link>http://www.bcbbs.net/News/Content.aspx?ID=28676</link><subject>ODBC来完成多平台数据库操作ODBC 是Open Database Connect 即开放数据库互连的简称，它是由Microsoft 公司于1991 年提出的一个用于访问数据库的统一界面标准，是应用程序和数据库系统之间的中间件。它通过使用相应应用平台上和所需数据库对应的驱动程序与应用程序的交互来实现对数据库的操作，避免了在应用程序中直接调用与数据库相关的操作，从而提供了数据库的独立性。    其中微软开发了一套完整的OBDC API，具体参阅：http://msdn.microsoft.com/en-us/library/ms131675.aspx    ODBC API：函数调用库、错误代码集和用于访问 DBMS 上数据的标准结构化查询语言的 (SQL) 语法。     在linux下也有很多对ODBC的实现，其中比较出色的是UnixODBC和iODBC。本文提供的多ODBC封装类CppODBC是在UnixODBC下测试的。由于UnixODBC的接口和微软开发的ODBC API一致（估计iODBC亦是如此），使不同操作系统、不同数据库之间的数据操作一致性带来了可能。     作者经过几天的时间，完成了一个简单的对ODBCapi的封装，下载地址：http://download.csdn.net/source/998541</subject><description><![CDATA[ODBC来完成多平台数据库操作ODBC 是Open Database Connect 即开放数据库互连的简称，它是由Microsoft 公司于1991 年提出的一个用于访问数据库的统一界面标准，是应用程序和数据库系统之间的中间件。它通过使用相应应用平台上和所需数据库对应的驱动程序与应用程序的交互来实现对数据库的操作，避免了在应用程序中直接调用与数据库相关的操作，从而提供了数据库的独立性。    其中微软开发了一套完整的OBDC API，具体参阅：http://msdn.microsoft.com/en-us/library/ms131675.aspx    ODBC API：函数调用库、错误代码集和用于访问 DBMS 上数据的标准结构化查询语言的 (SQL) 语法。     在linux下也有很多对ODBC的实现，其中比较出色的是UnixODBC和iODBC。本文提供的多ODBC封装类CppODBC是在UnixODBC下测试的。由于UnixODBC的接口和微软开发的ODBC API一致（估计iODBC亦是如此），使不同操作系统、不同数据库之间的数据操作一致性带来了可能。     作者经过几天的时间，完成了一个简单的对ODBCapi的封装，下载地址：http://download.csdn.net/source/998541]]></description><PubDate>2009-2-9 0:00:00</PubDate><category>C/C++编程</category></item><item><title>c++表达式中的-类型转换讲解</title><link>http://www.bcbbs.net/News/Content.aspx?ID=28987</link><subject>隐式类型转换1. 混合算术运算中，最宽的类型为目标转换类型　int x = 2.32+2;   //２被提升为double型2. 赋值运算中，被赋值对象为目标转换类型   int x = 2.32+2   //２被提升为double型后，相加为4.32  double再转换为int, 赋值给x, 注意小数位直接舍去。3. 函数调用参数中    double sqrt(double);    sqrt(2);   //会将２转为double型4. 函数调用返回值中    double sqrt(double)   {      return 2;      //２会被转为double型   }算术转换1. 为防止精度损失，类型总是转换为较宽的类型2. 所有小于整形的有序序列表达式中，在运算前都会转为整形char cval;bool found;enum mumble{m1, m2, m3} mval;unsigned long ulong;char c1='a', c2='b';cval+ulong;    //可认为cval先转为int型，再进行计算sizeof(c1+c2);  //会先将c1, c2都转换为int型unsigned int 与int相比，unsinged为较宽的有些例外，如long型+ unsinged int型，会都转为unsigned long(32位操作系统)</subject><description><![CDATA[隐式类型转换1. 混合算术运算中，最宽的类型为目标转换类型　int x = 2.32+2;   //２被提升为double型2. 赋值运算中，被赋值对象为目标转换类型   int x = 2.32+2   //２被提升为double型后，相加为4.32  double再转换为int, 赋值给x, 注意小数位直接舍去。3. 函数调用参数中    double sqrt(double);    sqrt(2);   //会将２转为double型4. 函数调用返回值中    double sqrt(double)   {      return 2;      //２会被转为double型   }算术转换1. 为防止精度损失，类型总是转换为较宽的类型2. 所有小于整形的有序序列表达式中，在运算前都会转为整形char cval;bool found;enum mumble{m1, m2, m3} mval;unsigned long ulong;char c1='a', c2='b';cval+ulong;    //可认为cval先转为int型，再进行计算sizeof(c1+c2);  //会先将c1, c2都转换为int型unsigned int 与int相比，unsinged为较宽的有些例外，如long型+ unsinged int型，会都转为unsigned long(32位操作系统)]]></description><PubDate>2009-2-17 0:00:00</PubDate><category>C/C++教程</category></item><item><title>链表的C语言实现之单链表的实现</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16878</link><subject>一、单链表的建立有了动态内存分配的基础，要实现链表就不难了。所谓链表，就是用一组任意的存储单元存储线性表元素的一种数据结构。链表又分为单链表、双向链表和循环链表等。我们先讲讲单链表。所谓单链表，是指数据接点是单向排列的。一个单链表结点，其结构类型分为两部分：1、数据域：用来存储本身数据2、链域或称为指针域：用来存储下一个结点地址或者说指向其直接后继的指针。例：typedef struct node
{char name[20];struct node *link;
}stud; 这样就定义了一个单链表的结构，其中char name[20]是一个用来存储姓名的字符型数组，指针*link是一个用来存储其直接后继的指针。定义好了链表的结构之后，只要在程序运行的时候爱数据域中存储适当的数据，如有后继结点，则把链域指向其直接后继，若没有，则置为NULL。下面就来看一个建立带表头（若未说明，以下所指链表均带表头）的单链表的完整程序。#include ＜stdio.h＞
#include ＜malloc.h＞ /*包含动态内存分配函数的头文件*/
#define N 10 /*N为人数*/ 
typedef struct node
{char name[20];struct node *link;
}stud; 
stud * creat(int n) /*建立单链表的函数，形参n为人数*/
{stud *p,*h,*s; /* *h保存表头结点的指针，*p指向当前结点的前一个结点，*s指向当前结点*/int i; /*计数器*/if((h=(stud *)malloc(sizeof(stud)))==NULL) /*分配空间并检测*/{printf(&quot;不能分配内存空间!&quot;);exit(0);}h-＞name[0]='\0'; /*把表头结点的数据域置空*/h-＞link=NULL; /*把表头结点的链域置空*/p=h; /*p指向表头结点*/for(i=0;i＜n;i++){if((s= (stud *) malloc(sizeof(stud)))==NULL) /*分配新存储空间并检测*/{printf(&quot;不能分配内存空间!&quot;);exit(0);}p-＞link=s; /*把s的地址赋给p所指向的结点的链域，这样就把p和s所指向的结点连接起来了*/printf(&quot;请输入第%d个人的姓名&quot;,i+1);scanf(&quot;%s&quot;,s-＞name); /*在当前结点s的数据域中存储姓名*/s-＞link=NULL;p=s;}return(h);
} 
main()
{int number; /*保存人数的变量*/stud *head; /*head是保存单链表的表头结点地址的指针*/number=N;head=creat(number); /*把所新建的单链表表头地址赋给head*/
}  这样就写好了一个可以建立包含N个人姓名的单链表了。写动态内存分配的程序应注意，请尽量对分配是否成功进行检测。</subject><description><![CDATA[一、单链表的建立有了动态内存分配的基础，要实现链表就不难了。所谓链表，就是用一组任意的存储单元存储线性表元素的一种数据结构。链表又分为单链表、双向链表和循环链表等。我们先讲讲单链表。所谓单链表，是指数据接点是单向排列的。一个单链表结点，其结构类型分为两部分：1、数据域：用来存储本身数据2、链域或称为指针域：用来存储下一个结点地址或者说指向其直接后继的指针。例：typedef struct node
{char name[20];struct node *link;
}stud; 这样就定义了一个单链表的结构，其中char name[20]是一个用来存储姓名的字符型数组，指针*link是一个用来存储其直接后继的指针。定义好了链表的结构之后，只要在程序运行的时候爱数据域中存储适当的数据，如有后继结点，则把链域指向其直接后继，若没有，则置为NULL。下面就来看一个建立带表头（若未说明，以下所指链表均带表头）的单链表的完整程序。#include ＜stdio.h＞
#include ＜malloc.h＞ /*包含动态内存分配函数的头文件*/
#define N 10 /*N为人数*/ 
typedef struct node
{char name[20];struct node *link;
}stud; 
stud * creat(int n) /*建立单链表的函数，形参n为人数*/
{stud *p,*h,*s; /* *h保存表头结点的指针，*p指向当前结点的前一个结点，*s指向当前结点*/int i; /*计数器*/if((h=(stud *)malloc(sizeof(stud)))==NULL) /*分配空间并检测*/{printf(&quot;不能分配内存空间!&quot;);exit(0);}h-＞name[0]='\0'; /*把表头结点的数据域置空*/h-＞link=NULL; /*把表头结点的链域置空*/p=h; /*p指向表头结点*/for(i=0;i＜n;i++){if((s= (stud *) malloc(sizeof(stud)))==NULL) /*分配新存储空间并检测*/{printf(&quot;不能分配内存空间!&quot;);exit(0);}p-＞link=s; /*把s的地址赋给p所指向的结点的链域，这样就把p和s所指向的结点连接起来了*/printf(&quot;请输入第%d个人的姓名&quot;,i+1);scanf(&quot;%s&quot;,s-＞name); /*在当前结点s的数据域中存储姓名*/s-＞link=NULL;p=s;}return(h);
} 
main()
{int number; /*保存人数的变量*/stud *head; /*head是保存单链表的表头结点地址的指针*/number=N;head=creat(number); /*把所新建的单链表表头地址赋给head*/
}  这样就写好了一个可以建立包含N个人姓名的单链表了。写动态内存分配的程序应注意，请尽量对分配是否成功进行检测。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>链表的C语言实现之动态内存分配</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16879</link><subject>一、为什么用动态内存分配但我们未学习链表的时候，如果要存储数量比较多的同类型或同结构的数据的时候，总是使用一个数组。比如说我们要存储一个班级学生的某科分数，总是定义一个float型（存在0.5分）数组：float score[30]; 但是，在使用数组的时候，总有一个问题困扰着我们：数组应该有多大？在很多的情况下，你并不能确定要使用多大的数组，比如上例，你可能并不知道该班级的学生的人数，那么你就要把数组定义得足够大。这样，你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道该班级的学生数，但是如果因为某种特殊原因人数有增加或者减少，你又必须重新去修改程序，扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷，特别是处理某些问题时：在大多数情况下会浪费大量的内存空间，在少数情况下，当你定义的数组不够大时，可能引起下标越界错误，甚至导致严重后果。那么有没有其它的方法来解决这样的外呢体呢？有，那就是动态内存分配。所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间，而是由系统根据程序的需要即时分配，且分配的大小就是程序要求的大小。从以上动、静态内存分配比较可以知道动态内存分配相对于景泰内存分配的特点：1、不需要预先分配存储空间；2、分配的空间可以根据程序的需要扩大或缩小。二、如何实现动态内存分配及其管理要实现根据程序的需要动态分配存储空间，就必须用到以下几个函数1、malloc函数malloc函数的原型为：void *malloc (unsigned int size) 其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数，返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是，当函数未能成功分配存储空间（如内存不足）就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。下例是一个动态分配的程序：#include 
#include 
main()
{int count,*array; /*count是一个计数器，array是一个整型指针，也可以理解为指向一个整型数组的首地址*/if((array(int *) malloc(10*sizeof(int)))==NULL){printf(&quot;不能成功分配存储空间。&quot;);exit(1);}for (count=0;count〈10;count++) /*给数组赋值*/array[count]=count;for(count=0;count〈10;count++) /*打印数组元素*/printf(&quot;%2d&quot;,array[count]);
} 上例中动态分配了10个整型存储区域，然后进行赋值并打印。例中if((array(int *) malloc(10*sizeof(int)))==NULL)语句可以分为以下几步：1）分配10个整型的连续存储空间，并返回一个指向其起始地址的整型指针2）把此整型指针地址赋给array3）检测返回值是否为NULL2、free函数由于内存区域总是有限的，不能不限制地分配下去，而且一个程序要尽量节省资源，所以当所分配的内存区域不用时，就要释放它，以便其它的变量或者程序使用。这时我们就要用到free函数。其函数原型是：void free(void *p) 作用是释放指针p所指向的内存区。其参数p必须是先前调用malloc函数或calloc函数（另一个动态分配存储区域的函数）时返回的指针。给free函数传递其它的值很可能造成死机或其它灾难性的后果。注意：这里重要的是指针的值，而不是用来申请动态内存的指针本身。例：int *p1,*p2;
p1=malloc(10*sizeof(int));
p2=p1;
……
free(p2) /*或者free(p2)*/ malloc返回值赋给p1，又把p1的值赋给p2，所以此时p1，p2都可作为free函数的参数。malloc函数是对存储区域进行分配的。free函数是释放已经不用的内存区域的。所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。</subject><description><![CDATA[一、为什么用动态内存分配但我们未学习链表的时候，如果要存储数量比较多的同类型或同结构的数据的时候，总是使用一个数组。比如说我们要存储一个班级学生的某科分数，总是定义一个float型（存在0.5分）数组：float score[30]; 但是，在使用数组的时候，总有一个问题困扰着我们：数组应该有多大？在很多的情况下，你并不能确定要使用多大的数组，比如上例，你可能并不知道该班级的学生的人数，那么你就要把数组定义得足够大。这样，你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道该班级的学生数，但是如果因为某种特殊原因人数有增加或者减少，你又必须重新去修改程序，扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷，特别是处理某些问题时：在大多数情况下会浪费大量的内存空间，在少数情况下，当你定义的数组不够大时，可能引起下标越界错误，甚至导致严重后果。那么有没有其它的方法来解决这样的外呢体呢？有，那就是动态内存分配。所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间，而是由系统根据程序的需要即时分配，且分配的大小就是程序要求的大小。从以上动、静态内存分配比较可以知道动态内存分配相对于景泰内存分配的特点：1、不需要预先分配存储空间；2、分配的空间可以根据程序的需要扩大或缩小。二、如何实现动态内存分配及其管理要实现根据程序的需要动态分配存储空间，就必须用到以下几个函数1、malloc函数malloc函数的原型为：void *malloc (unsigned int size) 其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数，返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是，当函数未能成功分配存储空间（如内存不足）就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。下例是一个动态分配的程序：#include 
#include 
main()
{int count,*array; /*count是一个计数器，array是一个整型指针，也可以理解为指向一个整型数组的首地址*/if((array(int *) malloc(10*sizeof(int)))==NULL){printf(&quot;不能成功分配存储空间。&quot;);exit(1);}for (count=0;count〈10;count++) /*给数组赋值*/array[count]=count;for(count=0;count〈10;count++) /*打印数组元素*/printf(&quot;%2d&quot;,array[count]);
} 上例中动态分配了10个整型存储区域，然后进行赋值并打印。例中if((array(int *) malloc(10*sizeof(int)))==NULL)语句可以分为以下几步：1）分配10个整型的连续存储空间，并返回一个指向其起始地址的整型指针2）把此整型指针地址赋给array3）检测返回值是否为NULL2、free函数由于内存区域总是有限的，不能不限制地分配下去，而且一个程序要尽量节省资源，所以当所分配的内存区域不用时，就要释放它，以便其它的变量或者程序使用。这时我们就要用到free函数。其函数原型是：void free(void *p) 作用是释放指针p所指向的内存区。其参数p必须是先前调用malloc函数或calloc函数（另一个动态分配存储区域的函数）时返回的指针。给free函数传递其它的值很可能造成死机或其它灾难性的后果。注意：这里重要的是指针的值，而不是用来申请动态内存的指针本身。例：int *p1,*p2;
p1=malloc(10*sizeof(int));
p2=p1;
……
free(p2) /*或者free(p2)*/ malloc返回值赋给p1，又把p1的值赋给p2，所以此时p1，p2都可作为free函数的参数。malloc函数是对存储区域进行分配的。free函数是释放已经不用的内存区域的。所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>深入探讨C++中的引用</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16889</link><subject>摘要：介绍C++引用的基本概念，通过详细的应用分析与说明，对引用进行全面、透彻地阐述。关键词：引用，const，多态，指针引用是C++引入的新语言特性，是C++常用的一个重要内容之一，正确、灵活地使用引用，可以使程序简洁、高效。我在工作中发现，许多人使用它仅仅是想当然，在某些微妙的场合，很容易出错，究其原由，大多因为没有搞清本源。故在本篇中我将对引用进行详细讨论，希望对大家更好地理解和使用引用起到抛砖引玉的作用。引用简介引用就是某一变量（目标）的一个别名，对引用的操作与对变量直接操作完全一样。引用的声明方法：类型标识符 &amp;引用名=目标变量名；【例1】：int a; int &amp;ra=a; //定义引用ra,它是变量a的引用，即别名说明：（1）&amp;在此不是求地址运算，而是起标识作用。（2）类型标识符是指目标变量的类型。（3）声明引用时，必须同时对其进行初始化。（4）引用声明完毕后，相当于目标变量名有两个名称，即该目标原名称和引用名，且不能再把该引用名作为其他变量名的别名。ra=1; 等价于 a=1; （5）声明一个引用，不是新定义了一个变量，它只表示该引用名是目标变量名的一个别名，它本身不是一种数据类型，因此引用本身不占存储单元，系统也不给引用分配存储单元。故：对引用求地址，就是对目标变量求地址。&amp;ra与&amp;a相等。（6）不能建立数组的引用。因为数组是一个由若干个元素所组成的集合，所以无法建立一个数组的别名。引用应用1、引用作为参数 引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递，如果有大块数据作为参数传递的时候，采用的方案往往是指针，因为这样可以避免将整块数据全部压栈，可以提高程序的效率。但是现在（C++中）又增加了一种同样有效率的选择（在某些特殊情况下又是必须的选择），就是引用。【例2】：void swap(int &amp;p1, int &amp;p2) //此处函数的形参p1, p2都是引用 
{ int p; p=p1; p1=p2; p2=p; } 为在程序中调用该函数，则相应的主调函数的调用点处，直接以变量作为实参进行调用即可，而不需要实参变量有任何的特殊要求。如：对应上面定义的swap函数，相应的主调函数可写为：main( )
{ int a,b;cin&gt;&gt;a&gt;&gt;b; //输入a,b两变量的值swap(a,b); //直接以变量a和b作为实参调用swap函数 cout&lt;&lt;a&lt;&lt; ' ' &lt;&lt;b; //输出结果 
} 上述程序运行时，如果输入数据10 20并回车后，则输出结果为20 10。由【例2】可看出：（1）传递引用给函数与传递指针的效果是一样的。这时，被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用，所以在被调函数中对形参变量的操作就是对其相应的目标对象（在主调函数中）的操作。（2）使用引用传递函数的参数，在内存中并没有产生实参的副本，它是直接对实参操作；而使用一般变量传递函数的参数，当发生函数调用时，需要给形参分配存储单元，形参变量是实参变量的副本；如果传递的是对象，还将调用拷贝构造函数。因此，当参数传递的数据较大时，用引用比用一般变量传递参数的效率和所占空间都好。（3）使用指针作为函数的参数虽然也能达到与使用引用的效果，但是，在被调函数中同样要给形参分配存储单元，且需要重复使用&quot;*指针变量名&quot;的形式进行运算，这很容易产生错误且程序的阅读性较差；另一方面，在主调函数的调用点处，必须用变量的地址作为实参。而引用更容易使用，更清晰。如果既要利用引用提高程序的效率，又要保护传递给函数的数据不在函数中被改变，就应使用常引用。2、常引用常引用声明方式：const 类型标识符 &amp;引用名=目标变量名；用这种方式声明的引用，不能通过引用对目标变量的值进行修改,从而使引用的目标成为const，达到了引用的安全性。【例3】： int a ;
const int &amp;ra=a;
ra=1; //错误
a=1; //正确 这不光是让代码更健壮，也有些其它方面的需要。【例4】：假设有如下函数声明：string foo( );
void bar(string &amp; s); 那么下面的表达式将是非法的：bar(foo( ));
bar(&quot;hello world&quot;); 原因在于foo( )和&quot;hello world&quot;串都会产生一个临时对象，而在C++中，这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型，这是非法的。引用型参数应该在能被定义为const的情况下，尽量定义为const 。3、引用作为返回值 要以引用返回函数值，则函数定义时要按以下格式：类型标识符 &amp;函数名（形参列表及类型说明）
{函数体}说明：（1）以引用返回函数值，定义函数时需要在函数名前加&amp;（2）用引用返回一个函数值的最大好处是，在内存中不产生被返回值的副本。【例5】以下程序中定义了一个普通的函数fn1（它用返回值的方法返回函数值），另外一个函数fn2，它以引用的方法返回函数值。#include &lt;iostream.h&gt;
float temp; //定义全局变量temp
float fn1(float r); //声明函数fn1
float &amp;fn2(float r); //声明函数fn2
float fn1(float r) //定义函数fn1，它以返回值的方法返回函数值
{ temp=(float)(r*r*3.14); return temp; 
}
float &amp;fn2(float r) //定义函数fn2，它以引用方式返回函数值
{ temp=(float)(r*r*3.14); return temp;
}
void main() //主函数
{ float a=fn1(10.0); //第1种情况，系统生成要返回值的副本（即临时变量）float &amp;b=fn1(10.0); //第2种情况，可能会出错（不同 C++系统有不同规定）//不能从被调函数中返回一个临时变量或局部变量的引用float c=fn2(10.0); //第3种情况，系统不生成返回值的副本//可以从被调函数中返回一个全局变量的引用float &amp;d=fn2(10.0); //第4种情况，系统不生成返回值的副本//可以从被调函数中返回一个全局变量的引用cout&lt;&lt;a&lt;&lt;c&lt;&lt;d;
} 引用作为返回值，必须遵守以下规则：（1）不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁，因此被返回的引用就成为了&quot;无所指&quot;的引用，程序会进入未知状态。 （2）不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题，可对于这种情况（返回函数内部new分配内存的引用），又面临其它尴尬局面。例如，被函数返回的引用只是作为一个临时变量出现，而没有被赋予一个实际的变量，那么这个引用所指向的空间（由new分配）就无法释放，造成memory leak。（3）可以返回类成员的引用，但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则（business rule）相关联的时候，其赋值常常与某些其它属性或者对象的状态有关，因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用（或指针），那么对该属性的单纯赋值就会破坏业务规则的完整性。（4）引用与一些操作符的重载：流操作符&lt;&lt;和&gt;&gt;，这两个操作符常常希望被连续使用，例如：cout &lt;&lt; &quot;hello&quot; &lt;&lt; endl;　因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括：返回一个流对象和返回一个流对象指针。但是对于返回一个流对象，程序必须重新（拷贝）构造一个新的流对象，也就是说，连续的两个&lt;&lt;操作符实际上是针对不同对象的！这无法让人接受。对于返回一个流指针则不能连续使用&lt;&lt;操作符。因此，返回一个流对象引用是惟一选择。这个唯一选择很关键，它说明了引用的重要性以及无可替代性，也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样，是可以连续使用的，例如：x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值，以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。【例6】 测试用返回引用的函数值作为赋值表达式的左值。#include &lt;iostream.h&gt;
int &amp;put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值，等价于vals[0]=10; 
put(9)=20; //以put(9)函数值作为左值，等价于vals[9]=10; 
cout&lt;&lt;vals[0]; 
cout&lt;&lt;vals[9];
} 
int &amp;put(int n)
{
if (n&gt;=0 &amp;&amp; n&lt;=9 ) return vals[n]; 
else { cout&lt;&lt;&quot;subscript error&quot;; return error; }
} （5）在另外的一些操作符中，却千万不能返回引用：+-*/ 四则运算符。它们不能返回引用，Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect，因此，它们必须构造一个对象作为返回值，可选的方案包括：返回一个对象、返回一个局部变量的引用，返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则，第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。4、引用和多态 引用是除指针外另一个可以产生多态效果的手段。这意味着，一个基类的引用可以指向它的派生类实例。【例7】： class 　A;
class 　B：public A{……};
B 　b;
A 　&amp;Ref = b; // 用派生类对象初始化基类对象的引用Ref 只能用来访问派生类对象中从基类继承下来的成员，是基类引用指向派生类。如果A类中定义有虚函数，并且在B类中重写了这个虚函数，就可以通过Ref产生多态效果。引用总结（1）在引用的使用中，单纯给某个变量取个别名是毫无意义的，引用的目的主要用于在函数参数传递中，解决大块数据或对象的传递效率和空间不如意的问题。（2）用引用传递函数的参数，能保证参数传递中不产生副本，提高传递的效率，且通过const的使用，保证了引用传递的安全性。（3）引用与指针的区别是，指针通过某个指针变量指向一个对象后，对它所指向的变量间接操作。程序中使用指针，程序的可读性差；而引用本身就是目标变量的别名，对引用的操作就是对目标变量的操作。（4）使用引用的时机。流操作符&lt;&lt;和&gt;&gt;、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。</subject><description><![CDATA[摘要：介绍C++引用的基本概念，通过详细的应用分析与说明，对引用进行全面、透彻地阐述。关键词：引用，const，多态，指针引用是C++引入的新语言特性，是C++常用的一个重要内容之一，正确、灵活地使用引用，可以使程序简洁、高效。我在工作中发现，许多人使用它仅仅是想当然，在某些微妙的场合，很容易出错，究其原由，大多因为没有搞清本源。故在本篇中我将对引用进行详细讨论，希望对大家更好地理解和使用引用起到抛砖引玉的作用。引用简介引用就是某一变量（目标）的一个别名，对引用的操作与对变量直接操作完全一样。引用的声明方法：类型标识符 &amp;引用名=目标变量名；【例1】：int a; int &amp;ra=a; //定义引用ra,它是变量a的引用，即别名说明：（1）&amp;在此不是求地址运算，而是起标识作用。（2）类型标识符是指目标变量的类型。（3）声明引用时，必须同时对其进行初始化。（4）引用声明完毕后，相当于目标变量名有两个名称，即该目标原名称和引用名，且不能再把该引用名作为其他变量名的别名。ra=1; 等价于 a=1; （5）声明一个引用，不是新定义了一个变量，它只表示该引用名是目标变量名的一个别名，它本身不是一种数据类型，因此引用本身不占存储单元，系统也不给引用分配存储单元。故：对引用求地址，就是对目标变量求地址。&amp;ra与&amp;a相等。（6）不能建立数组的引用。因为数组是一个由若干个元素所组成的集合，所以无法建立一个数组的别名。引用应用1、引用作为参数 引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递，如果有大块数据作为参数传递的时候，采用的方案往往是指针，因为这样可以避免将整块数据全部压栈，可以提高程序的效率。但是现在（C++中）又增加了一种同样有效率的选择（在某些特殊情况下又是必须的选择），就是引用。【例2】：void swap(int &amp;p1, int &amp;p2) //此处函数的形参p1, p2都是引用 
{ int p; p=p1; p1=p2; p2=p; } 为在程序中调用该函数，则相应的主调函数的调用点处，直接以变量作为实参进行调用即可，而不需要实参变量有任何的特殊要求。如：对应上面定义的swap函数，相应的主调函数可写为：main( )
{ int a,b;cin&gt;&gt;a&gt;&gt;b; //输入a,b两变量的值swap(a,b); //直接以变量a和b作为实参调用swap函数 cout&lt;&lt;a&lt;&lt; ' ' &lt;&lt;b; //输出结果 
} 上述程序运行时，如果输入数据10 20并回车后，则输出结果为20 10。由【例2】可看出：（1）传递引用给函数与传递指针的效果是一样的。这时，被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用，所以在被调函数中对形参变量的操作就是对其相应的目标对象（在主调函数中）的操作。（2）使用引用传递函数的参数，在内存中并没有产生实参的副本，它是直接对实参操作；而使用一般变量传递函数的参数，当发生函数调用时，需要给形参分配存储单元，形参变量是实参变量的副本；如果传递的是对象，还将调用拷贝构造函数。因此，当参数传递的数据较大时，用引用比用一般变量传递参数的效率和所占空间都好。（3）使用指针作为函数的参数虽然也能达到与使用引用的效果，但是，在被调函数中同样要给形参分配存储单元，且需要重复使用&quot;*指针变量名&quot;的形式进行运算，这很容易产生错误且程序的阅读性较差；另一方面，在主调函数的调用点处，必须用变量的地址作为实参。而引用更容易使用，更清晰。如果既要利用引用提高程序的效率，又要保护传递给函数的数据不在函数中被改变，就应使用常引用。2、常引用常引用声明方式：const 类型标识符 &amp;引用名=目标变量名；用这种方式声明的引用，不能通过引用对目标变量的值进行修改,从而使引用的目标成为const，达到了引用的安全性。【例3】： int a ;
const int &amp;ra=a;
ra=1; //错误
a=1; //正确 这不光是让代码更健壮，也有些其它方面的需要。【例4】：假设有如下函数声明：string foo( );
void bar(string &amp; s); 那么下面的表达式将是非法的：bar(foo( ));
bar(&quot;hello world&quot;); 原因在于foo( )和&quot;hello world&quot;串都会产生一个临时对象，而在C++中，这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型，这是非法的。引用型参数应该在能被定义为const的情况下，尽量定义为const 。3、引用作为返回值 要以引用返回函数值，则函数定义时要按以下格式：类型标识符 &amp;函数名（形参列表及类型说明）
{函数体}说明：（1）以引用返回函数值，定义函数时需要在函数名前加&amp;（2）用引用返回一个函数值的最大好处是，在内存中不产生被返回值的副本。【例5】以下程序中定义了一个普通的函数fn1（它用返回值的方法返回函数值），另外一个函数fn2，它以引用的方法返回函数值。#include &lt;iostream.h&gt;
float temp; //定义全局变量temp
float fn1(float r); //声明函数fn1
float &amp;fn2(float r); //声明函数fn2
float fn1(float r) //定义函数fn1，它以返回值的方法返回函数值
{ temp=(float)(r*r*3.14); return temp; 
}
float &amp;fn2(float r) //定义函数fn2，它以引用方式返回函数值
{ temp=(float)(r*r*3.14); return temp;
}
void main() //主函数
{ float a=fn1(10.0); //第1种情况，系统生成要返回值的副本（即临时变量）float &amp;b=fn1(10.0); //第2种情况，可能会出错（不同 C++系统有不同规定）//不能从被调函数中返回一个临时变量或局部变量的引用float c=fn2(10.0); //第3种情况，系统不生成返回值的副本//可以从被调函数中返回一个全局变量的引用float &amp;d=fn2(10.0); //第4种情况，系统不生成返回值的副本//可以从被调函数中返回一个全局变量的引用cout&lt;&lt;a&lt;&lt;c&lt;&lt;d;
} 引用作为返回值，必须遵守以下规则：（1）不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁，因此被返回的引用就成为了&quot;无所指&quot;的引用，程序会进入未知状态。 （2）不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题，可对于这种情况（返回函数内部new分配内存的引用），又面临其它尴尬局面。例如，被函数返回的引用只是作为一个临时变量出现，而没有被赋予一个实际的变量，那么这个引用所指向的空间（由new分配）就无法释放，造成memory leak。（3）可以返回类成员的引用，但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则（business rule）相关联的时候，其赋值常常与某些其它属性或者对象的状态有关，因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用（或指针），那么对该属性的单纯赋值就会破坏业务规则的完整性。（4）引用与一些操作符的重载：流操作符&lt;&lt;和&gt;&gt;，这两个操作符常常希望被连续使用，例如：cout &lt;&lt; &quot;hello&quot; &lt;&lt; endl;　因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括：返回一个流对象和返回一个流对象指针。但是对于返回一个流对象，程序必须重新（拷贝）构造一个新的流对象，也就是说，连续的两个&lt;&lt;操作符实际上是针对不同对象的！这无法让人接受。对于返回一个流指针则不能连续使用&lt;&lt;操作符。因此，返回一个流对象引用是惟一选择。这个唯一选择很关键，它说明了引用的重要性以及无可替代性，也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样，是可以连续使用的，例如：x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值，以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。【例6】 测试用返回引用的函数值作为赋值表达式的左值。#include &lt;iostream.h&gt;
int &amp;put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值，等价于vals[0]=10; 
put(9)=20; //以put(9)函数值作为左值，等价于vals[9]=10; 
cout&lt;&lt;vals[0]; 
cout&lt;&lt;vals[9];
} 
int &amp;put(int n)
{
if (n&gt;=0 &amp;&amp; n&lt;=9 ) return vals[n]; 
else { cout&lt;&lt;&quot;subscript error&quot;; return error; }
} （5）在另外的一些操作符中，却千万不能返回引用：+-*/ 四则运算符。它们不能返回引用，Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect，因此，它们必须构造一个对象作为返回值，可选的方案包括：返回一个对象、返回一个局部变量的引用，返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则，第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。4、引用和多态 引用是除指针外另一个可以产生多态效果的手段。这意味着，一个基类的引用可以指向它的派生类实例。【例7】： class 　A;
class 　B：public A{……};
B 　b;
A 　&amp;Ref = b; // 用派生类对象初始化基类对象的引用Ref 只能用来访问派生类对象中从基类继承下来的成员，是基类引用指向派生类。如果A类中定义有虚函数，并且在B类中重写了这个虚函数，就可以通过Ref产生多态效果。引用总结（1）在引用的使用中，单纯给某个变量取个别名是毫无意义的，引用的目的主要用于在函数参数传递中，解决大块数据或对象的传递效率和空间不如意的问题。（2）用引用传递函数的参数，能保证参数传递中不产生副本，提高传递的效率，且通过const的使用，保证了引用传递的安全性。（3）引用与指针的区别是，指针通过某个指针变量指向一个对象后，对它所指向的变量间接操作。程序中使用指针，程序的可读性差；而引用本身就是目标变量的别名，对引用的操作就是对目标变量的操作。（4）使用引用的时机。流操作符&lt;&lt;和&gt;&gt;、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十四讲 枚举与位运算(2)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16893</link><subject>位域有些信息在存储时，并不需要占用一个完整的字节， 而只需占几个或一个二进制位。例如在存放一个开关量时，只有0和1 两种状态， 用一位二进位即可。为了节省存储空间，并使处理简便，C语言又提供了一种数据结构，称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域， 并说明每个区域的位数。每个域有一个域名，允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿，其形式为： struct 位域结构名 { 位域列表 };其中位域列表的形式为： 类型说明符 位域名：位域长度 例如： struct bs
{int a:8;int b:2;int c:6;
};  位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明，同时定义说明或者直接说明这三种方式。例如： struct bs
{int a:8;int b:2;int c:6;
}data; 说明data为bs变量，共占两个字节。其中位域a占8位，位域b占2位，位域c占6位。对于位域的定义尚有以下几点说明：1. 一个位域必须存储在同一个字节中，不能跨两个字节。如一个字节所剩空间不够存放另一位域时，应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如： struct bs
{unsigned a:4unsigned :0 /*空域*/unsigned b:4 /*从下一单元开始存放*/unsigned c:4
} 在这个位域定义中，a占第一字节的4位，后4位填0表示不使用，b从第二字节开始，占用4位，c占用4位。2. 由于位域不允许跨两个字节，因此位域的长度不能大于一个字节的长度，也就是说不能超过8位二进位。3. 位域可以无位域名，这时它只用来作填充或调整位置。无名的位域是不能使用的。例如： struct k
{int a:1int :2 /*该2位不能使用*/int b:3int c:2
};  从以上分析可以看出，位域在本质上就是一种结构类型， 不过其成员是按二进位分配的。二、位域的使用位域的使用和结构成员的使用相同，其一般形式为： 位域变量名&#183;位域名 位域允许用各种格式输出。main(){struct bs{unsigned a:1;unsigned b:3;unsigned c:4;} bit,*pbit;bit.a=1;bit.b=7;bit.c=15;printf(&quot;%d,%d,%d\n&quot;,bit.a,bit.b,bit.c);pbit=&amp;bit;pbit-&gt;a=0;pbit-&gt;b&amp;=3;pbit-&gt;c|=1;printf(&quot;%d,%d,%d\n&quot;,pbit-&gt;a,pbit-&gt;b,pbit-&gt;c);
}  上例程序中定义了位域结构bs，三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针方式给位域a重新赋值，赋为0。第15行使用了复合的位运算符&quot;&amp;=&quot;， 该行相当于： pbit-&gt;b=pbit-&gt;b&amp;3位域b中原有值为7，与3作按位与运算的结果为3(111&amp;011=011,十进制值为3)。同样，程序第16行中使用了复合位运算&quot;|=&quot;， 相当于： pbit-&gt;c=pbit-&gt;c|1其结果为15。程序第17行用指针方式输出了这三个域的值。类型定义符typedef C语言不仅提供了丰富的数据类型，而且还允许由用户自己定义类型说明符，也就是说允许由用户为数据类型取“别名”。 类型定义符typedef即可用来完成此功能。例如，有整型量a,b,其说明如下： int aa,b; 其中int是整型变量的类型说明符。int的完整写法为integer，为了增加程序的可读性，可把整型说明符用typedef定义为： typedef int INTEGER 这以后就可用INTEGER来代替int作整型变量的类型说明了。 例如： INTEGER a,b;它等效于： int a,b; 用typedef定义数组、指针、结构等类型将带来很大的方便，不仅使程序书写简单而且使意义更为明确，因而增强了可读性。例如：typedef char NAME[20]; 表示NAME是字符数组类型，数组长度为20。 然后可用NAME 说明变量，如： NAME a1,a2,s1,s2;完全等效于： char a1[20],a2[20],s1[20],s2[20]
又如： typedef struct stu{ char name[20];int age;char sex;
} STU; 定义STU表示stu的结构类型，然后可用STU来说明结构变量： STU body1,body2;typedef定义的一般形式为： typedef 原类型名 新类型名 其中原类型名中含有定义部分，新类型名一般用大写表示， 以便于区别。在有时也可用宏定义来代替typedef的功能，但是宏定义是由预处理完成的，而typedef则是在编译时完成的，后者更为灵活方便。</subject><description><![CDATA[位域有些信息在存储时，并不需要占用一个完整的字节， 而只需占几个或一个二进制位。例如在存放一个开关量时，只有0和1 两种状态， 用一位二进位即可。为了节省存储空间，并使处理简便，C语言又提供了一种数据结构，称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域， 并说明每个区域的位数。每个域有一个域名，允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿，其形式为： struct 位域结构名 { 位域列表 };其中位域列表的形式为： 类型说明符 位域名：位域长度 例如： struct bs
{int a:8;int b:2;int c:6;
};  位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明，同时定义说明或者直接说明这三种方式。例如： struct bs
{int a:8;int b:2;int c:6;
}data; 说明data为bs变量，共占两个字节。其中位域a占8位，位域b占2位，位域c占6位。对于位域的定义尚有以下几点说明：1. 一个位域必须存储在同一个字节中，不能跨两个字节。如一个字节所剩空间不够存放另一位域时，应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如： struct bs
{unsigned a:4unsigned :0 /*空域*/unsigned b:4 /*从下一单元开始存放*/unsigned c:4
} 在这个位域定义中，a占第一字节的4位，后4位填0表示不使用，b从第二字节开始，占用4位，c占用4位。2. 由于位域不允许跨两个字节，因此位域的长度不能大于一个字节的长度，也就是说不能超过8位二进位。3. 位域可以无位域名，这时它只用来作填充或调整位置。无名的位域是不能使用的。例如： struct k
{int a:1int :2 /*该2位不能使用*/int b:3int c:2
};  从以上分析可以看出，位域在本质上就是一种结构类型， 不过其成员是按二进位分配的。二、位域的使用位域的使用和结构成员的使用相同，其一般形式为： 位域变量名&#183;位域名 位域允许用各种格式输出。main(){struct bs{unsigned a:1;unsigned b:3;unsigned c:4;} bit,*pbit;bit.a=1;bit.b=7;bit.c=15;printf(&quot;%d,%d,%d\n&quot;,bit.a,bit.b,bit.c);pbit=&amp;bit;pbit-&gt;a=0;pbit-&gt;b&amp;=3;pbit-&gt;c|=1;printf(&quot;%d,%d,%d\n&quot;,pbit-&gt;a,pbit-&gt;b,pbit-&gt;c);
}  上例程序中定义了位域结构bs，三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针方式给位域a重新赋值，赋为0。第15行使用了复合的位运算符&quot;&amp;=&quot;， 该行相当于： pbit-&gt;b=pbit-&gt;b&amp;3位域b中原有值为7，与3作按位与运算的结果为3(111&amp;011=011,十进制值为3)。同样，程序第16行中使用了复合位运算&quot;|=&quot;， 相当于： pbit-&gt;c=pbit-&gt;c|1其结果为15。程序第17行用指针方式输出了这三个域的值。类型定义符typedef C语言不仅提供了丰富的数据类型，而且还允许由用户自己定义类型说明符，也就是说允许由用户为数据类型取“别名”。 类型定义符typedef即可用来完成此功能。例如，有整型量a,b,其说明如下： int aa,b; 其中int是整型变量的类型说明符。int的完整写法为integer，为了增加程序的可读性，可把整型说明符用typedef定义为： typedef int INTEGER 这以后就可用INTEGER来代替int作整型变量的类型说明了。 例如： INTEGER a,b;它等效于： int a,b; 用typedef定义数组、指针、结构等类型将带来很大的方便，不仅使程序书写简单而且使意义更为明确，因而增强了可读性。例如：typedef char NAME[20]; 表示NAME是字符数组类型，数组长度为20。 然后可用NAME 说明变量，如： NAME a1,a2,s1,s2;完全等效于： char a1[20],a2[20],s1[20],s2[20]
又如： typedef struct stu{ char name[20];int age;char sex;
} STU; 定义STU表示stu的结构类型，然后可用STU来说明结构变量： STU body1,body2;typedef定义的一般形式为： typedef 原类型名 新类型名 其中原类型名中含有定义部分，新类型名一般用大写表示， 以便于区别。在有时也可用宏定义来代替typedef的功能，但是宏定义是由预处理完成的，而typedef则是在编译时完成的，后者更为灵活方便。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>Java与C++编程的不同</title><link>http://www.bcbbs.net/News/Content.aspx?ID=55563</link><subject>1. 数据类型和变量C++ 中的变量类型与Java很相似。像Java一样，C++ 有int 和 double 类型。但是这些数字类型的取值范围是依赖于机器的。比如在16位系统上，例如运行DOS 或Windows 3.x的PC机上，int 是双字节(2-byte)的，取值范围比Java的4-byte的int 要小很多。在这些机器上，如果 int 不够用的话，你需要使用长整型long.C++ 有 short 和 unsigned 类型来更有效的存储数字。(我认为所谓有效是指更高的空间利用率。) 最好是尽量避免使用这些类型除非是空间利用的有效性对你的系统真的非常重要。在C++中布尔型用 bool 表示，而不像在Java中用boolean.C++ 中字符串类型用 string 表示。它与Java中的String 类型非常相似，但是，还是要逐一以下几点不同之处：1. C++ 字符串存储ASCII 码字符，而不是标准码Unicode 字符2. C++ 字符串是可以被修改的，而Java字符串的内容是不可修改的(immutable)。3. 取子字符串的操作在 C++ 中叫做substr，这个命令s.substr(i， n) 从字符串s中取得从位置 i 始长度为n的子字符串。4. 在C++中，你只能够将字符串与其它字符串对象相串联(concatenate)，而不能够与任意的对象相串联。5. C++中可以直接使用关系操作符 ==、 !=、 &lt;、 &lt;=、 &gt;、 &gt;= 来进行字符串比较，其中后面四个操作符是按字母顺序进行比较的。 这比Java中使用函数equals和compareTo来比较要方便很多。2.  变量和常量在C++中，本地变量的定义看起来与Java中相同，例如：int n = 5;实际上这正是C++和Java的一个重要不同之处。C++编译器不对本地变量进行初始化检验，所以在C++中很容易忘记初始化一个变量，这种情况下，变量的值该变量所占内存区域中刚好当前存在随机值。这显然是很容易产生程序出错的地方。与Java一样， C++中类可以有数据域和静态变量。不同的是，C++中变量可以在函数甚至是类的外面定义，这些所谓的全局变量可以在程序的任何函数中被访问，因而不易被很好的管理。所C++中应该尽量避免使用全局变量。在C++中，常量可以在任何地方被定义(记得在Java中，常量必须是类的静态数据static data)。 C++ 使用关键字 const 来定义常量，而Java中是 final。例如：const int DAYS_PER_YEAR = 365;3. 类C++中对类的定义与Java有些不同，这里是一个例子：一个C++ 版本的Point 类：class Point /* C++ */{public:Point();Point(double xval, double yval);void move(double dx, double dy);double getX() const;double getY() const;private:double x;double y;};这里几点重要的不同是：1. C++的类定义中分为公共和私有部分，分别以关键字 public 和 private开始。而在Java中，每一个元素都必须标明 public 或 private；2. C++中类的定义只包含函数的声明，真正的实现另外单独列出；3. 访问函数(accessor methods)标有关键字 const ，表明这个函数不会改变本对象的元素值；4. 类定义的结尾处有分号。类中函数的实现跟在类的定义之后。因为函数是在类外面定义的，所以每一个函数的名字前面要加类名称作为前缀，并使用操作符双冒号：：来分割类的名称和函数的名称。不改变隐含参数值(即当前对象的值)的访问函数用 const标明。如下所示是上面类定义中的函数的实现：Point::Point() { x = 0; y = 0; }void Point::move(double dx, double dy){x = x + dx;y = y + dy;}double Point::getX() const{return x;}</subject><description><![CDATA[1. 数据类型和变量C++ 中的变量类型与Java很相似。像Java一样，C++ 有int 和 double 类型。但是这些数字类型的取值范围是依赖于机器的。比如在16位系统上，例如运行DOS 或Windows 3.x的PC机上，int 是双字节(2-byte)的，取值范围比Java的4-byte的int 要小很多。在这些机器上，如果 int 不够用的话，你需要使用长整型long.C++ 有 short 和 unsigned 类型来更有效的存储数字。(我认为所谓有效是指更高的空间利用率。) 最好是尽量避免使用这些类型除非是空间利用的有效性对你的系统真的非常重要。在C++中布尔型用 bool 表示，而不像在Java中用boolean.C++ 中字符串类型用 string 表示。它与Java中的String 类型非常相似，但是，还是要逐一以下几点不同之处：1. C++ 字符串存储ASCII 码字符，而不是标准码Unicode 字符2. C++ 字符串是可以被修改的，而Java字符串的内容是不可修改的(immutable)。3. 取子字符串的操作在 C++ 中叫做substr，这个命令s.substr(i， n) 从字符串s中取得从位置 i 始长度为n的子字符串。4. 在C++中，你只能够将字符串与其它字符串对象相串联(concatenate)，而不能够与任意的对象相串联。5. C++中可以直接使用关系操作符 ==、 !=、 &lt;、 &lt;=、 &gt;、 &gt;= 来进行字符串比较，其中后面四个操作符是按字母顺序进行比较的。 这比Java中使用函数equals和compareTo来比较要方便很多。2.  变量和常量在C++中，本地变量的定义看起来与Java中相同，例如：int n = 5;实际上这正是C++和Java的一个重要不同之处。C++编译器不对本地变量进行初始化检验，所以在C++中很容易忘记初始化一个变量，这种情况下，变量的值该变量所占内存区域中刚好当前存在随机值。这显然是很容易产生程序出错的地方。与Java一样， C++中类可以有数据域和静态变量。不同的是，C++中变量可以在函数甚至是类的外面定义，这些所谓的全局变量可以在程序的任何函数中被访问，因而不易被很好的管理。所C++中应该尽量避免使用全局变量。在C++中，常量可以在任何地方被定义(记得在Java中，常量必须是类的静态数据static data)。 C++ 使用关键字 const 来定义常量，而Java中是 final。例如：const int DAYS_PER_YEAR = 365;3. 类C++中对类的定义与Java有些不同，这里是一个例子：一个C++ 版本的Point 类：class Point /* C++ */{public:Point();Point(double xval, double yval);void move(double dx, double dy);double getX() const;double getY() const;private:double x;double y;};这里几点重要的不同是：1. C++的类定义中分为公共和私有部分，分别以关键字 public 和 private开始。而在Java中，每一个元素都必须标明 public 或 private；2. C++中类的定义只包含函数的声明，真正的实现另外单独列出；3. 访问函数(accessor methods)标有关键字 const ，表明这个函数不会改变本对象的元素值；4. 类定义的结尾处有分号。类中函数的实现跟在类的定义之后。因为函数是在类外面定义的，所以每一个函数的名字前面要加类名称作为前缀，并使用操作符双冒号：：来分割类的名称和函数的名称。不改变隐含参数值(即当前对象的值)的访问函数用 const标明。如下所示是上面类定义中的函数的实现：Point::Point() { x = 0; y = 0; }void Point::move(double dx, double dy){x = x + dx;y = y + dy;}double Point::getX() const{return x;}]]></description><PubDate>2009-7-18 0:00:00</PubDate><category>C/C++</category></item><item><title>C语言初学者入门讲座 第十一讲 指针的慨念(1)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16901</link><subject>指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构； 能很方便地使用数组和字符串； 并能象汇编语言一样处理内存地址，从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环， 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时， 指针也是C语言中最为困难的一部分，在学习中除了要正确理解基本概念，还必须要多编程，上机调试。只要作到这些，指针也是不难掌握的。指针的基本概念 在计算机中，所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元， 不同的数据类型所占用的内存单元数不等，如整型量占2个单元，字符量占1个单元等， 在第二章中已有详细的介绍。为了正确地访问这些内存单元， 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元，所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时， 银行工作人员将根据我们的帐号去找我们的存款单， 找到之后在存单上写入存款、取款的金额。在这里，帐号就是存单的指针， 存款数是存单的内容。对于一个内存单元来说，单元的地址即为指针， 其中存放的数据才是该单元的内容。在C语言中， 允许用一个变量来存放指针，这种变量称为指针变量。因此， 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中，设有字符变量C，其内容为“K”(ASCII码为十进制数 75)，C占用了011A号单元(地址用十六进数表示)。设有指针变量P，内容为011A， 这种情况我们称为P指向变量C，或说P是指向变量C的指针。 严格地说，一个指针是一个地址， 是一个常量。而一个指针变量却可以被赋予不同的指针值，是变。 但在常把指针变量简称为指针。为了避免混淆，我们中约定：“指针”是指地址， 是常量，“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。既然指针变量的值是一个地址， 那么这个地址不仅可以是变量的地址， 也可以是其它数据结构的地址。在一个指针变量中存放一
个数组或一个函数的首地址有何意义呢？ 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址， 也就找到了该数组或函数。这样一来， 凡是出现数组，函数的地方都可以用一个指针变量来表示， 只要该指针变量中赋予数组或函数的首地址即可。这样做， 将会使程序的概念十分清楚，程序本身也精练，高效。在C语言中， 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构， 而“指针”虽然实际上也是一个地址，但它却是一个数据结构的首地址， 它是“指向”一个数据结构的，因而概念更为清楚，表示更为明确。 这也是引入“指针”概念的一个重要原因。指针变量的类型说明对指针变量的类型说明包括三个内容：(1)指针类型说明，即定义变量为一个指针变量； (2)指针变量名；(3)变量值(指针)所指向的变量的数据类型。其一般形式为： 类型说明符 *变量名； 其中，*表示这是一个指针变量，变量名即为定义的指针变量名，类型说明符表示本指针变量所指向的变量的数据类型。例如： int *p1;表示p1是一个指针变量，它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量， 应由向p1赋予的地址来决定。再如：staic int *p2; /*p2是指向静态整型变量的指针变量*/
float *p3; /*p3是指向浮点变量的指针变量*/
char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是，一个指针变量只能指向同类型的变量，如P3 只能指向浮点变量，不能时而指向一个浮点变量， 时而又指向一个字符变量。 指针变量的赋值指针变量同普通变量一样，使用之前不仅要定义说明， 而且必须赋予具体的值。未经赋值的指针变量不能使用， 否则将造成系统混乱，甚至死机。指针变量的赋值只能赋予地址， 决不能赋予任何其它数据，否则将引起错误。在C语言中， 变量的地址是由编译系统分配的，对用户完全透明，用户不知道变量的具体地址。 C语言中提供了地址运算符&amp;来表示变量的地址。其一般形式为： &amp; 变量名； 如&amp;a变示变量a的地址，&amp;b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p，如要把整型变量a 的地址赋予p可以有以下两种方式：(1)指针变量初始化的方法 int a;
int *p=&amp;a; (2)赋值语句的方法int a;
int *p;
p=&amp;a; 不允许把一个数赋予指针变量，故下面的赋值是错误的： int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符，如写为*p=&amp;a 也是错误的指针变量的运算指针变量可以进行某些运算，但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。1.指针运算符(1)取地址运算符&amp;取地址运算符&amp;是单目运算符，其结合性为自右至左，其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中，我们已经了解并使用了&amp;运算符。(2)取内容运算符*取内容运算符*是单目运算符，其结合性为自右至左，用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中，“*”是类型说明符，表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。main(){int a=5,*p=&amp;a;printf (&quot;%d&quot;,*p);
}
...... 表示指针变量p取得了整型变量a的地址。本语句表示输出变量a的值。2.指针变量的运算(1)赋值运算指针变量的赋值运算有以下几种形式：①指针变量初始化赋值，前面已作介绍。②把一个变量的地址赋予指向相同数据类型的指针变量。例如：int a,*pa;
pa=&amp;a; /*把整型变量a的地址赋予整型指针变量pa*/ ③把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如：int a,*pa=&amp;a,*pb;
pb=pa; /*把a的地址赋予指针变量pb*/ 由于pa,pb均为指向整型变量的指针变量，因此可以相互赋值。④把数组的首地址赋予指向数组的指针变量。例如：int a[5],*pa;
pa=a; (数组名表示数组的首地址，故可赋予指向数组的指针变量pa) 也可写为：pa=&amp;a[0]; /*数组第一个元素的地址也是整个数组的首地址， 也可赋予pa*/当然也可采取初始化赋值的方法：int a[5],*pa=a; ⑤把字符串的首地址赋予指向字符类型的指针变量。例如： char *pc;pc=&quot;c language&quot;;或用初始化赋值的方法写为： char *pc=&quot;C Language&quot;; 这里应说明的是并不是把整个字符串装入指针变量， 而是把存放该字符串的字符数组的首地址装入指针变量。 在后面还将详细介绍。⑥把函数的入口地址赋予指向函数的指针变量。例如： int (*pf)();pf=f; /*f为函数名*/</subject><description><![CDATA[指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构； 能很方便地使用数组和字符串； 并能象汇编语言一样处理内存地址，从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环， 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时， 指针也是C语言中最为困难的一部分，在学习中除了要正确理解基本概念，还必须要多编程，上机调试。只要作到这些，指针也是不难掌握的。指针的基本概念 在计算机中，所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元， 不同的数据类型所占用的内存单元数不等，如整型量占2个单元，字符量占1个单元等， 在第二章中已有详细的介绍。为了正确地访问这些内存单元， 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元，所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时， 银行工作人员将根据我们的帐号去找我们的存款单， 找到之后在存单上写入存款、取款的金额。在这里，帐号就是存单的指针， 存款数是存单的内容。对于一个内存单元来说，单元的地址即为指针， 其中存放的数据才是该单元的内容。在C语言中， 允许用一个变量来存放指针，这种变量称为指针变量。因此， 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中，设有字符变量C，其内容为“K”(ASCII码为十进制数 75)，C占用了011A号单元(地址用十六进数表示)。设有指针变量P，内容为011A， 这种情况我们称为P指向变量C，或说P是指向变量C的指针。 严格地说，一个指针是一个地址， 是一个常量。而一个指针变量却可以被赋予不同的指针值，是变。 但在常把指针变量简称为指针。为了避免混淆，我们中约定：“指针”是指地址， 是常量，“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。既然指针变量的值是一个地址， 那么这个地址不仅可以是变量的地址， 也可以是其它数据结构的地址。在一个指针变量中存放一
个数组或一个函数的首地址有何意义呢？ 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址， 也就找到了该数组或函数。这样一来， 凡是出现数组，函数的地方都可以用一个指针变量来表示， 只要该指针变量中赋予数组或函数的首地址即可。这样做， 将会使程序的概念十分清楚，程序本身也精练，高效。在C语言中， 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构， 而“指针”虽然实际上也是一个地址，但它却是一个数据结构的首地址， 它是“指向”一个数据结构的，因而概念更为清楚，表示更为明确。 这也是引入“指针”概念的一个重要原因。指针变量的类型说明对指针变量的类型说明包括三个内容：(1)指针类型说明，即定义变量为一个指针变量； (2)指针变量名；(3)变量值(指针)所指向的变量的数据类型。其一般形式为： 类型说明符 *变量名； 其中，*表示这是一个指针变量，变量名即为定义的指针变量名，类型说明符表示本指针变量所指向的变量的数据类型。例如： int *p1;表示p1是一个指针变量，它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量， 应由向p1赋予的地址来决定。再如：staic int *p2; /*p2是指向静态整型变量的指针变量*/
float *p3; /*p3是指向浮点变量的指针变量*/
char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是，一个指针变量只能指向同类型的变量，如P3 只能指向浮点变量，不能时而指向一个浮点变量， 时而又指向一个字符变量。 指针变量的赋值指针变量同普通变量一样，使用之前不仅要定义说明， 而且必须赋予具体的值。未经赋值的指针变量不能使用， 否则将造成系统混乱，甚至死机。指针变量的赋值只能赋予地址， 决不能赋予任何其它数据，否则将引起错误。在C语言中， 变量的地址是由编译系统分配的，对用户完全透明，用户不知道变量的具体地址。 C语言中提供了地址运算符&amp;来表示变量的地址。其一般形式为： &amp; 变量名； 如&amp;a变示变量a的地址，&amp;b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p，如要把整型变量a 的地址赋予p可以有以下两种方式：(1)指针变量初始化的方法 int a;
int *p=&amp;a; (2)赋值语句的方法int a;
int *p;
p=&amp;a; 不允许把一个数赋予指针变量，故下面的赋值是错误的： int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符，如写为*p=&amp;a 也是错误的指针变量的运算指针变量可以进行某些运算，但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。1.指针运算符(1)取地址运算符&amp;取地址运算符&amp;是单目运算符，其结合性为自右至左，其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中，我们已经了解并使用了&amp;运算符。(2)取内容运算符*取内容运算符*是单目运算符，其结合性为自右至左，用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中，“*”是类型说明符，表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。main(){int a=5,*p=&amp;a;printf (&quot;%d&quot;,*p);
}
...... 表示指针变量p取得了整型变量a的地址。本语句表示输出变量a的值。2.指针变量的运算(1)赋值运算指针变量的赋值运算有以下几种形式：①指针变量初始化赋值，前面已作介绍。②把一个变量的地址赋予指向相同数据类型的指针变量。例如：int a,*pa;
pa=&amp;a; /*把整型变量a的地址赋予整型指针变量pa*/ ③把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如：int a,*pa=&amp;a,*pb;
pb=pa; /*把a的地址赋予指针变量pb*/ 由于pa,pb均为指向整型变量的指针变量，因此可以相互赋值。④把数组的首地址赋予指向数组的指针变量。例如：int a[5],*pa;
pa=a; (数组名表示数组的首地址，故可赋予指向数组的指针变量pa) 也可写为：pa=&amp;a[0]; /*数组第一个元素的地址也是整个数组的首地址， 也可赋予pa*/当然也可采取初始化赋值的方法：int a[5],*pa=a; ⑤把字符串的首地址赋予指向字符类型的指针变量。例如： char *pc;pc=&quot;c language&quot;;或用初始化赋值的方法写为： char *pc=&quot;C Language&quot;; 这里应说明的是并不是把整个字符串装入指针变量， 而是把存放该字符串的字符数组的首地址装入指针变量。 在后面还将详细介绍。⑥把函数的入口地址赋予指向函数的指针变量。例如： int (*pf)();pf=f; /*f为函数名*/]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>经典c程序100例(71--80)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16840</link><subject>【程序71】题目：编写input()和output()函数输入，输出5个学生的数据记录。1.程序分析：2.程序源代码：#define N 5struct student{ char num[6];　char name[8];　int score[4];} stu[N];input(stu)struct student stu[];{ int i,j;　for(i=0;i&lt;N;i++)　{ printf(&quot;\n please input %d of %d\n&quot;,i+1,N);　　printf(&quot;num: &quot;);　　scanf(&quot;%s&quot;,stu[i].num);　　printf(&quot;name: &quot;);　　scanf(&quot;%s&quot;,stu[i].name);　　　for(j=0;j&lt;3;j++)　　　{ printf(&quot;score %d.&quot;,j+1);　　　　scanf(&quot;%d&quot;,&amp;stu[i].score[j]);　　　}　　printf(&quot;\n&quot;);　}}print(stu)struct student stu[];{ int i,j;printf(&quot;\nNo. Name Sco1 Sco2 Sco3\n&quot;);for(i=0;i&lt;N;i++){ printf(&quot;%-6s%-10s&quot;,stu[i].num,stu[i].name);　for(j=0;j&lt;3;j++)　　printf(&quot;%-8d&quot;,stu[i].score[j]);　printf(&quot;\n&quot;);}}main(){　input();　print();}==============================================================【程序72】题目：创建一个链表。1.程序分析：　　　　　　　　　　　2.程序源代码：/*creat a list*/#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;struct list *next;};typedef struct list node;typedef node *link;void main(){ link ptr,head;int num,i;ptr=(link)malloc(sizeof(node));ptr=head;printf(&quot;please input 5 numbers==&gt;\n&quot;);for(i=0;i&lt;=4;i++){　scanf(&quot;%d&quot;,&amp;num);　ptr-&gt;data=num;　ptr-&gt;next=(link)malloc(sizeof(node));　if(i==4) ptr-&gt;next=NULL;　else ptr=ptr-&gt;next;}ptr=head;while(ptr!=NULL){ printf(&quot;The value is ==&gt;%d\n&quot;,ptr-&gt;data);　ptr=ptr-&gt;next;}}==============================================================【程序73】题目：反向输出一个链表。　　　1.程序分析：2.程序源代码：/*reverse output a list*/#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;　struct list *next;};typedef struct list node;typedef node *link;void main(){ link ptr,head,tail;　　int num,i;　tail=(link)malloc(sizeof(node));　tail-&gt;next=NULL;　ptr=tail;　printf(&quot;\nplease input 5 data==&gt;\n&quot;);　for(i=0;i&lt;=4;i++)　{　　scanf(&quot;%d&quot;,&amp;num);　　ptr-&gt;data=num;　　head=(link)malloc(sizeof(node));　　head-&gt;next=ptr;　　ptr=head;　}ptr=ptr-&gt;next;while(ptr!=NULL){ printf(&quot;The value is ==&gt;%d\n&quot;,ptr-&gt;data);　ptr=ptr-&gt;next;}}==============================================================【程序74】题目：连接两个链表。1.程序分析：2.程序源代码：#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;struct list *next;};typedef struct list node;typedef node *link;link delete_node(link pointer,link tmp){if (tmp==NULL) /*delete first node*/　return pointer-&gt;next;else{ if(tmp-&gt;next-&gt;next==NULL)/*delete last node*/　　tmp-&gt;next=NULL;　else /*delete the other node*/　　tmp-&gt;next=tmp-&gt;next-&gt;next;　return pointer;}}void selection_sort(link pointer,int num){ link tmp,btmp;　int i,min;　for(i=0;i&lt;num;i++)　{　tmp=pointer;　min=tmp-&gt;data;　btmp=NULL;　while(tmp-&gt;next)　{ if(min&gt;tmp-&gt;next-&gt;data)　{min=tmp-&gt;next-&gt;data;　　btmp=tmp;　}　tmp=tmp-&gt;next;　}printf(&quot;\40: %d\n&quot;,min);pointer=delete_node(pointer,btmp);}}link create_list(int array[],int num){ link tmp1,tmp2,pointer;int i;pointer=(link)malloc(sizeof(node));pointer-&gt;data=array[0];tmp1=pointer;for(i=1;i&lt;num;i++){ tmp2=(link)malloc(sizeof(node));　tmp2-&gt;next=NULL;　tmp2-&gt;data=array[i];　tmp1-&gt;next=tmp2;　tmp1=tmp1-&gt;next;}return pointer;}link concatenate(link pointer1,link pointer2){ link tmp;tmp=pointer1;while(tmp-&gt;next)　tmp=tmp-&gt;next;tmp-&gt;next=pointer2;return pointer1;}void main(void){ int arr1[]={3,12,8,9,11};　link ptr;　ptr=create_list(arr1,5);　selection_sort(ptr,5);}==============================================================【程序75】题目：放松一下，算一道简单的题目。1.程序分析：2.程序源代码：main(){int i,n;for(i=1;i&lt;5;i++){ n=0;　if(i!=1)　n=n+1;　if(i==3)　n=n+1;　if(i==4)　n=n+1;　if(i!=4)　n=n+1;　if(n==3)　　printf(&quot;zhu hao shi de shi:%c&quot;,64+i);　}}==============================================================【程序76】题目：编写一个函数，输入n为偶数时，调用函数求1/2+1/4+...+1/n,当输入n为奇数时，调用函数　　　1/1+1/3+...+1/n(利用指针函数)1.程序分析：2.程序源代码：main()#include &quot;stdio.h&quot;main(){float peven(),podd(),dcall();float sum;int n;while (1){　scanf(&quot;%d&quot;,&amp;n);　if(n&gt;1)　　break;}if(n%2==0){　printf(&quot;Even=&quot;);　sum=dcall(peven,n);}else{　printf(&quot;Odd=&quot;);　sum=dcall(podd,n);}printf(&quot;%f&quot;,sum);}float peven(int n){float s;int i;s=1;for(i=2;i&lt;=n;i+=2)　s+=1/(float)i;return(s);}float podd(n)int n;{float s;int i;s=0;for(i=1;i&lt;=n;i+=2)　s+=1/(float)i;return(s);}float dcall(fp,n)float (*fp)();int n;{float s;s=(*fp)(n);return(s);}==============================================================【程序77】题目：填空练习（指向指针的指针）1.程序分析：　　　　　2.程序源代码：main(){ char *s[]={&quot;man&quot;,&quot;woman&quot;,&quot;girl&quot;,&quot;boy&quot;,&quot;sister&quot;};char **q;int k;for(k=0;k&lt;5;k++){　　　　　　　;/*这里填写什么语句*/　printf(&quot;%s\n&quot;,*q);}}==============================================================【程序78】题目：找到年龄最大的人，并输出。请找出程序中有什么问题。1.程序分析：2.程序源代码：#define N 4#include &quot;stdio.h&quot;static struct man{ char name[20];int age;} person[N]={&quot;li&quot;,18,&quot;wang&quot;,19,&quot;zhang&quot;,20,&quot;sun&quot;,22};main(){struct man *q,*p;int i,m=0;p=person;for (i=0;i&lt;N;i++){if(m&lt;p-&gt;age)　q=p++;　m=q-&gt;age;}printf(&quot;%s,%d&quot;,(*q).name,(*q).age);}==============================================================【程序79】题目：字符串排序。1.程序分析：2.程序源代码：main(){char *str1[20],*str2[20],*str3[20];char swap();printf(&quot;please input three strings\n&quot;);scanf(&quot;%s&quot;,str1);scanf(&quot;%s&quot;,str2);scanf(&quot;%s&quot;,str3);if(strcmp(str1,str2)&gt;0) swap(str1,str2);if(strcmp(str1,str3)&gt;0) swap(str1,str3);if(strcmp(str2,str3)&gt;0) swap(str2,str3);printf(&quot;after being sorted\n&quot;);printf(&quot;%s\n%s\n%s\n&quot;,str1,str2,str3);}char swap(p1,p2)char *p1,*p2;{char *p[20];strcpy(p,p1);strcpy(p1,p2);strcpy(p2,p);}==============================================================【程序80】题目：海滩上有一堆桃子，五只猴子来分。第一只猴子把这堆桃子凭据分为五份，多了一个，这只　　　猴子把多的一个扔入海中，拿走了一份。第二只猴子把剩下的桃子又平均分成五份，又多了　　　一个，它同样把多的一个扔入海中，拿走了一份，第三、第四、第五只猴子都是这样做的，　　　问海滩上原来最少有多少个桃子？1.程序分析：2.程序源代码：main(){int i,m,j,k,count;for(i=4;i&lt;10000;i+=4){ count=0;m=i;for(k=0;k&lt;5;k++){　j=i/4*5+1;　i=j;　if(j%4==0)　　count++;　else　　break;}　i=m;　if(count==4)　{printf(&quot;%d\n&quot;,count);　　break;}}}</subject><description><![CDATA[【程序71】题目：编写input()和output()函数输入，输出5个学生的数据记录。1.程序分析：2.程序源代码：#define N 5struct student{ char num[6];　char name[8];　int score[4];} stu[N];input(stu)struct student stu[];{ int i,j;　for(i=0;i&lt;N;i++)　{ printf(&quot;\n please input %d of %d\n&quot;,i+1,N);　　printf(&quot;num: &quot;);　　scanf(&quot;%s&quot;,stu[i].num);　　printf(&quot;name: &quot;);　　scanf(&quot;%s&quot;,stu[i].name);　　　for(j=0;j&lt;3;j++)　　　{ printf(&quot;score %d.&quot;,j+1);　　　　scanf(&quot;%d&quot;,&amp;stu[i].score[j]);　　　}　　printf(&quot;\n&quot;);　}}print(stu)struct student stu[];{ int i,j;printf(&quot;\nNo. Name Sco1 Sco2 Sco3\n&quot;);for(i=0;i&lt;N;i++){ printf(&quot;%-6s%-10s&quot;,stu[i].num,stu[i].name);　for(j=0;j&lt;3;j++)　　printf(&quot;%-8d&quot;,stu[i].score[j]);　printf(&quot;\n&quot;);}}main(){　input();　print();}==============================================================【程序72】题目：创建一个链表。1.程序分析：　　　　　　　　　　　2.程序源代码：/*creat a list*/#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;struct list *next;};typedef struct list node;typedef node *link;void main(){ link ptr,head;int num,i;ptr=(link)malloc(sizeof(node));ptr=head;printf(&quot;please input 5 numbers==&gt;\n&quot;);for(i=0;i&lt;=4;i++){　scanf(&quot;%d&quot;,&amp;num);　ptr-&gt;data=num;　ptr-&gt;next=(link)malloc(sizeof(node));　if(i==4) ptr-&gt;next=NULL;　else ptr=ptr-&gt;next;}ptr=head;while(ptr!=NULL){ printf(&quot;The value is ==&gt;%d\n&quot;,ptr-&gt;data);　ptr=ptr-&gt;next;}}==============================================================【程序73】题目：反向输出一个链表。　　　1.程序分析：2.程序源代码：/*reverse output a list*/#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;　struct list *next;};typedef struct list node;typedef node *link;void main(){ link ptr,head,tail;　　int num,i;　tail=(link)malloc(sizeof(node));　tail-&gt;next=NULL;　ptr=tail;　printf(&quot;\nplease input 5 data==&gt;\n&quot;);　for(i=0;i&lt;=4;i++)　{　　scanf(&quot;%d&quot;,&amp;num);　　ptr-&gt;data=num;　　head=(link)malloc(sizeof(node));　　head-&gt;next=ptr;　　ptr=head;　}ptr=ptr-&gt;next;while(ptr!=NULL){ printf(&quot;The value is ==&gt;%d\n&quot;,ptr-&gt;data);　ptr=ptr-&gt;next;}}==============================================================【程序74】题目：连接两个链表。1.程序分析：2.程序源代码：#include &quot;stdlib.h&quot;#include &quot;stdio.h&quot;struct list{ int data;struct list *next;};typedef struct list node;typedef node *link;link delete_node(link pointer,link tmp){if (tmp==NULL) /*delete first node*/　return pointer-&gt;next;else{ if(tmp-&gt;next-&gt;next==NULL)/*delete last node*/　　tmp-&gt;next=NULL;　else /*delete the other node*/　　tmp-&gt;next=tmp-&gt;next-&gt;next;　return pointer;}}void selection_sort(link pointer,int num){ link tmp,btmp;　int i,min;　for(i=0;i&lt;num;i++)　{　tmp=pointer;　min=tmp-&gt;data;　btmp=NULL;　while(tmp-&gt;next)　{ if(min&gt;tmp-&gt;next-&gt;data)　{min=tmp-&gt;next-&gt;data;　　btmp=tmp;　}　tmp=tmp-&gt;next;　}printf(&quot;\40: %d\n&quot;,min);pointer=delete_node(pointer,btmp);}}link create_list(int array[],int num){ link tmp1,tmp2,pointer;int i;pointer=(link)malloc(sizeof(node));pointer-&gt;data=array[0];tmp1=pointer;for(i=1;i&lt;num;i++){ tmp2=(link)malloc(sizeof(node));　tmp2-&gt;next=NULL;　tmp2-&gt;data=array[i];　tmp1-&gt;next=tmp2;　tmp1=tmp1-&gt;next;}return pointer;}link concatenate(link pointer1,link pointer2){ link tmp;tmp=pointer1;while(tmp-&gt;next)　tmp=tmp-&gt;next;tmp-&gt;next=pointer2;return pointer1;}void main(void){ int arr1[]={3,12,8,9,11};　link ptr;　ptr=create_list(arr1,5);　selection_sort(ptr,5);}==============================================================【程序75】题目：放松一下，算一道简单的题目。1.程序分析：2.程序源代码：main(){int i,n;for(i=1;i&lt;5;i++){ n=0;　if(i!=1)　n=n+1;　if(i==3)　n=n+1;　if(i==4)　n=n+1;　if(i!=4)　n=n+1;　if(n==3)　　printf(&quot;zhu hao shi de shi:%c&quot;,64+i);　}}==============================================================【程序76】题目：编写一个函数，输入n为偶数时，调用函数求1/2+1/4+...+1/n,当输入n为奇数时，调用函数　　　1/1+1/3+...+1/n(利用指针函数)1.程序分析：2.程序源代码：main()#include &quot;stdio.h&quot;main(){float peven(),podd(),dcall();float sum;int n;while (1){　scanf(&quot;%d&quot;,&amp;n);　if(n&gt;1)　　break;}if(n%2==0){　printf(&quot;Even=&quot;);　sum=dcall(peven,n);}else{　printf(&quot;Odd=&quot;);　sum=dcall(podd,n);}printf(&quot;%f&quot;,sum);}float peven(int n){float s;int i;s=1;for(i=2;i&lt;=n;i+=2)　s+=1/(float)i;return(s);}float podd(n)int n;{float s;int i;s=0;for(i=1;i&lt;=n;i+=2)　s+=1/(float)i;return(s);}float dcall(fp,n)float (*fp)();int n;{float s;s=(*fp)(n);return(s);}==============================================================【程序77】题目：填空练习（指向指针的指针）1.程序分析：　　　　　2.程序源代码：main(){ char *s[]={&quot;man&quot;,&quot;woman&quot;,&quot;girl&quot;,&quot;boy&quot;,&quot;sister&quot;};char **q;int k;for(k=0;k&lt;5;k++){　　　　　　　;/*这里填写什么语句*/　printf(&quot;%s\n&quot;,*q);}}==============================================================【程序78】题目：找到年龄最大的人，并输出。请找出程序中有什么问题。1.程序分析：2.程序源代码：#define N 4#include &quot;stdio.h&quot;static struct man{ char name[20];int age;} person[N]={&quot;li&quot;,18,&quot;wang&quot;,19,&quot;zhang&quot;,20,&quot;sun&quot;,22};main(){struct man *q,*p;int i,m=0;p=person;for (i=0;i&lt;N;i++){if(m&lt;p-&gt;age)　q=p++;　m=q-&gt;age;}printf(&quot;%s,%d&quot;,(*q).name,(*q).age);}==============================================================【程序79】题目：字符串排序。1.程序分析：2.程序源代码：main(){char *str1[20],*str2[20],*str3[20];char swap();printf(&quot;please input three strings\n&quot;);scanf(&quot;%s&quot;,str1);scanf(&quot;%s&quot;,str2);scanf(&quot;%s&quot;,str3);if(strcmp(str1,str2)&gt;0) swap(str1,str2);if(strcmp(str1,str3)&gt;0) swap(str1,str3);if(strcmp(str2,str3)&gt;0) swap(str2,str3);printf(&quot;after being sorted\n&quot;);printf(&quot;%s\n%s\n%s\n&quot;,str1,str2,str3);}char swap(p1,p2)char *p1,*p2;{char *p[20];strcpy(p,p1);strcpy(p1,p2);strcpy(p2,p);}==============================================================【程序80】题目：海滩上有一堆桃子，五只猴子来分。第一只猴子把这堆桃子凭据分为五份，多了一个，这只　　　猴子把多的一个扔入海中，拿走了一份。第二只猴子把剩下的桃子又平均分成五份，又多了　　　一个，它同样把多的一个扔入海中，拿走了一份，第三、第四、第五只猴子都是这样做的，　　　问海滩上原来最少有多少个桃子？1.程序分析：2.程序源代码：main(){int i,m,j,k,count;for(i=4;i&lt;10000;i+=4){ count=0;m=i;for(k=0;k&lt;5;k++){　j=i/4*5+1;　i=j;　if(j%4==0)　　count++;　else　　break;}　i=m;　if(count==4)　{printf(&quot;%d\n&quot;,count);　　break;}}}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>自定义类_string类</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16848</link><subject>////////////////////////////////////////////////////////////
// string类源程序
// 时间: 2005-11-2
// 程序员: 黄江斌( blog主页 http://blog.csdn.net/zerodspace
// 开发平台: VC++6.0&lt;&lt;单文件程序&gt;&gt;
////////////////////////////////////////////////////////////#include &quot;iostream.h&quot;////////////////////////////////////////////////////////////
// string类声明部分
// 时间: 2005-11-2
// 程序员: 黄江斌
////////////////////////////////////////////////////////////
#include &quot;string.h&quot;
#define TRIM_LEFT 1
#define TRIM_RIGHT 2
#define TRIM_BOTH 0class string
{
public:string();string( const char *src );string( string &amp;src );~string();
//操作符重载void operator = ( const char *src );//char * operator + ( string &amp;add2 );string operator + ( string &amp;add2 );operator char *();
//成员函数const char *ToString();char GetAt( int index );int Len();int FindFirstSub( string substr , int start );bool InStr( string substr );string Trim( int part );string Left( int sublen );string Right( int sublen );string Mid( int start , int sublen );void Replace( string strNeedReplaced , string strReplace );private:char *str;int len;
};
////////////////////////////////////////////////////////////
// string类实现部分
// 时间: 2005-11-2
// 程序员: 黄江斌
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// string在构造、析构函数
// string()
// string( const char *src )
// string( string &amp;src )
// ~string()string::string()
{len = 0;str = new char(len+1);str[0] = 0;
}
string::string( const char *src )
{len = strlen(src);str = new char(len+1);strcpy( str , src );
}
string::string( string &amp;src )
{len = src.Len();str = new char(len+1);strcpy( str , (char*)src );
}
string::~string()
{
}////////////////////////////////////////////////////////////
// string类操作符重载
// string operator = ( const char *src )
// string operator + ( string &amp;add2 )
// operator char *()void string::operator = ( const char *src )
{len = strlen(src);str = new char(len+1);strcpy( str , src );
}
string string::operator + ( string &amp;add2 )
{int len2 = add2.Len();int len0 = len + len2;char *str0 = new char(len0+1);strcpy( str0 , str );strcat( str0 , (char*)add2 );string sum(str0);return sum;
}
string::operator char *()
{return str;
}////////////////////////////////////////////////////////////
// string类功能函数
// const char *ToString()
// char GetAt( int index )
// int Len()
// int FindFirstSub( string substr , int start = 0 )
// bool InStr( string substr )
// string Trim( int part )
// string Left()
// string Right()
// string Mid()
// void Replace( string strNeedReplaced , string strReplace )const char *string::ToString()
{return str;
}
//
//得到内存编号为index的字符
char string::GetAt( int index )
{return str[index];
}
//
//得到字符串的长度
int string::Len()
{return len;
}
//
//得到start后（包括start）子串substr的内存编号
int string::FindFirstSub( string substr , int start = 0 )
{if( start &gt; len )
  return -1;for( int i = start ; i &lt;= len - substr.Len() ; i++ ){
  if( str[i] == substr.GetAt(0) )
  {
   bool flag = true;
   int j;
   for( j = 0 ; flag &amp;&amp; j &lt; substr.Len() &amp;&amp; (i + j &lt; len) ; j++ )
   {
    if( str[i+j] != substr.GetAt(j) )
     flag = false;
   }
   if( j &gt;= substr.Len() &amp;&amp; flag )
   {
    return i;
   }
  }}return -1;
}
//
//判断字符串substr是否字符串的子串
bool string::InStr( string substr )
{if( FindFirstSub( substr ) &lt; 0 )
  return false;else
  return true;
}
//
//去前导、后导空格
string string::Trim( int part = TRIM_BOTH )
{string s = *this;char *temp = new char(Len()+1);int i , j , k;if( part == TRIM_LEFT || part == TRIM_BOTH ){
  j = 0;
  while( s.GetAt(j) == '' '' &amp;&amp; j &lt; s.Len() )
   j++;
  k = s.Len();
  i = 0;
  while( j &lt;= k )
   temp[i++] = s.GetAt(j++);
  s = (string)temp;}if( part == TRIM_RIGHT || part == TRIM_BOTH ){
  j = s.Len() - 1;
  while( s.GetAt(j) == '' '' )
   j--;
  for( i = 0 ; i &lt;= j ; i++ )
   temp[i] = s.GetAt(i);
  temp[i] = 0;
  s = (string)temp;}return s;
}
//
//字符串左部长度为sublen的子串
string string::Left( int sublen )
{if( sublen &gt; len )
  sublen = len;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//字符串右部长度为sublen的子串
string string::Right( int sublen )
{if( sublen &gt; len )
  sublen = len;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[len-sublen+i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//字符串中间从start开始长度为sublen的子串
string string::Mid( int start , int sublen )
{if( start &gt; len || sublen &lt;= 0 ){
  string sub(&quot;&quot;);
  return sub;}if( start + sublen &gt; len )
  sublen = len - start;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[start+i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//将字符串中所有形如strNeedReplaced的子串替换为strReplace
void string::Replace( string strNeedReplaced , string strReplace )
{string temp;int start = 0;int index = FindFirstSub( strNeedReplaced , start );while( index &gt;= 0 ){
  temp = temp + Mid( start , index - start ) + strReplace;  start = index + strNeedReplaced.Len();
  index = FindFirstSub( strNeedReplaced , start );}temp = temp + Right( len - start );len = temp.Len();str = new char(len+1);strcpy( str , (char*)temp );
}
// string类结束
////////////////////////////////////////////////////////////void main()
{string str = &quot;    a        b            c        &quot;;cout&lt;&lt;str&lt;&lt;&quot;|&quot;&lt;&lt;endl;while( str.InStr( &quot;  &quot; ) )
  str.Replace( &quot;  &quot; , &quot; &quot; );cout&lt;&lt;str.Trim()&lt;&lt;&quot;|&quot;&lt;&lt;endl;
}</subject><description><![CDATA[////////////////////////////////////////////////////////////
// string类源程序
// 时间: 2005-11-2
// 程序员: 黄江斌( blog主页 http://blog.csdn.net/zerodspace
// 开发平台: VC++6.0&lt;&lt;单文件程序&gt;&gt;
////////////////////////////////////////////////////////////#include &quot;iostream.h&quot;////////////////////////////////////////////////////////////
// string类声明部分
// 时间: 2005-11-2
// 程序员: 黄江斌
////////////////////////////////////////////////////////////
#include &quot;string.h&quot;
#define TRIM_LEFT 1
#define TRIM_RIGHT 2
#define TRIM_BOTH 0class string
{
public:string();string( const char *src );string( string &amp;src );~string();
//操作符重载void operator = ( const char *src );//char * operator + ( string &amp;add2 );string operator + ( string &amp;add2 );operator char *();
//成员函数const char *ToString();char GetAt( int index );int Len();int FindFirstSub( string substr , int start );bool InStr( string substr );string Trim( int part );string Left( int sublen );string Right( int sublen );string Mid( int start , int sublen );void Replace( string strNeedReplaced , string strReplace );private:char *str;int len;
};
////////////////////////////////////////////////////////////
// string类实现部分
// 时间: 2005-11-2
// 程序员: 黄江斌
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// string在构造、析构函数
// string()
// string( const char *src )
// string( string &amp;src )
// ~string()string::string()
{len = 0;str = new char(len+1);str[0] = 0;
}
string::string( const char *src )
{len = strlen(src);str = new char(len+1);strcpy( str , src );
}
string::string( string &amp;src )
{len = src.Len();str = new char(len+1);strcpy( str , (char*)src );
}
string::~string()
{
}////////////////////////////////////////////////////////////
// string类操作符重载
// string operator = ( const char *src )
// string operator + ( string &amp;add2 )
// operator char *()void string::operator = ( const char *src )
{len = strlen(src);str = new char(len+1);strcpy( str , src );
}
string string::operator + ( string &amp;add2 )
{int len2 = add2.Len();int len0 = len + len2;char *str0 = new char(len0+1);strcpy( str0 , str );strcat( str0 , (char*)add2 );string sum(str0);return sum;
}
string::operator char *()
{return str;
}////////////////////////////////////////////////////////////
// string类功能函数
// const char *ToString()
// char GetAt( int index )
// int Len()
// int FindFirstSub( string substr , int start = 0 )
// bool InStr( string substr )
// string Trim( int part )
// string Left()
// string Right()
// string Mid()
// void Replace( string strNeedReplaced , string strReplace )const char *string::ToString()
{return str;
}
//
//得到内存编号为index的字符
char string::GetAt( int index )
{return str[index];
}
//
//得到字符串的长度
int string::Len()
{return len;
}
//
//得到start后（包括start）子串substr的内存编号
int string::FindFirstSub( string substr , int start = 0 )
{if( start &gt; len )
  return -1;for( int i = start ; i &lt;= len - substr.Len() ; i++ ){
  if( str[i] == substr.GetAt(0) )
  {
   bool flag = true;
   int j;
   for( j = 0 ; flag &amp;&amp; j &lt; substr.Len() &amp;&amp; (i + j &lt; len) ; j++ )
   {
    if( str[i+j] != substr.GetAt(j) )
     flag = false;
   }
   if( j &gt;= substr.Len() &amp;&amp; flag )
   {
    return i;
   }
  }}return -1;
}
//
//判断字符串substr是否字符串的子串
bool string::InStr( string substr )
{if( FindFirstSub( substr ) &lt; 0 )
  return false;else
  return true;
}
//
//去前导、后导空格
string string::Trim( int part = TRIM_BOTH )
{string s = *this;char *temp = new char(Len()+1);int i , j , k;if( part == TRIM_LEFT || part == TRIM_BOTH ){
  j = 0;
  while( s.GetAt(j) == '' '' &amp;&amp; j &lt; s.Len() )
   j++;
  k = s.Len();
  i = 0;
  while( j &lt;= k )
   temp[i++] = s.GetAt(j++);
  s = (string)temp;}if( part == TRIM_RIGHT || part == TRIM_BOTH ){
  j = s.Len() - 1;
  while( s.GetAt(j) == '' '' )
   j--;
  for( i = 0 ; i &lt;= j ; i++ )
   temp[i] = s.GetAt(i);
  temp[i] = 0;
  s = (string)temp;}return s;
}
//
//字符串左部长度为sublen的子串
string string::Left( int sublen )
{if( sublen &gt; len )
  sublen = len;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//字符串右部长度为sublen的子串
string string::Right( int sublen )
{if( sublen &gt; len )
  sublen = len;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[len-sublen+i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//字符串中间从start开始长度为sublen的子串
string string::Mid( int start , int sublen )
{if( start &gt; len || sublen &lt;= 0 ){
  string sub(&quot;&quot;);
  return sub;}if( start + sublen &gt; len )
  sublen = len - start;char *substr = new char(sublen+1);for( int i = 0 ; i &lt; sublen ; i++ )
  substr[i] = str[start+i];substr[sublen] = 0;string sub(substr);return sub;
}
//
//将字符串中所有形如strNeedReplaced的子串替换为strReplace
void string::Replace( string strNeedReplaced , string strReplace )
{string temp;int start = 0;int index = FindFirstSub( strNeedReplaced , start );while( index &gt;= 0 ){
  temp = temp + Mid( start , index - start ) + strReplace;  start = index + strNeedReplaced.Len();
  index = FindFirstSub( strNeedReplaced , start );}temp = temp + Right( len - start );len = temp.Len();str = new char(len+1);strcpy( str , (char*)temp );
}
// string类结束
////////////////////////////////////////////////////////////void main()
{string str = &quot;    a        b            c        &quot;;cout&lt;&lt;str&lt;&lt;&quot;|&quot;&lt;&lt;endl;while( str.InStr( &quot;  &quot; ) )
  str.Replace( &quot;  &quot; , &quot; &quot; );cout&lt;&lt;str.Trim()&lt;&lt;&quot;|&quot;&lt;&lt;endl;
}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>c语言中时间陷阱的实现</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16851</link><subject>时间陷阱.它能是主程序的运行和对事件的处理异步进行.通过设置时间陷阱.可以在规定的时间间隔到达后自动执行某个子程序.子程序运行结束后又回到主程序的断点处继续执行.basic语言提供了有关时间陷阱的语句.可以直接调用.但C语言没有这样的功能,为了能实现上述功能,这里给出了几个子函数以帮助实现.#include&lt;dos.h&gt;#define Sizeprogram 375
void interrupt (*oldtimer)();
void interrupt newtimer();
static struct SREGS seg;
unsigned intsp,intss;
unsigned myss,stack;
unsigned vseg;
int running=0;
int m1,m2,m3=0;
int (*sub_ptr)();
void on_timer();
void timer_on();
void timer_off();void on_timer(t,pp);
int t;
int (*pp)();
{
sub_ptr=pp;
m1=t;
segread(&amp;seg);
stack=(Sizeprogram-(seg.ds-seg.cs))*16-300;
myss=_SS;
oldtimer=getvect(0x1c);
}void timer_on()
{
m2=0;
m3=1;
setvect(0x1c,newtimer);
}
void timer_off()
{
m3=0;
setvect(0x1c,oldtimer);
}
void interrupt newtimer()
{
(*oldtimer)();
if(running==0)
running=1;
disable();
intsp=_SP;
intss=_SS;
_SP=stack;
_SS=myss;
enable();
if(m3==1){
if(m2==m1*18){
m2=0;
(*sb_ptr)();
}
m2+=1;
}
disable();
_SP=intsp;
_SS=intss;
enable();
running=0;
}
}#include&lt;dos.h&gt;
#define TRUE 1
void ptr();
main()
{
char ch;
char *p:
p=(char *)ptr;
on_timer(t,p);
while(TRUE){
ch=getch();
switch(ch){
case ''0'': timer_off();break;
case ''1'': timer_on();break;
case ''2'': timer_off();brea;
default: printf(&quot;%c&quot;,ch);
}
}
}void ptr()
{
union REGS in,out;
in.h.al=''A'';
in.h.ah=14;
int86(0x10,&amp;in,&amp;out);
}</subject><description><![CDATA[时间陷阱.它能是主程序的运行和对事件的处理异步进行.通过设置时间陷阱.可以在规定的时间间隔到达后自动执行某个子程序.子程序运行结束后又回到主程序的断点处继续执行.basic语言提供了有关时间陷阱的语句.可以直接调用.但C语言没有这样的功能,为了能实现上述功能,这里给出了几个子函数以帮助实现.#include&lt;dos.h&gt;#define Sizeprogram 375
void interrupt (*oldtimer)();
void interrupt newtimer();
static struct SREGS seg;
unsigned intsp,intss;
unsigned myss,stack;
unsigned vseg;
int running=0;
int m1,m2,m3=0;
int (*sub_ptr)();
void on_timer();
void timer_on();
void timer_off();void on_timer(t,pp);
int t;
int (*pp)();
{
sub_ptr=pp;
m1=t;
segread(&amp;seg);
stack=(Sizeprogram-(seg.ds-seg.cs))*16-300;
myss=_SS;
oldtimer=getvect(0x1c);
}void timer_on()
{
m2=0;
m3=1;
setvect(0x1c,newtimer);
}
void timer_off()
{
m3=0;
setvect(0x1c,oldtimer);
}
void interrupt newtimer()
{
(*oldtimer)();
if(running==0)
running=1;
disable();
intsp=_SP;
intss=_SS;
_SP=stack;
_SS=myss;
enable();
if(m3==1){
if(m2==m1*18){
m2=0;
(*sb_ptr)();
}
m2+=1;
}
disable();
_SP=intsp;
_SS=intss;
enable();
running=0;
}
}#include&lt;dos.h&gt;
#define TRUE 1
void ptr();
main()
{
char ch;
char *p:
p=(char *)ptr;
on_timer(t,p);
while(TRUE){
ch=getch();
switch(ch){
case ''0'': timer_off();break;
case ''1'': timer_on();break;
case ''2'': timer_off();brea;
default: printf(&quot;%c&quot;,ch);
}
}
}void ptr()
{
union REGS in,out;
in.h.al=''A'';
in.h.ah=14;
int86(0x10,&amp;in,&amp;out);
}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C标准中一些预定义的宏</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16860</link><subject>C标准中指定了一些预定义的宏，对于编程经常会用到。下面这个表中就是一些常常用到的预定义宏。__DATE__进行预处理的日期（“Mmm dd yyyy”形式的字符串文字）__FILE__代表当前源代码文件名的字符串文字__LINE__代表当前源代码中的行号的整数常量__TIME__源文件编译时间，格式微“hh：mm：ss”__func__当前所在函数名        对于__FILE__，__LINE__，__func__这样的宏，在调试程序时是很有用的，因为你可以很容易的知道程序运行到了哪个文件的那一行，是哪个函数。       下面一个例子是打印上面这些预定义的宏的。 #include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
void why_me();
int main()
{
    printf( &quot;The file is %s.\n&quot;, __FILE__ );
    printf( &quot;The date is %s.\n&quot;, __DATE__ );
    printf( &quot;The time is %s.\n&quot;, __TIME__ );
    printf( &quot;This is line %d.\n&quot;, __LINE__ );
    printf( &quot;This function is %s.\n&quot;, __func__ );
    why_me();
    return 0;
}void why_me()
{
    printf( &quot;This function is %s\n&quot;, __func__ );
    printf( &quot;The file is %s.\n&quot;, __FILE__ );
    printf( &quot;This is line %d.\n&quot;, __LINE__ );
}</subject><description><![CDATA[C标准中指定了一些预定义的宏，对于编程经常会用到。下面这个表中就是一些常常用到的预定义宏。__DATE__进行预处理的日期（“Mmm dd yyyy”形式的字符串文字）__FILE__代表当前源代码文件名的字符串文字__LINE__代表当前源代码中的行号的整数常量__TIME__源文件编译时间，格式微“hh：mm：ss”__func__当前所在函数名        对于__FILE__，__LINE__，__func__这样的宏，在调试程序时是很有用的，因为你可以很容易的知道程序运行到了哪个文件的那一行，是哪个函数。       下面一个例子是打印上面这些预定义的宏的。 #include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
void why_me();
int main()
{
    printf( &quot;The file is %s.\n&quot;, __FILE__ );
    printf( &quot;The date is %s.\n&quot;, __DATE__ );
    printf( &quot;The time is %s.\n&quot;, __TIME__ );
    printf( &quot;This is line %d.\n&quot;, __LINE__ );
    printf( &quot;This function is %s.\n&quot;, __func__ );
    why_me();
    return 0;
}void why_me()
{
    printf( &quot;This function is %s\n&quot;, __func__ );
    printf( &quot;The file is %s.\n&quot;, __FILE__ );
    printf( &quot;This is line %d.\n&quot;, __LINE__ );
}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C++箴言：防止异常离开析构函数</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16869</link><subject>C++ 并不禁止从析构函数中引发异常，但是这确实妨碍了实践。至于有什么好的理由，考虑： class Widget { public: ... ~Widget() { ... } // assume this might emit an exception }; void doSomething() { std::vector v; ... } // v is automatically destroyed here 当 vector v 被析构时，它有责任销毁它包含的所有 Widgets。假设 v 中有十个 Widgets，在销毁第一个的时候，抛出一个异常。其他 9个 Widgets 仍然必须被销毁（否则他们持有的任何资源将被泄漏），所以 v 应该调用它们的析构函数。但是假设在这个调用期间，第二个 Widgets 的析构函数又抛出一个异常。现在有两个异常同时在活动中，对于 C++ 来说这太多了。在非常巧合的条件下发生这样两个同时活动的异常，程序的执行会终止或者引发未定义行为。在本例中，将引发未定义行为。与此相同，使用任何标准库容器（比如，list，set），任何 TR1中的容器，甚至是一个数组，都可能会引发未定义问题。并非必须是容器或数组才会陷入麻烦。程序夭折或未定义行为是析构函数引发异常的结果，即使没有使用容器或数组也会如此。C++ 不喜欢引发异常的析构函数。 这比较容易理解，但是如果你的析构函数需要执行一个可能失败而抛出异常的操作，该怎么办呢？例如，假设你与一个数据库连接类一起工作： class DBConnection { public: ... static DBConnection create(); // function to return // DBConnection objects; params // omitted for simplicity void close(); // close connection; throw an }; // exception if closing fails</subject><description><![CDATA[C++ 并不禁止从析构函数中引发异常，但是这确实妨碍了实践。至于有什么好的理由，考虑： class Widget { public: ... ~Widget() { ... } // assume this might emit an exception }; void doSomething() { std::vector v; ... } // v is automatically destroyed here 当 vector v 被析构时，它有责任销毁它包含的所有 Widgets。假设 v 中有十个 Widgets，在销毁第一个的时候，抛出一个异常。其他 9个 Widgets 仍然必须被销毁（否则他们持有的任何资源将被泄漏），所以 v 应该调用它们的析构函数。但是假设在这个调用期间，第二个 Widgets 的析构函数又抛出一个异常。现在有两个异常同时在活动中，对于 C++ 来说这太多了。在非常巧合的条件下发生这样两个同时活动的异常，程序的执行会终止或者引发未定义行为。在本例中，将引发未定义行为。与此相同，使用任何标准库容器（比如，list，set），任何 TR1中的容器，甚至是一个数组，都可能会引发未定义问题。并非必须是容器或数组才会陷入麻烦。程序夭折或未定义行为是析构函数引发异常的结果，即使没有使用容器或数组也会如此。C++ 不喜欢引发异常的析构函数。 这比较容易理解，但是如果你的析构函数需要执行一个可能失败而抛出异常的操作，该怎么办呢？例如，假设你与一个数据库连接类一起工作： class DBConnection { public: ... static DBConnection create(); // function to return // DBConnection objects; params // omitted for simplicity void close(); // close connection; throw an }; // exception if closing fails]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C++程序设计从零开始之表达式</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16880</link><subject>本篇是此系列的开头，在学英语时，第一时间学的是字母，其是英语的基础。同样，在C++中，所有的代码都是通过标识符（Identifier）、表达式（Expression）和语句（Statement）及一些必要的符号（如大括号等）组成，在此先说明何谓标识符。标识符标识符是一个字母序列，由大小写英文字母、下划线及数字组成，用于标识。标识就是标出并识别，也就是名字。其可以作为后面将提到的变量或者函数或者类等的名字，也就是说用来标识某个特定的变量或者函数或者类等C++中的元素。比如：abc就是一个合法的标识符，即abc可以作为变量、函数等元素的名字，但并不代表abc就是某个变量或函数的名字，而所谓的合法就是任何一个标识符都必须不能以数字开头，只能包括大小写英文字母、下划线及数字，不能有其它符号，如,!^等，并且不能与C++关键字相同。也就是我们在给一个变量或函数起名字的时候，必须将起的名字看作是一个标识符，并进而必须满足上面提出的要求。如12ab_C就不是一个合法的标识符，因此我们不能给某个变量或函数起12ab_C这样的名字；ab_12C就是合法的标识符，因此可以被用作变量或函数的名字。前面提到关键字，在后续的语句及一些声明修饰符的介绍中将发现，C++提供了一些特殊的标识符作为语句的名字，用以标识某一特定语句，如if、while等；或者提供一些修饰符用以修饰变量、函数等元素以实现语义或给编译器及连接器提供一些特定信息以进行优化、查错等操作，如extern、static等。因此在命名变量或函数或其他元素时，不能使用if、extern等这种C++关键字作为名字，否则将导致编译器无法确认是一个变量（或函数或其它C++元素）还是一条语句，进而无法编译。如果要让某个标识符是特定变量或函数或类的名字，就需要使用声明，在后续的文章中再具体说明。数字C++作为电脑编程语言，电脑是处理数字的，因此C++中的基础东西就是数字。C++中提供两种数字：整型数和浮点数，也就是整数和小数。但由于电脑实际并不是想象中的数字化的（详情参见《C++从零开始（三）》中的类型一节），所以整型数又分成了有符号和无符号整型数，而浮点数则由精度的区别而分成单精度和双精度浮点数，同样的整型数也根据长度分成长整型和短整型。要在C++代码中表示一个数字，直接书写数字即可，如：123、34.23、-34.34等。由于电脑并非以数字为基础而导致了前面数字的分类，为了在代码中表现出来，C++提供了一系列的后缀进行表示，如下：u或U 表示数字是无符号整型数，如：123u，但并不说明是长整型还是短整型l或L 表示数字是长整型数，如：123l；而123ul就是无符号长整型数；而34.4l就是长双精度浮点数，等效于双精度浮点数i64或I64 表示数字是长长整型数，其是为64位操作系统定义的，长度比长整型数长。如：43i64f或F 表示数字是单精度浮点数，如：12.3fe或E 表示数字的次幂，如：34.4e-2就是0.344；0.2544e3f表示一个单精度浮点数，值为254.4当什么后缀都没写时，则根据有无小数点及位数来决定其具体类型，如：123表示的是有符号整型数，而12341434则是有符号长整型数；而34.43表示双精度浮点数。为什么要搞这么多事出来，还分什么有符号无符号之类的？这全是因为电脑并非基于数字的，而是基于状态的，详情在下篇中将详细说明。作为科学计算，可能经常会碰到使用非十进制数字，如16进制、8进制等，C++也为此提供了一些前缀以进行支持。在数字前面加上0x或0X表示这个数字是16进制表示的，如：0xF3Fa、0x11cF。而在前面加一个0则表示这个数字是用8进制表示的，如： 0347，变为十进制数就为231。但16进制和8进制都不能用于表示浮点数，只能表示整型数，即0x34.343是错误的。字符串C++除了提供数字这种最基础的表示方式外，还提供了字符及字符串。这完全只是出于方便编写程序而提供的，C++作为电脑语言，根本没有提供字符串的必要性。不过由于人对电脑的基本要求就是显示结果，而字符和字符串都由于是人易读的符号而被用于显示结果，所以C++专门提供了对字符串的支持。前面说过，电脑只认识数字，而字符就是文字符号，是一种图形符号。为了使电脑能够处理符号，必须通过某种方式将符号变成数字，在电脑中这通过在符号和数字之间建立一个映射来实现，也就是一个表格。表格有两列，一列就是我们欲显示的图形符号，而另一列就是一个数字，通过这么一张表就可以在图形符号和数字之间建立映射。现在已经定义出一标准表，称为ASCII码表，几乎所有的电脑硬件都支持这个转换表以将数字变成符号进而显示计算结果。有了上面的表，当想说明结果为“A”时，就查ASCII码表，得到“A”这个图形符号对应的数字是65，然后就告诉电脑输出序号为65的字符，最后屏幕上显示“A”。这明显地繁杂得异常，为此C++就提供了字符和字符串。当我们想得到某一个图形符号的ASCII码表的序号时，只需通过单引号将那个字符括起来即可，如：'A'，其效果和65是一样的。当要使用不止一个字符时，则用双引号将多个字符括起来，也就是所谓的字符串了，如：&quot;ABC&quot;。因此字符串就是多个字符连起来而已。但根据前面的说明易发现，字符串也需要映射成数字，但它的映射就不像字符那么简单可以通过查表就搞定的，对于此，将在后续文章中对数组作过介绍后再说明。操作符电脑的基本是数字，那么电脑的所有操作都是改变数字，因此很正常地C++提供了操作数字的一些基本操作，称作操作符（Operator），如：+ - * / 等。任何操作符都要返回一个数字，称为操作符的返回值，因此操作符就是操作数字并返回数字的符号。作为一般性地分类，按操作符同时作用的数字个数分为一元、二元和三元操作符。一元操作符有：+     　　其后接数字，原封不动地返回后接的数字。如： +4.4f的返回值是4.4；+-9.3f的返回值是-9.3。完全是出于语义的需要，如表示此数为正数。
-     　　其后接数字，将后接的数字的符号取反。如： -34.4f的返回值是-34.4；-(-54)的返回值是54。用于表示负数。
!     　　其后接数字，逻辑取反后接的数字。逻辑值就是“真”或“假”，为了用数字表示逻辑值，在 C++中规定，非零值即为逻辑真，而零则为逻辑假。因此3、43.4、'A'都表示逻辑真，而0则表示逻辑假。逻辑值被应用于后续的判断及循环语句中。而逻辑取反就是先判断“!”后面接的数字是逻辑真还是逻辑假，然后再将相应值取反。如：!5的返回值是0，因为先由5非零而知是逻辑真，然后取反得逻辑假，故最后返回0。!!345.4的返回值是1，先因345.4非零得逻辑真，取反后得逻辑假，再取反得逻辑真。虽然只要非零就是逻辑真，但作为编译器返回的逻辑真，其一律使用1来代表逻辑真。
~     　　其后接数字，取反后接的数字。取反是逻辑中定义的操作，不能应用于数字。为了对数字应用取反操作，电脑中将数字用二进制表示，然后对数字的每一位进行取反操作（因为二进制数的每一位都只能为1或0，正好符合逻辑的真和假）。如~123的返回值就为-124。先将123 转成二进制数01111011，然后各位取反得10000100，最后得-124。这里的问题就是为什么是8位而不是16位二进制数。因为123小于128，被定位为char类型，故为8位（关于char是什么将下篇介绍）。如果是~123ul，则返回值为4294967172。为什么要有数字取反这个操作？因为CPU提供了这样的指令。并且其还有着很不错且很重要的应用，后面将介绍。关于其他的一元操作符将在后续文章中陆续提到（但不一定全部提到）。二元操作符有：+
-
*
/
%     
其前后各接一数字，返回两数字之和、差、积、商、余数。如：
34+4.4f的返回值是38.4；3+-9.3f的返回值是-6.3。
34-4的返回值是30；5-234的返回值是-229。
3*2的返回值是6；10/3的返回值是3。
10%3的返回值是1；20%7的返回值是6。
&amp;&amp;
||     其前后各接一逻辑值，返回两逻辑值之“与”运算逻辑值和“或”运算逻辑值。如：
'A'&amp;&amp;34.3f的返回值是逻辑真，为1；34&amp;&amp;0的返回值是逻辑假，为0。
0||'B'的返回值是逻辑真，为 1；0||0的返回值是逻辑假，为0。
&amp;
|
^     其前后各接一数字，返回两数字之“与”运算、“或”运算、“异或”运算值。如前面所说，先将两侧的数字转成二进制数，然后对各位进行与、或、异或操作。如：
4&amp;6的返回值是4，4转为00000100，6转为00000110各位相与得，00000100，为4。
4|6的返回值是6，4转为00000100，6转为00000110各位相或得，00000110，为6。
4^6的返回值是2，4转为00000100，6转为00000110各位相异或得，00000010，为2。
&gt;
&lt;
==
&gt;=
&lt;=
!=     其前后各接一数字，根据两数字是否大于、小于、等于、大于等于、小于等于及不等于而返回相应的逻辑值。如：
34&gt;34的返回值是0，为逻辑假；32&lt;345的返回值为1，为逻辑真。
23&gt;=23和23&gt;=14的返回值都是1，为逻辑真；54&lt;=4的返回值为0，为逻辑假。
56==6的返回值是0，为逻辑假；45==45的返回值是1，为逻辑真。
5!=5的返回值是0，为逻辑假；5!=35的返回值是真，为逻辑真。
&gt;&gt;
&lt;&lt;     　　其前后各接一数字，将左侧数字右移或左移右侧数字指定的位数。与前面的 ~、&amp;、|等操作一样，之所以要提供左移、右移操作主要是因为CPU提供了这些指令，主要用于编一些基于二进制数的算法。&lt;&lt;将左侧的数字转成二进制数，然后将各位向左移动右侧数值的位数，如：4，转为00000100，左移2位，则变成00010000，得16。&gt;&gt;与&lt;&lt;一样，只不过是向右移动罢了。如：6，转为00000110，右移1位，变成00000011，得3。如果移2位，则有一位超出，将截断，则6&gt;&gt;2的返回值就是00000001，为1。左移和右移有什么用？用于一些基于二进制数的算法，不过还可以顺便作为一个简单的优化手段。考虑十进制数3524，我们将它左移2位，变成 352400，比原数扩大了100倍，准确的说应该是扩大了10的2次方倍。如果将3524右移2位，变成35，相当于原数除以100的商。
同样，前面4&gt;&gt;2，等效于4/4的商；32&gt;&gt;3相当于32/8，即相当于32除以2的3次方的商。而4&lt;&lt;2等效于4*4，相当于4乘以2的2次方。因此左移和右移相当于乘法和除法，只不过只能是乘或除相应进制数的次方罢了，但它的运行速度却远远高于乘法和除法，因此说它是一种简单的优化手段。
,     　　其前后各接一数字，简单的返回其右侧的数字。如：34.45f,54的返回值是54；-324,4545f的返回值是4545f。那它到底有什么用？用于将多个数字整和成一个数字，在《C++从零开始（四）》中将进一步说明。关于其他的二元操作符将在后续文章中陆续提到（但不一定全部提到）。三元操作符只有一个，为?:，其格式为：&lt;数字1&gt;?&lt;数字2&gt;:&lt;数字3&gt;。它的返回值为：如果&lt;数字1&gt;是逻辑真，返回&lt;数字2&gt;，否则返回&lt;数字3&gt;。如：34?4:2的返回值就是4，因为34非零，为逻辑真，返回4。而0?4:2的返回值就是2，因为0为逻辑假，返回2。表达式你应该发现前面的荒谬之处了——12&gt;435返回值为0，那为什么不直接写0还吃饱了撑了写个12&gt;435在那？这就是表达式的意义了。前面说“&gt;”的前后各接一数字，但是操作符是操作数字并返回数字的符号，因为它返回数字，因此可以放在上面说的任何一个要求接数字的地方，也就形成了所谓的表达式。如：23*54/45&gt;34的返回值就是0，因为23*54的返回值为1242；然后又将1242作为“/”的左接数字，得到新的返回值27.6；最后将27.6作为“&gt;”的左接数字进而得到返回值0，为逻辑假。因此表达式就是由一系列返回数字的东西和操作符组合而成的一段代码，其由于是由操作符组成的，故一定返回值。而前面说的“返回数字的东西”则可以是另一个表达式，或者一个变量，或者一个具有返回值的函数，或者具有数字类型操作符重载的类的对象等，反正只要是能返回一个数字的东西。如果对于何谓变量、函数、类等这些名词感到陌生，不需要去管它们，在后继的文章中将会一一说明。因此34也是一个表达式，其返回值为34，只不过是没有操作符的表达式罢了（在后面将会了解到34其实是一种操作符）。故表达式的概念其实是很广的，只要有返回值的东西就可以称为表达式。由于表达式里有很多操作符，执行操作符的顺序依赖于操作符的优先级，就和数学中的一样，*、/的优先级大于+、-，而+、-又大于&gt;、&lt;等逻辑操作符。不用去刻意记住操作符的优先级，当不能确定操作符的执行顺序时，可以使用小括号来进行指定。如：
((1+2)*3)+3)/4的返回值为3，而1+2*3+3/4的返回值为7。注意3/4为0，因为3/4的商是0。当希望进行浮点数除法或乘法时，只需让操作数中的某一个为浮点数即可，如：3/4.0的返回值为0.75。&amp; | ^ ~等的应用前面提过逻辑操作符“&amp;&amp;”、“||”、“!”等，作为表示逻辑，其被C++提供一点都不值得惊奇。但是为什么要有一个将数字转成二进制数，然后对二进制数的各位进行逻辑操作的这么一类操作符呢？首先是CPU提供了相应的指令，并且其还有着下面这个非常有意义的应用。
考虑一十字路口，每个路口有三盏红绿灯，分别指明能否左转、右转及直行。共有12盏，现在要为它编写一个控制程序，不管这程序的功能怎样，首先需要将红绿灯的状态转化为数字，因为电脑只知道数字。所以用3个数字分别表示某路口的三盏红绿灯，因此每个红绿灯的状态由一个数字来表示，假设红灯为0，绿灯为1（不考虑黄灯或其他情况）。后来忽然发现，其实也可以用一个数字表示一个路口的三盏红绿灯状态，如用110表示左转绿灯、直行绿灯而右转红灯。上面的110是一个十进制数字，它的每一位实际都可以为0~9十个数字，但是这里只应用到了两个：0和1，感觉很浪费。故选择二进制数来表示，还是110，但是是二进制数了，转成十进制数为6，即使当为111时转成十进制数也只是7，比前面的110这个十进制数小多了，节约了……？？什么？？我们在纸上写数字235425234一定比写134这个数字要更多地占用纸张（假设字都一样大）。因此记录一个大的数比记录一个小的数要花费更多的资源。简直荒谬！不管是100还是1000，都只是一个数字，为什么记录大的数字就更费资源？因为电脑并不是数字计算机，而是电子计算机，它是基于状态而不是基于数字的，这在下篇会详细说明。电脑必须使用某种表示方式来代表一个数字，而那个表示方式和二进制很像，但并不是二进制数，故出现记录大的数较小的数更耗资源，这也就是为什么上面整型数要分什么长整型短整型的原因了。下面继续上面的思考。使用了110这个二进制数来表示三盏红绿灯的状态，那么现在要知道110这个数字代表左转红绿灯的什么状态。以数字的第三位表示左转，不过电脑并不知道这个，因此如下：110&amp;100。这个表达式的返回值是100，非零，逻辑真。假设某路口的状态为010，则同样的010&amp;100，返回值为0，逻辑假。因此使用“&amp;”操作符可以将二进制数中的某一位或几位的状态提取出来。所以我们要了解一个数字代表的红绿灯状态中的左转红绿灯是否绿灯时，只需让它和100相与即可。现在要保持其他红绿灯的状态不变，仅仅使左转红绿灯为绿灯，如当前状态为010，为了使左转红绿灯为绿灯，值应该为110，这可以通过010|100做到。如果当前状态是001，则001|100为101，正确——直行和右转的红绿灯状态均没有发生变化。因此使用“|”操作符可以给一个二进制数中的某一位或几位设置状态，但只能设置为1，如果想设置为0，如101，要关掉左转的绿灯，则101&amp;~100，返回值为001。上面一直提到的路口红绿灯的状态实际编写时可以使用一个变量来表示，而上面的100也可以用一个标识符来表示，如state&amp;TS_LEFT，就可以表示检查变量state所表示的状态中的左转红绿灯的状态。上面的这种方法被大量地运用，如创建一个窗口，一个窗口可能有二三十个风格，则通过上面的方法，就可以只用一个32位长的二进制数字就表示了窗口的风格，而不用去弄二三十个数字来分别代表每种风格是否具有。</subject><description><![CDATA[本篇是此系列的开头，在学英语时，第一时间学的是字母，其是英语的基础。同样，在C++中，所有的代码都是通过标识符（Identifier）、表达式（Expression）和语句（Statement）及一些必要的符号（如大括号等）组成，在此先说明何谓标识符。标识符标识符是一个字母序列，由大小写英文字母、下划线及数字组成，用于标识。标识就是标出并识别，也就是名字。其可以作为后面将提到的变量或者函数或者类等的名字，也就是说用来标识某个特定的变量或者函数或者类等C++中的元素。比如：abc就是一个合法的标识符，即abc可以作为变量、函数等元素的名字，但并不代表abc就是某个变量或函数的名字，而所谓的合法就是任何一个标识符都必须不能以数字开头，只能包括大小写英文字母、下划线及数字，不能有其它符号，如,!^等，并且不能与C++关键字相同。也就是我们在给一个变量或函数起名字的时候，必须将起的名字看作是一个标识符，并进而必须满足上面提出的要求。如12ab_C就不是一个合法的标识符，因此我们不能给某个变量或函数起12ab_C这样的名字；ab_12C就是合法的标识符，因此可以被用作变量或函数的名字。前面提到关键字，在后续的语句及一些声明修饰符的介绍中将发现，C++提供了一些特殊的标识符作为语句的名字，用以标识某一特定语句，如if、while等；或者提供一些修饰符用以修饰变量、函数等元素以实现语义或给编译器及连接器提供一些特定信息以进行优化、查错等操作，如extern、static等。因此在命名变量或函数或其他元素时，不能使用if、extern等这种C++关键字作为名字，否则将导致编译器无法确认是一个变量（或函数或其它C++元素）还是一条语句，进而无法编译。如果要让某个标识符是特定变量或函数或类的名字，就需要使用声明，在后续的文章中再具体说明。数字C++作为电脑编程语言，电脑是处理数字的，因此C++中的基础东西就是数字。C++中提供两种数字：整型数和浮点数，也就是整数和小数。但由于电脑实际并不是想象中的数字化的（详情参见《C++从零开始（三）》中的类型一节），所以整型数又分成了有符号和无符号整型数，而浮点数则由精度的区别而分成单精度和双精度浮点数，同样的整型数也根据长度分成长整型和短整型。要在C++代码中表示一个数字，直接书写数字即可，如：123、34.23、-34.34等。由于电脑并非以数字为基础而导致了前面数字的分类，为了在代码中表现出来，C++提供了一系列的后缀进行表示，如下：u或U 表示数字是无符号整型数，如：123u，但并不说明是长整型还是短整型l或L 表示数字是长整型数，如：123l；而123ul就是无符号长整型数；而34.4l就是长双精度浮点数，等效于双精度浮点数i64或I64 表示数字是长长整型数，其是为64位操作系统定义的，长度比长整型数长。如：43i64f或F 表示数字是单精度浮点数，如：12.3fe或E 表示数字的次幂，如：34.4e-2就是0.344；0.2544e3f表示一个单精度浮点数，值为254.4当什么后缀都没写时，则根据有无小数点及位数来决定其具体类型，如：123表示的是有符号整型数，而12341434则是有符号长整型数；而34.43表示双精度浮点数。为什么要搞这么多事出来，还分什么有符号无符号之类的？这全是因为电脑并非基于数字的，而是基于状态的，详情在下篇中将详细说明。作为科学计算，可能经常会碰到使用非十进制数字，如16进制、8进制等，C++也为此提供了一些前缀以进行支持。在数字前面加上0x或0X表示这个数字是16进制表示的，如：0xF3Fa、0x11cF。而在前面加一个0则表示这个数字是用8进制表示的，如： 0347，变为十进制数就为231。但16进制和8进制都不能用于表示浮点数，只能表示整型数，即0x34.343是错误的。字符串C++除了提供数字这种最基础的表示方式外，还提供了字符及字符串。这完全只是出于方便编写程序而提供的，C++作为电脑语言，根本没有提供字符串的必要性。不过由于人对电脑的基本要求就是显示结果，而字符和字符串都由于是人易读的符号而被用于显示结果，所以C++专门提供了对字符串的支持。前面说过，电脑只认识数字，而字符就是文字符号，是一种图形符号。为了使电脑能够处理符号，必须通过某种方式将符号变成数字，在电脑中这通过在符号和数字之间建立一个映射来实现，也就是一个表格。表格有两列，一列就是我们欲显示的图形符号，而另一列就是一个数字，通过这么一张表就可以在图形符号和数字之间建立映射。现在已经定义出一标准表，称为ASCII码表，几乎所有的电脑硬件都支持这个转换表以将数字变成符号进而显示计算结果。有了上面的表，当想说明结果为“A”时，就查ASCII码表，得到“A”这个图形符号对应的数字是65，然后就告诉电脑输出序号为65的字符，最后屏幕上显示“A”。这明显地繁杂得异常，为此C++就提供了字符和字符串。当我们想得到某一个图形符号的ASCII码表的序号时，只需通过单引号将那个字符括起来即可，如：'A'，其效果和65是一样的。当要使用不止一个字符时，则用双引号将多个字符括起来，也就是所谓的字符串了，如：&quot;ABC&quot;。因此字符串就是多个字符连起来而已。但根据前面的说明易发现，字符串也需要映射成数字，但它的映射就不像字符那么简单可以通过查表就搞定的，对于此，将在后续文章中对数组作过介绍后再说明。操作符电脑的基本是数字，那么电脑的所有操作都是改变数字，因此很正常地C++提供了操作数字的一些基本操作，称作操作符（Operator），如：+ - * / 等。任何操作符都要返回一个数字，称为操作符的返回值，因此操作符就是操作数字并返回数字的符号。作为一般性地分类，按操作符同时作用的数字个数分为一元、二元和三元操作符。一元操作符有：+     　　其后接数字，原封不动地返回后接的数字。如： +4.4f的返回值是4.4；+-9.3f的返回值是-9.3。完全是出于语义的需要，如表示此数为正数。
-     　　其后接数字，将后接的数字的符号取反。如： -34.4f的返回值是-34.4；-(-54)的返回值是54。用于表示负数。
!     　　其后接数字，逻辑取反后接的数字。逻辑值就是“真”或“假”，为了用数字表示逻辑值，在 C++中规定，非零值即为逻辑真，而零则为逻辑假。因此3、43.4、'A'都表示逻辑真，而0则表示逻辑假。逻辑值被应用于后续的判断及循环语句中。而逻辑取反就是先判断“!”后面接的数字是逻辑真还是逻辑假，然后再将相应值取反。如：!5的返回值是0，因为先由5非零而知是逻辑真，然后取反得逻辑假，故最后返回0。!!345.4的返回值是1，先因345.4非零得逻辑真，取反后得逻辑假，再取反得逻辑真。虽然只要非零就是逻辑真，但作为编译器返回的逻辑真，其一律使用1来代表逻辑真。
~     　　其后接数字，取反后接的数字。取反是逻辑中定义的操作，不能应用于数字。为了对数字应用取反操作，电脑中将数字用二进制表示，然后对数字的每一位进行取反操作（因为二进制数的每一位都只能为1或0，正好符合逻辑的真和假）。如~123的返回值就为-124。先将123 转成二进制数01111011，然后各位取反得10000100，最后得-124。这里的问题就是为什么是8位而不是16位二进制数。因为123小于128，被定位为char类型，故为8位（关于char是什么将下篇介绍）。如果是~123ul，则返回值为4294967172。为什么要有数字取反这个操作？因为CPU提供了这样的指令。并且其还有着很不错且很重要的应用，后面将介绍。关于其他的一元操作符将在后续文章中陆续提到（但不一定全部提到）。二元操作符有：+
-
*
/
%     
其前后各接一数字，返回两数字之和、差、积、商、余数。如：
34+4.4f的返回值是38.4；3+-9.3f的返回值是-6.3。
34-4的返回值是30；5-234的返回值是-229。
3*2的返回值是6；10/3的返回值是3。
10%3的返回值是1；20%7的返回值是6。
&amp;&amp;
||     其前后各接一逻辑值，返回两逻辑值之“与”运算逻辑值和“或”运算逻辑值。如：
'A'&amp;&amp;34.3f的返回值是逻辑真，为1；34&amp;&amp;0的返回值是逻辑假，为0。
0||'B'的返回值是逻辑真，为 1；0||0的返回值是逻辑假，为0。
&amp;
|
^     其前后各接一数字，返回两数字之“与”运算、“或”运算、“异或”运算值。如前面所说，先将两侧的数字转成二进制数，然后对各位进行与、或、异或操作。如：
4&amp;6的返回值是4，4转为00000100，6转为00000110各位相与得，00000100，为4。
4|6的返回值是6，4转为00000100，6转为00000110各位相或得，00000110，为6。
4^6的返回值是2，4转为00000100，6转为00000110各位相异或得，00000010，为2。
&gt;
&lt;
==
&gt;=
&lt;=
!=     其前后各接一数字，根据两数字是否大于、小于、等于、大于等于、小于等于及不等于而返回相应的逻辑值。如：
34&gt;34的返回值是0，为逻辑假；32&lt;345的返回值为1，为逻辑真。
23&gt;=23和23&gt;=14的返回值都是1，为逻辑真；54&lt;=4的返回值为0，为逻辑假。
56==6的返回值是0，为逻辑假；45==45的返回值是1，为逻辑真。
5!=5的返回值是0，为逻辑假；5!=35的返回值是真，为逻辑真。
&gt;&gt;
&lt;&lt;     　　其前后各接一数字，将左侧数字右移或左移右侧数字指定的位数。与前面的 ~、&amp;、|等操作一样，之所以要提供左移、右移操作主要是因为CPU提供了这些指令，主要用于编一些基于二进制数的算法。&lt;&lt;将左侧的数字转成二进制数，然后将各位向左移动右侧数值的位数，如：4，转为00000100，左移2位，则变成00010000，得16。&gt;&gt;与&lt;&lt;一样，只不过是向右移动罢了。如：6，转为00000110，右移1位，变成00000011，得3。如果移2位，则有一位超出，将截断，则6&gt;&gt;2的返回值就是00000001，为1。左移和右移有什么用？用于一些基于二进制数的算法，不过还可以顺便作为一个简单的优化手段。考虑十进制数3524，我们将它左移2位，变成 352400，比原数扩大了100倍，准确的说应该是扩大了10的2次方倍。如果将3524右移2位，变成35，相当于原数除以100的商。
同样，前面4&gt;&gt;2，等效于4/4的商；32&gt;&gt;3相当于32/8，即相当于32除以2的3次方的商。而4&lt;&lt;2等效于4*4，相当于4乘以2的2次方。因此左移和右移相当于乘法和除法，只不过只能是乘或除相应进制数的次方罢了，但它的运行速度却远远高于乘法和除法，因此说它是一种简单的优化手段。
,     　　其前后各接一数字，简单的返回其右侧的数字。如：34.45f,54的返回值是54；-324,4545f的返回值是4545f。那它到底有什么用？用于将多个数字整和成一个数字，在《C++从零开始（四）》中将进一步说明。关于其他的二元操作符将在后续文章中陆续提到（但不一定全部提到）。三元操作符只有一个，为?:，其格式为：&lt;数字1&gt;?&lt;数字2&gt;:&lt;数字3&gt;。它的返回值为：如果&lt;数字1&gt;是逻辑真，返回&lt;数字2&gt;，否则返回&lt;数字3&gt;。如：34?4:2的返回值就是4，因为34非零，为逻辑真，返回4。而0?4:2的返回值就是2，因为0为逻辑假，返回2。表达式你应该发现前面的荒谬之处了——12&gt;435返回值为0，那为什么不直接写0还吃饱了撑了写个12&gt;435在那？这就是表达式的意义了。前面说“&gt;”的前后各接一数字，但是操作符是操作数字并返回数字的符号，因为它返回数字，因此可以放在上面说的任何一个要求接数字的地方，也就形成了所谓的表达式。如：23*54/45&gt;34的返回值就是0，因为23*54的返回值为1242；然后又将1242作为“/”的左接数字，得到新的返回值27.6；最后将27.6作为“&gt;”的左接数字进而得到返回值0，为逻辑假。因此表达式就是由一系列返回数字的东西和操作符组合而成的一段代码，其由于是由操作符组成的，故一定返回值。而前面说的“返回数字的东西”则可以是另一个表达式，或者一个变量，或者一个具有返回值的函数，或者具有数字类型操作符重载的类的对象等，反正只要是能返回一个数字的东西。如果对于何谓变量、函数、类等这些名词感到陌生，不需要去管它们，在后继的文章中将会一一说明。因此34也是一个表达式，其返回值为34，只不过是没有操作符的表达式罢了（在后面将会了解到34其实是一种操作符）。故表达式的概念其实是很广的，只要有返回值的东西就可以称为表达式。由于表达式里有很多操作符，执行操作符的顺序依赖于操作符的优先级，就和数学中的一样，*、/的优先级大于+、-，而+、-又大于&gt;、&lt;等逻辑操作符。不用去刻意记住操作符的优先级，当不能确定操作符的执行顺序时，可以使用小括号来进行指定。如：
((1+2)*3)+3)/4的返回值为3，而1+2*3+3/4的返回值为7。注意3/4为0，因为3/4的商是0。当希望进行浮点数除法或乘法时，只需让操作数中的某一个为浮点数即可，如：3/4.0的返回值为0.75。&amp; | ^ ~等的应用前面提过逻辑操作符“&amp;&amp;”、“||”、“!”等，作为表示逻辑，其被C++提供一点都不值得惊奇。但是为什么要有一个将数字转成二进制数，然后对二进制数的各位进行逻辑操作的这么一类操作符呢？首先是CPU提供了相应的指令，并且其还有着下面这个非常有意义的应用。
考虑一十字路口，每个路口有三盏红绿灯，分别指明能否左转、右转及直行。共有12盏，现在要为它编写一个控制程序，不管这程序的功能怎样，首先需要将红绿灯的状态转化为数字，因为电脑只知道数字。所以用3个数字分别表示某路口的三盏红绿灯，因此每个红绿灯的状态由一个数字来表示，假设红灯为0，绿灯为1（不考虑黄灯或其他情况）。后来忽然发现，其实也可以用一个数字表示一个路口的三盏红绿灯状态，如用110表示左转绿灯、直行绿灯而右转红灯。上面的110是一个十进制数字，它的每一位实际都可以为0~9十个数字，但是这里只应用到了两个：0和1，感觉很浪费。故选择二进制数来表示，还是110，但是是二进制数了，转成十进制数为6，即使当为111时转成十进制数也只是7，比前面的110这个十进制数小多了，节约了……？？什么？？我们在纸上写数字235425234一定比写134这个数字要更多地占用纸张（假设字都一样大）。因此记录一个大的数比记录一个小的数要花费更多的资源。简直荒谬！不管是100还是1000，都只是一个数字，为什么记录大的数字就更费资源？因为电脑并不是数字计算机，而是电子计算机，它是基于状态而不是基于数字的，这在下篇会详细说明。电脑必须使用某种表示方式来代表一个数字，而那个表示方式和二进制很像，但并不是二进制数，故出现记录大的数较小的数更耗资源，这也就是为什么上面整型数要分什么长整型短整型的原因了。下面继续上面的思考。使用了110这个二进制数来表示三盏红绿灯的状态，那么现在要知道110这个数字代表左转红绿灯的什么状态。以数字的第三位表示左转，不过电脑并不知道这个，因此如下：110&amp;100。这个表达式的返回值是100，非零，逻辑真。假设某路口的状态为010，则同样的010&amp;100，返回值为0，逻辑假。因此使用“&amp;”操作符可以将二进制数中的某一位或几位的状态提取出来。所以我们要了解一个数字代表的红绿灯状态中的左转红绿灯是否绿灯时，只需让它和100相与即可。现在要保持其他红绿灯的状态不变，仅仅使左转红绿灯为绿灯，如当前状态为010，为了使左转红绿灯为绿灯，值应该为110，这可以通过010|100做到。如果当前状态是001，则001|100为101，正确——直行和右转的红绿灯状态均没有发生变化。因此使用“|”操作符可以给一个二进制数中的某一位或几位设置状态，但只能设置为1，如果想设置为0，如101，要关掉左转的绿灯，则101&amp;~100，返回值为001。上面一直提到的路口红绿灯的状态实际编写时可以使用一个变量来表示，而上面的100也可以用一个标识符来表示，如state&amp;TS_LEFT，就可以表示检查变量state所表示的状态中的左转红绿灯的状态。上面的这种方法被大量地运用，如创建一个窗口，一个窗口可能有二三十个风格，则通过上面的方法，就可以只用一个32位长的二进制数字就表示了窗口的风格，而不用去弄二三十个数字来分别代表每种风格是否具有。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C/C++头文件一览</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16883</link><subject>C、传统 C++#include &lt;assert.h&gt;　　　　//设定插入点
#include &lt;ctype.h&gt;　　　　 //字符处理
#include &lt;errno.h&gt;　　　　 //定义错误码
#include &lt;float.h&gt;　　　　 //浮点数处理
#include &lt;fstream.h&gt;　　　 //文件输入／输出
#include &lt;iomanip.h&gt;　　　 //参数化输入／输出
#include &lt;iostream.h&gt;　　　//数据流输入／输出
#include &lt;limits.h&gt;　　　　//定义各种数据类型最值常量
#include &lt;locale.h&gt;　　　　//定义本地化函数
#include &lt;math.h&gt;　　　　　//定义数学函数
#include &lt;stdio.h&gt;　　　　 //定义输入／输出函数
#include &lt;stdlib.h&gt;　　　　//定义杂项函数及内存分配函数
#include &lt;string.h&gt;　　　　//字符串处理
#include &lt;strstrea.h&gt;　　　//基于数组的输入／输出
#include &lt;time.h&gt;　　　　　//定义关于时间的函数
#include &lt;wchar.h&gt;　　　　 //宽字符处理及输入／输出
#include &lt;wctype.h&gt;　　　　//宽字符分类//////////////////////////////////////////////////////////////////////////标准 C++　（同上的不再注释）#include &lt;algorithm&gt;　　　 //STL 通用算法
#include &lt;bitset&gt;　　　　　//STL 位集容器
#include &lt;cctype&gt;
#include &lt;cerrno&gt;
#include &lt;clocale&gt;
#include &lt;cmath&gt;
#include &lt;complex&gt;　　　　 //复数类
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;cstring&gt;
#include &lt;ctime&gt;
#include &lt;deque&gt;　　　　　 //STL 双端队列容器
#include &lt;exception&gt;　　　 //异常处理类
#include &lt;fstream&gt;
#include &lt;functional&gt;　　　//STL 定义运算函数（代替运算符）
#include &lt;limits&gt;
#include &lt;list&gt;　　　　　　//STL 线性列表容器
#include &lt;map&gt;　　　　　　 //STL 映射容器
#include &lt;iomanip&gt;
#include &lt;ios&gt;　　　　　　 //基本输入／输出支持
#include &lt;iosfwd&gt;　　　　　//输入／输出系统使用的前置声明
#include &lt;iostream&gt;
#include &lt;istream&gt;　　　　 //基本输入流
#include &lt;ostream&gt;　　　　 //基本输出流
#include &lt;queue&gt;　　　　　 //STL 队列容器
#include &lt;set&gt;　　　　　　 //STL 集合容器
#include &lt;sstream&gt;　　　　 //基于字符串的流
#include &lt;stack&gt;　　　　　 //STL 堆栈容器　　　　
#include &lt;stdexcept&gt;　　　 //标准异常类
#include &lt;streambuf&gt;　　　 //底层输入／输出支持
#include &lt;string&gt;　　　　　//字符串类
#include &lt;utility&gt;　　　　 //STL 通用模板类
#include &lt;vector&gt;　　　　　//STL 动态数组容器
#include &lt;cwchar&gt;
#include &lt;cwctype&gt;using namespace std;//////////////////////////////////////////////////////////////////////////C99 增加#include &lt;complex.h&gt;　　 //复数处理
#include &lt;fenv.h&gt;　　　　//浮点环境
#include &lt;inttypes.h&gt;　　//整数格式转换
#include &lt;stdbool.h&gt;　　 //布尔环境
#include &lt;stdint.h&gt;　　　//整型环境
#include &lt;tgmath.h&gt;　　　//通用类型数学宏</subject><description><![CDATA[C、传统 C++#include &lt;assert.h&gt;　　　　//设定插入点
#include &lt;ctype.h&gt;　　　　 //字符处理
#include &lt;errno.h&gt;　　　　 //定义错误码
#include &lt;float.h&gt;　　　　 //浮点数处理
#include &lt;fstream.h&gt;　　　 //文件输入／输出
#include &lt;iomanip.h&gt;　　　 //参数化输入／输出
#include &lt;iostream.h&gt;　　　//数据流输入／输出
#include &lt;limits.h&gt;　　　　//定义各种数据类型最值常量
#include &lt;locale.h&gt;　　　　//定义本地化函数
#include &lt;math.h&gt;　　　　　//定义数学函数
#include &lt;stdio.h&gt;　　　　 //定义输入／输出函数
#include &lt;stdlib.h&gt;　　　　//定义杂项函数及内存分配函数
#include &lt;string.h&gt;　　　　//字符串处理
#include &lt;strstrea.h&gt;　　　//基于数组的输入／输出
#include &lt;time.h&gt;　　　　　//定义关于时间的函数
#include &lt;wchar.h&gt;　　　　 //宽字符处理及输入／输出
#include &lt;wctype.h&gt;　　　　//宽字符分类//////////////////////////////////////////////////////////////////////////标准 C++　（同上的不再注释）#include &lt;algorithm&gt;　　　 //STL 通用算法
#include &lt;bitset&gt;　　　　　//STL 位集容器
#include &lt;cctype&gt;
#include &lt;cerrno&gt;
#include &lt;clocale&gt;
#include &lt;cmath&gt;
#include &lt;complex&gt;　　　　 //复数类
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;cstring&gt;
#include &lt;ctime&gt;
#include &lt;deque&gt;　　　　　 //STL 双端队列容器
#include &lt;exception&gt;　　　 //异常处理类
#include &lt;fstream&gt;
#include &lt;functional&gt;　　　//STL 定义运算函数（代替运算符）
#include &lt;limits&gt;
#include &lt;list&gt;　　　　　　//STL 线性列表容器
#include &lt;map&gt;　　　　　　 //STL 映射容器
#include &lt;iomanip&gt;
#include &lt;ios&gt;　　　　　　 //基本输入／输出支持
#include &lt;iosfwd&gt;　　　　　//输入／输出系统使用的前置声明
#include &lt;iostream&gt;
#include &lt;istream&gt;　　　　 //基本输入流
#include &lt;ostream&gt;　　　　 //基本输出流
#include &lt;queue&gt;　　　　　 //STL 队列容器
#include &lt;set&gt;　　　　　　 //STL 集合容器
#include &lt;sstream&gt;　　　　 //基于字符串的流
#include &lt;stack&gt;　　　　　 //STL 堆栈容器　　　　
#include &lt;stdexcept&gt;　　　 //标准异常类
#include &lt;streambuf&gt;　　　 //底层输入／输出支持
#include &lt;string&gt;　　　　　//字符串类
#include &lt;utility&gt;　　　　 //STL 通用模板类
#include &lt;vector&gt;　　　　　//STL 动态数组容器
#include &lt;cwchar&gt;
#include &lt;cwctype&gt;using namespace std;//////////////////////////////////////////////////////////////////////////C99 增加#include &lt;complex.h&gt;　　 //复数处理
#include &lt;fenv.h&gt;　　　　//浮点环境
#include &lt;inttypes.h&gt;　　//整数格式转换
#include &lt;stdbool.h&gt;　　 //布尔环境
#include &lt;stdint.h&gt;　　　//整型环境
#include &lt;tgmath.h&gt;　　　//通用类型数学宏]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十讲 函数(1)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16906</link><subject>概述 在第一章中已经介绍过，C源程序是由函数组成的。 虽然在前面各章的程序中都只有一个主函数main()， 但实用程序往往由多个函数组成。函数是C源程序的基本模块， 通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C，MS C 都提供了三百多个库函数)，还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块，然后用调用的方法来使用函数。可以说C程序的全部工作都是由各式各样的函数完成的， 所以也把C语言称为函数式语言。 由于采用了函数模块式的结构， C语言易于实现结构化程序设计。使程序的层次结构清晰，便于程序的编写、阅读、调试。在C语言中可从不同的角度对函数分类。1. 从函数定义的角度看，函数可分为库函数和用户定义函数两种。(1)库函数由C系统提供，用户无须定义， 也不必在程序中作类型说明，只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar 、putchar、gets、puts、strcat等函数均属此类。(2)用户定义函数由用户按需要写的函数。对于用户自定义函数， 不仅要在程序中定义函数本身， 而且在主调函数模块中还必须对该被调函数进行类型说明，然后才能使用。2. C语言的函数兼有其它语言中的函数和过程两种功能，从这个角度看，又可把函数分为有返回值函数和无返回值函数两种。(1)有返回值函数此类函数被调用执行完后将向调用者返回一个执行结果， 称为函数返回值。如数学函数即属于此类函数。 由用户定义的这种要返回函数值的函数，必须在函数定义和函数说明中明确返回值的类型。(2)无返回值函数此类函数用于完成某项特定的处理任务， 执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值，用户在定义此类函数时可指定它的返回为“空类型”， 空类型的说明符为“void”。3. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。(1)无参函数函数定义、函数说明及函数调用中均不带参数。 主调函数和被调函数之间不进行参数传送。 此类函数通常用来完成一组指定的功能，可以返回或不返回函数值。(2)有参函数也称为带参函数。在函数定义及函数说明时都有参数， 称为形式参数(简称为形参)。在函数调用时也必须给出参数， 称为实际参数(简称为实参)。 进行函数调用时，主调函数将把实参的值传送给形参，供被调函数使用。4. C语言提供了极为丰富的库函数， 这些库函数又可从功能角度作以下分类。(1)字符类型分类函数用于对字符按ASCII码分类：字母，数字，控制字符，分隔符，大小写字母等。(2)转换函数用于字符或字符串的转换；在字符量和各类数字量 (整型， 实型等)之间进行转换；在大、小写之间进行转换。(3)目录路径函数用于文件目录和路径操作。(4)诊断函数用于内部错误检测。(5)图形函数用于屏幕管理和各种图形功能。 (6)输入输出函数用于完成输入输出功能。(7)接口函数用于与DOS，BIOS和硬件的接口。(8)字符串函数 用于字符串操作和处理。(9)内存管理函数用于内存管理。(10)数学函数用于数学函数计算。(11)日期和时间函数用于日期，时间转换操作。(12)进程控制函数用于进程管理和控制。(13)其它函数用于其它各种功能。以上各类函数不仅数量多，而且有的还需要硬件知识才会使用，因此要想全部掌握则需要一个较长的学习过程。 应首先掌握一些最基本、 最常用的函数，再逐步深入。由于篇幅关系，本书只介绍了很少一部分库函数， 其余部分读者可根据需要查阅有关手册。还应该指出的是，在C语言中，所有的函数定义，包括主函数main在内，都是平行的。也就是说，在一个函数的函数体内， 不能再定义另一个函数， 即不能嵌套定义。但是函数之间允许相互调用，也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己，称为递归调用。main 函数是主函数，它可以调用其它函数，而不允许被其它函数调用。 因此，C程序的执行总是从main函数开始， 完成对其它函数的调用后再返回到main函数，最后由main函数结束整个程序。一个C源程序必须有，也只能有一个主函数main。函数定义的一般形式1.无参函数的一般形式 类型说明符 函数名() { 类型说明 语句 }其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型，函数的类型实际上是函数返回值的类型。 该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符，函数名后有一个空括号，其中无参数，但括号不可少。{} 中的内容称为函数体。在函数体中也有类型说明， 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值， 此时函数类型符可以写为void。我们可以改为一个函数定义： void Hello()
{printf (&quot;Hello,world \n&quot;);
} 这里，只把main改为Hello作为函数名，其余不变。Hello 函数是一个无参函数，当被其它函数调用时，输出Hello world字符串。2.有参函数的一般形式 类型说明符 函数名(形式参数表) 型式参数类型说明 { 类型说明 语句 }有参函数比无参函数多了两个内容，其一是形式参数表， 其二是形式参数类型说明。在形参表中给出的参数称为形式参数， 它们可以是各种类型的变量， 各参数之间用逗号间隔。在进行函数调用时，主调函数将赋予这些形式参数实际的值。 形参既然是变量，当然必须给以类型说明。例如，定义一个函数， 用于求两个数中的大数，可写为：int max(a,b)
int a,b;
{
if (a&gt;b) return a;
else return b;
}  第一行说明max函数是一个整型函数，其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b 的具体值是由主调函数在调用时传送过来的。在{}中的函数体内， 除形参外没有使用其它变量，因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。 这种格式不易于编译系统检查，从而会引起一些非常细微而且难于跟踪的错误。ANSI C 的新标准中把对形参的类型说明合并到形参表中，称为“现代格式”。例如max函数用现代格式可定义为：int max(int a,int b)
{
if(a&gt;b) return a;
else return b;
} 现代格式在函数定义和函数说明(后面将要介绍)时， 给出了形式参数及其类型，在编译时易于对它们进行查错， 从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。 在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。 在C程序中，一个函数的定义可以放在任意位置， 既可放在主函数main之前，也可放在main之后。例如例1.3中定义了一个max 函数，其位置在main之后， 也可以把它放在main之前。修改后的程序如下所示。int max(int a,int b)
{
if(a&gt;b)return a;
else return b;
}
void main()
{
int max(int a,int b);
int x,y,z;
printf(&quot;input two numbers:\n&quot;);
scanf(&quot;%d%d&quot;,&amp;x,&amp;y);
z=max(x,y);
printf(&quot;maxmum=%d&quot;,z);
} 现在我们可以从函数定义、 函数说明及函数调用的角度来分析整个程序，从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后，因为准备调用max函数，故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事，在后面还要专门讨论。 可以看出函数说明与函数定义中的函数头部分相同，但是末尾要加分号。程序第12 行为调用max函数，并把x,y中的值传送给max的形参a,b。max函数执行的结果 (a或b)将返回给变量z。最后由主函数输出z的值。函数调用的一般形式前面已经说过，在程序中是通过对函数的调用来执行函数体的，其过程与其它语言的子程序调用相似。C语言中， 函数调用的一般形式为： 函数名(实际参数表) 对无参函数调用时则无实际参数表。 实际参数表中的参数可以是常数，变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of Page在C语言中，可以用以下几种方式调用函数：1.函数表达式函数作表达式中的一项出现在表达式中，以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如： z=max(x,y)是一个赋值表达式，把max的返回值赋予变量z。'Next of Page2.函数语句函数调用的一般形式加上分号即构成函数语句。例如： printf (&quot;%D&quot;,a);scanf (&quot;%d&quot;,&amp;b);都是以函数语句的方式调用函数。3.函数实参函数作为另一个函数调用的实际参数出现。 这种情况是把该函数的返回值作为实参进行传送，因此要求该函数必须是有返回值的。例如： printf(&quot;%d&quot;,max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。 所谓求值顺序是指对实参表中各量是自左至右使用呢，还是自右至左使用。 对此， 各系统的规定不一定相同。在3.1.3节介绍printf 函数时已提到过，这里从函数调用的角度再强调一下。 看例5.2程序。void main()
{
int i=8;
printf(&quot;%d\n%d\n%d\n%d\n&quot;,++i,--i,i++,i--);
} 如按照从右至左的顺序求值。例5.2的运行结果应为：8778如对printf语句中的++i，--i，i++，i--从左至右求值，结果应为：9889应特别注意的是，无论是从左至右求值， 还是自右至左求值，其输出顺序都是不变的， 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值，所以结果为8，7，7，8。上述问题如还不理解，上机一试就明白了。函数的参数和函数的值</subject><description><![CDATA[概述 在第一章中已经介绍过，C源程序是由函数组成的。 虽然在前面各章的程序中都只有一个主函数main()， 但实用程序往往由多个函数组成。函数是C源程序的基本模块， 通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C，MS C 都提供了三百多个库函数)，还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块，然后用调用的方法来使用函数。可以说C程序的全部工作都是由各式各样的函数完成的， 所以也把C语言称为函数式语言。 由于采用了函数模块式的结构， C语言易于实现结构化程序设计。使程序的层次结构清晰，便于程序的编写、阅读、调试。在C语言中可从不同的角度对函数分类。1. 从函数定义的角度看，函数可分为库函数和用户定义函数两种。(1)库函数由C系统提供，用户无须定义， 也不必在程序中作类型说明，只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar 、putchar、gets、puts、strcat等函数均属此类。(2)用户定义函数由用户按需要写的函数。对于用户自定义函数， 不仅要在程序中定义函数本身， 而且在主调函数模块中还必须对该被调函数进行类型说明，然后才能使用。2. C语言的函数兼有其它语言中的函数和过程两种功能，从这个角度看，又可把函数分为有返回值函数和无返回值函数两种。(1)有返回值函数此类函数被调用执行完后将向调用者返回一个执行结果， 称为函数返回值。如数学函数即属于此类函数。 由用户定义的这种要返回函数值的函数，必须在函数定义和函数说明中明确返回值的类型。(2)无返回值函数此类函数用于完成某项特定的处理任务， 执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值，用户在定义此类函数时可指定它的返回为“空类型”， 空类型的说明符为“void”。3. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。(1)无参函数函数定义、函数说明及函数调用中均不带参数。 主调函数和被调函数之间不进行参数传送。 此类函数通常用来完成一组指定的功能，可以返回或不返回函数值。(2)有参函数也称为带参函数。在函数定义及函数说明时都有参数， 称为形式参数(简称为形参)。在函数调用时也必须给出参数， 称为实际参数(简称为实参)。 进行函数调用时，主调函数将把实参的值传送给形参，供被调函数使用。4. C语言提供了极为丰富的库函数， 这些库函数又可从功能角度作以下分类。(1)字符类型分类函数用于对字符按ASCII码分类：字母，数字，控制字符，分隔符，大小写字母等。(2)转换函数用于字符或字符串的转换；在字符量和各类数字量 (整型， 实型等)之间进行转换；在大、小写之间进行转换。(3)目录路径函数用于文件目录和路径操作。(4)诊断函数用于内部错误检测。(5)图形函数用于屏幕管理和各种图形功能。 (6)输入输出函数用于完成输入输出功能。(7)接口函数用于与DOS，BIOS和硬件的接口。(8)字符串函数 用于字符串操作和处理。(9)内存管理函数用于内存管理。(10)数学函数用于数学函数计算。(11)日期和时间函数用于日期，时间转换操作。(12)进程控制函数用于进程管理和控制。(13)其它函数用于其它各种功能。以上各类函数不仅数量多，而且有的还需要硬件知识才会使用，因此要想全部掌握则需要一个较长的学习过程。 应首先掌握一些最基本、 最常用的函数，再逐步深入。由于篇幅关系，本书只介绍了很少一部分库函数， 其余部分读者可根据需要查阅有关手册。还应该指出的是，在C语言中，所有的函数定义，包括主函数main在内，都是平行的。也就是说，在一个函数的函数体内， 不能再定义另一个函数， 即不能嵌套定义。但是函数之间允许相互调用，也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己，称为递归调用。main 函数是主函数，它可以调用其它函数，而不允许被其它函数调用。 因此，C程序的执行总是从main函数开始， 完成对其它函数的调用后再返回到main函数，最后由main函数结束整个程序。一个C源程序必须有，也只能有一个主函数main。函数定义的一般形式1.无参函数的一般形式 类型说明符 函数名() { 类型说明 语句 }其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型，函数的类型实际上是函数返回值的类型。 该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符，函数名后有一个空括号，其中无参数，但括号不可少。{} 中的内容称为函数体。在函数体中也有类型说明， 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值， 此时函数类型符可以写为void。我们可以改为一个函数定义： void Hello()
{printf (&quot;Hello,world \n&quot;);
} 这里，只把main改为Hello作为函数名，其余不变。Hello 函数是一个无参函数，当被其它函数调用时，输出Hello world字符串。2.有参函数的一般形式 类型说明符 函数名(形式参数表) 型式参数类型说明 { 类型说明 语句 }有参函数比无参函数多了两个内容，其一是形式参数表， 其二是形式参数类型说明。在形参表中给出的参数称为形式参数， 它们可以是各种类型的变量， 各参数之间用逗号间隔。在进行函数调用时，主调函数将赋予这些形式参数实际的值。 形参既然是变量，当然必须给以类型说明。例如，定义一个函数， 用于求两个数中的大数，可写为：int max(a,b)
int a,b;
{
if (a&gt;b) return a;
else return b;
}  第一行说明max函数是一个整型函数，其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b 的具体值是由主调函数在调用时传送过来的。在{}中的函数体内， 除形参外没有使用其它变量，因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。 这种格式不易于编译系统检查，从而会引起一些非常细微而且难于跟踪的错误。ANSI C 的新标准中把对形参的类型说明合并到形参表中，称为“现代格式”。例如max函数用现代格式可定义为：int max(int a,int b)
{
if(a&gt;b) return a;
else return b;
} 现代格式在函数定义和函数说明(后面将要介绍)时， 给出了形式参数及其类型，在编译时易于对它们进行查错， 从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。 在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。 在C程序中，一个函数的定义可以放在任意位置， 既可放在主函数main之前，也可放在main之后。例如例1.3中定义了一个max 函数，其位置在main之后， 也可以把它放在main之前。修改后的程序如下所示。int max(int a,int b)
{
if(a&gt;b)return a;
else return b;
}
void main()
{
int max(int a,int b);
int x,y,z;
printf(&quot;input two numbers:\n&quot;);
scanf(&quot;%d%d&quot;,&amp;x,&amp;y);
z=max(x,y);
printf(&quot;maxmum=%d&quot;,z);
} 现在我们可以从函数定义、 函数说明及函数调用的角度来分析整个程序，从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后，因为准备调用max函数，故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事，在后面还要专门讨论。 可以看出函数说明与函数定义中的函数头部分相同，但是末尾要加分号。程序第12 行为调用max函数，并把x,y中的值传送给max的形参a,b。max函数执行的结果 (a或b)将返回给变量z。最后由主函数输出z的值。函数调用的一般形式前面已经说过，在程序中是通过对函数的调用来执行函数体的，其过程与其它语言的子程序调用相似。C语言中， 函数调用的一般形式为： 函数名(实际参数表) 对无参函数调用时则无实际参数表。 实际参数表中的参数可以是常数，变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of Page在C语言中，可以用以下几种方式调用函数：1.函数表达式函数作表达式中的一项出现在表达式中，以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如： z=max(x,y)是一个赋值表达式，把max的返回值赋予变量z。'Next of Page2.函数语句函数调用的一般形式加上分号即构成函数语句。例如： printf (&quot;%D&quot;,a);scanf (&quot;%d&quot;,&amp;b);都是以函数语句的方式调用函数。3.函数实参函数作为另一个函数调用的实际参数出现。 这种情况是把该函数的返回值作为实参进行传送，因此要求该函数必须是有返回值的。例如： printf(&quot;%d&quot;,max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。 所谓求值顺序是指对实参表中各量是自左至右使用呢，还是自右至左使用。 对此， 各系统的规定不一定相同。在3.1.3节介绍printf 函数时已提到过，这里从函数调用的角度再强调一下。 看例5.2程序。void main()
{
int i=8;
printf(&quot;%d\n%d\n%d\n%d\n&quot;,++i,--i,i++,i--);
} 如按照从右至左的顺序求值。例5.2的运行结果应为：8778如对printf语句中的++i，--i，i++，i--从左至右求值，结果应为：9889应特别注意的是，无论是从左至右求值， 还是自右至左求值，其输出顺序都是不变的， 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值，所以结果为8，7，7，8。上述问题如还不理解，上机一试就明白了。函数的参数和函数的值]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言的发展</title><link>http://www.bcbbs.net/News/Content.aspx?ID=55451</link><subject>C 语言是介与汇编语言和高级语言之间的语言，属于高级语言; 是集汇编语言和高级语言的优点于一身的程序设计语言.1972年,C语言在美国贝尔实验室里问世，其发展历程如下：　 Algol60 由一个国际委员会于19世纪60年代早期设计。CPL (Combined Programming Language 混合编程语言）由剑桥和伦敦大学于１９６３年开发而成。BCPL （Basic Combined Programming Language 基础混合编程语言）由剑桥大学的Martin Richards于1967年发明。B 由贝尔实验室的Ken Thompson于1970年发明C 由贝尔实验室的Dennis Ritchie于1972年发明</subject><description><![CDATA[C 语言是介与汇编语言和高级语言之间的语言，属于高级语言; 是集汇编语言和高级语言的优点于一身的程序设计语言.1972年,C语言在美国贝尔实验室里问世，其发展历程如下：　 Algol60 由一个国际委员会于19世纪60年代早期设计。CPL (Combined Programming Language 混合编程语言）由剑桥和伦敦大学于１９６３年开发而成。BCPL （Basic Combined Programming Language 基础混合编程语言）由剑桥大学的Martin Richards于1967年发明。B 由贝尔实验室的Ken Thompson于1970年发明C 由贝尔实验室的Dennis Ritchie于1972年发明]]></description><PubDate>2009-7-16 0:00:00</PubDate><category>C/C++</category></item><item><title>C++标准头文件结构</title><link>http://www.bcbbs.net/News/Content.aspx?ID=55564</link><subject>在C语言中，并没有任何内在的机制来完成如下一些功能：在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作，就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序，但通常认为它们是独立于编译器的。预处理过程读入源代码，检查包含预处理指令的语句和宏定义，并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字，在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令，该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令：指令 用途# 空指令，无任何效果#include 包含一个源代码文件#define 定义宏#undef 取消已定义的宏#if 如果给定条件为真，则编译下面代码#ifdef 如果宏已经定义，则编译下面代码#ifndef 如果宏没有定义，则编译下面代码#elif 如果前面的#if给定条件不为真，当前条件为真，则编译下面代码#endif 结束一个#if……#else条件编译块#error 停止编译并显示错误信息一、文件包含#include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的，也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时，通过给定编译时的条件来达到不同的效果。例如：#define AAA #include &quot;t.c&quot; #undef AAA #include &quot;t.c&quot;为了避免那些只能包含一次的头文件被多次包含，可以在头文件中用编译时条件来进行控制。例如：*my.h*/#ifndef MY_H#define MY_H……#endif在程序中包含头文件有两种格式：#include #include &quot;my.h&quot;第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件，如果找不到，再搜索编译器自带的头文件。采用两种不同包含格式的理由在于，编译器是安装在公共子目录下的，而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件，也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。</subject><description><![CDATA[在C语言中，并没有任何内在的机制来完成如下一些功能：在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作，就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序，但通常认为它们是独立于编译器的。预处理过程读入源代码，检查包含预处理指令的语句和宏定义，并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字，在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令，该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令：指令 用途# 空指令，无任何效果#include 包含一个源代码文件#define 定义宏#undef 取消已定义的宏#if 如果给定条件为真，则编译下面代码#ifdef 如果宏已经定义，则编译下面代码#ifndef 如果宏没有定义，则编译下面代码#elif 如果前面的#if给定条件不为真，当前条件为真，则编译下面代码#endif 结束一个#if……#else条件编译块#error 停止编译并显示错误信息一、文件包含#include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的，也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时，通过给定编译时的条件来达到不同的效果。例如：#define AAA #include &quot;t.c&quot; #undef AAA #include &quot;t.c&quot;为了避免那些只能包含一次的头文件被多次包含，可以在头文件中用编译时条件来进行控制。例如：*my.h*/#ifndef MY_H#define MY_H……#endif在程序中包含头文件有两种格式：#include #include &quot;my.h&quot;第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件，如果找不到，再搜索编译器自带的头文件。采用两种不同包含格式的理由在于，编译器是安装在公共子目录下的，而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件，也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。]]></description><PubDate>2009-7-18 0:00:00</PubDate><category>C/C++</category></item><item><title>this指针在继承中的重要性</title><link>http://www.bcbbs.net/News/Content.aspx?ID=56127</link><subject>#include&lt;iostream&gt;using std::cout;using std::endl;class base;base* x;   //全局指针class base{public: base(){      x=this;  //this指向实际的对象，派生类产生后将指向派生类对象。 }    virtual void rprint(){  cout&lt;&lt;&quot;this point to base&quot;&lt;&lt;endl; }};int main(){ x-&gt;rprint(); return 0;}class derive:public base{public: derive(){}    void rprint();};//全局应用对象derive d1;void derive::rprint(){ cout&lt;&lt;&quot;this point to derive&quot;&lt;&lt;endl;}</subject><description><![CDATA[#include&lt;iostream&gt;using std::cout;using std::endl;class base;base* x;   //全局指针class base{public: base(){      x=this;  //this指向实际的对象，派生类产生后将指向派生类对象。 }    virtual void rprint(){  cout&lt;&lt;&quot;this point to base&quot;&lt;&lt;endl; }};int main(){ x-&gt;rprint(); return 0;}class derive:public base{public: derive(){}    void rprint();};//全局应用对象derive d1;void derive::rprint(){ cout&lt;&lt;&quot;this point to derive&quot;&lt;&lt;endl;}]]></description><PubDate>2009-8-21 0:00:00</PubDate><category>C/C++</category></item><item><title>c语言中使用环境变量的技巧</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16852</link><subject>程序readenv.c显示了在c语言中存取环境变量的若干技巧。该程序将以与dos的SET命令相同的格式打印出当前所有的环境变量，然后寻找PATH变量，打印出整个路径字符串。该程序中有两个重要的子程序，find_env_string(),它的主要作用是：根据从调用这传来的要求，或者从环境空间的起点开始，或者从上一次搜索後停留的地方开始，寻找随后的第一个环境字符串，具体程序清单如下。/**************readnv.c****************/
#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;dos.h&gt;#define MAX 127
#define TRUE 1
#define FALSE 0
#define FIRST 1
#define NEXT 0void dispenv(void);
int find_env_string(int restart);char env_string[MAX+2];
int env_offset=0;void main()
{
char *path_string; /*调用子程序dispenv(),以与SET命令相同的格式打印出当前环境字符串*/
dispenv(); /*在环境变量中寻找PATH变量，并打印出整个论经字符串*/
if(path_string==getenv(&quot;PATH&quot;))
printf(&quot;\nPATH=%s&quot;,path_string);
}
/********************************************/void dispenv()
{
int restart; /*在for循环中，先置restart为FIRST，调用子程序find_env_string()
寻找第一个环境字符串并打印，而后置restart为NEXT,继续寻找下一
个环境字符串，直到结束子程序find_env_string()返回FALSE为止*/
for(restart=FIRST;find_env_string(restart);restart=NEXT)
printf(&quot;\n%s&quot;,env_string);
}
/********************************************/int find_env_string(int restart)
{
int buf_offset;
int environment;
if(!environment) /*将环境空间短地址送入变量environment中*/
environment=peek(_psp,0x2c);
if(restart)
environment=0; /*首次调用时，偏移量置0，即从环境空间起点开始*/
for(buf_offset=0;TRUE;env_offset++){
env_string[buf_offset]=peekb(environment,env_offset);
/*将环境空间中的字符逐个读入数组env_string中
if(env_string[buf_offset]){/*若为空字符，继续*/
buf_offset++;
continue;
}
if(!buf_offset) /*若buf_offset=0,则中个环境结束，回FALSE*/
return FALSE;
env_offset++;/*若为空字符，则一个环境字符串结束，回TRUE*/
return TRUE;
}
}</subject><description><![CDATA[程序readenv.c显示了在c语言中存取环境变量的若干技巧。该程序将以与dos的SET命令相同的格式打印出当前所有的环境变量，然后寻找PATH变量，打印出整个路径字符串。该程序中有两个重要的子程序，find_env_string(),它的主要作用是：根据从调用这传来的要求，或者从环境空间的起点开始，或者从上一次搜索後停留的地方开始，寻找随后的第一个环境字符串，具体程序清单如下。/**************readnv.c****************/
#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;dos.h&gt;#define MAX 127
#define TRUE 1
#define FALSE 0
#define FIRST 1
#define NEXT 0void dispenv(void);
int find_env_string(int restart);char env_string[MAX+2];
int env_offset=0;void main()
{
char *path_string; /*调用子程序dispenv(),以与SET命令相同的格式打印出当前环境字符串*/
dispenv(); /*在环境变量中寻找PATH变量，并打印出整个论经字符串*/
if(path_string==getenv(&quot;PATH&quot;))
printf(&quot;\nPATH=%s&quot;,path_string);
}
/********************************************/void dispenv()
{
int restart; /*在for循环中，先置restart为FIRST，调用子程序find_env_string()
寻找第一个环境字符串并打印，而后置restart为NEXT,继续寻找下一
个环境字符串，直到结束子程序find_env_string()返回FALSE为止*/
for(restart=FIRST;find_env_string(restart);restart=NEXT)
printf(&quot;\n%s&quot;,env_string);
}
/********************************************/int find_env_string(int restart)
{
int buf_offset;
int environment;
if(!environment) /*将环境空间短地址送入变量environment中*/
environment=peek(_psp,0x2c);
if(restart)
environment=0; /*首次调用时，偏移量置0，即从环境空间起点开始*/
for(buf_offset=0;TRUE;env_offset++){
env_string[buf_offset]=peekb(environment,env_offset);
/*将环境空间中的字符逐个读入数组env_string中
if(env_string[buf_offset]){/*若为空字符，继续*/
buf_offset++;
continue;
}
if(!buf_offset) /*若buf_offset=0,则中个环境结束，回FALSE*/
return FALSE;
env_offset++;/*若为空字符，则一个环境字符串结束，回TRUE*/
return TRUE;
}
}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>算术编码用c++的实现</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16855</link><subject>算术编码在图象数据压缩标准（如jpeg，jbig）中扮演了重要的角色。在算术编码中，消息用0到1之间的实数进行编码。算术编码用到了两个基本的参数：符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率，也决定编码过程中信源符号的间隔，而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。     算术编码需要输入的是符号，各个符号的概率还有需要编码的符号序列，根据概率可以算出初始编码间隔，先设几个变量在后面可用：High——当前编码的上限，Low——当前编码的下限，high——中间变量，用来计算下一个编码符号的当前间隔的上限，low——中间变量，用来计算下一个编码符号的当前间隔的下限，d——当前间隔之间的距离。第1个编码符号的当前间隔为其初始的编码间隔，第i个编码符号的当前间隔为第i-1个编码后的[Low，High），第i+1个编码符号的当前间隔算法如下：high=Low+d*第i+1个初始编码符号对应的上限，low=Low+d*第i+1个编码符号对应的下限，然后High=high，Low=low，d=d*第i个编码符号的概率。编码程序如下：#include &lt;iostream.h&gt;
#define M 100
#define N 4
class suanshu
{ int count,length;char number[N],n;long double chance[N],c;char code[M];long double High,Low,high,low,d;
public:suanshu()
  {High=0;Low=0;}void get_number();void get_code();void coding();~suanshu(){}
};void suanshu::get_number()
{cout&lt;&lt;&quot;please input the number and its chance.&quot;&lt;&lt;endl;for(int i=0;i&lt;N;i++){
  cin&gt;&gt;n&gt;&gt;c;
  number[i]=n;
  chance[i]=c;}if(i==20)
  cout&lt;&lt;&quot;the number is full.&quot;&lt;&lt;endl;count=i;
}void suanshu::get_code()
{cout&lt;&lt;&quot;please input the code''s length:&quot;;cin&gt;&gt;length;while(length&gt;=M){
  cout&lt;&lt;&quot;the length is too larger,please input a smaller one.&quot;;
  cin&gt;&gt;length;}for(int i=0;i&lt;length;i++){
  cin&gt;&gt;code[i];}
}void suanshu::coding()
{int i,j=0;for(i=0;i&lt;count;i++)
  if(code[0]==number[i]) break;while(j&lt;i)
  Low+=chance[j++];d=chance[j];High=Low+d;for(i=1;i&lt;length;i++)
  for(j=0;j&lt;count;j++)
  {
   if(code[i]==number[j])
   {
    if(j==0)
    {
     low=Low;
     high=Low+chance[j]*d;
     High=high;
     d*=chance[j];
    }
    else
    {
     float chance_l=0.0;
     for(int k=0;k&lt;=j-1;k++)
      chance_l+=chance[k];
     low=Low+d*chance_l;
     high=Low+d*(chance_l+chance[j]);
     Low=low;
     High=high;
     d*=chance[j];
    }
   }
   else continue;
  }cout&lt;&lt;&quot;the result is:&quot;&lt;&lt;Low&lt;&lt;endl;
}int main()
{suanshu a;a.get_number();a.get_code();a.coding();return 0;
}本程序在VC6.0和xp专业版下运行通过，这是我个人第一次用c++写的比较完整的程序，还有些不尽人意的地方，比如变量和函数命名不太专业，以后会注意，慢慢也会好的。呵呵~</subject><description><![CDATA[算术编码在图象数据压缩标准（如jpeg，jbig）中扮演了重要的角色。在算术编码中，消息用0到1之间的实数进行编码。算术编码用到了两个基本的参数：符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率，也决定编码过程中信源符号的间隔，而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。     算术编码需要输入的是符号，各个符号的概率还有需要编码的符号序列，根据概率可以算出初始编码间隔，先设几个变量在后面可用：High——当前编码的上限，Low——当前编码的下限，high——中间变量，用来计算下一个编码符号的当前间隔的上限，low——中间变量，用来计算下一个编码符号的当前间隔的下限，d——当前间隔之间的距离。第1个编码符号的当前间隔为其初始的编码间隔，第i个编码符号的当前间隔为第i-1个编码后的[Low，High），第i+1个编码符号的当前间隔算法如下：high=Low+d*第i+1个初始编码符号对应的上限，low=Low+d*第i+1个编码符号对应的下限，然后High=high，Low=low，d=d*第i个编码符号的概率。编码程序如下：#include &lt;iostream.h&gt;
#define M 100
#define N 4
class suanshu
{ int count,length;char number[N],n;long double chance[N],c;char code[M];long double High,Low,high,low,d;
public:suanshu()
  {High=0;Low=0;}void get_number();void get_code();void coding();~suanshu(){}
};void suanshu::get_number()
{cout&lt;&lt;&quot;please input the number and its chance.&quot;&lt;&lt;endl;for(int i=0;i&lt;N;i++){
  cin&gt;&gt;n&gt;&gt;c;
  number[i]=n;
  chance[i]=c;}if(i==20)
  cout&lt;&lt;&quot;the number is full.&quot;&lt;&lt;endl;count=i;
}void suanshu::get_code()
{cout&lt;&lt;&quot;please input the code''s length:&quot;;cin&gt;&gt;length;while(length&gt;=M){
  cout&lt;&lt;&quot;the length is too larger,please input a smaller one.&quot;;
  cin&gt;&gt;length;}for(int i=0;i&lt;length;i++){
  cin&gt;&gt;code[i];}
}void suanshu::coding()
{int i,j=0;for(i=0;i&lt;count;i++)
  if(code[0]==number[i]) break;while(j&lt;i)
  Low+=chance[j++];d=chance[j];High=Low+d;for(i=1;i&lt;length;i++)
  for(j=0;j&lt;count;j++)
  {
   if(code[i]==number[j])
   {
    if(j==0)
    {
     low=Low;
     high=Low+chance[j]*d;
     High=high;
     d*=chance[j];
    }
    else
    {
     float chance_l=0.0;
     for(int k=0;k&lt;=j-1;k++)
      chance_l+=chance[k];
     low=Low+d*chance_l;
     high=Low+d*(chance_l+chance[j]);
     Low=low;
     High=high;
     d*=chance[j];
    }
   }
   else continue;
  }cout&lt;&lt;&quot;the result is:&quot;&lt;&lt;Low&lt;&lt;endl;
}int main()
{suanshu a;a.get_number();a.get_code();a.coding();return 0;
}本程序在VC6.0和xp专业版下运行通过，这是我个人第一次用c++写的比较完整的程序，还有些不尽人意的地方，比如变量和函数命名不太专业，以后会注意，慢慢也会好的。呵呵~]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C++程序设计之四书五经</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16862</link><subject>C++是一门广泛用于工业软件研发的大型语言。它自身的复杂性和解决现实问题的能力，使其极具学术研究价值和工业价值。和C语言一样，C++已经在许多重要的领域大获成功。 然而，一个不可否认的现实是，在低阶程序设计领域，C++挤压着C同时也在承受着C的强烈反弹，而在高阶程序设计领域，Java和C#正在不断蚕食着C++的地盘。也许C++与C合为一体永远都是一个梦想，也许Java和C#的狂潮终将迫使C++回归本位 — 回到它有着根本性优势的开发领域：低级系统程序设计、高级大规模高性能应用设计、嵌入式程序设计以及数值科学计算等。果真如此，我认为这未尝不是一件好事。 C++吸引如此之多的智力投入，以至于这个领域的优秀作品，包括重量级的软件产品、程序库以及书籍等，数不胜数。文题“C++程序设计之四书五经”一个不太严格的含义是：C++程序设计之四书 ⅹ 五经。是的，在本文（及其下篇）中，我将分门别类推荐20多本C++好书，你可以根据自己的需要选读。 TCPL和D&amp;E TCPL和D&amp;E分别是《The C++ Programming Language》和《The Design and Evolution of C++》的简称，均出自Bjarne Stroustrup之手。我将它们单列出来，首先是因为Bjarne是C++语言的创建者，然后是因为比“首先”那个原因更重要的原因：这两本书是C++领域毋庸置疑的杰作。说它们是C++语言圣经，并不为过。 Bjarne Stroustrup, The C++ Programming Language (Special 3rd Edition) 《C++程序设计语言（特别版）》，机械工业出版社 《C++程序设计语言（特别版）（英文影印版）》，高等教育出版社 迄今为止，TCPL是除了C++标准文献之外最权威的C++参考手册。和大多数人的看法不大一样，我认为Bjarne的文字语言并不逊色于他所创建的程序语言，至少我喜欢这种学院气息浓厚的作品。本书对C++语言的描述轮廓鲜明、直截了当。它从C++语言创建者的角度来观察C++，这是任何别的作者和书籍做不到的 — 没有任何人比Bjarne自己更清楚该怎么来使用C++。 这是一本严肃的著作，以中、高级C++开发人员为目标读者。如果你是一名有经验的C++程序员，需要了解更加本质的C++知识，本书正是为你而写。它不是那种让你看了会不断窃喜的小书，需要用心体会，反复咀嚼。在阅读过程中，请特别留心Bjarne先生强调了什么，又对什么一语带过。我个人比较喜欢这本书的第四部分“使用C++做设计”，这样的内容在类似的程序设计语言书籍中很难看到 — 我甚至认为Bjarne应该将这部分独立出来单独写一本书。 Bjarne Stroustrup, The Design and Evolution of C++ 《C++语言的设计和演化》，机械工业出版社 《C++语言的设计和演化（英文版）》，机械工业出版社 D&amp;E是一本关于C++语言设计原理、设计决策和设计哲学的专著。它清晰地回答了C++为什么会成为今天这个样子而没有变成另外一种语言。作为C++语言的创建者，Bjarne淋漓尽致地展示了他独到而深刻的见解。除了广受赞誉的语言特性外，Bjarne没有回避那些引起争议的甚至被拒绝的C++特性，他一一给出了逻辑严密、令人信服的解释。内容涵盖C++的史前时代、带类的C、C++的设计规则、标准化、库、内存管理、多重继承、模板等，对包括异常机制、运行时类型信息和名字空间在内的重要的新特性都分别进行了深入探讨。每一名C++程序员都应该可以从Bjarne的阐释中加深对手中这门语言的认识。 需要再次提醒的是，这两本书知识浓缩，信息量极大，请不要错过Bjarne每一句看似漫不经意的话。 入门教程 学习任何一门语言都需要一个从入门到精通、从新手到高手循序渐进的过程。不过，对于一个所谓的新手而言，究竟是一个完完全全的新手，还是一个熟悉某种别的语言的“新手”，甚至是在某种语言程序设计领域已经颇有建树的高手，很难一概而论？不同的C++新手需要不同的入门书籍。 Andrew Koenig, Barbara E. Moo, Accelerated C++: Practical Programming by Example 《Accelerated C++中文版》，中国电力出版社 和市面上大多数C++教程不同，本书不是从“C++中的C”开始讲解，而是始于地道的C++特性。从一开始就使用标准库来写程序，随着讲述的逐渐深入，又一一解释这些标准库组件所依赖的基础概念。另外，和其他C++教材不同的是，这本书以实例拉动语言和标准库的讲解，对后两者的讲解是为了给实例程序提供支持，而不是像绝大多数C++教材那样，例子只是用作演示语言特性和标准库用法的辅助工具。 作者在C++领域的编程实践、教育培训以及技术写作方面都是世界一流水准。我喜欢这种大量使用标准库和C++语言原生特性的清新的写作风格。在这本教材面前，几乎迄今为止的所有C++教材都黯然失色或显得过时。尽管这本教材也许对于国内的高校教育来说有些前卫，不过我仍然极力向我的同行们推荐。顺带一提，在Bjarne和我最近的一封通信里，他这样评价本书：对于有经验的程序员学习C++而言，这本书可能是世界上最好的一本。 Stanley B.Lippman, Josee Lajoie, C++ Primer (3rd Edition) 《C++ Primer (3RD)中文版》，中国电力出版社 这本书的名字多少有点让人误解。尽管作者声称这本书是为C++新手而写，但无论是它的厚度还是讲解的深度都暴露了似乎并非如此。也许说它是一本“从入门到精通”的C++教程会更合适一些。我个人认为它并不适合完全不懂C++的初学者 — 在阅读这本书之前，你至少应该先有那么一点C或C++的背景知识，或者至少要具有一些其他语言的编程经验。 尽管这本书省略了一些高级C++特性的讨论，但仍然可以称得上是迄今为止最全面的C++学习教程。事实上，如果一名C++初学者能够扎扎实实地读完本书并对照《C++ Primer Answer Book》完成全部习题的话，他的水平肯定可以进入职业C++程序员的行列。我个人认为，即使你已经拥有了TCPL，这本书依然有拥有的价值，因为在许多方面它比TCPL来得更详细、更易懂。 Stanley B. Lippman, Essential C++ 《Essential C++中文版》，华中科技大学出版社 《Essential C++（影印版）》，中国电力出版社 可以不太严格地认为这本书是《C++ Primer》的精简版。本书一一讲述了C++中最具代表性的主题，包括过程式编程、泛型编程、基于对象编程、面向对象编程、模板编程以及异常处理等。Stanley将门槛调低到“具有其他语言程序设计经验”的C++新手所能接受的最基本的层次，使他们能够迅速开始使用C++编程而又免于阅读《C++ Primer》那样的大部头。它以实例引导学习，力图使读者在最短的时间内把握C++的精粹。 也许换一个人来概述C++编程范型（paradigm）的方方面面需要好几百页才能说清楚，但这本小书不可思议地做到了这一点。我个人非常喜欢这种满是技术、简明扼要并且“有话好好说”的书。这本书同样具有一个明显的风格：所有程序例子全部采用标准库组件，让人耳目一新。 以上三本书都不是为了完完全全的编程新手而写。完全的C++编程新手可以阅读Francis Glassborow的新书（尚未出版）：《A Beginners Introduction to Computer Programming : You Can Do It!》。这也是Bjarne的推荐。Francis Glassborow是ACCU主席，多年来他对几乎每一本C++经典名著评头论足，他自己的这一本自然会引起C++社群的极大兴趣。 高效、健壮编程 两年前我在负责一个省级电力调度系统项目时编写了一个网关程序，它从SCADA系统获取电力实时信息。通讯接口采用了不常用的数据库直连方式（这个网关程序一端连接SQL Server 6.5，另一端连接Oralce 8.1.6）。由于实时测点近万，每次将全部取样更新或插入一遍显然是低效的。我在网关程序里建了一个内存库，获取到的数据首先在其中进行比较，然后决定是否更新物理数据库（同时还做了别的更复杂的事情……），从而在效率和资源占用两方面达到了预期效果。 这个程序一直运行得很好，但在离开现场之后的某一天，系统管理员打来电话，说大概因为网络故障等原因，有时这个网关程序会崩溃掉 — 它自己崩掉也就罢了，问题是它还会把Windows 2000 Advanced Server搞成“蓝屏”！坦白地说，我还从来没看过哪个非蓄意的程序有这个“能耐”。由于当时正忙于另外一个大项目，无法去现场调试，最后只有凭经验对内存库代码小心翼翼地封装以异常处理代码（同时也做了一些别的修改……）。这样，虽然没有彻底解决问题，但程序终究不再死得那么难看了。 在这儿讲这么一段花絮有什么意思呢（当初为那个可怕的bug朝思暮想时我可不认为这是一个“花絮”）？我想说的是，对于任何软件而言，离开强健，效率也就无从谈起。而对于C++程序员来说，也许编写一个高效的程序并不难，但要编写一个需要7 ⅹ 24小时持续运行的服务端软件就不是那么容易了，需要考虑许多因素，有时这些因素甚至远远超出C++语言和开发工具的本身。作为一名开发实际项目软件的程序员，并非非得自己碰钉子才能积累经验，只要我们足够虚心，别人的经验往往都是我们很好的借鉴。鉴于此，我推荐以下几本书供你选读，它们可以让你从强健和效率两方面受益（当然了，它们涵盖的内容远不限于异常处理J）。 Scott Meyers, Effective C++: 50 Specific Ways to Improve Your Programs and Design (2nd Edition) Scott Meyers, More Effective C++: 35 New Ways to Improve Your Programs and Designs 《Effective C++中文版》，华中科技大学出版社 《More Effective C++中文版》，中国电力出版社 《Effective C++（影印版）》，中国电力出版社 如果说《Effective C++》主要讨论C++中一些相对基础的概念和技巧的话，那么《More Effective C++》则着重探讨了包括异常处理在内的一系列高级技术。与前者相比，后者具有两大主要区别：其一，它包含很多时新的标准C++的内容；第二，它讨论的主题倾向于“战略化”而非“战术化”，并且讨论得更深入、更彻底。尤其是对虚析构函数、智能指针、引用计数以及代理类（proxy classe）等技术和模式论述的深入程度，让人很难想象是出现于这样的一本小书之中。 游刃有余的技术，高超的写作技巧，Scott无疑是世界上最优秀的C++技术作家之一。在简洁、清晰、易读等方面，这两本书都卓尔不群。总之，Scott提供的这85个可以改善编程技术和设计思维的方法，都是中、高级C++程序员必备的技能。我强烈推荐这两本书（实际上还有一本，稍后就会看到）。 Herb Sutter, Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions Herb Sutter, More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions 《Exceptional C++中文版》，中国电力出版社 《More Exceptional C++中文版》，华中科技大学出版社 你自认为是一名C++语言专家吗？读一读ISO C++标准委员会秘书长的这两本书再回答。在这两本书中，Herb采用了“问答”的方式指导你学习C++语言特性。对于每一个专题，Herb首先合理地设想出你的疑问和困惑，接着又猜测出你十有八九是错误的解答，然后给你以指点并提出最佳解决方案，最后还归纳出解决类似问题的普适性原则。 这两本书是典型的深究C++语言细节的著作，很薄，但内容密集，远远超过Scott的那两本书，读起来很费脑筋 — 我个人认为它们要比Scott的书难懂得多。若要研习这薄薄的两本书所包含的知识，至少需要花费数月的时间！（在Scott的荐序中，他坦陈不止一次陷入GotW问题的陷阱，你应该知道这意味着什么）对于语言细节的深究有什么好处呢？尽管在大多数情况下，我们不必关心C++代码幕后的动作，然而当我们不得不关心时，这两本书可以为我们提供很好的线索，因为它们揭示了C++语言中微妙而又至关重要的东西。 Stephen C. Dewhurst, C++ Gotchas: Avoiding Common Problems in Coding and Design 《C++程序设计陷阱》，中国青年出版社 Stephen的理论素养和实践经验注定这是一本值得一读的好书。Stephen曾经是贝尔实验室中第一批C++使用者。他已经使用C++成功解决了包括编译器、证券交易、电子商务以及嵌入式系统等领域中的问题。本书汇集了作者来自开发一线的99条编程真知灼见，洞悉它们，你可以避免几乎所有常见的C++设计和编程问题。 我甚至认为，对于C++编程菜鸟而言，阅读这本书会比阅读Scott和Herb的书更能轻松而立竿见影地获得更大的提高。我个人很喜欢这本书的写作风格 — Stephen的许多观点看似极端却无可辩驳。当然了，这种自信（以及冷幽默）来自于作者深厚的技术素养，而非自大的偏执。 除了上面推荐的书籍外，Dov Bulka和 David Mayhew合著的《Efficient C++: Performance Programming Techniques》（《提高C++性能的编程技术》，清华大学出版社）也值得一看。这本超薄小书聚焦于高性能C++应用程序开发。两位作者都是IBM软件专家，都工作于对性能要求极高的系统构建领域，本书是他们的经验之谈。也有人不喜欢这本书，因为它花了不少的篇幅讲述和C++无关的东西，我却恰恰因为这一点而对这本书产生好感，正是这些东西让我开阔了眼界。 模板和泛型编程 模板和基于模板的泛型编程无疑是当今发展最活跃的C++程序设计技术。模板的第一个革命性的应用是STL，它将模板技术在泛型容器和算法领域的运用展现得淋漓尽致，而Boost、Loki等现代程序库则将模板技术的潜能不断发挥到极致。在模板和泛型编程领域，我推荐以下两本重量级著作： David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide 《C++ Templates全览（繁体版）》，台湾碁峰资讯股份有限公司 《C++ Templates全览（简体版）》，人民邮电出版社 有一种老套的赞美一本书的手法，大致是“没有看过这本书，你就怎么怎么地”，这里面往往夸张的成分居多。不过，倘若说“没有看过《C++ Templates: The Complete Guide》，你就不可能精通C++模板编程”，那么这个论断对于世界上绝大多数C++程序员来说是成立的。 这本书填补了C++模板书籍领域由来已久的空白。此前，上有《Modern C++ Design》这样的专注于模板高级编程技术和泛型模式的著作，下有《The C++ Standard Library》这样的针对特定模板框架和组件的使用指南。然而，假如对模板机制缺乏深入的理解，你就很难“上下”自如。鉴于此，我向每一位渴望透彻理解C++模板技术的朋友推荐这本书。 这本书在内地、台湾各有一个译本，但出自不同的译者之手。当你看到这篇文章时，两个译本应该都已经上市，对于读者来说当然也就多了一种选择。侯捷先生个人网站上开放了繁体译本大部分章节，不妨先睹为快。 Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied 《C++设计新思维：泛型编程与设计模式之应用》，华中科技大学出版社 《C++设计新思维（影印版）》，中国电力出版社 你自认为是C++模板编程高手吗？请看过这本书再回答J 这是一本出自天才之手令人敬畏的杰作。泛型模式，无限延伸你的视野，足以挑战任何一名C++程序员的思维极限。 这本书共分为两大部分，第一部分讨论了 Loki程序库采用的基础技术以及一些高级语言特性，包括基于策略的类设计、模板局部特化、编译期断言、Typelist以及小型对象分配技术等。第二部分则着重介绍了Loki中的重要组件和泛型模式技术，包括泛化仿函数（Generalization Functor）、单件（Singleton）、智能指针、对象工厂（Object Factory）、抽象工厂（Abstract Factory）、访问者（Visitor）以及多方法（Multimethods）等。每一种技术都让人大开眼界，叹为观止。 在C++的学习方面，过犹不及往往成了不求甚解的借口。然而，面向对象并非C++的全部，模板和泛型编程亦占半壁江山。对于“严肃”的C++程序员而言，及时跟进这项早经例证的成功技术，不失为明智之举。 结语 这些著作是如此大名鼎鼎，也许根本不缺我一个推荐。然而，纵然C++程序员队伍的发展壮大速度不像其他更时髦的语言那样迅速，新人进总是多于旧人出。除了热忱地欢迎新人，我个人认为到了对C++书籍进行“盘点”的时候了，并且希望这样的“盘点”有益于感兴趣的读者。请保持耐心和宽厚。在下篇中，我将继续介绍标准库、网络编程以及其他方面的C++好书。有好书相伴，这个冬天不会冷。 C++程序设计之四书五经（下篇） 我在上篇中“盘点”了TCPL和D&amp;E以及入门教程、高效和健壮编程、模板和泛型编程等方面共十几本C++好书。冬去春来，让我们继续C++书籍精彩之旅J 标准库 当我还在研究院工作时，与同院另外两家研究所合作开发过一个大型水利枢纽调度集成项目。我们三家软件系统之间都要相互通信。在调试通讯模块时，细心的客户（一名好学的系统管理员）发现对于同一通信规约的解释代码，我的不超过30行，而对方的则超过了150行且很难看懂。这位系统管理员很纳闷，我说大家编程风格和习惯不一样，我使用了标准库，而他使用了传统C编程风格以及他所习惯的另外一些技术。 别误会！我绝无贬低这位合作伙伴的意思。事实上，我对那些真正有着深厚的C编程功力的程序员常常怀有钦佩之心。毕竟，C++能有今天的成功在很大程度上缘于它深深地植根于C。作为一名C++程序员，倘若不熟悉C++中的C，我往往会认为他的基本功是不扎实的，他的技术底气是不足的。 不过话又说回来，C++是一种多范型（paradigm）编程语言，具体采用哪种编程风格，专业程序员应该知道视具体情况而定。作为一名经常需要在现场做即兴开发的项目负责人，为了短平快地解决当务之急，我习惯尽量采用现有的库（和组件）。效率（以及强健性）久经验证的C++标准库已经摆在那儿了，何乐而不用呢？ Nicolai M. Josuttis, The C++ Standard Library: A Tutorial and Reference 《C++标准程序库：自修教程与参考手册》，华中科技大学出版社 这是一本百科全书式的C++标准库著作，是一本需要一再查阅的参考大全。它在完备性、细致性以及精确性方面都是无与伦比的。本书详细介绍了每一标准库组件的规格和用法，内容涵盖包括流和本地化在内的整个标准库而不仅仅是STL。正如本书副标题所示，它首先适合作为教程阅读，尔后又可用作参考手册。 浅显易懂的写作风格使得这本书非常易读。如果你希望学习标准库的用法并尽可能地发挥其潜能，那你必须拥有这本书。正如网络上所言，这本书不仅仅应该摆在你的书橱中，更应该放到你的电脑桌上。我向每一位职业C++程序员强烈推荐。 Angelika Langer, Klaus Kreft, Standard C++ IOStreams and Locales: Advanced Programmer''s Guide and Reference 《标准C++输入输出流与本地化》，人民邮电出版社 C++标准库由STL、流和本地化三部分构成。关于STL的书市面上已经有不少，但罕见流和本地化方面的专著。本书是这两个领域中最优秀的一本，迄今为止没有任何一本书比这一本更全面详尽地讨论了流和本地化。如果你不满足于停留在“会用”流库的层面，千万不要错过它。 2001年夏天，我草草翻阅过这本书的中文版，从内容到包装都给我留下了比较深刻的印象 — 不过负面的居多一些。2003年秋天，无意中得知某网络书店正以超低价格甩卖这本书的中译本，情不自禁，一阵唏嘘。 Scott Meyers, Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library 《Effective STL（影印版）》，中国电力出版社 读完Scott 的《Effective C++》和《More Effective C++》的中译本之后，我一直期待这本书的中文版。我从潘爱民先生的个人主页上了解到，他和他的合作伙伴似乎早已完成了这本书的翻译工作，可惜至今市面上仍不得见。幸运的是，我们可以看到它的原版。 本书是使用STL的程序员必读之作。在这本书中，Scott向我们讲述STL容器和算法的工作机制以及如何以最佳方式使用它们。和Scott的其他作品一样，这本书的写作风格清晰、精确，具有极佳的可读性。看过这本书以后，我想你也许会和我以及其他C++程序员一样产生这样的想法：Scott什么时候会写出一本“More Effective STL”？ 关于STL，我还提醒你留心Matthew H. Austern的《Generic Programming and the STL: Using and Extending the C++ Standard Template Library》（《泛型编程与STL》，中国电力出版社）。这本书散发着浓厚的学院气息。Andrew Koenig和Barbara Moo在《Accelerated C++: Practical Programming by Example》一书末尾郑重推荐另外两本进阶好书（除了他们自己的《Ruminations on C++》外），其中一本是TCPL，另外一本就是本书！ 网络编程 在网络编程时代，C++应该扮演着怎样的角色，让ACE（Adaptive Communications Environment）来告诉你。 Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 1: Mastering Complexity with ACE and Patterns Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 2: Systematic Reuse with ACE and Frameworks 《C++网络编程，卷1：运用ACE和模式消除复杂性》，华中科技大学出版社 《C++网络编程，卷2：基于 ACE 和框架的系统化复用》，电子工业出版社 采用C++进行企业级网络编程，目前ACE（以及这两本书）是一个值得考虑的选择。ACE是一个面向对象、跨平台、开放源码的网络编程框架，目标在于构建高性能网络应用和中间件。Douglas是ACE的创始人，Stephen则已为ACE提供了数年的技术支持和顾问服务，两位都是ACE社群（是的，ACE的影响和实际应用的程度已经形成了一个社群）的专家。 ACE并不单单被大学和研究所追捧，它已经被成功地应用于世界上成千上万个商业应用中。在电信、宇航、医药和财经领域的网络系统中，ACE已经并继续发挥着重要的作用。如果你准备开发高性能通讯系统，你应该考虑考虑这一汇集世界顶尖专家智慧的成果。 除了使用C++面向对象设计技术和模板等高级语言特性外，ACE还运用了大量的模式。《C++网络编程》卷1和卷2并不仅仅教你关于ACE的方方面面，它还会教给你模式和通用框架设计等高级技术等。所以，作为一名中、高级C++程序员，即使你很少进行正儿八经的C++网络程序设计，阅读这两本书同样可以从中受益。 是的，并非所有网络应用都要使用Web服务器（以及其他应用服务器）和重量级组件模型，换个思路，它们或许也可以从轻量级的ACE组件中获益。 杂项 以下这几本书之所以被列入“杂项”单元，只是因为我没有考虑出更合适的归类方法，它们和上面的书籍一样，值得一读。 Bruce Eckel, Thinking in C++, Volume 1: Introduction to Standard C++ (2nd Edition) Bruce Eckel, Thinking in C++, Volume 2: Practical Programming (Second Edition) 《C++编程思想（第2版）第1卷：标准C++导引》，机械工业出版社 《C++编程思想（英文版 第2版）》，机械工业出版社 《Thinking in C++》的第1版于1996年荣获“软件研发”杂志评选的图书震撼大奖。最新推出的第2版对内容进行了大幅改写和调整，以反映C++标准化带来的影响以及近几年面向对象领域最新研究和实践成果。“输入输入流”、“多重继承”、“异常处理”和“运行时类型识别”等高级主题连同C++标准化以后增加的一些内容则被放入第二卷中。Bruce是一名经验丰富的C++讲师和顾问，其培训和写作经验都是世界一流水准，他的作品比那些“玩票”的技术人员写的东西更能吸引读者。事实上，在同类图书中，对于大多数读者而言，这本书的可读性要超过TCPL和《C++ Primer》。顺带一提，访问作者的站点，你可以先睹第二卷的风采。 Andrew Koenig, Barbara E. Moo, Ruminations on C++: A Decade of Programming Insight and Experience 《C++沉思录》，人民邮电出版社 Andrew是世界上屈指可数的C++专家。这是一本关于C++编程思想和程序设计技术而非语言细节的著作。如果你已经具有一定的基础，这本书将教你在进行C++编程时应该怎样思考，应该如何表达解决方案。整本书技术表达透彻，文字通俗易懂。Bjarne这样评价这本书：本书遍布“C++是什么、C++能够做什么”的真知灼见。 Stanley B. Lippman, Inside The C++ Object Model 《深度探索C++对象模型》，华中科技大学出版社 《深度探索C++对象模型（影印版）》，中国电力出版社 从编译器的角度观察C++可以使你知其然并知其所以然。本书探讨了大量的C++面向对象程序设计的底层运作机制，包括构造函数、函数、临时对象、继承、虚拟、模板的实例化、异常处理、运行期类型识别等，另外还介绍了一些在实现C++对象模型过程中做出的权衡折衷。喜欢刨根问底的C++程序员不要错过这本书。 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented software 《设计模式：可复用面向对象软件的基础》，机械工业出版社 《设计模式：可复用面向对象软件的基础（英文版）》，机械工业出版社 设计可复用的面向对象的软件，你需要掌握设计模式。本书并非专为C++程序员而写，但它采用了C++（以及Smalltalk）作为主要示例语言，C++程序员尤其易于从中受益。四位作者都是国际公认的面向对象软件领域专家，他们将面向对象软件的设计经验作为设计模式详细记录下来。这本书影响是如此深远，以至于四位作者以及本书都被昵称为GoF（Gang of Four）。本书学院气息浓厚，行文风格严谨简洁，虽然它不如某些讲解模式的书籍易读，但真正要精准地理解设计模式，本书是终极权威。学习设计模式，这本书需要一而再、再而三的咀嚼。顺带一句：请将设计模式化作开拓思维的钥匙，切莫成为封闭思维的枷锁。 还有一些C++好书值得一读，恕此处无法一一列出。例如John Lakos的著作《Large-Scale C++ Software Design》（《大规模C++程序设计》，中国电力出版社）和侯捷先生的《STL 源码剖析》（华中科技大学出版社）等。 《STL 源码剖析》是一本很有特色的书，但我认为它还可以更好。我个人期待侯捷先生自第一版发行以来经过对模板技术的沉淀和再思考之后，再写一本剖析得更深入、更透彻并且更全面的“第二版”。遗憾的是，侯捷先生在完成《C++ Templates: The Complete Guide》一书的翻译后似乎决定暂时告别模板、泛型编程和STL领域。 2004年3月31日补充：我目前最常查阅的两本参考书是《C++标准程序库》和《STL源码剖析》。当然了，这与我年内的写作计划有很大的关系。 使用C++成功开发大规模软件系统，不仅需要很好地理解大多数C++书籍中讲述的逻辑设计问题，更需要掌握《大规模C++程序设计》中讲述的物理设计技术。当然，这本书的确有点过时了，不过，如果你的精力和金钱都比较宽绰，买一本看看并无坏处。 至此，我想有必要声明一下，有一些（好）书没有得到推荐，主要原因如下： 以上这些书已经足够多、足够好了。 我不会推荐通过正常渠道很难购买到的书籍 — 不管是中文版还是英文版。 作（译）者名气大小不影响我的推荐。我们是在看书，不是看人。 我不会推荐我从来没有看过的书。我至少要看过其中的某个版本（包括电子档）。这个“看”，一般指“认真阅读”，不过有一些也只能算是“浏览”。 结语 作为一名普通技术写译者，我深知技术创作和翻译的艰辛（和快乐），并多多少少了解一些有关技术书籍创作、翻译、制作、出版以及市场推介背后的细节。今天，我不会再对一本看上去差强人意的图书信口开河。罗列同一本书的各种版本的用意只在于为你多提供一些信息，让你多一种选择。 在本文成文的后期，我给Bjarne写了一封信，请教如果他来写这篇文章会怎么写。他给了我简明扼要的建议。在肯定以上列出的绝大部分图书都是世界顶尖水平的C++著作的同时，Bjarne提醒我别忘了向专家级程序员推荐《The C++ Standard : Incorporating Technical Corrigendum No. 1》。这本书是 C++标准规范的“图书版”，Bjarne亲自为之作序。 Bjarne还友好地提醒我，在我的推荐列表中没有哪一本有助于C++程序员进行Windows编程 — 这正是我的本意。在这篇文章中，我只推荐、点评平台中立的C++著作（网络编程除外） — 和操作系统无关，和集成开发环境无关，我甚至幻想它们和编译器也无关。你可以根据业务开发需要，选读自己喜爱的领域相关的C++书籍。 说到“系统无关、平台中立”，我不由得想起了“抽象层”的概念。开发实际应用的C++程序员通常工作于特定操作系统、特定开发环境和特定业务领域之中，而对标准C++和C++标准库扎实而深刻的把握，无疑是你得以在不同的操作系统、不同的开发环境以及不同的业务领域之间纵横驰骋的“抽象”本钱。 荣耀
2004年1月
南京师范大学</subject><description><![CDATA[C++是一门广泛用于工业软件研发的大型语言。它自身的复杂性和解决现实问题的能力，使其极具学术研究价值和工业价值。和C语言一样，C++已经在许多重要的领域大获成功。 然而，一个不可否认的现实是，在低阶程序设计领域，C++挤压着C同时也在承受着C的强烈反弹，而在高阶程序设计领域，Java和C#正在不断蚕食着C++的地盘。也许C++与C合为一体永远都是一个梦想，也许Java和C#的狂潮终将迫使C++回归本位 — 回到它有着根本性优势的开发领域：低级系统程序设计、高级大规模高性能应用设计、嵌入式程序设计以及数值科学计算等。果真如此，我认为这未尝不是一件好事。 C++吸引如此之多的智力投入，以至于这个领域的优秀作品，包括重量级的软件产品、程序库以及书籍等，数不胜数。文题“C++程序设计之四书五经”一个不太严格的含义是：C++程序设计之四书 ⅹ 五经。是的，在本文（及其下篇）中，我将分门别类推荐20多本C++好书，你可以根据自己的需要选读。 TCPL和D&amp;E TCPL和D&amp;E分别是《The C++ Programming Language》和《The Design and Evolution of C++》的简称，均出自Bjarne Stroustrup之手。我将它们单列出来，首先是因为Bjarne是C++语言的创建者，然后是因为比“首先”那个原因更重要的原因：这两本书是C++领域毋庸置疑的杰作。说它们是C++语言圣经，并不为过。 Bjarne Stroustrup, The C++ Programming Language (Special 3rd Edition) 《C++程序设计语言（特别版）》，机械工业出版社 《C++程序设计语言（特别版）（英文影印版）》，高等教育出版社 迄今为止，TCPL是除了C++标准文献之外最权威的C++参考手册。和大多数人的看法不大一样，我认为Bjarne的文字语言并不逊色于他所创建的程序语言，至少我喜欢这种学院气息浓厚的作品。本书对C++语言的描述轮廓鲜明、直截了当。它从C++语言创建者的角度来观察C++，这是任何别的作者和书籍做不到的 — 没有任何人比Bjarne自己更清楚该怎么来使用C++。 这是一本严肃的著作，以中、高级C++开发人员为目标读者。如果你是一名有经验的C++程序员，需要了解更加本质的C++知识，本书正是为你而写。它不是那种让你看了会不断窃喜的小书，需要用心体会，反复咀嚼。在阅读过程中，请特别留心Bjarne先生强调了什么，又对什么一语带过。我个人比较喜欢这本书的第四部分“使用C++做设计”，这样的内容在类似的程序设计语言书籍中很难看到 — 我甚至认为Bjarne应该将这部分独立出来单独写一本书。 Bjarne Stroustrup, The Design and Evolution of C++ 《C++语言的设计和演化》，机械工业出版社 《C++语言的设计和演化（英文版）》，机械工业出版社 D&amp;E是一本关于C++语言设计原理、设计决策和设计哲学的专著。它清晰地回答了C++为什么会成为今天这个样子而没有变成另外一种语言。作为C++语言的创建者，Bjarne淋漓尽致地展示了他独到而深刻的见解。除了广受赞誉的语言特性外，Bjarne没有回避那些引起争议的甚至被拒绝的C++特性，他一一给出了逻辑严密、令人信服的解释。内容涵盖C++的史前时代、带类的C、C++的设计规则、标准化、库、内存管理、多重继承、模板等，对包括异常机制、运行时类型信息和名字空间在内的重要的新特性都分别进行了深入探讨。每一名C++程序员都应该可以从Bjarne的阐释中加深对手中这门语言的认识。 需要再次提醒的是，这两本书知识浓缩，信息量极大，请不要错过Bjarne每一句看似漫不经意的话。 入门教程 学习任何一门语言都需要一个从入门到精通、从新手到高手循序渐进的过程。不过，对于一个所谓的新手而言，究竟是一个完完全全的新手，还是一个熟悉某种别的语言的“新手”，甚至是在某种语言程序设计领域已经颇有建树的高手，很难一概而论？不同的C++新手需要不同的入门书籍。 Andrew Koenig, Barbara E. Moo, Accelerated C++: Practical Programming by Example 《Accelerated C++中文版》，中国电力出版社 和市面上大多数C++教程不同，本书不是从“C++中的C”开始讲解，而是始于地道的C++特性。从一开始就使用标准库来写程序，随着讲述的逐渐深入，又一一解释这些标准库组件所依赖的基础概念。另外，和其他C++教材不同的是，这本书以实例拉动语言和标准库的讲解，对后两者的讲解是为了给实例程序提供支持，而不是像绝大多数C++教材那样，例子只是用作演示语言特性和标准库用法的辅助工具。 作者在C++领域的编程实践、教育培训以及技术写作方面都是世界一流水准。我喜欢这种大量使用标准库和C++语言原生特性的清新的写作风格。在这本教材面前，几乎迄今为止的所有C++教材都黯然失色或显得过时。尽管这本教材也许对于国内的高校教育来说有些前卫，不过我仍然极力向我的同行们推荐。顺带一提，在Bjarne和我最近的一封通信里，他这样评价本书：对于有经验的程序员学习C++而言，这本书可能是世界上最好的一本。 Stanley B.Lippman, Josee Lajoie, C++ Primer (3rd Edition) 《C++ Primer (3RD)中文版》，中国电力出版社 这本书的名字多少有点让人误解。尽管作者声称这本书是为C++新手而写，但无论是它的厚度还是讲解的深度都暴露了似乎并非如此。也许说它是一本“从入门到精通”的C++教程会更合适一些。我个人认为它并不适合完全不懂C++的初学者 — 在阅读这本书之前，你至少应该先有那么一点C或C++的背景知识，或者至少要具有一些其他语言的编程经验。 尽管这本书省略了一些高级C++特性的讨论，但仍然可以称得上是迄今为止最全面的C++学习教程。事实上，如果一名C++初学者能够扎扎实实地读完本书并对照《C++ Primer Answer Book》完成全部习题的话，他的水平肯定可以进入职业C++程序员的行列。我个人认为，即使你已经拥有了TCPL，这本书依然有拥有的价值，因为在许多方面它比TCPL来得更详细、更易懂。 Stanley B. Lippman, Essential C++ 《Essential C++中文版》，华中科技大学出版社 《Essential C++（影印版）》，中国电力出版社 可以不太严格地认为这本书是《C++ Primer》的精简版。本书一一讲述了C++中最具代表性的主题，包括过程式编程、泛型编程、基于对象编程、面向对象编程、模板编程以及异常处理等。Stanley将门槛调低到“具有其他语言程序设计经验”的C++新手所能接受的最基本的层次，使他们能够迅速开始使用C++编程而又免于阅读《C++ Primer》那样的大部头。它以实例引导学习，力图使读者在最短的时间内把握C++的精粹。 也许换一个人来概述C++编程范型（paradigm）的方方面面需要好几百页才能说清楚，但这本小书不可思议地做到了这一点。我个人非常喜欢这种满是技术、简明扼要并且“有话好好说”的书。这本书同样具有一个明显的风格：所有程序例子全部采用标准库组件，让人耳目一新。 以上三本书都不是为了完完全全的编程新手而写。完全的C++编程新手可以阅读Francis Glassborow的新书（尚未出版）：《A Beginners Introduction to Computer Programming : You Can Do It!》。这也是Bjarne的推荐。Francis Glassborow是ACCU主席，多年来他对几乎每一本C++经典名著评头论足，他自己的这一本自然会引起C++社群的极大兴趣。 高效、健壮编程 两年前我在负责一个省级电力调度系统项目时编写了一个网关程序，它从SCADA系统获取电力实时信息。通讯接口采用了不常用的数据库直连方式（这个网关程序一端连接SQL Server 6.5，另一端连接Oralce 8.1.6）。由于实时测点近万，每次将全部取样更新或插入一遍显然是低效的。我在网关程序里建了一个内存库，获取到的数据首先在其中进行比较，然后决定是否更新物理数据库（同时还做了别的更复杂的事情……），从而在效率和资源占用两方面达到了预期效果。 这个程序一直运行得很好，但在离开现场之后的某一天，系统管理员打来电话，说大概因为网络故障等原因，有时这个网关程序会崩溃掉 — 它自己崩掉也就罢了，问题是它还会把Windows 2000 Advanced Server搞成“蓝屏”！坦白地说，我还从来没看过哪个非蓄意的程序有这个“能耐”。由于当时正忙于另外一个大项目，无法去现场调试，最后只有凭经验对内存库代码小心翼翼地封装以异常处理代码（同时也做了一些别的修改……）。这样，虽然没有彻底解决问题，但程序终究不再死得那么难看了。 在这儿讲这么一段花絮有什么意思呢（当初为那个可怕的bug朝思暮想时我可不认为这是一个“花絮”）？我想说的是，对于任何软件而言，离开强健，效率也就无从谈起。而对于C++程序员来说，也许编写一个高效的程序并不难，但要编写一个需要7 ⅹ 24小时持续运行的服务端软件就不是那么容易了，需要考虑许多因素，有时这些因素甚至远远超出C++语言和开发工具的本身。作为一名开发实际项目软件的程序员，并非非得自己碰钉子才能积累经验，只要我们足够虚心，别人的经验往往都是我们很好的借鉴。鉴于此，我推荐以下几本书供你选读，它们可以让你从强健和效率两方面受益（当然了，它们涵盖的内容远不限于异常处理J）。 Scott Meyers, Effective C++: 50 Specific Ways to Improve Your Programs and Design (2nd Edition) Scott Meyers, More Effective C++: 35 New Ways to Improve Your Programs and Designs 《Effective C++中文版》，华中科技大学出版社 《More Effective C++中文版》，中国电力出版社 《Effective C++（影印版）》，中国电力出版社 如果说《Effective C++》主要讨论C++中一些相对基础的概念和技巧的话，那么《More Effective C++》则着重探讨了包括异常处理在内的一系列高级技术。与前者相比，后者具有两大主要区别：其一，它包含很多时新的标准C++的内容；第二，它讨论的主题倾向于“战略化”而非“战术化”，并且讨论得更深入、更彻底。尤其是对虚析构函数、智能指针、引用计数以及代理类（proxy classe）等技术和模式论述的深入程度，让人很难想象是出现于这样的一本小书之中。 游刃有余的技术，高超的写作技巧，Scott无疑是世界上最优秀的C++技术作家之一。在简洁、清晰、易读等方面，这两本书都卓尔不群。总之，Scott提供的这85个可以改善编程技术和设计思维的方法，都是中、高级C++程序员必备的技能。我强烈推荐这两本书（实际上还有一本，稍后就会看到）。 Herb Sutter, Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions Herb Sutter, More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions 《Exceptional C++中文版》，中国电力出版社 《More Exceptional C++中文版》，华中科技大学出版社 你自认为是一名C++语言专家吗？读一读ISO C++标准委员会秘书长的这两本书再回答。在这两本书中，Herb采用了“问答”的方式指导你学习C++语言特性。对于每一个专题，Herb首先合理地设想出你的疑问和困惑，接着又猜测出你十有八九是错误的解答，然后给你以指点并提出最佳解决方案，最后还归纳出解决类似问题的普适性原则。 这两本书是典型的深究C++语言细节的著作，很薄，但内容密集，远远超过Scott的那两本书，读起来很费脑筋 — 我个人认为它们要比Scott的书难懂得多。若要研习这薄薄的两本书所包含的知识，至少需要花费数月的时间！（在Scott的荐序中，他坦陈不止一次陷入GotW问题的陷阱，你应该知道这意味着什么）对于语言细节的深究有什么好处呢？尽管在大多数情况下，我们不必关心C++代码幕后的动作，然而当我们不得不关心时，这两本书可以为我们提供很好的线索，因为它们揭示了C++语言中微妙而又至关重要的东西。 Stephen C. Dewhurst, C++ Gotchas: Avoiding Common Problems in Coding and Design 《C++程序设计陷阱》，中国青年出版社 Stephen的理论素养和实践经验注定这是一本值得一读的好书。Stephen曾经是贝尔实验室中第一批C++使用者。他已经使用C++成功解决了包括编译器、证券交易、电子商务以及嵌入式系统等领域中的问题。本书汇集了作者来自开发一线的99条编程真知灼见，洞悉它们，你可以避免几乎所有常见的C++设计和编程问题。 我甚至认为，对于C++编程菜鸟而言，阅读这本书会比阅读Scott和Herb的书更能轻松而立竿见影地获得更大的提高。我个人很喜欢这本书的写作风格 — Stephen的许多观点看似极端却无可辩驳。当然了，这种自信（以及冷幽默）来自于作者深厚的技术素养，而非自大的偏执。 除了上面推荐的书籍外，Dov Bulka和 David Mayhew合著的《Efficient C++: Performance Programming Techniques》（《提高C++性能的编程技术》，清华大学出版社）也值得一看。这本超薄小书聚焦于高性能C++应用程序开发。两位作者都是IBM软件专家，都工作于对性能要求极高的系统构建领域，本书是他们的经验之谈。也有人不喜欢这本书，因为它花了不少的篇幅讲述和C++无关的东西，我却恰恰因为这一点而对这本书产生好感，正是这些东西让我开阔了眼界。 模板和泛型编程 模板和基于模板的泛型编程无疑是当今发展最活跃的C++程序设计技术。模板的第一个革命性的应用是STL，它将模板技术在泛型容器和算法领域的运用展现得淋漓尽致，而Boost、Loki等现代程序库则将模板技术的潜能不断发挥到极致。在模板和泛型编程领域，我推荐以下两本重量级著作： David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide 《C++ Templates全览（繁体版）》，台湾碁峰资讯股份有限公司 《C++ Templates全览（简体版）》，人民邮电出版社 有一种老套的赞美一本书的手法，大致是“没有看过这本书，你就怎么怎么地”，这里面往往夸张的成分居多。不过，倘若说“没有看过《C++ Templates: The Complete Guide》，你就不可能精通C++模板编程”，那么这个论断对于世界上绝大多数C++程序员来说是成立的。 这本书填补了C++模板书籍领域由来已久的空白。此前，上有《Modern C++ Design》这样的专注于模板高级编程技术和泛型模式的著作，下有《The C++ Standard Library》这样的针对特定模板框架和组件的使用指南。然而，假如对模板机制缺乏深入的理解，你就很难“上下”自如。鉴于此，我向每一位渴望透彻理解C++模板技术的朋友推荐这本书。 这本书在内地、台湾各有一个译本，但出自不同的译者之手。当你看到这篇文章时，两个译本应该都已经上市，对于读者来说当然也就多了一种选择。侯捷先生个人网站上开放了繁体译本大部分章节，不妨先睹为快。 Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied 《C++设计新思维：泛型编程与设计模式之应用》，华中科技大学出版社 《C++设计新思维（影印版）》，中国电力出版社 你自认为是C++模板编程高手吗？请看过这本书再回答J 这是一本出自天才之手令人敬畏的杰作。泛型模式，无限延伸你的视野，足以挑战任何一名C++程序员的思维极限。 这本书共分为两大部分，第一部分讨论了 Loki程序库采用的基础技术以及一些高级语言特性，包括基于策略的类设计、模板局部特化、编译期断言、Typelist以及小型对象分配技术等。第二部分则着重介绍了Loki中的重要组件和泛型模式技术，包括泛化仿函数（Generalization Functor）、单件（Singleton）、智能指针、对象工厂（Object Factory）、抽象工厂（Abstract Factory）、访问者（Visitor）以及多方法（Multimethods）等。每一种技术都让人大开眼界，叹为观止。 在C++的学习方面，过犹不及往往成了不求甚解的借口。然而，面向对象并非C++的全部，模板和泛型编程亦占半壁江山。对于“严肃”的C++程序员而言，及时跟进这项早经例证的成功技术，不失为明智之举。 结语 这些著作是如此大名鼎鼎，也许根本不缺我一个推荐。然而，纵然C++程序员队伍的发展壮大速度不像其他更时髦的语言那样迅速，新人进总是多于旧人出。除了热忱地欢迎新人，我个人认为到了对C++书籍进行“盘点”的时候了，并且希望这样的“盘点”有益于感兴趣的读者。请保持耐心和宽厚。在下篇中，我将继续介绍标准库、网络编程以及其他方面的C++好书。有好书相伴，这个冬天不会冷。 C++程序设计之四书五经（下篇） 我在上篇中“盘点”了TCPL和D&amp;E以及入门教程、高效和健壮编程、模板和泛型编程等方面共十几本C++好书。冬去春来，让我们继续C++书籍精彩之旅J 标准库 当我还在研究院工作时，与同院另外两家研究所合作开发过一个大型水利枢纽调度集成项目。我们三家软件系统之间都要相互通信。在调试通讯模块时，细心的客户（一名好学的系统管理员）发现对于同一通信规约的解释代码，我的不超过30行，而对方的则超过了150行且很难看懂。这位系统管理员很纳闷，我说大家编程风格和习惯不一样，我使用了标准库，而他使用了传统C编程风格以及他所习惯的另外一些技术。 别误会！我绝无贬低这位合作伙伴的意思。事实上，我对那些真正有着深厚的C编程功力的程序员常常怀有钦佩之心。毕竟，C++能有今天的成功在很大程度上缘于它深深地植根于C。作为一名C++程序员，倘若不熟悉C++中的C，我往往会认为他的基本功是不扎实的，他的技术底气是不足的。 不过话又说回来，C++是一种多范型（paradigm）编程语言，具体采用哪种编程风格，专业程序员应该知道视具体情况而定。作为一名经常需要在现场做即兴开发的项目负责人，为了短平快地解决当务之急，我习惯尽量采用现有的库（和组件）。效率（以及强健性）久经验证的C++标准库已经摆在那儿了，何乐而不用呢？ Nicolai M. Josuttis, The C++ Standard Library: A Tutorial and Reference 《C++标准程序库：自修教程与参考手册》，华中科技大学出版社 这是一本百科全书式的C++标准库著作，是一本需要一再查阅的参考大全。它在完备性、细致性以及精确性方面都是无与伦比的。本书详细介绍了每一标准库组件的规格和用法，内容涵盖包括流和本地化在内的整个标准库而不仅仅是STL。正如本书副标题所示，它首先适合作为教程阅读，尔后又可用作参考手册。 浅显易懂的写作风格使得这本书非常易读。如果你希望学习标准库的用法并尽可能地发挥其潜能，那你必须拥有这本书。正如网络上所言，这本书不仅仅应该摆在你的书橱中，更应该放到你的电脑桌上。我向每一位职业C++程序员强烈推荐。 Angelika Langer, Klaus Kreft, Standard C++ IOStreams and Locales: Advanced Programmer''s Guide and Reference 《标准C++输入输出流与本地化》，人民邮电出版社 C++标准库由STL、流和本地化三部分构成。关于STL的书市面上已经有不少，但罕见流和本地化方面的专著。本书是这两个领域中最优秀的一本，迄今为止没有任何一本书比这一本更全面详尽地讨论了流和本地化。如果你不满足于停留在“会用”流库的层面，千万不要错过它。 2001年夏天，我草草翻阅过这本书的中文版，从内容到包装都给我留下了比较深刻的印象 — 不过负面的居多一些。2003年秋天，无意中得知某网络书店正以超低价格甩卖这本书的中译本，情不自禁，一阵唏嘘。 Scott Meyers, Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library 《Effective STL（影印版）》，中国电力出版社 读完Scott 的《Effective C++》和《More Effective C++》的中译本之后，我一直期待这本书的中文版。我从潘爱民先生的个人主页上了解到，他和他的合作伙伴似乎早已完成了这本书的翻译工作，可惜至今市面上仍不得见。幸运的是，我们可以看到它的原版。 本书是使用STL的程序员必读之作。在这本书中，Scott向我们讲述STL容器和算法的工作机制以及如何以最佳方式使用它们。和Scott的其他作品一样，这本书的写作风格清晰、精确，具有极佳的可读性。看过这本书以后，我想你也许会和我以及其他C++程序员一样产生这样的想法：Scott什么时候会写出一本“More Effective STL”？ 关于STL，我还提醒你留心Matthew H. Austern的《Generic Programming and the STL: Using and Extending the C++ Standard Template Library》（《泛型编程与STL》，中国电力出版社）。这本书散发着浓厚的学院气息。Andrew Koenig和Barbara Moo在《Accelerated C++: Practical Programming by Example》一书末尾郑重推荐另外两本进阶好书（除了他们自己的《Ruminations on C++》外），其中一本是TCPL，另外一本就是本书！ 网络编程 在网络编程时代，C++应该扮演着怎样的角色，让ACE（Adaptive Communications Environment）来告诉你。 Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 1: Mastering Complexity with ACE and Patterns Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 2: Systematic Reuse with ACE and Frameworks 《C++网络编程，卷1：运用ACE和模式消除复杂性》，华中科技大学出版社 《C++网络编程，卷2：基于 ACE 和框架的系统化复用》，电子工业出版社 采用C++进行企业级网络编程，目前ACE（以及这两本书）是一个值得考虑的选择。ACE是一个面向对象、跨平台、开放源码的网络编程框架，目标在于构建高性能网络应用和中间件。Douglas是ACE的创始人，Stephen则已为ACE提供了数年的技术支持和顾问服务，两位都是ACE社群（是的，ACE的影响和实际应用的程度已经形成了一个社群）的专家。 ACE并不单单被大学和研究所追捧，它已经被成功地应用于世界上成千上万个商业应用中。在电信、宇航、医药和财经领域的网络系统中，ACE已经并继续发挥着重要的作用。如果你准备开发高性能通讯系统，你应该考虑考虑这一汇集世界顶尖专家智慧的成果。 除了使用C++面向对象设计技术和模板等高级语言特性外，ACE还运用了大量的模式。《C++网络编程》卷1和卷2并不仅仅教你关于ACE的方方面面，它还会教给你模式和通用框架设计等高级技术等。所以，作为一名中、高级C++程序员，即使你很少进行正儿八经的C++网络程序设计，阅读这两本书同样可以从中受益。 是的，并非所有网络应用都要使用Web服务器（以及其他应用服务器）和重量级组件模型，换个思路，它们或许也可以从轻量级的ACE组件中获益。 杂项 以下这几本书之所以被列入“杂项”单元，只是因为我没有考虑出更合适的归类方法，它们和上面的书籍一样，值得一读。 Bruce Eckel, Thinking in C++, Volume 1: Introduction to Standard C++ (2nd Edition) Bruce Eckel, Thinking in C++, Volume 2: Practical Programming (Second Edition) 《C++编程思想（第2版）第1卷：标准C++导引》，机械工业出版社 《C++编程思想（英文版 第2版）》，机械工业出版社 《Thinking in C++》的第1版于1996年荣获“软件研发”杂志评选的图书震撼大奖。最新推出的第2版对内容进行了大幅改写和调整，以反映C++标准化带来的影响以及近几年面向对象领域最新研究和实践成果。“输入输入流”、“多重继承”、“异常处理”和“运行时类型识别”等高级主题连同C++标准化以后增加的一些内容则被放入第二卷中。Bruce是一名经验丰富的C++讲师和顾问，其培训和写作经验都是世界一流水准，他的作品比那些“玩票”的技术人员写的东西更能吸引读者。事实上，在同类图书中，对于大多数读者而言，这本书的可读性要超过TCPL和《C++ Primer》。顺带一提，访问作者的站点，你可以先睹第二卷的风采。 Andrew Koenig, Barbara E. Moo, Ruminations on C++: A Decade of Programming Insight and Experience 《C++沉思录》，人民邮电出版社 Andrew是世界上屈指可数的C++专家。这是一本关于C++编程思想和程序设计技术而非语言细节的著作。如果你已经具有一定的基础，这本书将教你在进行C++编程时应该怎样思考，应该如何表达解决方案。整本书技术表达透彻，文字通俗易懂。Bjarne这样评价这本书：本书遍布“C++是什么、C++能够做什么”的真知灼见。 Stanley B. Lippman, Inside The C++ Object Model 《深度探索C++对象模型》，华中科技大学出版社 《深度探索C++对象模型（影印版）》，中国电力出版社 从编译器的角度观察C++可以使你知其然并知其所以然。本书探讨了大量的C++面向对象程序设计的底层运作机制，包括构造函数、函数、临时对象、继承、虚拟、模板的实例化、异常处理、运行期类型识别等，另外还介绍了一些在实现C++对象模型过程中做出的权衡折衷。喜欢刨根问底的C++程序员不要错过这本书。 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented software 《设计模式：可复用面向对象软件的基础》，机械工业出版社 《设计模式：可复用面向对象软件的基础（英文版）》，机械工业出版社 设计可复用的面向对象的软件，你需要掌握设计模式。本书并非专为C++程序员而写，但它采用了C++（以及Smalltalk）作为主要示例语言，C++程序员尤其易于从中受益。四位作者都是国际公认的面向对象软件领域专家，他们将面向对象软件的设计经验作为设计模式详细记录下来。这本书影响是如此深远，以至于四位作者以及本书都被昵称为GoF（Gang of Four）。本书学院气息浓厚，行文风格严谨简洁，虽然它不如某些讲解模式的书籍易读，但真正要精准地理解设计模式，本书是终极权威。学习设计模式，这本书需要一而再、再而三的咀嚼。顺带一句：请将设计模式化作开拓思维的钥匙，切莫成为封闭思维的枷锁。 还有一些C++好书值得一读，恕此处无法一一列出。例如John Lakos的著作《Large-Scale C++ Software Design》（《大规模C++程序设计》，中国电力出版社）和侯捷先生的《STL 源码剖析》（华中科技大学出版社）等。 《STL 源码剖析》是一本很有特色的书，但我认为它还可以更好。我个人期待侯捷先生自第一版发行以来经过对模板技术的沉淀和再思考之后，再写一本剖析得更深入、更透彻并且更全面的“第二版”。遗憾的是，侯捷先生在完成《C++ Templates: The Complete Guide》一书的翻译后似乎决定暂时告别模板、泛型编程和STL领域。 2004年3月31日补充：我目前最常查阅的两本参考书是《C++标准程序库》和《STL源码剖析》。当然了，这与我年内的写作计划有很大的关系。 使用C++成功开发大规模软件系统，不仅需要很好地理解大多数C++书籍中讲述的逻辑设计问题，更需要掌握《大规模C++程序设计》中讲述的物理设计技术。当然，这本书的确有点过时了，不过，如果你的精力和金钱都比较宽绰，买一本看看并无坏处。 至此，我想有必要声明一下，有一些（好）书没有得到推荐，主要原因如下： 以上这些书已经足够多、足够好了。 我不会推荐通过正常渠道很难购买到的书籍 — 不管是中文版还是英文版。 作（译）者名气大小不影响我的推荐。我们是在看书，不是看人。 我不会推荐我从来没有看过的书。我至少要看过其中的某个版本（包括电子档）。这个“看”，一般指“认真阅读”，不过有一些也只能算是“浏览”。 结语 作为一名普通技术写译者，我深知技术创作和翻译的艰辛（和快乐），并多多少少了解一些有关技术书籍创作、翻译、制作、出版以及市场推介背后的细节。今天，我不会再对一本看上去差强人意的图书信口开河。罗列同一本书的各种版本的用意只在于为你多提供一些信息，让你多一种选择。 在本文成文的后期，我给Bjarne写了一封信，请教如果他来写这篇文章会怎么写。他给了我简明扼要的建议。在肯定以上列出的绝大部分图书都是世界顶尖水平的C++著作的同时，Bjarne提醒我别忘了向专家级程序员推荐《The C++ Standard : Incorporating Technical Corrigendum No. 1》。这本书是 C++标准规范的“图书版”，Bjarne亲自为之作序。 Bjarne还友好地提醒我，在我的推荐列表中没有哪一本有助于C++程序员进行Windows编程 — 这正是我的本意。在这篇文章中，我只推荐、点评平台中立的C++著作（网络编程除外） — 和操作系统无关，和集成开发环境无关，我甚至幻想它们和编译器也无关。你可以根据业务开发需要，选读自己喜爱的领域相关的C++书籍。 说到“系统无关、平台中立”，我不由得想起了“抽象层”的概念。开发实际应用的C++程序员通常工作于特定操作系统、特定开发环境和特定业务领域之中，而对标准C++和C++标准库扎实而深刻的把握，无疑是你得以在不同的操作系统、不同的开发环境以及不同的业务领域之间纵横驰骋的“抽象”本钱。 荣耀
2004年1月
南京师范大学]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C++操作符重载的变态用途之子类转换</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16882</link><subject>如果类的成员变量是特定类和自定义结构，使用该类名或结构作为操作符进行重载。（当然是基本类型也可以，不过实用性不强，只会降低代码可读性。）如下，一个CPerson，强行转换为hand，也可以使用。类似于现实，我们只会对某个实物的具体特征表示强烈的兴趣，也就是特征聚焦的意思。如HR部门只会关注一个应聘者的skill。当然在实际用途中，过度使用这种子类转换，只会降低代码可读性。另外如类中有多个同类型的成员，这样的转换让人莫名其妙。实例代码：// Person.h: interface for the CPerson class.
//
//////////////////////////////////////////////////////////////////////#if !defined(AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_)
#define AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_#if _MSC_VER &gt; 1000
#pragma once
#endif // _MSC_VER &gt; 1000typedef struct tag_hand
{bool bSix;bool bLefty;
} hand;class CSkill
{public:CSkill():strDesc(NULL){}virtual ~CSkill(){}public:char *strDesc;
};class CPerson 
{public:CPerson();virtual ~CPerson();hand m_hand;CSkill m_skill;operator hand() const;operator CSkill() const;static void Test();
};#endif // !defined(AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_)// Person.cpp: implementation of the CPerson class.
//
//////////////////////////////////////////////////////////////////////#include &quot;stdafx.h&quot;
#include &quot;Person.h&quot;//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////CPerson::CPerson()
{}CPerson::~CPerson()
{}CPerson::operator hand() const
{return m_hand;
}CPerson::operator CSkill() const
{return m_skill;
}void CPerson::Test()
{CPerson person;person.m_hand.bSix = false;person.m_hand.bLefty = true;person.m_skill.strDesc = new char[1024];strcpy( person.m_skill.strDesc, &quot;Good at programming...&quot; );printf( &quot;%d, %d\n&quot;, ((hand)person).bSix, ((hand)person).bLefty );printf( &quot;%s\n&quot;, ((CSkill)person).strDesc );delete[] person.m_skill.strDesc;return;
}int main(int argc, char* argv[])
{CPerson::Test();return 0;
} 输出：0, 1Good at programming...</subject><description><![CDATA[如果类的成员变量是特定类和自定义结构，使用该类名或结构作为操作符进行重载。（当然是基本类型也可以，不过实用性不强，只会降低代码可读性。）如下，一个CPerson，强行转换为hand，也可以使用。类似于现实，我们只会对某个实物的具体特征表示强烈的兴趣，也就是特征聚焦的意思。如HR部门只会关注一个应聘者的skill。当然在实际用途中，过度使用这种子类转换，只会降低代码可读性。另外如类中有多个同类型的成员，这样的转换让人莫名其妙。实例代码：// Person.h: interface for the CPerson class.
//
//////////////////////////////////////////////////////////////////////#if !defined(AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_)
#define AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_#if _MSC_VER &gt; 1000
#pragma once
#endif // _MSC_VER &gt; 1000typedef struct tag_hand
{bool bSix;bool bLefty;
} hand;class CSkill
{public:CSkill():strDesc(NULL){}virtual ~CSkill(){}public:char *strDesc;
};class CPerson 
{public:CPerson();virtual ~CPerson();hand m_hand;CSkill m_skill;operator hand() const;operator CSkill() const;static void Test();
};#endif // !defined(AFX_PERSON_H__A825C71F_CB10_4997_8F9C_DBE792C5C387__INCLUDED_)// Person.cpp: implementation of the CPerson class.
//
//////////////////////////////////////////////////////////////////////#include &quot;stdafx.h&quot;
#include &quot;Person.h&quot;//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////CPerson::CPerson()
{}CPerson::~CPerson()
{}CPerson::operator hand() const
{return m_hand;
}CPerson::operator CSkill() const
{return m_skill;
}void CPerson::Test()
{CPerson person;person.m_hand.bSix = false;person.m_hand.bLefty = true;person.m_skill.strDesc = new char[1024];strcpy( person.m_skill.strDesc, &quot;Good at programming...&quot; );printf( &quot;%d, %d\n&quot;, ((hand)person).bSix, ((hand)person).bLefty );printf( &quot;%s\n&quot;, ((CSkill)person).strDesc );delete[] person.m_skill.strDesc;return;
}int main(int argc, char* argv[])
{CPerson::Test();return 0;
} 输出：0, 1Good at programming...]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C/C++中的整型常识</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16885</link><subject>很多人对C/C++中的整型不太了解，导致代码移植的时候出现问题，本人在此总结一下，若有描述错误，请务必指出，谢谢！a. C/C++对整型长度的规定是为了执行效率，将int定义为机器字长可以取得最大的执行速度;
b. C/C++中整型包括：int, char 和 enum, C++中还包含bool类型,C99中bool是一个宏，实际为_Bool; 
c. C 和 C++ 对 enum 的规定有所不同，这里不描述;
d. 修饰整型正负的有 signed 和 unsigned，对于 int 默认为 signed;
e. 修饰 int 大小的有 short 和 long, 部分编译器还扩展了一些更长的整型，比如 long long 和 __int64, C99中增加了long long和unsigned long long;
f. int 的长度 与 机器字长相同, 16位的编译器上int长16位,32位的编译器上int长32位;
g. short int 的长度 小于等于 int 的长度，注意她们可能长度相等，这取决于编译器;
h. long int 的长度 大于等于 int 的长度，注意她们可能长度相等，这取决于编译器;
i. char 的长度应当可以包容得下一个字符，大部分系统中就是一个字节，而有的系统中可能是4个字节，因为这些系统中一个字符需要四个字节来描述;
j. char 的正负取决于编译器，而编译器的决定取决于操作系统，在不同的编译器中char可能等同于signed char，也可能等同于unsigned char;总结:
a. 出于效率考虑，应该尽量使用int和unsigned int;
b. 当需要指定容量的整型时，不应该直接使用short、int、long等，因为在不同的编译器上她们的容量不相同。此时应该定义她们相应的宏或类型，比如在VC++6.0中，可以如下定义：
typedef unsigned char UBYTE;
typedef   signed char SBYTE;
typedef unsigned short int UWORD;
typedef   signed short int SWORD;
typedef unsigned int UDWORD;
typedef   signed int SDWORD;
typedef unsigned __int64 UQWORD;
typedef   signed __int64 SQWORD;
然后在代码中使用 UBYTE、SBYTE、UWORD 等，这样当代码移植的时候只需要修改相应的类型即可。
定义自己的类型虽然在代码移植的时候只需要修改一处即可，但仍然属于源代码级别的修改，所以 C++ 2.0 中将这些类型定义在模板中，可以做到代码移植时无需修改代码。
c. 在定义char时，一定要加上 signed 或 unsigned，因为她的正负在不同的编译器上并不相同。
d. 不要想当然的以为char是1字节长，因为她的长度在不同的编译器上并不相同。</subject><description><![CDATA[很多人对C/C++中的整型不太了解，导致代码移植的时候出现问题，本人在此总结一下，若有描述错误，请务必指出，谢谢！a. C/C++对整型长度的规定是为了执行效率，将int定义为机器字长可以取得最大的执行速度;
b. C/C++中整型包括：int, char 和 enum, C++中还包含bool类型,C99中bool是一个宏，实际为_Bool; 
c. C 和 C++ 对 enum 的规定有所不同，这里不描述;
d. 修饰整型正负的有 signed 和 unsigned，对于 int 默认为 signed;
e. 修饰 int 大小的有 short 和 long, 部分编译器还扩展了一些更长的整型，比如 long long 和 __int64, C99中增加了long long和unsigned long long;
f. int 的长度 与 机器字长相同, 16位的编译器上int长16位,32位的编译器上int长32位;
g. short int 的长度 小于等于 int 的长度，注意她们可能长度相等，这取决于编译器;
h. long int 的长度 大于等于 int 的长度，注意她们可能长度相等，这取决于编译器;
i. char 的长度应当可以包容得下一个字符，大部分系统中就是一个字节，而有的系统中可能是4个字节，因为这些系统中一个字符需要四个字节来描述;
j. char 的正负取决于编译器，而编译器的决定取决于操作系统，在不同的编译器中char可能等同于signed char，也可能等同于unsigned char;总结:
a. 出于效率考虑，应该尽量使用int和unsigned int;
b. 当需要指定容量的整型时，不应该直接使用short、int、long等，因为在不同的编译器上她们的容量不相同。此时应该定义她们相应的宏或类型，比如在VC++6.0中，可以如下定义：
typedef unsigned char UBYTE;
typedef   signed char SBYTE;
typedef unsigned short int UWORD;
typedef   signed short int SWORD;
typedef unsigned int UDWORD;
typedef   signed int SDWORD;
typedef unsigned __int64 UQWORD;
typedef   signed __int64 SQWORD;
然后在代码中使用 UBYTE、SBYTE、UWORD 等，这样当代码移植的时候只需要修改相应的类型即可。
定义自己的类型虽然在代码移植的时候只需要修改一处即可，但仍然属于源代码级别的修改，所以 C++ 2.0 中将这些类型定义在模板中，可以做到代码移植时无需修改代码。
c. 在定义char时，一定要加上 signed 或 unsigned，因为她的正负在不同的编译器上并不相同。
d. 不要想当然的以为char是1字节长，因为她的长度在不同的编译器上并不相同。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C++ 中重载 + 操作符的正确方法</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16887</link><subject>用户定义的类型，如：字符串，日期，复数，联合体以及文件常常重载二元 + 操作符以实现对象的连接，附加或合并机制。但是要正确实现 + 操作符会给设计，实现和性能带来一定的挑战。本文将概要性地介绍如何选择正确的策略来为用户定义类型重载这个操作符。
考虑如下的表达式： int x=4+2;内建的 + 操作符有两个类型相同的操作数，相加并返回右值 6，然后被赋值给 x。我们可以断定内建的 + 是一个二元的，对称的，可交换的操作符。它产生的结果的类型与其操作数类型相同。按照这个规测，当你为某个用户定义类型重载操作符时，也应该遵循相应内建操作符的特征。 为用户定义类型重载 + 操作符是很常见的编程任务。尽管 C++ 提供了几种实现方法，但是它们容易使人产生设计上的误解，这种误解常常影响代码的正确性，性能以及与标准库组件之间的兼容性。 下面我们就来分析内建操作符的特征并尝试模仿其相应的重载机制。第一步：在成员函数和非成员函数之间选择你可以用类成员函数的方式实现二元操作符如：+、- 以及 ==，例如：class String
{
public:
bool operator==(const String &amp; s); // 比较 *this 和 s
};  这个方法是有问题的。相对于其内建的操作符来说，重载的操作符在这里不具有对称性；它的两个参数一个类型为：const String * const（这个参数是隐含的），另一个类型为：const String &amp;。因此，一些 STL 算法和容器将无法正确处理这样的对象。另外一个可选方法是把重载操作符 + 定义为一个外部（extern）函数，该函数带两个类型相同的参数：String operator + (const String &amp; s1, const String s2); 这样一来，类 String 必须将该重载操作符声明为友元： class String
{
public:
friend String operator+(const String&amp; s1,const String&amp;s2);
}; 第二步：返回值的两难选择如前所述，内建操作符 + 返回右值，其类型与操作数相同。但是在调用者堆栈里返回一个对象效率很低，处理大型对象时尤其如此。那么能不能返回一个指针或引用呢？答案是不行。因为返回指针破坏参数类型与返回值类型应该相同的规则。更糟的是，链接多个表达式将成为不可能：String s1,s2,s3;
String res;
res=s1+s2+s3; // 不可能用 String* 作为返回值 虽然有一个办法可以定义额外的 + 操作符重载版本，但这个办法是我们不希望用的，因为返回的指针必须指向动态分配的对象。这样的话，如果调用者释放（delete）返回的指针失败，那么将导致内存泄漏。显然，返回 String* 不是一个好主意。那么返回 String&amp; 好不好呢？返回的引用必须一定要是一个有效的 String。它避免了使用动态对象分配，该方法返回的是一个本地静态对象的引用。静态对象确实解决了内存泄漏问题，但这个方法的可行性仍然值得怀疑。在一个多线程应用中，两个线程可能会并发调用 + 操作符，因此造成 String 对象的混乱。而且，因为静态对象总是保留其调用前的状态，所以有必要针对每次 + 操作符的调用都清除该静态 String 对象。由此看来，在堆栈上返回结果仍然是最安全和最简单的解决方案。</subject><description><![CDATA[用户定义的类型，如：字符串，日期，复数，联合体以及文件常常重载二元 + 操作符以实现对象的连接，附加或合并机制。但是要正确实现 + 操作符会给设计，实现和性能带来一定的挑战。本文将概要性地介绍如何选择正确的策略来为用户定义类型重载这个操作符。
考虑如下的表达式： int x=4+2;内建的 + 操作符有两个类型相同的操作数，相加并返回右值 6，然后被赋值给 x。我们可以断定内建的 + 是一个二元的，对称的，可交换的操作符。它产生的结果的类型与其操作数类型相同。按照这个规测，当你为某个用户定义类型重载操作符时，也应该遵循相应内建操作符的特征。 为用户定义类型重载 + 操作符是很常见的编程任务。尽管 C++ 提供了几种实现方法，但是它们容易使人产生设计上的误解，这种误解常常影响代码的正确性，性能以及与标准库组件之间的兼容性。 下面我们就来分析内建操作符的特征并尝试模仿其相应的重载机制。第一步：在成员函数和非成员函数之间选择你可以用类成员函数的方式实现二元操作符如：+、- 以及 ==，例如：class String
{
public:
bool operator==(const String &amp; s); // 比较 *this 和 s
};  这个方法是有问题的。相对于其内建的操作符来说，重载的操作符在这里不具有对称性；它的两个参数一个类型为：const String * const（这个参数是隐含的），另一个类型为：const String &amp;。因此，一些 STL 算法和容器将无法正确处理这样的对象。另外一个可选方法是把重载操作符 + 定义为一个外部（extern）函数，该函数带两个类型相同的参数：String operator + (const String &amp; s1, const String s2); 这样一来，类 String 必须将该重载操作符声明为友元： class String
{
public:
friend String operator+(const String&amp; s1,const String&amp;s2);
}; 第二步：返回值的两难选择如前所述，内建操作符 + 返回右值，其类型与操作数相同。但是在调用者堆栈里返回一个对象效率很低，处理大型对象时尤其如此。那么能不能返回一个指针或引用呢？答案是不行。因为返回指针破坏参数类型与返回值类型应该相同的规则。更糟的是，链接多个表达式将成为不可能：String s1,s2,s3;
String res;
res=s1+s2+s3; // 不可能用 String* 作为返回值 虽然有一个办法可以定义额外的 + 操作符重载版本，但这个办法是我们不希望用的，因为返回的指针必须指向动态分配的对象。这样的话，如果调用者释放（delete）返回的指针失败，那么将导致内存泄漏。显然，返回 String* 不是一个好主意。那么返回 String&amp; 好不好呢？返回的引用必须一定要是一个有效的 String。它避免了使用动态对象分配，该方法返回的是一个本地静态对象的引用。静态对象确实解决了内存泄漏问题，但这个方法的可行性仍然值得怀疑。在一个多线程应用中，两个线程可能会并发调用 + 操作符，因此造成 String 对象的混乱。而且，因为静态对象总是保留其调用前的状态，所以有必要针对每次 + 操作符的调用都清除该静态 String 对象。由此看来，在堆栈上返回结果仍然是最安全和最简单的解决方案。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>c++俄罗斯方块 (含源程序)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=28948</link><subject>俄罗斯方块 (含源程序)【软件简介】    虽然是非常古老的小游戏, 但现在仍然百玩不厌。    这个程序用BCB实现也非常简单, 程序运行界面如右图所示。    为了让大家看清程序的算法, 程序的界面尽量简单, 没有过多的修饰。【游戏操作】    左、右、下移动光标键: 可移动下落的方块;    空格、回车分别按顺时针和逆时针方向旋转下落的方块;    Esc: 暂停/继续;    F10: 开始新游戏;    其它操作可通过选择菜单来实现。【计分方法】    只是落下方块:  1 分;    同时消去一行: 10 分;    同时消去两行: 30 分;    同时消去三行: 50 分;    同时消去四行: 80 分;    如果你的分数取得了名次, 会显示对话框提示你输入名字, 你的名字就会加入英雄榜。    选择菜单: 游戏→英雄榜: 可查看英雄榜的内容。【文件内容】    Games 文件夹：游戏执行文件    Source 文件夹：游戏源程序 (Borland C++ Builder 6.0)【源程序的解释和分析】    在网友留言上, 解答了网友的一些问题, 暂时到这里来看, 过些时间再整理。点击此处下载俄罗斯方块 (含源程序) 242,476 字节 (下载 53662 次)</subject><description><![CDATA[俄罗斯方块 (含源程序)【软件简介】    虽然是非常古老的小游戏, 但现在仍然百玩不厌。    这个程序用BCB实现也非常简单, 程序运行界面如右图所示。    为了让大家看清程序的算法, 程序的界面尽量简单, 没有过多的修饰。【游戏操作】    左、右、下移动光标键: 可移动下落的方块;    空格、回车分别按顺时针和逆时针方向旋转下落的方块;    Esc: 暂停/继续;    F10: 开始新游戏;    其它操作可通过选择菜单来实现。【计分方法】    只是落下方块:  1 分;    同时消去一行: 10 分;    同时消去两行: 30 分;    同时消去三行: 50 分;    同时消去四行: 80 分;    如果你的分数取得了名次, 会显示对话框提示你输入名字, 你的名字就会加入英雄榜。    选择菜单: 游戏→英雄榜: 可查看英雄榜的内容。【文件内容】    Games 文件夹：游戏执行文件    Source 文件夹：游戏源程序 (Borland C++ Builder 6.0)【源程序的解释和分析】    在网友留言上, 解答了网友的一些问题, 暂时到这里来看, 过些时间再整理。点击此处下载俄罗斯方块 (含源程序) 242,476 字节 (下载 53662 次)]]></description><PubDate>2009-2-16 0:00:00</PubDate><category>C/C++教程</category></item><item><title>Boost在C++自己的墙上打洞</title><link>http://www.bcbbs.net/News/Content.aspx?ID=28950</link><subject>Boost是极有才华的程序员们编写的C++库。Boost中的编程技巧、对C++及泛型的使用方式、以及其最终实现的效果都令人吃惊，甚至叹为观止。C++是强类型语言，有严格的类型检查。而Boost使用C++实现了弱类型的效果，着实在C++的墙上打了一个洞。    C++的强类型特征曾被视为一种优点。至今它仍然也是优点。严格的类型检查可以帮助程序员检测出代码中的错误，可以提醒程序员注意到数据的取值范围，可以规范程序员对变量的使用，并在一定程度上增加程序的易读性。可以说C++编译器对数据类型的检查筑起了一堵类型高墙，对变量任何错误的赋值、错误的使用都会被挡住，任何可能会出错的地方都会被提醒。比如下面的代码，在编译的时候是通不过的。  int i;i = \&quot;I am a string!\&quot;;    这堵墙被筑在了编译器上，而在这堵墙之后，在程序的运行期，则几乎完全没有类型检查。不仅如此，类型信息似乎已经被抛弃了。程序不能在运行期得到数据的类型信息，因此也无法在运行期依据类型选择相应的操作。    比如在C++中就实现不了像下面这样的语句：  void * makePlace( para){    int len;    len = para类型数据占用空间的大小;    return 分配len个字节的内存;}    在模板被引入之前，C++中只能用函数重载来增加可读性。使多个类型的内存分配功能都使用makePlace这个函数名，如：  void * makePlace (int para){    return new char[sizeof(para)];}void * makePlace (float para){    return new char[sizeof(para)];}    如果有很多类型，那么就要写很多个makePlace.这些makePlace函数都是相似的。这只是一个简单的例子，事实上，有很多算法、结构都是可以应用到多种数据类型的，而为每种类型重写一堆代码，显然麻烦的很。    后来C++增加了泛型特征，可以在程序中使用模板。模板放在类型墙的前面，相当于一个变身器。把一个类型无关的算法放进来，生成一个符合类型法则的新算法，然后再放过到墙的另一边去。比如上面的makePlace代码，可以写成下面这样：  template&lt;typename T&gt;void * makePlace(T para){    return new char[sizeof(T)];}    也许在C++中引入泛型模板只是为了这个朴素的目的，让程序员少做些重复的工作。也许泛型标准的设计者当初认为这一点算不上类型墙上的裂缝，不会影响到C++的强类型特征。呵呵，想想IE浏览器忙着封堵的那些漏洞吧。    这世上总会有一些智慧让你惊。Boost就是这样的东西。    Boost没有修改C++的任何规则。它是一个完全符合C++规范的代码库。    但是看看这段代码：  int i;long j;X x; //假设X为用户定义的类any anyVal=i;... //use anyVal as a int valueanyVal=j;... //use anyVal as a long valueanyVal=x;... //use anyVal as a long value    噢……这是什么，那个anyVal是什么类型的？any？它能除了int，long，X，还能赋值成别的类型么？这是C++程序么？ 还是别的什么东东？    any是Boost的泛型指针，它确实可以赋值成任何类型。any其实是一个类。但any确实可以是任何类型。但单纯的any也什么类型也不是。当它是C#中的var好了。    再看看下面这段代码：  #include \&quot;boost/assign/std/vector.hpp\&quot;using namespace boost::assign;vector i_v;i_v += 1,2,3,4,5;    看看上面代码中最后一句的赋值语句。你真的还相信这是C++程序么？这样的代码能通过编译么？在C++编译器这堵墙还竖立在那里的时候，Boost能让这些代码顺利穿过么？    Boost做到了！    我想知道Boost是怎么做到的，我下载了Boost的源文件，我看到了Boost的源代码，我看到了复杂的模板，我看到了自己想像不出的高超技巧！ Boost确实能在C++中实现这些代码。    事实上Boost实现的效果远不止文中描述的这些。Boost的源代码似乎也高于我的智慧，以至于我仍没有完全弄清楚它是怎么实现的，只能以后慢慢研究了。    C++的类型检查、语法规范等都很严格。它们像墙一样保护了C++程序的安全，并严格限制了C++的代码。Boost是使用C++编写的库，符合C++规范却让弱类型操作和奇怪的语句顺利穿过了这些墙。我觉得Boost不只是在发展C++，也是在C++自己的墙上打洞。</subject><description><![CDATA[Boost是极有才华的程序员们编写的C++库。Boost中的编程技巧、对C++及泛型的使用方式、以及其最终实现的效果都令人吃惊，甚至叹为观止。C++是强类型语言，有严格的类型检查。而Boost使用C++实现了弱类型的效果，着实在C++的墙上打了一个洞。    C++的强类型特征曾被视为一种优点。至今它仍然也是优点。严格的类型检查可以帮助程序员检测出代码中的错误，可以提醒程序员注意到数据的取值范围，可以规范程序员对变量的使用，并在一定程度上增加程序的易读性。可以说C++编译器对数据类型的检查筑起了一堵类型高墙，对变量任何错误的赋值、错误的使用都会被挡住，任何可能会出错的地方都会被提醒。比如下面的代码，在编译的时候是通不过的。  int i;i = \&quot;I am a string!\&quot;;    这堵墙被筑在了编译器上，而在这堵墙之后，在程序的运行期，则几乎完全没有类型检查。不仅如此，类型信息似乎已经被抛弃了。程序不能在运行期得到数据的类型信息，因此也无法在运行期依据类型选择相应的操作。    比如在C++中就实现不了像下面这样的语句：  void * makePlace( para){    int len;    len = para类型数据占用空间的大小;    return 分配len个字节的内存;}    在模板被引入之前，C++中只能用函数重载来增加可读性。使多个类型的内存分配功能都使用makePlace这个函数名，如：  void * makePlace (int para){    return new char[sizeof(para)];}void * makePlace (float para){    return new char[sizeof(para)];}    如果有很多类型，那么就要写很多个makePlace.这些makePlace函数都是相似的。这只是一个简单的例子，事实上，有很多算法、结构都是可以应用到多种数据类型的，而为每种类型重写一堆代码，显然麻烦的很。    后来C++增加了泛型特征，可以在程序中使用模板。模板放在类型墙的前面，相当于一个变身器。把一个类型无关的算法放进来，生成一个符合类型法则的新算法，然后再放过到墙的另一边去。比如上面的makePlace代码，可以写成下面这样：  template&lt;typename T&gt;void * makePlace(T para){    return new char[sizeof(T)];}    也许在C++中引入泛型模板只是为了这个朴素的目的，让程序员少做些重复的工作。也许泛型标准的设计者当初认为这一点算不上类型墙上的裂缝，不会影响到C++的强类型特征。呵呵，想想IE浏览器忙着封堵的那些漏洞吧。    这世上总会有一些智慧让你惊。Boost就是这样的东西。    Boost没有修改C++的任何规则。它是一个完全符合C++规范的代码库。    但是看看这段代码：  int i;long j;X x; //假设X为用户定义的类any anyVal=i;... //use anyVal as a int valueanyVal=j;... //use anyVal as a long valueanyVal=x;... //use anyVal as a long value    噢……这是什么，那个anyVal是什么类型的？any？它能除了int，long，X，还能赋值成别的类型么？这是C++程序么？ 还是别的什么东东？    any是Boost的泛型指针，它确实可以赋值成任何类型。any其实是一个类。但any确实可以是任何类型。但单纯的any也什么类型也不是。当它是C#中的var好了。    再看看下面这段代码：  #include \&quot;boost/assign/std/vector.hpp\&quot;using namespace boost::assign;vector i_v;i_v += 1,2,3,4,5;    看看上面代码中最后一句的赋值语句。你真的还相信这是C++程序么？这样的代码能通过编译么？在C++编译器这堵墙还竖立在那里的时候，Boost能让这些代码顺利穿过么？    Boost做到了！    我想知道Boost是怎么做到的，我下载了Boost的源文件，我看到了Boost的源代码，我看到了复杂的模板，我看到了自己想像不出的高超技巧！ Boost确实能在C++中实现这些代码。    事实上Boost实现的效果远不止文中描述的这些。Boost的源代码似乎也高于我的智慧，以至于我仍没有完全弄清楚它是怎么实现的，只能以后慢慢研究了。    C++的类型检查、语法规范等都很严格。它们像墙一样保护了C++程序的安全，并严格限制了C++的代码。Boost是使用C++编写的库，符合C++规范却让弱类型操作和奇怪的语句顺利穿过了这些墙。我觉得Boost不只是在发展C++，也是在C++自己的墙上打洞。]]></description><PubDate>2009-2-16 0:00:00</PubDate><category>C/C++教程</category></item><item><title>C++和C#编写调用COM组件</title><link>http://www.bcbbs.net/News/Content.aspx?ID=28738</link><subject>作者：徐凌超下载源代码摘要：现在COM组件的使用越来越广泛，在各个程序中调用COM组件我想大家都遇到过，这篇文章就是关于COM组件的编写和调用的，主要包含了使用VC6.0编写和调用COM组件，VS2005中使用C#编写和调用COM组件，以及在VC6.0和VS2005之间互相调用COM组件。关键字：VC6.0编写调用COM VS2005中C#编写和调用COM VC6.0和VS2005之间互相调用COM正文：前一阵在工作中做项目的时候，遇到了COM组件的调用和使用问题，当时研究和好一阵，才把中间的环节打通，现在写出来为大家提供方便，这里包含了四个类型:1、在VS2005中，C#编写DLL并使用C++调用2、在VS2005中C#编写的COM组件，使用VC6.0调用3、在VC6.0中编写COM组件，使用VS2005 C#调用4、在VC6.0中编写COM组件，使用VC6.0调用其中每个类型都写了两个程序，一个为COM组件程序，一个为调用程序程序实现：1、在VS2005中，C#编写DLL并使用C++调用（1）C#编写DLL程序建立C#编写的DLL程序AddDll，项目类型为：类库程序代码：using System;using System.Collections.Generic;using System.Text;namespace AddDll{public class Add{public int iadd(int a, int b){int c = a + b;return c;}}}（2）C++编写调用程序建立C++的Win32控制台应用程序UseDll，项目类型为：Win32控制台应用程序配置：右键点击解决方案资源管理器中的UseDll，选择“属性”，将公共语言运行库支持设置为“公共语言运行库支持(/clr)”图一 公共语言运行库设置程序代码：#include &quot;stdafx.h&quot;#include &quot;stdio.h&quot;#using &quot;..\debug\AddDll.dll&quot;using namespace AddDll;int _tmain(int argc, _TCHAR* argv[]){int result;Add ^add = gcnew Add();result = add-&gt;iadd(10,90);printf(&quot;%d&quot;,result);scanf(&quot;%s&quot;);return 0;}2、在VS2005中C#编写的COM组件，使用VC6.0调用（1）VS2005中使用C#编写COM组件建立C#编写的COM组件，项目类型为类库配置：右键点击解决方案资源管理器中的AddCom，选择“属性”，选择“生成”，选择“为COM Interop注册(_P)”打开AssemblyInfo.cs文件，设置[assembly: ComVisible(true)]这用就可以生成AddCom.tlb文件图二 COM生成设置程序代码：using System;using System.Collections.Generic;using System.Text;using System.Runtime.InteropServices;namespace AddCom{//可以通过//菜单的 “工具/guid生成”。//注意要选择Define Guid{….}格式，并全//部保存下来，保存到哪都行，记事本呀什么的。//因为在做VC程序/////////的时候要用到的。[Guid(&quot;298D881C-E2A3-4638-B872-73EADE25511C&quot;)]  public interface AddComInterface{[DispId(1)]int iadd(int a, int b);[DispId(2)]float ladd(float a, float b);}[Guid(&quot;2C5B7580-4038-4d90-BABD-8B83FCE5A467&quot;)][ClassInterface(ClassInterfaceType.None)]public class AddComService : AddComInterface{public AddComService(){}public int iadd(int a, int b){int c = 0;c = a + b;return c;}public float ladd(float a, float b){float c = 0;c = a + b;return c;}}}（2）VC6.0编写调用程序使用VC6.0编写建立MFC应用程序UseCom，项目类型为MFC AppWizard(exe)在stdafx.h添加：#import &quot;AddCom.tlb&quot;using namespace AddCom;程序代码：void CUseComDlg::OnButtonUse() {// TODO: Add your control notification handler code hereint dresult;float fresult;CString strResult;CoInitialize(NULL);//NULL换成0也可以AddCom::AddComInterfacePtr p_Add(__uuidof(AddComService));dresult = p_Add-&gt;iadd(1,2);fresult = p_Add-&gt;fadd(1.2,2.3);strResult.Format(&quot;int:%d \nfloat:%f&quot;,dresult,fresult);MessageBox(strResult,&quot;计算结果&quot;,MB_OK);CoUninitialize();   }3、在VC6.0中编写COM组件，使用VS2005 C#调用（1）VC6.0编写COM使用VC6.0建立COM组件，工程类型：ATL COM AppWizard程序代码：接口：interface IAdd : IDispatch{[id(1), helpstring(&quot;method iadd&quot;)] HRESULT iadd([in]int a, [in]int b, [out]int * c);[id(2), helpstring(&quot;method fadd&quot;)] HRESULT fadd([in]float a, [in]float b, [out]float * c);[id(3), helpstring(&quot;method isub&quot;)] HRESULT isub([in]int a, [in]int b, [out]int * c);};实现：STDMETHODIMP CAdd::iadd(int a, int b, int *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::fadd(float a, float b, float *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::isub(int a, int b, int *c){// TODO: Add your implementation code here*c = a - b;return S_OK;}（2）VS2005使用C#编写调用程序（网站程序）使用VS2005建立网站UseCom配置：在解决方案资源管理器中的主目录点击右键，选择添加引用，选择COM，添加刚刚建立的AddCom 1.0 Type Library在程序中要using编写的COM组件：using ADDCOMLib;图三 引用COM程序代码：using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using ADDCOMLib;public partial class _Default : System.Web.UI.Page {protected void Page_Load(object sender, EventArgs e){}protected void ButtonCom_Click(object sender, EventArgs e){Add add = new Add();int iresult;float fresult;int sresult;add.IAdd(10, 20, out iresult);add.fadd((float)1.2,(float)2.3, out fresult);add.isub(100, 10, out sresult);TextBoxResult.Text = iresult.ToString();TextBoxRe2.Text = fresult.ToString();TextBoxRe3.Text = sresult.ToString();}}4、在VC6.0中编写COM组件，使用VC6.0调用（1）VC6.0编写COM组件使用VC6.0建立COM组件，工程类型：ATL COM AppWizard程序代码：接口：interface IAdd : IDispatch{[id(1), helpstring(&quot;method iadd&quot;)] HRESULT iadd([in]int a, [in]int b, [out]int * c);[id(2), helpstring(&quot;method fadd&quot;)] HRESULT fadd([in]float a, [in]float b, [out]float * c);[id(3), helpstring(&quot;method isub&quot;)] HRESULT isub([in]int a, [in]int b, [out]int * c);};实现：STDMETHODIMP CAdd::iadd(int a, int b, int *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::fadd(float a, float b, float *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::isub(int a, int b, int *c){// TODO: Add your implementation code here*c = a - b;return S_OK;}（2）VC6.0编写调用程序使用VC6.0建立MFC应用程序UseCOM，调用刚刚建立的COM组件将上面程序AddCom生成的AddCom.dll放入本程序的工程目录和程序生成目录中在StdAfx.h中加入：#import &quot;AddCom.dll&quot; no_namespace程序代码：void CUseComDlg::OnBUTTONUse() {// TODO: Add your control notification handler code hereCString strResult;CoInitialize(NULL);//NULL换成0也可以IAddPtr m_add = NULL;HRESULT hr = S_OK;hr = m_add.CreateInstance(__uuidof(Add));int d_a = 90;int d_b = 10;int d_c;int d_d;float f_a = 1;float f_b = 2;float f_c;m_add-&gt;_IAdd(d_a,d_b,&amp;d_c);m_add-&gt;fadd(f_a,f_b,&amp;f_c);m_add-&gt;isub(d_a,d_b,&amp;d_d);strResult.Format(&quot;返回结果：%d; %f; %d&quot;,d_c,f_c,d_d);MessageBox(strResult,&quot;结果&quot;,MB_OK);m_add.Release();m_add = NULL;CoUninitialize();   }结束语：希望能对大家有帮助！</subject><description><![CDATA[作者：徐凌超下载源代码摘要：现在COM组件的使用越来越广泛，在各个程序中调用COM组件我想大家都遇到过，这篇文章就是关于COM组件的编写和调用的，主要包含了使用VC6.0编写和调用COM组件，VS2005中使用C#编写和调用COM组件，以及在VC6.0和VS2005之间互相调用COM组件。关键字：VC6.0编写调用COM VS2005中C#编写和调用COM VC6.0和VS2005之间互相调用COM正文：前一阵在工作中做项目的时候，遇到了COM组件的调用和使用问题，当时研究和好一阵，才把中间的环节打通，现在写出来为大家提供方便，这里包含了四个类型:1、在VS2005中，C#编写DLL并使用C++调用2、在VS2005中C#编写的COM组件，使用VC6.0调用3、在VC6.0中编写COM组件，使用VS2005 C#调用4、在VC6.0中编写COM组件，使用VC6.0调用其中每个类型都写了两个程序，一个为COM组件程序，一个为调用程序程序实现：1、在VS2005中，C#编写DLL并使用C++调用（1）C#编写DLL程序建立C#编写的DLL程序AddDll，项目类型为：类库程序代码：using System;using System.Collections.Generic;using System.Text;namespace AddDll{public class Add{public int iadd(int a, int b){int c = a + b;return c;}}}（2）C++编写调用程序建立C++的Win32控制台应用程序UseDll，项目类型为：Win32控制台应用程序配置：右键点击解决方案资源管理器中的UseDll，选择“属性”，将公共语言运行库支持设置为“公共语言运行库支持(/clr)”图一 公共语言运行库设置程序代码：#include &quot;stdafx.h&quot;#include &quot;stdio.h&quot;#using &quot;..\debug\AddDll.dll&quot;using namespace AddDll;int _tmain(int argc, _TCHAR* argv[]){int result;Add ^add = gcnew Add();result = add-&gt;iadd(10,90);printf(&quot;%d&quot;,result);scanf(&quot;%s&quot;);return 0;}2、在VS2005中C#编写的COM组件，使用VC6.0调用（1）VS2005中使用C#编写COM组件建立C#编写的COM组件，项目类型为类库配置：右键点击解决方案资源管理器中的AddCom，选择“属性”，选择“生成”，选择“为COM Interop注册(_P)”打开AssemblyInfo.cs文件，设置[assembly: ComVisible(true)]这用就可以生成AddCom.tlb文件图二 COM生成设置程序代码：using System;using System.Collections.Generic;using System.Text;using System.Runtime.InteropServices;namespace AddCom{//可以通过//菜单的 “工具/guid生成”。//注意要选择Define Guid{….}格式，并全//部保存下来，保存到哪都行，记事本呀什么的。//因为在做VC程序/////////的时候要用到的。[Guid(&quot;298D881C-E2A3-4638-B872-73EADE25511C&quot;)]  public interface AddComInterface{[DispId(1)]int iadd(int a, int b);[DispId(2)]float ladd(float a, float b);}[Guid(&quot;2C5B7580-4038-4d90-BABD-8B83FCE5A467&quot;)][ClassInterface(ClassInterfaceType.None)]public class AddComService : AddComInterface{public AddComService(){}public int iadd(int a, int b){int c = 0;c = a + b;return c;}public float ladd(float a, float b){float c = 0;c = a + b;return c;}}}（2）VC6.0编写调用程序使用VC6.0编写建立MFC应用程序UseCom，项目类型为MFC AppWizard(exe)在stdafx.h添加：#import &quot;AddCom.tlb&quot;using namespace AddCom;程序代码：void CUseComDlg::OnButtonUse() {// TODO: Add your control notification handler code hereint dresult;float fresult;CString strResult;CoInitialize(NULL);//NULL换成0也可以AddCom::AddComInterfacePtr p_Add(__uuidof(AddComService));dresult = p_Add-&gt;iadd(1,2);fresult = p_Add-&gt;fadd(1.2,2.3);strResult.Format(&quot;int:%d \nfloat:%f&quot;,dresult,fresult);MessageBox(strResult,&quot;计算结果&quot;,MB_OK);CoUninitialize();   }3、在VC6.0中编写COM组件，使用VS2005 C#调用（1）VC6.0编写COM使用VC6.0建立COM组件，工程类型：ATL COM AppWizard程序代码：接口：interface IAdd : IDispatch{[id(1), helpstring(&quot;method iadd&quot;)] HRESULT iadd([in]int a, [in]int b, [out]int * c);[id(2), helpstring(&quot;method fadd&quot;)] HRESULT fadd([in]float a, [in]float b, [out]float * c);[id(3), helpstring(&quot;method isub&quot;)] HRESULT isub([in]int a, [in]int b, [out]int * c);};实现：STDMETHODIMP CAdd::iadd(int a, int b, int *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::fadd(float a, float b, float *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::isub(int a, int b, int *c){// TODO: Add your implementation code here*c = a - b;return S_OK;}（2）VS2005使用C#编写调用程序（网站程序）使用VS2005建立网站UseCom配置：在解决方案资源管理器中的主目录点击右键，选择添加引用，选择COM，添加刚刚建立的AddCom 1.0 Type Library在程序中要using编写的COM组件：using ADDCOMLib;图三 引用COM程序代码：using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using ADDCOMLib;public partial class _Default : System.Web.UI.Page {protected void Page_Load(object sender, EventArgs e){}protected void ButtonCom_Click(object sender, EventArgs e){Add add = new Add();int iresult;float fresult;int sresult;add.IAdd(10, 20, out iresult);add.fadd((float)1.2,(float)2.3, out fresult);add.isub(100, 10, out sresult);TextBoxResult.Text = iresult.ToString();TextBoxRe2.Text = fresult.ToString();TextBoxRe3.Text = sresult.ToString();}}4、在VC6.0中编写COM组件，使用VC6.0调用（1）VC6.0编写COM组件使用VC6.0建立COM组件，工程类型：ATL COM AppWizard程序代码：接口：interface IAdd : IDispatch{[id(1), helpstring(&quot;method iadd&quot;)] HRESULT iadd([in]int a, [in]int b, [out]int * c);[id(2), helpstring(&quot;method fadd&quot;)] HRESULT fadd([in]float a, [in]float b, [out]float * c);[id(3), helpstring(&quot;method isub&quot;)] HRESULT isub([in]int a, [in]int b, [out]int * c);};实现：STDMETHODIMP CAdd::iadd(int a, int b, int *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::fadd(float a, float b, float *c){// TODO: Add your implementation code here*c = a + b;return S_OK;}STDMETHODIMP CAdd::isub(int a, int b, int *c){// TODO: Add your implementation code here*c = a - b;return S_OK;}（2）VC6.0编写调用程序使用VC6.0建立MFC应用程序UseCOM，调用刚刚建立的COM组件将上面程序AddCom生成的AddCom.dll放入本程序的工程目录和程序生成目录中在StdAfx.h中加入：#import &quot;AddCom.dll&quot; no_namespace程序代码：void CUseComDlg::OnBUTTONUse() {// TODO: Add your control notification handler code hereCString strResult;CoInitialize(NULL);//NULL换成0也可以IAddPtr m_add = NULL;HRESULT hr = S_OK;hr = m_add.CreateInstance(__uuidof(Add));int d_a = 90;int d_b = 10;int d_c;int d_d;float f_a = 1;float f_b = 2;float f_c;m_add-&gt;_IAdd(d_a,d_b,&amp;d_c);m_add-&gt;fadd(f_a,f_b,&amp;f_c);m_add-&gt;isub(d_a,d_b,&amp;d_d);strResult.Format(&quot;返回结果：%d; %f; %d&quot;,d_c,f_c,d_d);MessageBox(strResult,&quot;结果&quot;,MB_OK);m_add.Release();m_add = NULL;CoUninitialize();   }结束语：希望能对大家有帮助！]]></description><PubDate>2009-2-11 0:00:00</PubDate><category>C/C++教程</category></item><item><title>经典c程序100例(11--20)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16846</link><subject>【程序11】题目：古典问题：有一对兔子，从出生后第3个月起每个月都生一对兔子，小兔子长到第三个月　　　后每个月又生一对兔子，假如兔子都不死，问每个月的兔子总数为多少？1.程序分析：　兔子的规律为数列1,1,2,3,5,8,13,21....2.程序源代码：main(){long f1,f2;int i;f1=f2=1;for(i=1;i&lt;=20;i++)　{ printf(&quot;%12ld %12ld&quot;,f1,f2);　　　if(i%2==0) printf(&quot;\n&quot;);/*控制输出，每行四个*/　　　f1=f1+f2; /*前两个月加起来赋值给第三个月*/　　　f2=f1+f2; /*前两个月加起来赋值给第三个月*/　}}==============================================================【程序12】题目：判断101-200之间有多少个素数，并输出所有素数。1.程序分析：判断素数的方法：用一个数分别去除2到sqrt(这个数)，如果能被整除，　　　　　　则表明此数不是素数，反之是素数。 　　　　　　2.程序源代码：#include &quot;math.h&quot;main(){　int m,i,k,h=0,leap=1;　printf(&quot;\n&quot;);　for(m=101;m&lt;=200;m++)　　{ k=sqrt(m+1);　　　for(i=2;i&lt;=k;i++)　　　　　if(m%i==0)　　　　　　{leap=0;break;}　　　if(leap) {printf(&quot;%-4d&quot;,m);h++;　　　　　　　　if(h%10==0)　　　　　　　　printf(&quot;\n&quot;);　　　　　　　 }　　　leap=1;　　}　printf(&quot;\nThe total is %d&quot;,h);}==============================================================【程序13】题目：打印出所有的“水仙花数”，所谓“水仙花数”是指一个三位数，其各位数字立方和等于该数　　　本身。例如：153是一个“水仙花数”，因为153=1的三次方＋5的三次方＋3的三次方。1.程序分析：利用for循环控制100-999个数，每个数分解出个位，十位，百位。2.程序源代码：main(){int i,j,k,n;printf(&quot;water flowernumber is:&quot;);　for(n=100;n&lt;1000;n++)　{　　i=n/100;/*分解出百位*/　　j=n/10%10;/*分解出十位*/　　k=n%10;/*分解出个位*/　　if(i*100+j*10+k==i*i*i+j*j*j+k*k*k)　　　{　　　printf(&quot;%-5d&quot;,n);　　　}　}printf(&quot;\n&quot;);}==============================================================【程序14】题目：将一个正整数分解质因数。例如：输入90,打印出90=2*3*3*5。程序分析：对n进行分解质因数，应先找到一个最小的质数k，然后按下述步骤完成： (1)如果这个质数恰等于n，则说明分解质因数的过程已经结束，打印出即可。(2)如果n&lt;&gt;k，但n能被k整除，则应打印出k的值，并用n除以k的商,作为新的正整数你n,　重复执行第一步。(3)如果n不能被k整除，则用k+1作为k的值,重复执行第一步。2.程序源代码：/* zheng int is divided yinshu*/main(){int n,i;printf(&quot;\nplease input a number:\n&quot;);scanf(&quot;%d&quot;,&amp;n);printf(&quot;%d=&quot;,n);for(i=2;i&lt;=n;i++)　{　　while(n!=i)　　{　　　if(n%i==0)　　　{ printf(&quot;%d*&quot;,i);　　　　n=n/i;　　　}　　　else　　　　break;　　}}printf(&quot;%d&quot;,n);}==============================================================【程序15】题目：利用条件运算符的嵌套来完成此题：学习成绩&gt;=90分的同学用A表示，60-89分之间的用B表示，　　　60分以下的用C表示。1.程序分析：(a&gt;b)?a:b这是条件运算符的基本例子。2.程序源代码：main(){　int score;　char grade;　printf(&quot;please input a score\n&quot;);　scanf(&quot;%d&quot;,&amp;score);　grade=score&gt;=90?A:(score&gt;=60?B:C);　printf(&quot;%d belongs to %c&quot;,score,grade);}==============================================================【程序16】题目：输入两个正整数m和n，求其最大公约数和最小公倍数。1.程序分析：利用辗除法。2.程序源代码：main(){　int a,b,num1,num2,temp;　printf(&quot;please input two numbers:\n&quot;);　scanf(&quot;%d,%d&quot;,&amp;num1,&amp;num2);　if(num1 　{ temp=num1;　　num1=num2;　　　num2=temp;　}a=num1;b=num2;while(b!=0)/*利用辗除法，直到b为0为止*/　{　　temp=a%b;　　a=b;　　b=temp;　}printf(&quot;gongyueshu:%d\n&quot;,a);printf(&quot;gongbeishu:%d\n&quot;,num1*num2/a);}==============================================================【程序17】题目：输入一行字符，分别统计出其中英文字母、空格、数字和其它字符的个数。1.程序分析：利用while语句,条件为输入的字符不为\n.　　　　　　2.程序源代码：#include &quot;stdio.h&quot;main(){char c;　int letters=0,space=0,digit=0,others=0;　printf(&quot;please input some characters\n&quot;);　while((c=getchar())!=\n)　{　if(c&gt;=a&amp;&amp;c&lt;=z||c&gt;=A&amp;&amp;c&lt;=Z)　　letters++;　else if(c== )　　space++;　　　else if(c&gt;=0&amp;&amp;c&lt;=9)　　　　　　　digit++;　　　　　else　　　　　　　others++;}printf(&quot;all in all:char=%d space=%d digit=%d others=%d\n&quot;,letters,space,digit,others);}==============================================================【程序18】题目：求s=a+aa+aaa+aaaa+aa...a的值，其中a是一个数字。例如2+22+222+2222+22222(此时　　　共有5个数相加)，几个数相加有键盘控制。1.程序分析：关键是计算出每一项的值。2.程序源代码：main(){　int a,n,count=1;　long int sn=0,tn=0;　printf(&quot;please input a and n\n&quot;);　scanf(&quot;%d,%d&quot;,&amp;a,&amp;n);　printf(&quot;a=%d,n=%d\n&quot;,a,n);　while(count&lt;=n)　{　　tn=tn+a;　　sn=sn+tn;　　a=a*10;　　++count;　}printf(&quot;a+aa+...=%ld\n&quot;,sn);}==============================================================【程序19】题目：一个数如果恰好等于它的因子之和，这个数就称为“完数”。例如6=1＋2＋3.编程　　　找出1000以内的所有完数。1. 程序分析：请参照程序&lt;--上页程序14. 2.程序源代码：main(){static int k[10];int i,j,n,s;for(j=2;j&lt;1000;j++)　{　n=-1;　s=j;　　for(i=1;i 　　{　　　if((j%i)==0)　　　{　n++;　　　　s=s-i;　　　　k[n]=i;　　　}　　}　if(s==0)　{　printf(&quot;%d is a wanshu&quot;,j);　for(i=0;i 　printf(&quot;%d,&quot;,k[i]);　printf(&quot;%d\n&quot;,k[n]);　}}}============================================================== 【程序20】题目：一球从100米高度自由落下，每次落地后反跳回原高度的一半；再落下，求它在　　　第10次落地时，共经过多少米？第10次反弹多高？1.程序分析：见下面注释2.程序源代码：main(){float sn=100.0,hn=sn/2;int n;for(n=2;n&lt;=10;n++)　{　　sn=sn+2*hn;/*第n次落地时共经过的米数*/　　hn=hn/2; /*第n次反跳高度*/　}printf(&quot;the total of road is %f\n&quot;,sn);printf(&quot;the tenth is %f meter\n&quot;,hn);}</subject><description><![CDATA[【程序11】题目：古典问题：有一对兔子，从出生后第3个月起每个月都生一对兔子，小兔子长到第三个月　　　后每个月又生一对兔子，假如兔子都不死，问每个月的兔子总数为多少？1.程序分析：　兔子的规律为数列1,1,2,3,5,8,13,21....2.程序源代码：main(){long f1,f2;int i;f1=f2=1;for(i=1;i&lt;=20;i++)　{ printf(&quot;%12ld %12ld&quot;,f1,f2);　　　if(i%2==0) printf(&quot;\n&quot;);/*控制输出，每行四个*/　　　f1=f1+f2; /*前两个月加起来赋值给第三个月*/　　　f2=f1+f2; /*前两个月加起来赋值给第三个月*/　}}==============================================================【程序12】题目：判断101-200之间有多少个素数，并输出所有素数。1.程序分析：判断素数的方法：用一个数分别去除2到sqrt(这个数)，如果能被整除，　　　　　　则表明此数不是素数，反之是素数。 　　　　　　2.程序源代码：#include &quot;math.h&quot;main(){　int m,i,k,h=0,leap=1;　printf(&quot;\n&quot;);　for(m=101;m&lt;=200;m++)　　{ k=sqrt(m+1);　　　for(i=2;i&lt;=k;i++)　　　　　if(m%i==0)　　　　　　{leap=0;break;}　　　if(leap) {printf(&quot;%-4d&quot;,m);h++;　　　　　　　　if(h%10==0)　　　　　　　　printf(&quot;\n&quot;);　　　　　　　 }　　　leap=1;　　}　printf(&quot;\nThe total is %d&quot;,h);}==============================================================【程序13】题目：打印出所有的“水仙花数”，所谓“水仙花数”是指一个三位数，其各位数字立方和等于该数　　　本身。例如：153是一个“水仙花数”，因为153=1的三次方＋5的三次方＋3的三次方。1.程序分析：利用for循环控制100-999个数，每个数分解出个位，十位，百位。2.程序源代码：main(){int i,j,k,n;printf(&quot;water flowernumber is:&quot;);　for(n=100;n&lt;1000;n++)　{　　i=n/100;/*分解出百位*/　　j=n/10%10;/*分解出十位*/　　k=n%10;/*分解出个位*/　　if(i*100+j*10+k==i*i*i+j*j*j+k*k*k)　　　{　　　printf(&quot;%-5d&quot;,n);　　　}　}printf(&quot;\n&quot;);}==============================================================【程序14】题目：将一个正整数分解质因数。例如：输入90,打印出90=2*3*3*5。程序分析：对n进行分解质因数，应先找到一个最小的质数k，然后按下述步骤完成： (1)如果这个质数恰等于n，则说明分解质因数的过程已经结束，打印出即可。(2)如果n&lt;&gt;k，但n能被k整除，则应打印出k的值，并用n除以k的商,作为新的正整数你n,　重复执行第一步。(3)如果n不能被k整除，则用k+1作为k的值,重复执行第一步。2.程序源代码：/* zheng int is divided yinshu*/main(){int n,i;printf(&quot;\nplease input a number:\n&quot;);scanf(&quot;%d&quot;,&amp;n);printf(&quot;%d=&quot;,n);for(i=2;i&lt;=n;i++)　{　　while(n!=i)　　{　　　if(n%i==0)　　　{ printf(&quot;%d*&quot;,i);　　　　n=n/i;　　　}　　　else　　　　break;　　}}printf(&quot;%d&quot;,n);}==============================================================【程序15】题目：利用条件运算符的嵌套来完成此题：学习成绩&gt;=90分的同学用A表示，60-89分之间的用B表示，　　　60分以下的用C表示。1.程序分析：(a&gt;b)?a:b这是条件运算符的基本例子。2.程序源代码：main(){　int score;　char grade;　printf(&quot;please input a score\n&quot;);　scanf(&quot;%d&quot;,&amp;score);　grade=score&gt;=90?A:(score&gt;=60?B:C);　printf(&quot;%d belongs to %c&quot;,score,grade);}==============================================================【程序16】题目：输入两个正整数m和n，求其最大公约数和最小公倍数。1.程序分析：利用辗除法。2.程序源代码：main(){　int a,b,num1,num2,temp;　printf(&quot;please input two numbers:\n&quot;);　scanf(&quot;%d,%d&quot;,&amp;num1,&amp;num2);　if(num1 　{ temp=num1;　　num1=num2;　　　num2=temp;　}a=num1;b=num2;while(b!=0)/*利用辗除法，直到b为0为止*/　{　　temp=a%b;　　a=b;　　b=temp;　}printf(&quot;gongyueshu:%d\n&quot;,a);printf(&quot;gongbeishu:%d\n&quot;,num1*num2/a);}==============================================================【程序17】题目：输入一行字符，分别统计出其中英文字母、空格、数字和其它字符的个数。1.程序分析：利用while语句,条件为输入的字符不为\n.　　　　　　2.程序源代码：#include &quot;stdio.h&quot;main(){char c;　int letters=0,space=0,digit=0,others=0;　printf(&quot;please input some characters\n&quot;);　while((c=getchar())!=\n)　{　if(c&gt;=a&amp;&amp;c&lt;=z||c&gt;=A&amp;&amp;c&lt;=Z)　　letters++;　else if(c== )　　space++;　　　else if(c&gt;=0&amp;&amp;c&lt;=9)　　　　　　　digit++;　　　　　else　　　　　　　others++;}printf(&quot;all in all:char=%d space=%d digit=%d others=%d\n&quot;,letters,space,digit,others);}==============================================================【程序18】题目：求s=a+aa+aaa+aaaa+aa...a的值，其中a是一个数字。例如2+22+222+2222+22222(此时　　　共有5个数相加)，几个数相加有键盘控制。1.程序分析：关键是计算出每一项的值。2.程序源代码：main(){　int a,n,count=1;　long int sn=0,tn=0;　printf(&quot;please input a and n\n&quot;);　scanf(&quot;%d,%d&quot;,&amp;a,&amp;n);　printf(&quot;a=%d,n=%d\n&quot;,a,n);　while(count&lt;=n)　{　　tn=tn+a;　　sn=sn+tn;　　a=a*10;　　++count;　}printf(&quot;a+aa+...=%ld\n&quot;,sn);}==============================================================【程序19】题目：一个数如果恰好等于它的因子之和，这个数就称为“完数”。例如6=1＋2＋3.编程　　　找出1000以内的所有完数。1. 程序分析：请参照程序&lt;--上页程序14. 2.程序源代码：main(){static int k[10];int i,j,n,s;for(j=2;j&lt;1000;j++)　{　n=-1;　s=j;　　for(i=1;i 　　{　　　if((j%i)==0)　　　{　n++;　　　　s=s-i;　　　　k[n]=i;　　　}　　}　if(s==0)　{　printf(&quot;%d is a wanshu&quot;,j);　for(i=0;i 　printf(&quot;%d,&quot;,k[i]);　printf(&quot;%d\n&quot;,k[n]);　}}}============================================================== 【程序20】题目：一球从100米高度自由落下，每次落地后反跳回原高度的一半；再落下，求它在　　　第10次落地时，共经过多少米？第10次反弹多高？1.程序分析：见下面注释2.程序源代码：main(){float sn=100.0,hn=sn/2;int n;for(n=2;n&lt;=10;n++)　{　　sn=sn+2*hn;/*第n次落地时共经过的米数*/　　hn=hn/2; /*第n次反跳高度*/　}printf(&quot;the total of road is %f\n&quot;,sn);printf(&quot;the tenth is %f meter\n&quot;,hn);}]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C++指针使用方法解惑</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16853</link><subject>在下列函数声明中，为什么要同时使用*和&amp;符号？以及什么场合使用这种声明方式? void func1（ MYCLASS *&amp;pBuildingElement ）； 论坛中经常有人问到这样的问题。本文试图通过一些实际的指针使用经验来解释这个问题。
仔细看一下这种声明方式，确实有点让人迷惑。在某种意义上，&quot;*&quot;和&quot;&amp;&quot;是意思相对的两个东西，把它们放在一起有什么意义呢？。为了理解指针的这种做法，我们先复习一下C/C++编程中无所不在的指针概念。我们都知道MYCLASS*的意思：指向某个对象的指针，此对象的类型为MYCLASS。 Void func1（MYCLASS *pMyClass）； // 例如： MYCLASS* p = new MYCLASS；
func1（p）； 
上面这段代码的这种处理方法想必谁都用过，创建一个MYCLASS对象，然后将它传入func1函数。现在假设此函数要修改pMyClass： void func1（MYCLASS *pMyClass）
{
DoSomething（pMyClass）；
pMyClass = // 其它对象的指针
} 第二条语句在函数过程中只修改了pMyClass的值。并没有修改调用者的变量p的值。如果p指向某个位于地址0x008a00的对象，当func1返回时，它仍然指向这个特定的对象。（除非func1有bug将堆弄乱了，完全有这种可能。）现在假设你想要在func1中修改p的值。这是你的权利。调用者传入一个指针，然后函数给这个指针赋值。以往一般都是传双指针，即指针的指针，例如，CMyClass**。MYCLASS* p = NULL；
func1（&amp;p）；void func1（MYCLASS** pMyClass）；
{
*pMyClass = new MYCLASS；
……
}调用func1之后，p指向新的对象。在COM编程中，你到处都会碰到这样的用法--例如在查询对象接口的QueryInterface函数中：interface ISomeInterface { 
HRESULT QueryInterface（IID &amp;iid, void** ppvObj）； 
…… 
}； 
LPSOMEINTERFACE p=NULL； 
pOb-&gt;QueryInterface（IID_SOMEINTERFACE, &amp;p）；  此处，p是SOMEINTERFACE类型的指针，所以&amp;p便是指针的指针，在QueryInterface返回的时候，如果调用成功，则变量p包含一个指向新的接口的指针。如果你理解指针的指针，那么你肯定就理解指针引用，因为它们完全是一回事。如果你象下面这样声明函数：void func1（MYCLASS *&amp;pMyClass）；
{
pMyClass = new MYCLASS； 
……
} 其实，它和前面所讲得指针的指针例子是一码事，只是语法有所不同。传递的时候不用传p的地址&amp;p，而是直接传p本身：MYCLASS* p = NULL；func1（p）；在调用之后，p指向一个新的对象。一般来讲，引用的原理或多或少就象一个指针，从语法上看它就是一个普通变量。所以只要你碰到*&amp;，就应该想到**。也就是说这个函数修改或可能修改调用者的指针，而调用者象普通变量一样传递这个指针，不使用地址操作符&amp;。至于说什么场合要使用这种方法，我会说，极少。MFC在其集合类中用到了它--例如，CObList，它是一个Cobjects指针列表。Class CObList : public Cobject {
……// 获取/修改指定位置的元素
Cobject*&amp; GetAt（POSITION position）；
Cobject* GetAt（POSITION position） const；
}；这里有两个GetAt函数，功能都是获取给定位置的元素。区别何在呢？区别在于一个让你修改列表中的对象，另一个则不行。所以如果你写成下面这样： Cobject* pObj = mylist.GetAt（pos）；则pObj是列表中某个对象的指针，如果接着改变pObj的值： pObj = pSomeOtherObj；这并改变不了在位置pos处的对象地址，而仅仅是改变了变量pObj。但是，如果你写成下面这样： Cobject*&amp; rpObj = mylist.GetAt（pos）；现在，rpObj是引用一个列表中的对象的指针，所以当改变rpObj时，也会改变列表中位置pos处的对象地址--换句话说，替代了这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的值，另一个则不能。注意我在此说的是指针，不是对象本身。这两个函数都可以修改对象，但只有*&amp;版本可以替代对象。 在C/C++中引用是很重要的，同时也是高效的处理手段。所以要想成为C/C++高手，对引用的概念没有透彻的理解和熟练的应用是不行的。</subject><description><![CDATA[在下列函数声明中，为什么要同时使用*和&amp;符号？以及什么场合使用这种声明方式? void func1（ MYCLASS *&amp;pBuildingElement ）； 论坛中经常有人问到这样的问题。本文试图通过一些实际的指针使用经验来解释这个问题。
仔细看一下这种声明方式，确实有点让人迷惑。在某种意义上，&quot;*&quot;和&quot;&amp;&quot;是意思相对的两个东西，把它们放在一起有什么意义呢？。为了理解指针的这种做法，我们先复习一下C/C++编程中无所不在的指针概念。我们都知道MYCLASS*的意思：指向某个对象的指针，此对象的类型为MYCLASS。 Void func1（MYCLASS *pMyClass）； // 例如： MYCLASS* p = new MYCLASS；
func1（p）； 
上面这段代码的这种处理方法想必谁都用过，创建一个MYCLASS对象，然后将它传入func1函数。现在假设此函数要修改pMyClass： void func1（MYCLASS *pMyClass）
{
DoSomething（pMyClass）；
pMyClass = // 其它对象的指针
} 第二条语句在函数过程中只修改了pMyClass的值。并没有修改调用者的变量p的值。如果p指向某个位于地址0x008a00的对象，当func1返回时，它仍然指向这个特定的对象。（除非func1有bug将堆弄乱了，完全有这种可能。）现在假设你想要在func1中修改p的值。这是你的权利。调用者传入一个指针，然后函数给这个指针赋值。以往一般都是传双指针，即指针的指针，例如，CMyClass**。MYCLASS* p = NULL；
func1（&amp;p）；void func1（MYCLASS** pMyClass）；
{
*pMyClass = new MYCLASS；
……
}调用func1之后，p指向新的对象。在COM编程中，你到处都会碰到这样的用法--例如在查询对象接口的QueryInterface函数中：interface ISomeInterface { 
HRESULT QueryInterface（IID &amp;iid, void** ppvObj）； 
…… 
}； 
LPSOMEINTERFACE p=NULL； 
pOb-&gt;QueryInterface（IID_SOMEINTERFACE, &amp;p）；  此处，p是SOMEINTERFACE类型的指针，所以&amp;p便是指针的指针，在QueryInterface返回的时候，如果调用成功，则变量p包含一个指向新的接口的指针。如果你理解指针的指针，那么你肯定就理解指针引用，因为它们完全是一回事。如果你象下面这样声明函数：void func1（MYCLASS *&amp;pMyClass）；
{
pMyClass = new MYCLASS； 
……
} 其实，它和前面所讲得指针的指针例子是一码事，只是语法有所不同。传递的时候不用传p的地址&amp;p，而是直接传p本身：MYCLASS* p = NULL；func1（p）；在调用之后，p指向一个新的对象。一般来讲，引用的原理或多或少就象一个指针，从语法上看它就是一个普通变量。所以只要你碰到*&amp;，就应该想到**。也就是说这个函数修改或可能修改调用者的指针，而调用者象普通变量一样传递这个指针，不使用地址操作符&amp;。至于说什么场合要使用这种方法，我会说，极少。MFC在其集合类中用到了它--例如，CObList，它是一个Cobjects指针列表。Class CObList : public Cobject {
……// 获取/修改指定位置的元素
Cobject*&amp; GetAt（POSITION position）；
Cobject* GetAt（POSITION position） const；
}；这里有两个GetAt函数，功能都是获取给定位置的元素。区别何在呢？区别在于一个让你修改列表中的对象，另一个则不行。所以如果你写成下面这样： Cobject* pObj = mylist.GetAt（pos）；则pObj是列表中某个对象的指针，如果接着改变pObj的值： pObj = pSomeOtherObj；这并改变不了在位置pos处的对象地址，而仅仅是改变了变量pObj。但是，如果你写成下面这样： Cobject*&amp; rpObj = mylist.GetAt（pos）；现在，rpObj是引用一个列表中的对象的指针，所以当改变rpObj时，也会改变列表中位置pos处的对象地址--换句话说，替代了这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的值，另一个则不能。注意我在此说的是指针，不是对象本身。这两个函数都可以修改对象，但只有*&amp;版本可以替代对象。 在C/C++中引用是很重要的，同时也是高效的处理手段。所以要想成为C/C++高手，对引用的概念没有透彻的理解和熟练的应用是不行的。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>用C++实现简单的文件I/O操作</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16873</link><subject>文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里，我会详细解释ASCII和二进制文件的输入输出的每个细节，值得注意的是，所有这些都是用C++完成的。一、ASCII 输出为了使用下面的方法, 你必须包含头文件&lt;fstream.h&gt;(译者注：在标准C++中，已经使用&lt;fstream&gt;取代&lt; fstream.h&gt;，所有的C++标准头文件都是无后缀的。)。这是 &lt;iostream.h&gt;的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, &lt;iostream.h&gt; 已经被&lt;fstream.h&gt;包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们，那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。如果你猜是&quot;fstream,&quot; 恭喜你答对了！ 但这篇文章介绍的方法,我们分别使用&quot;ifstream&quot;?和 &quot;ofstream&quot; 来作输入输出。如果你用过标准控制台流&quot;cin&quot;?和 &quot;cout,&quot; 那现在的事情对你来说很简单。 我们现在开始讲输出部分，首先声明一个类对象。ofstream fout;这就可以了，不过你要打开一个文件的话, 必须像这样调用ofstream::open()。fout.open(&quot;output.txt&quot;);你也可以把文件名作为构造参数来打开一个文件.ofstream fout(&quot;output.txt&quot;);这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在，它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件，看起来和&quot;cout&quot;的操作很像。 对不了解控制台输出&quot;cout&quot;的人, 这里有个例子。int num = 150;
char name[] = &quot;John Doe&quot;;
fout &lt;&lt; &quot;Here is a number: &quot; &lt;&lt; num &lt;&lt; &quot;\n&quot;;
fout &lt;&lt; &quot;Now here is a string: &quot; &lt;&lt; name &lt;&lt; &quot;\n&quot;;现在保存文件，你必须关闭文件，或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它，它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样：fout &lt;&lt; flush; fout.close();现在你用文本编辑器打开文件，内容看起来是这样：Here is a number: 150 Now here is a string: John Doe很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作，对 &quot;&lt;&lt;&quot; 和&quot;&gt;&gt;&quot; 比较熟悉了, 因为你接下来还要用到他们。继续…二、ASCII 输入输入和&quot;cin&quot; 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本：12 GameDev 15.45 L This is really awesome!为了打开这个文件，你必须创建一个in-stream对象,?像这样。ifstream fin(&quot;input.txt&quot;);现在读入前四行. 你还记得怎么用&quot;&lt;&lt;&quot; 操作符往流里插入变量和符号吧？好,?在 &quot;&lt;&lt;&quot; (插入)?操作符之后，是&quot;&gt;&gt;&quot; (提取) 操作符. 使用方法是一样的. 看这个代码片段.int number;
float real;
char letter, word[8];
fin &gt;&gt; number; fin &gt;&gt; word; fin &gt;&gt; real; fin &gt;&gt; letter;也可以把这四行读取文件的代码写为更简单的一行。fin &gt;&gt; number &gt;&gt; word &gt;&gt; real &gt;&gt; letter;它是如何运作的呢? 文件的每个空白之后, &quot;&gt;&gt;&quot; 操作符会停止读取内容, 直到遇到另一个&gt;&gt;操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), &quot;&gt;&gt;&quot; 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是，可别忘了文件的最后一行。This is really awesome!如果你想把整行读入一个char数组, 我们没办法用&quot;&gt;&gt;&quot;?操作符，因为每个单词之间的空格（空白字符）会中止文件的读取。为了验证：char sentence[101]; fin &gt;&gt; sentence;我们想包含整个句子, &quot;This is really awesome!&quot; 但是因为空白, 现在它只包含了&quot;This&quot;. 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。fin.getline(sentence, 100);这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前，数组允许接受的最大元素数量. 现在我们得到了想要的结果：“This is really awesome!”。你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休，因为二进制文件还在等着我们。三、二进制 输入输出二进制文件会复杂一点, 但还是很简单的。首先你要注意我们不再使用插入和提取操作符(译者注：&lt;&lt; 和 &gt;&gt; 操作符). 你可以这么做，但它不会用二进制方式读写。你必须使用read() 和write() 方法读取和写入二进制文件. 创建一个二进制文件, 看下一行。ofstream fout(&quot;file.dat&quot;, ios::binary);这会以二进制方式打开文件, 而不是默认的ASCII模式。首先从写入文件开始。函数write() 有两个参数。 第一个是指向对象的char类型的指针, 第二个是对象的大小（译者注：字节数）。 为了说明，看例子。int number = 30; fout.write((char *)(&amp;number), sizeof(number));第一个参数写做&quot;(char *)(&amp;number)&quot;. 这是把一个整型变量转为char *指针。如果你不理解，可以立刻翻阅C++的书籍，如果有必要的话。第二个参数写作&quot;sizeof(number)&quot;. sizeof() 返回对象大小的字节数. 就是这样!二进制文件最好的地方是可以在一行把一个结构写入文件。 如果说，你的结构有12个不同的成员。 用ASCII?文件，你不得不每次一条的写入所有成员。 但二进制文件替你做好了。 看这个。struct OBJECT { int number; char letter; } obj;
obj.number = 15;
obj.letter = ‘M’;
fout.write((char *)(&amp;obj), sizeof(obj));这样就写入了整个结构! 接下来是输入. 输入也很简单，因为read()?函数的参数和 write()是完全一样的, 使用方法也相同。ifstream fin(&quot;file.dat&quot;, ios::binary); fin.read((char *)(&amp;obj), sizeof(obj));我不多解释用法, 因为它和write()是完全相同的。二进制文件比ASCII文件简单, 但有个缺点是无法用文本编辑器编辑。 接着, 我解释一下ifstream 和ofstream 对象的其他一些方法作为结束.四、更多方法我已经解释了ASCII文件和二进制文件, 这里是一些没有提及的底层方法。检查文件你已经学会了open() 和close() 方法, 不过这里还有其它你可能用到的方法。方法good() 返回一个布尔值，表示文件打开是否正确。类似的，bad() 返回一个布尔值表示文件打开是否错误。 如果出错，就不要继续进一步的操作了。最后一个检查的方法是fail(), 和bad()有点相似, 但没那么严重。读文件方法get() 每次返回一个字符。方法ignore(int,char) 跳过一定数量的某个字符, 但你必须传给它两个参数。第一个是需要跳过的字符数。 第二个是一个字符, 当遇到的时候就会停止。 例子,fin.ignore(100, ‘\n’);会跳过100个字符，或者不足100的时候，跳过所有之前的字符，包括 ‘\n’。方法peek() 返回文件中的下一个字符, 但并不实际读取它。所以如果你用peek() 查看下一个字符, 用get() 在peek()之后读取，会得到同一个字符, 然后移动文件计数器。方法putback(char) 输入字符, 一次一个, 到流中。我没有见到过它的使用，但这个函数确实存在。写文件只有一个你可能会关注的方法.?那就是 put(char), 它每次向输出流中写入一个字符。打开文件当我们用这样的语法打开二进制文件:ofstream fout(&quot;file.dat&quot;, ios::binary);&quot;ios::binary&quot;是你提供的打开选项的额外标志. 默认的, 文件以ASCII方式打开, 不存在则创建, 存在就覆盖. 这里有些额外的标志用来改变选项。ios::app 添加到文件尾ios::ate 把文件标志放在末尾而非起始。ios::trunc 默认. 截断并覆写文件。ios::nocreate 文件不存在也不创建。ios::noreplace 文件存在则失败。文件状态我用过的唯一一个状态函数是eof(), 它返回是否标志已经到了文件末尾。 我主要用在循环中。 例如, 这个代码断统计小写‘e’ 在文件中出现的次数。ifstream fin(&quot;file.txt&quot;);
char ch; int counter;
while (!fin.eof()) {
ch = fin.get();
if (ch == ‘e’) counter++;
}
fin.close();我从未用过这里没有提到的其他方法。 还有很多方法，但是他们很少被使用。参考C++书籍或者文件流的帮助文档来了解其他的方法。结论你应该已经掌握了如何使用ASCII文件和二进制文件。有很多方法可以帮你实现输入输出，尽管很少有人使用他们。我知道很多人不熟悉文件I/O操作，我希望这篇文章对你有所帮助。 每个人都应该知道. 文件I/O还有很多显而易见的方法,?例如包含文件 &lt;stdio.h&gt;. 我更喜欢用流是因为他们更简单。 祝所有读了这篇文章的人好运, 也许以后我还会为你们写些东西。</subject><description><![CDATA[文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里，我会详细解释ASCII和二进制文件的输入输出的每个细节，值得注意的是，所有这些都是用C++完成的。一、ASCII 输出为了使用下面的方法, 你必须包含头文件&lt;fstream.h&gt;(译者注：在标准C++中，已经使用&lt;fstream&gt;取代&lt; fstream.h&gt;，所有的C++标准头文件都是无后缀的。)。这是 &lt;iostream.h&gt;的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, &lt;iostream.h&gt; 已经被&lt;fstream.h&gt;包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们，那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。如果你猜是&quot;fstream,&quot; 恭喜你答对了！ 但这篇文章介绍的方法,我们分别使用&quot;ifstream&quot;?和 &quot;ofstream&quot; 来作输入输出。如果你用过标准控制台流&quot;cin&quot;?和 &quot;cout,&quot; 那现在的事情对你来说很简单。 我们现在开始讲输出部分，首先声明一个类对象。ofstream fout;这就可以了，不过你要打开一个文件的话, 必须像这样调用ofstream::open()。fout.open(&quot;output.txt&quot;);你也可以把文件名作为构造参数来打开一个文件.ofstream fout(&quot;output.txt&quot;);这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在，它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件，看起来和&quot;cout&quot;的操作很像。 对不了解控制台输出&quot;cout&quot;的人, 这里有个例子。int num = 150;
char name[] = &quot;John Doe&quot;;
fout &lt;&lt; &quot;Here is a number: &quot; &lt;&lt; num &lt;&lt; &quot;\n&quot;;
fout &lt;&lt; &quot;Now here is a string: &quot; &lt;&lt; name &lt;&lt; &quot;\n&quot;;现在保存文件，你必须关闭文件，或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它，它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样：fout &lt;&lt; flush; fout.close();现在你用文本编辑器打开文件，内容看起来是这样：Here is a number: 150 Now here is a string: John Doe很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作，对 &quot;&lt;&lt;&quot; 和&quot;&gt;&gt;&quot; 比较熟悉了, 因为你接下来还要用到他们。继续…二、ASCII 输入输入和&quot;cin&quot; 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本：12 GameDev 15.45 L This is really awesome!为了打开这个文件，你必须创建一个in-stream对象,?像这样。ifstream fin(&quot;input.txt&quot;);现在读入前四行. 你还记得怎么用&quot;&lt;&lt;&quot; 操作符往流里插入变量和符号吧？好,?在 &quot;&lt;&lt;&quot; (插入)?操作符之后，是&quot;&gt;&gt;&quot; (提取) 操作符. 使用方法是一样的. 看这个代码片段.int number;
float real;
char letter, word[8];
fin &gt;&gt; number; fin &gt;&gt; word; fin &gt;&gt; real; fin &gt;&gt; letter;也可以把这四行读取文件的代码写为更简单的一行。fin &gt;&gt; number &gt;&gt; word &gt;&gt; real &gt;&gt; letter;它是如何运作的呢? 文件的每个空白之后, &quot;&gt;&gt;&quot; 操作符会停止读取内容, 直到遇到另一个&gt;&gt;操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), &quot;&gt;&gt;&quot; 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是，可别忘了文件的最后一行。This is really awesome!如果你想把整行读入一个char数组, 我们没办法用&quot;&gt;&gt;&quot;?操作符，因为每个单词之间的空格（空白字符）会中止文件的读取。为了验证：char sentence[101]; fin &gt;&gt; sentence;我们想包含整个句子, &quot;This is really awesome!&quot; 但是因为空白, 现在它只包含了&quot;This&quot;. 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。fin.getline(sentence, 100);这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前，数组允许接受的最大元素数量. 现在我们得到了想要的结果：“This is really awesome!”。你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休，因为二进制文件还在等着我们。三、二进制 输入输出二进制文件会复杂一点, 但还是很简单的。首先你要注意我们不再使用插入和提取操作符(译者注：&lt;&lt; 和 &gt;&gt; 操作符). 你可以这么做，但它不会用二进制方式读写。你必须使用read() 和write() 方法读取和写入二进制文件. 创建一个二进制文件, 看下一行。ofstream fout(&quot;file.dat&quot;, ios::binary);这会以二进制方式打开文件, 而不是默认的ASCII模式。首先从写入文件开始。函数write() 有两个参数。 第一个是指向对象的char类型的指针, 第二个是对象的大小（译者注：字节数）。 为了说明，看例子。int number = 30; fout.write((char *)(&amp;number), sizeof(number));第一个参数写做&quot;(char *)(&amp;number)&quot;. 这是把一个整型变量转为char *指针。如果你不理解，可以立刻翻阅C++的书籍，如果有必要的话。第二个参数写作&quot;sizeof(number)&quot;. sizeof() 返回对象大小的字节数. 就是这样!二进制文件最好的地方是可以在一行把一个结构写入文件。 如果说，你的结构有12个不同的成员。 用ASCII?文件，你不得不每次一条的写入所有成员。 但二进制文件替你做好了。 看这个。struct OBJECT { int number; char letter; } obj;
obj.number = 15;
obj.letter = ‘M’;
fout.write((char *)(&amp;obj), sizeof(obj));这样就写入了整个结构! 接下来是输入. 输入也很简单，因为read()?函数的参数和 write()是完全一样的, 使用方法也相同。ifstream fin(&quot;file.dat&quot;, ios::binary); fin.read((char *)(&amp;obj), sizeof(obj));我不多解释用法, 因为它和write()是完全相同的。二进制文件比ASCII文件简单, 但有个缺点是无法用文本编辑器编辑。 接着, 我解释一下ifstream 和ofstream 对象的其他一些方法作为结束.四、更多方法我已经解释了ASCII文件和二进制文件, 这里是一些没有提及的底层方法。检查文件你已经学会了open() 和close() 方法, 不过这里还有其它你可能用到的方法。方法good() 返回一个布尔值，表示文件打开是否正确。类似的，bad() 返回一个布尔值表示文件打开是否错误。 如果出错，就不要继续进一步的操作了。最后一个检查的方法是fail(), 和bad()有点相似, 但没那么严重。读文件方法get() 每次返回一个字符。方法ignore(int,char) 跳过一定数量的某个字符, 但你必须传给它两个参数。第一个是需要跳过的字符数。 第二个是一个字符, 当遇到的时候就会停止。 例子,fin.ignore(100, ‘\n’);会跳过100个字符，或者不足100的时候，跳过所有之前的字符，包括 ‘\n’。方法peek() 返回文件中的下一个字符, 但并不实际读取它。所以如果你用peek() 查看下一个字符, 用get() 在peek()之后读取，会得到同一个字符, 然后移动文件计数器。方法putback(char) 输入字符, 一次一个, 到流中。我没有见到过它的使用，但这个函数确实存在。写文件只有一个你可能会关注的方法.?那就是 put(char), 它每次向输出流中写入一个字符。打开文件当我们用这样的语法打开二进制文件:ofstream fout(&quot;file.dat&quot;, ios::binary);&quot;ios::binary&quot;是你提供的打开选项的额外标志. 默认的, 文件以ASCII方式打开, 不存在则创建, 存在就覆盖. 这里有些额外的标志用来改变选项。ios::app 添加到文件尾ios::ate 把文件标志放在末尾而非起始。ios::trunc 默认. 截断并覆写文件。ios::nocreate 文件不存在也不创建。ios::noreplace 文件存在则失败。文件状态我用过的唯一一个状态函数是eof(), 它返回是否标志已经到了文件末尾。 我主要用在循环中。 例如, 这个代码断统计小写‘e’ 在文件中出现的次数。ifstream fin(&quot;file.txt&quot;);
char ch; int counter;
while (!fin.eof()) {
ch = fin.get();
if (ch == ‘e’) counter++;
}
fin.close();我从未用过这里没有提到的其他方法。 还有很多方法，但是他们很少被使用。参考C++书籍或者文件流的帮助文档来了解其他的方法。结论你应该已经掌握了如何使用ASCII文件和二进制文件。有很多方法可以帮你实现输入输出，尽管很少有人使用他们。我知道很多人不熟悉文件I/O操作，我希望这篇文章对你有所帮助。 每个人都应该知道. 文件I/O还有很多显而易见的方法,?例如包含文件 &lt;stdio.h&gt;. 我更喜欢用流是因为他们更简单。 祝所有读了这篇文章的人好运, 也许以后我还会为你们写些东西。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C/C++中的日期和时间</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16875</link><subject>撰文/周翔摘要：
本文从介绍基础概念入手，探讨了在C/C++中对日期和时间操作所用到的数据结构和函数，并对计时、时间的获取、时间的计算和显示格式等方面进行了阐述。本文还通过大量的实例向你展示了time.h头文件中声明的各种函数和数据结构的详细使用方法。关键字：UTC（世界标准时间），Calendar Time（日历时间），epoch（时间点），clock tick（时钟计时单元）1．概念
在C/C++中，对字符串的操作有很多值得注意的问题，同样，C/C++对时间的操作也有许多值得大家注意的地方。最近，在技术群中有很多网友也多次问到过C++语言中对时间的操作、获取和显示等等的问题。下面，在这篇文章中，笔者将主要介绍在C/C++中时间和日期的使用方法.通过学习许多C/C++库，你可以有很多操作、使用时间的方法。但在这之前你需要了解一些“时间”和“日期”的概念，主要有以下几个：Coordinated Universal Time（UTC）：协调世界时，又称为世界标准时间，也就是大家所熟知的格林威治标准时间（Greenwich Mean Time，GMT）。比如，中国内地的时间与UTC的时差为+8，也就是UTC+8。美国是UTC-5。Calendar Time：日历时间，是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同，但对一个编译系统来说，这个标准时间点是不变的，该编译系统中的时间对应的日历时间都通过该标准时间点来衡量，所以可以说日历时间是“相对时间”，但是无论你在哪一个时区，在同一时刻对同一个标准时间点来说，日历时间都是一样的。epoch：时间点。时间点在标准C/C++中是一个整数，它用此时的时间和标准时间点相差的秒数（即日历时间）来表示。clock tick：时钟计时单元（而不把它叫做时钟滴答次数），一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期，而是C/C++的一个基本计时单位。我们可以使用ANSI标准库中的time.h头文件。这个头文件中定义的时间和日期所使用的方法，无论是在结构定义，还是命名，都具有明显的C语言风格。下面，我将说明在C/C++中怎样使用日期的时间功能。2． 计时C/C++中的计时函数是clock()，而与其相关的数据类型是clock_t。在MSDN中，查得对clock函数定义如下：clock_t clock( void );这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元（clock tick）数，在MSDN中称之为挂钟时间（wal-clock）。其中clock_t是用来保存时间的数据类型，在time.h文件中，我们可以找到对它的定义：#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif很明显，clock_t是一个长整形数。在time.h文件中，还定义了一个常量CLOCKS_PER_SEC，它用来表示一秒钟会有多少个时钟计时单元，其定义如下：#define CLOCKS_PER_SEC ((clock_t)1000)可以看到每过千分之一秒（1毫秒），调用clock（）函数返回的值就加1。下面举个例子，你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间：void elapsed_time()
{
printf(&quot;Elapsed time:%u secs.\n&quot;,clock()/CLOCKS_PER_SEC);
}当然，你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间：#include “stdio.h”
#include “stdlib.h”
#include “time.h”int main( void )
{
   long    i = 10000000L;
   clock_t start, finish;
   double  duration;
   /* 测量一个事件持续的时间*/
   printf( &quot;Time to do %ld empty loops is &quot;, i );
   start = clock();
   while( i-- )      ;
   finish = clock();
   duration = (double)(finish - start) / CLOCKS_PER_SEC;
   printf( &quot;%f seconds\n&quot;, duration );
   system(&quot;pause&quot;);
}在笔者的机器上，运行结果如下：Time to do 10000000 empty loops is 0.03000 seconds上面我们看到时钟计时单元的长度为1毫秒，那么计时的精度也为1毫秒，那么我们可不可以通过改变CLOCKS_PER_SEC的定义，通过把它定义的大一些，从而使计时精度更高呢？通过尝试，你会发现这样是不行的。在标准C/C++中，最小的计时单位是一毫秒。3．与日期和时间相关的数据结构在标准C/C++中，我们可通过tm结构来获得日期和时间，tm结构在time.h中的定义如下：#ifndef _TM_DEFINED
struct tm {
        int tm_sec;     /* 秒 – 取值区间为[0,59] */
        int tm_min;     /* 分 - 取值区间为[0,59] */
        int tm_hour;    /* 时 - 取值区间为[0,23] */
        int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */
        int tm_mon;     /* 月份（从一月开始，0代表一月） - 取值区间为[0,11] */
        int tm_year;    /* 年份，其值等于实际年份减去1900 */
        int tm_wday;    /* 星期 – 取值区间为[0,6]，其中0代表星期天，1代表星期一，以此类推 */
        int tm_yday;    /* 从每年的1月1日开始的天数 – 取值区间为[0,365]，其中0代表1月1日，1代表1月2日，以此类推 */
        int tm_isdst;   /* 夏令时标识符，实行夏令时的时候，tm_isdst为正。不实行夏令时的进候，tm_isdst为0；不了解情况时，tm_isdst()为负。*/
        };
#define _TM_DEFINED
#endifANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。而日历时间（Calendar Time）是通过time_t数据类型来表示的，用time_t表示的时间（日历时间）是从一个时间点（例如：1970年1月1日0时0分0秒）到此时的秒数。在time.h中，我们也可以看到time_t是一个长整型数：#ifndef _TIME_T_DEFINED
typedef long time_t;         /* 时间值 */
#define _TIME_T_DEFINED      /* 避免重复定义 time_t */
#endif大家可能会产生疑问：既然time_t实际上是长整型，到未来的某一天，从一个时间点（一般是1970年1月1日0时0分0秒）到那时的秒数（即日历时间）超出了长整形所能表示的数的范围怎么办？对time_t数据类型的值来说，它所表示的时间不能晚于2038年1月18日19时14分07秒。为了能够表示更久远的时间，一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间，并通过_time64()函数来获得日历时间（而不是通过使用32位字的time()函数），这样就可以通过该数据类型保存3001年1月1日0时0分0秒（不包括该时间点）之前的时间。在time.h头文件中，我们还可以看到一些函数，它们都是以time_t为参数类型或返回值类型的函数：double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);此外，time.h还提供了两种不同的函数将日历时间（一个用time_t表示的整数）转换为我们平时看到的把年月日时分秒分开显示的时间格式tm：struct tm * gmtime(const time_t *timer);                                          
struct tm * localtime(const time_t * timer);通过查阅MSDN，我们可以知道Microsoft C/C++ 7.0中时间点的值（time_t对象的值）是从1899年12月31日0时0分0秒到该时间点所经过的秒数，而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。4．与日期和时间相关的函数及应用
在本节，我将向大家展示怎样利用time.h中声明的函数对时间进行操作。这些操作包括取当前时间、计算时间间隔、以不同的形式显示时间等内容。4.1 获得日历时间我们可以通过time()函数来获得日历时间（Calendar Time），其原型为：time_t time(time_t * timer);如果你已经声明了参数timer，你可以从参数timer返回现在的日历时间，同时也可以通过返回值返回现在的日历时间，即从一个时间点（例如：1970年1月1日0时0分0秒）到现在此时的秒数。如果参数为空（NUL），函数将只通过返回值返回现在的日历时间，比如下面这个例子用来显示当前的日历时间：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
printf(&quot;The Calendar Time now is %d\n&quot;,lt);
return 0;
}运行的结果与当时的时间有关，我当时运行的结果是：The Calendar Time now is 1122707619其中1122707619就是我运行程序时的日历时间。即从1970年1月1日0时0分0秒到此时的秒数。4.2 获得日期和时间这里说的日期和时间就是我们平时所说的年、月、日、时、分、秒等信息。从第2节我们已经知道这些信息都保存在一个名为tm的结构体中，那么如何将一个日历时间保存为一个tm结构的对象呢？其中可以使用的函数是gmtime()和localtime()，这两个函数的原型为：struct tm * gmtime(const time_t *timer);                                          
struct tm * localtime(const time_t * timer);其中gmtime()函数是将日历时间转化为世界标准时间（即格林尼治时间），并返回一个tm结构体来保存这个时间，而localtime()函数是将日历时间转化为本地时间。比如现在用gmtime()函数获得的世界标准时间是2005年7月30日7点18分20秒，那么我用localtime()函数在中国地区获得的本地时间会比世界标准时间晚8个小时，即2005年7月30日15点18分20秒。下面是个例子：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *local;
time_t t;
t=time(NUL);
local=localtime(&amp;t);
printf(&quot;Local hour is: %d\n&quot;,local-&gt;tm_hour);
local=gmtime(&amp;t);
printf(&quot;UTC hour is: %d\n&quot;,local-&gt;tm_hour);
return 0;
}运行结果是：Local hour is: 15
UTC hour is: 74.3 固定的时间格式我们可以通过asctime()函数和ctime()函数将时间以固定的格式显示出来，两者的返回值都是char*型的字符串。返回的时间格式为：星期几 月份 日期 时:分:秒 年\n\0
例如：Wed Jan 02 02:03:55 1980\n\0其中\n是一个换行符，\0是一个空字符，表示字符串结束。下面是两个函数的原型：char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);其中asctime()函数是通过tm结构来生成具有固定格式的保存时间信息的字符串，而ctime()是通过日历时间来生成时间字符串。这样的话，asctime（）函数只是把tm结构对象中的各个域填到时间字符串的相应位置就行了，而ctime（）函数需要先参照本地的时间设置，把日历时间转化为本地时间，然后再生成格式化后的字符串。在下面，如果t是一个非空的time_t变量的话，那么：printf(ctime(&amp;t));等价于：struct tm *ptr;
ptr=localtime(&amp;t);
printf(asctime(ptr));那么，下面这个程序的两条printf语句输出的结果就是不同的了（除非你将本地时区设为世界标准时间所在的时区）：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
ptr=gmtime(&amp;lt);
printf(asctime(ptr));
printf(ctime(&amp;lt));
return 0;
}运行结果：Sat Jul 30 08:43:03 2005
Sat Jul 30 16:43:03 20054.4 自定义时间格式我们可以使用strftime（）函数将时间格式化为我们想要的格式。它的原型如下：size_t strftime(
   char *strDest,
   size_t maxsize,
   const char *format,
   const struct tm *timeptr 
);我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中，最多向strDest中存放maxsize个字符。该函数返回向strDest指向的字符串中放置的字符数。函数strftime()的操作有些类似于sprintf()：识别以百分号(%)开始的格式命令集合，格式化输出结果放在一个字符串中。格式化命令说明串strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面，它们是区分大小写的。%a 星期几的简写 
%A 星期几的全称 
%b 月分的简写 
%B 月份的全称 
%c 标准的日期的时间串 
%C 年份的后两位数字 
%d 十进制表示的每月的第几天 
%D 月/天/年 
%e 在两字符域中，十进制表示的每月的第几天 
%F 年-月-日 
%g 年份的后两位数字，使用基于周的年 
%G 年分，使用基于周的年 
%h 简写的月份名 
%H 24小时制的小时 
%I 12小时制的小时
%j 十进制表示的每年的第几天 
%m 十进制表示的月份 
%M 十时制表示的分钟数 
%n 新行符 
%p 本地的AM或PM的等价显示 
%r 12小时的时间 
%R 显示小时和分钟：hh:mm 
%S 十进制的秒数 
%t 水平制表符 
%T 显示时分秒：hh:mm:ss 
%u 每周的第几天，星期一为第一天 （值从0到6，星期一为0）
%U 第年的第几周，把星期日做为第一天（值从0到53）
%V 每年的第几周，使用基于周的年 
%w 十进制表示的星期几（值从0到6，星期天为0）
%W 每年的第几周，把星期一做为第一天（值从0到53） 
%x 标准的日期串 
%X 标准的时间串 
%y 不带世纪的十进制年份（值从0到99）
%Y 带世纪部分的十进制年份 
%z，%Z 时区名称，如果不能得到时区名称则返回空字符。
%% 百分号如果想显示现在是几点了，并以12小时制显示，就象下面这段程序：#include “time.h”
#include “stdio.h”
int main(void)
{
struct tm *ptr;
time_t lt;
char str[80];
lt=time(NUL);
ptr=localtime(&amp;lt);
strftime(str,100,&quot;It is now %I %p&quot;,ptr);
printf(str);
return 0;
}其运行结果为：
It is now 4PM而下面的程序则显示当前的完整日期：#include &lt;stdio.h&gt;
#include &lt;time.h&gt;void main( void )
{
        struct tm *newtime;
        char tmpbuf[128];
        time_t lt1;
        time( &amp;lt1 );
        newtime=localtime(&amp;lt1);
        strftime( tmpbuf, 128, &quot;Today is %A, day %d of %B in the year %Y.\n&quot;, newtime);
        printf(tmpbuf);
}运行结果：Today is Saturday, day 30 of July in the year 2005.4.5 计算持续时间的长度有时候在实际应用中要计算一个事件持续的时间长度，比如计算打字速度。在第1节计时部分中，我已经用clock函数举了一个例子。Clock()函数可以精确到毫秒级。同时，我们也可以使用difftime()函数，但它只能精确到秒。该函数的定义如下：double difftime(time_t time1, time_t time0);虽然该函数返回的以秒计算的时间间隔是double类型的，但这并不说明该时间具有同double一样的精确度，这是由它的参数觉得的（time_t是以秒为单位计算的）。比如下面一段程序：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
#include &quot;stdlib.h&quot;
int main(void)
{
time_t start,end;
start = time(NUL);
system(&quot;pause&quot;);
end = time(NUL);
printf(&quot;The pause used %f seconds.\n&quot;,difftime(end,start));//&lt;-
system(&quot;pause&quot;);
return 0;
}运行结果为：
请按任意键继续. . .
The pause used 2.000000 seconds.
请按任意键继续. . .可以想像，暂停的时间并不那么巧是整整2秒钟。其实，你将上面程序的带有“//&lt;-”注释的一行用下面的一行代码替换：printf(&quot;The pause used %f seconds.\n&quot;,end-start);其运行结果是一样的。4.6 分解时间转化为日历时间这里说的分解时间就是以年、月、日、时、分、秒等分量保存的时间结构，在C/C++中是tm结构。我们可以使用mktime（）函数将用tm结构表示的时间转化为日历时间。其函数原型如下：time_t mktime(struct tm * timeptr);其返回值就是转化后的日历时间。这样我们就可以先制定一个分解时间，然后对这个时间进行操作了，下面的例子可以计算出1997年7月1日是星期几：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
#include &quot;stdlib.h&quot;
int main(void)
{
struct tm t;
time_t t_of_day;
t.tm_year=1997-1900;
t.tm_mon=6;
t.tm_mday=1;
t.tm_hour=0;
t.tm_min=0;
t.tm_sec=1;
t.tm_isdst=0;
t_of_day=mktime(&amp;t);
printf(ctime(&amp;t_of_day));
return 0;
}运行结果：Tue Jul 01 00:00:01 1997现在注意了，有了mktime()函数，是不是我们可以操作现在之前的任何时间呢？你可以通过这种办法算出1945年8月15号是星期几吗？答案是否定的。因为这个时间在1970年1月1日之前，所以在大多数编译器中，这样的程序虽然可以编译通过，但运行时会异常终止。5．总结本文介绍了标准C/C++中的有关日期和时间的概念，并通过各种实例讲述了这些函数和数据结构的使用方法。笔者认为，和时间相关的一些概念是相当重要的，理解这些概念是理解各种时间格式的转换的基础，更是应用这些函数和数据结构的基础。参考文献[1] 标准C++程序设计教程，电子工业出版社，2003。
[2] MSDN Library， Microsoft Corporation，2003。</subject><description><![CDATA[撰文/周翔摘要：
本文从介绍基础概念入手，探讨了在C/C++中对日期和时间操作所用到的数据结构和函数，并对计时、时间的获取、时间的计算和显示格式等方面进行了阐述。本文还通过大量的实例向你展示了time.h头文件中声明的各种函数和数据结构的详细使用方法。关键字：UTC（世界标准时间），Calendar Time（日历时间），epoch（时间点），clock tick（时钟计时单元）1．概念
在C/C++中，对字符串的操作有很多值得注意的问题，同样，C/C++对时间的操作也有许多值得大家注意的地方。最近，在技术群中有很多网友也多次问到过C++语言中对时间的操作、获取和显示等等的问题。下面，在这篇文章中，笔者将主要介绍在C/C++中时间和日期的使用方法.通过学习许多C/C++库，你可以有很多操作、使用时间的方法。但在这之前你需要了解一些“时间”和“日期”的概念，主要有以下几个：Coordinated Universal Time（UTC）：协调世界时，又称为世界标准时间，也就是大家所熟知的格林威治标准时间（Greenwich Mean Time，GMT）。比如，中国内地的时间与UTC的时差为+8，也就是UTC+8。美国是UTC-5。Calendar Time：日历时间，是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同，但对一个编译系统来说，这个标准时间点是不变的，该编译系统中的时间对应的日历时间都通过该标准时间点来衡量，所以可以说日历时间是“相对时间”，但是无论你在哪一个时区，在同一时刻对同一个标准时间点来说，日历时间都是一样的。epoch：时间点。时间点在标准C/C++中是一个整数，它用此时的时间和标准时间点相差的秒数（即日历时间）来表示。clock tick：时钟计时单元（而不把它叫做时钟滴答次数），一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期，而是C/C++的一个基本计时单位。我们可以使用ANSI标准库中的time.h头文件。这个头文件中定义的时间和日期所使用的方法，无论是在结构定义，还是命名，都具有明显的C语言风格。下面，我将说明在C/C++中怎样使用日期的时间功能。2． 计时C/C++中的计时函数是clock()，而与其相关的数据类型是clock_t。在MSDN中，查得对clock函数定义如下：clock_t clock( void );这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元（clock tick）数，在MSDN中称之为挂钟时间（wal-clock）。其中clock_t是用来保存时间的数据类型，在time.h文件中，我们可以找到对它的定义：#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif很明显，clock_t是一个长整形数。在time.h文件中，还定义了一个常量CLOCKS_PER_SEC，它用来表示一秒钟会有多少个时钟计时单元，其定义如下：#define CLOCKS_PER_SEC ((clock_t)1000)可以看到每过千分之一秒（1毫秒），调用clock（）函数返回的值就加1。下面举个例子，你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间：void elapsed_time()
{
printf(&quot;Elapsed time:%u secs.\n&quot;,clock()/CLOCKS_PER_SEC);
}当然，你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间：#include “stdio.h”
#include “stdlib.h”
#include “time.h”int main( void )
{
   long    i = 10000000L;
   clock_t start, finish;
   double  duration;
   /* 测量一个事件持续的时间*/
   printf( &quot;Time to do %ld empty loops is &quot;, i );
   start = clock();
   while( i-- )      ;
   finish = clock();
   duration = (double)(finish - start) / CLOCKS_PER_SEC;
   printf( &quot;%f seconds\n&quot;, duration );
   system(&quot;pause&quot;);
}在笔者的机器上，运行结果如下：Time to do 10000000 empty loops is 0.03000 seconds上面我们看到时钟计时单元的长度为1毫秒，那么计时的精度也为1毫秒，那么我们可不可以通过改变CLOCKS_PER_SEC的定义，通过把它定义的大一些，从而使计时精度更高呢？通过尝试，你会发现这样是不行的。在标准C/C++中，最小的计时单位是一毫秒。3．与日期和时间相关的数据结构在标准C/C++中，我们可通过tm结构来获得日期和时间，tm结构在time.h中的定义如下：#ifndef _TM_DEFINED
struct tm {
        int tm_sec;     /* 秒 – 取值区间为[0,59] */
        int tm_min;     /* 分 - 取值区间为[0,59] */
        int tm_hour;    /* 时 - 取值区间为[0,23] */
        int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */
        int tm_mon;     /* 月份（从一月开始，0代表一月） - 取值区间为[0,11] */
        int tm_year;    /* 年份，其值等于实际年份减去1900 */
        int tm_wday;    /* 星期 – 取值区间为[0,6]，其中0代表星期天，1代表星期一，以此类推 */
        int tm_yday;    /* 从每年的1月1日开始的天数 – 取值区间为[0,365]，其中0代表1月1日，1代表1月2日，以此类推 */
        int tm_isdst;   /* 夏令时标识符，实行夏令时的时候，tm_isdst为正。不实行夏令时的进候，tm_isdst为0；不了解情况时，tm_isdst()为负。*/
        };
#define _TM_DEFINED
#endifANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。而日历时间（Calendar Time）是通过time_t数据类型来表示的，用time_t表示的时间（日历时间）是从一个时间点（例如：1970年1月1日0时0分0秒）到此时的秒数。在time.h中，我们也可以看到time_t是一个长整型数：#ifndef _TIME_T_DEFINED
typedef long time_t;         /* 时间值 */
#define _TIME_T_DEFINED      /* 避免重复定义 time_t */
#endif大家可能会产生疑问：既然time_t实际上是长整型，到未来的某一天，从一个时间点（一般是1970年1月1日0时0分0秒）到那时的秒数（即日历时间）超出了长整形所能表示的数的范围怎么办？对time_t数据类型的值来说，它所表示的时间不能晚于2038年1月18日19时14分07秒。为了能够表示更久远的时间，一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间，并通过_time64()函数来获得日历时间（而不是通过使用32位字的time()函数），这样就可以通过该数据类型保存3001年1月1日0时0分0秒（不包括该时间点）之前的时间。在time.h头文件中，我们还可以看到一些函数，它们都是以time_t为参数类型或返回值类型的函数：double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);此外，time.h还提供了两种不同的函数将日历时间（一个用time_t表示的整数）转换为我们平时看到的把年月日时分秒分开显示的时间格式tm：struct tm * gmtime(const time_t *timer);                                          
struct tm * localtime(const time_t * timer);通过查阅MSDN，我们可以知道Microsoft C/C++ 7.0中时间点的值（time_t对象的值）是从1899年12月31日0时0分0秒到该时间点所经过的秒数，而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。4．与日期和时间相关的函数及应用
在本节，我将向大家展示怎样利用time.h中声明的函数对时间进行操作。这些操作包括取当前时间、计算时间间隔、以不同的形式显示时间等内容。4.1 获得日历时间我们可以通过time()函数来获得日历时间（Calendar Time），其原型为：time_t time(time_t * timer);如果你已经声明了参数timer，你可以从参数timer返回现在的日历时间，同时也可以通过返回值返回现在的日历时间，即从一个时间点（例如：1970年1月1日0时0分0秒）到现在此时的秒数。如果参数为空（NUL），函数将只通过返回值返回现在的日历时间，比如下面这个例子用来显示当前的日历时间：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
printf(&quot;The Calendar Time now is %d\n&quot;,lt);
return 0;
}运行的结果与当时的时间有关，我当时运行的结果是：The Calendar Time now is 1122707619其中1122707619就是我运行程序时的日历时间。即从1970年1月1日0时0分0秒到此时的秒数。4.2 获得日期和时间这里说的日期和时间就是我们平时所说的年、月、日、时、分、秒等信息。从第2节我们已经知道这些信息都保存在一个名为tm的结构体中，那么如何将一个日历时间保存为一个tm结构的对象呢？其中可以使用的函数是gmtime()和localtime()，这两个函数的原型为：struct tm * gmtime(const time_t *timer);                                          
struct tm * localtime(const time_t * timer);其中gmtime()函数是将日历时间转化为世界标准时间（即格林尼治时间），并返回一个tm结构体来保存这个时间，而localtime()函数是将日历时间转化为本地时间。比如现在用gmtime()函数获得的世界标准时间是2005年7月30日7点18分20秒，那么我用localtime()函数在中国地区获得的本地时间会比世界标准时间晚8个小时，即2005年7月30日15点18分20秒。下面是个例子：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *local;
time_t t;
t=time(NUL);
local=localtime(&amp;t);
printf(&quot;Local hour is: %d\n&quot;,local-&gt;tm_hour);
local=gmtime(&amp;t);
printf(&quot;UTC hour is: %d\n&quot;,local-&gt;tm_hour);
return 0;
}运行结果是：Local hour is: 15
UTC hour is: 74.3 固定的时间格式我们可以通过asctime()函数和ctime()函数将时间以固定的格式显示出来，两者的返回值都是char*型的字符串。返回的时间格式为：星期几 月份 日期 时:分:秒 年\n\0
例如：Wed Jan 02 02:03:55 1980\n\0其中\n是一个换行符，\0是一个空字符，表示字符串结束。下面是两个函数的原型：char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);其中asctime()函数是通过tm结构来生成具有固定格式的保存时间信息的字符串，而ctime()是通过日历时间来生成时间字符串。这样的话，asctime（）函数只是把tm结构对象中的各个域填到时间字符串的相应位置就行了，而ctime（）函数需要先参照本地的时间设置，把日历时间转化为本地时间，然后再生成格式化后的字符串。在下面，如果t是一个非空的time_t变量的话，那么：printf(ctime(&amp;t));等价于：struct tm *ptr;
ptr=localtime(&amp;t);
printf(asctime(ptr));那么，下面这个程序的两条printf语句输出的结果就是不同的了（除非你将本地时区设为世界标准时间所在的时区）：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
ptr=gmtime(&amp;lt);
printf(asctime(ptr));
printf(ctime(&amp;lt));
return 0;
}运行结果：Sat Jul 30 08:43:03 2005
Sat Jul 30 16:43:03 20054.4 自定义时间格式我们可以使用strftime（）函数将时间格式化为我们想要的格式。它的原型如下：size_t strftime(
   char *strDest,
   size_t maxsize,
   const char *format,
   const struct tm *timeptr 
);我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中，最多向strDest中存放maxsize个字符。该函数返回向strDest指向的字符串中放置的字符数。函数strftime()的操作有些类似于sprintf()：识别以百分号(%)开始的格式命令集合，格式化输出结果放在一个字符串中。格式化命令说明串strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面，它们是区分大小写的。%a 星期几的简写 
%A 星期几的全称 
%b 月分的简写 
%B 月份的全称 
%c 标准的日期的时间串 
%C 年份的后两位数字 
%d 十进制表示的每月的第几天 
%D 月/天/年 
%e 在两字符域中，十进制表示的每月的第几天 
%F 年-月-日 
%g 年份的后两位数字，使用基于周的年 
%G 年分，使用基于周的年 
%h 简写的月份名 
%H 24小时制的小时 
%I 12小时制的小时
%j 十进制表示的每年的第几天 
%m 十进制表示的月份 
%M 十时制表示的分钟数 
%n 新行符 
%p 本地的AM或PM的等价显示 
%r 12小时的时间 
%R 显示小时和分钟：hh:mm 
%S 十进制的秒数 
%t 水平制表符 
%T 显示时分秒：hh:mm:ss 
%u 每周的第几天，星期一为第一天 （值从0到6，星期一为0）
%U 第年的第几周，把星期日做为第一天（值从0到53）
%V 每年的第几周，使用基于周的年 
%w 十进制表示的星期几（值从0到6，星期天为0）
%W 每年的第几周，把星期一做为第一天（值从0到53） 
%x 标准的日期串 
%X 标准的时间串 
%y 不带世纪的十进制年份（值从0到99）
%Y 带世纪部分的十进制年份 
%z，%Z 时区名称，如果不能得到时区名称则返回空字符。
%% 百分号如果想显示现在是几点了，并以12小时制显示，就象下面这段程序：#include “time.h”
#include “stdio.h”
int main(void)
{
struct tm *ptr;
time_t lt;
char str[80];
lt=time(NUL);
ptr=localtime(&amp;lt);
strftime(str,100,&quot;It is now %I %p&quot;,ptr);
printf(str);
return 0;
}其运行结果为：
It is now 4PM而下面的程序则显示当前的完整日期：#include &lt;stdio.h&gt;
#include &lt;time.h&gt;void main( void )
{
        struct tm *newtime;
        char tmpbuf[128];
        time_t lt1;
        time( &amp;lt1 );
        newtime=localtime(&amp;lt1);
        strftime( tmpbuf, 128, &quot;Today is %A, day %d of %B in the year %Y.\n&quot;, newtime);
        printf(tmpbuf);
}运行结果：Today is Saturday, day 30 of July in the year 2005.4.5 计算持续时间的长度有时候在实际应用中要计算一个事件持续的时间长度，比如计算打字速度。在第1节计时部分中，我已经用clock函数举了一个例子。Clock()函数可以精确到毫秒级。同时，我们也可以使用difftime()函数，但它只能精确到秒。该函数的定义如下：double difftime(time_t time1, time_t time0);虽然该函数返回的以秒计算的时间间隔是double类型的，但这并不说明该时间具有同double一样的精确度，这是由它的参数觉得的（time_t是以秒为单位计算的）。比如下面一段程序：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
#include &quot;stdlib.h&quot;
int main(void)
{
time_t start,end;
start = time(NUL);
system(&quot;pause&quot;);
end = time(NUL);
printf(&quot;The pause used %f seconds.\n&quot;,difftime(end,start));//&lt;-
system(&quot;pause&quot;);
return 0;
}运行结果为：
请按任意键继续. . .
The pause used 2.000000 seconds.
请按任意键继续. . .可以想像，暂停的时间并不那么巧是整整2秒钟。其实，你将上面程序的带有“//&lt;-”注释的一行用下面的一行代码替换：printf(&quot;The pause used %f seconds.\n&quot;,end-start);其运行结果是一样的。4.6 分解时间转化为日历时间这里说的分解时间就是以年、月、日、时、分、秒等分量保存的时间结构，在C/C++中是tm结构。我们可以使用mktime（）函数将用tm结构表示的时间转化为日历时间。其函数原型如下：time_t mktime(struct tm * timeptr);其返回值就是转化后的日历时间。这样我们就可以先制定一个分解时间，然后对这个时间进行操作了，下面的例子可以计算出1997年7月1日是星期几：#include &quot;time.h&quot;
#include &quot;stdio.h&quot;
#include &quot;stdlib.h&quot;
int main(void)
{
struct tm t;
time_t t_of_day;
t.tm_year=1997-1900;
t.tm_mon=6;
t.tm_mday=1;
t.tm_hour=0;
t.tm_min=0;
t.tm_sec=1;
t.tm_isdst=0;
t_of_day=mktime(&amp;t);
printf(ctime(&amp;t_of_day));
return 0;
}运行结果：Tue Jul 01 00:00:01 1997现在注意了，有了mktime()函数，是不是我们可以操作现在之前的任何时间呢？你可以通过这种办法算出1945年8月15号是星期几吗？答案是否定的。因为这个时间在1970年1月1日之前，所以在大多数编译器中，这样的程序虽然可以编译通过，但运行时会异常终止。5．总结本文介绍了标准C/C++中的有关日期和时间的概念，并通过各种实例讲述了这些函数和数据结构的使用方法。笔者认为，和时间相关的一些概念是相当重要的，理解这些概念是理解各种时间格式的转换的基础，更是应用这些函数和数据结构的基础。参考文献[1] 标准C++程序设计教程，电子工业出版社，2003。
[2] MSDN Library， Microsoft Corporation，2003。]]></description><PubDate>2008-12-16 16:18:54</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十六讲 文件(2)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16890</link><subject>字符串读写函数fgets和fputs一、读字符串函数fgets函数的功能是从指定的文件中读一个字符串到字符数组中，函数调用的形式为： fgets(字符数组名，n，文件指针)； 其中的n是一个正整数。表示从文件中读出的字符串不超过 n-1个字符。在读入的最后一个字符后加上串结束标志'\0'。例如：fgets(str,n,fp);的意义是从fp所指的文件中读出n-1个字符送入字符数组str中。[例10.4]从e10_1.c文件中读入一个含10个字符的字符串。#include&lt;stdio.h&gt;
main()
{
FILE *fp;
char str[11];
if((fp=fopen(&quot;e10_1.c&quot;,&quot;rt&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
fgets(str,11,fp);
printf(&quot;%s&quot;,str);
fclose(fp);
} 本例定义了一个字符数组str共11个字节，在以读文本文件方式打开文件e101.c后，从中读出10个字符送入str数组，在数组最后一个单元内将加上'\0'，然后在屏幕上显示输出str数组。输出的十个字符正是例10.1程序的前十个字符。对fgets函数有两点说明：1. 在读出n-1个字符之前，如遇到了换行符或EOF，则读出结束。2. fgets函数也有返回值，其返回值是字符数组的首地址。二、写字符串函数fputsfputs函数的功能是向指定的文件写入一个字符串，其调用形式为： fputs(字符串，文件指针) 其中字符串可以是字符串常量，也可以是字符数组名， 或指针 变量，例如：fputs(“abcd“，fp)；其意义是把字符串“abcd”写入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一个字符串。#include&lt;stdio.h&gt;
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen(&quot;string&quot;,&quot;at+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;input a string:\n&quot;);
scanf(&quot;%s&quot;,st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf(&quot;\n&quot;);
fclose(fp);
} 本例要求在string文件末加写字符串，因此，在程序第6行以追加读写文本文件的方式打开文件string 。 然后输入字符串， 并用fputs函数把该串写入文件string。在程序15行用rewind函数把文件内部位置指针移到文件首。 再进入循环逐个显示当前文件中的全部内容。数据块读写函数fread和fwriteC语言还提供了用于整块数据的读写函数。 可用来读写一组数据，如一个数组元素，一个结构变量的值等。读数据块函数调用的一般形式为： fread(buffer,size,count,fp); 写数据块函数调用的一般形式为： fwrite(buffer,size,count,fp); 其中buffer是一个指针，在fread函数中，它表示存放输入数据的首地址。在fwrite函数中，它表示存放输出数据的首地址。 size 表示数据块的字节数。count 表示要读写的数据块块数。fp 表示文件指针。例如：fread(fa,4,5,fp); 其意义是从fp所指的文件中，每次读4个字节(一个实数)送入实数组fa中，连续读5次，即读5个实数到fa中。[例10.6]从键盘输入两个学生数据，写入一个文件中， 再读出这两个学生的数据显示在屏幕上。#include&lt;stdio.h&gt;
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen(&quot;stu_list&quot;,&quot;wb+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;\ninput data\n&quot;);
for(i=0;i&lt;2;i++,pp++)
scanf(&quot;%s%d%d%s&quot;,pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf(&quot;\n\nname\tnumber age addr\n&quot;);
for(i=0;i&lt;2;i++,qq++)
printf(&quot;%s\t%5d%7d%s\n&quot;,qq-&gt;name,qq-&gt;num,qq-&gt;age,qq-&gt;addr);
fclose(fp);
} 本例程序定义了一个结构stu,说明了两个结构数组boya和 boyb以及两个结构指针变量pp和qq。pp指向boya,qq指向boyb。程序第16行以读写方式打开二进制文件“stu_list”，输入二个学生数据之后，写入该文件中， 然后把文件内部位置指针移到文件首，读出两块学生数据后，在屏幕上显示。格式化读写函数fscanf和fprintffscanf函数，fprintf函数与前面使用的scanf和printf 函数的功能相似，都是格式化读写函数。 两者的区别在于 fscanf 函数和fprintf函数的读写对象不是键盘和显示器，而是磁盘文件。这两个函数的调用格式为： fscanf(文件指针，格式字符串，输入表列)； fprintf(文件指针，格式字符串，输出表列)； 例如：fscanf(fp,&quot;%d%s&quot;,&amp;i,s);fprintf(fp,&quot;%d%c&quot;,j,ch); 用fscanf和fprintf函数也可以完成例10.6的问题。修改后的程序如例10.7所示。[例10.7]#include&lt;stdio.h&gt;
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen(&quot;stu_list&quot;,&quot;wb+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;\ninput data\n&quot;);
for(i=0;i&lt;2;i++,pp++)
scanf(&quot;%s%d%d%s&quot;,pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);
pp=boya;
for(i=0;i&lt;2;i++,pp++)
fprintf(fp,&quot;%s %d %d %s\n&quot;,pp-&gt;name,pp-&gt;num,pp-&gt;age,pp-&gt;
addr);
rewind(fp);
for(i=0;i&lt;2;i++,qq++)
fscanf(fp,&quot;%s %d %d %s\n&quot;,qq-&gt;name,&amp;qq-&gt;num,&amp;qq-&gt;age,qq-&gt;addr);
printf(&quot;\n\nname\tnumber age addr\n&quot;);
qq=boyb;
for(i=0;i&lt;2;i++,qq++)
printf(&quot;%s\t%5d %7d %s\n&quot;,qq-&gt;name,qq-&gt;num, qq-&gt;age,
qq-&gt;addr);
fclose(fp);
} 与例10.6相比，本程序中fscanf和fprintf函数每次只能读写一个结构数组元素，因此采用了循环语句来读写全部数组元素。 还要注意指针变量pp,qq由于循环改变了它们的值，因此在程序的25和32行分别对它们重新赋予了数组的首地址。文件的随机读写前面介绍的对文件的读写方式都是顺序读写， 即读写文件只能从头开始，顺序读写各个数据。 但在实际问题中常要求只读写文件中某一指定的部分。 为了解决这个问题可移动文件内部的位置指针到需要读写的位置，再进行读写，这种读写称为随机读写。 实现随机读写的关键是要按要求移动位置指针，这称为文件的定位。文件定位移动文件内部位置指针的函数主要有两个， 即 rewind 函数和fseek函数。rewind函数前面已多次使用过，其调用形式为： rewind(文件指针)； 它的功能是把文件内部的位置指针移到文件首。 下面主要介绍
fseek函数。fseek函数用来移动文件内部位置指针，其调用形式为： fseek(文件指针，位移量，起始点)； 其中：“文件指针”指向被移动的文件。 “位移量”表示移动的字节数，要求位移量是long型数据，以便在文件长度大于64KB 时不会出错。当用常量表示位移量时，要求加后缀“L”。“起始点”表示从何处开始计算位移量，规定的起始点有三种：文件首，当前位置和文件尾。其表示方法如表10.2。 起始点 　　　表示符号 　　　数字表示
──────────────────────────
文件首 　　　SEEK—SET　　　　0
当前位置 　　SEEK—CUR　　　　1
文件末尾 　　SEEK—END 　　　 2例如：fseek(fp,100L,0);其意义是把位置指针移到离文件首100个字节处。还要说明的是fseek函数一般用于二进制文件。在文本文件中由于要进行转换，故往往计算的位置会出现错误。文件的随机读写在移动位置指针之后， 即可用前面介绍的任一种读写函数进行读写。由于一般是读写一个数据据块，因此常用fread和fwrite函数。下面用例题来说明文件的随机读写。[例10.8]在学生文件stu list中读出第二个学生的数据。#include&lt;stdio.h&gt;
struct stu
{char name[10];int num;int age;char addr[15];
}boy,*qq;
main()
{FILE *fp;char ch;int i=1;qq=&amp;boy;if((fp=fopen(&quot;stu_list&quot;,&quot;rb&quot;))==NULL){printf(&quot;Cannot open file strike any key exit!&quot;);getch();exit(1);}rewind(fp);fseek(fp,i*sizeof(struct stu),0);fread(qq,sizeof(struct stu),1,fp);printf(&quot;\n\nname\tnumber age addr\n&quot;);printf(&quot;%s\t%5d %7d %s\n&quot;,qq-&gt;name,qq-&gt;num,qq-&gt;age,qq-&gt;addr);
} 文件stu_list已由例10.6的程序建立，本程序用随机读出的方法读出第二个学生的数据。程序中定义boy为stu类型变量，qq为指向boy的指针。以读二进制文件方式打开文件，程序第22行移动文件位置指针。其中的i值为1，表示从文件头开始，移动一个stu类型的长度， 然后再读出的数据即为第二个学生的数据。文件检测函数C语言中常用的文件检测函数有以下几个。一、文件结束检测函数feof函数调用格式： feof(文件指针)； 功能：判断文件是否处于文件结束位置，如文件结束，则返回值为1，否则为0。二、读写文件出错检测函数ferror函数调用格式： ferror(文件指针)； 功能：检查文件在用各种输入输出函数进行读写时是否出错。 如ferror返回值为0表示未出错，否则表示有错。三、文件出错标志和文件结束标志置0函数clearerr函数调用格式： clearerr(文件指针); 功能：本函数用于清除出错标志和文件结束标志，使它们为0值。C库文件C系统提供了丰富的系统文件，称为库文件，C的库文件分为两类，一类是扩展名为&quot;.h&quot;的文件，称为头文件， 在前面的包含命令中我们已多次使用过。在&quot;.h&quot;文件中包含了常量定义、 类型定义、宏定义、函数原型以及各种编译选择设置等信息。另一类是函数库，包括了各种函数的目标代码，供用户在程序中调用。 通常在程序中调用一个库函数时，要在调用之前包含该函数原型所在的&quot;.h&quot; 文件。在附录中给出了全部库函数。ALLOC.H 　　　说明内存管理函数(分配、释放等)。
ASSERT.H 　 　定义 assert调试宏。
BIOS.H 　　 　说明调用IBM—PC ROM BIOS子程序的各个函数。
CONIO.H 　　　说明调用DOS控制台I/O子程序的各个函数。
CTYPE.H 　　　包含有关字符分类及转换的名类信息(如 isalpha和toascii等)。
DIR.H 　　　　包含有关目录和路径的结构、宏定义和函数。
DOS.H 　　　　定义和说明MSDOS和8086调用的一些常量和函数。
ERRON.H 　　　定义错误代码的助记符。
FCNTL.H 　　　定义在与open库子程序连接时的符号常量。
FLOAT.H 　　　包含有关浮点运算的一些参数和函数。
GRAPHICS.H 　 说明有关图形功能的各个函数，图形错误代码的常量定义，正对不同驱动程序的各种颜色值，及函数用到的一些特殊结构。
IO.H 　　　　 包含低级I/O子程序的结构和说明。
LIMIT.H 　　　包含各环境参数、编译时间限制、数的范围等信息。
MATH.H 　　　 说明数学运算函数，还定了 HUGE VAL 宏， 说明了matherr和matherr子程序用到的特殊结构。
MEM.H 　　　　说明一些内存操作函数(其中大多数也在STRING.H 中说明)。
PROCESS.H 　　说明进程管理的各个函数，spawn…和EXEC …函数的结构说明。
SETJMP.H 　　 定义longjmp和setjmp函数用到的jmp buf类型， 说明这两个函数。
SHARE.H 　　　定义文件共享函数的参数。
SIGNAL.H 　　 定义SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量，说明rajse和signal两个函数。
STDARG.H 　　 定义读函数参数表的宏。(如vprintf,vscarf函数)。
STDDEF.H 　　 定义一些公共数据类型和宏。
STDIO.H 　　　定义Kernighan和Ritchie在Unix System V 中定义的标准和扩展的类型和宏。还定义标准I/O 预定义流：stdin,stdout和stderr，说明 I/O流子程序。
STDLIB.H 　　 说明一些常用的子程序：转换子程序、搜索/ 排序子程序等。
STRING.H 　　 说明一些串操作和内存操作函数。
SYS\STAT.H 　 定义在打开和创建文件时用到的一些符号常量。
SYS\TYPES.H 　说明ftime函数和timeb结构。
SYS\TIME.H 　 定义时间的类型time[ZZ(Z] [ZZ)]t。
TIME.H 　　　 定义时间转换子程序asctime、localtime和gmtime的结构，ctime、 difftime、 gmtime、 localtime和stime用到的类型，并提供这些函数的原型。
VALUE.H 　　　定义一些重要常量， 包括依赖于机器硬件的和为与Unix System V相兼容而说明的一些常量，包括浮点和双精度值的范围。</subject><description><![CDATA[字符串读写函数fgets和fputs一、读字符串函数fgets函数的功能是从指定的文件中读一个字符串到字符数组中，函数调用的形式为： fgets(字符数组名，n，文件指针)； 其中的n是一个正整数。表示从文件中读出的字符串不超过 n-1个字符。在读入的最后一个字符后加上串结束标志'\0'。例如：fgets(str,n,fp);的意义是从fp所指的文件中读出n-1个字符送入字符数组str中。[例10.4]从e10_1.c文件中读入一个含10个字符的字符串。#include&lt;stdio.h&gt;
main()
{
FILE *fp;
char str[11];
if((fp=fopen(&quot;e10_1.c&quot;,&quot;rt&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
fgets(str,11,fp);
printf(&quot;%s&quot;,str);
fclose(fp);
} 本例定义了一个字符数组str共11个字节，在以读文本文件方式打开文件e101.c后，从中读出10个字符送入str数组，在数组最后一个单元内将加上'\0'，然后在屏幕上显示输出str数组。输出的十个字符正是例10.1程序的前十个字符。对fgets函数有两点说明：1. 在读出n-1个字符之前，如遇到了换行符或EOF，则读出结束。2. fgets函数也有返回值，其返回值是字符数组的首地址。二、写字符串函数fputsfputs函数的功能是向指定的文件写入一个字符串，其调用形式为： fputs(字符串，文件指针) 其中字符串可以是字符串常量，也可以是字符数组名， 或指针 变量，例如：fputs(“abcd“，fp)；其意义是把字符串“abcd”写入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一个字符串。#include&lt;stdio.h&gt;
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen(&quot;string&quot;,&quot;at+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;input a string:\n&quot;);
scanf(&quot;%s&quot;,st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf(&quot;\n&quot;);
fclose(fp);
} 本例要求在string文件末加写字符串，因此，在程序第6行以追加读写文本文件的方式打开文件string 。 然后输入字符串， 并用fputs函数把该串写入文件string。在程序15行用rewind函数把文件内部位置指针移到文件首。 再进入循环逐个显示当前文件中的全部内容。数据块读写函数fread和fwriteC语言还提供了用于整块数据的读写函数。 可用来读写一组数据，如一个数组元素，一个结构变量的值等。读数据块函数调用的一般形式为： fread(buffer,size,count,fp); 写数据块函数调用的一般形式为： fwrite(buffer,size,count,fp); 其中buffer是一个指针，在fread函数中，它表示存放输入数据的首地址。在fwrite函数中，它表示存放输出数据的首地址。 size 表示数据块的字节数。count 表示要读写的数据块块数。fp 表示文件指针。例如：fread(fa,4,5,fp); 其意义是从fp所指的文件中，每次读4个字节(一个实数)送入实数组fa中，连续读5次，即读5个实数到fa中。[例10.6]从键盘输入两个学生数据，写入一个文件中， 再读出这两个学生的数据显示在屏幕上。#include&lt;stdio.h&gt;
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen(&quot;stu_list&quot;,&quot;wb+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;\ninput data\n&quot;);
for(i=0;i&lt;2;i++,pp++)
scanf(&quot;%s%d%d%s&quot;,pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf(&quot;\n\nname\tnumber age addr\n&quot;);
for(i=0;i&lt;2;i++,qq++)
printf(&quot;%s\t%5d%7d%s\n&quot;,qq-&gt;name,qq-&gt;num,qq-&gt;age,qq-&gt;addr);
fclose(fp);
} 本例程序定义了一个结构stu,说明了两个结构数组boya和 boyb以及两个结构指针变量pp和qq。pp指向boya,qq指向boyb。程序第16行以读写方式打开二进制文件“stu_list”，输入二个学生数据之后，写入该文件中， 然后把文件内部位置指针移到文件首，读出两块学生数据后，在屏幕上显示。格式化读写函数fscanf和fprintffscanf函数，fprintf函数与前面使用的scanf和printf 函数的功能相似，都是格式化读写函数。 两者的区别在于 fscanf 函数和fprintf函数的读写对象不是键盘和显示器，而是磁盘文件。这两个函数的调用格式为： fscanf(文件指针，格式字符串，输入表列)； fprintf(文件指针，格式字符串，输出表列)； 例如：fscanf(fp,&quot;%d%s&quot;,&amp;i,s);fprintf(fp,&quot;%d%c&quot;,j,ch); 用fscanf和fprintf函数也可以完成例10.6的问题。修改后的程序如例10.7所示。[例10.7]#include&lt;stdio.h&gt;
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen(&quot;stu_list&quot;,&quot;wb+&quot;))==NULL)
{
printf(&quot;Cannot open file strike any key exit!&quot;);
getch();
exit(1);
}
printf(&quot;\ninput data\n&quot;);
for(i=0;i&lt;2;i++,pp++)
scanf(&quot;%s%d%d%s&quot;,pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);
pp=boya;
for(i=0;i&lt;2;i++,pp++)
fprintf(fp,&quot;%s %d %d %s\n&quot;,pp-&gt;name,pp-&gt;num,pp-&gt;age,pp-&gt;
addr);
rewind(fp);
for(i=0;i&lt;2;i++,qq++)
fscanf(fp,&quot;%s %d %d %s\n&quot;,qq-&gt;name,&amp;qq-&gt;num,&amp;qq-&gt;age,qq-&gt;addr);
printf(&quot;\n\nname\tnumber age addr\n&quot;);
qq=boyb;
for(i=0;i&lt;2;i++,qq++)
printf(&quot;%s\t%5d %7d %s\n&quot;,qq-&gt;name,qq-&gt;num, qq-&gt;age,
qq-&gt;addr);
fclose(fp);
} 与例10.6相比，本程序中fscanf和fprintf函数每次只能读写一个结构数组元素，因此采用了循环语句来读写全部数组元素。 还要注意指针变量pp,qq由于循环改变了它们的值，因此在程序的25和32行分别对它们重新赋予了数组的首地址。文件的随机读写前面介绍的对文件的读写方式都是顺序读写， 即读写文件只能从头开始，顺序读写各个数据。 但在实际问题中常要求只读写文件中某一指定的部分。 为了解决这个问题可移动文件内部的位置指针到需要读写的位置，再进行读写，这种读写称为随机读写。 实现随机读写的关键是要按要求移动位置指针，这称为文件的定位。文件定位移动文件内部位置指针的函数主要有两个， 即 rewind 函数和fseek函数。rewind函数前面已多次使用过，其调用形式为： rewind(文件指针)； 它的功能是把文件内部的位置指针移到文件首。 下面主要介绍
fseek函数。fseek函数用来移动文件内部位置指针，其调用形式为： fseek(文件指针，位移量，起始点)； 其中：“文件指针”指向被移动的文件。 “位移量”表示移动的字节数，要求位移量是long型数据，以便在文件长度大于64KB 时不会出错。当用常量表示位移量时，要求加后缀“L”。“起始点”表示从何处开始计算位移量，规定的起始点有三种：文件首，当前位置和文件尾。其表示方法如表10.2。 起始点 　　　表示符号 　　　数字表示
──────────────────────────
文件首 　　　SEEK—SET　　　　0
当前位置 　　SEEK—CUR　　　　1
文件末尾 　　SEEK—END 　　　 2例如：fseek(fp,100L,0);其意义是把位置指针移到离文件首100个字节处。还要说明的是fseek函数一般用于二进制文件。在文本文件中由于要进行转换，故往往计算的位置会出现错误。文件的随机读写在移动位置指针之后， 即可用前面介绍的任一种读写函数进行读写。由于一般是读写一个数据据块，因此常用fread和fwrite函数。下面用例题来说明文件的随机读写。[例10.8]在学生文件stu list中读出第二个学生的数据。#include&lt;stdio.h&gt;
struct stu
{char name[10];int num;int age;char addr[15];
}boy,*qq;
main()
{FILE *fp;char ch;int i=1;qq=&amp;boy;if((fp=fopen(&quot;stu_list&quot;,&quot;rb&quot;))==NULL){printf(&quot;Cannot open file strike any key exit!&quot;);getch();exit(1);}rewind(fp);fseek(fp,i*sizeof(struct stu),0);fread(qq,sizeof(struct stu),1,fp);printf(&quot;\n\nname\tnumber age addr\n&quot;);printf(&quot;%s\t%5d %7d %s\n&quot;,qq-&gt;name,qq-&gt;num,qq-&gt;age,qq-&gt;addr);
} 文件stu_list已由例10.6的程序建立，本程序用随机读出的方法读出第二个学生的数据。程序中定义boy为stu类型变量，qq为指向boy的指针。以读二进制文件方式打开文件，程序第22行移动文件位置指针。其中的i值为1，表示从文件头开始，移动一个stu类型的长度， 然后再读出的数据即为第二个学生的数据。文件检测函数C语言中常用的文件检测函数有以下几个。一、文件结束检测函数feof函数调用格式： feof(文件指针)； 功能：判断文件是否处于文件结束位置，如文件结束，则返回值为1，否则为0。二、读写文件出错检测函数ferror函数调用格式： ferror(文件指针)； 功能：检查文件在用各种输入输出函数进行读写时是否出错。 如ferror返回值为0表示未出错，否则表示有错。三、文件出错标志和文件结束标志置0函数clearerr函数调用格式： clearerr(文件指针); 功能：本函数用于清除出错标志和文件结束标志，使它们为0值。C库文件C系统提供了丰富的系统文件，称为库文件，C的库文件分为两类，一类是扩展名为&quot;.h&quot;的文件，称为头文件， 在前面的包含命令中我们已多次使用过。在&quot;.h&quot;文件中包含了常量定义、 类型定义、宏定义、函数原型以及各种编译选择设置等信息。另一类是函数库，包括了各种函数的目标代码，供用户在程序中调用。 通常在程序中调用一个库函数时，要在调用之前包含该函数原型所在的&quot;.h&quot; 文件。在附录中给出了全部库函数。ALLOC.H 　　　说明内存管理函数(分配、释放等)。
ASSERT.H 　 　定义 assert调试宏。
BIOS.H 　　 　说明调用IBM—PC ROM BIOS子程序的各个函数。
CONIO.H 　　　说明调用DOS控制台I/O子程序的各个函数。
CTYPE.H 　　　包含有关字符分类及转换的名类信息(如 isalpha和toascii等)。
DIR.H 　　　　包含有关目录和路径的结构、宏定义和函数。
DOS.H 　　　　定义和说明MSDOS和8086调用的一些常量和函数。
ERRON.H 　　　定义错误代码的助记符。
FCNTL.H 　　　定义在与open库子程序连接时的符号常量。
FLOAT.H 　　　包含有关浮点运算的一些参数和函数。
GRAPHICS.H 　 说明有关图形功能的各个函数，图形错误代码的常量定义，正对不同驱动程序的各种颜色值，及函数用到的一些特殊结构。
IO.H 　　　　 包含低级I/O子程序的结构和说明。
LIMIT.H 　　　包含各环境参数、编译时间限制、数的范围等信息。
MATH.H 　　　 说明数学运算函数，还定了 HUGE VAL 宏， 说明了matherr和matherr子程序用到的特殊结构。
MEM.H 　　　　说明一些内存操作函数(其中大多数也在STRING.H 中说明)。
PROCESS.H 　　说明进程管理的各个函数，spawn…和EXEC …函数的结构说明。
SETJMP.H 　　 定义longjmp和setjmp函数用到的jmp buf类型， 说明这两个函数。
SHARE.H 　　　定义文件共享函数的参数。
SIGNAL.H 　　 定义SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量，说明rajse和signal两个函数。
STDARG.H 　　 定义读函数参数表的宏。(如vprintf,vscarf函数)。
STDDEF.H 　　 定义一些公共数据类型和宏。
STDIO.H 　　　定义Kernighan和Ritchie在Unix System V 中定义的标准和扩展的类型和宏。还定义标准I/O 预定义流：stdin,stdout和stderr，说明 I/O流子程序。
STDLIB.H 　　 说明一些常用的子程序：转换子程序、搜索/ 排序子程序等。
STRING.H 　　 说明一些串操作和内存操作函数。
SYS\STAT.H 　 定义在打开和创建文件时用到的一些符号常量。
SYS\TYPES.H 　说明ftime函数和timeb结构。
SYS\TIME.H 　 定义时间的类型time[ZZ(Z] [ZZ)]t。
TIME.H 　　　 定义时间转换子程序asctime、localtime和gmtime的结构，ctime、 difftime、 gmtime、 localtime和stime用到的类型，并提供这些函数的原型。
VALUE.H 　　　定义一些重要常量， 包括依赖于机器硬件的和为与Unix System V相兼容而说明的一些常量，包括浮点和双精度值的范围。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十五讲 预处理</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16892</link><subject>概述在前面各章中，已多次使用过以“#”号开头的预处理命令。如包含命令# include，宏定义命令# define等。在源程序中这些命令都放在函数之外， 而且一般都放在源文件的前面，它们称为预处理部分。 所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能， 它由预处理程序负责完成。当对一个源文件进行编译时， 系统将自动引用预处理程序对源程序中的预处理部分作处理， 处理完毕自动进入对源程序的编译。C语言提供了多种预处理功能，如宏定义、文件包含、 条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、 移植和调试，也有利于模块化程序设计。本章介绍常用的几种预处理功能。宏定义在C语言源程序中允许用一个标识符来表示一个字符串， 称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时，对程序中所有出现的“宏名”，都用宏定义中的字符串去代换， 这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。 宏代换是由预处理程序自动完成的。在C语言中，“宏”分为有参数和无参数两种。 下面分别讨论这两种“宏”的定义和调用。无参宏定义无参宏的宏名后不带参数。其定义的一般形式为： #define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。 “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。在前面介绍过的符号常量的定义就是一种无参宏定义。 此外，常对程序中反复使用的表达式进行宏定义。例如： # define M (y*y+3*y) 定义M表达式(y*y+3*y)。在编写源程序时，所有的(y*y+3*y)都可由M代替，而对源程序作编译时，将先由预处理程序进行宏代换，即用(y*y+3*y)表达式去置换所有的宏名M，然后再进行编译。#define M (y*y+3*y)
main(){int s,y;printf(&quot;input a number: &quot;);scanf(&quot;%d&quot;,&amp;y);s=3*M+4*M+5*M;printf(&quot;s=%d\n&quot;,s);
} 上例程序中首先进行宏定义，定义M表达式(y*y+3*y),在s= 3*M+4*M+5* M中作了宏调用。在预处理时经宏展开后该语句变为：s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意的是，在宏定义中表达式(y*y+3*y)两边的括号不能少。否则会发生错误。当作以下定义后： #difine M y*y+3*y在宏展开时将得到下述语句： s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y；这相当于； 3y2+3y+4y2+3y+5y2+3y；显然与原题意要求不符。计算结果当然是错误的。 因此在作宏定义时必须十分注意。应保证在宏代换之后不发生错误。对于宏定义还要说明以下几点：1. 宏定义是用宏名来表示一个字符串，在宏展开时又以该字符串取代宏名，这只是一种简单的代换，字符串中可以含任何字符，可以是常数，也可以是表达式，预处理程序对它不作任何检查。如有错误，只能在编译已被宏展开后的源程序时发现。2. 宏定义不是说明或语句，在行末不必加分号，如加上分号则连分号也一起置换。3. 宏定义必须写在函数之外，其作用域为宏定义命令起到源程序结 束。如要终止其作用域可使用# undef命令，例如： # define PI 3.14159
main() 
{
……
} # undef PIPI的作用域f1()....表示PI只在main函数中有效，在f1中无效。4. 宏名在源程序中若用引号括起来，则预处理程序不对其作宏代换。#define OK 100
main()
{printf(&quot;OK&quot;);printf(&quot;\n&quot;);
} 上例中定义宏名OK表示100，但在printf语句中OK被引号括起来，因此不作宏代换。程序的运行结果为：OK这表示把“OK”当字符串处理。5. 宏定义允许嵌套，在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如： #define PI 3.1415926
#define S PI*y*y /* PI是已定义的宏名*/对语句： printf(&quot;%f&quot;,s); 在宏代换后变为： printf(&quot;%f&quot;,3.1415926*y*y);6. 习惯上宏名用大写字母表示，以便于与变量区别。但也允许用小写字母。7. 可用宏定义表示数据类型，使书写方便。例如： #define STU struct stu在程序中可用STU作变量说明：STU body[5],*p;#define INTEGER int 在程序中即可用INTEGER作整型变量说明： INTEGER a,b; 应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串代换，是在预处理完成的，而typedef是在编译时处理的，它不是作简单的代换， 而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。请看下面的例子： #define PIN1 int*　typedef (int*) PIN2;从形式上看这两者相似， 但在实际使用中却不相同。下面用PIN1，PIN2说明变量时就可以看出它们的区别： PIN1 a,b;在宏代换后变成 int *a,b;表示a是指向整型的指针变量，而b是整型变量。然而：PIN2 a,b;表示a,b都是指向整型的指针变量。因为PIN2是一个类型说明符。由这个例子可见，宏定义虽然也可表示数据类型， 但毕竟是作字符代换。在使用时要分外小心，以避出错。8. 对“输出格式”作宏定义，可以减少书写麻烦。例9.3 中就采用了这种方法。#define P printf
#define D &quot;%d\n&quot;
#define F &quot;%f\n&quot;
main(){
int a=5, c=8, e=11;
float b=3.8, d=9.7, f=21.08;
P(D F,a,b);
P(D F,c,d);
P(D F,e,f);
} 带参宏定义C语言允许宏带有参数。在宏定义中的参数称为形式参数， 在宏调用中的参数称为实际参数。对带参数的宏，在调用中，不仅要宏展开， 而且要用实参去代换形参。带参宏定义的一般形式为： #define 宏名(形参表) 字符串 在字符串中含有各个形参。带参宏调用的一般形式为： 宏名(实参表)； 
例如： #define M(y) y*y+3*y /*宏定义*/
:
k=M(5); /*宏调用*/
: 在宏调用时，用实参5去代替形参y， 经预处理宏展开后的语句
为： k=5*5+3*5
#define MAX(a,b) (a&gt;b)?a:b
main(){
int x,y,max;
printf(&quot;input two numbers: &quot;);
scanf(&quot;%d%d&quot;,&amp;x,&amp;y);
max=MAX(x,y);
printf(&quot;max=%d\n&quot;,max);
} 上例程序的第一行进行带参宏定义，用宏名MAX表示条件表达式(a&gt;b)?a:b，形参a,b均出现在条件表达式中。程序第七行max=MAX(x,
y)为宏调用，实参x,y，将代换形参a,b。宏展开后该语句为： max=(x&gt;y)?x:y;用于计算x,y中的大数。对于带参的宏定义有以下问题需要说明：1. 带参宏定义中，宏名和形参表之间不能有空格出现。例如把： #define MAX(a,b) (a&gt;b)?a:b写为： #define MAX (a,b) (a&gt;b)?a:b 将被认为是无参宏定义，宏名MAX代表字符串 (a,b)(a&gt;b)?a:b。宏展开时，宏调用语句： max=MAX(x,y);将变为： max=(a,b)(a&gt;b)?a:b(x,y);这显然是错误的。2. 在带参宏定义中，形式参数不分配内存单元，因此不必作类型定义。而宏调用中的实参有具体的值。要用它们去代换形参，因此必须作类型说明。这是与函数中的情况不同的。在函数中，形参和实参是两个不同的量，各有自己的作用域，调用时要把实参值赋予形参，进行“值传递”。而在带参宏中，只是符号代换，不存在值传递的问题。3. 在宏定义中的形参是标识符，而宏调用中的实参可以是表达式。#define SQ(y) (y)*(y)
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 上例中第一行为宏定义，形参为y。程序第七行宏调用中实参为a+1，是一个表达式，在宏展开时，用a+1代换y，再用(y)*(y) 代换SQ，得到如下语句： sq=(a+1)*(a+1); 这与函数的调用是不同的， 函数调用时要把实参表达式的值求出来再赋予形参。 而宏代换中对实参表达式不作计算直接地照原样代换。4. 在宏定义中，字符串内的形参通常要用括号括起来以避免出错。 在上例中的宏定义中(y)*(y)表达式的y都用括号括起来，因此结果是正确的。如果去掉括号，把程序改为以下形式：#define SQ(y) y*y
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 运行结果为：input a number:3sq=7 同样输入3，但结果却是不一样的。问题在哪里呢? 这是由于代换只作符号代换而不作其它处理而造成的。 宏代换后将得到以下语句： sq=a+1*a+1; 由于a为3故sq的值为7。这显然与题意相违，因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的，请看下面程序：#define SQ(y) (y)*(y)
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=160/SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 本程序与前例相比，只把宏调用语句改为： sq=160/SQ(a+1); 运行本程序如输入值仍为3时，希望结果为10。但实际运行的结果如下：input a number:3　sq=160为什么会得这样的结果呢?分析宏调用语句，在宏代换之后变为： sq=160/(a+1)*(a+1);a为3时，由于“/”和“*”运算符优先级和结合性相同， 则先作160/(3+1)得40，再作40*(3+1)最后得160。为了得到正确答案应在宏定义中的整个字符串外加括号， 程序修改如下#define SQ(y) ((y)*(y))
main(){int a,sq;printf(&quot;input a number: &quot;);scanf(&quot;%d&quot;,&amp;a);sq=160/SQ(a+1);printf(&quot;sq=%d\n&quot;,sq);
} 以上讨论说明，对于宏定义不仅应在参数两侧加括号， 也应在整个字符串外加括号。5. 带参的宏和带参函数很相似，但有本质上的不同，除上面已谈到的各点外，把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。main(){
int i=1;
while(i&lt;=5)
printf(&quot;%d\n&quot;,SQ(i++));
}
SQ(int y)
{return((y)*(y));
}#define SQ(y) ((y)*(y))
main(){int i=1;while(i&lt;=5)printf(&quot;%d\n&quot;,SQ(i++));
}  在上例中函数名为SQ，形参为Y，函数体表达式为((y)*(y))。在例9.6中宏名为SQ，形参也为y，字符串表达式为(y)*(y))。 两例是相同的。例9.6的函数调用为SQ(i++)，例9.7的宏调用为SQ(i++)，实参也是相同的。从输出结果来看，却大不相同。分析如下：在例9.6中，函数调用是把实参i值传给形参y后自增1。 然后输出函数值。因而要循环5次。输出1～5的平方值。而在例9.7中宏调用时，只作代换。SQ(i++)被代换为((i++)*(i++))。在第一次循环时，由于i等于1，其计算过程为：表达式中前一个i初值为1，然后i自增1变为2，因此表达式中第2个i初值为2，两相乘的结果也为2，然后i值再自增1，得3。在第二次循环时，i值已有初值为3，因此表达式中前一个i为3，后一个i为4， 乘积为12，然后i再自增1变为5。进入第三次循环，由于i 值已为5，所以这将是最后一次循环。计算表达式的值为5*6等于30。i值再自增1变为6，不再满足循环条件，停止循环。从以上分析可以看出函数调用和宏调用二者在形式上相似， 在本质上是完全不同的。6. 宏定义也可用来定义多个语句，在宏调用时，把这些语句又代换到源程序内。看下面的例子。#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
main(){int l=3,w=4,h=5,sa,sb,sc,vv;SSSV(sa,sb,sc,vv);printf(&quot;sa=%d\nsb=%d\nsc=%d\nvv=%d\n&quot;,sa,sb,sc,vv);
} 程序第一行为宏定义，用宏名SSSV表示4个赋值语句，4 个形参分别为4个赋值符左部的变量。在宏调用时，把4 个语句展开并用实参代替形参。使计算结果送入实参之中。文件包含 文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式为： #include&quot;文件名&quot; 在前面我们已多次用此命令包含过库函数的头文件。例如： #include&quot;stdio.h&quot;
#include&quot;math.h&quot;  文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行， 从而把指定的文件和当前的源程序文件连成一个源文件。在程序设计中，文件包含是很有用的。 一个大的程序可以分为多个模块，由多个程序员分别编程。 有些公用的符号常量或宏定义等可单独组成一个文件， 在其它文件的开头用包含命令包含该文件即可使用。这样，可避免在每个文件开头都去书写那些公用量， 从而节省时间，并减少出错。对文件包含命令还要说明以下几点：1. 包含命令中的文件名可以用双引号括起来，也可以用尖括号括起来。例如以下写法都是允许的： #include&quot;stdio.h&quot;　#include&lt;math.h&gt; 但是这两种形式是有区别的：使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的)， 而不在源文件目录去查找； 使用双引号则表示首先在当前的源文件目录中查找，若未找到才到包含目录中去查找。 用户编程时可根据自己文件所在的目录来选择某一种命令形式。2. 一个include命令只能指定一个被包含文件， 若有多个文件要包含，则需用多个include命令。3. 文件包含允许嵌套，即在一个被包含的文件中又可以包含另一个文件。条件编译预处理程序提供了条件编译的功能。 可以按不同的条件去编译不同的程序部分，因而产生不同的目标代码文件。 这对于程序的移植和调试是很有用的。 条件编译有三种形式，下面分别介绍：1. 第一种形式： #ifdef 标识符 
程序段1 
#else 
程序段2 
#endif  它的功能是，如果标识符已被 #define命令定义过则对程序段1进行编译；否则对程序段2进行编译。如果没有程序段2(它为空)，本格式中的#else可以没有， 即可以写为： #ifdef 标识符 
程序段 #endif 
#define NUM ok
main(){struct stu{int num;char *name;char sex;float score;} *ps;ps=(struct stu*)malloc(sizeof(struct stu));ps-&gt;num=102;ps-&gt;name=&quot;Zhang ping&quot;;ps-&gt;sex='M';ps-&gt;score=62.5;#ifdef NUMprintf(&quot;Number=%d\nScore=%f\n&quot;,ps-&gt;num,ps-&gt;score);#elseprintf(&quot;Name=%s\nSex=%c\n&quot;,ps-&gt;name,ps-&gt;sex);#endiffree(ps);
}  由于在程序的第16行插入了条件编译预处理命令， 因此要根据NUM是否被定义过来决定编译那一个printf语句。而在程序的第一行已对NUM作过宏定义，因此应对第一个printf语句作编译故运行结果是输出了学号和成绩。在程序的第一行宏定义中，定义NUM表示字符串OK，其实也可以为任何字符串，甚至不给出任何字符串，写为： #define NUM 也具有同样的意义。 只有取消程序的第一行才会去编译第二个printf语句。读者可上机试作。2. 第二种形式： #ifndef 标识符 
程序段1 
#else 
程序段2 
#endif  与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是，如果标识符未被#define命令定义过则对程序段1进行编译， 否则对程序段2进行编译。这与第一种形式的功能正相反。 3. 第三种形式： #if 常量表达式 
程序段1 
#else 
程序段2 
#endif  它的功能是，如常量表达式的值为真(非0)，则对程序段1 进行编译，否则对程序段2进行编译。因此可以使程序在不同条件下，完成不同的功能#define R 1
main(){float c,r,s;printf (&quot;input a number: &quot;);scanf(&quot;%f&quot;,&amp;c);#if Rr=3.14159*c*c;printf(&quot;area of round is: %f\n&quot;,r);#elses=c*c;printf(&quot;area of square is: %f\n&quot;,s);#endif
} 本例中采用了第三种形式的条件编译。在程序第一行宏定义中，定义R为1，因此在条件编译时，常量表达式的值为真， 故计算并输出圆面积。上面介绍的条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译，生成的目标代码程序很长，而采用条件编译，则根据条件只编译其中的程序段1或程序段2， 生成的目标程序较短。如果条件选择的程序段很长， 采用条件编译的方法是十分必要的。</subject><description><![CDATA[概述在前面各章中，已多次使用过以“#”号开头的预处理命令。如包含命令# include，宏定义命令# define等。在源程序中这些命令都放在函数之外， 而且一般都放在源文件的前面，它们称为预处理部分。 所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能， 它由预处理程序负责完成。当对一个源文件进行编译时， 系统将自动引用预处理程序对源程序中的预处理部分作处理， 处理完毕自动进入对源程序的编译。C语言提供了多种预处理功能，如宏定义、文件包含、 条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、 移植和调试，也有利于模块化程序设计。本章介绍常用的几种预处理功能。宏定义在C语言源程序中允许用一个标识符来表示一个字符串， 称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时，对程序中所有出现的“宏名”，都用宏定义中的字符串去代换， 这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。 宏代换是由预处理程序自动完成的。在C语言中，“宏”分为有参数和无参数两种。 下面分别讨论这两种“宏”的定义和调用。无参宏定义无参宏的宏名后不带参数。其定义的一般形式为： #define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。 “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。在前面介绍过的符号常量的定义就是一种无参宏定义。 此外，常对程序中反复使用的表达式进行宏定义。例如： # define M (y*y+3*y) 定义M表达式(y*y+3*y)。在编写源程序时，所有的(y*y+3*y)都可由M代替，而对源程序作编译时，将先由预处理程序进行宏代换，即用(y*y+3*y)表达式去置换所有的宏名M，然后再进行编译。#define M (y*y+3*y)
main(){int s,y;printf(&quot;input a number: &quot;);scanf(&quot;%d&quot;,&amp;y);s=3*M+4*M+5*M;printf(&quot;s=%d\n&quot;,s);
} 上例程序中首先进行宏定义，定义M表达式(y*y+3*y),在s= 3*M+4*M+5* M中作了宏调用。在预处理时经宏展开后该语句变为：s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意的是，在宏定义中表达式(y*y+3*y)两边的括号不能少。否则会发生错误。当作以下定义后： #difine M y*y+3*y在宏展开时将得到下述语句： s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y；这相当于； 3y2+3y+4y2+3y+5y2+3y；显然与原题意要求不符。计算结果当然是错误的。 因此在作宏定义时必须十分注意。应保证在宏代换之后不发生错误。对于宏定义还要说明以下几点：1. 宏定义是用宏名来表示一个字符串，在宏展开时又以该字符串取代宏名，这只是一种简单的代换，字符串中可以含任何字符，可以是常数，也可以是表达式，预处理程序对它不作任何检查。如有错误，只能在编译已被宏展开后的源程序时发现。2. 宏定义不是说明或语句，在行末不必加分号，如加上分号则连分号也一起置换。3. 宏定义必须写在函数之外，其作用域为宏定义命令起到源程序结 束。如要终止其作用域可使用# undef命令，例如： # define PI 3.14159
main() 
{
……
} # undef PIPI的作用域f1()....表示PI只在main函数中有效，在f1中无效。4. 宏名在源程序中若用引号括起来，则预处理程序不对其作宏代换。#define OK 100
main()
{printf(&quot;OK&quot;);printf(&quot;\n&quot;);
} 上例中定义宏名OK表示100，但在printf语句中OK被引号括起来，因此不作宏代换。程序的运行结果为：OK这表示把“OK”当字符串处理。5. 宏定义允许嵌套，在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如： #define PI 3.1415926
#define S PI*y*y /* PI是已定义的宏名*/对语句： printf(&quot;%f&quot;,s); 在宏代换后变为： printf(&quot;%f&quot;,3.1415926*y*y);6. 习惯上宏名用大写字母表示，以便于与变量区别。但也允许用小写字母。7. 可用宏定义表示数据类型，使书写方便。例如： #define STU struct stu在程序中可用STU作变量说明：STU body[5],*p;#define INTEGER int 在程序中即可用INTEGER作整型变量说明： INTEGER a,b; 应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串代换，是在预处理完成的，而typedef是在编译时处理的，它不是作简单的代换， 而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。请看下面的例子： #define PIN1 int*　typedef (int*) PIN2;从形式上看这两者相似， 但在实际使用中却不相同。下面用PIN1，PIN2说明变量时就可以看出它们的区别： PIN1 a,b;在宏代换后变成 int *a,b;表示a是指向整型的指针变量，而b是整型变量。然而：PIN2 a,b;表示a,b都是指向整型的指针变量。因为PIN2是一个类型说明符。由这个例子可见，宏定义虽然也可表示数据类型， 但毕竟是作字符代换。在使用时要分外小心，以避出错。8. 对“输出格式”作宏定义，可以减少书写麻烦。例9.3 中就采用了这种方法。#define P printf
#define D &quot;%d\n&quot;
#define F &quot;%f\n&quot;
main(){
int a=5, c=8, e=11;
float b=3.8, d=9.7, f=21.08;
P(D F,a,b);
P(D F,c,d);
P(D F,e,f);
} 带参宏定义C语言允许宏带有参数。在宏定义中的参数称为形式参数， 在宏调用中的参数称为实际参数。对带参数的宏，在调用中，不仅要宏展开， 而且要用实参去代换形参。带参宏定义的一般形式为： #define 宏名(形参表) 字符串 在字符串中含有各个形参。带参宏调用的一般形式为： 宏名(实参表)； 
例如： #define M(y) y*y+3*y /*宏定义*/
:
k=M(5); /*宏调用*/
: 在宏调用时，用实参5去代替形参y， 经预处理宏展开后的语句
为： k=5*5+3*5
#define MAX(a,b) (a&gt;b)?a:b
main(){
int x,y,max;
printf(&quot;input two numbers: &quot;);
scanf(&quot;%d%d&quot;,&amp;x,&amp;y);
max=MAX(x,y);
printf(&quot;max=%d\n&quot;,max);
} 上例程序的第一行进行带参宏定义，用宏名MAX表示条件表达式(a&gt;b)?a:b，形参a,b均出现在条件表达式中。程序第七行max=MAX(x,
y)为宏调用，实参x,y，将代换形参a,b。宏展开后该语句为： max=(x&gt;y)?x:y;用于计算x,y中的大数。对于带参的宏定义有以下问题需要说明：1. 带参宏定义中，宏名和形参表之间不能有空格出现。例如把： #define MAX(a,b) (a&gt;b)?a:b写为： #define MAX (a,b) (a&gt;b)?a:b 将被认为是无参宏定义，宏名MAX代表字符串 (a,b)(a&gt;b)?a:b。宏展开时，宏调用语句： max=MAX(x,y);将变为： max=(a,b)(a&gt;b)?a:b(x,y);这显然是错误的。2. 在带参宏定义中，形式参数不分配内存单元，因此不必作类型定义。而宏调用中的实参有具体的值。要用它们去代换形参，因此必须作类型说明。这是与函数中的情况不同的。在函数中，形参和实参是两个不同的量，各有自己的作用域，调用时要把实参值赋予形参，进行“值传递”。而在带参宏中，只是符号代换，不存在值传递的问题。3. 在宏定义中的形参是标识符，而宏调用中的实参可以是表达式。#define SQ(y) (y)*(y)
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 上例中第一行为宏定义，形参为y。程序第七行宏调用中实参为a+1，是一个表达式，在宏展开时，用a+1代换y，再用(y)*(y) 代换SQ，得到如下语句： sq=(a+1)*(a+1); 这与函数的调用是不同的， 函数调用时要把实参表达式的值求出来再赋予形参。 而宏代换中对实参表达式不作计算直接地照原样代换。4. 在宏定义中，字符串内的形参通常要用括号括起来以避免出错。 在上例中的宏定义中(y)*(y)表达式的y都用括号括起来，因此结果是正确的。如果去掉括号，把程序改为以下形式：#define SQ(y) y*y
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 运行结果为：input a number:3sq=7 同样输入3，但结果却是不一样的。问题在哪里呢? 这是由于代换只作符号代换而不作其它处理而造成的。 宏代换后将得到以下语句： sq=a+1*a+1; 由于a为3故sq的值为7。这显然与题意相违，因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的，请看下面程序：#define SQ(y) (y)*(y)
main(){
int a,sq;
printf(&quot;input a number: &quot;);
scanf(&quot;%d&quot;,&amp;a);
sq=160/SQ(a+1);
printf(&quot;sq=%d\n&quot;,sq);
} 本程序与前例相比，只把宏调用语句改为： sq=160/SQ(a+1); 运行本程序如输入值仍为3时，希望结果为10。但实际运行的结果如下：input a number:3　sq=160为什么会得这样的结果呢?分析宏调用语句，在宏代换之后变为： sq=160/(a+1)*(a+1);a为3时，由于“/”和“*”运算符优先级和结合性相同， 则先作160/(3+1)得40，再作40*(3+1)最后得160。为了得到正确答案应在宏定义中的整个字符串外加括号， 程序修改如下#define SQ(y) ((y)*(y))
main(){int a,sq;printf(&quot;input a number: &quot;);scanf(&quot;%d&quot;,&amp;a);sq=160/SQ(a+1);printf(&quot;sq=%d\n&quot;,sq);
} 以上讨论说明，对于宏定义不仅应在参数两侧加括号， 也应在整个字符串外加括号。5. 带参的宏和带参函数很相似，但有本质上的不同，除上面已谈到的各点外，把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。main(){
int i=1;
while(i&lt;=5)
printf(&quot;%d\n&quot;,SQ(i++));
}
SQ(int y)
{return((y)*(y));
}#define SQ(y) ((y)*(y))
main(){int i=1;while(i&lt;=5)printf(&quot;%d\n&quot;,SQ(i++));
}  在上例中函数名为SQ，形参为Y，函数体表达式为((y)*(y))。在例9.6中宏名为SQ，形参也为y，字符串表达式为(y)*(y))。 两例是相同的。例9.6的函数调用为SQ(i++)，例9.7的宏调用为SQ(i++)，实参也是相同的。从输出结果来看，却大不相同。分析如下：在例9.6中，函数调用是把实参i值传给形参y后自增1。 然后输出函数值。因而要循环5次。输出1～5的平方值。而在例9.7中宏调用时，只作代换。SQ(i++)被代换为((i++)*(i++))。在第一次循环时，由于i等于1，其计算过程为：表达式中前一个i初值为1，然后i自增1变为2，因此表达式中第2个i初值为2，两相乘的结果也为2，然后i值再自增1，得3。在第二次循环时，i值已有初值为3，因此表达式中前一个i为3，后一个i为4， 乘积为12，然后i再自增1变为5。进入第三次循环，由于i 值已为5，所以这将是最后一次循环。计算表达式的值为5*6等于30。i值再自增1变为6，不再满足循环条件，停止循环。从以上分析可以看出函数调用和宏调用二者在形式上相似， 在本质上是完全不同的。6. 宏定义也可用来定义多个语句，在宏调用时，把这些语句又代换到源程序内。看下面的例子。#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
main(){int l=3,w=4,h=5,sa,sb,sc,vv;SSSV(sa,sb,sc,vv);printf(&quot;sa=%d\nsb=%d\nsc=%d\nvv=%d\n&quot;,sa,sb,sc,vv);
} 程序第一行为宏定义，用宏名SSSV表示4个赋值语句，4 个形参分别为4个赋值符左部的变量。在宏调用时，把4 个语句展开并用实参代替形参。使计算结果送入实参之中。文件包含 文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式为： #include&quot;文件名&quot; 在前面我们已多次用此命令包含过库函数的头文件。例如： #include&quot;stdio.h&quot;
#include&quot;math.h&quot;  文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行， 从而把指定的文件和当前的源程序文件连成一个源文件。在程序设计中，文件包含是很有用的。 一个大的程序可以分为多个模块，由多个程序员分别编程。 有些公用的符号常量或宏定义等可单独组成一个文件， 在其它文件的开头用包含命令包含该文件即可使用。这样，可避免在每个文件开头都去书写那些公用量， 从而节省时间，并减少出错。对文件包含命令还要说明以下几点：1. 包含命令中的文件名可以用双引号括起来，也可以用尖括号括起来。例如以下写法都是允许的： #include&quot;stdio.h&quot;　#include&lt;math.h&gt; 但是这两种形式是有区别的：使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的)， 而不在源文件目录去查找； 使用双引号则表示首先在当前的源文件目录中查找，若未找到才到包含目录中去查找。 用户编程时可根据自己文件所在的目录来选择某一种命令形式。2. 一个include命令只能指定一个被包含文件， 若有多个文件要包含，则需用多个include命令。3. 文件包含允许嵌套，即在一个被包含的文件中又可以包含另一个文件。条件编译预处理程序提供了条件编译的功能。 可以按不同的条件去编译不同的程序部分，因而产生不同的目标代码文件。 这对于程序的移植和调试是很有用的。 条件编译有三种形式，下面分别介绍：1. 第一种形式： #ifdef 标识符 
程序段1 
#else 
程序段2 
#endif  它的功能是，如果标识符已被 #define命令定义过则对程序段1进行编译；否则对程序段2进行编译。如果没有程序段2(它为空)，本格式中的#else可以没有， 即可以写为： #ifdef 标识符 
程序段 #endif 
#define NUM ok
main(){struct stu{int num;char *name;char sex;float score;} *ps;ps=(struct stu*)malloc(sizeof(struct stu));ps-&gt;num=102;ps-&gt;name=&quot;Zhang ping&quot;;ps-&gt;sex='M';ps-&gt;score=62.5;#ifdef NUMprintf(&quot;Number=%d\nScore=%f\n&quot;,ps-&gt;num,ps-&gt;score);#elseprintf(&quot;Name=%s\nSex=%c\n&quot;,ps-&gt;name,ps-&gt;sex);#endiffree(ps);
}  由于在程序的第16行插入了条件编译预处理命令， 因此要根据NUM是否被定义过来决定编译那一个printf语句。而在程序的第一行已对NUM作过宏定义，因此应对第一个printf语句作编译故运行结果是输出了学号和成绩。在程序的第一行宏定义中，定义NUM表示字符串OK，其实也可以为任何字符串，甚至不给出任何字符串，写为： #define NUM 也具有同样的意义。 只有取消程序的第一行才会去编译第二个printf语句。读者可上机试作。2. 第二种形式： #ifndef 标识符 
程序段1 
#else 
程序段2 
#endif  与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是，如果标识符未被#define命令定义过则对程序段1进行编译， 否则对程序段2进行编译。这与第一种形式的功能正相反。 3. 第三种形式： #if 常量表达式 
程序段1 
#else 
程序段2 
#endif  它的功能是，如常量表达式的值为真(非0)，则对程序段1 进行编译，否则对程序段2进行编译。因此可以使程序在不同条件下，完成不同的功能#define R 1
main(){float c,r,s;printf (&quot;input a number: &quot;);scanf(&quot;%f&quot;,&amp;c);#if Rr=3.14159*c*c;printf(&quot;area of round is: %f\n&quot;,r);#elses=c*c;printf(&quot;area of square is: %f\n&quot;,s);#endif
} 本例中采用了第三种形式的条件编译。在程序第一行宏定义中，定义R为1，因此在条件编译时，常量表达式的值为真， 故计算并输出圆面积。上面介绍的条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译，生成的目标代码程序很长，而采用条件编译，则根据条件只编译其中的程序段1或程序段2， 生成的目标程序较短。如果条件选择的程序段很长， 采用条件编译的方法是十分必要的。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十二讲 结构(3)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16896</link><subject>结构指针变量作函数参数在ANSI C标准中允许用结构变量作函数参数进行整体传送。 但是这种传送要将全部成员逐个传送， 特别是成员为数组时将会使传送的时间和空间开销很大，严重地降低了程序的效率。 因此最好的办法就是使用指针，即用指针变量作函数参数进行传送。 这时由实参传向形参的只是地址，从而减少了时间和空间的开销。[例7.8]题目与例7.4相同，计算一组学生的平均成绩和不及格人数。用结构指针变量作函数参数编程。struct stu
{int num;char *name;char sex;float score;}boy[5]={{101,&quot;Li ping&quot;,'M',45},{102,&quot;Zhang ping&quot;,'M',62.5},{103,&quot;He fang&quot;,'F',92.5},{104,&quot;Cheng ling&quot;,'F',87},{105,&quot;Wang ming&quot;,'M',58},};
main()
{struct stu *ps;void ave(struct stu *ps);ps=boy;ave(ps);
}
void ave(struct stu *ps)
{int c=0,i;float ave,s=0;for(i=0;i&lt;5;i++,ps++){s+=ps-&gt;score;if(ps-&gt;score&lt;60) c+=1;}printf(&quot;s=%f\n&quot;,s);ave=s/5;printf(&quot;average=%f\ncount=%d\n&quot;,ave,c);
}  本程序中定义了函数ave，其形参为结构指针变量ps。boy 被定义为外部结构数组，因此在整个源程序中有效。在main 函数中定义说明了结构指针变量ps，并把boy的首地址赋予它，使ps指向boy 数组。然后以ps作实参调用函数ave。在函数ave 中完成计算平均成绩和统计不及格人数的工作并输出结果。与例7.4程序相比，由于本程序全部采用指针变量作运算和处理，故速度更快，程序效率更高。.topoic=动态存储分配在数组一章中，曾介绍过数组的长度是预先定义好的， 在整个程序中固定不变。C语言中不允许动态数组类型。例如： int n;scanf(&quot;%d&quot;,&amp;n);int a[n]; 用变量表示长度，想对数组的大小作动态说明， 这是错误的。但是在实际的编程中，往往会发生这种情况， 即所需的内存空间取决于实际输入的数据，而无法预先确定。对于这种问题， 用数组的办法很难解决。为了解决上述问题，C语言提供了一些内存管理函数，这些内存管理函数可以按需要动态地分配内存空间， 也可把不再使用的空间回收待用，为有效地利用内存资源提供了手段。 常用的内存管理函数有以下三个：1.分配内存空间函数malloc调用形式： (类型说明符*) malloc (size) 功能：在内存的动态存储区中分配一块长度为&quot;size&quot; 字节的连续区域。函数的返回值为该区域的首地址。 “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如： pc=(char *) malloc (100); 表示分配100个字节的内存空间，并强制转换为字符数组类型， 函数的返回值为指向该字符数组的指针， 把该指针赋予指针变量pc。2.分配内存空间函数 calloccalloc 也用于分配内存空间。调用形式： (类型说明符*)calloc(n,size) 功能：在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如： ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是：按stu的长度分配2块连续区域，强制转换为stu类型，并把其首地址赋予指针变量ps。3.释放内存空间函数free调用形式： free(void*ptr); 功能：释放ptr所指向的一块内存空间，ptr 是一个任意类型的指针变量，它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域：[例7.9]分配一块区域，输入一个学生数据。main()
{struct stu{int num;char *name;char sex;float score;} *ps;ps=(struct stu*)malloc(sizeof(struct stu));ps-&gt;num=102;ps-&gt;name=&quot;Zhang ping&quot;;ps-&gt;sex='M';ps-&gt;score=62.5;printf(&quot;Number=%d\nName=%s\n&quot;,ps-&gt;num,ps-&gt;name);printf(&quot;Sex=%c\nScore=%f\n&quot;,ps-&gt;sex,ps-&gt;score);free(ps);
} 本例中，定义了结构stu，定义了stu类型指针变量ps。 然后分配一块stu大内存区，并把首地址赋予ps，使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值，并用printf 输出各成员值。最后用free函数释放ps指向的内存空间。 整个程序包含了申请内存空间、使用内存空间、释放内存空间三个步骤， 实现存储空间的动态分配。链表的概念在例7.9中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据， 我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间， 也就是说要建立多少个结点。当然用结构数组也可以完成上述工作， 但如果预先不能准确把握学生人数，也就无法确定数组大小。 而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。 用动态存储的方法可以很好地解决这些问题。 有一个学生就分配一个结点，无须预先确定学生的准确人数，某学生退学， 可删去该结点，并释放该结点占用的存储空间。从而节约了宝贵的内存资源。 另一方面，用数组的方法必须占用一块连续的内存区域。 而使用动态分配时，每个结点之间可以是不连续的(结点内是连续的)。 结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址，这个用于存放地址的成员，常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址， 在第二个结点的指针域内又存放第三个结点的首地址， 如此串连下去直到最后一个结点。最后一个结点因无后续结点连接，其指针域可赋为0。这样一种连接方式，在数据结构中称为“链表”。在链表中，第0个结点称为头结点， 它存放有第一个结点的首地址，它没有数据，只是一个指针变量。 以下的每个结点都分为两个域，一个是数据域，存放各种实际的数据，如学号num，姓名name，性别sex和成绩score等。另一个域为指针域， 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。例如， 一个存放学生学号和成绩的结点应为以下结构：struct stu
{int num;int score;struct stu *next;
} 前两个成员项组成数据域，后一个成员项next构成指针域， 它是一个指向stu类型结构的指针变量。链表的基本操作对链表的主要操作有以下几种： 1.建立链表；2.结构的查找与输出；3.插入一个结点；4.删除一个结点；下面通过例题来说明这些操作。[例7.10]建立一个三个结点的链表，存放学生数据。 为简单起见， 我们假定学生数据结构中只有学号和年龄两项。可编写一个建立链表的函数creat。程序如下：#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{int num;int age;struct stu *next;
};
TYPE *creat(int n)
{struct stu *head,*pf,*pb;int i;for(i=0;i&lt;n;i++){ pb=(TYPE*) malloc(LEN);printf(&quot;input Number and Age\n&quot;);scanf(&quot;%d%d&quot;,&amp;pb-&gt;num,&amp;pb-&gt;age);if(i==0)pf=head=pb;else pf-&gt;next=pb;pb-&gt;next=NULL;pf=pb;}return(head);
} 在函数外首先用宏定义对三个符号常量作了定义。这里用TYPE表示struct stu，用LEN表示sizeof(struct stu)主要的目的是为了在以下程序内减少书写并使阅读更加方便。结构stu定义为外部类型，程序中的各个函数均可使用该定义。creat函数用于建立一个有n个结点的链表，它是一个指针函数，它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针，pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内，用malloc函数建立长度与stu长度相等的空间作为一结点，首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0)，则把pb值 (该结点指针)赋予head和pf。如非第一结点，则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点，其指针域赋NULL。 再把pb值赋予pf以作下一次循环准备。creat函数的形参n，表示所建链表的结点数，作为for语句的循环次数。图7.4表示了creat函数的执行过程。[例7.11]写一个函数，在链表中按学号查找该结点。TYPE * search (TYPE *head,int n)
{TYPE *p;int i;p=head;while (p-&gt;num!=n &amp;&amp; p-&gt;next!=NULL)p=p-&gt;next; /* 不是要找的结点后移一步*/if (p-&gt;num==n) return (p);if (p-&gt;num!=n&amp;&amp; p-&gt;next==NULL)printf (&quot;Node %d has not been found!\n&quot;,n
} 本函数中使用的符号常量TYPE与例7.10的宏定义相同，等于struct　stu。函数有两个形参，head是指向链表的指针变量，n为要查找的学号。进入while语句，逐个检查结点的num成员是否等于n，如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点，继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。[例7.12]写一个函数，删除链表中的指定结点。删除一个结点有两种情况：1. 被删除结点是第一个结点。这种情况只需使head指向第二个结点即可。即head=pb-&gt;next。其过程如图7.5所示。2. 被删结点不是第一个结点，这种情况使被删结点的前一结点指向被删结点的后一结点即可。即pf-&gt;next=pb-&gt;next。函数编程如下：TYPE * delete(TYPE * head,int num)
{TYPE *pf,*pb;if(head==NULL) /*如为空表， 输出提示信息*/{printf(&quot;\nempty list!\n&quot;);goto end;}pb=head;while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL)/*当不是要删除的结点，而且也不是最后一个结点时，继续循环*/{pf=pb;pb=pb-&gt;next;}/*pf指向当前结点，pb指向下一结点*/if(pb-&gt;num==num){if(pb==head) head=pb-&gt;next;/*如找到被删结点，且为第一结点，则使head指向第二个结点，否则使pf所指结点的指针指向下一结点*/else pf-&gt;next=pb-&gt;next;free(pb);printf(&quot;The node is deleted\n&quot;);}elseprintf(&quot;The node not been foud!\n&quot;);end:return head;}  函数有两个形参，head为指向链表第一结点的指针变量，num删结点的学号。 首先判断链表是否为空，为空则不可能有被删结点。若不为空，则使pb指针指向链表的第一个结点。进入while语句后逐个查找被删结点。找到被删结点之后再看是否为第一结点，若是则使head指向第二结点(即把第一结点从链中删去)，否则使被删结点的前一结点(pf所指)指向被删结点的后一结点(被删结点的指针域所指)。如若循环结束未找到要删的结点， 则输出“末找到”的提示信息。最后返回head值。[例7.13]写一个函数，在链表中指定位置插入一个结点。在一个链表的指定位置插入结点， 要求链表本身必须是已按某种规律排好序的。例如，在学生数据链表中， 要求学号顺序插入一个结点。设被插结点的指针为pi。 可在三种不同情况下插入。1. 原表是空表，只需使head指向被插结点即可。2. 被插结点值最小，应插入第一结点之前。这种情况下使head指向被插结点，被插结点的指针域指向原来的第一结点则可。即：pi-&gt;next=pb;
head=pi; 3. 在其它位置插入。这种情况下，使插入位置的前一结点的指针域指向被插结点，使被插结点的指针域指向插入位置的后一结点。即为：pi-&gt;next=pb;pf-&gt;next=pi；4. 在表末插入。这种情况下使原表末结点指针域指向被插结点，被插结点指针域置为NULL。即：pb-&gt;next=pi;
pi-&gt;next=NULL； TYPE * insert(TYPE * head,TYPE *pi)
{TYPE *pf,*pb;pb=head;if(head==NULL) /*空表插入*/(head=pi;pi-&gt;next=NULL;}else{while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL)){pf=pb;pb=pb-&gt;next;}/*找插入位置*/if(pi-&gt;num&lt;=pb-&gt;num){if(head==pb)head=pi;/*在第一结点之前插入*/else pf-&gt;next=pi;/*在其它位置插入*/pi-&gt;next=pb; }else{pb-&gt;next=pi;pi-&gt;next=NULL;} /*在表末插入*/}return head;
}本函数有两个形参均为指针变量，head指向链表，pi 指向被插结点。函数中首先判断链表是否为空，为空则使head指向被插结点。表若不空，则用while语句循环查找插入位置。找到之后再判断是否在第一结点之前插入，若是则使head 指向被插结点被插结点指针域指向原第一结点，否则在其它位置插入， 若插入的结点大于表中所有结点，则在表末插入。本函数返回一个指针， 是链表的头指针。 当插入的位置在第一个结点之前时， 插入的新结点成为链表的第一个结点，因此head的值也有了改变， 故需要把这个指针返回主调函数。[例7.14]将以上建立链表，删除结点，插入结点的函数组织在一起，再建一个输出全部结点的函数，然后用main函数调用它们。#define NULL 0
#define TYPE struct stu
#define LEN sizeof(struct stu)
struct stu
{int num;int age;struct stu *next;
};
TYPE * creat(int n)
{struct stu *head,*pf,*pb;int i;for(i=0;i&lt;n;i++){pb=(TYPE *)malloc(LEN);printf(&quot;input Number and Age\n&quot;);scanf(&quot;%d%d&quot;,&amp;pb-&gt;num,&amp;pb-&gt;age);if(i==0)pf=head=pb;else pf-&gt;next=pb;pb-&gt;next=NULL;pf=pb;}return(head);
}
TYPE * delete(TYPE * head,int num)
{TYPE *pf,*pb;if(head==NULL){printf(&quot;\nempty list!\n&quot;);goto end;}pb=head;while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL){pf=pb;pb=pb-&gt;next;}if(pb-&gt;num==num){if(pb==head) head=pb-&gt;next;else pf-&gt;next=pb-&gt;next;printf(&quot;The node is deleted\n&quot;);}elsefree(pb);printf(&quot;The node not been found!\n&quot;);end:return head;
}
TYPE * insert(TYPE * head,TYPE * pi)
{TYPE *pb ,*pf;pb=head;if(head==NULL){head=pi;pi-&gt;next=NULL;}else{while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL)){pf=pb;pb=pb-&gt;next; }if(pi-&gt;num&lt;=pb-&gt;num){ if(head==pb) head=pi;else pf-&gt;next=pi;pi-&gt;next=pb;}else{pb-&gt;next=pi;pi-&gt;next=NULL;}}return head;
}
void print(TYPE * head)
{printf(&quot;Number\t\tAge\n&quot;);while(head!=NULL){printf(&quot;%d\t\t%d\n&quot;,head-&gt;num,head-&gt;age);head=head-&gt;next;}
}
main()
{TYPE * head,*pnum;int n,num;printf(&quot;input number of node: &quot;);scanf(&quot;%d&quot;,&amp;n);head=creat(n);print(head);printf(&quot;Input the deleted number: &quot;);scanf(&quot;%d&quot;,&amp;num);head=delete(head,num);print(head);printf(&quot;Input the inserted number and age: &quot;);pnum=(TYPE *)malloc(LEN);scanf(&quot;%d%d&quot;,&amp;pnum-&gt;num,&amp;pnum-&gt;age);head=insert(head,pnum);print(head);
} 本例中，print函数用于输出链表中各个结点数据域值。函数的形参head的初值指向链表第一个结点。在while语句中，输出结点值后，head值被改变，指向下一结点。若保留头指针head， 则应另设一个指针变量，把head值赋予它，再用它来替代head。在main函数中，n为建立结点的数目， num为待删结点的数据域值；head为指向链表的头指针，pnum为指向待插结点的指针。 main函数中各行的意义是：第六行输入所建链表的结点数；第七行调creat函数建立链表并把头指针返回给head；第八行调print函数输出链表；第十行输入待删结点的学号；第十一行调delete函数删除一个结点；第十二行调print函数输出链表；第十四行调malloc函数分配一个结点的内存空间， 并把其地址赋予pnum;第十五行输入待插入结点的数据域值；第十六行调insert函数插入pnum所指的结点；第十七行再次调print函数输出链表。从运行结果看，首先建立起3个结点的链表，并输出其值；再删103号结点，只剩下105，108号结点；又输入106号结点数据， 插入后链表中的结点为105，106，108。联合“联合”也是一种构造类型的数据结构。 在一个“联合”内可以定义多种不同的数据类型， 一个被说明为该“联合”类型的变量中，允许装入该“联合”所定义的任何一种数据。 这在前面的各种数据类型中都是办不到的。例如， 定义为整型的变量只能装入整型数据，定义为实型的变量只能赋予实型数据。</subject><description><![CDATA[结构指针变量作函数参数在ANSI C标准中允许用结构变量作函数参数进行整体传送。 但是这种传送要将全部成员逐个传送， 特别是成员为数组时将会使传送的时间和空间开销很大，严重地降低了程序的效率。 因此最好的办法就是使用指针，即用指针变量作函数参数进行传送。 这时由实参传向形参的只是地址，从而减少了时间和空间的开销。[例7.8]题目与例7.4相同，计算一组学生的平均成绩和不及格人数。用结构指针变量作函数参数编程。struct stu
{int num;char *name;char sex;float score;}boy[5]={{101,&quot;Li ping&quot;,'M',45},{102,&quot;Zhang ping&quot;,'M',62.5},{103,&quot;He fang&quot;,'F',92.5},{104,&quot;Cheng ling&quot;,'F',87},{105,&quot;Wang ming&quot;,'M',58},};
main()
{struct stu *ps;void ave(struct stu *ps);ps=boy;ave(ps);
}
void ave(struct stu *ps)
{int c=0,i;float ave,s=0;for(i=0;i&lt;5;i++,ps++){s+=ps-&gt;score;if(ps-&gt;score&lt;60) c+=1;}printf(&quot;s=%f\n&quot;,s);ave=s/5;printf(&quot;average=%f\ncount=%d\n&quot;,ave,c);
}  本程序中定义了函数ave，其形参为结构指针变量ps。boy 被定义为外部结构数组，因此在整个源程序中有效。在main 函数中定义说明了结构指针变量ps，并把boy的首地址赋予它，使ps指向boy 数组。然后以ps作实参调用函数ave。在函数ave 中完成计算平均成绩和统计不及格人数的工作并输出结果。与例7.4程序相比，由于本程序全部采用指针变量作运算和处理，故速度更快，程序效率更高。.topoic=动态存储分配在数组一章中，曾介绍过数组的长度是预先定义好的， 在整个程序中固定不变。C语言中不允许动态数组类型。例如： int n;scanf(&quot;%d&quot;,&amp;n);int a[n]; 用变量表示长度，想对数组的大小作动态说明， 这是错误的。但是在实际的编程中，往往会发生这种情况， 即所需的内存空间取决于实际输入的数据，而无法预先确定。对于这种问题， 用数组的办法很难解决。为了解决上述问题，C语言提供了一些内存管理函数，这些内存管理函数可以按需要动态地分配内存空间， 也可把不再使用的空间回收待用，为有效地利用内存资源提供了手段。 常用的内存管理函数有以下三个：1.分配内存空间函数malloc调用形式： (类型说明符*) malloc (size) 功能：在内存的动态存储区中分配一块长度为&quot;size&quot; 字节的连续区域。函数的返回值为该区域的首地址。 “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如： pc=(char *) malloc (100); 表示分配100个字节的内存空间，并强制转换为字符数组类型， 函数的返回值为指向该字符数组的指针， 把该指针赋予指针变量pc。2.分配内存空间函数 calloccalloc 也用于分配内存空间。调用形式： (类型说明符*)calloc(n,size) 功能：在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如： ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是：按stu的长度分配2块连续区域，强制转换为stu类型，并把其首地址赋予指针变量ps。3.释放内存空间函数free调用形式： free(void*ptr); 功能：释放ptr所指向的一块内存空间，ptr 是一个任意类型的指针变量，它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域：[例7.9]分配一块区域，输入一个学生数据。main()
{struct stu{int num;char *name;char sex;float score;} *ps;ps=(struct stu*)malloc(sizeof(struct stu));ps-&gt;num=102;ps-&gt;name=&quot;Zhang ping&quot;;ps-&gt;sex='M';ps-&gt;score=62.5;printf(&quot;Number=%d\nName=%s\n&quot;,ps-&gt;num,ps-&gt;name);printf(&quot;Sex=%c\nScore=%f\n&quot;,ps-&gt;sex,ps-&gt;score);free(ps);
} 本例中，定义了结构stu，定义了stu类型指针变量ps。 然后分配一块stu大内存区，并把首地址赋予ps，使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值，并用printf 输出各成员值。最后用free函数释放ps指向的内存空间。 整个程序包含了申请内存空间、使用内存空间、释放内存空间三个步骤， 实现存储空间的动态分配。链表的概念在例7.9中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据， 我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间， 也就是说要建立多少个结点。当然用结构数组也可以完成上述工作， 但如果预先不能准确把握学生人数，也就无法确定数组大小。 而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。 用动态存储的方法可以很好地解决这些问题。 有一个学生就分配一个结点，无须预先确定学生的准确人数，某学生退学， 可删去该结点，并释放该结点占用的存储空间。从而节约了宝贵的内存资源。 另一方面，用数组的方法必须占用一块连续的内存区域。 而使用动态分配时，每个结点之间可以是不连续的(结点内是连续的)。 结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址，这个用于存放地址的成员，常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址， 在第二个结点的指针域内又存放第三个结点的首地址， 如此串连下去直到最后一个结点。最后一个结点因无后续结点连接，其指针域可赋为0。这样一种连接方式，在数据结构中称为“链表”。在链表中，第0个结点称为头结点， 它存放有第一个结点的首地址，它没有数据，只是一个指针变量。 以下的每个结点都分为两个域，一个是数据域，存放各种实际的数据，如学号num，姓名name，性别sex和成绩score等。另一个域为指针域， 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。例如， 一个存放学生学号和成绩的结点应为以下结构：struct stu
{int num;int score;struct stu *next;
} 前两个成员项组成数据域，后一个成员项next构成指针域， 它是一个指向stu类型结构的指针变量。链表的基本操作对链表的主要操作有以下几种： 1.建立链表；2.结构的查找与输出；3.插入一个结点；4.删除一个结点；下面通过例题来说明这些操作。[例7.10]建立一个三个结点的链表，存放学生数据。 为简单起见， 我们假定学生数据结构中只有学号和年龄两项。可编写一个建立链表的函数creat。程序如下：#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{int num;int age;struct stu *next;
};
TYPE *creat(int n)
{struct stu *head,*pf,*pb;int i;for(i=0;i&lt;n;i++){ pb=(TYPE*) malloc(LEN);printf(&quot;input Number and Age\n&quot;);scanf(&quot;%d%d&quot;,&amp;pb-&gt;num,&amp;pb-&gt;age);if(i==0)pf=head=pb;else pf-&gt;next=pb;pb-&gt;next=NULL;pf=pb;}return(head);
} 在函数外首先用宏定义对三个符号常量作了定义。这里用TYPE表示struct stu，用LEN表示sizeof(struct stu)主要的目的是为了在以下程序内减少书写并使阅读更加方便。结构stu定义为外部类型，程序中的各个函数均可使用该定义。creat函数用于建立一个有n个结点的链表，它是一个指针函数，它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针，pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内，用malloc函数建立长度与stu长度相等的空间作为一结点，首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0)，则把pb值 (该结点指针)赋予head和pf。如非第一结点，则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点，其指针域赋NULL。 再把pb值赋予pf以作下一次循环准备。creat函数的形参n，表示所建链表的结点数，作为for语句的循环次数。图7.4表示了creat函数的执行过程。[例7.11]写一个函数，在链表中按学号查找该结点。TYPE * search (TYPE *head,int n)
{TYPE *p;int i;p=head;while (p-&gt;num!=n &amp;&amp; p-&gt;next!=NULL)p=p-&gt;next; /* 不是要找的结点后移一步*/if (p-&gt;num==n) return (p);if (p-&gt;num!=n&amp;&amp; p-&gt;next==NULL)printf (&quot;Node %d has not been found!\n&quot;,n
} 本函数中使用的符号常量TYPE与例7.10的宏定义相同，等于struct　stu。函数有两个形参，head是指向链表的指针变量，n为要查找的学号。进入while语句，逐个检查结点的num成员是否等于n，如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点，继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。[例7.12]写一个函数，删除链表中的指定结点。删除一个结点有两种情况：1. 被删除结点是第一个结点。这种情况只需使head指向第二个结点即可。即head=pb-&gt;next。其过程如图7.5所示。2. 被删结点不是第一个结点，这种情况使被删结点的前一结点指向被删结点的后一结点即可。即pf-&gt;next=pb-&gt;next。函数编程如下：TYPE * delete(TYPE * head,int num)
{TYPE *pf,*pb;if(head==NULL) /*如为空表， 输出提示信息*/{printf(&quot;\nempty list!\n&quot;);goto end;}pb=head;while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL)/*当不是要删除的结点，而且也不是最后一个结点时，继续循环*/{pf=pb;pb=pb-&gt;next;}/*pf指向当前结点，pb指向下一结点*/if(pb-&gt;num==num){if(pb==head) head=pb-&gt;next;/*如找到被删结点，且为第一结点，则使head指向第二个结点，否则使pf所指结点的指针指向下一结点*/else pf-&gt;next=pb-&gt;next;free(pb);printf(&quot;The node is deleted\n&quot;);}elseprintf(&quot;The node not been foud!\n&quot;);end:return head;}  函数有两个形参，head为指向链表第一结点的指针变量，num删结点的学号。 首先判断链表是否为空，为空则不可能有被删结点。若不为空，则使pb指针指向链表的第一个结点。进入while语句后逐个查找被删结点。找到被删结点之后再看是否为第一结点，若是则使head指向第二结点(即把第一结点从链中删去)，否则使被删结点的前一结点(pf所指)指向被删结点的后一结点(被删结点的指针域所指)。如若循环结束未找到要删的结点， 则输出“末找到”的提示信息。最后返回head值。[例7.13]写一个函数，在链表中指定位置插入一个结点。在一个链表的指定位置插入结点， 要求链表本身必须是已按某种规律排好序的。例如，在学生数据链表中， 要求学号顺序插入一个结点。设被插结点的指针为pi。 可在三种不同情况下插入。1. 原表是空表，只需使head指向被插结点即可。2. 被插结点值最小，应插入第一结点之前。这种情况下使head指向被插结点，被插结点的指针域指向原来的第一结点则可。即：pi-&gt;next=pb;
head=pi; 3. 在其它位置插入。这种情况下，使插入位置的前一结点的指针域指向被插结点，使被插结点的指针域指向插入位置的后一结点。即为：pi-&gt;next=pb;pf-&gt;next=pi；4. 在表末插入。这种情况下使原表末结点指针域指向被插结点，被插结点指针域置为NULL。即：pb-&gt;next=pi;
pi-&gt;next=NULL； TYPE * insert(TYPE * head,TYPE *pi)
{TYPE *pf,*pb;pb=head;if(head==NULL) /*空表插入*/(head=pi;pi-&gt;next=NULL;}else{while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL)){pf=pb;pb=pb-&gt;next;}/*找插入位置*/if(pi-&gt;num&lt;=pb-&gt;num){if(head==pb)head=pi;/*在第一结点之前插入*/else pf-&gt;next=pi;/*在其它位置插入*/pi-&gt;next=pb; }else{pb-&gt;next=pi;pi-&gt;next=NULL;} /*在表末插入*/}return head;
}本函数有两个形参均为指针变量，head指向链表，pi 指向被插结点。函数中首先判断链表是否为空，为空则使head指向被插结点。表若不空，则用while语句循环查找插入位置。找到之后再判断是否在第一结点之前插入，若是则使head 指向被插结点被插结点指针域指向原第一结点，否则在其它位置插入， 若插入的结点大于表中所有结点，则在表末插入。本函数返回一个指针， 是链表的头指针。 当插入的位置在第一个结点之前时， 插入的新结点成为链表的第一个结点，因此head的值也有了改变， 故需要把这个指针返回主调函数。[例7.14]将以上建立链表，删除结点，插入结点的函数组织在一起，再建一个输出全部结点的函数，然后用main函数调用它们。#define NULL 0
#define TYPE struct stu
#define LEN sizeof(struct stu)
struct stu
{int num;int age;struct stu *next;
};
TYPE * creat(int n)
{struct stu *head,*pf,*pb;int i;for(i=0;i&lt;n;i++){pb=(TYPE *)malloc(LEN);printf(&quot;input Number and Age\n&quot;);scanf(&quot;%d%d&quot;,&amp;pb-&gt;num,&amp;pb-&gt;age);if(i==0)pf=head=pb;else pf-&gt;next=pb;pb-&gt;next=NULL;pf=pb;}return(head);
}
TYPE * delete(TYPE * head,int num)
{TYPE *pf,*pb;if(head==NULL){printf(&quot;\nempty list!\n&quot;);goto end;}pb=head;while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL){pf=pb;pb=pb-&gt;next;}if(pb-&gt;num==num){if(pb==head) head=pb-&gt;next;else pf-&gt;next=pb-&gt;next;printf(&quot;The node is deleted\n&quot;);}elsefree(pb);printf(&quot;The node not been found!\n&quot;);end:return head;
}
TYPE * insert(TYPE * head,TYPE * pi)
{TYPE *pb ,*pf;pb=head;if(head==NULL){head=pi;pi-&gt;next=NULL;}else{while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL)){pf=pb;pb=pb-&gt;next; }if(pi-&gt;num&lt;=pb-&gt;num){ if(head==pb) head=pi;else pf-&gt;next=pi;pi-&gt;next=pb;}else{pb-&gt;next=pi;pi-&gt;next=NULL;}}return head;
}
void print(TYPE * head)
{printf(&quot;Number\t\tAge\n&quot;);while(head!=NULL){printf(&quot;%d\t\t%d\n&quot;,head-&gt;num,head-&gt;age);head=head-&gt;next;}
}
main()
{TYPE * head,*pnum;int n,num;printf(&quot;input number of node: &quot;);scanf(&quot;%d&quot;,&amp;n);head=creat(n);print(head);printf(&quot;Input the deleted number: &quot;);scanf(&quot;%d&quot;,&amp;num);head=delete(head,num);print(head);printf(&quot;Input the inserted number and age: &quot;);pnum=(TYPE *)malloc(LEN);scanf(&quot;%d%d&quot;,&amp;pnum-&gt;num,&amp;pnum-&gt;age);head=insert(head,pnum);print(head);
} 本例中，print函数用于输出链表中各个结点数据域值。函数的形参head的初值指向链表第一个结点。在while语句中，输出结点值后，head值被改变，指向下一结点。若保留头指针head， 则应另设一个指针变量，把head值赋予它，再用它来替代head。在main函数中，n为建立结点的数目， num为待删结点的数据域值；head为指向链表的头指针，pnum为指向待插结点的指针。 main函数中各行的意义是：第六行输入所建链表的结点数；第七行调creat函数建立链表并把头指针返回给head；第八行调print函数输出链表；第十行输入待删结点的学号；第十一行调delete函数删除一个结点；第十二行调print函数输出链表；第十四行调malloc函数分配一个结点的内存空间， 并把其地址赋予pnum;第十五行输入待插入结点的数据域值；第十六行调insert函数插入pnum所指的结点；第十七行再次调print函数输出链表。从运行结果看，首先建立起3个结点的链表，并输出其值；再删103号结点，只剩下105，108号结点；又输入106号结点数据， 插入后链表中的结点为105，106，108。联合“联合”也是一种构造类型的数据结构。 在一个“联合”内可以定义多种不同的数据类型， 一个被说明为该“联合”类型的变量中，允许装入该“联合”所定义的任何一种数据。 这在前面的各种数据类型中都是办不到的。例如， 定义为整型的变量只能装入整型数据，定义为实型的变量只能赋予实型数据。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十二讲 结构(1)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16898</link><subject>在实际问题中，一组数据往往具有不同的数据类型。例如， 在学生登记表中，姓名应为字符型；学号可为整型或字符型； 年龄应为整型；性别应为字符型；成绩可为整型或实型。 显然不能用一个数组来存放这一组数据。 因为数组中各元素的类型和长度都必须一致，以便于编译系统处理。为了解决这个问题，C语言中给出了另一种构造数据类型——“结构”。 它相当于其它高级语言中的记录。“结构”是一种构造类型，它是由若干“成员”组成的。 每一个成员可以是一个基本数据类型或者又是一个构造类型。 结构既是一种“构造”而成的数据类型， 那么在说明和使用之前必须先定义它，也就是构造它。如同在说明和调用函数之前要先定义函数一样。结构的定义定义一个结构的一般形式为： struct 结构名 
{ 成员表列 
}; 成员表由若干个成员组成， 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明，其形式为：类型说明符 成员名; 成员名的命名应符合标识符的书写规定。例如： struct stu
{int num;char name[20];char sex;float score;
};  在这个结构定义中，结构名为stu，该结构由4个成员组成。 第一个成员为num，整型变量；第二个成员为name，字符数组；第三个成员为sex，字符变量；第四个成员为score，实型变量。 应注意在括号后的分号是不可少的。结构定义之后，即可进行变量说明。 凡说明为结构stu的变量都由上述4个成员组成。由此可见， 结构是一种复杂的数据类型，是数目固定，类型不同的若干有序变量的集合。结构类型变量的说明说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。1. 先定义结构，再说明结构变量。如： struct stu
{int num;char name[20];char sex;float score;
};
struct stu boy1,boy2; 说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型，例如： #define STU struct stu
STU
{int num;char name[20];char sex;float score;
};
STU boy1,boy2; 2. 在定义结构类型的同时说明结构变量。例如： struct stu
{int num;char name[20];char sex;float score;
}boy1,boy2; 3. 直接说明结构变量。例如： struct
{int num;char name[20];char sex;float score;
}boy1,boy2;  第三种方法与第二种方法的区别在于第三种方法中省去了结构名，而直接给出结构变量。说明了boy1,boy2变量为stu类型后，即可向这两个变量中的各个成员赋值。在上述stu结构定义中，所有的成员都是基本数据类型或数组类型。成员也可以又是一个结构， 即构成了嵌套的结构。例如： struct date{int month;int day;int year;
}
struct{int num;char name[20];char sex;struct date birthday;float score;
}boy1,boy2; 首先定义一个结构date，由month(月)、day(日)、year(年) 三个成员组成。 在定义并说明变量 boy1 和 boy2 时， 其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名，互不干扰。结构变量成员的表示方法在程序中使用结构变量时， 往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构变量相互赋值以外， 一般对结构变量的使用，包括赋值、输入、输出、 运算等都是通过结构变量的成员来实现的。表示结构变量成员的一般形式是： 结构变量名.成员名 例如：boy1.num 即第一个人的学号　boy2.sex 即第二个人的性别 如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如：boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用，与普通变量完全相同。结构变量的赋值前面已经介绍，结构变量的赋值就是给各成员赋值。 可用输入语句或赋值语句来完成。[例7.1]给结构变量赋值并输出其值。main(){struct stu{int num;char *name;char sex;float score;} boy1,boy2;boy1.num=102;boy1.name=&quot;Zhang ping&quot;;printf(&quot;input sex and score\n&quot;);scanf(&quot;%c %f&quot;,&amp;boy1.sex,&amp;boy1.score);boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
struct stu
{int num;char *name;char sex;float score;
}boy1,boy2;
boy1.num=102;
boy1.name=&quot;Zhang ping&quot;;
printf(&quot;input sex and score\n&quot;);
scanf(&quot;%c %f&quot;,&amp;boy1.sex,&amp;boy1.score);
boy2=boy1;
printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);
printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score); 本程序中用赋值语句给num和name两个成员赋值，name是一个字符串指针变量。用scanf函数动态地输入sex和score成员值，然后把boy1的所有成员的值整体赋予boy2。最后分别输出boy2 的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。结构变量的初始化如果结构变量是全局变量或为静态变量， 则可对它作初始化赋值。对局部或自动结构变量不能作初始化赋值。[例7.2]外部结构变量初始化。struct stu /*定义结构*/
{int num;char *name;char sex;float score;
} boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};
main()
{boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
struct stu
{int num;char *name;char sex;float score;
}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};
main()
{ boy2=boy1;……
} 本例中，boy2,boy1均被定义为外部结构变量，并对boy1作了初始化赋值。在main函数中，把boy1的值整体赋予boy2， 然后用两个printf语句输出boy2各成员的值。[例7.3]静态结构变量初始化。main()
{static struct stu /*定义静态结构变量*/{int num;char *name;char sex;float score;}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
static struct stu
{int num;char *name;char sex;float score;
}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};  本例是把boy1，boy2都定义为静态局部的结构变量， 同样可以作初始化赋值。</subject><description><![CDATA[在实际问题中，一组数据往往具有不同的数据类型。例如， 在学生登记表中，姓名应为字符型；学号可为整型或字符型； 年龄应为整型；性别应为字符型；成绩可为整型或实型。 显然不能用一个数组来存放这一组数据。 因为数组中各元素的类型和长度都必须一致，以便于编译系统处理。为了解决这个问题，C语言中给出了另一种构造数据类型——“结构”。 它相当于其它高级语言中的记录。“结构”是一种构造类型，它是由若干“成员”组成的。 每一个成员可以是一个基本数据类型或者又是一个构造类型。 结构既是一种“构造”而成的数据类型， 那么在说明和使用之前必须先定义它，也就是构造它。如同在说明和调用函数之前要先定义函数一样。结构的定义定义一个结构的一般形式为： struct 结构名 
{ 成员表列 
}; 成员表由若干个成员组成， 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明，其形式为：类型说明符 成员名; 成员名的命名应符合标识符的书写规定。例如： struct stu
{int num;char name[20];char sex;float score;
};  在这个结构定义中，结构名为stu，该结构由4个成员组成。 第一个成员为num，整型变量；第二个成员为name，字符数组；第三个成员为sex，字符变量；第四个成员为score，实型变量。 应注意在括号后的分号是不可少的。结构定义之后，即可进行变量说明。 凡说明为结构stu的变量都由上述4个成员组成。由此可见， 结构是一种复杂的数据类型，是数目固定，类型不同的若干有序变量的集合。结构类型变量的说明说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。1. 先定义结构，再说明结构变量。如： struct stu
{int num;char name[20];char sex;float score;
};
struct stu boy1,boy2; 说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型，例如： #define STU struct stu
STU
{int num;char name[20];char sex;float score;
};
STU boy1,boy2; 2. 在定义结构类型的同时说明结构变量。例如： struct stu
{int num;char name[20];char sex;float score;
}boy1,boy2; 3. 直接说明结构变量。例如： struct
{int num;char name[20];char sex;float score;
}boy1,boy2;  第三种方法与第二种方法的区别在于第三种方法中省去了结构名，而直接给出结构变量。说明了boy1,boy2变量为stu类型后，即可向这两个变量中的各个成员赋值。在上述stu结构定义中，所有的成员都是基本数据类型或数组类型。成员也可以又是一个结构， 即构成了嵌套的结构。例如： struct date{int month;int day;int year;
}
struct{int num;char name[20];char sex;struct date birthday;float score;
}boy1,boy2; 首先定义一个结构date，由month(月)、day(日)、year(年) 三个成员组成。 在定义并说明变量 boy1 和 boy2 时， 其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名，互不干扰。结构变量成员的表示方法在程序中使用结构变量时， 往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构变量相互赋值以外， 一般对结构变量的使用，包括赋值、输入、输出、 运算等都是通过结构变量的成员来实现的。表示结构变量成员的一般形式是： 结构变量名.成员名 例如：boy1.num 即第一个人的学号　boy2.sex 即第二个人的性别 如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如：boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用，与普通变量完全相同。结构变量的赋值前面已经介绍，结构变量的赋值就是给各成员赋值。 可用输入语句或赋值语句来完成。[例7.1]给结构变量赋值并输出其值。main(){struct stu{int num;char *name;char sex;float score;} boy1,boy2;boy1.num=102;boy1.name=&quot;Zhang ping&quot;;printf(&quot;input sex and score\n&quot;);scanf(&quot;%c %f&quot;,&amp;boy1.sex,&amp;boy1.score);boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
struct stu
{int num;char *name;char sex;float score;
}boy1,boy2;
boy1.num=102;
boy1.name=&quot;Zhang ping&quot;;
printf(&quot;input sex and score\n&quot;);
scanf(&quot;%c %f&quot;,&amp;boy1.sex,&amp;boy1.score);
boy2=boy1;
printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);
printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score); 本程序中用赋值语句给num和name两个成员赋值，name是一个字符串指针变量。用scanf函数动态地输入sex和score成员值，然后把boy1的所有成员的值整体赋予boy2。最后分别输出boy2 的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。结构变量的初始化如果结构变量是全局变量或为静态变量， 则可对它作初始化赋值。对局部或自动结构变量不能作初始化赋值。[例7.2]外部结构变量初始化。struct stu /*定义结构*/
{int num;char *name;char sex;float score;
} boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};
main()
{boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
struct stu
{int num;char *name;char sex;float score;
}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};
main()
{ boy2=boy1;……
} 本例中，boy2,boy1均被定义为外部结构变量，并对boy1作了初始化赋值。在main函数中，把boy1的值整体赋予boy2， 然后用两个printf语句输出boy2各成员的值。[例7.3]静态结构变量初始化。main()
{static struct stu /*定义静态结构变量*/{int num;char *name;char sex;float score;}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};boy2=boy1;printf(&quot;Number=%d\nName=%s\n&quot;,boy2.num,boy2.name);printf(&quot;Sex=%c\nScore=%f\n&quot;,boy2.sex,boy2.score);
}
static struct stu
{int num;char *name;char sex;float score;
}boy2,boy1={102,&quot;Zhang ping&quot;,'M',78.5};  本例是把boy1，boy2都定义为静态局部的结构变量， 同样可以作初始化赋值。]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第十一讲 指针的慨念(2)</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16900</link><subject>(2)加减算术运算对于指向数组的指针变量，可以加上或减去一个整数n。设pa是指向数组a的指针变量，则pa+n,pa-n,pa++,++pa,pa--,--pa 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意，数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型， 各种类型的数组元素所占的字节长度是不同的。如指针变量加1，即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如：int a[5],*pa;
pa=a; /*pa指向数组a，也是指向a[0]*/
pa=pa+2; /*pa指向a[2]，即pa的值为&amp;pa[2]*/ 指针变量的加减运算只能对数组指针变量进行， 对指向其它类型变量的指针变量作加减运算是毫无意义的。(3)两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算， 否则运算毫无意义。 ①两指针变量相减两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2 是指向同一浮点数组的两个指针变量，设pf1的值为2010H，pf2的值为2000H，而浮点数组每个元素占4个字节，所以pf1-pf2的结果为(2000H-2010H)/4=4，表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如， pf1+pf2是什么意思呢?毫无实际意义。②两指针变量进行关系运算指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如：pf1==pf2表示pf1和pf2指向同一数组元素
pf1&gt;pf2表示pf1处于高地址位置
pf1&lt;pf2表示pf2处于低地址位置
main(){int a=10,b=20,s,t,*pa,*pb; pa=&amp;a;pb=&amp;b;s=*pa+*pb;t=*pa**pb;printf(&quot;a=%d\nb=%d\na+b=%d\na*b=%d\n&quot;,a,b,a+b,a*b);printf(&quot;s=%d\nt=%d\n&quot;,s,t);
}
...... 说明pa,pb为整型指针变量给指针变量pa赋值，pa指向变量a。给指针变量pb赋值，pb指向变量b。本行的意义是求a+b之和，(*pa就是a，*pb就是b)。本行是求a*b之积。输出结果。输出结果。...... 指针变量还可以与0比较。设p为指针变量，则p==0表明p是空指针，它不指向任何变量；p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如： #define NULL 0　int *p=NULL; 对指针变量赋0值和不赋值是不同的。指针变量未赋值时，可以是任意值，是不能使用的。否则将造成意外错误。而指针变量赋0值后，则可以使用，只是它不指向具体的变量而已。main(){int a,b,c,*pmax,*pmin;printf(&quot;input three numbers:\n&quot;);scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c);if(a&gt;b){pmax=&amp;a;pmin=&amp;b;}else{pmax=&amp;b;pmin=&amp;a;}if(c&gt;*pmax) pmax=&amp;c;if(c&lt;*pmin) pmin=&amp;c;printf(&quot;max=%d\nmin=%d\n&quot;,*pmax,*pmin);
}
......  pmax,pmin为整型指针变量。输入提示。输入三个数字。如果第一个数字大于第二个数字．．．指针变量赋值指针变量赋值指针变量赋值指针变量赋值判断并赋值判断并赋值 输出结果......数组指针变量的说明和使用指向数组的指针变量称为数组指针变量。 在讨论数组指针变量的说明和使用之前，我们先明确几个关系。一个数组是由连续的一块内存单元组成的。 数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量) 组成的。每个数组元素按其类型不同占有几个连续的内存单元。 一个数组元素的首地址也是指它所占有的几个内存单元的首地址。 一个指针变量既可以指向一个数组，也可以指向一个数组元素， 可把数组名或第一个元素的地址赋予它。如要使指针变量指向第i号元素可以把i元素的首地址赋予它或把数组名加i赋予它。设有实数组a，指向a的指针变量为pa，从图6.3中我们可以看出有以下关系：pa,a,&amp;a[0]均指向同一单元，它们是数组a的首地址，也是0 号元素a[0]的首地址。pa+1,a+1,&amp;a[1]均指向1号元素a[1]。类推可知a+i,a+i,&amp;a[i]指向i号元素a[i]。应该说明的是pa是变量，而a,&amp;a[i]都是常量。在编程时应予以注意。main(){int a[5],i;for(i=0;i&lt;5;i++){a[i]=i;printf(&quot;a[%d]=%d\n&quot;,i,a[i]);}printf(&quot;\n&quot;);
} 主函数定义一个整型数组和一个整型变量循环语句 给数组赋值打印每一个数组的值 ......输出换行......数组指针变量说明的一般形式为：类型说明符 * 指针变量名 其中类型说明符表示所指数组的类型。 从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。
引入指针变量后，就可以用两种方法来访问数组元素了。第一种方法为下标法，即用a[i]形式访问数组元素。 在第四章中介绍数组时都是采用这种方法。第二种方法为指针法，即采用*(pa+i)形式，用间接访问的方法来访问数组元素。main(){int a[5],i,*pa;pa=a;for(i=0;i&lt;5;i++){*pa=i;pa++;}pa=a;for(i=0;i&lt;5;i++){printf(&quot;a[%d]=%d\n&quot;,i,*pa);pa++;}
} 主函数定义整型数组和指针将指针pa指向数组a循环将变量i的值赋给由指针pa指向的a[]的数组单元将指针pa指向a[]的下一个单元......指针pa重新取得数组a的首地址循环用数组方式输出数组a中的所有元素将指针pa指向a[]的下一个单元............ 下面，另举一例，该例与上例本意相同，但是实现方式不同。main(){int a[5],i,*pa=a;for(i=0;i&lt;5;){*pa=i;printf(&quot;a[%d]=%d\n&quot;,i++,*pa++);}
} 主函数定义整型数组和指针，并使指针指向数组a循环将变量i的值赋给由指针pa指向的a[]的数组单元用指针输出数组a中的所有元素，同时指针pa指向a[]的下一个单元............数组名和数组指针变量作函数参数在第五章中曾经介绍过用数组名作函数的实参和形参的问题。在学习指针变量之后就更容易理解这个问题了。 数组名就是数组的首地址，实参向形参传送数组名实际上就是传送数组的地址， 形参得到该地址后也指向同一数组。 这就好象同一件物品有两个彼此不同的名称一样。同样，指针变量的值也是地址， 数组指针变量的值即为数组的首地址，当然也可作为函数的参数使用。float aver(float *pa);
main(){float sco[5],av,*sp;int i;sp=sco;printf(&quot;\ninput 5 scores:\n&quot;);for(i=0;i&lt;5;i++) scanf(&quot;%f&quot;,&amp;sco[i]);av=aver(sp);printf(&quot;average score is %5.2f&quot;,av);
}
float aver(float *pa)
{int i;float av,s=0;for(i=0;i&lt;5;i++) s=s+*pa++;av=s/5;return av;
}</subject><description><![CDATA[(2)加减算术运算对于指向数组的指针变量，可以加上或减去一个整数n。设pa是指向数组a的指针变量，则pa+n,pa-n,pa++,++pa,pa--,--pa 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意，数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型， 各种类型的数组元素所占的字节长度是不同的。如指针变量加1，即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如：int a[5],*pa;
pa=a; /*pa指向数组a，也是指向a[0]*/
pa=pa+2; /*pa指向a[2]，即pa的值为&amp;pa[2]*/ 指针变量的加减运算只能对数组指针变量进行， 对指向其它类型变量的指针变量作加减运算是毫无意义的。(3)两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算， 否则运算毫无意义。 ①两指针变量相减两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2 是指向同一浮点数组的两个指针变量，设pf1的值为2010H，pf2的值为2000H，而浮点数组每个元素占4个字节，所以pf1-pf2的结果为(2000H-2010H)/4=4，表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如， pf1+pf2是什么意思呢?毫无实际意义。②两指针变量进行关系运算指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如：pf1==pf2表示pf1和pf2指向同一数组元素
pf1&gt;pf2表示pf1处于高地址位置
pf1&lt;pf2表示pf2处于低地址位置
main(){int a=10,b=20,s,t,*pa,*pb; pa=&amp;a;pb=&amp;b;s=*pa+*pb;t=*pa**pb;printf(&quot;a=%d\nb=%d\na+b=%d\na*b=%d\n&quot;,a,b,a+b,a*b);printf(&quot;s=%d\nt=%d\n&quot;,s,t);
}
...... 说明pa,pb为整型指针变量给指针变量pa赋值，pa指向变量a。给指针变量pb赋值，pb指向变量b。本行的意义是求a+b之和，(*pa就是a，*pb就是b)。本行是求a*b之积。输出结果。输出结果。...... 指针变量还可以与0比较。设p为指针变量，则p==0表明p是空指针，它不指向任何变量；p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如： #define NULL 0　int *p=NULL; 对指针变量赋0值和不赋值是不同的。指针变量未赋值时，可以是任意值，是不能使用的。否则将造成意外错误。而指针变量赋0值后，则可以使用，只是它不指向具体的变量而已。main(){int a,b,c,*pmax,*pmin;printf(&quot;input three numbers:\n&quot;);scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c);if(a&gt;b){pmax=&amp;a;pmin=&amp;b;}else{pmax=&amp;b;pmin=&amp;a;}if(c&gt;*pmax) pmax=&amp;c;if(c&lt;*pmin) pmin=&amp;c;printf(&quot;max=%d\nmin=%d\n&quot;,*pmax,*pmin);
}
......  pmax,pmin为整型指针变量。输入提示。输入三个数字。如果第一个数字大于第二个数字．．．指针变量赋值指针变量赋值指针变量赋值指针变量赋值判断并赋值判断并赋值 输出结果......数组指针变量的说明和使用指向数组的指针变量称为数组指针变量。 在讨论数组指针变量的说明和使用之前，我们先明确几个关系。一个数组是由连续的一块内存单元组成的。 数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量) 组成的。每个数组元素按其类型不同占有几个连续的内存单元。 一个数组元素的首地址也是指它所占有的几个内存单元的首地址。 一个指针变量既可以指向一个数组，也可以指向一个数组元素， 可把数组名或第一个元素的地址赋予它。如要使指针变量指向第i号元素可以把i元素的首地址赋予它或把数组名加i赋予它。设有实数组a，指向a的指针变量为pa，从图6.3中我们可以看出有以下关系：pa,a,&amp;a[0]均指向同一单元，它们是数组a的首地址，也是0 号元素a[0]的首地址。pa+1,a+1,&amp;a[1]均指向1号元素a[1]。类推可知a+i,a+i,&amp;a[i]指向i号元素a[i]。应该说明的是pa是变量，而a,&amp;a[i]都是常量。在编程时应予以注意。main(){int a[5],i;for(i=0;i&lt;5;i++){a[i]=i;printf(&quot;a[%d]=%d\n&quot;,i,a[i]);}printf(&quot;\n&quot;);
} 主函数定义一个整型数组和一个整型变量循环语句 给数组赋值打印每一个数组的值 ......输出换行......数组指针变量说明的一般形式为：类型说明符 * 指针变量名 其中类型说明符表示所指数组的类型。 从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。
引入指针变量后，就可以用两种方法来访问数组元素了。第一种方法为下标法，即用a[i]形式访问数组元素。 在第四章中介绍数组时都是采用这种方法。第二种方法为指针法，即采用*(pa+i)形式，用间接访问的方法来访问数组元素。main(){int a[5],i,*pa;pa=a;for(i=0;i&lt;5;i++){*pa=i;pa++;}pa=a;for(i=0;i&lt;5;i++){printf(&quot;a[%d]=%d\n&quot;,i,*pa);pa++;}
} 主函数定义整型数组和指针将指针pa指向数组a循环将变量i的值赋给由指针pa指向的a[]的数组单元将指针pa指向a[]的下一个单元......指针pa重新取得数组a的首地址循环用数组方式输出数组a中的所有元素将指针pa指向a[]的下一个单元............ 下面，另举一例，该例与上例本意相同，但是实现方式不同。main(){int a[5],i,*pa=a;for(i=0;i&lt;5;){*pa=i;printf(&quot;a[%d]=%d\n&quot;,i++,*pa++);}
} 主函数定义整型数组和指针，并使指针指向数组a循环将变量i的值赋给由指针pa指向的a[]的数组单元用指针输出数组a中的所有元素，同时指针pa指向a[]的下一个单元............数组名和数组指针变量作函数参数在第五章中曾经介绍过用数组名作函数的实参和形参的问题。在学习指针变量之后就更容易理解这个问题了。 数组名就是数组的首地址，实参向形参传送数组名实际上就是传送数组的地址， 形参得到该地址后也指向同一数组。 这就好象同一件物品有两个彼此不同的名称一样。同样，指针变量的值也是地址， 数组指针变量的值即为数组的首地址，当然也可作为函数的参数使用。float aver(float *pa);
main(){float sco[5],av,*sp;int i;sp=sco;printf(&quot;\ninput 5 scores:\n&quot;);for(i=0;i&lt;5;i++) scanf(&quot;%f&quot;,&amp;sco[i]);av=aver(sp);printf(&quot;average score is %5.2f&quot;,av);
}
float aver(float *pa)
{int i;float av,s=0;for(i=0;i&lt;5;i++) s=s+*pa++;av=s/5;return av;
}]]></description><PubDate>2008-12-16 16:19:04</PubDate><category>C/C++编程</category></item><item><title>C语言初学者入门讲座 第四讲 运算符和表达式</title><link>http://www.bcbbs.net/News/Content.aspx?ID=16915</link><subject>运算符的种类、优先级和结合性C语言中运算符和表达式数量之多， 在高级语言中是少见的。正是丰富的运算符和表达式使C语言功能十分完善。 这也是C语言的主要特点之一。C语言的运算符不仅具有不同的优先级， 而且还有一个特点，就是它的结合性。在表达式中， 各运算量参与运算的先后顺序不仅要遵守运算符优先级别的规定，还要受运算符结合性的制约， 以便确定是自左向右进行运算还是自右向左进行运算。 这种结合性是其它高级语言的运算符所没有的，因此也增加了C语言的复杂性。运算符的种类C语言的运算符可分为以下几类：1.算术运算符用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算，%)、自增(++)、自减(--)共七种。2.关系运算符用于比较运算。包括大于(&gt;)、小于(&lt;)、等于(==)、 大于等于(&gt;=)、小于等于(&lt;=)和不等于(!=)六种。3.逻辑运算符用于逻辑运算。包括与(&amp;&amp;)、或(||)、非(!)三种。4.位操作运算符参与运算的量，按二进制位进行运算。包括位与(&amp;)、位或(|)、位非(~)、位异或(^)、左移(&lt;&lt;)、右移(&gt;&gt;)六种。5.赋值运算符用于赋值运算，分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&amp;=,|=,^=,&gt;&gt;=,&lt;&lt;=)三类共十一种。6.条件运算符这是一个三目运算符，用于条件求值(?:)。7.逗号运算符用于把若干表达式组合成一个表达式(，)。8.指针运算符用于取内容(*)和取地址(&amp;)二种运算。9.求字节数运算符用于计算数据类型所占的字节数(sizeof)。10.特殊运算符有括号()，下标[]，成员(→，.)等几种。优先级和结合性C语言中，运算符的运算优先级共分为15级。1级最高，15级最低。在表达式中，优先级较高的先于优先级较低的进行运算。 而在一个运算量两侧的运算符优先级相同时， 则按运算符的结合性所规定的结合方向处理。 C语言中各运算符的结合性分为两种，即左结合性(自左至右)和右结合性(自右至左)。例如算术运算符的结合性是自左至右，即先左后右。如有表达式x-y+z则y应先与“-”号结合， 执行x-y运算，然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。 最典型的右结合性运算符是赋值运算符。如x=y=z,由于“=”的右结合性，应先执行y=z再执行x=(y=z)运算。 C语言运算符中有不少为右结合性，应注意区别，以避免理解错误。算术运算符和算术表达式基本的算术运算符1.加法运算符“+”加法运算符为双目运算符，即应有两个量参与加法运算。如a+b,4+8等。具有右结合性。2.减法运算符“-”减法运算符为双目运算符。但“-”也可作负值运算符，此时为单目运算，如-x,-5等具有左结合性。3.乘法运算符“*”双目运算，具有左结合性。4.除法运算符“/”双目运算具有左结合性。参与运算量均为整型时， 结果也为整型，舍去小数。如果运算量中有一个是实型，则结果为双精度实型。void main(){
printf(&quot;\n\n%d,%d\n&quot;,20/7,-20/7);
printf(&quot;%f,%f\n&quot;,20.0/7,-20.0/7);
}双目运算具有左结合性。参与运算量均为整型时， 结果也为整型，舍去小数。如果运算量中有一个是实型，则结果为双精度实型。 printf(&quot;\n\n%d,%d\n&quot;,20/7,-20/7);printf(&quot;%f,%f\n&quot;,20.0/7,-20.0/7);本例中，20/7，-20/7的结果均为整型，小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算，因此结果也为实型。5.求余运算符(模运算符)“%”双目运算，具有左结合性。要求参与运算的量均为整型。 求余运算的结果等于两数相除后的余数。 void main(){
printf(&quot;%d\n&quot;,100%3);
}双目运算，具有左结合性。求余运算符% 要求参与运算的量均为整型。本例输出100除以3所得的余数1。自增1，自减1运算符自增1运算符记为“++”，其功能是使变量的值自增1。自减1运算符记为“--”，其功能是使变量值自减1。自增1，自减1运算符均为单目运算，都具有右结合性。可有以下几种形式： ++i i自增1后再参与其它运算。--i i自减1后再参与其它运算。i++ 　i参与运算后，i的值再自增1。i-- 　i参与运算后，i的值再自减1。 在理解和使用上容易出错的是i++和i--。 特别是当它们出在较复杂的表达式或语句中时，常常难于弄清，因此应仔细分析。void main(){
int i=8;
printf(&quot;%d\n&quot;,++i);
printf(&quot;%d\n&quot;,--i);
printf(&quot;%d\n&quot;,i++);
printf(&quot;%d\n&quot;,i--);
printf(&quot;%d\n&quot;,-i++);
printf(&quot;%d\n&quot;,-i--);
} i&lt;--8
i&lt;--i+1
i&lt;--i-1
i&lt;--i+1
i&lt;--i-1
i&lt;--i+1
i&lt;--i-1 int i=8;
printf(&quot;%d\n&quot;,++i);
printf(&quot;%d\n&quot;,--i);
printf(&quot;%d\n&quot;,i++);
printf(&quot;%d\n&quot;,i--);
printf(&quot;%d\n&quot;,-i++);
printf(&quot;%d\n&quot;,-i--);  i的初值为8第2行i加1后输出故为9；第3行减1后输出故为8；第4行输出i为8之后再加1(为9)；第5行输出i为9之后再减1(为8) ；第6行输出-8之后再加1(为9)；第7行输出-9之后再减1(为8)void main(){int i=5,j=5,p,q;p=(i++)+(i++)+(i++);q=(++j)+(++j)+(++j);printf(&quot;%d,%d,%d,%d&quot;,p,q,i,j);
}
i&lt;--5,j&lt;--5,p&lt;--0,q&lt;--0
i+i+i---&gt;p,i+1--&gt;i,i+1--&gt;i,i+1--&gt;i
j+1-&gt;j,j+1-&gt;j,j+1-&gt;j,j+j+j-&gt;q int i=5,j=5,p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j); 这个程序中，对P=(i++)+(i++)+(i++)应理解为三个i相加，故P值为15。然后i再自增1三次相当于加3故i的最后值为8。而对于q 的值则不然，q=(++j)+(++j)+(++j)应理解为q先自增1，再参与运算，由于q自增1三次后值为8，三个8相加的和为24，j的最后值仍为8。算术表达式表达式是由常量、变量、函数和运算符组合起来的式子。 一个表达式有一个值及其类型， 它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。 单个的常量、变量、函数可以看作是表达式的特例。算术表达式是由算术运算符和括号连接起来的式子， 以下是算术表达式的例子： a+b 　(a*2)／c　(x+r)*8-(a+b)／7　　++i　sin(x)+sin(y) 　(++i)-(j++)+(k--)赋值运算符和赋值表达式简单赋值运算符和表达式，简单赋值运算符记为“=”。由“= ”连接的式子称为赋值表达式。其一般形式为： 变量=表达式 例如：x=a+b
w=sin(a)+sin(b)
y=i+++--j 赋值表达式的功能是计算表达式的值再赋予左边的变量。赋值运算符具有右结合性。因此
a=b=c=5可理解为a=(b=(c=5))在其它高级语言中，赋值构成了一个语句，称为赋值语句。 而在C中，把“=”定义为运算符，从而组成赋值表达式。 凡是表达式可以出现的地方均可出现赋值表达式。例如，式子x=(a=5)+(b=8)是合法的。它的意义是把5赋予a，8赋予b，再把a,b相加，和赋予x ，故x应等于13。在C语言中也可以组成赋值语句，按照C语言规定， 任何表达式在其未尾加上分号就构成为语句。因此如x=8;a=b=c=5；都是赋值语句，在前面各例中我们已大量使用过了。如果赋值运算符两边的数据类型不相同， 系统将自动进行类型转换，即把赋值号右边的类型换成左边的类型。具体规定如下：1.实型赋予整型，舍去小数部分。前面的例2.9已经说明了这种情况。2.整型赋予实型，数值不变，但将以浮点形式存放， 即增加小数部分(小数部分的值为0)。3.字符型赋予整型，由于字符型为一个字节， 而整型为二个字节，故将字符的ASCII码值放到整型量的低八位中，高八位为0。4.整型赋予字符型，只把低八位赋予字符量。void main(){int a,b=322;float x,y=8.88;char c1='k',c2;a=y;x=b;a=c1;c2=b;printf(&quot;%d,%f,%d,%c&quot;,a,x,a,c2);
}
int a,b=322;
float x,y=8.88;
char c1='k',c2;
printf(&quot;%d,%f,%d,%c&quot;,a=y,x=b,a=c1,c2=b); 本例表明了上述赋值运算中类型转换的规则。a为整型，赋予实型量y值888后只取整数8。x为实型，赋予整型量b值322， 后增加了小数部分。字符型量c1赋予a变为整型，整型量b赋予c2 后取其低八位成为字符型(b的低八位为01000010，即十进制66，按ASCII码对应于字符B)。复合赋值符及表达式在赋值符“=”之前加上其它二目运算符可构成复合赋值符。如 
+=,-=,*=,／=,%=,&lt;&lt;=,&gt;&gt;=,&amp;=,^=,|=。 构成复合赋值表达式的一般形式为： 变量 双目运算符=表达式 它等效于 变量=变量 运算符 表达式 例如： a+=5 等价于a=a+5　　x*=y+7 等价于x=x*(y+7)　　r%=p 等价于r=r%p 复合赋值符这种写法，对初学者可能不习惯， 但十分有利于编译处理，能提高编译效率并产生质量较高的目标代码。逗号运算符和逗号表达式在逗号运算符C语言中逗号“，”也是一种运算符，称为逗号运算符。 其功能是把两个表达式连接起来组成一个表达式， 称为逗号表达式。其一般形式为： 表达式1，表达式2 其求值过程是分别求两个表达式的值，并以表达式2的值作为整个逗号表达式的值。void main(){int a=2,b=4,c=6,x,y;y=(x=a+b),(b+c);printf(&quot;y=%d,x=%d&quot;,y,x);
}
a&lt;--2,b&lt;--4,c&lt;--6,x&lt;--0,y&lt;--0
x&lt;--a+b,y&lt;---b+c  本例中，y等于整个逗号表达式的值，也就是表达式2的值，x是第一个表达式的值。对于逗号表达式还要说明两点：1.逗号表达式一般形式中的表达式1和表达式2 也可以又是逗号表达式。例如： 表达式1，(表达式2，表达式3) 形成了嵌套情形。因此可以把逗号表达式扩展为以下形式： 表达式1，表达式2，…表达式n 整个逗号表达式的值等于表达式n的值。2.程序中使用逗号表达式，通常是要分别求逗号表达式内各表达式的值，并不一定要求整个逗号表达式的值。3.并不是在所有出现逗号的地方都组成逗号表达式，如在变量说明中，函数参数表中逗号只是用作各变量之间的间隔符。</subject><description><![CDATA[运算符的种类、优先级和结合性C语言中运算符和表达式数量之多， 在高级语言中是少见的。正是丰富的运算符和表达式使C语言功能十分完善。 这也是C语言的主要特点之一。C语言的运算符不仅具有不同的优先级， 而且还有一个特点，就是它的结合性。在表达式中， 各运算量参与运算的先后顺序不仅要遵守运算符优先级别的规定，还要受运算符结合性的制约， 以便确定是自左向右进行运算还是自右向左进行运算。 这种结合性是其它高级语言的运算符所没有的，因此也增加了C语言的复杂性。运算符的种类C语言的运算符可分为以下几类：1.算术运算符用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算，%)、自增(++)、自减(--)共七种。2.关系运算符用于比较运算。包括大于(&gt;)、小于(&lt;)、等于(==)、 大于等于(&gt;=)、小于等于(&lt;=)和不等于(!=)六种。3.逻辑运算符用于逻辑运算。包括与(&amp;&amp;)、或(||)、非(!)三种。4.位操作运算符参与运算的量，按二进制位进行运算。包括位与(&amp;)、位或(|)、位非(~)、位异或(^)、左移(&lt;&lt;)、右移(&gt;&gt;)六种。5.赋值运算符用于赋值运算，分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&amp;=,|=,^=,&gt;&gt;=,&lt;&lt;=)三类共十一种。6.条件运算符这是一个三目运算符，用于条件求值(?:)。7.逗号运算符用于把若干表达式组合成一个表达式(，)。8.指针运算符用于取内容(*)和取地址(&amp;)二种运算。9.求字节数运算符用于计算数据类型所占的字节数(sizeof)。10.特殊运算符有括号()，下标[]，成员(→，.)等几种。优先级和结合性C语言中，运算符的运算优先级共分为15级。1级最高，15级最低。在表达式中，优先级较高的先于优先级较低的进行运算。 而在一个运算量两侧的运算符优先级相同时， 则按运算符的结合性所规定的结合方向处理。 C语言中各运算符的结合性分为两种，即左结合性(自左至右)和右结合性(自右至左)。例如算术运算符的结合性是自左至右，即先左后右。如有表达式x-y+z则y应先与“-”号结合， 执行x-y运算，然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。 最典型的右结合性运算符是赋值运算符。如x=y=z,由于“=”的右结合性，应先执行y=z再执行x=(y=z)运算。 C语言运算符中有不少为右结合性，应注意区别，以避免理解错误。算术运算符和算术表达式基本的算术运算符1.加法运算符“+”加法运算符为双目运算符，即应有两个量参与加法运算。如a+b,4+8等。具有右结合性。2.减法运算符“-”减法运算符为双目运算符。但“-”也可作负值运算符，此时为单目运算，如-x,-5等具有左结合性。3.乘法运算符“*”双目运算，具有左结合性。4.除法运算符“/”双目运算具有左结合性。参与运算量均为整型时， 结果也为整型，舍去小数。如果运算量中有一个是实型，则结果为双精度实型。void main(){
printf(&quot;\n\n%d,%d\n&quot;,20/7,-20/7);
printf(&quot;%f,%f\n&quot;,20.0/7,-20.0/7);
}双目运算具有左结合性。参与运算量均为整型时， 结果也为整型，舍去小数。如果运算量中有一个是实型，则结果为双精度实型。 printf(&quot;\n\n%d,%d\n&quot;,20/7,-20/7);printf(&quot;%f,%f\n&quot;,20.0/7,-20.0/7);本例中，20/7，-20/7的结果均为整型，小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算，因此结果也为实型。5.求余运算符(模运算符)“%”双目运算，具有左结合性。要求参与运算的量均为整型。 求余运算的结果等于两数相除后的余数。 void main(){
printf(&quot;%d\n&quot;,100%3);
}双目运算，具有左结合性。求余运算符% 要求参与运算的量均为整型。本例输出100除以3所得的余数1。自增1，自减1运算符自增1运算符记为“++”，其功能是使变量的值自增1。自减1运算符记为“--”，其功能是使变量值自减1。自增1，自减1运算符均为单目运算，都具有右结合性。可有以下几种形式： ++i i自增1后再参与其它运算。--i i自减1后再参与其它运算。i++ 　i参与运算后，i的值再自增1。i-- 　i参与运算后，i的值再自减1。 在理解和使用上容易出错的是i++和i--。 特别是当它们出在较复杂的表达式或语句中时，常常难于弄清，因此应仔细分析。void main(){
int i=8;
printf(&quot;%d\n&quot;,++i);
printf(&quot;%d\n&quot;,--i);
printf(&quot;%d\n&quot;,i++);
printf(&quot;%d\n&quot;,i--);
printf(&quot;%d\n&quot;,-i++);
printf(&quot;%d\n&quot;,-i--);
} i&lt;--8
i&lt;--i+1
i&lt;--i-1
i&lt;--i+1
i&lt;--i-1
i&lt;--i+1
i&lt;--i-1 int i=8;
printf(&quot;%d\n&quot;,++i);
printf(&quot;%d\n&quot;,--i);
printf(&quot;%d\n&quot;,i++);
printf(&quot;%d\n&quot;,i--);
printf(&quot;%d\n&quot;,-i++);
printf(&quot;%d\n&quot;,-i--);  i的初值为8第2行i加1后输出故为9；第3行减1后输出故为8；第4行输出i为8之后再加1(为9)；第5行输出i为9之后再减1(为8) ；第6行输出-8之后再加1(为9)；第7行输出-9之后再减1(为8)void main(){int i=5,j=5,p,q;p=(i++)+(i++)+(i++);q=(++j)+(++j)+(++j);printf(&quot;%d,%d,%d,%d&quot;,p,q,i,j);
}
i&lt;--5,j&lt;--5,p&lt;--0,q&lt;--0
i+i+i---&gt;p,i+1--&gt;i,i+1--&gt;i,i+1--&gt;i
j+1-&gt;j,j+1-&gt;j,j+1-&gt;j,j+j+j-&gt;q int i=5,j=5,p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j); 这个程序中，对P=(i++)+(i++)+(i++)应理解为三个i相加，故P值为15。然后i再自增1三次相当于加3故i的最后值为8。而对于q 的值则不然，q=(++j)+(++j)+(++j)应理解为q先自增1，再参与运算，由于q自增1三次后值为8，三个8相加的和为24，j的最后值仍为8。算术表达式表达式是由常量、变量、函数和运算符组合起来的式子。 一个表达式有一个值及其类型， 它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。 单个的常量、变量、函数可以看作是表达式的特例。算术表达式是由算术运算符和括号连接起来的式子， 以下是算术表达式的例子： a+b 　(a*2)／c　(x+r)*8-(a+b)／7　　++i　sin(x)+sin(y) 　(++i)-(j++)+(k--)赋值运算符和赋值表达式简单赋值运算符和表达式，简单赋值运算符记为“=”。由“= ”连接的式子称为赋值表达式。其一般形式为： 变量=表达式 例如：x=a+b
w=sin(a)+sin(b)
y=i+++--j 赋值表达式的功能是计算表达式的值再赋予左边的变量。赋值运算符具有右结合性。因此
a=b=c=5可理解为a=(b=(c=5))在其它高级语言中，赋值构成了一个语句，称为赋值语句。 而在C中，把“=”定义为运算符，从而组成赋值表达式。 凡是表达式可以出现的地方均可出现赋值表达式。例如，式子x=(a=5)+(b=8)是合法的。它的意义是把5赋予a，8赋予b，再把a,b相加，和赋予x ，故x应等于13。在C语言中也可以组成赋值语句，按照C语言规定， 任何表达式在其未尾加上分号就构成为语句。因此如x=8;a=b=c=5；都是赋值语句，在前面各例中我们已大量使用过了。如果赋值运算符两边的数据类型不相同， 系统将自动进行类型转换，即把赋值号右边的类型换成左边的类型。具体规定如下：1.实型赋予整型，舍去小数部分。前面的例2.9已经说明了这种情况。2.整型赋予实型，数值不变，但将以浮点形式存放， 即增加小数部分(小数部分的值为0)。3.字符型赋予整型，由于字符型为一个字节， 而整型为二个字节，故将字符的ASCII码值放到整型量的低八位中，高八位为0。4.整型赋予字符型，只把低八位赋予字符量。void m