C 语言小结( 自我总结,仅供参考 )

1./(★)/可以通过指针来改变int a 中 a 的值;因为指针指是对其地址的操作... ...(很重要!!!)

2.(★)函数名和数组名一样都是 常量指针 ,只能指向唯一的内存

3.c语言中 static uint8ucState=0类型为占8个bit的无符号整型的静态变量ucState,赋初值为0。

(附加:误区:const 是常量 而stastic是静态变量  是不一样的;前者修饰的量不能改变;后者是累加)


(附加:在给数组初始化时应当要注意的:例如: int num[100] = { 0 }; 也就是赋值所有的元素为 0;但是
     
(★)(★)  如果是这个样子: int num[100] = { 1 }; 结果是num[0] =1; 其余元素为 0  )(★)(★)(★)


4.(★)
         也就是说只要涉及到要用指针来储存值时先必须分配内存!!       
        !!!
        
       如果仅仅是声明一个指针*p,然后让他指向一个struct x;即p=&x;此时无需分配内存                                          

                
      
       

5.数组之间的赋值需要用到strcpy()函数,但是一旦转化为指针,就可以直接是p1=p2;


6.char **和const char **事不一样的,前者指向char *,后者指向const char *
原因:例如:const float *  其实是只想一个具有const限定的float的指针

 

7./####################################################################/
/####################################################################/
/####################################################################/
/@@@@@@@/其实我们平时的声明就是!!!申请内存!!! 例如int i;即申请了一个内存为sizeof(int)大小的内存的房间,房间名字叫  i 

!!!!!!
然后赋值就是给房间住进客官!!!!!!(即赋值是对房间名进行操作,即变量名!!)

8./@@@@@@@/inti=20;  int* p; p=&i;
指针的意义:名称为 i 的房间里住着20,p相当于服务员,一旦申请int* p后(相当于聘请好这个服务员)也必须申请了一处内存(即服务员

站在吧台) 我现在要找 20 ,怎么办? 去吧台找服务员,服务员指着 i 房间说他在那里,然后我就找到了,所以此处的服务员就是一个指

针!!!!!!!!!!!! 他知道 20  的地址,即储存的是 20 的地址 ,所以我才能找到20 这个人!!!
请注意:::::int* p  中 int* 是一个整体!!!!  是声明指针的!符号!  p才是指针变量!!!所以 p 的值是 &20(即20 的地址(房间号))

;  所以要读取20 就必须要读 *p  !!!!!!


9./@@@@@@@/ 可以通过指针改变原 房间中的值  也就相当于 服务员 送走 20后;又迎来 15 客人!!!!!
即:  int i=20; int *p;p=&i;(p是指向i的指针) *p=15;  printf("%d",i);
此时 输出的值是 15 而不是 20 !!!!!(root:可以通过指针改变原 房间中的值)
/####################################################################/
/####################################################################/
/####################################################################/


10.注意细节: int *p;
         int a[]={1,2,3,4,5,6};

要把 a[] 赋值给p ,直接是p=a即可, 因为a是首地址!!!

 

/####################################################################/
/####################################################################/

11.请注意数组名与指针的区别: 数组名只不过是一个const 指针(数组名只是指向首地址!!!如果要取得后面的数就只能是相对于首地址的

偏移量!!!而不能是指针的向后移动!!! 因为他是const指针,而指针变量p可以这样),即是指针常量!!!  而指针本身的意义是指针变

量!!!!!!!!!! 所以又有一种情况是指针能做到而数组名做不到的: 
int *p;
int a[]={1,2,3,4,5,6};
p=a;
int i;
for(i=0;i<6;i++)
{
   printf("%d",p);   
   p++;              
}


偏移量: 例如  *(p+3); *(a+3); a[3]; 三者都是等价的  ^_^

/####################################################################/
/####################################################################/

 


12.指向指针的指针(即二阶指针):
int **p;
int a=10;

我们可以知道 p 是储存地址的地址的变量;所以必有 *p=&a;而p是储存&a(也就是*p) 的地址的; 所以如果调用出 10 就必须是printf

(**p);  而printf(*p)其实是输出 &a的地址  ......

//  好好理解  二阶指针!!!
#include <stdio.h>

void main()
{
 int a=10;
 int*point;     //一维指针 储存 10 的地址
 int**ppoint;   //二维指针 储存 一维指针的地址
   point=&a;
 ppoint=&point;  //此处千万不能是 ppoint=&(&a);之类;  因为此处的 &a已经是常量了;不能对常量取地址;而point是变量;

所以可以...
 printf("%d\n",*point);
 printf("%d\n",**ppoint);
}


void find2(char array[], char search, char **ppa)
    {
           int i;
           for (i=0; *(array + i) != 0; i++)
           {
                   if(*(array + i) == search)
                   {
                      *ppa = array + i;
                      break;
                   }
                  else if(*(array + i) == 0)
                   {
                     *ppa = 0;
                      break;
                   }
           }
    }
ppa指向指针p 的地址。

对*ppa的修改就是对p值的修改。
                        

13.函数指针和指针函数:

函数指针: 例如:void (*fun)(int )

(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)
void MyFun(int x)   //声明
{}

void (*fun)(int );  //声明

void main()
{
   fun=&MyFun;    //把函数地址给函数指针

   fun=MyFun;     //这是什么?哈哈;也是把函数地址给函数指针

   MyFun(10);     //输出结果
    
   (*fun)(10);    //可以这样

   fun(10);       //还可以这样

   (*MyFun)(10);   //竟然还可以这样
 
以上说明了什么?》》》》》函数名就是 指针(★)     

MyFun 的函数名与FunP函数指针都是一样的,即都是函数指针。
MyFun 函数名是一个函数指针常量,而 FunP 是一个函数数指针变量,这是它
们的关系。
 
}

(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)

指针函数: 例如:char *fun(int x) 必须有返回值,而且类型是char * 型


14.
   (1)为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预
    处理块。(★)

    (2)用#include<filename.h>格式来引用标准库的头文件(编译器将从
   标准库目录开始搜索)。
    用 #include“filename.h” 格式来引用非标准库的头文件(编译器将
   从用户的工作目录开始搜索,如果工作目录没有找到,在返回标准库开始搜索)。


15.
/..............#ifndef..#define...#endif............................................./
  
   #ifndef GRAPHICS_H     // 防止graphics.h  被重复引用 (★)(★)(★)
   #define GRAPHICS_H                                   (★)(★)(★)

   #include<math.h>       // 引用标准库的头文件
   …
   #include“myheader.h”  // 引用非标准库的头文件
   …
   voidFunction1(…);     // 全局函数声明
   …
   classBox               // 类结构声明
   {
   …
   };
   #endif
  以上是一个头文件块;是用ifndef/define/endif 结构产生  预处理块(所有声明在一起是一个
   整体);
  
   然后我要生成这些函数或者类;必须在 graphics.cpp中单独写入;
   例如:

   #include"graphics.h"  //声明包含在此头文件中
  
   void Function1()
   {
     printf("hello world");
   }
   class Box  //类结构声明
   {
     public:
           ....
     private:
           ....
     protected:
           ....
   };


   现在我要应用此头文件:
   #include "graphics.cpp"//声明包含在此头文件中
   void main()
  {
    .....
  }

/............#ifndef..#define...#endif..................................................../   
16.头文件的作用

 (1 )通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要
      向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功
      能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
 
(2 )头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中
     的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的
     负担。

 

17.代码格式(注意空格的添加和 {} 的随时添加 !!!)

(a)为风格良好的代码行                 (b )为风格不良的代码行。
 
 int width;  //宽度                      int width, height, depth; // 宽度高度深度

 int height; // 高度

 int depth;  // 深度


 x = a +b;                                X = a + b;   y = c +d;  z = e + f;

 y = c + d;

 z = e + f;


 if (width <height)                      if (width < height) dosomething();

 {

   dosomething();

 }

 

 for (initialization; condition;update)   for (initialization;condition; update)

 {                                             dosomething();

     dosomething();                      other();

 }

 // 空行

 other();

   if 、for 、while等关键字之后应留一个空格再跟左括号 ‘(’,以突出关键字。
  
  ‘,’之后要留空格,如 Function(x, y, z) 。如果‘;’不是一行的结束
   符号,其后要留空格,如 for(initialization;  condition; update)。
   
  赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+= ” “>=”、   “<=”、“+ ”、“* ”、“% ”

、“&&”、“||”、“<<”, “^    ”等二元操作符的前后应当  加空格。 
  
 void Func1(int x, int y, intz);          // 良好的风格

 void Func1 (int x,int y,intz);           // 不良的风格

 if (year >=2000)                         // 良好的风格

 if(year>=2000)                            // 不良的风格

 if ((a>=b)&&(c<=d))                     // 良好的风格

 if(a>=b&&c<=d)                            // 不良的风格

 for (i=0; i<10;i++)                      // 良好的风格

 for(i=0;i<10;i++)                         // 不良的风格

 for (i = 0; I < 10; i++)                 // 过多的空格

 x = a < b ? a :b;                        // 良好的风格

 x=a<b?a:b;                                // 不好的风格

 int *x =&y;                              // 良好的风格

 int * x = &y;                            // 不良的风格

 array[5] =0;                             // 不要写成 array [ 5 ] = 0;

 a.Function();                             // 不要写成 a . Function();

 b->Function();                            // 不要写成 b -> Function();


18. 命名 规则
 
   匈牙利命名规则的主要思想:

   是“在变量和函数名中加入前缀以增进人们对程序的理解”。例如所有的字符变量均以

    ch为前缀,若是指针变量则追加前缀p。如果一个变量由ppch 开头,则表明它是指向字

   符指针的指针。

   “匈牙利”法最大的缺点是烦琐,例如

   int   i,  j,  k;

   float  x,  y, z;

    倘若采用“匈牙利”命名规则,则应当写成

   int   iI,  iJ, ik;   // 前缀 i 表示 int 类型

   float  fX,  fY, fZ;   // 前缀 f 表示 float 类型


共性:
(1)
Windows  应用程序的标识符通常采用“大小写”混排的方式,如 AddChild。
而 Unix  应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两混在一起使用!

(2)
尽量避免名字中出现数字编号,如 Value1,Value2等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生

无意义的名字(因为用数字编号最省事)。 

(3)
1>类名和函数名采用大写字母开头的字母组合而成,例如:class TreeNode; voidSetValue();
2>变量和参数采用小写字母开头,组合名的后面字母仍用大写字母 例如:inttreeNode;  BOOL flag;
3>所有常量都用大写字母和下划线的组合 例如:const int MAX; const intMAX_LENGTH;
4>静态变量前都加上s_;然后的命名和变量时一样的 !!!例如:static ints_treeNode;
5>全局变量前都加上g_(global) 例如:intg_treeNode;  (一般情况下不用(少用)全局变量)
6>类的数据成员前加上m_(member)以示区别


19.写if 语句与零值比较
(1):BOOL值 例如:bool flag;   if(flag)//flag 为真 和  if (!flag) //flag 为假
       其它的用法都属于不良风格,例如:
       if (flag == TRUE)   //注意空格
       if (flag == 1)    //注意空格   
       if (flag == FALSE)  //注意空格
       if (flag ==0)     //注意空格
(2):整形与零值的比较
       if (curentValue == 0)  //注意空格
       if (curentValue != 0)  //注意空格
(3):浮点值与零值的比较
       if ((curentValue > -0.000001)&& (curentValue <0.000001))
       千万不能直接与0.0之类比较; if (curentValue==0.0)  //错误
       要转化为:if((curentValue >-EPSINON)&& (curentValue<EPSINON))
       其中 EPSINON 是允许的误差(此处是0.000001)
(4):指针变量与零值的比较
       定义一个指针变量 p
       if (p == NULL)  //不要写成 p ==0   //注意空格
       if (p != 0)
       
       不要写成:
       if (p == 0 )
       if (p != 0)
       if (p)
       if (!p)

(5):重点补充:有时候把 if (p == NULL) 写成 if (NULL ==p)   其实是防止把if (p == NULL)写成
         if (p = NULL) 而导致出错;编译器编译时 if (p = NULL)是不会报错的,但是如果写成
         if (NULL = p) 是会报错的...(值得学习!!!) 

(6): 不良风格:
         if (condition)  //注意空格 
            return x;
         return y;
    改写后:
        if (condition)   //注意空格
        {
           return x; 
        }
        else
        {
           return y;
        }
    或者:
        return (condition ? x : y);  //注意空格

20. for 语句
       (1):在多重循环中,如果有可能,将最大循环放在最里面!in fact 为了减少 CPU的跨切循环层        的次数        
       (2):如果循环体内存在逻辑条件判断,最好将它移到循环外面:
       例如:
       for (i = 0; i <= N;i++)           if(condition)
       {                                  {
          if(condition)                     for (i = 0; i <= N;i++)         
          {                                  {  
             DoSomething();                     DoSomething();  
          }                                  }
          else                            }
          {                               else
             DoOtherthing();              {
          }                                  for (i = 0; i <=0; i++)
       }                                     {
                                                    DoOtherthing();  
                                              }
                                           } 
 
      如果 N 不大 ;那么两个执行效率差不多,可以用左边的;如果 N 很大 那就用右边的
      (3):最好采用半开半闭的方法
          将上面语句改成:
          for (i = 0; i < N+1; i++)
          {
               ... ...
          }
     
21. switch语句: 结尾的 default 一定要加上;以防别人误以为你忘记 default 的处理

 

22.
   函数方面:
   (1).书写要完整:例如: voidTreeNode(int x, int y);
                         void TreeNode(int , int );  //不良写法
                         
                         int value(void);
                         intvalue();       //不良写法
  
  (2).对于指针作为参数的处理,如果只做输入用,最好在前面加上 const ;避免被无意修改!!!
   
      例如: char *Strcpy(char *strCopyTo, const char *strCopyFrom);

  (3).如果输入参数以值传递的方式传递对象,则宜改用“const &“方式来传递,这样可以省去临时对      
      象的构造和析构过程,从而提高效率。 (★)(★)(★)(不懂!!!!!!)

   (4).尽量不要使用类型和数目不确定的参数。C标准库函数 printf 是采用不确定参数的典型代表,     
      其原型为:int printf(const chat *format[, argument]…);这种风格的函数在编译时丧失了严    
      格的类型安全检查。  (★)(★)(★)(不懂!!!!!!)

  (5).有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。 

      例如字符串拷贝函数strcpy  的原型:

      char *Strcpy(char *strDest,const char*strSrc);   //格式

      strcpy  函数将strSrc 拷贝至输出参数strDest  中,同时函数的返回值又是 strDest 。这样做
      并非多此一举,可以获得如下灵活性:

      char str[20];

      int  length = strlen( strcpy(str, “Hello World”)); //经典
      
   (6).关于函数的 return 语句(★)(★)(★)
      
      1> return 语句不可以返回指向栈内存的指针或者引用 ,因为该内存在函数结束时自动销毁
         例如:  char *Fun(void)
                 {
                     char str[] = "hello word";  //str 内存位于栈上
                     ...
                     returnstr;                 //错误
                 }
     
     2> 要尽量提高函数的执行效率
        例如:
                return String(s1 + s2);  和  temp= String(s1 + s2); return temp;
                
                两者的执行效率是不同的!! 前者更好!!前者是创建一个临时的对象并返回它。
                
                对于后者:首先,temp 对象被创建,同时完成初始化;然后拷贝构造函数把temp 拷贝
             
                到保存返回值的外部存储单元中;最后,temp 在函数结束时被销毁(调用析构函数)
                
                。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建
              
                并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。
          
                类似的 我们 要写:return (x + y); 而不是:temp = x + y; returntemp;  

     3>(★)
       
        程序一般分为 Debug 和 Release 版本,Debug 版本用于内部测试;Release版本用于发行给用        
        户。 断言( assert ) 的使用时很重要的!!!一般在函数的入口处最好用断言来判断参数的可
       
        行性。而且 assert 只在 Debug 起作用,是一种宏结构,不是一种函数,是为了避免对我们的
        
        函数主题产生不必要的影响。assert 的作用是:只要其条件不满足,就会终止程序的运行!!

        例如:
            char *Copy(char *copyTo, const char *copyFrom)
            {
                assert((CopyTo != NULL) &&(CopyFrom != NULL)); // 使用断言
                
                byte *to=(byte*)copyTo;     //防止改变地址
                 
                byte *from=(byte *)copyFrom;  //防止改变地址

                while(*to++ ==*from++);   
        
                return copyTo 
            }
               
      4>(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)
        (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)

         引用与指针的比较 :( 经 典 )

         引用:n是m的一个引用,m是被引用物;int m ; int &n = m;//好好看看它的形式哦,( ⊙o⊙
               )哇,怎么会是这样啊,?这样也行?O(∩_∩)O~;例如一个人叫 m;现在他有一个绰号               叫 n ;我们叫 n

;其实也就是在叫 m ;其实 n 就是 m;m 就是n;是同一个人;所以对                n的处理也就是对 m 的处理 ! ! ! ! ! ! (真的要

注意哦对n的处理就是对 m 的处理 )

         注意点:(1).引用在被创建的同时必须被初始化(必须的哦)
                 
                 (2).一旦被初始化,就不能改变引用的关系咯
                   
                 例如:int iI =5;    //注意  空格  命名法则( 匈牙利 )
                       
                       int iJ = 10;
                       
                       int &iK = iI;  //引用参数的定义以及初始化

                       iK =iJ;       //此时 iK 的值改变咯(知道 iK 其实就是 iI )
                                       //所以此时就是相当于 iI 改变了
                       printf("%d\n", iK); ----> 10

                       printf("%d\n", iI); ----> 10

                       printf("%d\n", iJ); ---->10  

        引用传递参数的小例子:
                       void Add(int &x)  //引用传递的 处理像是指针的处理
                       {
                           x += 1;
                       } 
                       
                       void main()
                       {
                           int x;
                           
                           Add(x);   //引用传递的 传入 像是值传递

                           printf("%d\n", x);  
                       }            

       (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)
       (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)                         
               
23. (深入研究 内存 问题 )
   
   欢迎进入内存这片雷区。伟大的 Bill Gates 曾经失言:

    640Kought to be enough for everybody

                                           — Bill Gates  

   23-1.内存分配的方式:
                 (1).从静态储存区域分配:内存在程序编译时就已经分配好了,这块内存在程序整个                     
                     运行期间都是存在的。例如:全局变量;static 变量
               
                 (2).在栈上创建:在执行结束时被自动释放;
                     例如之前的一段程序:
                     
                     char *String(void)
                     {
                         char str[]="hello world";  // 在栈上创建的
        
                         ... ...

                         returnstr;               // 错误 错误 错误 错误 错误 错误 错误
                     }                             //内存在程序结束时被自动释放

                 (3).在堆上创建:也就是所谓的动态分配(malloc 或者 new 申请内存单元);程序员

                     自己决定何时释放( free 或 delete )
   23-2.常见的错误:
                 (1).内存未分配成功却使用了它,解决方法:在使用前检查 指针 是否为 NULL;
                  
                     如果指针 p 是参数;那么可以 用断言 assert(p != NULL) 来判断;

                     如果用 malloc 和 new 申请内存,必须用if (p == NULL) or if (p != NULL)
              
                     来防止出现错误!
                 
                 (2).分配成功;但是未初始化就使用了它
 
                 (3).操作超过内存边界

                 (4).请注意:自己经常犯的错误:在动态分配时要时刻牢记的不仅是申请内存;更重      
                     要的是释放内存!!! 规则:malloc 和 free次数必须相同;new 和delete次              
                     数必须相同;
                 
                 (5).释放了内存却还在使用它:
                  
                     典例1: 栈指针或者栈引用的返回问题 : 在程序执行完后,内存被自动释放;所

                             以是不可以将栈内存的指针或引用返回的! ! ! ! !!  
                             
                     典例2: 动态分配后,用free和new 释放了内存;但是指针没有设置为 NULL ;
                             导致指针成为野指针   


  【规则1】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
             
   【规则2】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1 ” 操作。
      
  【规则3】动态内存的申请与释放必须配对,防止内存泄漏。

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

  23-3.
     指针与数组对比:( 宏观上 )(★)(★)(★)(★)(★)(★)(★)(★)(★)
                   
               数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而

               不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变(此
               句话说明或者照应了我以前的说法:数组名实际上就是一个 const 指针,特点是:指向
               唯一内存;但是内存中的值可以改变;函数名本质上也是指针;具体的请看以前的...

               O(∩_∩)O~)。 指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们

               常用指针来操作动态内存。指针远比数组灵活,但也更危险。

    例如:    char str[] = "hello";  //在栈内存中的分配
        
               char *p ="world";    //实际上是位于静态存储区 相当于指针 p 指向的是const
                                      //即:const char *p;
               str[0] = 'X';

               p[0] ='X';           //由以上可知此句是错误的;const 是不能改变的额

               puts( str );
 
               puts( p );

  23-4.计算内存容量:
           (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)
                  
      (1)int str[100]; 用sizeof();来计算,输出的是 100 ;现在有一个指针p指向 数组str;

      计算 sizeof( p ); 输出来的结果是 4 ( 很诧异吧,O(∩_∩)O哈哈~ );

      实际上sizeof计算的是 sizeof( char * ) == 4;C 和 Cpp都是不能知道指针所指的内存容量的 

      除非在分配内存的时候记住它;所以sizeof( 指针) == 4 ; 所以计算内存不能用指针计算!!!

      (2)一个典例:
      
                   int TreeNode( char str[100] ) 或者   void TreeNode( char str[100])
                   {                                    {
                       return(sizeof(str));                 printf("%d\n",sizeof(str));  
                   }                                    }
 
                   以上两种情况输出的结果都是 4;哇塞,不会吧;会的;因为此处的数组已经退化               
                   为指针咯

      (3)注意:例如:  char str[]="hello"; printf("%d\n", sizeof(str));
                 请问输出的结果是多少呢?   对了是 6 ;请不要把 '\0'不当人!!!我靠!!!
       
      (4)还要注意:sizeof() 是计算 内存大小的 ;而 strlen() 是计算实际字符串大小的哦


  23-5. 指针参数如何传递的?

       编译器在编译时总是要给每个参数制作一个副本;指针参数 p 的副本是 _p ;执行函数时;副本
 
       _p 的改变就是 指针 P 的改变;

       典例:
                 void GetMemory(char *p, int num)
                 {
                      p = (char *)malloc(sizeof(char) *num)      
                 }  

                 void main()
                 {
                      char *str = NULL;

                      GetMemory(str, 100);

                      assert(str != NULL);

                      strcpy(str, "hello,world");

                      puts(str);  

                      free(str);   //重要重要重要重要重要重要重要重要重要重要重要
                 }
      貌似这个程序非常正确;其实是从根本上错了;str 根本就没有分配到内存;不信就“断言”吧;
   
      其实想想也是很简单的;不就是相当于要为   str 分配内存吗?刚开始str中是NULL;现在我要
      
      改变它就是了;那么不就可以通过指向 str 的指针来改变吗? 所以不就可以定义一个指向指针的
     
      指针来处理吗?
     (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)
      简单一句话-:如果参数是指针;请不要指望让它去申请内存;  
     
      总之如果要用 函数 来实现给 字符串 分配内存;请不要传递实参 str 即不要写成形参是指针 *p
  
      形式;不要指望它;要找就找 &str 和 **p 他们可以做到为你分配内存 !!!
      
     void GetMemory(char **p, int num )
     {
        *p = (char *)malloc(sizeof(char) * num);
     }
     
      
     void main()
     {
        char *str = NULL;

        GetMemory(&str, 10);

        assert(str != NULL);

        strcpy(str, "hello");

        puts(str);

        free(str);//重要重要重要重要重要重要重要重要重要重要重要
    
        str = NULL;  //好习惯
     
     }

    另类形式:
      
     char *GetMemory(int num)
     {
          p = (char *)malloc(sizeof(char) * num);

          returnp;    
     }  
  
     void main()
     {
        char *str = NULL;

        str = GetMemory(str, 10);

        assert(str != NULL);

        strcpy(str, "hello");

        puts(str);

        free(str);//重要重要重要重要重要重要重要重要重要重要重要

        str = NULL;  //好习惯
     }

    (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)

  
    这里还强调 return 函数的问题;;;
  
    例如:    char*GetChar()
              {
                  char str[] = "hello";  //此处是 栈 内存;程序结束时自动消亡

                  ...

                  return str;  //错误错误错误错误错误错误错误错误错误错误错误错误错误错误
              }

              void  main()
              {
                  char *str;

                  str = GetChar();  //得到的是  乱码

                  puts(str);
              }
 

  23-6.
      探讨 free 和 delete 把指针怎么了???
    
      O(∩_∩)O哈!  它们只不过把指针的内存给释放掉咯;但是并没有把指针本身给干掉((★))

      所以这个指针本身还是存在的;发现指针 p 被 free  以后其地址仍然不变(非NULL),只是

      该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把 p 设置为 NULL ,会让人误

      以为 p 是个合法的指针。进而导致错误;而且此时如果用  if (p != NULL) or if(p == NULL)
      
      都是判断不了的 !!!所以切记:::::free or delete 之后必须要使之指针为NULL

      (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)

       切记:在 free 和 delete 指针之后;必须把指针赋值为 NULL ;防止指针成为 “野指针” !

      (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)

  23-7.
     切记:动 态 内 存 是 不 会 被 自 动 释 放 的... ...
     
     只要你的整个的大的 main() 函数还在执行;不管是那个局部的多么少的动态分配都不会自动释放

     ;必须的是我们自己 free() 或者 delete() 它们;并且要赋值指针为 NULL;防止变为野指针!
          

     ( 请不要偷懒或者对这个问题不以为然;会出大问题的!!!靠... ... )

      》》》》》》指针死了,并不代表它的内存回收咯;内存释放咯,也并不代表指针死了(可能变野          
                  指针咯);
     
      》》总结: 释放内存  和  让指针变为NULL是不可能同时达到目的的;但是我们又是必须要做的
 
 
 23-8.    
       ————》请杜绝野指针
                         
                            ------By  pt
       
      首先我们来看一下什么要的指针叫“ 野指针 ” ;野指针,顾名思义,是没有人需要的指针,
  
      或者说你人们害怕的指针!请不要把野指针和和 NULL 指针混淆, NULL指针 可不是野指针

   之前的 23-6. 也提到了野指针,但那时候重点讲的是 要 free (or delete) 内存,现在
 
     重点讲野指针:
    
                 野指针的产生:
                             1. 在定义指针的同时没有初始化指针,这时在使用时它就会乱指一气;

                                所以在定义时要将指针初始化;可以是 NULL 或者 指向...

                                    char *p = NULL;
                                或者
                                    char *p = (char *)malloc(100);
                                                            
                             2.就是之前所说的 free 和 delete 之后没有赋值指针为 NULL;(注意)
                             
                             3.指针操作超过了变量作用的范围
 
                               例如:
                                 class A

                                {

                                 public:

                                     void Func(void){ cout << “Func ofclass A” << endl; }

                                };

                                void Test(void)

                                {

                                     A  *p;

                                         {

                                          A  a;

                                          p =&a;    // 注意 a 的生命期

                                          p->Func();    // p 是正常指针

                                         }

                                     p->Func();    // p 是“野指针”

                                }

                
  23-9.
      free 和 malloc  与  delete 和 new之间的差别
   
      free 和 malloc 是库函数,但是 delete 和 new 不是库函数;对于一个外部的对象而言,它在创
      
      建时要执行 构造函数,在消亡时要执行 析构函数;但是free 和 malloc 是库函数是库函数,它
      
      们不在编译器的控制范围之内,不能把执行构造函数和析构函数的任务强加给 它们,所以就出现
     
      了delete 和 new ;理论上讲:delete 和 new 对于内部数据处理时也能代替free 和 malloc,但
      
      是由于 C 中只能是free 和 malloc,所以free 和 malloc是不能被遗忘的......                 
  23-10.
       内存耗尽怎么办???-----》也就是说我在申请动态内存时没有那么大的内存咯,返回了NULL
                                  
                                  我该怎么办???

       处理 1.
              判断 if ( p = NULL )
                   {
                       return       // 可以用return 语句返回
                   }
       处理 2.
              判断 if ( p = NULL )
                  {
                       cout << “Memory Exhausted”<< endl;
                          
                       exit(1);    // 马上用exit(1)杀死整个应用程序
                  } 

 
  附录:关于 exit(1) 和 exit(0)我在这里也要解释一下,其实在正常没有返回值的情况下他们两是完
       全一样的》》》都是杀死应用程序;当时如果有返回值是,exit(0) 表示的是 非正常情况下的结
       束,而 exit(1),或者 exit(2) 等非 0 的数都可以表示正常结束应用程序,一般都用exit(1);
            
  不过现在对于 32位以上的应用程序来说,基本上是不可能内存耗尽的,因为即使内存耗尽了,”虚存
 
“可以帮我们忙,自动用硬盘空间来顶替... ...
 
------》 
  个人建议最好用exit(1)杀死整个应用程序


  23-11.
       new 的使用 比malloc要简单多了
  
       例如:   int *p = (int*)malloc(sizeof(int) * num);
                    
                int *p = new int[num];
       例如:
                #include <iostream>
 
                using namespace std;

                void main()
               {
         int *pi = new int[10];   //用 new申请内存更方便
 
         int i ;
 
         for (i = 0; i < 5; i++)
         {
     cin >> pi[i]>> endl;
     //scanf("%d\n",&pi[i]);
         }

         for (i = 0; i < 5; i++)
         {
     cout << *(pi + i)<< endl;
         }

                deletepi;    //释放内存  挂嘴边
               }

     在 C++的类中,对于不同的对象的处理是不同的,所以会有不同的 内存的申请方式
     
     ---》  class  Obj
            {... ...};
         
            Obj *obja = newObj;    //声明一个
 
            Obj *objb = new Obj(1);  //... 外加赋值为1 ;            
    
    (★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)(★)  
    
     如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。例如

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

     不能写成:

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

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

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

     deleteobjects;    //错误的用法

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

 

  23-12.
       (★)(★)(★)(★)
 (★)(★)(★)(★)
 #ifndef BOOKSTORE_H  //有可能一个源文件中包含了两个以上此头文件, 这时防止重复处理相同的头文件
       #define BOOKSTORE_H

       

       #endif

条件指示符#ifndef 检查BOOKSTORE_H 在前面是否已经被定义 这里 BOOKSTORE_H

是一个预编译器常量  习惯上预编译器常量往往被写成大写字母 如果BOOKSTORE_H

在前面没有被定义 则条件指示符的值为真 于是从#ifndef 到#endif 之间的所有语句都被包

含进来进行处理 相反 如果#ifndef 指示符的值为假 则它与#endif 指示符之间的行将被忽

   为了保证头文件只被处理一次 把如下#define 指示符

    #defineBOOKSTORE_H

    放在#ifndef后面 这样在头文件的内容第一次被处理时 BOOKSTORE_H 将被定义

   从而防止了在程序文本文件中以后#ifndef 指示符的值为真

   只要不存在两个必须包含的头文件要检查一个同名的预处理器常量 这样的情形 这

   个策略就能够很好地运作

   #ifdef指示符常被用来判断一个预处理器常量是否已被定义 以便有条件地包含程序代


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值