经典问题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)。
=================================

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