C语言最富有迷幻色彩的部分当属指针部分,无论是指针的定义还是指针的意义都可算是C语言中最复杂的内容。指针不但提供给了程序员直接操作硬件部分的操作接口,还提供给了程序员更多灵活的用法。C++继承这一高效的机制,同时引入了另一个与指针相似但不相同的机制: 引用。

一、引用

  简单的来说,引用就是变量的别名(alias), 通过别名我们可以操作引用代表的变量。 定义一个引用的语法如下所示:

    变量类型   &引用标识符 = 变量名。

Exp:

  1.   int iVar=;
  2.  
  3.   int &iRef = iVar;
  4.  
  5.   iRef = ;
  6.  
  7.   cout<<iVar<<endl;

  这段程序执行的结果就是输出: 20 ;

  程序通过引用 iRef 改变了变量iVar的值。

要点:

  1、在定义引用的同事必须初始化,指出引用代表的是哪一个变量,而且这种“指向关系”不能改变。

  2、引用只是对象的另一个名字,可以通过对象的原标识符访问对象,也可以通过对象的引用访问对象。

  3、在一个语句定义多个引用的时候,每个引用标识符(引用名)的前面必须都加上&符号,否则就是错误。

1、const引用

  const引用是指向const对象的引用, 不能通过const引用改变原对象的值。如下所示:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. const int iVar=;
  16. const int &iRef = iVar;
  17. iRef = ;
  18. cout<<iVar<<endl;
  19.  
  20. return ;
  21. }

上面的程序编译的结果如下所示:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. test.cpp: In function int main()’:
  3. test.cpp:: 错误:assignment of read-only reference iRef

可以发现在第17行,试图对一个指向const对象的const引用赋值,结果编译报错。

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. const int iVar=;
  16. const int &iRef = iVar;
  17. iRef = ;
  18.  
  19. int &iRef1 = iVar;
  20. cout<<iVar<<endl;
  21.  
  22. return ;
  23. }

程序编译结果如下:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. test.cpp: In function int main()’:
  3. test.cpp:: 错误:assignment of read-only reference iRef
  4. test.cpp:: 错误:将类型为 int&’ 的引用初始化为类型为 const int 的表达式无效

我们发现在程序编译的时候第19行也报错啦,报错的类型是: 将 类型int &的引用初始化类型const int的表达式无效。

2、字面值引用

   可以定义const引用代表字面值。实例如下:

  1. int main()
  2. {
  3. int const &iRef = ;
  4. const string &strRef = "volcanol";
  5. cout << iRef <<endl;
  6. cout << strRef <<endl;
  7.  
  8. return ;
  9. }

程序的执行结果如下:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. [root@localhost cpp_src]# ./a.out
  3.  
  4. volcanol

上面的实例注意一点: 要对字面值定义别名引用,则必须将别名引用定义为const型的,否则将出现编译错误。

二、指针

  指针是什么,有的地方说是指针是一个地址。这里我们不对指针的复杂用法进行讨论,如果想了解指针的复杂用法可以产考我在园子里的另外一篇随笔,链接地址

为:http://www.cnblogs.com/volcanol/archive/2011/06/05/2073042.html

1、指针的定义

  在C++中定义指针,很简单,在定义的变量的时候,在变量的前面加上一个 * 就表示要定义一个指针变量。语法如下:

  1. 指针要指向的数据类型 * 指针变量名;

Exp:

  int  *pInt;  定义了一个指向整型变量的指针变量pInt;

  string *pStr;  定义了一个指向string类型的对象的指针pStr;

  vector<int>  *pVectorInt; 定义一个指向vector<int> 容器的指针。

  bitset<5>     *pBitset5;  定义一个指向bitset<5>类型的对象的指针。

2、指针变量赋值和初始化

  指针变量在使用前必须有一个确定的指向,否则就会造成一个游离的指针,操作的游离指针会得到一个意想不到的的结果。通过取得一个变量的地址然后赋值给

指针变量或者初始化指针变量使指针变量有一个确定的指向。  通过操作符 & 取得一个变量/对象的地址或者(指针)。

  指针变量初始化:

  1.       int iVar = ;
  2.       int *pInt = &iVar;

  指针变量赋值:

  1.       int iVar = ;
  2.       int *pInt1;
  3.       int *pInt2;
  4.       pInt1 = &iVar;
  5.       pInt2 = pInt1;

3、指针的引用

  通过解引用操作符 * 可以引用指针指向的变量。

  1.   int iVar = ;
  2.  
  3.   int *pInt = NULL;
  4.  
  5.   pInt = &iVar;
  6.  
  7.   cout<< * pInt<<endl;

Exp:

  1. int main()
  2. {
  3. int iVar = ;
  4. int *pInt = &iVar;
  5. cout<<(*pInt)<<endl;
  6.  
  7. string strVar = "volcanol";
  8. string *pStr = &strVar;
  9. cout<<(*pStr)<<endl;
  10.  
  11. vector<int> vInt();
  12. vector<int> *pVecInt=&vInt;
  13. cout<<(*pVecInt)[]<<endl;
  14.  
  15. bitset<> bitVar();
  16. bitset<> *pBitset5 = &bitVar;
  17. cout<< (*pBitset5) <<endl;
  18.  
  19. return ;
  20. }

程序的执行结果如下所示:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. [root@localhost cpp_src]# ./a.out
  3.  
  4. volcanol

要点:

  在定义指针变量的时候,必须在每个指针变量的前面都加上 * ,否则定义的就是一个非指针变量。

  int  *pInt1,pInt2; //pInt1 为指针变量,  pInt2为整型变量。

  在定义指针变量的时候,有两种风格的格式:   int  *pInt 和  int*  pInt; 这两种格式没有对错之分,两种格式C++都是接受的,只是在理解的时候可能会引起

误解。为了避免误解,在一个程序里面,最好选取一种格式一直保持下去。

4、指针的指针

  指针变量也是一种对象,同样可以给指针变量定义一个指向它的指针,就是指针的指针。定义语法如下:

    指针的指针变量指向的对象类型  **指针的指针变量标识符;

Exp:

  1.   int iVar = ;
  2.  
  3.   int *pInt = &iVar;
  4.  
  5.   int **ppInt = &pInt;

如上就定义了一个指向整型指针变量的指针变量ppInt;   ppInt指向的对象的类型为 int* 类型的对象。

  1. int main()
  2. {
  3. int iVar = ;
  4. int *pInt = &iVar;
  5. int **ppInt = &pInt;
  6.  
  7. cout <<"iVar ="<< iVar<<endl;
  8. cout <<"int *pInt = &iVar,then *pInt ="<<*pInt<<endl;
  9. cout <<"int **ppInt = &pInt,then *ppInt="<<*ppInt;
  10. cout <<";and then **ppInt="<<**ppInt<<endl;
  11.  
  12. return ;
  13. }

程序的执行结果如下所示:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. [root@localhost cpp_src]# ./a.out
  3. iVar =
  4. int *pInt = &iVar,then *pInt =
  5. int **ppInt = &pInt,then *ppInt=0xbfb949f8;and then **ppInt=

5、通过指针访问数组元素

  这里需要说明一个细节:  某一个数组的数组名是一个常量,而且数组名表示的是数组的第一个元素的首地址,同时数组元素在内存中是连续存放的。

正是因为数组具有上述的特点,才能方便的通过指针来访问数组的元素。

  通过指针访问数组元素的例子如下:

  1. int main()
  2. {
  3. int iArray[] = {,,,,};
  4. int *pInt = iArray;
  5.  
  6. cout << *pInt << endl; //
  7. cout << pInt[]<<endl; //
  8. cout << *++pInt<<endl; //
  9. cout << *pInt++<<endl; //
  10. cout << *pInt<<endl ; //
  11.  
  12. return
    }

程序的执行结果如下所示:

  1. [root@localhost cpp_src]# ./a.out

不但可以通过++运算符来改变指针的指向,指针还支持加整数和减整数运算,同时支持两个指针的减法运算。

  1. int main()
  2. {
  3. int iArray[] = {,,,,};
  4. int *pInt1 = iArray;
  5. int *pInt2= &iArray[];
  6.  
  7. cout <<*(pInt1 + )<<endl; //
  8. cout <<*(pInt2 - )<<endl; //
  9. cout << pInt2 - pInt1 <<endl;
  10.  
  11. return ;
  12. }

程序的执行结果如下:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. [root@localhost cpp_src]# ./a.out

要点:

  可以发现这个地方  pInt2 - pInt1 的结果是4, 这个结果与C语言的输出是存在差别的。这一点要非常注意,在指针与数组结合使用的过程中,两个指针相减

是经常见到的操作,因此这个地方需要注意。

  

  通过上面的实例,我们可知利用指针可以很方便的访问数组的元素,因此我们可以通过指针遍历整个数组。

  1. int main()
  2. {
  3. int iArray[] = {,,,,};
  4.  
  5. for(int *pBegin=iArray,*pEnd=iArray+; pBegin != pEnd; ++pBegin)
  6. cout<<*pBegin<<endl;
  7.  
  8. return ;
  9. }

程序的执行结果如下所示:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. [root@localhost cpp_src]# ./a.out

  指针和数组之间的定义还包括* 和 [] 符号同时在定义中出现的情况,

Exp:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. int iArray_1[] = {,,,,};
  16. int iArray_2[] = {};
  17. int *pInt1[] ={iArray_1, iArray_2};
  18. int (*pInt2)[] = iArray_1; //error
  19. pInt2 = iArray_2; //error
  20.  
  21. return ;
  22. }

上面的代码中, 我标出了两处错误,错误的原因是, pInt2 是一个二维的指针,而iArray_1 和 iArray_2 都是int * 类型的指针, 如果将程序修改一下就可以

得到如下的结果。

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. //int iArray_1[5] = {1,2,3,4,5};
  16. //int iArray_2[3]= {1};
  17. //int *pInt1[5] ={iArray_1, iArray_2};
  18. //int (*pInt2)[5] = iArray_1; //error
  19. //pInt2 = iArray_2; //error
  20.  
  21. int iArray_1[]={,,,,};
  22. int iArray_2[][]={{}};
  23. int iArray_3[][]={{}};
  24. int (*pInt)[] = iArray_2;
  25. pInt=iArray_3; //error
  26.  
  27. return ;
  28. }

上面的代码中,我们可以知道  25行的语法是错误的,错误的原因是二维数组的第二维的指针长度不一致。通过上面的例子我们可以知道,* 和 [] 在一起定义指针变量

的时候,需要注意 * 和 [] 符号的优先级,同事需要知道加上括号后,定义的时候[] 的维度的扩展。这个地方是C语言当中经常会使用的,而且是属于较复杂的用法,因

此需要因此特别的重视。

6、 指针 和 const限定符/修饰符

  指针和const的结合使用没有太多的说头,主要是注意const修饰的 *p 还是 p, 只要分清楚修饰对象的不同就很好理解。

  1. int main()
  2. {
  3. int iVar1 = ;
  4. int iVar2 = ;
  5. const int *pInt1 = &iVar1;
  6. int const *pInt2 = &iVar1;
  7. int * const pInt3 = &iVar1;
  8. const int * const pInt4 = &iVar1;
  9. int const * const pInt5 = &iVar2;
  10.  
  11. return ;
  12. }

  关于const限定符需要知道的就是上面的各个定义的意义,只要知道 const是修饰 *pInt 还是修饰pInt就可以准确的分辨各个定义的意义,具体可以关注我前面

给出的关于C语言趣事相关的链接文章。

  这里还有一个需要注意的地方,就是对于const对象如何定义指向其的指针,下面是一个例子:

  1. int main()
  2. {
  3. const int iVar = ;
  4. //int *pInt1 = &iVar; //error
  5.  
  6. int const *pInt1 = &iVar;
  7. const int *pInt2 = &iVar;
  8.  
  9. return ;
  10. }

  这里要注意加了注释部分错误的原因。这里就不解释了,这个与const对象与引用的关系是一样的。

7、指针和typedef的使用

  在C语言中进程会做这样的预处理指令。

  1. #define PINT int*

  这样定义宏以后,就可以通过这个宏来定义指针变量,如下所示:

  1. #define PINT int*
  2.  
  3. int iVar = ;
  4. PINT pInt = &iVar;

  这样是可以通过的,但是这样会存在一个漏洞,如果同时定义两个指针变量的话,就会出现错误。

  1. #define PINT int*
  2.  
  3. int iVar1 = ;
  4. int iVar2 = ;
  5. PINT pInt1 = &iVar1, pInt2 = &iVar2;

很显然上面的代码存在漏洞,  第二个变量 pInt2 不是指针变量,而是一个整型的变量, 好在这样的错误编译器在编译的时候会检查出来,这里需要引起注意。

我们可以利用typedef机制来规避上述的风险,  typedef 的作用就是为数据类型取一个别名,尤其在数据类型比较长时是一个非常有效的机制, typedef的语法

如下:

  typedef   数据类型   数据类型别名;

例如:

  1.   typedef int* PINT;

  这就为 int* 这种类型定义了一个新的别名 PINT,在使用的时候PINT就表示 int*。

Exp:

  1. typedef int* PINT;
  2.  
  3. int iVar1 = ;
  4. int iVar2 = ;
  5. PINT pInt1 = &iVar1, pInt2 = &iVar2;

  上面的代码定义了两个整型变量 iVar1、iVar2, 同时定义了两个指针变量pInt1 和 pInt2;

要点:

  通过上面两个例子,就可以清楚 typedef和#define 之间的差别。

 注意typedef是语句,因此后面必须有个分号结尾。  这个点是经常容易忘记的,好在编译器一般可以检测出这样的错误。

  

  typedef和指针的结合还有一个值得注意的地方,就是 typedef 、const和指针同时出现。

  1. typedef int* PINT
  2. const PINT pInt; //error

  这里定义的指针对象pInt是const指针对象, 这个指针对象在定义的时候必须初始化。因此要注意上面的这个错误。

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. typedef int* PINT;
  16. const PINT pInt;
  17.  
  18. return ;
  19. }

程序编译的结果如下所示:

  1. [root@localhost cpp_src]# g++ test.cpp
  2. test.cpp: In function int main()’:
  3. test.cpp:: 错误:未初始化的常量 pInt

将程序改成下面的形式则正确:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <bitset>
  5.  
  6. using std::cin;
  7. using std::cout;
  8. using std::endl;
  9. using std::string;
  10. using std::vector;
  11. using std::bitset;
  12.  
  13. int main()
  14. {
  15. typedef int* PINT;
  16. //const PINT pInt;
  17. int iVar = ;
  18. const PINT pInt = &iVar; //初始化const指针
  19.  
  20. return ;
  21. }

  当然还可以定义更加复杂的数据类型,这里就不再进行描述,后面如果碰到会进行相关的描述。

  指针的操作基本上就是这些,在C++语言中,大部分的人倾向于不使用指针, 但是指针确实是一种非常高效的机制,但是如果能把指针用好,则会对

程序的性能的提升具有很好的提高作用。

  

  关于指针和引用暂时就说到这, 接下来将要对C语言风格和C++风格的字符串进行一番讨论, 待续......

C++_系列自学课程_第_8_课_指针和引用_《C++ Primer 第四版》的更多相关文章

  1. C++_系列自学课程_第_7_课_数组_《C++ Primer 第四版》

    说到数组,大家应该都很熟悉,在C.Pascal.Java等语言中,都有数组的概念.在C++中也提供了对数组的支持.数组简单来说就是一堆相同 数据类型对象的集合. 这里要把握住两个要点: 相同的数据类型 ...

  2. C++_系列自学课程_第_6_课_bitset集_《C++ Primer 第四版》

    在C语言中要对一个整数的某一个位进行操作需要用到很多的技巧.这种情况在C++里面通过标准库提供的一个抽象数据类型 bitset得到了改善. 一.标准库bitset类型 1.bitset的作用 bits ...

  3. C++_系列自学课程_第_5_课_vector容器_《C++ Primer 第四版》

    再一次遇到 vector 这个单词; 每一次见到这个单词都感觉这个单词非常的 "高大上"; 数字遇到vector马上就可以360度旋转: 当 "电" 遇到vec ...

  4. C++_系列自学课程_第_3_课_变量和基本类型_《C++ Primer 第四版》

    最近复习C++相关内容,决定在这里记录自己复习的过程. 以前写过部分文字,但是没有坚持连续写,因此学完后 基本又忘光啦,主要是没有实践,这一次决定自学完后,在这里在复习一遍增强自己的记忆和理解程度. ...

  5. C++_系列自学课程_第_12_课_结构体

    #include <iostream> #include <string> using namespace std; struct CDAccount { double bal ...

  6. C++_系列自学课程_第_12_课_语句_《C++ Primer 第四版》

    前面的文章说完了表达式和类型转换的部分内容,在我参考的书里面,接下来讨论的是各种语句,包括:顺序语句.声明语句.复合语句(块语句).语句作用域 .if语句.while语句.for语句.do...whi ...

  7. C++_系列自学课程_第_11_课_类型转换_《C++ Primer 第四版》

    上次说了关于表达式的一些内容,说到还有一些关于数据类型转换的内容,今天我们接着八一八C++中的数据类型转换. 一.隐式类型转换 在表达式中,有些操作符可以对多种类型的操作数进行操作, 例如 + 操作符 ...

  8. C++_系列自学课程_第_10_课_表达式_《C++ Primer 第四版》

    程序设计语言中大部分程序都在进行表达式的求值操作, 例如求两个数的和,求一个表达式的逻辑结果,或者通过输入输出表达式语句进行输入和输出. 这里我们对表达式进行讨论. 一.表达式 1.表达式 表达式由一 ...

  9. C++_系列自学课程_第_9_课_C语言风格字符串_《C++ Primer 第四版》

    前面说了写关于数组和指针的内容,这次在这里讨论一下字符串,讨论一下C语言风格的字符串. 在C语言里面我们利用字符数组来对字符串进行处理, 在C++里面我们前面说过一种类类型string可以对字符串进行 ...

随机推荐

  1. MySQL5.7 新增配置

    1.log_timestamps 在5.7.2以后的版本中增加一个单独控制error log , general log,slow log的记录的时间,默认是UTC,需要配置成SYSTEM(本地时间) ...

  2. php单条件查询,关键字查询

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. KnockoutJS 3.X API 第六章 组件(2) 组件注册

    要使Knockout能够加载和实例化组件,必须使用ko.components.register注册它们,从而提供如此处所述的配置. 注意:作为替代,可以实现一个自定义组件加载器(自定义加载器下一节介绍 ...

  4. HTML5新增标签与属性

    目录 一.HTML5新增属性 1.1.contextmenu 1.2.contentEditable 1.3.hidden 1.4.draggable 1.5.data-* 1.6.placehold ...

  5. ClickOnce部署(1):一些发布方式

    ClickOnce是什么玩意儿,这个问题嘛,在21世纪的互联网严重发达的时代,估计也没有必要大费奏章去介绍了,弄不好的话,还有抄袭之嫌.因此,有关ClickOnce的介绍,各位朋友可以直接查找MSDN ...

  6. 试试看 ? 离奇古怪的javascript题目

    来源地址: http://dmitrysoshnikov.com/ecmascript/the-quiz/#q1 另一篇帖子 看看国外的javascript题目,你能全部做对吗? http://www ...

  7. android跟服务器使用json传递数据

    最近在做项目,使用了json传递数据,把服务器对象转换成json字符串返回,android使用gson包解析json字符串变成对象. 1.服务器代码编写,我这边是在servlet里面 Peron pe ...

  8. 安装Ubuntu时分区选择

    最近购买来一台二手笔记本.型号是:Dell Latitude D520.回来之后就装上来Ubuntu12.04,开始是安装的UbuntuKylin 13.04.不知道是机器配置不行,还是本身系统有点卡 ...

  9. ES6 - Note6:Set与Map

    Set和Map是ES6中新增的数据结构,Set是集合,无序唯一,Map类似于对象,也是"key-value"形式,但是key不局限于字符串. 1.Set的用法 Set是构造函数,可 ...

  10. 使用VS Code开发ASP.NET 5 应用程序

    本文简要地翻译了 https://code.visualstudio.com/Docs/runtimes/ASPnet5 并结合我的实践做了一些说明. 准备工作 1.安装VS Code  https: ...