一、基本知识

指针和引用的声明方式:

声明指针: char* pc;

声明引用: char c = 'A'

                  char& rc = c;

它们的区别:

①从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。

②从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。

③从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。

④不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。

⑤理论上,对于指针的级数没有限制,但是引用只能是一级。如下:

  int** p1;         // 合法。指向指针的指针

  int*& p2;         // 合法。指向指针的引用

  int&* p3;         // 非法。指向引用的指针是非法的

  int&& p4;         // 非法。指向引用的引用是非法的

  注意上述读法是从左到右。

 

程序1:

#include "stdio.h"

int main(void)

{

  // 声明一个char型指针pc,且让它指向空值

  char* pc = 0;

  char a = 'a';

  // 声明一个引用rc,且让它引用变量a

  char& rc = a;

  printf("%d, %c\n", pc, rc);

 

  char *pc2;

  // 声明一个指针,但可以不初始化

  pc2 = pc;

 

  // char& rc2;

  // 上面语句编译时,会产生如下错误:

  // error C2530: 'rc2' : references must be initialized

  // 即,应用必须初始化

  // rc = *pc;

  // 上面语句编译不会有问题,但运行时,会报如下错误:

  // "0x00401057"指令引用的"0x00000000"内存。该内存不能为"read"

  // 说明引用在任何情况下,都不能指向空值

 

  return 0;

}

程序2:

#include <iostream>

#include <string>

using namespace std;

int main(void)

{

  string s1("Hello");

  string s2("World");

  // printf("%s\n", s1); 不能用printf输出s1,而应该用cout

 

  cout << "s1的地址 = "<< &s1 << endl;                        // &s1 = 0012FF64

  cout << "s2的地址 = "<< &s2 << endl;                        // &s2 = 0012FF54

 

  string& rs = s1;                                                           // 1. 定义一个引用rs,rs引用s1

  cout << "引用rs的地址 = " << &rs << endl;                  // &rs = 0012FF64

 

  string* ps = &s1;                                                         //定义一个指针ps, ps指向s1

  cout << "指针ps的地址 = " << ps << endl;                    // ps = 0012FF64

 

  cout << rs << ", " << *ps << endl;                          // Hello, Hello

  // 如果没有#include <string>,上面的语句在编译的时候,会出现如下错误:

  // error C2679: binary '<<' : no operator defined which takes a right-

  // hand operand of type 'class std::basic_string<char,struct

  // std::char_traits<char>,class std::allocator<char> >'

  // (or there is no acceptable  conversion)

 

  rs = s2;                                                      // 2. rs仍旧引用s1, 但是s1现在的值是"World"

  ps = &s2;                                                   // ps现在指向s2

 

  cout << "引用rs的地址 = " << &rs << endl;                  // &rs = 0012FF64 未改变

  cout << "引用rs的值 = " << rs << endl;                       // rs = "World" 已改变

 

  cout << "指针ps的地址 = " << ps << endl;                        // ps = 0012FF54  已改变

  cout << "指针ps所指地址的内容 = " << *ps << endl;          // *ps = World    已改变

 

  cout << "s1的地址 = "<< &s1 << endl;                        // 3. &s1 = 0012FF64 未改变

  cout << "s1的值 = " << s1 << endl;                             // 4. s1 = World  已改变

 

  return 0;

}

 

可以认为:

引用就是变量的别名,在引用初始化的时候就已经确定,以后不能再改变。见程序2的粗体字语句。第1句,声明了rs引用s1,s1的值为”Hello”,从这以后,rs实际上就相当于变量s1了,或者从更本质的意义上来说,rs的地址就是初始化时s1的地址了,以后都不会再改变。这应该比较好理解,比如我们在程序中定义了一个变量a,不管我们如何给a赋值,但它的地址是不会改变的;

 

第2句,rs仍旧指向初始化时s1的地址,但此处的赋值就相当于重新给s1赋值,因此我们从第3句和第4句可以看到,s1的地址并没有发生变化,但是其值已经发生了变化。

二、作为参数传递

利用引用的这个特性,可以用它作为函数的传出参数。如程序3:

#include <iostream>

#include <string>

using namespace std;

int newEvaluation(string& aStr)

{

  string bStr("Hello,");

  aStr = bStr + aStr;

 

  return 0;

}

 

int main(void)

{

  string aStr("Patrick!");

  newEvaluation(aStr);

  std::cout << aStr << endl;                      // 输出结果:"Hello, Patrick!"

 

  return 0;

}

 

而一般变量,则不能从函数内部传值出来,比如程序4:

#include <iostream>

#include <string>

using namespace std;

 

int newEvaluation(string aStr)

{

  string bStr("Hello,");

  aStr = bStr + aStr;

 

  return 0;

}

 

int main(void)

{

  string aStr("Patrick!");

  newEvaluation(aStr);

  std::cout << aStr << endl;                     // 输出结果:"Patrick!",aStr的值没有变化

 

  return 0;

}

 

当然程序3引用传递的方式也可以写成指针传递的方式,如程序5:

#include <iostream>

#include <string>

using namespace std;

 

int newEvaluation(string* const aStr)

{

  string bStr("Hello,");

  *aStr = bStr + *aStr;

 

  return 0;

}

 

int main(void)

{

  string aStr("Patrick!");

  newEvaluation(&aStr);

  std::cout << aStr << endl;                      // 输出结果:"Hello, Patrick!"

 

  return 0;

}

 

注意程序中的陷井,如程序6:

#include <iostream.h>

int *pPointer;

void SomeFunction()

{

  int nNumber;

  nNumber = 25;

  //让指针指向nNumber

  pPointer = &nNumber;

}

 

void main()

{

  SomeFunction();    //为pPointer赋值

  //为什么这里失败了?为什么没有得到25

  cout << "Value of *pPointer: " << *pPointer << endl;

}

 

这段程序先调用了SomeFunction函数,创建了个叫nNumber的变量,接着让指针pPointer指向了它。可是问题出在哪儿呢?当函数结束后,nNumber被删掉了,因为这一个局部变量。局部变量在定义它的函数执行完后都会被系统自动删掉。也就是说当SomeFunction 函数返回主函数main()时,这个变量已经被删掉,但pPointer还指着变量曾经用过的但现在已不属于这个程序的区域。

 

尽管在SomeFunction中使用所谓的动态分配内存。程序7中也存在陷井:

#include <iostream.h>

int *pPointer;

 

void SomeFunction()

{

    int intNumber = 25;

    // 让指针指向一个新的整型

    pPointer = new int;

    pPointer = &intNumber;

}

 

void main()

{

    SomeFunction();                                           // 为pPointer赋值

    cout<< "Value of *pPointer: " << *pPointer << endl;

    delete pPointer;

}

原因也如上面所言,intNumber的作用范围仅限于SomeFunction中,离开了SomeFunction,那么intNumber就不存在了,那么&intNumber即intNumber的地址就变得没有意义了,因此,该地址所指向的值是不确定的。如果改为下面的程序就不会有问题了。

程序8:

#include <iostream.h>

int *pPointer;

 

void SomeFunction()

{

    int intNumber = 25;

    // 让指针指向一个新的整型

    pPointer = new int(intNumber);

}

 

void main()

{

    SomeFunction();                                           // 为pPointer赋值

    cout<< "Value of *pPointer: " << *pPointer << endl;

    delete pPointer;

}

三、指针的指针

前面说到,指针是没有级数限制的。

程序9:

#include<stdio.h>

#include<stdlib.h>

 

void main(void)

{

    int i, j;

    int a[10], b[3][4], *p1, *p2, **p3;   

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

       scanf("%d", &a[i]);               

 

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

       for(j = 0; j < 4; j++)

           scanf("%d", &b[i][j]);         

 

    p1 = a;

    p3 = &p1;

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

       printf("%4d", *(*p3+i));            

    printf("\n");

 

    for(p1 = a; p1 - a < 10; p1++)    

    {

       p3 = &p1;

       printf("%4d", **p3);

    }

    printf("\n");

 

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

    {

       p2 = b[i];

       p3 = &p2;

       for(j = 0; j < 4; j++)

       printf("%4d",*(*p3+j));

       printf("\n");

    }

 

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

    {

       p2 = b[i];

       for(p2 = b[i]; p2-b[i] < 4; p2++)

       {

           p3 = &p2;

           printf("%4d", **p3);

       }

       printf("\n");

    }

}

输出的结果:

    1   2   3   4   5   6   7   8   9   10

    1   2   3   4   5   6   7   8   9   10

    11  12  13  14

    15  16  17  18

    19  20  21  22

    11  12  13  14

    15  16  17  18

    19  20  21  22

四、函数指针和函数引用

函数指针是C++最大的优点之一。和使用普通指针相比,高级程序员只要有可能都更愿意使用引用,因为引用更容易处理一些。然而,当处理函数时,函数引用对比函数指针就未必有这个优势了。现有的代码很少使用函数引用。下面将向介绍如何函数指针、如何使用函数引用以及分别在什么情况下使用它们。

① 函数指针的例子

#include <iostream>

void print(int i)

{

    std::cout << i << std::endl;

}

 

void multiply(int& nDest, int nBy)

{

    nDest *= nBy;

}

 

void print_something()

{

    std::cout << "something" << std::endl;

}

 

int sayHello()

{

    std::cout << "Hello, World!" << std::endl;

    return 10;

}

 

int main()



    void (*pFunction_1)(int);

    pFunction_1 = &print;

    pFunction_1(1);

    // 输出结果为1

 

    void (*pFunction_2)(int&, int) = &multiply;

    int i = 1;

    pFunction_2(i, 10);

    std::cout << "i = " << i << std::endl;

    // 输出结果为10

   

    void (*pFunction_3)();

    pFunction_3 = &print_something;

    pFunction_3();

    // 输出结果为something

 

    int (*pFunction_4)();

    pFunction_4 = &sayHello;

    int a = pFunction_4();

    // 输出结果为Hello, World!

    std::cout << a << std::endl;

    // 输出结果为10

   

    return 0;

}

 

② 函数引用的例子

#include <iostream>

void print(int i)

{

    std::cout << i << std::endl;

}

 

void print2(int i)

{

    std::cout << i << std::endl;

}

 

void multiply(int& nDest, int nBy)

{

    nDest *= nBy;

}

 

void print_something()

{

    std::cout << "something" << std::endl;

}

 

int sayHello()

{

    std::cout << "Hello, World!" << std::endl;

    return 10;

}

 

 

int main()

{  

    // void (&rFunction_1)(int);

    // 错误:未初始化引用!引用必须初始化

 

    void (&rFunction_2)(int) = print;

    rFunction_2(1);

    // 输出1

 

    rFunction_2 = print2;

    rFunction_2(2);

    // 输出2

 

    void (&rFunction_3)(int&, int) = multiply;

    int i = 1;

    rFunction_3(i, 10); 

    std::cout << i << std::endl;

    // 输出10

 

    void (&rFunction_4)() = print_something;

    rFunction_4();

    // 输出something

 

    int (&rFunction_5)();

    rFunction_5 = sayHello;

    int a = rFunction_5();   // 输出Hello, World!

    std::cout << a << std::endl;

    // 输出10

 

    return 0;

}

 

③ 函数指针和函数引用作为函数参数

#include <iostream>

 

void print(int i)

{

    std::cout << i << std::endl;

}

 

void print2(int i)

{

    std::cout << i * 2 << std::endl;

}

 

void printSomething()

{

    std::cout << "Something" << std::endl;

}

 

void sayHello()

{

    std::cout << "Hello, World!" << std::endl;

}

 

void call_p_func(void (*func)(int))

{

    func(1);

    func(2);

    func(3);

}

 

void call_r_func(void (&func)(int))

{

    func(1);

    func(2);

    func(3);

}

 

void call_p_function(void (*func)())

{

    func();

}

 

int main()



    std::cout << "函数指针作为参数" << std::endl;

    call_p_func(&print);

    call_p_func(&print2);

    call_p_function(&printSomething);

    call_p_function(&sayHello);

    call_p_function(sayHello);

    // 上面两句对于某些编译器来说是一样的,但是推荐使用前者的写法,

    // 这样可以是程序的可读性更好一些

 

    std::cout << "函数引用作为参数" << std::endl;

    call_r_func(print);

    call_r_func(print2);

 

    return 0;

}

 

总结:

函数指针的声明使用方式:

<想要指向的函数之返回类型>(*函数指针的名称)<想要指向的函数之参数类型…>

如要想声明一个函数指针指向以下函数:

void print(int i)

{

    std::cout << i << std::endl;

}

那么就可以如下操作:

void (*pFunction)(int);

然后如下用函数的地址给pFunction赋值:

pFunction = &print;

在然后,pFunction就可以和函数print一样使用了,比如,

pFunction(1);

等等。

 

函数引用的声明和使用方式:

<欲引用的函数之返回类型>(&函数引用的名称)<欲引用的函数之参数类型…>=<欲引用的函数的名称>,至所以如此,是引用在声明的时候必须初始化,引用不能指向空值。

如要想声明一个函数引用指向以下函数:

void print(int i)

{

    std::cout << i << std::endl;

}

那么就可以如下操作:

void (&rFunction)(int)=print;

在然后,rFunction就可以和函数print一样使用了,比如,

rFunction(1);

等等。

五、const修饰指针和引用

大致而言,const修饰指针和引用分三种情况,即const修饰指针、const修饰引用和const修饰指针的引用。下面分别讨论之。

 

① const修饰指针

   const修饰指针又分为三种情况,即const修饰指针本身、const修饰指针所指的变量(或对象)以及const修饰指针本身和指针所指的变量(或对象)。

 

    a. const修饰指针本身

    在这种情况下,指针本身是常量,不能改变,任何修改指针本身的行为都是非法的,例如:

    double pi = 3.1416;            

    double* const PI = &pi;

 

    double alpha = 3.14;

    PI = &alpha;                   // 错误。因为指针PI是常量,不能再被改变。

    *PI = alpha;                   // OK。虽然指针PI不能被改变,但指针所指的变量或者对象可变。

   

    b. const修饰指针指向的变量(或对象)

    在这种情况下,指针本身可以改变,但const所修饰的指针所指向的对象不能被改变,例如:

    double pi = 3.1416;

    const double* PI = &pi;

 

    double alpha = 3.14;

    *PI = alpha;    // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。

    PI = &alpha;    // OK。虽然指针所指的内容不能被改变,但指针PI本身可改变。从而通过这种方式改变*PI。

    

    c. const修饰指针本身和指针所指向的变量(或对象)

    在这种情况下,指针本身和指针指向的变量(或对象)均不能被改变,例如:

    double pi = 3.146;

    const double* const PI = &pi;

    //double const* const PI = &pi;

    cout << "PI = " << PI << endl;

    cout << "*PI = " << *PI << endl;

 

    double alpha = 3.14;

    //*PI = alpha;                 // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。

    //PI = &alpha;                 // 错误。因为指针PI是常量,不能再被改变。

 

② const修饰引用

   const修饰引用没有指针修饰指针那么复杂,只有一种形式。引用本身不能被改变,但所指向的对象是可以被改变的,见上面“一、基本知识”。

    double pi = 3.1416;

    //const double& PI = pi;

    double const& PI = pi;                  //和上面一句是等价的

    //double& const PI = pi;                //有问题。很多编译器会产生warning

    cout << PI << endl;

 

③ const修饰指针引用

   我们用例子来说明。

   double pi = 3.14;

   const double* pPI = &pi;

   //const double*& rPI = &pi;             //错误。不能将double* 转换成const double *&

   const double*& rPI = pPI;               //OK。声明指针引用的正确方法

说明:const double*& rPI = &pi; 为什么会出现错误呢?我们知道,引用是被引用对象的别名,正因为如此,由于rPI是pPI的别名,因此rPI和pPI的类型必须完全一致。从上面的代码段我们可以看到,rPI的类型是const double*,而&pi的类型是double*,因此这句程序是错误的。

 

下面这段代码和 ① 中的b中的情形对应(即内容不可变,指针可变):

double pi = 3.1416;

double api = 3.14;

const double* pPI = &pi;

const double* pAPI = &api;

const double*& rPI = pPI;

const double*& rAPI = pPI;

*rAPI = api;                 // 错误。指针所指向的值不能被直接改变

rAPI = pAPI;               // OK。指针本身可以被改变

指针引用的用法还有其它的情形,由于罕用,故此不谈及。

C/C++中指针和引用之相关问题研究的更多相关文章

  1. 转 浅谈C++中指针和引用的区别

    浅谈C++中指针和引用的区别 浅谈C++中指针和引用的区别   指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性 ...

  2. 转贴:C++中指针和引用的区别

    从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有 ...

  3. C++中指针和引用、数组之间的区别

    指针指向一块内存,它的内容是所指内存的地址:而引用则是某块内存的别名,引用初始化后不能改变指向.使用时,引用更加安全,指针更加灵活. 初始化.引用必须初始化,且初始化之后不能呢改变:指针可以不必初始化 ...

  4. C++ 中指针与引用的区别

    指向不同类型的指针的区别在于指针类型可以知道编译器解释某个特定地址(指针指向的地址)中的内存内容及大小,而void*指针则只表示一个内存地址,编译器不能通过该指针所指向对象的类型和大小,因此想要通过v ...

  5. C++中指针和引用的区别

    ①指针可以为空,引用不能为空: ②指针可以被赋值,引用必须在声明时赋值,之后不能被赋值: ③指针可以指向堆中空间,引用不能指向堆中空间,如int &p=new int; 会编译出错.

  6. C语言中 指针、引用和取值

    指针是一个存储计算机内存地址的变量.从指针指向的内存读取数据称作指针的取值.指针可以指向某些具体类型的变量地址,例如int.long和double.指针也可以是void类型.NULL指针和未初始化指针 ...

  7. 浅谈C++中指针和引用的区别者之间的区别和用法(转)

    指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个 ...

  8. 浅谈C++中指针和引用的区别

    指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个 ...

  9. 在C++中指针和引用传值区别

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

随机推荐

  1. Tomcat启动失败原因

    Tomcat启动失败原因 一.Tomcat启动时,要求被加载的项目必须拥有独立路径名称 发布的两个工程具有相同路径名称,这是不允许. 二.8080端口号已经被占用了 启动失败原因,就是8085端口上, ...

  2. schedule of 2016-11-7~2016-11-10(Monday~Thursday)——1st semester of 2nd Grade

    most important things to do 1.joint phd preparations 2.journal paper to write 3.solid fundamental kn ...

  3. Python - 线性回归(Linear Regression) 的 Python 实现

    背景 学习 Linear Regression in Python – Real Python,前面几篇文章分别讲了"regression怎么理解","线性回归怎么理解& ...

  4. Map2Shp7专业版新增功能

    Map2Shp7专业版产品在上一版基础上,新增并优化了用户界面.转换对象.专业数据模型等相关功能.具体新增特性如下: 用户界面采用最新流行的Microsoft Office 2016 风格的界面(Ri ...

  5. window 10 安装Oracle odac 64位

    下载地址:https://www.oracle.com/cn/database/technologies/windows/downloads.html 可以下载XCopy版,也可以继续往下看下载安装文 ...

  6. 怎么将文件夹上传到GitHub上

    1. 在GitHub上新建一个仓库地址: http://github.com/......git 2. 在需要上传的文件夹目录下,运行 git   init  初始化git: 3. 运行git  ad ...

  7. 关于SDWebImage的一点小坑

        做项目遇到一个问题,是用sd加载图片,明明本地有图片,使用sd的内部方法也可以拿到那些个图片,但是就是加载缓慢,如果网络还行,网络加载图片都比加载本地图片快.而使用[[SDImageCache ...

  8. JAVA中常用的异常处理方法

    1.在Java项目中经常遇到的异常情况 算术异常类:ArithmeticExecption 空指针异常类:NullPointerException 类型强制转换异常:ClassCastExceptio ...

  9. pycharm 安装vue

    1.设置JS为ES6 2.安装vue.js 3.重启pycharm 4.检查

  10. Java入门 - 语言基础 - 13.Character类

    原文地址:http://www.work100.net/training/java-character.html 更多教程:光束云 - 免费课程 Character类 序号 文内章节 视频 1 概述 ...