<kbd id="daqct"></kbd>

  • <nav id="daqct"></nav>
    <wbr id="daqct"><pre id="daqct"></pre></wbr>
    <wbr id="daqct"></wbr>
    <form id="daqct"><th id="daqct"></th></form>
    更多課程 選擇中心

    C/C++培訓
    達內IT學院

    400-111-8989

    C/C++數組知識,C/C++數組問題知識總結

    • 發布:C++培訓
    • 來源:學習筆記
    • 時間:2017-09-25 15:32

    char *s 和 char s[] 的區別小結

    char *s1 ="hello";

    char s2[] = "hello";

    【區別所在】

    char *s1 的s1,而指針是指向一塊內存區域,它指向的內存區域的大小可以隨時改變,而且當指針指向常量字符串時,它的內容是不可以被修改的,否則在運行時會報錯。

    char s2[]的s2 是數組對應著一塊內存區域,其地址和容量在生命期里不會改變,只有數組的內容可以改變

    【內存模型】

    +-----+ +---+---+---+---+---+---+

    s1: | *======> | h | e | l | l | o |\0 |

    +-----+ +---+---+---+---+---+---+

    +---+---+---+---+---+---+

    s2: | h | e | l | l | o |\0 |

    +---+---+---+---+---+---+

    場景一)

    char *s1 = "hello";

    char s2[] = "hello";

    s2=s1; //編譯ERROR

    s1=s2; //OK

    分析:s2其地址和容量在生命期里不能改變

    場景二)

    char s2[] = "hello";

    char *s1 = s2; //編譯器做了隱式的轉換實際為&s2

    char *s1 = &s2;

    分析:以上兩個指針復值完全等價,由于編譯器會做這個隱式轉換也容易導致初學者誤認為char *s 與char s[]是一回事。

    另用第二種在一些編譯器甚至會報警告信息。

    /* 變量名只是相當于一個標簽,他所表示的內容是否能夠修改不取決于這個標簽而取決于存放內容的內存屬性*/

    場景三)

    char *s1 = "hello";

    char s2[] = "hello";

    s1[0]='a'; //×運行ERROR(這一句好像在一些的編譯器不會出錯,原因待查)

    s2[0]='a'; //OK

    分析:運行時會報錯,原因在于企圖改變s1的內容,由于s1指向的是常量字符串,其內容是不可修改的,因此在運行時不會通過。而s2指向的是變量區字符串,可以修改。

    場景四)

    讓我們來給一個指針的指針賦值,在使用某些含char**參數的函數時會用到,場景二的增強版。

    char *s1="hello";

    char s2[]="hello";

    char *s3=s2; //★注意這句必須要★

    char **s4=&s3; //s2(char[])要用兩步才能完成賦值

    char **s5=&s1; //s1(char*)只需一步

    printf("s4=[%s]\n",*s4);//打印結果:s4=[hello]

    printf("s5=[%s]\n",*s5);//打印結果:s5=[hello]

    分析:這個例子應當說最能反映出char *與char []的差異,但是由于使用場合不多,新人尤其需要注意。

    下面是一些char *s1 和 char s2[]相同的地方(同樣編譯器對char[]做了隱式變化):

    1)作為形參完全相同

    如:

    void function(char *s1);

    void function(char s1[]);

    2)只讀取不修改的時候

    如:

    char *s1="hello";

    char s2[]="hello";

    printf("s1[1]=[%c]\n",s1[1]); //s1[1]=[e]

    printf("s2[1]=[%c]\n",s2[1]); //s2[1]=[e]

    printf("s1=[%s]\n",s1); //s1=[hello]

    printf("s2=[%s]\n",s2); //s2=[hello]

    1. 問題介紹

    問題引入:

    在實習過程中發現了一個以前一直默認的錯誤,同樣char *c ="abc"和charc[]="abc",前者改變其內

    容程序是會崩潰的,而后者完全正確。

    程序演示:

    測試環境Devc++

    代碼

    #include <iostream>

    using namespace std;

    main()

    {

    char *c1 = "abc";

    char c2[] = "abc";

    char *c3 = ( char* )malloc(3);

    c3 = "abc";

    printf("%d %d %s\n",&c1,c1,c1);

    printf("%d %d %s\n",&c2,c2,c2);

    printf("%d %d %s\n",&c3,c3,c3);

    getchar();

    }

    運行結果

    2293628 4199056 abc

    2293624 2293624 abc

    2293620 4199056 abc

    參考資料:

    首先要搞清楚編譯程序占用的內存的分區形式:

    一、預備知識—程序的內存分配

    一個由c/C++編譯的程序占用的內存分為以下幾個部分

    1、棧區(stack)—由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于

    數據結構中的棧。

    2、堆區(heap)—一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據

    結構中的堆是兩回事,分配方式倒是類似于鏈表,呵呵。

    3、全局區(靜態區)(static)—全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態

    變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后由系統

    釋放。

    4、文字常量區—常量字符串就是放在這里的。程序結束后由系統釋放。

    5、程序代碼區

    這是一個前輩寫的,非常詳細

    //main.cpp

    int a=0; //全局初始化區

    char *p1; //全局未初始化區

    main()

    {

    int b;棧

    char s[]="abc"; //棧

    char *p2; //棧

    char *p3="123456"; //123456\0在常量區,p3在棧上。

    static int c=0; //全局(靜態)初始化區

    p1 = (char*)malloc(10);

    p2 = (char*)malloc(20); //分配得來得10和20字節的區域就在堆區。

    strcpy(p1,"123456"); //123456\0放在常量區,編譯器可能會將它與p3所向"123456"優化成一個

    地方。

    }

    二、堆和棧的理論知識

    2.1申請方式

    stack:

    由系統自動分配。例如,聲明在函數中一個局部變量int b;系統自動在棧中為b開辟空間

    heap:

    需要程序員自己申請,并指明大小,在c中malloc函數

    如p1=(char*)malloc(10);

    在C++中用new運算符

    如p2=(char*)malloc(10);

    但是注意p1、p2本身是在棧中的。

    2.2

    申請后系統的響應

    棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。

    堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,

    會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將

    該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大

    小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正

    好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。

    2.3申請大小的限制

    棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地

    址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯

    時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間

    較小。

    堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地

    址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的

    虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

    2.4申請效率的比較:

    棧:由系統自動分配,速度較快。但程序員是無法控制的。

    堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.

    另外,在WINDOWS下,最好的方式是用Virtual Alloc分配內存,他不是在堆,也不是在棧,而是直接在進

    程的地址空間中保留一塊內存,雖然用起來最不方便。但是速度快,也最靈活。

    2.5堆和棧中的存儲內容

    棧:在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的

    地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變

    量。注意靜態變量是不入棧的。

    當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主

    函數中的下一條指令,程序由該點繼續運行。

    堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。

    2.6存取效率的比較

    char s1[]="aaaaaaaaaaaaaaa";

    char *s2="bbbbbbbbbbbbbbbbb";

    aaaaaaaaaaa是在運行時刻賦值的;

    而bbbbbbbbbbb是在編譯時就確定的;

    但是,在以后的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。

    比如:

    #include

    voidmain()

    {

    char a=1;

    char c[]="1234567890";

    char *p="1234567890";

    a = c[1];

    a = p[1];

    return;

    }

    對應的匯編代碼

    10:a=c[1];

    004010678A4DF1movcl,byteptr[ebp-0Fh]

    0040106A884DFCmovbyteptr[ebp-4],cl

    11:a=p[1];

    0040106D8B55ECmovedx,dwordptr[ebp-14h]

    004010708A4201moval,byteptr[edx+1]

    004010738845FCmovbyteptr[ebp-4],al

    第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據

    edx讀取字符,顯然慢了。

    2.7小結:

    堆和棧的區別可以用如下的比喻來看出:

    使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會

    切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。

    使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。

    自我總結:

    char *c1 = "abc";實際上先是在文字常量區分配了一塊內存放"abc",然后在棧上分配一地址給c1并指向

    這塊地址,然后改變常量"abc"自然會崩潰

    然而char c2[] = "abc",實際上abc分配內存的地方和上者并不一樣,可以從

    4199056

    2293624 看出,完全是兩塊地方,推斷4199056處于常量區,而2293624處于棧區

    2293628

    2293624

    2293620 這段輸出看出三個指針分配的區域為棧區,而且是從高地址到低地址

    2293620 4199056abc 看出編譯器將c3優化指向常量區的"abc"

    繼續思考:

    代碼:

    #include <iostream>

    using namespace std;

    main()

    {

    char *c1 = "abc";

    char c2[] = "abc";

    char *c3 = ( char* )malloc(3);

    // *c3 = "abc" //error

    strcpy(c3,"abc");

    c3[0] = 'g';

    printf("%d %d %s\n",&c1,c1,c1);

    printf("%d %d %s\n",&c2,c2,c2);

    printf("%d %d %s\n",&c3,c3,c3);

    getchar();

    }

    輸出:

    2293628 4199056 abc

    2293624 2293624 abc

    2293620 4012976 gbc

    寫成注釋那樣,后面改動就會崩潰

    可見strcpy(c3,"abc");abc是另一塊地方分配的,而且可以改變,和上面的參考文檔說法有些不一定,

    而且我不能斷定4012976是哪個區的,可能要通過算區的長度,希望高人繼續深入解釋,謝謝

    2. 一個實例

    1. int *ip = new int;

    2. char s[] = "abcd";

    3. char* p = "abcd";

    4.

    5. cout<<ip<<endl;

    6. cout<<*ip<<endl;

    7. cout<<&ip<<endl;

    8. // s = p; //error C2440: '=' : cannot convert from 'char *' to 'char'

    9. //難道s不是指向第一個字符的指針嗎?

    10. cout << s <<endl;//果然這句話輸出的是abcd!

    11. cout << *s <<endl;//但這句輸出的是a!

    12. cout << &s <<endl;

    13. cout << (s+1) <<endl;

    14.// cout << &(s+1) <<endl;//error C2102: '&' requires l-value

    15. cout << *(s+1) <<endl;

    16. cout << &s[1] <<endl;

    17.

    18. cout << p <<endl;

    19. cout << *p <<endl;

    20. cout << &p <<endl;

    21. cout << (p+1) <<endl;

    22.// cout << &(p+1) <<endl;//error C2102: '&' requires l-value

    23. cout << *(p+1) <<endl;

    24. cout << &p[1] <<endl;

    輸出:

    相關解釋:

    char[]是一個數組定義,char*是指針定義,你可以看下他們的區別,對你會有幫助。

    1 指針和數組的區別

    (1)指針和數組的分配

    數組是開辟一塊連續的內存空間,數組本身的標識符(也就是通常所說的數組名)代表整個數組,可以使用sizeof來獲得數組所占據內存空間的大小(注意,不是數組元素的個數,而是數組占據內存空間的大小,這是以字節為單位的)。舉例如下:

    #include <stdio.h>

    int main(void)

    {

    char a[] = "hello";

    int b[] = {1, 2, 3, 4, 5};

    printf("a: %d\n", sizeof(a));

    printf("b memory size: %d bytes\n", sizeof(b));

    printf("b elements: %d\n", sizeof(b)/sizeof(int));

    return 0;

    }

    數組a為字符型,后面的字符串實際上占據6個字節空間(注意最后有一個\0標識字符串的結束)。從后面sizeof(b)就可以看出如何獲得數組占據的內存空間,如何獲得數組的元素數目。至于int數據類型分配內存空間的多少,則是編譯器相關的。gcc默認為int類型分配4個字節的內存空間。

    (2)空間的分配

    這里又分為兩種情況。

    第一,如果是全局的和靜態的

    char *p = “hello”;

    這是定義了一個指針,指向rodata section里面的“hello”,可以被編譯器放到字符串池。在匯編里面的關鍵字為.ltorg。意思就是在字符串池里的字符串是可以共享的,這也是編譯器優化的一個措施。

    char a[] = “hello”;

    這是定義了一個數組,分配在可寫數據塊,不會被放到字符串池。

    第二,如果是局部的

    char *p = “hello”;

    這是定義了一個指針,指向rodata section里面的“hello”,可以被編譯器放到字符串池。在匯編里面的關鍵字為.ltorg。意思就是在字符串池里的字符串是可以共享的,這也是編譯器優化的一個措施。另外,在函數中可以返回它的地址,也就是說,指針是局部變量,但是它指向的內容是全局的。

    char a[] = “hello”;

    這是定義了一個數組,分配在堆棧上,初始化由編譯器進行。(短的時候直接用指令填充,長的時候就從全局字符串表拷貝),不會被放到字符串池(同樣如前,可能會從字符串池中拷貝過來)。注意不應該返回它的地址。

    cout經研究得出以下結論:

    1、對于數字指針如int *p=new int; 那么cout<<p只會輸出這個指針的值,而不會輸出這個指針指向的內容。

    2、對于字符指針入char *p="sdff";那么cout<<p就會輸出指針指向的數據,即sdf f

    ============================================================================

    如果還不是很理解,水木上也有高人對此進行解釋:

    這里的charch[]="abc";

    表示ch是一個足以存放字符串初值和空字符'/0'的一維數組,可以更改數組中的字符,但是char本身是不可改變的常量。

    char *pch ="abc";

    那么pch是一個指針,其初值指向一個字符串常量,之后它可以指向其他位置,但如果試圖修改字符串的內容,結果將不確定。

    ______ ______ ______

    ch: |abc\0 | pch: | ◎-----> |abc\0 |

    ______ ______ ______

    char chArray[100];

    chArray[i] 等價于 *(chArray+i)

    和指針的不同在于 chArray不是變量 無法對之賦值

    另 事實上 i[chArray] 也等價于 *(chArray+i)

    因此,總結如下:

    1. char[] p表示p是一個數組指針,相當于const pointer,不允許對該指針進行修改。但該指針所指向的數組內容,是分配在棧上面的,是可以修改的。

    2. char * pp表示pp是一個可變指針,允許對其進行修改,即可以指向其他地方,如pp = p也是可以的。對于*pp ="abc";這樣的情況,由于編譯器優化,一般都會將abc存放在常量區域內,然后pp指針是局部變量,存放在棧中,因此,在函數返回中,允許返回該地址(實際上指向一個常量地址,字符串常量區);而,char[] p是局部變量,當函數結束,存在棧中的數組內容均被銷毀,因此返回p地址是不允許的。

    同時,從上面的例子可以看出,cout確實存在一些規律:

    1、對于數字指針如int *p=new int; 那么cout<<p只會輸出這個指針的值,而不會輸出這個指針指向的內容。

    2、對于字符指針入char *p="sdff";那么cout<<p就會輸出指針指向的數據,即sdf f

    那么,像&(p+1),由于p+1指向的是一個地址,不是一個指針,無法進行取址操作。

    &p[1] = &p+ 1,這樣取到的實際上是從p+1開始的字符串內容。

    分析上面的程序:

    *pp ="abc";

    p[] ="abc";

    *pp指向的是字符串中的第一個字符。

    cout << pp;// 返回pp地址開始的字符串:abc

    cout << p;// 返回p地址開始的字符串:abc

    cout << *p;// 返回第一個字符:a

    cout <<*(p+1); // 返回第二個字符:b

    cout << &p[1];// 返回從第二個字符開始的字符串:bc

    Machine Learning

    分類: C++

    1.如果相同,那么將&數組名賦值或者傳遞給數組參數的時候,會提示:&數組名類型是 int (*)[],而數組類型是int [],他們不能賦值。 2.如果不同,那么為什么,sizeof(&數組名)==sizeof(數組名)?? 3.同樣,&函數名和函數名的區別 請盡量詳盡地回答,最...展開

    原來是這樣的,&數組名和數組名是不同的,根據其數據類型就知道了。問題的關鍵在于為什么sizeof(數組名)==sizeof(&數組名)呢,原因如下:

    1,數組名本質上是個地址,但不能說是指針,它能給指針賦值,是因為,指針本身的數據結構和數組名(地址)是一樣的 都是32位的int,所以這里能通過指針來對數組進行操作

    2,sizeof(數組名)為什么等于sizeof(元素類型)*元素個數呢?這個問題就好比 int i;然后sizeof(i)是一樣的道理,因為i實際上代表了一塊內存為4byte的大小,同理,a這個數組的“名字”也代表了一塊數組整體大小的內存塊,所以 sizeof(數組名)==sizeof(元素類型)*元素個數

    3,&數組名 只是個指向數組名的指針,大小為4byte(32位),所以,它和數組名不能等同

    4,vc6下sizeof(&數組名)==sizeof(數組名),我認為是錯的,因為我在linux 下用gcc測試inta[10]的結果是:sizeof(a)==40,sizeof(&a)==4,這也論證了第3點是正確的

    5,因為個人覺得gcc對ansi c支持得比較好,所以我認為vc6的編譯器在此處的處理是錯誤的

    6,以上觀點在陳正沖寫的《c語言深度解剖》等到了論證,詳見此書的第四章 指針和數組

    各位的回答都很好,謝謝各位了

    sadman000 | 瀏覽 1853 次 2011-03-16 23:24

    2011-03-17 00:50

    #團隊奪寶戰,快來開啟本次奪寶之旅吧!#

    最佳答案

    的概念

    指針是一個特殊的變量,它里面存儲的數值被解釋成為內存里的一個地址。

    要搞清一個指針需要搞清指針的四方面的內容:指針的類型,指針所指向的 類型,指針的值或者叫指針所指向的內存區,還有指針本身所占據的內存區。讓 我們分別說明。

    先聲明幾個指針放著做例子:

    例一:

    (1)int *ptr;

    (2)char *ptr;

    (3)int **ptr;

    (4)int (*ptr)[3];

    (5)int *(*ptr)[4];

    1。 指針的類型。

    從語法的角度看,你只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個指針的類型。這是指針本身所具有的類型。讓我們看看例一中各個指針的 類型:

    (1)int *ptr; //指針的類型是int *

    (2)char *ptr; //指針的類型是char *

    (3)int **ptr; //指針的類型是 int **

    (4)int (*ptr)[3]; //指針的類型是 int(*)[3]

    (5)int *(*ptr)[4]; //指針的類型是 int *(*)[4]

    怎么樣?找出指針的類型的方法是不是很簡單?

    2。指針所指向的類型。

    當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯 器將把那片內存區里的內容當做什么來看待。

    從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符 *去掉,剩下的就是指針所指向的類型。例如:

    (1)int *ptr; //指針所指向的類型是int

    (2)char *ptr; //指針所指向的的類型是char

    (3)int **ptr; //指針所指向的的類型是 int *

    (4)int (*ptr)[3]; //指針所指向的的類型是 int()[3]

    (5)int *(*ptr)[4]; //指針所指向的的類型是 int *()[4]

    在指針的算術運算中,指針所指向的類型有很大的作用。

    指針的類型(即指針本身的類型)和指針所指向的類型是兩個概念。當你對C越 來越熟悉時,你會發現,把與指針攪和在一起的"類型"這個概念分成"指針的 類型"和"指針所指向的類型"兩個概念,是精通指針的關鍵點之一。我看了不 少書,發現有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前后矛盾,越看越糊涂。

    3。 指針的值,或者叫指針所指向的內存區或地址。

    指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程序里,所有類型的指針的值都是一個32位整數,因為32位程序里內存地址全都是32位長。

    指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度為sizeof(指針所指向的類型)的一片內存區。以后,我們說一個指針的值是XX,就相當于說該指針指向了以XX為首地址的一片內存區域;我們說一個指針指向了某塊

    內存區域,就相當于說該指針的值是這塊內存區域的首地址。

    指針所指向的內存區和指針所指向的類型是兩個完全不同的概念。在例一中,指針所指向的類型已經有了,但由于指針還未初始化,所以它所指向的內存區是不存在的,或者說是無意義的。

    以后,每遇到一個指針,都應該問問:這個指針的類型是什么?指針指向的類型是什么?該指針指向了哪里?

    4。 指針本身所占據的內存區。

    指針本身占了多大的內存?你只要用函數sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據了4個字節的長度。

    指針本身占據的內存這個概念在判斷一個指針表達式是否是左值時很有用。

    第二章。指針的算術運算

    指針可以加上或減去一個整數。指針的這種運算的意義和通常的數值的加減運算的意義是不一樣的。例如:

    例二:

    1。 char a[20];

    2。 int *ptr=a;

    ...

    ...

    3。 ptr++;

    在上例中,指針ptr的類型是int*,它指向的類型是int,它被初始化為指向整 形變量a。接下來的第3句中,指針ptr被加了1,編譯器是這樣處理的:它把指針ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字節做單位的,故ptr所指向的地址由原來的變量a的地址向高地址方向增加了4個字節。

    由于char類型的長度是一個字節,所以,原來ptr是指向數組a的第0號單元開始的四個字節,此時指向了數組a中從第4號單元開始的四個字節。

    我們可以用一個指針和一個循環來遍歷一個數組,看例子:

    例三:

    int array[20];

    int *ptr=array;

    ...

    //此處略去為整型數組賦值的代碼。

    ...

    for(i=0;i<20;i++)

    {

    (*ptr)++;

    ptr++;

    }

    這個例子將整型數組中各個單元的值加1。由于每次循環都將指針ptr加1,所

    以每次循環都能訪問數組的下一個單元。

    再看例子:

    例四:

    1。 char a[20];

    2。 int *ptr=a;

    ...

    ...

    3。 ptr+=5;

    在這個例子中,ptr被加上了5,編譯器是這樣處理的:將指針ptr的值加上5 乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的單位是字節,故現在的ptr所指向的地址比起加5后的ptr所指向的地址來說,向高地址方向移動了20個字節。在這個例子中,沒加5前的ptr指向數組a的第0號單元開始的四個字節,加5后,ptr已經指向了數組a的合法范圍之外了。雖然這種情況在應用上會出問題,但在語法上卻是可以的。這也體現出了指針的靈活性。

    如果上例中,ptr是被減去5,那么處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動了20個字節。

    總結一下,一個指針ptrold加上一個整數n后,結果是一個新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的類型)個字節。就是說,ptrnew所指向的內存區將比ptrold所指向的內存區向高地址方向移動了n乘sizeof(ptrold所指向的類型)個字節。

    一個指針ptrold減去一個整數n后,結果是一個新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的類型)個字節,就是說,ptrnew所指向的內存區將比ptrold所指向的內存區向低地址方向移動了n乘sizeof(ptrold所指向的類型)個字節。

    第三章。運算?amp;和*

    這里&是取地址運算符,*是...書上叫做"間接運算符"。

    &a的運算結果是一個指針,指針的類型是a的類型加個*,指針所指向的類型是a的類型,指針所指向的地址嘛,那就是a的地址。

    *p的運算結果就五花八門了。總之*p的結果是p所指向的東西,這個東西有這些特點:它的類型是p指向的類型,它所占用的地址是p所指向的地址。

    例五:

    int a=12;

    int b;

    int *p;

    int **ptr;

    p=&a;//&a的結果是一個指針,類型是int*,指向的類型是int,指向的地址 是a的地址。

    *p=24;//*p的結果,在這里它的類型是int,它所占用的地址是p所指向的地 址,顯然,*p就是變量a。

    ptr=&p;//&p的結果是個指針,該指針的類型是p的類型加個*,在這里是int

    **。該指針所指向的類型是p的類型,這里是int*。該指針所指向的地址就是指針p自己的地址。

    *ptr=&b;//*ptr是個指針,&b的結果也是個指針,且這兩個指針的類型和所指向的類型是一樣的,所以用&b來給*ptr賦值就是毫無問題的了。

    **ptr=34;//*ptr的結果是ptr所指向的東西,在這里是一個指針,對這個指

    針再做一次*運算,結果就是一個int類型的變量。

    第四章。指針表達式。

    一個表達式的最后結果如果是一個指針,那么這個表達式就叫指針表達式。

    下面是一些指針表達式的例子:

    例六:

    int a,b;

    int array[10];

    int *pa;

    pa=&a;//&a是一個指針表達式。

    int **ptr=&pa;//&pa也是一個指針表達式。

    *ptr=&b;//*ptr和&b都是指針表達式。

    pa=array;

    pa++;//這也是指針表達式。

    例七:

    char *arr[20];

    char **parr=arr;//如果把arr看作指針的話,arr也是指針表達式

    char *str;

    str=*parr;//*parr是指針表達式

    str=*(parr+1);//*(parr+1)是指針表達式

    str=*(parr+2);//*(parr+2)是指針表達式

    由于指針表達式的結果是一個指針,所以指針表達式也具有指針所具有的四個要素:指針的類型,指針所指向的類型,指針指向的內存區,指針自身占據的內存。

    好了,當一個指針表達式的結果指針已經明確地具有了指針自身占據的內存的話,這個指針表達式就是一個左值,否則就不是一個左值。

    在例七中,&a不是一個左值,因為它還沒有占據明確的內存。*ptr是一個左值,因為*ptr這個指針已經占據了內存,其實*ptr就是指針pa,既然pa已經在內存中有了自己的位置,那么*ptr當然也有了自己的位置。

    第五章。數組和指針的關系

    如果對聲明數組的語句不太明白的話,請參閱我前段時間貼出的文?lt;<如何理解c和c++的復雜類型聲明>>。

    數組的數組名其實可以看作一個指針。看下例:

    例八:

    int array[10]=,value;

    ...

    ...

    value=array[0];//也可寫成:value=*array;

    value=array[3];//也可寫成:value=*(array+3);

    value=array[4];//也可寫成:value=*(array+4);

    上例中,一般而言數組名array代表數組本身,類型是int[10],但如果把a

    rray看做指針的話,它指向數組的第0個單元,類型是int *,所指向的類型是數組單元的類型即int。因此*array等于0就一點也不奇怪了。同理,array+3是一個指向數組第3個單元的指針,所以*(array+3)等于3。其它依此類推。

    例九:

    char *str[3]={

    "Hello,this is a sample!",

    "Hi,good morning.",

    "Hello world"

    };

    char s[80];

    strcpy(s,str[0]);//也可寫成strcpy(s,*str);

    strcpy(s,str[1]);//也可寫成strcpy(s,*(str+1));

    strcpy(s,str[2]);//也可寫成strcpy(s,*(str+2));

    上例中,str是一個三單元的數組,該數組的每個單元都是一個指針,這些指針各指向一個字符串。把指針數組名str當作一個指針的話,它指向數組的第0號單元,它的類型是char**,它指向的類型是char *。

    *str也是一個指針,它的類型是char*,它所指向的類型是char,它指向的地址是字符串"Hello,this is a sample!"的第一個字符的地址,即’H’的地址。

    str+1也是一個指針,它指向數組的第1號單元,它的類型是char**,它指向的類型是char *。

    *(str+1)也是一個指針,它的類型是char*,它所指向的類型是char,它指向"Hi,good morning."的第一個字符’H’,等等。

    下面總結一下數組的數組名的問題。聲明了一個數組TYPE array[n],則數組名稱array就有了兩重含義:第一,它代表整個數組,它的類型是TYPE [n];第二,它是一個指針,該指針的類型是TYPE*,該指針指向的類型是TYPE,也就是數組單元的類型,該指針指向的內存區就是數組第0號單元,該指針自己占有單獨的內存區,注意它和數組第0號單元占據的內存區是不同的。該指針的值是不能修改的,即類似array++的表達式是錯誤的。

    在不同的表達式中數組名array可以扮演不同的角色。

    在表達式sizeof(array)中,數組名array代表數組本身,故這時sizeof函數

    測出的是整個數組的大小。

    在表達式*array中,array扮演的是指針,因此這個表達式的結果就是數組第0號單元的值。sizeof(*array)測出的是數組單元的大小。

    表達式array+n(其中n=0,1,2,....。)中,array扮演的是指針,故arr

    ay+n的結果是一個指針,它的類型是TYPE*,它指向的類型是TYPE,它指向數組第n號單元。故sizeof(array+n)測出的是指針類型的大小。

    例十:

    int array[10];

    int (*ptr)[10];

    ptr=&array;

    上例中ptr是一個指針,它的類型是int (*)[10],他指向的類型是int [10]

    ,我們用整個數組的首地址來初始化它。在語句ptr=&array中,array代表數組本身。

    本節中提到了函數sizeof(),那么我來問一問,sizeof(指針名稱)測出的究

    竟是指針自身類型的大小呢還是指針所指向的類型的大小?答案是前者。例如:

    int (*ptr)[10];

    則在32位程序中,有:

    sizeof(int(*)[10])==4

    sizeof(int [10])==40

    sizeof(ptr)==4

    實際上,sizeof(對象)測出的都是對象自身的類型的大小,而不是別的什么類型的大小。

    第六章。指針和結構類型的關系

    可以聲明一個指向結構類型對象的指針。

    例十一:

    struct MyStruct

    {

    int a;

    int b;

    int c;

    }

    MyStruct ss=;//聲明了結構對象ss,并把ss的三個成員初始

    化為20,30和40。

    MyStruct *ptr=&ss;//聲明了一個指向結構對象ss的指針。它的類型是

    MyStruct*,它指向的類型是MyStruct。

    int *pstr=(int*)&ss;//聲明了一個指向結構對象ss的指針。但是它的

    類型和它指向的類型和ptr是不同的。

    請問怎樣通過指針ptr來訪問ss的三個成員變量?

    答案:

    ptr->a;

    ptr->b;

    ptr->c;

    又請問怎樣通過指針pstr來訪問ss的三個成員變量?

    答案:

    *pstr;//訪問了ss的成員a。

    *(pstr+1);//訪問了ss的成員b。

    *(pstr+2)//訪問了ss的成員c。

    呵呵,雖然我在我的MSVC++6.0上調式過上述代碼,但是要知道,這樣使用p

    str來訪問結構成員是不正規的,為了說明為什么不正規,讓我們看看怎樣通過指

    針來訪問數組的各個單元:

    例十二:

    int array[3]=;

    int *pa=array;

    通過指針pa訪問數組array的三個單元的方法是:

    *pa;//訪問了第0號單元

    *(pa+1);//訪問了第1號單元

    *(pa+2);//訪問了第2號單元

    從格式上看倒是與通過指針訪問結構成員的不正規方法的格式一樣。

    所有的C/C++編譯器在排列數組的單元時,總是把各個數組單元存放在連續的存儲區里,單元和單元之間沒有空隙。但在存放結構對象的各個成員時,在某種編譯環境下,可能會需要字對齊或雙字對齊或者是別的什么對齊,需要在相鄰兩個成員之間加若干?quot;填充字節",這就導致各個成員之間可能會有若干個字節的空隙。

    所以,在例十二中,即使*pstr訪問到了結構對象ss的第一個成員變量a,也不能保證*(pstr+1)就一定能訪問到結構成員b。因為成員a和成員b之間可能會有若干填充字節,說不定*(pstr+1)就正好訪問到了這些填充字節呢。這也證明了指針的靈活性。要是你的目的就是想看看各個結構成員之間到底有沒有填充字節,

    嘿,這倒是個不錯的方法。

    通過指針訪問結構成員的正確方法應該是象例十二中使用指針ptr的方法。

    第七章。指針和函數的關系

    可以把一個指針聲明成為一個指向函數的指針。

    int fun1(char*,int);

    int (*pfun1)(char*,int);

    pfun1=fun1;

    ....

    ....

    int a=(*pfun1)("abcdefg",7);//通過函數指針調用函數。

    可以把指針作為函數的形參。在函數調用語句中,可以用指針表達式來作為

    實參。

    例十三:

    int fun(char*);

    int a;

    char str[]="abcdefghijklmn";

    a=fun(str);

    ...

    ...

    int fun(char*s)

    {

    int num=0;

    for(int i=0;i

    {

    num+=*s;s++;

    }

    return num;

    )

    這個例子中的函數fun統計一個字符串中各個字符的ASCII碼值之和。前面說了,數組的名字也是一個指針。在函數調用中,當把str作為實參傳遞給形參s后,實際是把str的值傳遞給了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存儲空間。在函數體內對s進行自加1運算,并不意味著同時對str進行了自加1運算。

    第八章。指針類型轉換

    當我們初始化一個指針或給一個指針賦值時,賦值號的左邊是一個指針,賦值號的右邊是一個指針表達式。在我們前面所舉的例子中,絕大多數情況下,指針的類型和指針表達式的類型是一樣的,指針所指向的類型和指針表達式所指向的類型是一樣的。

    例十四:

    1。 float f=12.3;

    2。 float *fptr=&f;

    3。 int *p;

    在上面的例子中,假如我們想讓指針p指向實數f,應該怎么搞?是用下面的語句嗎?

    p=&f;

    不對。因為指針p的類型是int*,它指向的類型是int。表達式&f的結果是一

    個指針,指針的類型是float*,它指向的類型是float。兩者不一致,直接賦值的方法是不行的。至少在我的MSVC++6.0上,對指針的賦值語句要求賦值號兩邊的類型一致,所指向的類型也一致,其它的編譯器上我沒試過,大家可以試試。為了實現我們的目的,需要進行"強制類型轉換":

    p=(int*)&f; 如果有一個指針p,我們需要把它的類型和所指向的類型改為TYEP*和TYPE,

    那么語法格式是:

    (TYPE*)p;

    這樣強制類型轉換的結果是一個新指針,該新指針的類型是TYPE*,它指向的類型是TYPE,它指向的地址就是原指針指向的地址。而原來的指針p的一切屬性都沒有被修改。

    一個函數如果使用了指針作為形參,那么在函數調用語句的實參和形參的結合過程中,也會發生指針類型的轉換。

    例十五:

    void fun(char*);

    int a=125,b;

    fun((char*)&a);

    ...

    ...

    void fun(char*s)

    {

    char c;

    c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;

    c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;

    }

    }

    注意這是一個32位程序,故int類型占了四個字節,char類型占一個字節。函數fun的作用是把一個整數的四個字節的順序來個顛倒。注意到了嗎?在函數調用語句中,實參&a的結果是一個指針,它的類型是int *,它指向的類型是int。形參這個指針的類型是char*,它指向的類型是char。這樣,在實參和形參的結合過程中,我們必須進行一次從int*類型到char*類型的轉換。結合這個例子,我們可以這樣來想象編譯器進行轉換的過程:編譯器先構造一個臨時指針 char*temp,然后執行temp=(char*)&a,最后再把temp的值傳遞給s。所以最后的結果是:s的類型是char*,它指向的類型是char,它指向的地址就是a的首地址。

    我們已經知道,指針的值就是指針指向的地址,在32位程序中,指針的值其實是一個32位整數。那可不可以把一個整數當作指針的值直接賦給指針呢?就象下面的語句:

    unsigned int a;

    TYPE *ptr;//TYPE是int,char或結構類型等等類型。

    ...

    ...

    a=20345686;

    ptr=20345686;//我們的目的是要使指針ptr指向地址20345686(十進制

    )

    ptr=a;//我們的目的是要使指針ptr指向地址20345686(十進制)

    編譯一下吧。結果發現后面兩條語句全是錯的。那么我們的目的就不能達到了嗎?不,還有辦法:

    unsigned int a;

    TYPE *ptr;//TYPE是int,char或結構類型等等類型。

    ...

    ...

    a=某個數,這個數必須代表一個合法的地址;

    ptr=(TYPE*)a;//呵呵,這就可以了。

    嚴格說來這里的(TYPE*)和指針類型轉換中的(TYPE*)還不一樣。這里的(TYPE*)的意思是把無符號整數a的值當作一個地址來看待。

    上面強調了a的值必須代表一個合法的地址,否則的話,在你使用ptr的時候,就會出現非法操作錯誤。

    想想能不能反過來,把指針指向的地址即指針的值當作一個整數取出來。完全可以。下面的例子演示了把一個指針的值當作一個整數取出來,然后再把這個整數當作一個地址賦給一個指針:

    例十六:

    int a=123,b;

    int *ptr=&a;

    char *str;

    b=(int)ptr;//把指針ptr的值當作一個整數取出來。

    str=(char*)b;//把這個整數的值當作一個地址賦給指針str。

    好了,現在我們已經知道了,可以把指針的值當作一個整數取出來,也可以把一個整數值當作地址賦給一個指針。

    第九章。指針的安全問題

    看下面的例子:

    例十七:

    char s=’a’;

    int *ptr;

    ptr=(int*)&s;

    *ptr=1298;

    指針ptr是一個int*類型的指針,它指向的類型是int。它指向的地址就是s的首地址。在32位程序中,s占一個字節,int類型占四個字節。最后一條語句不但改變了s所占的一個字節,還把和s相臨的高地址方向的三個字節也改變了。這三個字節是干什么的?只有編譯程序知道,而寫程序的人是不太可能知道的。也許這三個字節里存儲了非常重要的數據,也許這三個字節里正好是程序的一條代碼,而由于你對指針的馬虎應用,這三個字節的值被改變了!這會造成崩潰性的錯誤。

    讓我們再來看一例:

    例十八:

    1。 char a;

    2。 int *ptr=&a;

    ...

    ...

    3。 ptr++;

    4。 *ptr=115;

    該例子完全可以通過編譯,并能執行。但是看到沒有?第3句對指針ptr進行自加1運算后,ptr指向了和整形變量a相鄰的高地址方向的一塊存儲區。這塊存儲區里是什么?我們不知道。有可能它是一個非常重要的數據,甚至可能是一條代碼。而第4句竟然往這片存儲區里寫入一個數據!這是嚴重的錯誤。所以在使用指針時,程序員心里必須非常清楚:我的指針究竟指向了哪里。

    在用指針訪問數組的時候,也要注意不要超出數組的低端和高端界限,否則也會造成類似的錯誤。

    在指針的強制類型轉換:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的類型)大

    于sizeof(ptr1的類型),那么在使用指針ptr1來訪問ptr2所指向的存儲區時是安全的。如果sizeof(ptr2的類型)小于sizeof(ptr1的類型),那么在使用指針ptr1來訪問ptr2所指向的存儲區時是不安全的。至于為什么,讀者結合例十七來想一想,應該會明白的。

    a和&a有什么區別

    2012-08-30 09:42 4379人閱讀 評論(0) 收藏 舉報

    分類:

    C++(43)

    請寫出以下代碼的打印結果,主要目的是考察a和&a的區別。

    #include<stdio.h>

    void main( void)

    {

    inta[5]={1,2,3,4,5};

    int*ptr=(int *)(&a+1);

    printf("%d,%d",*(a+1),*(ptr-1));

    return;

    }

    輸出結果:2,5。

    *(a+1)其實很簡單就是指a[1],輸出為2.

    問題關鍵就在于第二個點,*(ptr-1)輸出為多少?

    解釋如下,&a+1不是首地址+1,系統會認為加了一個整個a數組,偏移了整個數組a的大小(也就是5個int的大小)。所以int *ptr=(int *)(&a+1);其實ptr實際是&(a[5]),也就是a+5.

    原因為何呢?

    &a是數組指針,其類型為int(*)[5];

    而指針加1要根據指針類型加上一定的值,不同類型的指針+1之后增加的大小不同,a是長度為5的int數組指針,所以要加5*sizeof(int),所以ptr實際是a[5],但是ptr與(&a+1)類型是不一樣的,這點非常重要,所以ptr-1只會減去sizeof(int*),a,&a的地址是一樣的,但意思就不一樣了,a是數組首地址,也就是a[0]的地址,&a是對象(數組)首地址,a+1是數組下一元素的地址,即a[1],&a+1是下一個對象的地址,即a[5]。

    預約申請免費試聽課

    填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

    上一篇:C++培訓教程:C++ 中的智能指針知識點
    下一篇:C++基本數據類型盤點和詳解

    C語言創建windows窗口實例

    C++回調函數是什么?

    C++ shared_ptr和動態數組

    C語言有哪些關鍵詞,C語言44個關鍵詞大全

    • 掃碼領取資料

      回復關鍵字:視頻資料

      免費領取 達內課程視頻學習資料

    • 視頻學習QQ群

      添加QQ群:1143617948

      免費領取達內課程視頻學習資料

    Copyright ? 2021 Tedu.cn All Rights Reserved 京ICP備08000853號-56 京公網安備 11010802029508號 達內時代科技集團有限公司 版權所有

    選擇城市和中心
    黑龍江省

    吉林省

    河北省

    湖南省

    貴州省

    云南省

    廣西省

    海南省

    欧美三级片,白洁外传,第四色播日韩AV第一页,啪啪免费观看大全av 百度 好搜 搜狗
    <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>