本文共 16957 字,大约阅读时间需要 56 分钟。
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!!!!!!
然后赋值就是给房间住进客官!!!!!!(即赋值是对房间名进行操作,即变量名!!)i=20; int* p; p=&i;
指针的意义:名称为 i 的房间里住着20,p相当于服务员,一旦申请int* p后(相当于聘请好这个服务员)也必须申请了一处内存(即服务员站在吧台) 我现在要找 20 ,怎么办? 去吧台找服务员,服务员指着 i 房间说他在那里,然后我就找到了,所以此处的服务员就是一个指
针!!!!!!!!!!!! 他知道 20 的地址,即储存的是 20 的地址 ,所以我才能找到20 这个人!!!
请注意:::::int* p 中 int* 是一个整体!!!! 是声明指针的!符号! p才是指针变量!!!所以 p 的值是 &20(即20 的地址(房间号)); 所以要读取20 就必须要读 *p !!!!!!
可以通过指针改变原 房间中的值 也就相当于 服务员 送走 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); ----> 10printf("%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指示符常被用来判断一个预处理器常量是否已被定义 以便有条件地包含程序代
转载地址:http://joaci.baihongyu.com/