存档

‘编程’ 分类的存档

10的反,是多少?int型内存存放方式的一个小总结

2010年6月17日 4 条评论
昨天自习室,献哥读这本《c缺陷与陷阱》,看到激情之处,和我讨论 ~10 为多少?
想了一下,32位系统下是…结果算了半天没有一个确切的结果,我才晓得是这一块本科2年纪学的东西,都忘记差不多了。
回来翻翻书,百度、google一把,终于整明白了。
做个记录:
在计算机中,int数据是用补码形式保存的,用补码表示有两个好处:
(1)使符号位能与有效值部分一起参加运算,从而简化运算规则.
(2)使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计 所有这些转换都是在计算机的最底层进行的。
关于补码的详细介绍,我们可以看百科把(点我学习补码)
因此,我们可以计算出~10的过程:
10 = 00000000 00000000 00000000 00001010 (bin)
因此对10取反即为:
~10 = 11111111 11111111 11111111 11110101 (bin)
我们晓得,计算机中用补码表示,因此对 该补码求反,然后 + 1 就得到原址,当然啦还有符号。呵呵
11111111 11111111 11111111 11110101 (bin)
计算即为
-11 = – ( 00000000 00000000 00000000 00001011 (bin) )
这个应该是比较基础的了,都是以前的学的知识,看来还是基础不够扎实啊!呵呵
突然又想起了,前天面中兴的时候那位哥哥问的一个问题:int 范围 ,这个当然晓得: 2^31到 2^31-1
但是如果,深究一下,为什么呢?解释一下?
可能又牵扯到了内存中为的存储方法了,int 在内存中存储的是反码,第一位为符号位。具体可参考1.
我比较懒。引用论坛里一大牛写的吧:
考虑8位情况00—-ff(hex),也就是00000000(10进制0)—-11111111(bin)<10进制255>
它一共能直接表示的数值个数是2^8=256个,非负数:0-127(ff hex)<128个>
负数-1~-128<128个>
于是8位的范围就是-128~127 (-128,-127….-1,0,1,2,….127 <total 256>
注意01111111,这是8位中最大正数,0是符号位,那么显然能表示的最大正数是7F(hex)=127
还有一个0,剩下的就是负数,256-1-127=128个,就是-1~-128
为什么有 -128呢?
我们可以考虑,10000000(bin),也就是说 -0 的情况,在内存中反码表示。我们可以计算,“按位取反,末位加一”的方法,即为 10000000(bin) = 128 , 加上符号位。即为 -128 。
同理,n位整型范围是-2^n~~~2^n-1
参考 :
补码:
反码:
关于2的补码:
分类: 编程 标签:

手动实现的C语言string.h 头文件的字符串函数

2010年5月31日 2 条评论
研究C语言,首先要明白其库函数…
#include<stdio.h>
char* strcpy(char* dest,const char* src);  //字符串拷贝
char* strcat(char* dest,const char* src);  //字符串连接,返回dest字符串
char* strncat(char* dest, const char* src, int size);//把src前n个字符串连接到dest,返回dest字符串
int   strcmp(const char* src1,const char* src2);  //字符串比较  返回值: 1:> 0:= -1<
int   strncmp(const char* src,const char* dst,int size);  //dst前n个字符和src比较返回值: 1:>  0:=  -1<
int   strlen(const char* src);  //返回字符串长度
char* strchr(const char* src, int ch); //字符ch在字符串第一次出现的位置,返回出现字符ch位置开始到字符串结束位置的指针
char* strrchr(const char* src, int ch); //字符ch在字符串最后一次出现的位置,返回出现字符ch位置开始到字符串结束位置的指针
char* strstr(const char* src, const char* sub); //字符串sub在字符串第一次出现的位置,返回出现字符串sub位置开始到字符串结束位置的指针
//memery copy operate function==================//
void* memcpy(void* dest,const void* src,unsigned int size); //把src的内容拷到dest内存里去,并返回dest指向的内存地址
void* memset(void* dst, int ch, unsigned int size); //把dst内存中的size大小用使用ch来初始化,并返回dest指向的内存地址
int   memcmp(const void* src1, const void* src2, unsigned int size); //比较内存中src1与src2中的前size 个字符是否相等,1:>  0:=  -1<  (see strncmp(const char*,const char*,unsigned int))
void* memchr(const void* dst, int ch, unsigned int size); //在内存中的某一范围内,查找特定的字符ch,并返回指向ch的指针
void* memmove(void* dst, const void* src, unsigned int size); //memmove()与memcpy()一样都是用来拷贝src所指的内存内容前n个字节到dest所指的地址上。不同的是,当src和dest所指的内存区域重叠时,memmove()仍然可以正确的处理,不过执行效率上会比使用memcpy()略慢些.返回指向dest的指针
//memery copy operate function==================//

int main()
{
// printf("the string len=%d\n",strlen("helloWorld")); 

// char dest[20];
// printf("the string is=%s\n",strcpy(dest,"helloWorld"));
// char dest[30]="hello--";
// printf("after connect sub string is=%s\n",strcat(dest,"world"));
// char dest[30]="hello--";
// printf("after connect sub string is=%s\n",strncat(dest,"world_programe",4));

// printf("the string compare result is=%d\n",strcmp("hello","aello"));
// printf("the string compare result is=%d\n",strncmp("hello","hello_good",5));
// printf("the char appear position is=%s\n",strchr("hello-r-es-d",'-'));
// printf("the char appear position is=%s\n",strrchr("hel*lo-r*-e*s-d",'*'));

// printf("the sub string is=%s\n",strstr("hello_world","lo"));
 char dest[20];
// printf("the sub string is=%s\n",memcpy(dest,"hello..111",sizeof(dest)));
 printf("the sub string is=%s\n",memset(dest,'0',sizeof(dest)));

 return 0;
}
//=========================================================//
int  strlen(const char* src)
{
 const char* p=src;
 while(*p++!='\0');
 return p-src-1;
}
//=========================================================//
char* strcpy(char* dest,const char* src)
{
 if(dest&&src)
 {
  int i=0;
  while((*(dest+i)=*(src+i))!='\0') i++;
  *(dest+i)='\0';
 // return dest;
 }
 return dest;
}
//=========================================================//
char* strcat(char* dest,const char* src)
{
 if(dest&&src)
 {
  int len=strlen(dest);
 // printf("len=%d\n",len);
  int i=0;
  while((*(dest+len+i)=*(src+i))!='\0') i++;
  *(dest+len+i)='\0';
 }
 return dest;
}
//=========================================================//
char* strncat(char* dest, const char* src, int size)
{
 if(dest&&src)
 {
  int len=strlen(dest);
  int i=0;
  while((i<size)&&((*(dest+len+i)=*(src+i))!='\0')) i++;
 }
 return dest;
}
//=========================================================//
int strcmp(const char* src1,const char* src2)
{
 int equal;
 int i=0;
 while(!(equal=*(unsigned char*)(src1+i)-*(unsigned char*)(src2+i))&&(*(src1+i))&&(*(src2+i)))
  i++;
 if(equal<0) return -1;
 else if(equal>0) return 1;
 else return 0;
}
//=================比较两个字符串前size 个字符串是否相等==========================//
int strncmp(const char* src1,const char* src2,int size)
{
 int equal;
 int i=0;
 while((i<size)&&!(equal=*(unsigned char*)(src1+i)-*(unsigned char*)(src2+i))&&(*(src1+i))&&(*(src2+i)))
  i++;
 if(equal<0) return -1;
 else if(equal>0) return 1;
 else return 0;
}
//=========================================================//
char* strchr(const char* src, int ch)
{
 int i=0;
 while(*(src+i)&&(*(src+i)!=ch)) i++;
 return (char*)(src+i);
}
//=========================================================//
char* strrchr(const char* src, int ch)
{
 int len=strlen(src);
 int i=0;
 while(i<len&&(*(src+len-i)!=ch)) i++;
 return (char*)(src+(len-i));
}
//=========================================================//
char* strstr(const char* src, const char* sub)
{
 if(src&&sub)
 {
  int subLen=strlen(sub);
  int srcLen=strlen(src);
  int nomatch=1;
  int fds=srcLen-subLen+1;
  int i=0;
  if(fds>0) //find counts in the string
   while((nomatch=strncmp(src+i,sub,subLen))&&fds--)//把当前src的指针往后推,直到找到与sub指针相同为止
    i++;
  if(nomatch)
   return 0;
  else
   return (char*)(src+i);
 }
 return 0;
}
//====================memory operate=====================================//
//====================memory operate=====================================//
void* memcpy(void* dest,const void* src,unsigned int size)
{
 if(dest&&src&&size>0)
 {
  int i=0;
     unsigned char* p=(unsigned char*)dest;
  unsigned char* q=(unsigned char*)src;
  while((i<size)&&(*(p+i)=*(q+i)))
   i++;
  return dest;
 }
 return 0;
}
//=========================================================//
void* memset(void* dst, int ch, unsigned int size)
{
 int i=0;
 unsigned char* p=(unsigned char*)dst;
 while((i<size)&&(*(p+i)=ch))
  i++;
 return dst;
}
//=========================================================//
int  memcmp(const void* src1, const void* src2, unsigned int size)
{
}
//=========================================================//
void* memchr(const void* dst, int ch, unsigned int size)
{
}
void* memmove(void* dst, const void* src, unsigned int count)
{
   void * ret = dst;
   if (dst <= src || (char *)dst >= ((char *)src + count))
   {
      while (count--)
      {
        *(char *)dst = *(char *)src;
            dst = (char *)dst + 1;
            src = (char *)src + 1;
        }
   }
   else
   {
      dst = (char *)dst + count - 1;
      src = (char *)src + count - 1;
      while (count--)
      {
        *(char *)dst = *(char *)src;
        dst = (char *)dst - 1;
        src = (char *)src - 1;
      }
   }
   return(ret);

}
分类: 编程 标签:

华为程序设计大赛归来…

2010年5月29日 1 条评论

前几天大家一直的都在讨论中兴的程序设计“中兴捧月”,本来想去试一下的,苦于没有时间,结果放弃了。这几天华为来学校举行 华为程序设计大赛,貌似去年也有这个比赛。这个就是一个程序设计,也不会花太多功夫,前天报了名,昨天通知今天上午 8:00 – 12:00 ,看到四个小时,心里那叫一个汗哪!当时还在想想,肯定很多程序。结果今天拿到题才晓得,原来只有三道编程题。基本上都是对字符串的操作,不允许使用string.h中的库函数。

看起来还是蛮简单的,但是做起来还是有些恼火。没有库函数,好多指针操作,转来转去都转迷糊了。还好是在vc下写的,能断点调试。12点最后三个题全部搞定,只是 因为时间比较紧张,第二题考虑的不够全面,而且第三题也有可以再度优化的余地…
这次设计大赛让我深有感触:
1.眼高手低。再次感受到这个问题,看到程序,就感觉应该比较简单,结果真正动手去做的时候才晓得有很多问题。
2.基础知识重要。 这次仅仅三个题目,把基础知识搜罗了一大半。
3.指针比较要熟悉(c开发)。假如想要操作把字符串操作好,那指针必须要做到游刃有余。
分类: 编程 标签:

Open Flash Chart 修改字体及编译总结

2010年4月28日 4 条评论

最近,项目中要显示报表和曲线,去年暑假的时候选定了使用Open Flash Chart,这个是一个不错的插件,而且是源码开放的。(对作者以及贡献者表示感谢)

最近广电禁止英文缩略词,我们项目中显示了 月份和星期。我们可以知道 OFC所支持的这种坐标 显示 都是自动 显示 英文或者是英文所写的。怎么改呢?呵呵,既然是基于LGPL的(见http://teethgrinder.co.uk/open-flash-chart-2/develop.php),那么我们就去改他的源码好了,编译版本Version 2 Lug Wyrm Charmer。

找到string目录下的

  protected static var dateConsts:Object = {
   shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
   //longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
   longMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
   shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
   //longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
   longDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  }

OK!就是这里,改了就行了。修改以后,进行TEST,结果什么都没有,不显示?为啥子呢?

查了好半天才想起来,原来设置了 横坐标 斜着显示。斜着OFC斜着是不支持中文的。修改datafile: ”rotate”: 0  。问题解决。太棒了!呵呵!

guangmean(http://www.hooto.com/home/guangmean/blog/archives/5213.html) 提出了编译字体可以旋转显示中文,但是,后果就是生成文件有256Kb上升到6MB,这样用户体验会大大下降,所以放弃这个修改。不旋转就OK乐呗!

最后谢谢,空灵调调,和我交流,提示我改怎么修改。令附上 flashdevelop平台-搭建-配置

flashdevelop平台-搭建-配置

前段时间在啃完两本AS书后,开始蠢蠢欲动,Jo-Nick推荐用flashdevelop开发,于是就整了个来玩。这个软件 相当地淑女,喜欢她那简洁而又华丽的外表,还有一些非常实用的小细节,不过伺候起来还比较麻烦,前期要整好多东西。
PS:虽然天地会论坛里有讲怎么安装,但是因为在论坛里,看起来还比较麻烦,所以就打算整理一下,嘿嘿。

下面稍微介绍一下如何搭建这个 淑女一样的平台。

首先是下载相关软件:

1.flashdevelop 3.0.0 beta9+下载地址
2. .net framework 2.0 版本列表
3. java sdk 1.6 下载地址
4.flash SDK 4.0 下载地址 版本列表最近发现 4.0出来了 就下了4.0 听说4.0更牛逼·· 还有 flash player 10 (flash真强大)
补充: 有网友提出的trace显示不出来, 飞鱼经过实践发现flex_sdk_3 有些版本 在调试中不出trace, 建议下载,flex_sdk_4.

5.Flash player Debug 下载地址 下载列表 (调试用的呢· 不然flash里的trace 就显示不出来了。)
补充:网友aObject 反应 上面的debug不能用,经证实确实会提示错误. 通过交流,找到了正确的debug sa_flashplayer_9_debug.exe 也希望哪位达人给个解释, 3Q 

如果只开发 as 那么以上 软件已经够了呢。

下载完后,就开始配置咯。

分别装好.net framework 2.0,java sdk 1.6,FD(flashdevelop)。flash SDK 4.0 不用装呢·,解压开放在自己看得见找得到的地方,如: D:\flex_sdk_4 (之后有用)。
Flash player Debug 就放在 flash player 相同目录吧。

然后打开FD是开始配置

Tools -> Programe Settings 设置AS3context 下的 flex sdk 的地址为 上面你保存的地址路径
flashdevelop平台-搭建-配置

设置Flash viewer 下 的External player Path 为 flash player debug 的地址。
flashdevelop平台-搭建-配置

至此配置 简单完成。

初次调试:

新建一个empty project
flashdevelop平台-搭建-配置

新建一个main函数
flashdevelop平台-搭建-配置

写下 我的初夜··· hello world :
个人觉得这个目录方式很经典··。膜拜中。。
flashdevelop平台-搭建-配置

设置发布文件形式。
注意:要将下面的test movie 改成 External player 否则看不到trace的哦。
flashdevelop平台-搭建-配置

然后勇敢地 按ctrl + enter 测试吧。
flashdevelop平台-搭建-配置

经典知识分享–收藏

2010年4月3日 7 条评论

正则表达式30分钟入门教程

 

http://www.yqshare.com/tutorials/regex.htm

高质量C++-C编程指南

 

http://www.yqshare.com/tutorials/high-c.htm

分类: 编程 标签:

内存谨慎操作—mysql_free_result()引起的15G内存泄露问题…

2010年3月30日 1 条评论

我们的流量测试系统上的做系统测试,以前都用的随机用户ID,由于数据量过大,我做了一个简单过滤,只插入前5W条记录,这样程序运行很好,速度也在可接受之内。后来添加上用户,由于用户ID是存在数据库里面的,所以我们考虑到了使用内存表缓存关键字:ID和IP,然后每次通过IP匹配id,这样便有了多次数据库查询,由于不在监控记录内的IP我们都是丢掉的,所以便出现了如下代码:

  sprintf(strQuery,"select userid from heap_user_ip where ip=%lld",host_ip);

   if(mysql_query(mysql,strQuery))
   {
     DBG("select userid  error: %s\n", mysql_error(mysql));
    return 0;
   }
   res=mysql_store_result(mysql);
   row = mysql_fetch_row(res);
   if(row!=NULL)
    userid=atoi(row[0]);    
   else
  {
     userid=0; 

     return 0;
    }
 mysql_free_result(res);

开始的时候代码运行还是可以的,没有什么问题,但是运行时间久了,就发现这个程序特别吃内存,又一次运行了一天以后居然,吃了整整 15个G的内存,这下把我吓了一跳,一看就杯具了,一直在怀疑我的Hash 桶 和 链表数据有问题,但是把hash桶以及链表全部算上 估计出最大值也不会有2G的内存消耗。然后就怀疑是不是链表出现了,内存的泄露。查了一天,打印了N多地方。还是没有查出问题。

突然想起了,用top看下变化,能看到,这个程序每3秒钟就有一个+5MB内存,后来查看源码,发现全部是更新操作,没有插入,就可以知道链表是没有malloc内存的,怎么会是这样呢?

最后看到这个程序的用户ID查找时,有一个问题:如果该IP不在统计表里面,程序会直接退出,那么res便没有释放!我的天,原来在这里;在退出先加上

<mysql_free_result(res);

问题解决!

我们可以推测,mysql 开辟的不是栈上的内存,应该是类似 堆上的,要不然,函数退出改结果就会自动释放!

所以,做事要细心!想到每一步!

判断单链表是否存在环,判断两个链表是否相交-的相关讨论

2010年3月21日 4 条评论

有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。
问题:
1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如果找到环的入口点?

思考:

一、判断链表是否存在环,我们可以用循环实现(如算法二提到的),但是那样效率较低为:

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:


bool IsExitsLoop(slist *head)
{
    slist *slow = head, *fast = head;

    while ( fast && fast->next )
    {
        slow = slow->next;
        fast = fast->next->next;
        if ( slow == fast ) break;
    }

    return !(fast == NULL || fast->next == NULL);
}

二、找到环的入口点

(1)我们首先想到一个简单的方法,算法复杂度为O(N^2)的双循环的方法,如下


LinkedList* IsCyclicLinkedList(LinkedList *pHead)
{
      if(pHead==NULL || pHead->pNext == NULL) return NULL; //返回空说明无环

      LinkedList *pCur;
      LinkedList *pStart;

      pCur = pStart = pHead->pNext;  //头指针不用,指向头结点

      while(pCur != NULL)
      {
          for(pStart = pCur;pStart != NULL;)  //加上控制条件
            {
              if(pStart->pNext == pCur)
                    return pCur; //返回环的起始位置
              pStart = pStart->pNext;
            }
        pCur = pCur->pNext;
      }
      return pStart;

}

2.我们还是延续上面的算法,把这个计算出来:

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(n>=1)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L – a
a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:

(思考了一下,觉得胡满超分析的没有表达很明白,蓝色标示,如果存在环,相遇点和入口点必定两条路S1和S2,如果我们认定为S1=x的话,那么S2=L-a-x,这样就更严谨了,鉴于下面有位朋友不清楚,我添加了一个简单的示意图。呵呵)

示意图:


slist* FindLoopPort(slist *head)
{
    slist *slow = head, *fast = head;

    while ( fast && fast->next )
    {
        slow = slow->next;
        fast = fast->next->next;
        if ( slow == fast ) break;
    }

    if (fast == NULL || fast->next == NULL)
        return NULL;

    slow = head;
    while (slow != fast)
    {
         slow = slow->next;
         fast = fast->next;
    }

    return slow;
}

扩展问题:

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

本文参考了

http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html

http://www.rugesy.cn/it/u20090628_20_F90E2614-A675-4811-8120-79CD31B1E92B.html(该博客下面讨论的回复,很多不是楼主的意思)

【转载】声明与函数、函数指针

2010年3月21日 没有评论

概述

   在很多情况下,尤其是读别人所写代码的时候,对 C语言声明的理解能力变得非常重要,而C语言本身的凝练简约也使得C语言的声明常常会令人感到非常困惑,因此,在这里我用一篇的内容来集中阐述一下这个问题。

  问题:声明与函数

  有一段程序存储在起始地址为 0的一段内存上,如果我们想要调用这段程序,请问该如何去做?

  答案

  答案是 (*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。

  答案分析:从尾到头

  首先,最基本的函数声明: void function (paramList);

  最基本的函数调用: function(paramList);

  鉴于问题中的函数没有参数,函数调用可简化为 function();

   其次,根据问题描述,可以知道 0是这个函数的入口地址,也就是说,0是一个函数的指针。使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作:(*0)( )。

  第三,大家知道,函数指针变量不能是一个常数,因此上式中的 0必须要被转化为函数指针。

  我们先来研究一下,对于使用函数指针的函数:比如 void (*pFunction)( ),函数指针变量的原型是什么?这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。

  所以将 0强制转换为一个返回值为void,参数为空的函数指针如下:( void (*)( ) )。

   OK,结合2)和3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( ) 。

  答案分析:从头到尾理解答案

   (void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。
   (void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0.
   *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字
   (*(void (*)( ))0)( ),这当然就是一个函数了。

  我们可以使用 typedef清晰声明如下:

   typedef void (*pFun)( );

  这样函数变为 (*(pFun)0 )( );

  问题:三个声明的分析

  对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。

# 1:int* (*a[5])(int, char*);

   首先看到标识符名 a,”[]“优先级大于”*”,a与”[5]“先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向”(int, char*)”,很明显,指向的是一个函数,这个函数参数是”int, char*”,返回值是”int*”。OK,结束了一个。:)

# 2:void (*b[10]) (void (*)());

   b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是”void (*)()”【注10】,返回值是”void”。完毕!

  注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是 “void”。

# 3. doube(*)() (*pa)[9];

   pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是”doube(*)()”(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是”double”)。

C语言中的函数指针

函数在内存中有一个物理位置,而这个位置是可以赋给一个指针的。一零点函数的地址就是该函数的入口点。因此,函数指针可被用来调用一个函数。函数的地址是用不带任何括号或参数的函数名来得到的。(这很类似于数组地址的得到方法,即,在只有数组名而无下标是就得到数组地址。)

怎样说明一个函数指针变量呢 ?
为了说明一个变量 fn_pointer 的类型是”返回值为 int 的函数指针”, 你可以使用下面的说明语句:
int (*fn_pointer) ();
为了让编译器能正确地解释这句语句, *fn_pointer 必须用括号围起来。若漏了这对括号, 则:
int *fn_pointer ();
的意思完全不同了。fn_pointer 将是一个函数名, 其返回值为 int 类型的指针。

函数指针变量

  在C语言中规定,一个函数总是占用一段连续的内存区, 而函数名就是该函数所占内存区的首地址。 我们可以把函数的这个首地址 ( 或称入口地址 ) 赋予一个指针变量, 使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。 我们把这种指向函数的指针变量称为 ” 函数指针变量 ” 。
函数指针变量定义的一般形式为:
类型说明符 (* 指针变量名 )();
其中 ” 类型说明符 ” 表示被指函数的返回值的类型。 “(* 指针变量名 )” 表示 “*” 后面的变量是定义的指针变量。 最后的空括号表示指针变量所指的是一个函数。
例如: int (*pf)();
表示 pf 是一个指向函数入口的指针变量,该函数的返回值 ( 函数值 ) 是整型。
下面通过例子来说明用指针形式实现对函数调用的方法。


int max(int a,int b){
       if(a>b)
           return a;
       else
           return b;
}
main()
{
    int max(int a,int b);
    int(*pmax)();
    int x,y,z;
    pmax=max;
    printf("input two numbers:\n");
    scanf("%d%d",&x,&y);
    z=(*pmax)(x,y);
    printf("maxmum=%d",z);
} 

  从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:

1. 先定义函数指针变量,如后一程序中第 9 行 int (*pmax)(); 定义 pmax 为函数指针变量。

2. 把被调函数的入口地址 ( 函数名 ) 赋予该函数指针变量,如程序中第 11 行 pmax=max;

3. 用函数指针变量形式调用函数,如程序第 14 行 z=(*pmax)(x,y);  调用函数的一般形式为: (* 指针变量名 ) ( 实参表 ) 使用函数指针变量还应注意以下两点:

a. 函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。

b. 函数调用中 “(* 指针变量名 )” 的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处它只是一种表示符号。

指针型函数

前面我们介绍过,所谓函数类型是指函数返回值的类型。 在C语言中允许一个函数的返回值是一个指针 ( 即地址 ) , 这种返回指针值的函数称为指针型函数。
定义指针型函数的一般形式为:
类型说明符 * 函数名 ( 形参表 )
{
…… /* 函数体 */
}
其中函数名之前加了 “*” 号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。
如:
int *ap(int x,int y)
{
…… /* 函数体 */
}
  表示 ap 是一个返回指针值的指针型函数, 它返回的指针指向一个整型变量。下例中定义了一个指针型函数 day_name ,它的返回值指向一个字符串。该函数中定义了一个静态指针数组 name 。 name 数组初始化赋值为八个字符串,分别表示各个星期名及出错提示。形参 n 表示与星期名所对应的整数。在主函数中, 把输入的整数 i 作为实参, 在 printf 语句中调用 day_name 函数并把 i 值传送给形参 n 。 day_name 函数中的 return 语句包含一个条件表达式, n 值若大于 7 或小于 1 则把 name[0] 指针返回主函数输出出错提示字符串 “Illegal day” 。否则返回主函数输出对应的星期名。主函数中的第 7 行是个条件语句,其语义是,如输入为负数 (i<0) 则中止程序运行退出程序。 exit 是一个库函数, exit(1) 表示发生错误后退出程序, exit(0) 表示正常退出。

  应该特别注意的是函数指针变量和指针型函数这两者在写法和意义上的区别。如 int(*p)() 和 int *p() 是两个完全不同的量。 int(*p)() 是一个变量说明,说明 p 是一个指向函数入口的指针变量,该函数的返回值是整型量, (*p) 的两边的括号不能少。 int *p() 则不是变量说明而是函数说明,说明 p 是一个指针型函数,其返回值是一个指向整型量的指针, *p 两边没有括号。作为函数说明, 在括号内最好写入形式参数,这样便于与变量说明区别。 对于指针型函数定义, int *p() 只是函数头部分,一般还应该有函数体部分。

main(){
       int i;
       char *day_name(int n);
       printf("input Day No:\n");
       scanf("%d",&amp;i);
       if(i&lt;0)
          exit(1);
       printf("Day No:%2d--&gt;%s\n",i,day_name(i));
}

char *day_name(int n){
       static char *name[]={ "Illegal day",
                             "Monday",
                             "Tuesday",
                             "Wednesday",
                             "Thursday",
                             "Friday",
                             "Saturday",
                             "Sunday"};
        return((n&lt;1||n&gt;7) ? name[0] : name[n]);
}

  本程序是通过指针函数,输入一个 1 ~ 7 之间的整数, 输出对应的星期名。指针数组的说明与使用一个数组的元素值为指针则是指针数组。 指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
  指针数组说明的一般形式为: 类型说明符 * 数组名 [ 数组长度 ]
  其中类型说明符为指针值所指向的变量的类型。例如: int *pa[3] 表示 pa 是一个指针数组,它有三个数组元素, 每个元素值都是一个指针,指向整型变量。通常可用一个指针数组来指向一个二维数组。 指针数组中的每个元素被赋予二维数组每一行的首地址, 因此也可理解为指向一个一维数组。图 6—6 表示了这种关系。


int a[3][3]={1,2,3,4,5,6,7,8,9};
int *pa[3]={a[0],a[1],a[2]};
int *p=a[0];  

main(){
      int i;
      for(i=0;i<3;i++)
          printf("%d,%d,%d\n",a[i][2-i],*a[i],*(*(a+i)+i));
      for(i=0;i<3;i++)
          printf("%d,%d,%d\n",*pa[i],p[i],*(p+i));
} 

  本例程序中, pa 是一个指针数组,三个元素分别指向二维数组 a 的各行。然后用循环语句输出指定的数组元素。其中 *a[i] 表示 i 行 0 列元素值; *(*(a+i)+i) 表示 i 行 i 列的元素值; *pa[i] 表示 i 行 0 列元素值;由于 p 与 a[0] 相同,故 p[i] 表示 0 行 i 列的值; *(p+i) 表示 0 行 i 列的值。读者可仔细领会元素值的各种不同的表示方法。 应该注意指针数组和二维数组指针变量的区别。 这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/norbe/archive/2006/03/14/624257.aspx

分类: 编程 标签: , ,

【算法的魅力】给定一个长度为N的整数数组(元素有正有负),求所有元素之和,最大的一个子数组。

2010年3月20日 3 条评论

给定一整数序列A1, A2,… An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大。
例如: 整数序列-2, 11, -12, 13, -5, 2, -5, -3, 12, -9的最大子序列的和为14。
算法1:动态规划O(n^2)
分别计算以A1, A2,… An开头子序列的和,计算过程中利用动态规划简化计算。
令Sum(i, j)是A[i] … A[j]的和,则
j>=i,Sum(i, i)=A[i]
Sum(i, j+1) = Sum(i, j) + A[j+1]。

int max_sub(int a[],int size)
{
int i,j,v,max=a[0];
for(i=0;i<size;i++)//分别以A1, A2,... An开头
{
v=0;//以Ai开头子序列累加和
for(j=i;j<size;j++)
{
v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
if(v>max)
max=v;
}
}
return max;
}

算法2:进一步的优化O(n)

1 当Sum(i, j)<0时,Sum(i, j),Sum(i, j+1),Sum(i, j+2)…Sum(i, n)都不可能是最大值。
2 由1可知,最优子序列不可能以负数开头。
3 若Sum(i, j),Sum(i, j+1),Sum(i, j+2)…Sum(i, n)都不为负数,则以A[i+1]…A[n]开头的子序列和都不可能最大。
因而,算法变为:从左到右的一次扫描,不断累加,并更新最大值,当累加A[j]时值为负,则累加值清零,开始从A[j+1]累加。

int max_sub2(int a[], int size)
{
int i,max=0,temp_sum=0;
for(i=0;i<size;i++)
{
temp_sum+=a[i];
if(temp_sum>max)
max=temp_sum;
else if(temp_sum<0)
temp_sum=0;
}
return max;
}
分类: 算法讨论, 编程 标签: ,

踏入C 中的雷区——C 内存管理详解

2010年3月18日 没有评论

转自网上,部分高质量编程的一些内容,写的不错。很值得借鉴的知识:

伟大的Bill Gates 曾经失言:

  640K ought to be enough for everybody — Bill Gates 1981

  程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。

  1、内存分配方式

  内存分配方式有三种:

  (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

  (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  (3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

  2、常见的内存错误及其对策

  发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。常见的内存错误及其对策如下:

  * 内存分配未成功,却使用了它。

  编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

  * 内存分配虽然成功,但是尚未初始化就引用它。

  犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

  * 内存分配成功并且已经初始化,但操作越过了内存的边界。

  例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

  * 忘记了释放内存,造成内存泄露。

  含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

  动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。

  * 释放了内存却继续使用它。
 
  有三种情况:

  (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

  (2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

  (3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

  【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

  【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

  【规则4】动态内存的申请与释放必须配对,防止内存泄漏。

  【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

  3、指针与数组的对比

  C/C++程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

  数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

  指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

  下面以字符串为例比较指针与数组的特性。

  3.1 修改内容

  示例3-1中,字符数组a的容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

char a[] = “hello”;
a[0] = ‘X’;
cout << a << endl;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误
cout << p << endl;

      示例3.1 修改数组和指针的内容

  3.2 内容复制与比较

  不能对数组名进行直接复制与比较。示例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

  语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a) 1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

// 数组…
char a[] = “hello”;
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)

// 指针…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len 1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)

       示例3.2 数组和指针的内容复制与比较

  3.3 计算内存容量

  用运算符sizeof可以计算出数组的容量(字节数)。示例7-3-3(a)中,sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C /C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

  注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例7-3-3(b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。

char a[] = “hello world”;
char *p = a;
cout<< sizeof(a) << endl; // 12字节
cout<< sizeof(p) << endl; // 4字节

     示例3.3(a)计算数组和指针的内存容量

void Func(char a[100])
{
 cout<< sizeof(a) << endl; // 4字节而不是100字节
}

     示例3.3(b) 数组退化为指针

  4、指针参数是如何传递内存的?

  如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?

void GetMemory(char *p, int num)
{
 p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str, 100); // str 仍然为 NULL
 strcpy(str, “hello”); // 运行错误
}

      示例4.1 试图用指针参数申请动态内存

  毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

  如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例4.2。

void GetMemory2(char **p, int num)
{
 *p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
 char *str = NULL;
 GetMemory2(&str, 100); // 注意参数是 &str,而不是str
 strcpy(str, “hello”);
 cout<< str << endl;
 free(str);
}

      示例4.2用指向指针的指针申请动态内存

  由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例4.3。

char *GetMemory3(int num)
{
 char *p = (char *)malloc(sizeof(char) * num);
 return p;
}
void Test3(void)
{
 char *str = NULL;
 str = GetMemory3(100);
 strcpy(str, “hello”);
 cout<< str << endl;
 free(str);
}

       示例4.3 用函数返回值来传递动态内存

  用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例4.4。

char *GetString(void)
{
 char p[] = “hello world”;
 return p; // 编译器将提出警告
}
void Test4(void)
{
 char *str = NULL;
 str = GetString(); // str 的内容是垃圾
 cout<< str << endl;
}

      示例4.4 return语句返回指向“栈内存”的指针

  用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把示例4.4改写成示例4.5,会怎么样?

char *GetString2(void)
{
 char *p = “hello world”;
 return p;
}
void Test5(void)
{
 char *str = NULL;
 str = GetString2();
 cout<< str << endl;
}

     示例4.5 return语句返回常量字符串

  函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

  5、杜绝“野指针”

  “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:

  (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如

char *p = NULL;
char *str = (char *) malloc(100);

  (2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

  (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

class A
{
 public:
  void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
 A *p;
 {
  A a;
  p = &a; // 注意 a 的生命期
 }
 p->Func(); // p是“野指针”
}

  函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。

  6、有了malloc/free为什么还要new/delete?

  malloc与free是C/C++语言的标准库函数,new/delete是C++ 的运算符。它们都可用于申请动态内存和释放内存。

  对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

   因此C 语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例6。

class Obj
{
 public :
  Obj(void){ cout << “Initialization” << endl; }
  ~Obj(void){ cout << “Destroy” << endl; }
  void Initialize(void){ cout << “Initialization” << endl; }
  void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void)
{
 Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存
 a->Initialize(); // 初始化
 //…
 a->Destroy(); // 清除工作
 free(a); // 释放内存
}
void UseNewDelete(void)
{
 Obj *a = new Obj; // 申请动态内存并且初始化
 //…
 delete a; // 清除并且释放内存
}

     示例6 用malloc/free和new/delete如何实现对象的动态内存管理

  类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。

  所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

  既然new/delete的功能完全覆盖了malloc/free,为什么C 不把malloc/free淘汰出局呢?这是因为C 程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

  如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

  7、内存耗尽怎么办?

  如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。

  (1)判断指针是否为NULL,如果是则马上用return语句终止本函数。例如:

void Func(void)
{
 A *a = new A;
 if(a == NULL)
 {
  return;
 }
 …
}

  (2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:

void Func(void)
{
 A *a = new A;
 if(a == NULL)
 {
  cout << “Memory Exhausted” << endl;
  exit(1);
 }
 …
}

  (3)为new和malloc设置异常处理函数。例如Visual C 可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C 使用手册。

  上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。

  很多人不忍心用exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?”

  不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹徒在老死之前会犯下更多的罪。

  有一个很重要的现象要告诉大家。对于32位以上的应用程序而言,无论怎样使用malloc与new,几乎不可能导致“内存耗尽”。我在Windows 98下用Visual C 编写了测试程序,见示例7。这个程序会无休止地运行下去,根本不会终止。因为32位操作系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我只听到硬盘嘎吱嘎吱地响,Window 98已经累得对键盘、鼠标毫无反应。

  我可以得出这么一个结论:对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。

  我不想误导读者,必须强调:不加错误处理将导致程序的质量很差,千万不可因小失大。

void main(void)
{
 float *p = NULL;
 while(TRUE)
 {
  p = new float[1000000];
  cout << “eat memory” << endl;
  if(p==NULL)
   exit(1);
 }
}

  示例7试图耗尽操作系统的内存

  8、malloc/free 的使用要点

  函数malloc的原型如下:

void * malloc(size_t size);

  用malloc申请一块长度为length的整数类型的内存,程序如下:

int *p = (int *) malloc(sizeof(int) * length);

  我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

  * malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

  * malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。最好用以下程序作一次测试:

cout << sizeof(char) << endl;
cout << sizeof(int) << endl;
cout << sizeof(unsigned int) << endl;
cout << sizeof(long) << endl;
cout << sizeof(unsigned long) << endl;
cout << sizeof(float) << endl;
cout << sizeof(double) << endl;
cout << sizeof(void *) << endl;

  在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。

  * 函数free的原型如下:

void free( void * memblock );

  为什么free函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

  9、new/delete 的使用要点

  运算符new使用起来要比函数malloc简单得多,例如:

int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];

  这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如

class Obj
{
 public :
  Obj(void); // 无参数的构造函数
  Obj(int x); // 带一个参数的构造函数
  …
}
void Test(void)
{
 Obj *a = new Obj;
 Obj *b = new Obj(1); // 初值为1
 …
 delete a;
 delete b;
}

  如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如

Obj *objects = new Obj[100]; // 创建100个动态对象

  不能写成

Obj *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1

  在用delete释放对象数组时,留意不要丢了符号‘[]’。例如

delete []objects; // 正确的用法
delete objects; // 错误的用法

  后者相当于delete objects[0],漏掉了另外99个对象。

  10、一些心得体会

  我认识不少技术不错的C /C程序员,很少有人能拍拍胸脯说通晓指针与内存管理(包括我自己)。我最初学习C语言时特别怕指针,导致我开发第一个应用软件(约1万行C代码)时没有使用一个指针,全用数组来顶替指针,实在蠢笨得过分。躲避指针不是办法,后来我改写了这个软件,代码量缩小到原先的一半。

  我的经验教训是:

  (1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。

  (2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。

分类: 编程 标签: