經典問題10:指針與引用 ---指針相關問題

-------------------------------------------------------------------
經典問題10:指針與引用 ---指針基本問題
-------------------------------------------------------------------
=================================
    (1)面試題:指針和引用的差別?
答案:
★ 相同點:
1. 都是地址的概念;
指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。

★ 區別:
1. 指針是一個實體,而引用僅是個別名;
2. 引用使用時無需解引用(*),指針需要解引用;
3. 引用只能在定義時被初始化一次,之後不可變;指針可變;
4. 引用沒有 const,指針有 const,const 的指針不可變;
5. 引用不能爲空,指針可以爲空;
6. “sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大小;
但是當引用作爲成員時,其佔用空間與指針相同(沒找到標準的規定)
7. 指針和引用的自增(++)運算意義不一樣;

★ 聯繫
1. 引用在語言內部用指針實現(如何實現?)。
2. 對一般應用而言,把引用理解爲指針,不會犯嚴重語義錯誤。引用是操作受限了的指針(僅容許取內容操作)。
知識點:
引用的規則:
(1)引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。
(2)不能有NULL引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。
(3)一旦引用被初始化,就不能改變引用的關係(指針則可以隨時改變所指的對象)。
=================================
    (2)char str[] 和char *str 的區別?
答案:
1)char c[] = “hello world”;//是分配了一個局部數組;對應的是內存中的棧;
   char *c = “hello world”; //是分配了一個全局數組;對應的是內存中的全局區域;
2)全局區域的值是不能修改的,而局部區域的數據是可以修改的;
=================================
    (3)面試題:寫出const 指針,指向const的指針,指向const的const指針。
答案:
int * const
const int *
const int *  const

---------
知識點:
1.指向const對象的指針
const int *p;
這個p是一個指向int類型const對象的指針,const限定了指針p所指向的類型,而並非p本身。也就是說p本身並不是const。
在定義時不需要對它進行初始化,還可以給p重新賦值,使其指向另一個const對象。但不能通過p修改所指向對象的值。
示例1:int a=0; p=&a; 可以。
示例2:*p=20; 不可以。
結論:這種指向const對象的指針只是限制不能修改p指向對象的數值,而不是限制p指向什麼對象。
例如:函數FILE *fopen( const char *filename, const char *mode );
不能修改filename的內容。

把一個const對象的地址賦給一個不是指向const對象的指針也是不行的。
示例3:const int b=10;
int *p2=&b; //error
const int *p3=&b; //ok
結論:因爲變量b有const修飾,不能被修改。但指針p2是一個普通的指針,可以修改指向對象的值,兩種聲明矛盾,所以不合法。
而指向const對象的指針不允許修改指針指向對象的數值,所以這種方式合法。

2.const指針

int c=20;
int *const p4=&c;

指針p4稱爲const指針。它和指向const對象的指針恰好相反,它不能夠修改所指向對象,但卻能夠修改指向對象的數值。另外,這種指針在聲明時必須初始化。

3.指向const對象的const指針

const intd=30;
const int *const dp=&d;

指針dp既不能修改指向的對象,也不能修改只想對象的值。

=================================
    (4)面試題:Write in words the data type of the identifier involved in the
following definitions.
(1) float(**def)[10];
//def是一個二級指針,它指向的是一個一維數組的指針,數組的元素都是float 。
(2) double*(*gh)[10];
//gh是一個指針,它指向一個一維數組,數組元素都是double*.
(3) double(*f[10])();
//f是一個數組,f有10個元素,元素都是函數的指針,指向的函數類型是沒//有參數且返回值爲double的函數。
(4) int *((*b)[10]);
//b是一個一維數組指針,指向的數組有10個元素,每個元素也是指向int
//類型的指針。
(5) long (*fun)(int);//函數指針
(6) int (*(*F)(int,int))(int);
//F是一個函數的指針,指向的函數的類型是有兩個int參數並且返回一個
//函數指針的函數,返回的函數指針指向有一個int參數且返回int的函數;
//typedef int (*f)(int);
//f (*F)(int,int);
=================================

-------------------------------------------------------------------
經典問題:指針與引用 ---指針數組和數組指針
-------------------------------------------------------------------

=================================
    (1)寫出如下程序片段的輸出。
     1    #include <stdio.h>
     2     
     3    int main()
     4    {
     5        int a[] = {1,2,3,4,5};
     6        int *ptr = (int *)(&a+1);
     7        printf("%d /t %d /n",*(a+1),*(ptr-1));
     8        printf("%d /t %d /n",**(&a),**(&a+1));
     9        return 0;
    10    }
-----------------------
$ ./a.out
2      5
1      -1074332608
-----------------------
知識點:
時刻牢記這樣的觀點:數組名本身就是指針,再加上個&,就變成了雙指針,這裏的雙指針就是指二維數組,加1就是整體加上一行,
ptr指向a的第6個元素(實際上並不存在,但確實有這個元素地址);
=================================
-------------------------------------------------------------------
經典問題:指針與引用 ---迷途指針
-------------------------------------------------------------------
=================================
    (1)面試題 :以下代碼有什麼錯誤?會造成什麼問題?
     1        // Demonstrates a stray pointer 
     2        //注意:兩個指針可以指向同一塊內存地址 
     3          
     4        typedef unsigned short int USHORT; 
     5        #include <iostream> 
     6        int main() 
     7        { 
     8            USHORT * pInt = new USHORT; 
     9            *pInt = 10; 
    10          std::cout << "*pInt: " << *pInt << '/t' 
    11                  <<"pInt address: " << pInt << std::endl; 
    12           delete pInt; 
    13            
    14           long * pLong = new long;  //pLong應該被分配到了原pInt指向的地址  
    15           *pLong = 90000; 
    16           std::cout << "*pLong: " << *pLong << '/t' 
    17                   <<"pLong address: " << pLong << std::endl; 
    18            
    19            *pInt = 20; // uh oh, this was deleted! 
    20            
    21           std::cout << "*pInt: " << *pInt << '/t' << pInt << std::endl; 
    22           std::cout << "*pLong: " << *pLong << '/t'   <<pLong <<std::endl; 
    23          delete pLong; 
    24           
    25           return 0; 
    26      } 
-----------
結果:
8$ ./a.out
*pInt: 10    pInt address: 0x804a008
*pLong: 90000    pLong address: 0x804a008
*pInt: 20    0x804a008
*pLong: 65556    0x804a008
-----------
分析:
在程序清單中,pLong指向的內存的值變爲65556的過程如下:
1.指針pInt指向某個內存塊,並將10存儲到該內存塊中。
2.將delete用於指針pInt,這相當於告訴編譯器,可以將內存塊用於存儲其它東西。然後指針pLong指向該內存塊。
3.將90000賦給*pLong。在這個例子中使用的計算機按字節交換順序存儲4字節值90000(00 01 5f 90),因此被存儲爲 90 5f 01 00。
4.將20(十六進制表示爲00 14)賦給*pInt。由於pInt仍然指向原來的地址,因此pLong的前兩個字節被覆蓋,變成了14 00 01 00。
5.打印*pLong的值,將字節反轉爲正確的順序00 01 00 14(65536+20),然後被轉換爲65556
知識點:
    1)在C++ 中導致難以發現和解決的錯誤的罪魁禍首是迷途(stray)指針。
迷途指針也被稱爲失控(wild)指針或懸浮(dangling)指針,是將 delete用於指針(從而釋放它指向的內存),
但沒有將它設置爲空時引發的。如果隨後你在沒有重新賦值的情況下使用該指針,後果將是不可預料的:程序崩潰算你走運。
總之,對指針delete後就不要再使用它。雖然這個指針仍然指向原來的內存區域 ,但編譯器可能已經將其它數據存儲在這裏。
不重新給這個指針賦值就再次使用它可能導致程序崩潰;更糟糕的是,程序可能表面上運行正常,但過不了幾分鐘就崩潰了。
這被稱爲定時炸彈。爲了安全起見,刪除指針後,把其設置空(0),這樣便解除了它的武裝。
    2) little-endian big-endian
簡而言之:所謂的little-endian,就是我們在學習彙編時候的高高低低原則,而big-endian就是剛剛相反。
Big endian machine: It thinks the first byte it reads is the biggest.
Little endian machine: It thinks the first byte it reads is the littlest.
舉個例子,從內存地址0x0000開始有以下數據
0x0000        0x12
0x0001        0x34
0x0002        0xab
0x0003        0xcd
如果我們去讀取一個地址爲0x0000的四個字節變量,若字節序爲big-endian,則讀出結果0x1234abcd;若字節序位little-endian,則讀出結果爲0xcdab3412.

如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
        big-endian        little-endian
0x0000        0x12            0xcd
0x0001        0x34            0xab
0x0002        0xab            0x34
0x0003        0xcd            0x12
x86系列CPU都是little-endian的字節序.
    3)討論:迷途指針和空指針
    聲明定義一個指針myPtr,然後將delete用於該指針,實際上是讓編譯器釋放內存,但指針本身依然存在。它現在是個迷途指針。
    如果接下來使用myPtr = 0; 該迷途指針將變爲空指針。
    通常如果對指針使用delete後再次對其使用delete,後果是不確定的,也就是說,任何事情都有可能發生。
    但對空指針使用delete,什麼也不會發生,這樣做是安全的。
    使用迷途指針或空指針都是非法的(illegal),例如寫myPtr=5; 可能導致程序崩(crash)。
    使用空指針,程序也將崩潰,但相對於迷途指針來說,可預測的崩潰更可取,因爲這更容易調試。
=================================
    (2)面試題:C++中有了malloc/free,爲什麼還需要new/delete?
答案:
    1)malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。
 對於非內部數據類型的對象而言,光用maloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象
 在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造
 函數和析構函數的任務強加於malloc/free。因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及
 一個能完成清理與釋放內存工作的運算符delete。注意new/delete不是庫函數。所以我們不要企圖用malloc/free來完
 成動態對象的內存管理,應該用new/delete。由於內部數據類型的“對象”沒有構造與析構的過程,對它們而言malloc/free和new/delete是等價的。
    2)既然new/delete的功能完全覆蓋了malloc/free,爲什麼C++不把malloc/free淘汰出局呢?
    這是因爲C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。
    如果用free釋放“new創建的動態對象”,那麼該對象因無法執行析構函數而可能導致程序出錯。
    如果用delete釋放“malloc申請的動態內存”,理論上講程序不會出錯,但是該程序的可讀性很差。所以new/delete必須配對使用,malloc/free也一樣。
=================================
    (3)句柄,指針,智能指針的區別與聯繫是什麼?
答案:
    1)句柄是一個32位的整數,實際上是Windows在內存中維護的一個對象內存物理地址列表的整數索引。因爲Windows的內存管理經常會將空閒對象的內 存釋放掉,
    當需要時訪問再重新提交到物理內存,所以對象的物理地址是變化的,不允許程序直接通過物理地址來訪問對象。程序將想訪問的對象的句柄傳遞給系統,系統根據句柄檢索自己維護的對象列表就能知道程序想訪問的對象及物理地址了。
    2) 句柄是一種指向指針的指針,句柄簡而言之就是指向結構的指針。並且很有可以是與系統有關的,並且句柄中的某一些項,是與系統進行交互的。
    3)指針它也可以指向一個結構,但是通常是用戶定義的,所以的必需的工作都要用戶完成,特別是在刪除的時候。
    4)智能指針是存儲指向動態分配(堆)對象指針的類, 用於生存期控制, 能夠確保自動正確的銷燬動態分配的對象,防止內存泄露。它的一種通用實現技術是使用引用計數(reference count)。
=================================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章