知识小结

free函数——内存释放  

-------规范:free以后的delete以后必须要指空------------------

既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存却在空闲。与 malloc 对应的就是 free 函数了。

free 函数只有一个参数,就是所要释放的内存块的首地址。比如上例:free(p);

free 函数看上去挺狠的,但它到底作了什么呢?其实它就做了一件事:斩断指针变量与这块内存的关系。

比如上面的例子,我们可以说 malloc 函数分配的内存块是属于 p ,因为我们对这块内存的访问都需要通过 p 来进行。 

函数就是把这块内存和 p 之间的所有关free系斩断。从此 p 和那块内存之间再无瓜葛。

至于指针变量 p 本身保存的地址并没有改变,但是它对这个地址处的那块内存却已经没有所有权了。

那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。这就是 free 函数的功能。

按照上面的分析,如果对 p 连续两次以上使用 free 函数,肯定会发生错误。

因为第一使用 free 函数时,p 所属的内存已经被释放,第二次使用时已经无内存可释放了。

关于这点,我上课时让学生记住的是:一定要一夫一妻制,不然肯定出错。

malloc 两次只 free 一次会内存泄漏;malloc 一次 free 两次肯定会出错。

也就是说,在程序中 malloc 的使用次数一定要和 free 相等,否则必有错误。这种错误主要发生在循环使用malloc 函数时,往往把 malloc free 次数弄错了。

 

注意:内存释放之后

既然使用 free 函数之后指针变量 p 本身保存的地址并没有改变,那我们就需要重新把 p的值变为 NULL:p = NULL;

这个 NULL 就是我们前面所说的栓野狗的链子如果你不栓起来迟早会出问题的。

比如:

free(p)之后,你用 if(NULL != p)这样的校验语句还能起作用吗?

例如:

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

strcpy(p, “hello”);

free(p);

/* p 所指的内存被释放,但是 p 所指的地址仍然不变 */

?

if (NULL != p)

{

/* 没有起到防错作用 */

strcpy(p, “world”);

/* 出错 */

}

释放完块内存之后,没有把指针置 NULL,这个指针就成为了野指针”,也有书叫悬垂指针。这是很危险的,而且也是经常出错的地方。

所以一定要记住一条:free 完之后,一定要给指针置 NULL

 

同时留一个问题: NULL 指针连续 free 多次会出错吗?为什么?如果让你来设计 free

函数,你会怎么处理这个问题?

注意:内存已经被释放了,但是继续通过指针来使用

这里一般有三种情况:

第一种:就是上面所说的,free(p)之后,继续通过 p 指针来访问内存。解决的办法就是给 p NULL

第二种:函数返回栈内存。这是初学者最容易犯的错误。

比如在函数内部定义了一个数组,却用 return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。

第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法是重新设计程序,改善对象之间的调用关系。

------结论: free只是让内存释放,但他指向的东西并非NULL,而是乱码。

free(p);  p = NULL;------ok------

 

 

 

strlen 与sizeof()的区别

strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。 

  char aa[10];cout<<strlen(aa)<<endl; //结果是不定的 

  char aa[10]={'\0'}; cout<<strlen(aa)<<endl; //结果为0 

  char aa[10]="jun"; cout<<strlen(aa)<<endl; //结果为3 

  而sizeof()返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen是函数。 

  sizeof(aa) 返回10 

  int a[10]; sizeof(a) 返回40 

  1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。 

  该类型保证能容纳实现所建立的最大对象的字节大小。 

  2.sizeof是操作符(关键字),strlen是函数。 

  3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。 

  sizeof还可以用函数做参数,比如: 

  short f(); 

  printf("%d\n", sizeof(f())); 

  输出的结果是sizeof(short),即2。 

  4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。 

  5.大部分编译程序 在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因 

  char str[20]="0123456789"; 

  int a=strlen(str); //a=10; 

  int b=sizeof(str); //而b=20; 

6.strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。  

7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。 

  

反转字符串

解法一:第一次看到这题目,想到最简单、最直觉的解法就是:遍历字符串,将第一个字符和最后一个交换,第二个和倒数第二个交换,依次循环,即可,于是有了第一个解法:

char* strrev1(const char* str)

{

       int len = strlen(str);

       char* tmp = new char[len + 1];

       strcpy(tmp,str);

 

       for (int i = 0; i < len/2; ++i)

       {

              char c = tmp[i];

        tmp[i] = tmp[len – i - 1];

        tmp[len – i - 1] = c;

       }

 

       return tmp;

}

这里是通过数组的下标方式访问字符串的字符,实际上用指针直接操作即可。解法二正是基于此,实现代码为:

char* strrev2(const char* str)

{

       char* tmp = new char[strlen(str) + 1];

       strcpy(tmp,str);

       char* ret = tmp;

 

       char* p = tmp + strlen(str) - 1;

 

       while (p > tmp)

       {

              char t = *tmp;

              *tmp = *p;

              *p = t;

 

              --p;

              ++tmp;

       }

 

       return ret;

}

显然上面的两个解法中没有考虑时间和空间的优化,一个典型的优化策略就是两个字符交换的算法优化,我们可以完全不使用任何外部变量即完成两个字符(或者整数)的交换,这也是一个很经典的面试题目。特别是一些嵌入式硬件相关编程中经常要考虑寄存器的使用,因此经常有不使用任何第三个寄存器即完成两个寄存器数据的交换的题目。一般有两个解法,对应这里的解法三和解法四。

解法三的实现代码为:

char* strrev3(const char* str)

{

       char* tmp = new char[strlen(str) + 1];

       strcpy(tmp,str);

       char* ret = tmp;

 

       char* p = tmp + strlen(str) - 1;

 

       while (p > tmp)

       {

              *p ^= *tmp;

              *tmp ^= *p;             

              *p ^= *tmp;

 

              --p;

              ++tmp;

       }

 

       return ret;

}

解法四的实现代码为:

char* strrev4(const char* str)

{

       char* tmp = new char[strlen(str) + 1];

       strcpy(tmp,str);

       char* ret = tmp;

 

       char* p = tmp + strlen(str) - 1;

 

       while (p > tmp)

       {

              *p = *p + *tmp;

              *tmp = *p - *tmp;

              *p = *p - *tmp;

 

              --p;

              ++tmp;

       }

 

       return ret;

}

实际上我们还可以通过递归的思想来解决这个问题,思想很简单:每次交换首尾两个字符,中间部分则又变为和原来字符串同样的问题,因此可以通过递归的思想来解决这个问题,对应解法五的实现代码为:

char* strrev5(/*const */char* str,int len)

{

       if (len <= 1)

              return str;

 

       char t = *str;

       *str = *(str + len -1);

       *(str + len -1) = t;

 

       return (strrev5(str + 1,len - 2) - 1);

}

以下给出一个测试程序

int main(int argc,char* argv[])

{

       char* str = "hello";

       P(str);

 

       char* str2 = strrev1(str);

       P(str2);

 

       char* str3 = strrev2(str2);

       P(str3);

 

       char* str4 = strrev3(str3);

       P(str4);

 

       char* str5 = strrev4(str4);

       P(str5);

 

       char* str6 = strrev5(str5,strlen(str5));

       P(str6);

      

       return 0;

}

  你就可以看到字符串"hello""olleh"交替输出了。

  说明:1)这里解法中没有认真考虑输入字符串的合法性和特殊长度(如NULL、一个字符等)字符串的处理;2)前4个算法不改变输入字符串的值,解法五修改了输入字符串

 

 

 

C语言实现strlen函数的几种方法

关键字:C语言

  今天偶然看到了一个实现strlen函数的方法,也实际练习了一下,挺有意义的,其实现的一些思想值得学习,记录一下吧。我这里除了写两个比较巧妙的递归实现之外,也写了另外一种常规的方式。

 

  传说常见的一个笔试题:不使用中间变量求const字符串长度,即实现求字符串长度库函数strlen函数。函数接口声明如下:int strlen(const char *p);

 

  思路分析:

 

  在字符串中通常可以利用最后一个结束符‘\0',但此处参数为const,只读,那么我们不能打他的主意。

 

  函数运行过程中不占用内存基本不可能,除非都使用了寄存器。“不使用中间变量”只是说程序员不能显示的申请内存而已,即不能有局部变量或者动态内存申请。

 

  如果函数自动申请栈内存或者使用寄存器存储变量,或者使用立即数寻址即常量,那么就相当于“不使用中间变量”.

 

  从函数原型看,返回值为int,那么在函数内部必定需要一个地方存储这个值,要么是常数要么是寄存器。长度不为1时不能一次就求出来,说明必须有递归调用,这样递归时函数会自动申请栈内存,这样就相当于程序员“不使用中间变量”了。中间返回的值通过寄存器自动保存,最后一次返回时拷贝到int中去。C/C++中也有临时对象的概念,都是程序在运行过程中由编译器在栈中自动申请的对象,对程序员不可见,也相当于“不使用中间变量”

 

  另外一个不申请任何变量的典型题目是:反转字符串

 

  这种问题都是利用常量,或者将变量的申请交给编译器在递归过程中自动在栈中申请,也就是借刀杀人了。

 

  无代码,无真相;简单的源码如下:

 

  #include

 

  #include

 

  #include

 

  int myStrlen(const char *str);

 

  int myStrlen1(const char *str);

 

  int myStrlen2(const char *str);

 

  int main()

 

  {

 

  char *str=NULL;

 

  str = “Hello Jay!”;

 

  printf(“original strlen():%d\n”,strlen(str));

 

  printf(“myStrlen():%d\n”,myStrlen(str));

 

  printf(“myStrlen1():%d\n”,myStrlen1(str));

 

  printf(“myStrlen2():%d\n”,myStrlen2(str));

 

  }

 

  int myStrlen(const char *str) /* 不用中间变量,用递归实现,很容易看懂 */

 

  {

 

  if ( (str == NULL) || (*str == '\0’) ) {

 

  return 0;

 

  }

 

  else {

 

  return myStrlen(str+1)+1;

 

  }

 

  }

 

  int myStrlen1(const char *str) /* 不用中间变量,也是用递归实现,写得更简洁而已 */

 

  {

 

  assert(str != NULL);

 

  return *str ? (myStrlen1(++str) + 1) : 0;

 

  }

 

  int myStrlen2(const char *str) /* 使用了一个int型变量 */

 

  {

 

  if(str==NULL) return 0;

 

  int len = 0;

 

  for(; *str++ != '\0'; )

 

  {

 

  len++;

 

  }

 

  return len;

 

  }

 

  注:我的三个strlen都对str等于NULL做了兼容的,而貌似strlen(NULL)会“Segmentation fault”.

 

  另外,这里再把网上找的strcpy、strcat、strlen、strcmp的C语言简单实现贴在这里吧,也是很基础的东西,自己也好好学习一下。

 

  char * strcpy (char * dst, char * src)

 

  {

 

  char * cp = dst;

 

  while( *cp++ = *src++ )

 

  ; /* Copy src over dst */

 

  return( dst );

 

  }

 

  char * strcat (char * dst, char * src)

 

  {

 

  char * cp = dst;

 

  while( *cp )

 

  ++cp; /* Find end of dst */

 

  while( *cp++ = *src++ )

 

  ; /* Copy src to end of dst */

 

  return( dst );

 

  }

 

  int strlen (const char * str)

 

  {

 

  int length = 0;

 

  while( *str++ )

 

  ++length;

 

  return( length );

 

  }

 

  int strcmp (unsigned char *src, unsigned char *dst)

 

  {

 

  int ret = 0 ;

 

  while( ! (ret = *src - *dst) && *dst)

 

  ++src, ++dst;

 

  if ( ret < 0 )

 

  ret = -1 ;

 

  else if ( ret > 0 )

 

  ret = 1 ;

 

return( ret );

 

 

 

 

 

由搜狐的一道笔试题想到的(海隆软件)

标签: 编译器null

2012-09-28 12:33 1252人阅读 评论(0) 收藏 举报

http://static.blog.csdn.net/images/category_icon.jpg 分类:

 

算法学习101 http://static.blog.csdn.net/images/arrow_triangle%20_down.jpg

版权声明:本文为博主原创文章,未经博主允许不得转载。

题目如下:

[cpp] view plain copy

  1. #include<iostream>    
  2. using namespace std;    
  3.     
  4. void swap_int(int a , int b)    
  5. {    
  6.     int temp = a;    
  7.     a = b;    
  8.     b = temp;    
  9. }    
  10.     
  11. void swap_str(char* a , char* b)    
  12. {    
  13.     char* temp = a;    
  14.     a = b;    
  15.     b = temp;    
  16. }    
  17.     
  18. int main(void)    
  19. {    
  20.     int a = 10;    
  21.     int b = 5;    
  22.     char* str_a = "hello world";    
  23.     char* str_b = "world hello";    
  24.     swap_int(a , b);    
  25.     swap_str(str_a , str_b);    
  26.     printf("%d %d %s %s\n", a , b , str_a , str_b);    
  27.     
  28.     return 0;    
  29. }    


题目就是 判断能否交换两个字符串和两个整数

同时考察 值传递和引用传递区别。

值传递编译器自动创建临时变量存放响应的变量,当函数返回变量的作用失效。

这道题目难点不在swap_int,在于swap_str

把这道题目换个方式写出来,我想可能更容易理解。

[cpp] view plain copy

  1.    #include<iostream>    
  2.    using namespace std;    
  3.        
  4.    void swap_int(int a , int b)    
  5.    {    
  6.        int temp = a;    
  7.        a = b;    
  8.        b = temp;    
  9.    }    
  10.        
  11.    void swap_str(char* a , char* b)    
  12.    {    
  13.        char* temp = a;    
  14.        a = b;    
  15.        b = temp;    
  16.    }    
  17.        
  18.    int main(void)    
  19.    {    
  20.        int a = 10;    
  21.        int b = 5;    
  22. char *my_stra=NULL;  
  23. char *my_strb = NULL;  
  24.        char *temp =NULL;  
  25. char* str_a = "hello world";    
  26.        char* str_b = "world hello";  
  27. my_stra = str_a;  
  28. my_strb = str_b ;  
  29.        swap_int(a , b);    
  30.       // swap_str(str_a , str_b);    
  31.        printf("%d %d %s %s\n", a , b , str_a , str_b);    
  32.        temp = my_stra;  
  33. my_stra =my_strb;  
  34. my_strb =temp;  
  35. printf(" %s %s\n",  str_a , str_b);    
  36.        return 0;    
  37.    }    


实际上和上面的类似,这里my_stramy_strb都是指针变量,指针变量的内存地址 分别是str_astr_b;

此时我们交换my_stramy_strb实际上只是对他们两个的地址做变换。

str_astr_b是不会有变化的。

 

 

 

 

二维数组的动态分配(new)、初始化(memset)和撤销(delete)

本文转自:http://apps.hi.baidu.com/share/detail/43315704

一维数组的动态分配,初始化和撤销都好说,几乎每一本C++教材都会做出详细的说明。具体如下:

动态分配(例如分配10个单元的) int *array=new int [10]

初始化:memset(array,0,sizeof(array));       (也可以利用一个for循环对其赋值初始化)

撤销:delete [] array;

下面来说二维数组的。

二维数组(nm)利用new来进行动态分配实际上相当于对nm元数组进行动态分配,只不过我们不能一味的按照动态分配一维数组的方法来这项操作。MSVC目前还没有这般的人性化,具体应该这样做:

int **array;
array=new int *[10];
for(int i=0;i<10;i++)
         array[i]=new int [5];


   上面的操作完成了一个105列的二维数组array[10][5]的动态分配,可以看到我们先动态分配了一个10单元的数组的指针的指针的首地址**array,然后再对其每个首地址进行遍历,同时完成一个5单元的数组的动态分分配,并把首地址给*array[i],从而最终完成了二维数组array[10][5]的动态分配。我们可以依此类推得到三维以至多维的数组的动态分配方法。

二维数组的初始化:如果把一维数组初始化办法照搬过来就会发现对于动态分配的二维数组并不适用。这就要理解到memset这个函数三个参数的含义。MSDNmemset的描述如下:

memset

Sets buffers to a specified character.

void*memset(void*dest,intc,size_tcount);

可见memset只能作用于一个一维数组void*dest因此最好的办法就是和二维数组的动态分配结合起来,new一个,memset一个。具体写法如下:

int **array;
array=new int *[10];
for(int i=0;i<10;i++)

{
         array[i]=new int [5];

       memset(array[i],0,5*sizeof(int));

}

可以看到这里的memset的第三个参数有了变化。这是很关键的!这其中的原因是什么呢?我现在也还不太清楚。。。。。大家先照着这么写吧

二维数组的撤销:delete [] array

                            对于二维数组的动态分配与释放

首先,动态支持数组的分配,必须用 new 来进行创建一段堆内存,其它的存贮区域不允许动态分配的产生。

其次,C++并没有提供真正的动态多维数组语法,想动态分配数组,必须通过一维动态数组组合形成一个类似多维数组的存贮形式,并不像静态分配多维数组,它们的用法虽说有些地方有相似之处,但不完全相同。

再次,有些网友有一些很BT的分配方式。

例如:

int (*p)[4] = new int[3][4];

解释:可能有些初学者认为这样是可取的,也是利用 new 分配的数组,就可以是动态的,那你的想法就错了,它的分配必须得有最外层 const 的支持

int x = 3, y = 4;
int (*p)[y] = new int[x][y];//error,y必须是const

所以这种方式不能达到真正的动态分配二维数组的目的,只能相当于半自动化的一个分配方式。

那么如果依靠下面的这种方式是正确的:

例:

int x = 3, y = 4;
int *p = new int*[x];//创建一个动态 int* 型数组
for (int i = 0; i < y; ++i)
p[i] = new int [x]; //再创建一个动态 int 型数组

for (int i = 0; i < y; ++i)
{
delete p[i];//由里至外,进行释放内存。 ---------------我认为是delete [] p[i];!!!!!后来查到的帖子证实
p[i] = NULL;//不要忘记,释放空间后p[i]不会自动指向NULL值,还将守在原处,只是释放内存而已,仅此而已。
}
delete []p;
p = NULL;

它就是依靠一维数组的组合来完成,这样创建的动态数组就是一个全自动的个分配方式。

例(完美废人提供):

void * buf = malloc (x * y * sizeof (int) ); //这也是一个好方法,简单方便,但它是C里面的分配方式。

free(buf);//释放内存简单方便.

提示:千万不要有 int *p = new int[4][2]; 这样的错误写法。

猜你喜欢

转载自blog.csdn.net/weixin_42381351/article/details/84946444