1、 什么是定义,什么是声明?
定义:
编译器
创建一个对象,并为这个对象分配一块内存,给它取上一个名字。
声明:1>告诉编译器这个名字已经已经分配到一块内存上了
2>告诉编译器这个名字已经被预定了,别的地方不能再用它来作为变量名或对象名。
2、 auto:
在缺省的情况下,编译器默认所有的变量都是auto的,
3、 register:
register变量必须是单个的值,并且其长度应该小于或等于整形的长度,而且register变量可能不存放在内存中,所以不能用&—-取地址符来获取register变量的地址。
4、 static:在C语言中有两个作用
1> 修饰变量:变量又分为
局部变量
和
全局变量
但都存在内存的静态区。
静态全局变量:作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法使用它。或者说它的作用域从定义之处开始到文件结尾处结束。
静态局部变量:在函 数体中定义,只能在这个函数中使用,由于是存在静态区,所以函数运行结束该变量也不会被销毁,下次使用仍能用到这个值。
2> 修饰函数:此处static不是指存储方式,而是指函数的作用域仅限于本文件。
所以又称内部函数。
5、 不同类型的数据之间的运算要注意精度扩展问题,一般低精度数据向高精度数据扩展。
6、 柔型数组:
结构体中的最后一个元素允许是未知大小的数组,这就叫做柔型数组的成员。
但结构中的柔性数组成员前面必须至少包含一个其他成员。
柔型数组成员允许结构体中包含一个大小可变的数组,
柔型数组成员只作为一个符号地址存在,而且必须是结构体最后一个成员。
Sizeof
返回的这种结构体大小不包括柔性数组的内存,
柔型数组成员不仅可以用于字符数组,还可以是元素为其他类型形的数组。
包含柔型数组成员的结构用malloc函数进行内存的动态分配。并且分配的内存应大于结构的大小,以适应柔型数组的预期大小。
7、 signed char 范围-128~127
unsigned char 范围 0~255
-1 补码 1111 1111
-2 补码 1111 1110
-3 补码 1111 1101
.
.
.
-127 补码 1000 0001
-127绝对值为127:0111 1111 取反加一 1000 0001
-128 补码 1000 0000
-128绝对值为128:1000 0000 取反加一 0111 1111+1=1000 0000
-129补码 0111 1111
-129绝对值为129:1000 0001 取反加一 0111 1110+1=0111 1111
.
.
.
-255补码0000 0001
-255绝对值为255:1111 1111 取反加一 0000 0000 +1=0000 0001
-256补码0000 0000
-256绝对值为256:1 0000 0000取反加一
0 1111 1111 +1=1 0000 0000
-257 补码
-257绝对值257:1 0000 0001取反加一
0 1111 1110+1=0 1111 1111
8、
intmain {intj = -20;//11111111 11111111 11111111 11101100unsignedinti =10;//00000000 00000000 00000000 00001010unsignedk=i + j;//11111111 11111111 11111111 11110110(但是这是一个无符号数)=4292967286system("pause");return0; }
9、+0和-0在内存里怎么存储?
-0 原码:1000 0000
反码:1111 1111
补码:1 0000 0000
+0 原码:0000 0000
反码:1111 1111
补码:1 0000 0000
10、case后面只能是整形或者字符型的常量,或者常量表达式。
11、for语句的控制表达式不能包含任何浮点类型的对象
舍入误差和截取误差会通过循环的迭代过程传播,导致循环变量的显著误差
12、在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理
13、在C++中,参数为void的意思是这个函数不接受任何参数。
14、不能对void指针进行算法操作。
15、return语句不可以返回指向“栈内存”的“指针”,因为该内存在函数结束时自动销毁。
16、const在C和C++中的区别:
在C语言中:
1》 const修饰的是只读的变量,并不是一个常量(因此不可以做数组下标)
2》 节省空间,避免不必要的内存分配,同时提高效率
编译器通常不为普通const只读变量分配内存空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
3》 修饰一般变量,可以用在类型说明符前,也可以在类型说明符后面。
4》 修饰数组,用法同上
5》 修饰指针。离哪个近就修饰谁
6》 修饰函数的参数。不希望这个参数在函数体内被意外改变时用
7》 修饰函数的返回值,表示返回值不能改变。
Const和#
define
的比较:
1》 Const定义的只读变量从汇编的角度来讲,只给出了对应的内存地址,而不像是#define给出了立即数,所以const定义的只读变量在程序运行过程中只有一份备份(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个备份。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译时确定其值的。
2》 #define宏没有类型,而const修饰的只读变量有特定的类型。
在C++语言中:
1》 const修饰形参,一般和引用同时使用
2》 const修饰返回值
3》 const修饰类数据成员,必须在构造函数的初始化列表中初始化
4》 const修饰类成员函数,实际修饰隐含的this,表示在类中不可以对类的任何成员进行修改(将const加在函数后面)
5》 在const修饰的成员函数中要对类的某个数据成员进行修改,该数据成员定义声明时必须加mutable
问题:
1》 const对象可以调用非const成员函数(不可以)和成员函数(可以)吗?
2》 非const对象可以调用非const成员函数(y)和成员函数(y)吗?
3》 Const成员函数内可以调用它的const成员函数(y)和非const成员函数(n)吗?
4》 非Const成员函数内可以调用它的const成员函数(y)和非const成员函数(y)吗?
Const在C和C++中最大的不同:
在C中const默认具有外部链接,而C++中则是内部链接。
内连接:也就是说它只能在定义它的文件中使用,连接时其他编译单元看不见他。
外链接:就可能导致同一个变量在不同的CPP文件中都分配了地址
17、volatie—是易变不稳定的意思。
用它修饰的变量表示可以被某些编译器未知的因素修改,可以保证对特殊地址的稳定访问。
18、内存地址的最小单元为1个字节,空结构体的大小为1个字节。
19、在C++里struct的成员默认是public,而class成员默认是private
20、在C++里union的成员默认为public,union主要用来压缩空间,如果一些数据不可能在同一时间同时被用到,则可以使用union。
21、大小端:对于一个由2字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将底序字节存储在起始地址,称为小端模式;另一种方法是将高序字节存储在起始位置,称为大端模式。(一般先读取高地址存储的数据)
大端模式(Big-endian):字数据的高字节存储在低地址中,而字数据的低字节存储在高地址中。
小端模式(Little-endian) : 字数据的高字节存储在高地址中,而字数据的低字节存储在低地址中。
22、判断当前系统的大小端:
intCheckSystem { unioncheck{inti;charch; }c; c.i =1;returnc.ch ==1; }intmain {intret = CheckSystem;return0; }
23、位域:
1》位域是指信息在存储时,并不需要占用一个完整的字节,而只需占用几个或者一个二进制位。
2》所谓“位域”是把一个字节中的二进位划分为几个不同区域,并说明每个区域的位数。每个区域有一个位名,允许在程序中按照域名进行操作。这样就可以把几个不同的对象用一个字节的二进制与来表示。
24、枚举与#define宏的区别:
1》#define宏是在预编译阶段进行简单题替换的;枚举常量编译的时候确定其值的。
2》一般在调试器里,可以调试枚举常量,但是不能调试宏常量。
3》,枚举可以一次性定义大量相关的常量,而#define宏一次只能定义一个。
25、typedef:是给一个已经存在的类型取一个别名。
——再把typedef和const放在一起看看:
typedefstructstrudent {inta; }Stu_st,* Stu_pst;
这里1》const Stu_pst stu3; 2》Stu_pst const stu3; 以前说过const int i 和 int const i 完全一样。const 放在类型前类型后都可以 但是const int* p 和 int* const p则完全不一样(一个修饰指针指向的内容;一个修饰指针)
26、
27、单引号、双引号
1》双引号引起来的都是
字符串常量
;单引号引起来的都是
字符常量
。
2》’a’ 和 “a”则完全不一样,在内存里前者占1个字节,后者占2个字节(还有一个\0)。
3》再看看这个例子:1,‘1’,“1”
1》第一个1是个整形,32位系统下占4个字节。
2》第二个1是字符常量,占1个字节。
3》第三个1是字符串常量,占2个字节。
28、逻辑运算符:“||” 和 “&&”
1》“||”只要有一个条件为真,结果就为真
2》“&&”只要有一个条件为假,结果就为假。
3》例如:
inti=0; intj=0;if(++i0|| ++j0){ //打印出i和j的值(i=1;j=0),,因为先计算++i>0,发现结果为真,后面的就不再计算。 }
29、位运算符:“|”,“&”,“^”,”~”,”>>”,”<<”
1》位操作需要用宏定义好后再使用。
2》如果位操作符“~”和“<<”应用于基本类型无符号字符型或无符号短整形的操作数,结果会立即转换成操作数的基本类型。
3》位运算符不能用于基本类型是有符号的操作数上。
4》一元减运算符不能用在计本类型无符号的表达式上,除非在使用之前对两个操作数进行大小判断,且被减数必须大于减数。
30、左移、右移
1》0x01<<2+3;//+优先级高于<<,所以结果是32
2》0x01<<2+30 或者 0x01<<2-3
3》上述两个操作均会报错,因为一个整数长度为32位,左移32位发生溢出,右移-1位也会溢出。
4》所以左移和右移的位数,不能大于和等于数据的长度,不能小于0.
31、++、–
1》++、–作为前缀,先自加或者自减,再做其他运算。
2》逗号表达式,i在遇到每一个分号后,认为本计算单元已经结束,i这个时候自加。
intmain {inti =2;intj =0; j = (i++, i++, i++);return0; }
最后:i=2+1+1+1=5;j=2+1+1=4.
3》也就是说后缀运算是在本单元计算结束后再自加自减的。
intmain {inti =2;intj =0; j = (i++)+(i++)+(i++);return0; }
最后:j=2+2+2=6;i=2+1+1+1=5;
32、
33、贪心算法:
C语言有这样一个规则,每个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是:从左到右一个一个读入,如果该字符可能组成一个符号,那么再读入下一个时,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,知道读入的字符组成的字符串已经不再可能组成一个有意义的符号。
需要注意的是:除了字符串和
字符常量
,符号的中间不能嵌有空白(空格、制表符、换行符等)。
34、
35、预处理指令:
36、用define宏定义注释符号
看下这个例子:
#define BSC //
#define BMC /*
#define BMC */
1>BSC my single-line comment 2>BMC my multi-line comment EMC
1和2都错了,为什么呢?因为注释先于预处理指令被处理,当这两行被展开成“//…” 或者 “/* ……/”时,注释已经处理完毕,此时再出现“//…” 或者 “/……*/”时自然错误。因此,试图用宏开始或者结束一段注释是不行的。
37、注意:函数宏被调用时是以实参代换形参,而不是“值传送”。
38、#undef
#undef是用来撤销宏定义的,也就是说宏的生命周期从#define 到 #undef结束。
39、文件包含:
1》它实际上是宏替换的延伸。有两种格式:#include 和 #include ” filename”
2》第一种,表示预处理到系统规定的路径中去获得这个文件 ,找到文件后,用文件内容替换该语句。
3》第二种,双引号表示预处理应该在当前目录中查找文件名为filename的文件;如没有找到,则按系统指定的路径信息搜索其他目录。找到文件后,用文件的内容替换该语句。
4》需要注意:#include是将已存在文件的内容嵌入到当前文件中,
5》另外,include支持相对路径
40、#error预处理
1》#error
预处理指令
的作用是:编译程序时,只要遇到#error就会生成一个编译错误提示信息,并停止编译
2》其语法格式: #error error-message 注意 error-message 不用双引号包围
41、#line预处理
1》#line的作用是改变当前行数和文件名称,他们是在编译程序中预先定义的标识符
2》命令的基本形式:#line number[“fliename”] ,例如 #
line
30 a.h,其中文件名 a.h可以省略不写。
42、#pragma预处理:设定编译器的状态,或者指定编译器完成一些特定的动作
1》#pragma message :能够在编译信息输出窗口中输出相应的信息,他的用法:#pragma message(“消息文本”)
例如:
#ifdef _X86
#pragma message(“_X86 macro activated”)
#endif
当我们定义了这个宏后,编译器就会在窗口提示这个信息,就不用再但心是否定义过这个宏了没
2》#pragma code_seg:能够设置程序中函数代码存放的代码段,开发驱动程序的时候会使用到
例如:
#pragma code_seg( [“section-name”[,”section-class”] ] )
3》#pragma once:在头文件的最开始加入,可以保证头文件只被编译一次
4》#pragma hdrstop:表示预编译头文件到此为止,后面的头文件不进行预编译。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A进行编译,你可以用#pragma startup指定编译优先级,
5》#pragma resource:#pragma resource “. dfm”表示把
.dfm文件中的资源加入工程。
.dfm包括窗体外观的定义。
6》#pragma warning
例如:#pragma warning (disable:4507 34; once: 4385;error:164)
等价于:#pragma warning(disable; 4507 34) //不显示4507 和34号警告信息
#pragma warning(once : 4385) //4385号警告信息只显示一次
#pragma warning(errror:164) //吧164号警告信息作为一个错误
7》#pragma comment:该指令将一个注释记录放入一个对象文件或可执行文件中。
例如:#pragma comment( lib . “user32.lib”),该指令用来将user32.lib库文件加入到本工程中。
8》#pragma pack(n):设置内存对齐数
43、“#”运算符:
例如: #defien SQR(x) printf(“The
square
of x is %d\n”, ((x)*(x)) )
输入:SQR(8)
输出: The square of x is 64
这里的x并没有被替换,要修改这种情况,可以加上#
例如: #defien SQR(x) printf(“The square of “#x” is %d\n”, ((x)*(x)) )
输入:SQR(8)
输出: The
square
of 8 is 64
44、“##”运算符:可以用于函数宏的替换部分,可以把两个语言符号组合成单个语言符号。
例如;#
define
XNAME(n) x##n
如果这样使用宏:XNAME(8)
则展开就会是: x8
45、注意:在单一的宏定义中,最多可以出现一次“#”或者“##”,但“##”不能随意粘合任意字符,必须是合法的C语言标识符
46、指针和数组的定义雨声明:
注意:定义和声明的区别:定义分配内存、而声明没有;定义只能定义一次,可以声明多次
1》定义为数组,声明为指针:
extern char* arr;——编译器认为a是一个
指针变量
,占4个字节。
1》0x00D28000这个地址并没有用到,而是编译器按照int类型的取值方法一次性取出前4个字节的值,得到0x64636261
2》地址0x64636261上的内容,按照char类型读/写。但是这个地址并非是一个有效地址。退一步即使是一个有效地址那也不是我们想要的。
3》应该用 (char*)&arr 取出“abcdef\0”的值
2》定义为数组,声明为指针
在文件1中,编译器分配4字节空间,并命名为arr;同时arr里保存了字符串常量“abcdef”的首字符的首地址,这个字符创常量本身保存在内存的静态区,其内容不可以更改。
在文件2中,编译器认为arr是一个数组,其大小为4个字节,数组里保存的是char类型的数据。
1》指针arr里保存的是
字符串常量
的地址,0xac588100,而arr本身的地址(0x00818000)并没有用到。
2》编译器把指针便令arr当做一个包含4个char类型数据的数组来使用,按char类型取出arr[0],arr[1],arr[2],arr[3]的值为0xac,0x58,0x81,0x00
但这个并非我们所要的某块内存的地址。如果给arr[i]赋值会把原来arr中保持的真正地址覆盖,导致再也无法找出其原来指向的内存。
3》应该用 (char*)
(int
)arr 取出“abcdef\0”的值。
注意:以后一定要确认你的代码在一个地方定义为指针,在另一个地方只能声明为指针;在一个地方定义为数组,在另一个地方只能声明为数组。47、指针和数组的特性
48、指针数组和数组指针
1》指针数组:( int *p1[10]; )首先它是一个数组,数组的元素都是指针,数组占多少字节由数组本身决定。它是“存储指针的数组”的简称
2》数组指针:( int (*p2)[10]; )首先它是一个指针,它指向一个数组。在32位系统下永远占4字节,至于它指向的数组占多少字节并不知道。它是
“指向指针的数组”的简称。
注意:这里需要明白一个符号之间的优先级问题,
“”的优先级比“
”的优先级高,p1先于结合,构成数组的定义,数组名为p1,int
修饰的是数组的内容,即数组的每一个元素。即p1是一个数组其包含10个指向int类型数据的指针,即指针数组。
至于p2 *和p2构成一个指针的定义,指针变量名为p2,int修饰的是数组的内容,即数组的每一个元素。即p2是一个指针,它指向一个包含10个int类型数据的数组,即数组指针。
49、
structTest {intNum;char*pcName;shortsDate;charcha[2];shortsba[4]; }*p;intmain {printf("%x\n", p +0x1);printf("%x\n", (unsignedlong)p +0x1);printf("%x\n", (unsignedint*)p +0x1);return0; }
假设p的值为0x100000,sizeof(Test)=20
1》p+0x1:即为0x100000+0x1*sizeof(Test)=0x100014
2》(unsigned long)p+0x1:这里涉及强制类型转换,将指针变量p保存的值强制转换成无符号的长整型数。所以这个表达式就是一个无符号的长整形数加上另一个整数,其值为0x100001
3》(unsigned int
)p+0x1:这里p被强制转换为指向无符号整型的指针,所以其值为0x10000+sizeof(unsigned int
)*0x1
等于0x100004;
50、
51、二维数组
intmain {inta[3][2] = { (01), (23), (45) };int*p= NULL ; p = a[0];printf("%d", p[0]);return0; }
注意:这里花括号里嵌套的是小括号,而不是花括号。这里是花括号里嵌套了逗号表达式,其实就相当于 int a[3][2]={1,3,5};
52、
53、二级指针
一级指针保存的是数据的地址;二级指针保存的是一级指针的地址。
54、数组参数和指针参数
——在
C语言
中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素地址的指针。
55、一级指针参数
1》能否把指针变量本身传递给一个函数
voidfun(char*p) {charc = p [3]; }intmain {char*p2 ="abcdefg"; fun(p2);return0; }
1>在这里p2只是main函数里的局部变量,它只在main函数里面有效。
2>注意:main函数里面的变量不是全局变量,而是局部变量,只不过他的生命周期和全局变量一样长。全局变量一定是定义在函数外部的。
3>既然是局部变量,fun函数肯定无法使用p2的真身,函数调用的时候,对实参做一份拷贝并传递给被调用的函数。
2》无法把指针变量本身传递给一个函数
voidGetMemory(char*p,intnum) { p = (char*)malloc(num*sizeof(char)); }intmain {char*str= NULL ; GetMemory(str10); strcpy(str"hello"); free(str);return0; }
在运行strcpy(str,”hello”)语句的时候发生错误。这时候str的值任然为NULL。也就是说str本身并没有改变,函数malloc的内存地址并没有赋给str,而是赋给了_str.而这个_str是系统自动分配收回的,我们根本无法使用。所以free(str)并没有起作用,发生了内存泄漏。
这里有两个方法可以获取函数中分配的内存:
1>用return
#include <string.h>char* GetMemory(char*p,intnum) { p = (char*)malloc(num*sizeof(char));returnp ; }intmain {char*str= NULL ;str=GetMemory(str10); strcpy(str"hello"); free(str);return0; }
注意:1>函数返回值类型为char* 2>主函数里需要接受函数的返回值
2>用二级指针
#include <string.h>voidGetMemory(char* * p,intnum) { * p = (char*)malloc(num*sizeof(char)); }intmain {char* str = NULL ; GetMemory(&str,10);strcpy(str,"hello");free(str);return0; }
注意:这里的参数是&str而不是str。这样的话传递过去的是str的地址,是一个值。在函数内部用 “”来开锁:(&str),其值就是str。所以malloc分配的内存地址是真正赋给了str本身。
56、函数指针
1》定义一个函数指针:
char* (*fun)(char* p1,char* p2);
2》使用一个函数指针
char* fun(char* p1,char* p2) {inti =0; i = _strcmp(p1, p2);if(0== i) {returnp1; }else{returnp2; } }intmain {char* (*pf)(char* p1,char* p2);//定义一个函数指针char*ret =NULL; pf = &fun; ret=(*pf)("aa""bb");return0; }
注意:使用指针时,需要通过钥匙“*”来取其指向的内存里面的值,函数指针也是如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。
57、(int)&p——这是什么?
void FunTest {printf("Call FunTest"); }intmain { void (*p);*(int*)&p = (int)FunTest; (*p);return0; }
1> void (*p); ——这行代码定义了一个指针变量p,p指向一个函数,这个函数的参数和返回值都是void。 2>&p ——&p是求指针变量p本身的地址,这是一个32位的二进制常数。 3>(int*)&p ——表示将地址强制转换成指向int类型数据的指针。 4>(int)FunTest ——表示将函数的入口地址强制转换成int类型的数据。 5>*(int*)&p=(int)FunTest; ——表示将函数的入口地址赋值给变量p。 6>(*p) ——表示对函数的调用。
58、
(*(void(*))0)
——这是什么?
1>
——这是一个函数指针类型,这个函数没有参数,没有返回值
2>
(void(*))0;
——这是将0强制转换为
函数指针类型,0是一个地址,也就是说这个函数保存在首地址为0的一段区域内的函数。
3>
(
(void(
))0)
——这是取0地址开始的一段内存里面的内容,其实就是保存在首地址为0的一段区域内的函数
4>
(
(void(
))0)`
——这是函数的调用
59、函数指针数组
int*p1[10];//指针数组
char* (*pf[3])(char*p);
这是定义一个函数指针数组,它是一个数组,数组名为pf,数组内存储了3个指向函数的指针。这些指针指向一些返回值为指向字符的指针,参数为一个指向字符的指针,的函数。
60、函数指针数组指针
int(*p1)[10];//数组指针
char* (*(*pf)[3])(char*p);
这里的pf是一个指针。这个指针指向一个包含了3三个元素的数组,这个数组里面存的是指向函数的指针;这些指针指向一些返回值类型为(char*)指向字符的指针,参数为(char*)一个指向字符的指针,的函数。
61、堆、栈和静态区
1. 堆:由maollc系列函数或
new
操作符分配的内存。其生命周期由free或者delete决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但是容易出错。
2. 栈:保存局部变量。栈上的内容只在函数的范围内存在当函数运行结束时,这些内容也会自动销毁。其特点是效率高,但空间大小有限。
3. 静态区:保存自动
全局变量
和static变量(包括static全局和
局部变量
)。静态区的内容在整个程序的生命周期都存在,由编译器在编译的时候分配。
62、函数的入口校验
1. 一般在函数入口处使用assert(NULL!=p)对参数进行校验,在非参数的地方使用if(NULL!=p)来校验。但是这都有要求,即p在定义的时候被初始化为NULL。
2. assert是一个宏,而不是函数。包含在assert.h的头文件中。如果其后括号里的值为假,则程序终止运行,并提示错误;如果为真,则继续运行后续代码。这个宏只是在Debug版本里起作用,而在Relese版本里被
编译器
完全优化掉,这样就不会影响代码的性能
63、memset(a,0,sizeof(a))
memset有3个参数:第一个参数是要被设置的内存起始地址;第二个参数要被设置的值;第三个参数是要被设置的内存大小,单位为字节。
64、malloc函数
先看一下malloc函数的原型:
(void*)malloc(int size);
malloc函数的返回值是一个void类型的指针,参数为int类型的数据,即申请分配的内存大小,单位是字节。内存分配成功之后,malloc函数返回这块内存的首地址,你需要一个指针来接收这个地址。但由于函数的返回值是
void
*类型的,所以必须要强制转换成你所要接收的类型。
比如:
char* p=(char*)malloc(100);
在堆上分配了100字节的内存,返回这苦啊内存的首地址,把地址强制转换为(char*)类型后赋给(char*)类型的指针变量p;同时告诉我们这块内存将用来存储char类型的数据。也就是说只能通过指针变量p来操作这块内存。这块内存本身并没有名字,对它的访问是匿名的。
但是,并不是每一次内存分配都是成功的,所以在我们使用只想这块内存的指针时,必须用if(NULL != p)语句来验证内存确实分配成功了。
65、用malloc函数申请0字节内存
申请0字节内存并,函数不会返回NULL,而是返回一个正常地址,但是你却无法使用这块大小为0的内存。
对于这一点要小心,因为这个时候if(NULL != p)语句检验将不起作用。