3、C_指針才是精髓
阿新 • • 發佈:2018-03-10
type watermark 元素 linux 數組 分析 發生 首地址 分享圖片 指針到底是什麽
指針和變量的區別:
- 指針的實質就是變量,指針完整的名字應該叫指針變量,簡稱指針。
- 指針是為了實現間接訪問。在匯編中都有間接訪問,其實就是CPU的尋址方式中的間接尋址;
- 間接訪問(CPU的間接尋址)是CPU設計時決定的,這個決定了匯編語言必須能實現間接尋址,也決定了匯編之上的C語言也必須實現間接尋址;
- 高級語言C#、Java等並不是沒有指針,而是進行封裝了;
- C語言中代表乘號,也表示指針符號;
- 星號在用於指針相關功能時有2種用法:
- 第一種:指針定義時,* 結合前面的類型表明要定義指針的類型;
- 第二種:在解引用的時候,*p表示p指向的變量的本身;
- & 使用時,直接加在一個變量的前面,然後取地址符和變量加起來構成一個新的符號,這個符號表示這個變量的地址;
- 指針定義時,可以初始化,指針的初始化其實就是給指針變量賦初值(跟普通變量的初始化沒有任何本質區別);
- 指針變量定義並初始化的格式為:int a =32; int *p =&a;
- 不初始化時,指針變量先定義再賦值:int a =32; int *p; p =&a;
- 賦值操作其實就是:左值=右值;
- 變量做左值時,代表的是變量對應的內存空間;
- 變量做右值時,代表的是變量空間存的數據;
- 左值和右值的區別:就好象現實生活中“家”這個字的含義。譬如“我回家了”,這裏面的家指的是你家的房子(類似於左值);但是說“家比事業重要”,這時候的家指的是家人(家人就是住在家所對應的那個房子裏面的人,類似於右值)
野指針問題 什麽是野指針?哪裏來的?有什麽危害?
- 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制);
- 野指針很可能觸發段錯誤(Sgmenntation fault);
- 因為指針變量在定義時如果為初始化,值也是隨機的;指針變量的值其實就是別的變量(指針所指向的那個變量)的地址,所以意味著這個指針指向了一個地址是不正確的變量,這時候去解引用就是去訪問這個地址不正確的變量,所以結果是不可知的;
- 野指針因為指向地址是不可預知的,所以有3中情況:
- 第一種是不可訪問的(操作系統不允許訪問的敏感地址,譬如內核空間)的地址,運行結果是發生段錯誤,這種算是最好的情況了;
- 第二種是指向一個可用的、而且沒什麽特別意義的空間(譬如我們使用過的但是已經不用的棧空間或堆空間),這時候程序運行不會出錯,也不會對當前程序造成損害,這種情況不會掩蓋你的程序錯誤,讓你以為程序沒問題,其實是有問題的;
- 第三種是指向一個可用的空間,而且這個空間其實在程序中正在被使用(譬如說程序的一個變量x),那麽野指針的解引用就會剛好修改這個變量x的值,導致這個變量莫名其妙的被改變,程序出現離奇錯誤,一般最終會導致程序崩潰,或者數據被損害,這種危害是最大的;
- 指針變量如果是局部變量,則分配在棧上,本身遵從棧的規律(反復使用,使用完不擦除,所以是臟的,本次在棧上分配到的變量的默認值是上次這個棧空間被使用時余留下來的值),就決定了棧的使用多少會影響這個默認值。因此野指針的值是有一定規律不是完全隨機,但是這個值的規律對我們沒意義。因為不管落在上面野指針3種情況的哪一種,都不是我們想看到的。
- 野指針的錯誤來源就是指針定義之後沒有初始化,也沒有賦值(總之就是指針沒有明確的指向一個可用的內存空間),然後去解引用;
- 知道了野指針產生的原因,避免方法就出來了:在指針的解引用之前,一定確保指針指向一個絕對可用的空間;
- 常規的做法:
- 第一點:定義指針時,同時賦值為NULL;
- 第二點:在解引用之前先判斷指針是不是NULL;
- 第三點:指針使用後,賦值為NULL;
- 第四點:在指針使用之前,將其賦值綁定給一個可用地址空間;
- 以上四點絕對可行,但略顯麻煩,實際中的處理方法:在中小型程序中,自己水平能把握的情況下,不必完全參照這個標準;但大型程序或者自己水平感覺不好把握時,建議嚴格參照這個方法;
- NULL在C/C++中定義為:
- 在C語言中,int *p; 可以p=(int *)0;但是不可以p=0;因為類型不同;
- NULL的實質就是0,然後我們給指針賦初值為NULL,其實就是讓指針指向0地址處。
- 為什麽是0地址處?
- 原因1: 0地址處作為一個特殊地址(我們認為指針指向這裏就表示指針沒有被初始化,就表示是野指針);
- 原因2: 0地址在一般的操作系統中都是不可被訪問的,如果C語言程序員不按規矩(不檢查是否等於NULL就去解引用)寫代碼,直接去解引用就會觸發段錯誤,這種已經是最好的結果了;
- 一般在代碼段指針是否是野指針時,都寫成:if(NULL !=p) 而不是寫成 if(p !=NULL)。原因:如果NULL寫在後面,當中間是==號的時候,有時候容易忘記寫成了=,這時候其實程序已經錯誤,但是編譯器不會報錯。這個錯誤(對新手)很難檢查出來;如果習慣了把NULL寫在前面,當錯誤的把==寫成了=時,編譯器會報錯,程序員會發現這個錯誤。
- const關鍵字,在C語言中用來修飾變量,表示這個變量是常量。
- const修飾指針有4種形式,區分清楚這4種即可全部理解const和指針。
- const int *p; // p 可變, p 指向的對象不可變
- int const *p; // p 可變, p 指向的對象不可變
- int *const p; // p 不可變, p 指向的對象可變
- const int *const p; //指針 p 和 p 指向的對象都不可變
- 記憶方法:先忽略類型名,看const離哪個近,離誰近,const就修飾誰,被修飾的符號不可更改;
- 關於指針變量的理解,主要涉及到2個變量:第一個是指針變量p本身,第二個是p指向的那個變量(*p)。一個const關鍵字只能修飾一個變量,所以弄清楚這4個表達式的關鍵就是搞清楚const放在某個位置是修飾誰的
- typedef int * pint ; const pint p ;//p不可更改,但p指向的內容可更改
- #define PINT int * ; const PINT p ;//p可更改,但是p指向的內容不可更改。
- 具體區別查看網址:https://zhidao.baidu.com/question/46929256.html,http://www.cnblogs.com/afarmer/archive/2011/05/05/2038201.html
- 課堂練習說明:const修飾的變量其實是可以改的(前提是gcc環境下)。
- 在某些單片機環境下,const修飾的變量是不可以改的。const修飾的變量到底能不能真的被修改,取決於具體的環境,C語言本身並沒有完全嚴格一致的要求。
- 在gcc中,const是通過編譯器在編譯的時候執行檢查來確保實現的(也就是說const類型的變量不能改是編譯錯誤,不是運行時錯誤。)所以我們只要想辦法騙過編譯器,就可以修改const定義的常量,而運行時不會報錯。
- 更深入一層的原因,是因為gcc把const類型的常量也放在了data段,其實和普通的全局變量放在data段是一樣實現的,只是通過編譯器認定這個變量是const的,運行時並沒有標記const標誌,所以只要騙過編譯器就可以修改了。
- const修飾的變量可以使用 指針 方式進行修改;
- const是在編譯器中實現的,編譯時檢查,並非不能騙過。所以在C語言中使用const,就好象是一種道德約束而非法律約束,所以大家使用const時更多是傳遞一種信息,就是告訴編譯器、也告訴讀程序的人,這個變量是不應該也不必被修改的。
- 從內存角度講,數組變量就是一次分配多個變量,而且這多個變量在內存中的存儲單元是依次相連接的。
- 我們分開定義多個變量(譬如int a, b, c, d;)和一次定義一個數組(int a[4]);這兩種定義方法相同點是都定義了4個int型變量,而且這4個變量都是獨立的單個使用的;不同點是單獨定義時a、b、c、d在內存中的地址不一定相連,但是定義成數組後,數組中的4個元素地址肯定是依次相連的。
- 數組中多個變量雖然必須單獨訪問,但是因為他們的地址彼此相連,因此很適合用指針來操作,因此數組和指針天生就叫糾纏在一起。
- 從編譯器角度來講,數組變量也是變量,和普通變量和指針變量並沒有本質不同。變量的本質就是一個地址,這個地址在編譯器中決定具體數值,具體數值和變量名綁定,變量類型決定這個地址的延續長度。
- 搞清楚:變量、變量名、變量類型這三個概念的具體含義,很多問題都清楚了。
- int a; char a;
- 這4個符號搞清楚了,數組相關的很多問題都有答案了。理解這些符號的時候要和左值右值結合起來,也就是搞清楚每個符號分別做左值和右值時的不同含義。
- a就是數組名。a做左值時表示整個數組的所有空間(10×4=40字節),又因為C語言規定數組操作時要獨立單個操作,不能整體操作數組,所以a不能做左值;a做右值表示數組首元素的首地址(數組的第0個元素,也就是a[0]; 首地址就是起始地址,就是4個字節中最開始第一個字節的地址)。a做右值等同於&a[0];
- a[0]表示數組的首元素,也就是數組的第0個元素。做左值時表示數組第0個元素對應的內存空間(連續4字節);做右值時表示數組第0個元素的值(也就是數組第0個元素對應的內存空間中存儲的那個數)
- &a就是數組名a取地址,字面意思來看就應該是數組的地址。&a不能做左值(&a實質是一個常量,不是變量因此不能賦值,所以自然不能做左值。);&a做右值時表示整個數組的首地址。
- &a[0]字面意思就是數組第0個元素的首地址(搞清楚[]和&的優先級,[]的優先級要高於&,所以a先和[]結合再取地址)。做左值時表示數組首元素對應的內存空間,做右值時表示數組首元素的值(也就是數組首元素對應的內存空間中存儲的那個數值)。做右值時&a[0]等同於a。
- 解釋:為什麽數組的地址是常量?因為數組是編譯器在內存中自動分配的。當我們每次執行程序時,運行時都會幫我們分配一塊內存給這個數組,只要完成了分配,這個數組的地址就定好了,本次程序運行直到終止都無法再改了。那麽我們在程序中只能通過&a來獲取這個分配的地址,卻不能去x用賦值運算符修改它。
- &a和a做右值時的區別:&a是整個數組的首地址,而a是數組首元素的首地址。這兩個在數字上是相等的,但是意義不相同。意義不相同會導致他們在參與運算的時候有不同的表現。
- a和&a[0]做右值時意義和數值完全相同,完全可以互相替代。
- &a是常量,不能做左值。(a做左值代表整個數組所有空間,所以a不能做左值。)
- 數組元素使用時不能整體訪問,只能單個訪問。訪問方式有2種:數組形式和指針形式。
- 數組格式訪問數組元素是:數組名[下標]; (註意下標從0開始)
- 指針格式訪問數組元素是:*(指針+偏移量); 如果指針是數組首元素地址(a或者&a[0]),那麽偏移量就是下標;指針也可以不是首元素地址而是其他哪個元素的地址,這時候偏移量就要考慮疊加了。
- 數組下標方式和指針方式均可以訪問數組元素,兩者的實質其實是一樣的。在編譯器內部都是用指針方式來訪問數組元素的,數組下標方式只是編譯器提供給編程者一種殼(語法糖)而已。所以用指針方式來訪問數組才是本質的做法。
- 數組的特點就是:數組中各個元素的地址是依次相連的,而且數組還有一個很大的特點(其實也是數組的一個限制)就是數組中各個元素的類型比較相同。類型相同就決定了每個數組元素占幾個字節是相同的(譬如int數組每個元素都占4字節,沒有例外)。
- 數組中的元素其實就是地址相連接、占地大小相同的一串內存空間。這兩個特點就決定了只要知道數組中一個元素的地址,就可以很容易推算出其他元素的地址。
- int *p; int a[5]; p=a; //類型匹配,a表示數組首元素的首地址,為int *類型
- int *p; int a[5]; p=&a; //類型不匹配,&a表示數組首地址,不是int類型,是 int(*)[5]類型
- &a、a、&a[0]從數值上來看是完全相等的,但是從意義上看就不同了:從意義上看,a和&a[0]是元素的指針,也就是int *類型;而&a是數組指針,是int (*)[5] 類型;
- 指針參與運算時,因為指針變量本身存儲的數值是表示地址的,所以運算也是地址的運算;
- 指針參與運算的特點:指針變量+1,並不是真的+1,而是加1*sizeof(指針類型);如果是int *指針,則+1實際表示的是地址+4,如果是char *指針,則+1就表示地址+1;如果是double *指針,則+1就表示地址+8;
- 指針變量+1實際不是+1,而是加1×sizeof(指針類型),主要原因是希望指針+1後剛好指向下一個元素(而不是希望錯位);
- 所有的類型的數據存儲在內存中,都是按照二進制格式存儲的。所以內存中只知道有0和1,不知道是int的、還是float的還是其他類型。
- int、char、short等屬於整形,他們的存儲方式(數轉換成二進制往內存中放的方式)是相同的,只是內存格子大小不同(所以這幾種整形就彼此叫二進制兼容格式);而float和double的存儲方式彼此不同,和整形更不同。
- int a = 5;時,編譯器給a分配4字節空間,並且將5按照int類型的存儲方式轉成二進制存到a所對應的內存空間中去(a做左值的);我們printf去打印a的時候(a此時做右值),printf內部的vsprintf函數會按照格式化字符串(就是printf傳參的第一個字符串參數中的%d之類的東西)所代表的類型去解析a所對應的內存空間,解析出的值用來輸出。也就是說,存進去時是按照這個變量本身的數據類型來存儲的(譬如本例中a為int所以按照int格式來存儲);但是取出來時是按照printf中%d之類的格式化字符串的格式來提取的。此時雖然a所代表的內存空間中的10101序列並沒有變(內存是沒被修改的)但是怎麽理解(怎麽把這些1010轉成數字)就不一定了。譬如我們用%d來解析,那麽還是按照int格式解析則值自然還是5;但是如果用%f來解析,則printf就以為a對應的內存空間中存儲的是一個float類型的數,會按照float類型來解析,值自然是很奇怪的一個數字了。
- 總結:C語言中的數據類型的本質,就是決定了這個數在內存中怎麽存儲的問題,也就是決定了這個數如何轉成二進制的問題。一定要記住的一點是內存只是存儲1010的序列,而不管這些1010怎麽解析。所以要求我們平時數據類型不能瞎胡亂搞。
- 分析幾個題目:
- 按照int類型存卻按照float類型取 一定會出錯
- 按照int類型存卻按照char類型取 有可能出錯也有可能不出錯
- 按照short類型存卻按照int類型取 有可能出錯也有可能不出錯
- 按照float類型存卻按照double取 一定會出錯
- 指針的本質是:變量,指針就是指針變量
- 一個指針涉及2個變量:一個是指針變量自己本身,一個是指針變量指向的那個變量
- int *p;定義指針變量時,p(指針變量本身)是int *類型,*p(指針指向的那個變量)是int類型的。
- int *類型說白了就是指針類型,只要是指針類型就都是占4字節,解析方式都是按照地址的方式來解析(意思是裏面存的32個二進制加起來表示一個內存地址)的。結論就是:所有的指針類型(不管是int * 還是char * 還是double *)的解析方式是相同的,都是地址。
- 對於指針所指向的那個變量來說,指針的類型就很重要了。指針所指向的那個變量的類型(它所對應的內存空間的解析方法)要取決於指針類型。譬如指針是int *的,那麽指針所指向的變量就是int類型的。
- int和char類型都是整形,類型兼容的。所以互轉的時候有時候錯有時候對。
- int和char的不同在於char只有1個字節而int有4個字節,所以int的範圍比char大。在char所表示的範圍之內int和char是可以互轉的不會出錯;但是超過了char的範圍後char轉成int不會錯(向大方向轉就不會錯,就好比拿小瓶子的水往大瓶子倒不會漏掉不會丟掉),而從int到char轉就會出錯(就好象拿大瓶子水往小瓶子倒一樣)
- 之前分析過:int和float的解析方式是不兼容的,所以int *轉成float *再去訪問絕對會出錯。
- sizeof是C語言的一個運算符(主要sizeof不是函數,雖然用法很像函數),sizeof的作用是用來返回()裏面的變量或者數據類型占用的內存字節數。
- sizeof存在的價值?主要是因為在不同平臺下各種數據類型所占的內存字節數不盡相同(譬如int在32位系統中為4字節,在16位系統中為2字節???)。所以程序中需要使用sizeof來判斷當前變量/數據類型在當前環境下占幾個字節。
- char str[] = ”hello”; sizeof(str); sizeof(str[0]); strlen(str);
- char *p=str; sizeof(p); sizeof(*p); strlen(p);
- 32位系統中所有指針的長度都是4,不管是什麽類型的指針。
- strlen是一個C庫函數,用來返回一個字符串的長度(註意,字符串的長度是不計算字符串末尾的‘\0‘的)。一定要註意strlen接收的參數必須是一個字符串(字符串的特征是以‘\0‘結尾)
- sizeof 用來測試字符數組的大小,計算字符個數要加上末尾的 ‘\0‘
- int n=10; //相當於sizeof(n) ,sizeof測試一個變量本身與測試這個變量的類型,其結果是一樣的;
- int b[100]; //相當於sizeof(b) ,sizeof(數組名)的時候,變量名不做左值,也不做右值,純粹是數組名的含義。那麽sizeof(數組名)實際返回的是整個數組所占的內存空間(以字節為單位);
- 函數傳參,形參是可以用數組的
- 函數形參是數組時,實際傳遞是不是整個數組,而是數組的首元素首地址。也就是說函數傳參用數組來傳,實際相當於傳遞的是指針(指針指向數組的首元素首地址)。
- 函數在傳遞數組時,需要傳遞大小,因為數組在傳遞的時候不能傳遞大小,數組在經過函數調用後,數組的大小就丟了,只剩下首地址;
- #define 是宏命令,在編譯前,由預處理器做替代,如同文本編輯的替代命令,把程序中的所有遇到的詞,全部替代;
- typedef int* pint; 是語句,由編譯器在編譯過程中編譯處理。
- typedef int * pint ; const pint p ;//p不可更改,但p指向的內容可更改
- #define PINT int * ; const PINT p ;//p可更改,但是p指向的內容不可更改。
- 具體區別查看網址:https://zhidao.baidu.com/question/46929256.html,http://www.cnblogs.com/afarmer/archive/2011/05/05/2038201.html
- 在子函數內部,形參的值等於實參。原因是函數調用時把實參的值賦值給了形參。
- 這就是很多書上寫的“傳值調用”(相當於實參做右值,形參做左值)
- 數組名作為形參傳參時,實際傳遞是不是整個數組,而是數組的首元素的首地址(也就是整個數組的首地址。因為傳參時是傳值,所以這兩個沒區別)。所以在子函數內部,傳進來的數組名就等於是一個指向數組首元素首地址的指針。所以sizeof得到的是4.
- 在子函數內傳參得到的數組首元素首地址,和外面得到的數組首元素首地址的值是相同的。很多人把這種特性叫做“傳址調用”(所謂的傳址調用就是調用子函數時傳了地址(也就是指針),此時可以通過傳進去的地址來訪問實參。)
- 數組作為函數形參時,[]裏的數字是可有可無的。為什麽?因為數組名做形參傳遞的實際只是個指針,根本沒有數組長度這個信息。
- 和數組作為函數形參的用法一模一樣,只需要把(int a[])改為(int *p)即可。這種方式就好像指針方式訪問數組和數組方式訪問數組元素的結果一樣,是一樣的。
- 結構體變量作為函數形參的時候,實際上和普通變量(類似於int之類的)傳參時表現是一模一樣的,所以說結構體變量其實也是普通變量而已;
-
struct dd { int a; int b; float c; double d; }; void func1(struct dd *p) { printf("sizeof(p) = %d\n",sizeof(p)); //4 printf("sizeof(*p) = %d\n",sizeof(*p)); //20 printf("&p = %p\n",&p); //0xbffa0f70 printf("p->c = %f\n",p->c); //6.600000 } int main(void) { struct dd a = { .a = 4, .b = 5, .c = 6.6, .d = 7.7, }; printf("sizeof(a) = %d\n",sizeof(a)); //20 printf("&a = %p\n",&a); //0xbfb6e3c8 printf("a.b = %d\n",a.b); //5 func1(&a); return0; }
- 結構體因為自身太大,所以傳參應該用指針來傳(但是程序員可以自己決定,你非要傳結構體變量過去C語言也是允許的,只是效率低了);回想一下數組,為什麽C語言設計的時候數組傳參默認是傳的數組首元素首地址而不是整個數組?(因為傳數組效率低,C語言幫我們做了決定,而結構體需要我們自己做決定)。
- 傳值調用描述的是這樣一種現象:x和y作為實參,自己並沒有真身進入swap1函數內部,而只是拷貝了一份自己的副本(副本具有和自己一樣的值,但是是不同的變量)進入子函數swap1,然後我們在子函數swap1中交換的實際是副本而不是x、y真身。所以在swap1內部確實是交換了,但是到外部的x和y根本沒有受影響。
-
void swap1(int a, int b) { int tmp; tmp = a; a = b; b = tmp; printf("in swap1, a = %d, b = %d.\n", a, b); } intmain(void) { int x = 3, y =5; swap2(x, y); printf("x = %d, y = %d.\n", x, y); }
- 在swap2中x和y真的被改變了(但是x和y真身還是沒有進入swap2函數內,而是swap2函數內部跑出來把外面的x和y真身改了)。實際上實參x和y永遠無法真身進入子函數內部(進去的只能是一份拷貝),但是在swap2我們把x和y的地址傳進去給子函數了,於是乎在子函數內可以通過指針解引用方式從函數內部訪問到外部的x和y真身,從而改變x和y。
-
void swap2(int *a, int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; printf("in swap1, *a = %d, *b = %d.\n", *a, *b); } intmain(void) { int x = 3, y =5; swap2(&x, &y); printf("x = %d, y = %d.\n", x, y); // 交換成功 }
- 結論:這個世界上根本沒有傳值和傳址這兩種方式,C語言本身函數調用時一直是傳值的,只不過傳的值可以是變量名,也可以是變量的指針。
- 函數名是一個符號,表示整個函數代碼段的首地址,實質是一個指針常量,所以在程序中使用到函數名時都是當地址用的,用來調用這個函數的。
- 函數體是函數的關鍵,由一對{}括起來,包含很多句代碼,函數體就是函數實際做的工作。
- 形參列表和返回值。形參是函數的輸入部分,返回值是函數的輸出部分。對函數最好的理解就是把函數看成是一個加工機器(程序其實就是數據加工器),形參列表就是這個機器的原材料輸入端;而返回值就是機器的成品輸出端。
- 其實如果沒有形參列表和返回值,函數也能對數據進行加工,用全局變量即可。用全局變量來傳參和用函數參數列表返回值來傳參各有特點,在實踐中都有使用。總的來說,函數參數傳參用的比較多,因為這樣可以實現模塊化編程,而C語言中也是盡量減少使用全局變量。
- 全局變量傳參最大的好處就是省略了函數傳參的開銷,所以效率要高一些;但是實戰中用的最多的還是函數傳參,如果參數很多傳參開銷非常大,通常的做法是把很多參數打包成一個結構體,然後傳結構體變量指針進去。
- const一般在函數參數列表中,用法是const int *p;(意義是指針變量本身是可變的,指針指向的變量類型是不可變的);
- const用來修飾指針做函數傳參,作用在於申明函數內部不會改變這個指針所指向的內容,所以給函數傳一個不可改變的指針(char *p = "linux";這種)不會觸發錯誤;而一個為申明位const的指針的函數,給他傳一個不可更改的指針的時候就要小心了;
- 定義常量:被const修飾過的變量不能被修改,故此具有常量之稱。如果類的成員變量是常量,那麽在初始化的時候必須初始化。
- 修飾函數:const可以修飾函數的返回值,參數及,函數的定義體,被const修飾會受到強制的保護,能防止意外的修改,從而提高函數的健壯性。
- 1.修飾參數:不能在定義體中修改形參的值
- 2.修飾返回值:被修飾的返回值不能作為左值,只有作為右值使用
- 3.修飾函數定義體:被const修飾的函數定義體的函數能被const或者非const對象調用,但是const對象只能調用被const修飾過定義體的函數。
- 一般來說,函數的輸入部分就是函數的參數,輸出部分就是函數的返回值。可問題是:函數的參數可以有多個,但函數的返回值只有1個,這就使得我們無法讓一個函數返回多個值;
- 在編程中,使用一個函數返回多個返回值的用法是非常普遍的,因為安全依賴於返回值是不靠譜的,通常的做法是用參數來返回(在經典的linux風格中,返回值是不用來返回結果的,而是返回0或者負數來表示程序執行結果是對還是錯,是成功還是失敗);
- 普遍做法:編程中函數的輸入和輸出都是靠函數參數的,返回值只是用來表示函數執行的結果是對(成功)還是錯(失敗)。如果參數是用來輸入的,就叫輸入型參數;如果這個參數的目的是用來做輸出的,就叫輸出型參數;
- 輸出型參數就是用來讓函數內部把數據輸出到函數外部的。
- 看到一個函數的原型後,怎麽樣一眼就看出來哪個參數做輸入,哪個參數做輸出?函數傳參如果傳的是普通變量(不是指針),那肯定是輸入型參數;
- 如果傳指針就有2種可能性,為了區別,經常做法是:如果這個參數是做輸入就在指針前面加const來修飾;(通常做輸入的在函數內部只需要讀取這個參數而不會需要更改他),如果函數形參是指針變量並且還沒加const,那就表示這個參數是用來做輸出型參數的。
- 譬如C庫函數中strcpy函數:網址http://blog.csdn.net/wconvey/article/details/21150103
3、C_指針才是精髓