积木

No sub title

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  140 Posts :: 1 Stories :: 11 Comments :: 0 Trackbacks

常用链接

留言簿(1)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

原文转载自:http://blog.csdn.net/leishiwei/article/details/4009307

看完下面这篇文章,即可明白为什么在使用lua时,需要对所包含的头文件添加:extern "C"这样的符号来括起来。
extern "C"
{
    #include "lua.h"
    #include "lualib.h"
    #include "luaxlib.h"
}

Lua编译要考虑extern “C”

分类: 游戏脚本 219人阅读 评论(1) 收藏 举报

 (转 七星重剑)
C++项目要到了如Lua这种纯C写成的库时,如果链接不过,就考虑是包含头文件时没加extern “C”。
#ifdef __cplusplus
extern "C" {
#endif
    #include "lualib.h"
    #include "lauxlib.h"
#ifdef __cplusplus
}
#endif
  
时常在cpp的代码之中看到这样的代码:
#ifdef __cplusplus
extern "C" {
#endif
//一段代码
#ifdef __cplusplus
}
#endif
  这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。
  要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生的函数名字的处理是不一样的.
  比如下面的一段简单的函数,我们看看加入和不加入extern "C"产生的汇编代码都有哪些变化:
int f(void)
{
return 1;
}
  在加入extern "C"的时候产生的汇编代码是:
.file "test.cxx"
.text
.align 2
.globl _f
.def _f; .scl 2; .type 32; .endef
_f:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
  但是不加入了extern "C"之后
.file "test.cxx"
.text
.align 2
.globl __Z1fv
.def __Z1fv; .scl 2; .type 32; .endef
__Z1fv:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
  两段汇编代码同样都是使用gcc -S命令产生的,所有的地方都是一样的,唯独是产生的函数名,一个是_f,一个是__Z1fv。
  明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
  试想这样的情况:一个库文件已经用C写好了而且运行得很良好,这个时候我们需要使用这个库文件,但是我们需要使用C++来写这个新的代码。如果这个代码使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.我们来看一段代码:首先,我们使用C的处理方式来写一个函数,也就是说假设这个函数当时是用C写成的:
//f1.c
extern "C"
{
void f1()
{
return;
}
}
  编译命令是:gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文件。再写一段代码调用这个f1函数:
// test.cxx
//这个extern表示f1函数在别的地方定义,这样可以通过
//编译,但是链接的时候还是需要
//链接上原来的库文件.
extern void f1();
int main()
{
f1();
return 0;
}
  通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文件,可是出错了,错误的提示是:
test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'
  也就是说,在编译test.cxx的时候编译器是使用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,所以就会出现链接过不去的错误:因为链接器找不到函数。
  因此,为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。
  比如,现在我们有了一个C库文件,它的头文件是f.h,产生的lib文件是f.lib,那么我们如果要在C++中使用这个库文件,我们需要这样写:
extern "C"
{
#include "f.h"
}
  回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx:
extern "C"
{
extern void f1();
}
int main()
{
f1();
return 0;
}
  重新编译并且链接就可以过去了.
  总结
  C和C++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

posted @ 2012-12-03 10:28 Jacc.Kim 阅读(528) | 评论 (0)编辑 收藏

1) 正如书中所述,stl所述的heap都是max-heap。即:父节点的“值”[注释1],永远是 >= 其子节点的值。
2) 正如书中所述,stl所述的heap不归属于容器。因为它是一组算法。这些算法的实现原理,在此[注释2],是以一棵完全二叉树来设计的。
3) 以下对各个max-heap接口算法的小结:

a) make_heap()
说明:顾名思义,该接口就是用来“创建”一个heap的。是对原有的一堆任意存放的数据,按照第一点所述的规则,进行“排列”(注意:不是排序)。
示例(来自书中例子,抄出来,经常看,印象会更深刻。在此,我们重在理解算法与掌握运用):
int ai[9] = {0, 1, 2, 3, 4, 8, 9, 3, 5};
vector<int> ivec(ia, ia + 9);
make_heap(ivec.begin(), ivec.end());//调用后,ivec中的数据的排列将改变掉,并且已经是按max-heap的结构存储的。
for (int i = 0; i < ivec.size(); i++)
    cout << ivec[i] << ' ';  // 9 5 8 3 4 0 2 3 1
cout << endl;

b) push_heap()
说明:将新push_back()到ivec的末尾元素按照max-heap的要求规则,进行位置调整。使得新的整个ivec中的元素排列规则符合max-heap的规则要求。
注意:
    1) push_heap()的操作,一定都是针对最末尾的那个元素,对它的位置按照max-heap的要求,进行重新调整的。
    2) 执行push_heap()操作前,一定一定要保证除了最末尾的那个元素外,前面的元素的排列规则一定都满足max-heap()的规则存放的。
示例:
ivec.push_back(7);
push_heap(ivec.begin(), ivec.end());
for (int i = 0; i < ivec.size(); i++)
    cout << ivec[i] << ' '; // 9 7 8 3 5 0 2 3 1 4
cout << endl;

c) pop_heap()
说明:该接口意即:要从整个heap中,取出元素。但这里取出的一定是“值”最大的那个元素。而不是像vector或list等那样,可以取出任意位置的元素。
注意:
    1) 调用该接口“取出”元素后,其实该元素(即:“值”最大的那个元素)并未真正被取出来,而是将该元素放到了ivec的最末尾位置。(也正是因此,如果对整个ivec进行多次的pop_heap()操作,即可完成ivec的排序功能)
    2) 正如 注意1) 所述的,则在pop_heap()后,ivec除了最末尾的那个元素外,前面的元素仍然是保持着max-heap的规则存储的。
示例:
pop_heap(ivec.begin(), ivec.end());
cout << ivec.back() << endl; // 9. return but not remove.
ivec.pop_back(); // remove last elem and no return;

d) sort_heap()
说明:顾名思义,是对一个heap进行排序。
注意:
      1) 排序后的“heap"(即:原始的heap)将不复存在(理由很简单:排序后,原heap中的元素的存储规则不符合max-heap的规则,因此排序后的,就不能再称为heap)
示例:
sort_heap(ivec.begin(), ivec.end());
for (int i = 0; i < ivec.size(); i++)
    cout << ivec[i] << ' '; // 0 1 2 3 3 4 5 7 8
cout << endl;

补充:max-heap的隐式表达式的push_heap()与pop_heap()操作时间都只有:O(logN)。一种算是比较居中的,还算不错的时间性能参考值。

最后再说两点:
   1) 只要深刻理解了以上算法与接口的使用,对实际项目的动作,个人认为,是很有价值的。另外,理解了heap的原理,则我们也十分容易priority queue的实现细节。
   2) 对知识的掌握,还是重在理解。

以上表述有误之处,还望大伙多多指正啊。。:)

[注释1]:此处的值:我们可以当它是节点本身的值,也可以当它是某种权值。依自己使用需要而定。
[注释2]:指的是隐匿表达式实现的heap.即:以完全二叉树方式实现的heap。
posted @ 2012-11-21 12:07 Jacc.Kim 阅读(310) | 评论 (0)编辑 收藏

UPDATE creatureitem I
SET
  I.xxx =
  (select I.yyy from space s
  where s.type = 1
  AND s.id = i.spaceid
  AND (i.roleid = ''
  OR isnull(i.roleid)))
posted @ 2012-11-19 17:06 Jacc.Kim 阅读(104) | 评论 (0)编辑 收藏

     摘要: 原文转载自:http://www.cppblog.com/shenhuafeng/archive/2006/12/30/17041.html 模版偏特化--Partial Template Specialization(《Modern C++ Design读书笔记二》) Partial Template Specialization顾名思义,模版偏特化就是对模版进行特化的意思。举个例子: na...  阅读全文
posted @ 2012-11-15 10:23 Jacc.Kim 阅读(507) | 评论 (0)编辑 收藏

原文转载自:http://www.cnblogs.com/cutepig/archive/2009/01/14/1375917.html

按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象,如下面所示:
class String {
String ( const char* p ); // 用C风格的字符串p作为初始化值
//…
}
String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(“hello”);
 
但是有的时候可能会不需要这种隐式转换,如下:
class String {
       String ( int n ); //本意是预先分配n个字节给字符串
String ( const char* p ); // 用C风格的字符串p作为初始化值
//…
}
 
下面两种写法比较正常:
String s2 ( 10 );   //OK 分配10个字节的空字符串
String s3 = String ( 10 ); //OK 分配10个字节的空字符串
 
下面两种写法就比较疑惑了:
String s4 = 10; //编译通过,也是分配10个字节的空字符串
String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串
 
s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
为了避免这种错误的发生,我们可以声明显示的转换,使用explicit 关键字:
class String {
       explicit String ( int n ); //本意是预先分配n个字节给字符串
String ( const char* p ); // 用C风格的字符串p作为初始化值
//…
}
加上explicit就抑制了String ( int n )的隐式转换,
 
下面两种写法仍然正确:
String s2 ( 10 );   //OK 分配10个字节的空字符串
String s3 = String ( 10 ); //OK 分配10个字节的空字符串
 
下面两种写法就不允许了:
String s4 = 10; //编译不通过,不允许隐式的转换
String s5 = ‘a’; //编译不通过,不允许隐式的转换
 
因此,某些时候,explicit 可以有效得防止构造函数的隐式转换带来的错误或者误解

----------------------------------------------------------
explicit   只对构造函数起作用,用来抑制隐式转换。如:  
  class   A   {  
          A(int   a);  
  };  
  int   Function(A   a);  
   
  当调用   Function(2)   的时候,2   会隐式转换为   A   类型。这种情况常常不是程序员想要的结果,所以,要避免之,就可以这样写:  
   
  class   A   {  
          explicit   A(int   a);  
  };  
  int   Function(A   a);  
   
  这样,当调用   Function(2)   的时候,编译器会给出错误信息(除非   Function   有个以   int   为参数的重载形式),这就避免了在程序员毫不知情的情况下出现错误。

总结:explicit   只对构造函数起作用,用来抑制隐式转换。

参考:
http://blog.csdn.net/smilelance/archive/2007/03/14/1528737.aspx
http://topic.csdn.net/t/20040509/15/3046021.html
posted @ 2012-11-13 16:49 Jacc.Kim 阅读(275) | 评论 (0)编辑 收藏

原文转载自:http://www.eefocus.com/wang312/blog/2012-05/229203_12ffa.html

volatile的作用与用法  2011-12-15 13:34

本文引自百度百科volatile,就当作了笔记,和大家一起分享吧。

 

先说说我的理解,volatile我们叫它“易变的”,是一个类型修修饰符,一般情况下,我们定义了一个变量,如果说这个变量在整个代码中都没有改变,那么编译器就会把这个变量放到寄存器中,cpu取值就只在寄存器中取。但是在嵌入式中就会出现特殊情况了,很多时候,我们在代码中都不会改变一些变量的值(特别是寄存器的状态,或者某个引脚的状态),但是硬件很有可能去改变这个值,如按键改变某引脚的状态等一些情况,可是编译器并不知道这个变量已经发生改变,这个时候很有可能编译器所使用的值和实际值并不一致,所以C语言中采用volatile来修饰这一类变量,它的作用是让编译器每次取值的都会到内存单元中去重新读取该变量的值,而不是直接使用寄存器中的值。

在代码中经常可以看到(*((volatile unsigned short *)(x)))这样的代码,作用也就是这个了。

以下是百科原文:

就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

 

  推荐一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
  1). 并行设备的硬件寄存器(如:状态寄存器)
  2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  3). 多线程应用中被几个任务共享的变量
  回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
  假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。
  1). 一个参数既可以是const还可以是volatile吗?解释为什么。
  2). 一个指针可以是volatile 吗?解释为什么。
  3). 下面的函数有什么错误:
  int square(volatile int *ptr)
  {
  return *ptr * *ptr;
  }
  下面是答案:
  1).是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
  2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
  3).这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
  int square(volatile int *ptr)
  {
  int a,b;
  a = *ptr;
  b = *ptr;
  return a * b;
  }
  由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
  long square(volatile int *ptr)
  {
  int a;
  a = *ptr;
  return a * a;
  }
  讲讲我的理解: (欢迎打板子...~~!)
  关键在于两个地方:
  1. 编译器的优化 (请高手帮我看看下面的理解)
  在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;
  当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
  当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
  当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
  举一个不太准确的例子:
  发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资
  员工 -- 原始变量地址
  银行卡号 -- 原始变量在寄存器的备份
  2. 在什么情况下会出现(如1楼所说)
  1). 并行设备的硬件寄存器(如:状态寄存器)
  2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  3). 多线程应用中被几个任务共享的变量
  补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
  “易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
  而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了; 大家看看前面那种解释(易变的)是不是在误导人
  ------------简明示例如下:------------------
  volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
  使用该关键字的例子如下:
  int volatile nVint;
  >>>>当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
  例如:
  volatile int i=10;
  int a = i;
  ...
  //其他代码,并未明确告诉编译器,对i进行过操作
  int b = i;
  >>>>volatile 指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
  >>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
  >>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
  >>
  #i nclude
  void main()
  {
  int i=10;
  int a = i;
  printf("i= %d",a);
  //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
  __asm {
  mov dword ptr [ebp-4], 20h
  }
  int b = i;
  printf("i= %d",b);
  }
  然后,在调试版本模式运行程序,输出结果如下:
  i = 10
  i = 32
  然后,在release版本模式运行程序,输出结果如下:
  i = 10
  i = 10
  输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把i的声明加上volatile关键字,看看有什么变化:
  #i nclude
  void main()
  {
  volatile int i=10;
  int a = i;
  printf("i= %d",a);
  __asm {
  mov dword ptr [ebp-4], 20h
  }
  int b = i;
  printf("i= %d",b);
  }
  分别在调试版本和release版本运行程序,输出都是:
  i = 10
  i = 32
  这说明这个关键字发挥了它的作用!
  ------------------------------------
  volatile对应的变量可能在你的程序本身不知道的情况下发生改变
  比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
  你自己的程序,是无法判定何时这个变量会发生变化
  还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
  对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
  --------------------------------------------------------------------------------
  典型的例子
  for ( int i=0; i<100000; i++);
  这个语句用来测试空循环的速度的
  但是编译器肯定要把它优化掉,根本就不执行
  如果你写成
  for ( volatile int i=0; i<100000; i++);
  它就会执行了
  volatile的本意是“易变的”
  由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
  static int i=0;
  int main(void)
  {
  ...
  while (1)
  {
  if (i) dosomething();
  }
  }
  /* Interrupt service routine. */
  void ISR_2(void)
  {
  i=1;
  }
  程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
  可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
  调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
  一般说来,volatile用在如下的几个地方:
  1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2、多任务环境下各任务间共享的标志应该加volatile;
  3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
  另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
  现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
posted @ 2012-11-11 22:40 Jacc.Kim 阅读(239) | 评论 (0)编辑 收藏

由于在处理游戏服务端时,必须考虑到使用的处理。在此,使用了boost中的时间相关的内容。
用boost的好处很多,下面是项目中对此的一点应用小结:
1) 支持跨平台。
2) 时间的精确度极高。取得毫秒级的绝对没问题。(似乎微秒级的也可以)
3) 用法方便。

using namespace boost::posix_time;
using namespace boost::gregorian;
//using boost::gregorian::date;

#include <boost/date_time/posix_time/posix_time.hpp>
#define BOOST_DATE_TIME_SOURCE


static ptime const epoch(date(1970, 1, 1));//一个基准点。
std::string currtimestamp = CommonHelper::format("%I64u", (WE::uint64)((microsec_clock::universal_time() - epoch).total_milliseconds()));
//提示:
   1) microsec_clock::universal_time()是取得格林威治时间。
   2) 如果是:microsec_clock::local_time()则取得的是本地时间。
   3) 如果想取得北京时间,则只需要加上8小时即可。microsec_clock::universal_time() + hours(8);
   4) 上面的语句是计算当前格林威治时间的时间戳。

4) boost::posix_time::ptime 因重载了许多操作符运算。因此,对它的使用就如同基本类型一样。如:可以很方便计算两点时间点的差距。

boost::posix_time::ptime pt1(date(xxxx, x, x), time_duration(10, 1, 23));
//上面也可以这样写:boost::posix_time::ptime pt1(date(xxxx, x, x), time_duration(hours(10), minutes(1), seconds(23)));
//再比如:boost::posix_time::ptime pt1(date(xxxx, x, x), hours(10));如果想要详细了解,请自行研究boost源码。

boost::posix_time::ptime pt2(.....);//这边的构造函数参数就不写了。请自行参考上面的语句。

time_duration td = pt1 - pt2;//注意:ptime是不支持 + 的。因为它没有重载该操作。计算它们的加,是没有意义的。而 - 有意义。所以可如此操作。

5) 取得日期,取得时间都很方便。


此次,就暂时就写这么多吧。
posted @ 2012-11-06 20:18 Jacc.Kim 阅读(5787) | 评论 (0)编辑 收藏

原文转处:http://www.cppblog.com/software8/archive/2012/10/09/193049.html

一、面向对象是C++的重要特性.
   但是c++在c的基础上新增加的几点优化也是很耀眼的,就const直接可以取代c中的#define,以下几点很重要,学不好后果也也很严重
1. 限定符声明变量只能被读
[cpp] view plaincopyprint?
const int i=5;  
  int j=0;  
  ...  
  i=j; //非法,导致编译错误  
  j=i; //合法  
2. 必须初始化
[cpp] view plaincopyprint?
const int i=5; //合法  
  const int j; //非法,导致编译错误  
3. 在另一连接文件中引用const常量
[cpp] view plaincopyprint?
extern const int i; //合法  
extern const int j=10; //非法,常量不可以被再次赋值  
      在C++中const默认具有内部链接性,也就是说如果声明const int i = 10;等价于 static const int i =10;通常可以将const 常量和inline函数放在头文件中。所以要在A文件定义时用extern const int i=10;B文件中引用时用extern const int i。在C中恰好相反,const 默认具有外部链接属性,所以在引用外部const 常量时无需加extern。
4. 便于进行类型检查
  用const方法可以使编译器对处理内容有更多了解。 
[cpp] view plaincopyprint?
#define I=10  
const long &i=10; /*dapingguo提醒:由于编译器的优化,使得在const long i=10; 时i不被分配内存,而是已10直接代入以后的引用中,以致在以后的代码中没有错误,为达到说教效 
  果,特别地用&i明确地给出了i的内存分配。不过一旦你关闭所 
  有优化措施,即使const long i=10;也会引起后面的编译错误。*/  
  char h=I; //没有错  
  char h=i; //编译警告,可能由于数的截短带来错误赋值。  
5. 可以避免不必要的内存分配
[cpp] view plaincopyprint?
#define STRING "abcdefghijklmn\n"  
  const char string[]="abcdefghijklm\n";  
  ...  
  printf(STRING); //为STRING分配了第一次内存  
  printf(string); //为string一次分配了内存,以后不再分配  
  ...  
  printf(STRING); //为STRING分配了第二次内存  
  printf(string);  
  ...    
  由于const定义常量从汇编的角度来看,只是给出了对应的内存地址, 而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。但是这个地方也有点其他问题,大家自己好好研究下吧,给个例子:
[cpp] view plaincopyprint?
#include <stdio.h>   
#define ABCD    "ABCD"  
const char a[] = "ABCD";  
static void  
p(const char *s)  
{  
        printf("%X\n", s);  
}  
int   main(void)  
{  
        const char b[] = "ABCD";  
  
        p(ABCD);  
        p(ABCD);  
        p(a);  
        p(b);  
        return 0;  
}  
[plain] view plaincopyprint?
程序输出为:  
400748  
400748  
40073F  
FFFFEB00  
编译器管不了你运行是更改所谓的const int 变量。 但对于你声明的const int 变量。在它的编译空间里,它保留了这个数值。 所以,调用的时候,把立即数传过去了(这个值在编译时就确定了)。
6. 可以通过函数对常量进行初始化
[cpp] view plaincopyprint?
int value();    
const int i=value();  
  dapingguo说:假定对ROM编写程序时,由于目标代码的不可改写,本语句将会无效,不过可以变通一下:
  const int &i=value();
  只要令i的地址处于ROM之外,即可实现:i通过函数初始化,而其值有不会被修改。
7. 是不是const的常量值一定不可以被修改呢?
  观察以下一段代码:  
[cpp] view plaincopyprint?
const int i=0;  
int *p=(int*)&i;  
p=100;  
     通过强制类型转换,将地址赋给变量,再作修改即可以改变const常量值。
     说明了申明为常量的数据也可能被改变。我这里补充的是不要对const 的滥用。强制绕过const 检查可能引起运行错误。把const int i=0 声明在函数内,能够达到你的目的把const int i=0 声明为全局变量,虽然仍然能够用强制转换绕过编译器检查,但会引起运行错误。
可参考下例: 
[cpp] view plaincopyprint?
const int j=50;  
void main()  
{  
    const int i=0;  
    int *p=(int*)&i;  
    *p=100;   
    int *p2=(int *)&j;  
    *p2=200;  // runtime error  
    cout << &i << &j;  
    system("pause");  
}  
8. 请分清数值常量和指针常量,以下声明颇为玩味:
[cpp] view plaincopyprint?
int ii=0;  
const int i=0; //i是常量,i的值不会被修改  
const int *p1i=&i; //指针p1i所指内容是常量,可以不初始化  
int * const p2i=&ii //指针p2i是常量,所指内容可修改  
const int * const p3i=&i; //指针p3i是常量,所指内容也是常量  
p1i=&ii //不合法 左操作数包含“int *”类型  
*p2i=100; //不合法  右操作数包含“int *const ”类型  
指向常量的指针并不能保证所指向的值不被改变
[cpp] view plaincopyprint?
const int i=10;  
void main()  
{  
    const int j=20;  
    int k = 30;  
    const int * p1=&i;  
    const int * p2 = &j;  
    const int * p3 = &k;  
    // i=80; fail  
    // j= 20; fail  
    // *p3 = 50; fail  
    // 以上三种均未逃过编译器检查  
    k=80;   // succeed 逃过了编译器检查。 *p3 不行,但直接改k 允许。  
    system("pause");  
}  
所以对const 的理解,全局变量不仅有编译的保护,还有运行的保护。对局部变量,则只有编译的保护。
所以,当你声明一个局部const变量时,它可能在运行期被改变。
二、关于C++中的const关键字的用法非常灵活
1. const常量,如:
[cpp] view plaincopyprint?
const int max = 100;  <span style="font-family: simsun; "> </span>  
优点:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)
2. const 修饰类的数据成员。
如:
[cpp] view plaincopyprint?
class A  
{  
  const int size;  
  …    
}  
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。如
[cpp] view plaincopyprint?
class A  
{  
    const int size = 100; //错误  
    int array[size]; //错误,未知的size  
}  
const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。如
[cpp] view plaincopyprint?
class A  
{  
     …  
     enum {size1=100, size2 = 200 };  
     int array1[size1];  
     int array2[size2];    
}  
枚举常量不会占用对象的存储空间,他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。
3. const修饰指针的情况,见下式:
[cpp] view plaincopyprint?
int b = 500;    
const int* a = &b;//[1]  
int const *a = &b;//[2]  
int* const a = &b;//[3]   
const int* const a = &b;//[4]   
如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3 ;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。  
4. const的初始化  
先看一下const变量初始化的情况  
1) 非指针const常量初始化的情况:A b;  
[cpp] view plaincopyprint?
const A a = b;    
2) 指针const常量初始化的情况:
[cpp] view plaincopyprint?
A* d = new A();    
const A* c = d;  //或者:const A* c = new A();    
3)引用const常量初始化的情况:  
[cpp] view plaincopyprint?
A f;    
const A& e = f; // 这样作e只能访问声明为const的函数,而不能访问一般的成员函数<span style="font-family: simsun; font-size: 14px; line-height: 23px; text-align: left; ">;  </span>  
  [思考1]: 以下的这种赋值方法正确吗?  
  const A* c=new A();  
  A* e = c;  
  [思考2]: 以下的这种赋值方法正确吗?  
  A* const c = new A();  
  A* b = c;
5. 函数声明中的运用
      另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const 可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。有如下几种情况,以下会逐渐的说明用法:A& operator=(const A& a);  
[cpp] view plaincopyprint?
void fun0(const A* a );    
void fun1( ) const; // fun1( ) 为类成员函数   
const A fun2( );  
1) 修饰参数的const,如:
[cpp] view plaincopyprint?
void fun0(const A* a );  
void fun1(const A& a);    
     调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。  
[注意]:参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。
[总结]     
       对于非内部数据类型的输入参数,因该将“值传递”的方式改为“const引用传递”,目的是为了提高效率。例如,将void Func(A a)改为void Func(const A &a)。对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x); 修饰返回值的const,如:
[cpp] view plaincopyprint?
const A fun2( );  
const A* fun3( );<span style="font-family: simsun; ">  </span>  
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。
[cpp] view plaincopyprint?
const Rational operator*(const Rational& lhs, const Rational& rhs)    
{    
      return Rational(lhs.numerator() * rhs.numerator(),    
      lhs.denominator() * rhs.denominator());    
} <span style="font-family: simsun; "> </span>  
返回值用const修饰可以防止允许这样的操作发生:
[cpp] view plaincopyprint?
Rational a,b;    
Radional c;    
(a*b) = c; <span style="font-family: simsun; "> </span>  
一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。  
       一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。如:
[cpp] view plaincopyprint?
const char * GetString(void);  
如下语句将出现编译错误:
[cpp] view plaincopyprint?
char *str=GetString();  
正确的用法是:
[cpp] view plaincopyprint?
const char *str=GetString();  
     函数返回值采用“引用传递”的场合不多,这种方式一般只出现在类的赙值函数中,目的是为了实现链式表达。如:
[cpp] view plaincopyprint?
class A  
{  
    …  
    A &operate = (const A &other); //负值函数  
}  
   A a,b,c; //a,b,c为A的对象  
   …  
   a=b=c; //正常  
   (a=b)=c; //不正常,但是合法  
若负值函数的返回值加const修饰,那么该返回值的内容不允许修改,上例中a=b=c依然正确。(a=b)=c就不正确了。
[思考3]: 这样定义赋值操作符重载函数可以吗?  
const A& operator=(const A& a);
    类成员函数中const的使用,一般放在函数体后,形如:void fun() const;  任何不会修改数据成员的函数都因该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。如:
[cpp] view plaincopyprint?
class Stack  
{  
public:  
  void Push(int elem);  
  int Pop(void);  
  int GetCount(void) const; //const 成员函数  
private:    
  int m_num;  
  int m_data[100];  
};  
int Stack::GetCount(void) const  
{  
  ++m_num; //编译错误,企图修改数据成员m_num  
  Pop(); //编译错误,企图调用非const函数  
  Return m_num;  
}  
[思考题答案]  
1 这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;  
2 这种方法正确,因为声明指针所指向的内容可变;  
3 这种做法不正确;  
在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了:  
[cpp] view plaincopyprint?
A a,b,c:    
(a=b)=c; <span style="font-family: simsun; "> </span>  
因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。
posted @ 2012-10-09 10:40 Jacc.Kim 阅读(229) | 评论 (0)编辑 收藏

原文出处:http://zhidao.baidu.com/question/385301217.html

[转载] #include<iomanip>怎么用?在c++中

在c++程序里面经常见到下面的头文件
  #include <iomanip>
  io代表输入输出,manip是manipulator(操纵器)的缩写(在c++上只能通过输入缩写才有效。)
  iomanip的作用:
  主要是对cin,cout之类的一些操纵运算子,比如setfill,setw,setbase,setprecision等等。它是I/O流控制头文件,就像C里面的格式化输出一样.以下是一些常见的控制函数的:
  dec 置基数为10 相当于"%d"
  hex 置基数为16 相当于"%X"
  oct 置基数为8 相当于"%o"
  setfill( 'c' ) 设填充字符为c
  setprecision( n ) 设显示小数精度为n位
  setw( n ) 设域宽为n个字符
  这个控制符的意思是保证输出宽度为n。如:
  cout << setw( 3 ) << 1 << setw( 3 ) << 10 << setw( 3 ) << 100 << endl; 输出结果为
  1 10100 (默认是右对齐)当输出长度大于3时(<<1000),setw(3)不起作用。
  ▲setw(n)用法: 通俗地讲就是预设宽度
  如 cout<<setw(5)<<255<<endl;
  结果是:
  (空格)(空格)255
  ▲setfill(char c) 用法 : 就是在预设宽度中如果已存在没用完的宽度大小,则用设置的字符c填充
  如 cout<<setfill(‘@‘)<<setw(5)<<255<<endl;
  结果是:
  @@255
  ▲setbase(int n) : 将数字转换为 n 进制.
  如 cout<<setbase(8)<<setw(5)<<255<<endl;
  cout<<setbase(10)<<setw(5)<<255<<endl;
  cout<<setbase(16)<<255<<endl;
  结果是:
  (空格)(空格)377
  (空格)(空格) 255
  (空格)(空格)(空格) f f
  ▲ setprecision用法
  使用setprecision(n)可控制输出流显示浮点数的数字个数。C++默认的流输出数值有效位是6。
  如果setprecision(n)与setiosflags(ios::fixed)合用,可以控制小数点右边的数字个数。setiosflags(ios::fixed)是用定点方式表示实数。
  如果与setiosflags(ios::scientific)合用, 可以控制指数表示法的小数位数。setiosflags(ios::scientific)是用指数方式表示实数。
  setiosflags(ios::fixed) 固定的浮点显示
  setiosflags(ios::scientific) 指数表示
  setiosflags(ios::left) 左对齐
  setiosflags(ios::right) 右对齐
  setiosflags(ios::skipws) 忽略前导空白
  setiosflags(ios::uppercase) 16进制数大写输出
  setiosflags(ios::lowercase) 16进制小写输出
  setiosflags(ios::showpoint) 强制显示小数点
  setiosflags(ios::showpos) 强制显示符号
  举例:
  #include <iostream.h>
  #include <iomanip.h>
  using namespace std;
  int main()
  {
  cout<<12345.0<<endl;//输出"12345"
  cout<<setiosflags(ios::fixed)<<setprecision(3)<<1.2345<<endl;输出"1.235"(应该输出"1.235",而不是"1.234".因为要遵循4舍5入的原则)
  cout<<setiosflags(ios::scientific)<<12345.0<<endl;//输出"1.234500e+004 "
  cout<<setprecision(3)<<12345.0<<endl;//输出"1.23e+004 "
  return 0;
  }
posted @ 2012-09-27 17:14 Jacc.Kim 阅读(396) | 评论 (0)编辑 收藏

原来出处:http://blog.csdn.net/beyondhaven/article/details/4204345

[转载] C++模板:函数模板和模板函数

1.函数模板的声明和模板函数的生成

 

1.1函数模板的声明

函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。

函数模板的声明形式为:

template<typename 数据类型参数标识符>

<返回类型><函数名>(参数表)

{

    函数体

}

其中,template是定义模板函数的关键字;template后面的尖括号不能省略;typename(或class)是声明数据类型参数标识符的关键字,用以说明它后面的标识符是数据类型标识符。这样,在以后定义的这个函数中,凡希望根据实参数据类型来确定数据类型的变量,都可以用数据类型参数标识符来说明,从而使这个变量可以适应不同的数据类型。例如:

template<typename T>

T fuc(T x, int y)

{

    T x;

    //……

}

如果主调函数中有以下语句:

double d;

int a;

fuc(d,a);

则系统将用实参d的数据类型double去代替函数模板中的T生成函数:

double fuc(double x,int y)

{

    double x;

    //……

}

函数模板只是声明了一个函数的描述即模板,不是一个可以直接执行的函数,只有根据实际情况用实参的数据类型代替类型参数标识符之后,才能产生真正的函数。

关键字typename也可以使用关键字class,这时数据类型参数标识符就可以使用所有的C++数据类型。

1.2.模板函数的生成

函数模板的数据类型参数标识符实际上是一个类型形参,在使用函数模板时,要将这个形参实例化为确定的数据类型。将类型形参实例化的参数称为模板实参,用模板实参实例化的函数称为模板函数。模板函数的生成就是将函数模板的类型形参实例化的过程。例如:

使用中应注意的几个问题:

⑴ 函数模板允许使用多个类型参数,但在template定义部分的每个形参前必须有关键字typename或class,即:

template<class 数据类型参数标识符1,…,class 数据类型参数标识符n>

<返回类型><函数名>(参数表)

{

     函数体

}

⑵ 在template语句与函数模板定义语句<返回类型>之间不允许有别的语句。如下面的声明是错误的:

template<class T>

int I;

T min(T x,T y)

{

   函数体

}

⑶ 模板函数类似于重载函数,但两者有很大区别:函数重载时,每个函数体内可以执行不同的动作,但同一个函数模板实例化后的模板函数都必须执行相同的动作。


2 函数模板的异常处理

函数模板中的模板形参可实例化为各种类型,但当实例化模板形参的各模板实参之间不完全一致时,就可能发生错误,如:

template<typename T>       

void min(T &x, T &y)

{  return (x<y)?x:y;  }

void func(int i, char j)

{

   min(i, i);

   min(j, j);

   min(i, j);

   min(j, i);

}

例子中的后两个调用是错误的,出现错误的原因是,在调用时,编译器按最先遇到的实参的类型隐含地生成一个模板函数,并用它对所有模板函数进行一致性检查,例如对语句

min(i, j);

先遇到的实参i是整型的,编译器就将模板形参解释为整型,此后出现的模板实参j不能解释为整型而产生错误,此时没有隐含的类型转换功能。解决此种异常的方法有两种:

⑴采用强制类型转换,如将语句min(i, j);改写为min(i,int( j));

⑵用非模板函数重载函数模板

方法有两种:

① 借用函数模板的函数体

此时只声明非模板函数的原型,它的函数体借用函数模板的函数体。如改写上面的例子如下:

template<typename T>       

void min(T &x, T &y)

{  return (x<y)?x:y;  }

int min(int,int);

void func(int i, char j)

{

   min(i, i);

   min(j, j);

   min(i, j);

   min(j, i);

}

执行该程序就不会出错了,因为重载函数支持数据间的隐式类型转换。

② 重新定义函数体

就像一般的重载函数一样,重新定义一个完整的非模板函数,它所带的参数可以随意。C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:

• 寻找一个参数完全匹配的函数,若找到就调用它。若参数完全匹配的函数多于一个,则这个调用是一个错误的调用。

• 寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。

• 若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。

•若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。


此为转载文章,文章出处没有了。请大家见谅。


posted @ 2012-09-27 16:59 Jacc.Kim 阅读(199) | 评论 (0)编辑 收藏

仅列出标题
共14页: First 3 4 5 6 7 8 9 10 11 Last