代码改变世界

引用和指针

2011-05-23 19:46  htc开发  阅读(164)  评论(0编辑  收藏  举报

 

引用和指针

一.引用

1.1 引用的概念

  引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*

  例如: Point pt1(10,10);

  Point &pt2=pt1; 定义了pt2pt1的引用。通过这样的定义,pt1pt2表示同一对象。

  需要特别强调的是引用并不产生对象的副本,仅仅是对象的同义词。因此,当下面的语句执行后:

  pt1.offset22);

  pt1pt2都具有(1212)的值。

  引用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不能先定义一个引用后才

  初始化它。例如下面语句是非法的:

  Point &pt3

  pt3=pt1

  那么既然引用只是某个东西的同义词,它有什么用途呢?

  下面讨论引用的两个主要用途:作为函数参数以及从函数中返回左值。 

1.2引用参数

  1、传递可变参数

  传统的c中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。

  所以在传统的c中,如果需要函数的参数具有返回值的能力,往往是通过指针来实现的。比如,实现

  两整数变量值交换的c程序如下:

  void swapint(int *a,int *b)

  {

  int temp;

  temp=*a;

  a=*b;

  *b=temp;

  }

  使用引用机制后,以上程序的c++版本为:

  void swapint(int &a,int &b)

  {

  int temp;

  temp=a;

  a=b;

  b=temp;

  }

  调用该函数的c++方法为:swapintx,y); c++自动把x,y的地址作为参数传递给swapint函数。

  2、给函数传递大型对象

  当大型对象被传递给函数时,使用引用参数可使参数传递效率得到提高,因为引用并不产生对象的

  副本,也就是参数传递时,对象无须复制。下面的例子定义了一个有限整数集合的类: 

  const maxCard=100; 

  Class Set 

  {

  int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素个数的最大值。 

  int card; // 集合中元素的个数。 

  public:

  Set () {card=0;} //构造函数

  friend Set operator * (Set ,Set ) ; //重载运算符号*,用于计算集合的交集 用对象作为传值参数

  // friend Set operator * (Set & ,Set & ) 重载运算符号*,用于计算集合的交集 用对象的引用作为传值参数 

  ...

  }

  先考虑集合交集的实现

  Set operator *( Set Set1,Set Set2)

  {

  Set res;

  for(int i=0;i<Set1.card;++i)

  for(int j=0;j>Set2.card;++j)

  if(Set1.elems==Set2.elems[j])

  {

  res.elems[res.card++]=Set1.elems;

  break;

  }

  return res;

  }

  由于重载运算符不能对指针单独操作,我们必须把运算数声明为 Set 类型而不是 Set * 

  每次使用*做交集运算时,整个集合都被复制,这样效率很低。我们可以用引用来避免这种情况。

  Set operator *( Set &Set1,Set &Set2)

  { Set res;

  for(int i=0;i<Set1.card;++i)

  for(int j=0;j>Set2.card;++j)

  if(Set1.elems==Set2.elems[j])

  {

  res.elems[res.card++]=Set1.elems;

  break;

  }

  return res;

  }

1.3 引用返回值

  如果一个函数返回了引用,那么该函数的调用也可以被赋值。这里有一函数,它拥有两个引用参数并返回一个双精度数的引用:

  double &max(double &d1,double &d2)

  {

  return d1>d2?d1:d2;

  }

  由于max()函数返回一个对双精度数的引用,那么我们就可以用max() 来对其中较大的双精度数加1

  max(x,y)+=1.0; 

二.指针

2.1 指针的概念

  指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环, 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时, 指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要多编程,上机调试。只要作到这些,指针也是不难掌握的。

  指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75)C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:指针是指地址, 是常量,指针变量是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元

2.2 认识指针类型

  从类型上说来说,指针可以分为指向普通具体类型的指针、指针数组、函数指针、野指针等类型;从指针的修饰符上来说,指针分为常量指针和非常量指针,下面我们分别认识和讨论这些指针吧。

  下面通过一些简单的例子引入这些指针:

  (1)定义一个整型数

    int a

  (2)定义一个指向整型数的指针

    int *a 

  (3)定义一个指向指针的指针,它指向的指针是一个指向整型数指针

    int **a;

  (4)定义一个有10个整型数的数组

    int a[10];

  (5)定义一个有10个指针的数组,该指针是指向一个整型数的指针

    int (*a)[10]

  (6)定义一个指向有10个整型数组的指针

    int *a[10]

  (7)定义一个指向函数的指针,该函数有一个整型参数并返回一个整型数

    int (*p)(int)

  (8)定义一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回

一个整型数

int (*a[10])(int)

2.3 复杂指针声明读法

右左法则:首先从未定义的标识符开始阅读,然后往右看,再往左看。每当遇到圆括号时,就开始调转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

我们拿最后一个例子来说明右左法则:首先阅读到标识符a,然后向右看,说明a是一个数组,然后向左看,我们明白a这个数组的类型是指针,然后跳出括号,又遇到一个括号,说明a的类型是一个函数指针,该函数指针拥有一个int类型参数,返回int类型。

2.4 野指针

  野指针是指程序员或操作者不能控制的指针。当程序员定义了一个指针而又没有给这个指针一个具体地址指向时,这个指针会随意地指向一个地址,这样的地址就是一个野指针。如果这个指针后面的内存空间没有什么重要的数据则不会造成不严重后果,但是一旦里面存放了有用的数据,那么这些数据随时都有被野指针存取的危险,如果这样,数据就会被破坏,程序也会崩溃,因此在程序里禁止野指针的存在。

  下面一段程序中由于错误使用指针,造成野指针:

  Swap(int *p1, int *p2)

  {

      int *p;

      *p = *p1;

      *p1 = *p2;

      *p2 = *p;

  }

  在代码第三行中,声明了一个指针p,由于没有对p初始化,因此p是一个野指针,它可能指向系统区。因此在代码第四行,对p指向的内存区赋值非常危险,会导致程序运行时崩溃,所以程序应该改为:

  Swap(int *p1, int *p2)

  {

      int p;

      p = *p1;

      *p1 = *p2;

      *p2 = *p;

  }

2.5 扩展阅读知识

2.5.1 C++对象中this指针

  在C++中,对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。This指针的作用域是类内部,在类的非静态成员函数中访问类的非静态成员时,编译器会自动将对象地址作为一个隐含参数传递给函数。也就是说,即使没有this指针编译器在编译的时候也是会加上this指针的,它作为非静态成员函数的隐含形参,对各成员函数的访问通过this进行。

  例如调用:

  date.SetMonth(9);

  this帮助完成下面代码的转换:

  SetMonth(&data, 9);

  This指针的使用情况说明如下:

  一种情况是在类的非静态成员函数中返回类对象本身的时候,直接使用*this;另一种情况是当参数与成员变量名相同时用来区分两者,如this->n = n.

2,5,2 newdelete的作用

  MallocfreeC++/ C语言的标准库函数,new/deleteC++的运算符。它们的作用都是申请动态内存和释放内存。

  对于非内部数据类型对象而言,仅用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行西沟函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,因此不能把执行构造函数和析构函数的任务强加于malloc/free函数中。

  C++语言需要能完成动态内存分配和初始化工作的运算符,即new运算符,以及能完成清理与释放内存工作的运算符,即delete运算符、注意new/delete是运算符而不是库函数。下面例子详细说明了new/delete运算符与malloc/ free库函数的区别。

#include <iostream>

#include <cstdlib>

using namespace std;

class A

{

public:

A():a(0){ cout << "construct..." << endl; }

A(int value):a(value){ cout << "construct..." << endl; }

~A(){ cout << "deconstruct..." << endl; }

void print(){ cout << a << endl; }

private:

int a;

};

void use_malloc_free()

{

cout << "use_malloc_free" << endl;

A *a = (A *)malloc(sizeof(A));

free(a);

}

void use_new_delete()

{

cout << "use_new_delete" << endl;

A *a = new A();

delete a;

}

void fun(void)

{

cout << "after main" << endl;

system("pause");

}

int main()

{

A obj_a;

unsigned int * address;

//对于对象的非静态成员函数,编译器会自动添加形参thisthis==&obj_a

obj_a.print();

atexit(fun);//注册终止函数

use_malloc_free();//使用malloc方法

use_new_delete();//使用new操作符

}

  输出结果:
construct...

0

use_malloc_free

use_new_delete

construct...

deconstruct...

deconstruct...

after main

请按任意键继续. . .