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訓示符常被用來判斷一個預處理器常量是否已被定義 以便有條件地包含程式代