⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 c++7.dat

📁 程序字典 经典的东西 里面有C、汇编、JAVA的字典
💻 DAT
📖 第 1 页 / 共 2 页
字号:
第七章 指针与引用

第一节 指针变量的定义与使用

在C++中,一种比较重要、也较难掌握的一种数据类型,就是指针类型.所谓指针就是在内存中的地址,它可能是变量的地址,也可能是函数的入口地址.如果指针变量存储的地址是变量的地址,我们称该指针为变量的指针(或变量指针);如果指针变量存储的地址是函数的入口地址,我们称该指针为函数的指针(或函数指针).
  注意:指针变量与变量指针的含义不同:指针变量也简称为指针,是指它是一个变量,且该变量是指针类型的;而变量指针是指它是一个变量,该变量是指针类型的,且它存放另一个变量的地址.
  我们知道:局部变量等的内存空间是编译器在栈中自动分配的,它的作用域和生存期局限于所定义的程序块中.我们也可以在堆中用new运算符自己申请内存,该内存的生存期由程序员自己控制.指针的一个重要用途就是:可以匿名访问通过new运算符在堆中分配的内存.
  一般说来,变量有两种方法访问:直接通过变量名访问或通过指针间接访问.以前我们介绍的程序中,对变量的访问大多是通过变量名访问的,变量也可以通过指针间接访问,即通过变量的指针而找到变量的值,这是我们下面要学习的内容.此外,我们还要学习函数的指针.
  引用为变量提供了一个别名,变量通过引用访问与通过变量名访问是完全等价的.引用提供了与指针相同的能力,但比指针更为直观,更易于理解. 

1.1 指针变量的定义
  我们已经知道,指针类型的变量是用来存放内存地址的.定义了指针类型的变量,就可以在该变量中存放其它变量的地址.如果我们将变量v的地址存放在指针变量p中,就可以通过p访问到v,我们也说,指针p指向变量v.指针的定义方法是在它所指的变量的类型后面加一个"*".下面是指针变量定义的例子:
  int *ptr1;
  char *ptr2;
  这个定义说明:ptr1和ptr2均保存变量的地址,且ptr1指向整型变量,ptr2指向字符变量.定义指针变量时应该注意:
  int *ptr1;
  int* ptr1;
是等价的.严格地说,*是属于变量名的.例如:
  int* pa, pb;
pa和pb分别是属于什么类型?pa是一个指向整型变量的指针,而pb是一个整型变量.也就是说:*应该是属于变量名的.根据这个定义,我们可以写出下面的语句:
  pa = & pb;
  这个语句是给指针变量赋值.&称为地址运算符,它是单目运算符,有一个变量作为它的右操作数,其功能是获取变量的地址.该语句执行后,pb的地址就被赋给了pa,即pa指向pb

1.2 指针变量的使用 
  假定pa指向pb,下面的表达式:
  *pa
  是获取pa指向的变量,即为pb.*称为间接运算符.它是单目运算符,有一个变量作为它的右操作数,其功能是获取指向变量的值.
  *pa也能作为左值,即给pa指向的内容赋值.例如:
  *pa=10;
在本例中,它与:
  pb=10;
的效果是一样的.
  C++编译器能够检查数据类型,如果把一个变量赋给一个类型不匹配的数据,可能会出现错误,指针也不例外.例如,如果ptr1和ptr2的定义如前,下面的语句就会出现编译错误:
  ptr2=ptr1;
如果我们把ptr1强制转换成char*类型,再赋给ptr2,就可以了:
  ptr2 = (char*) ptr1;
如果指针类型是void*类型,则可以与任意数据类型的匹配.例如7-1:
  void指针在被使用之前,必须转换为正确的类型.例如:
  int i = 99;
  void *vp = &i;
而下面的语句会产生一个编译错误:
  *vp = 3; 
  如果我们没有让指针变量赋值,指针指向的内容并没有意义.在C++中,有几个头文件定义了一个常量NULL(它的值为0),表示指针不指向任何内存单元.我们可以把NULL常量赋给任意类型的指针变量,初始化指针变量.例如:
  int *ptr1=NULL;
  char *ptr2=NULL;
  NULL常用于基于指针的数据结构(例如链表)的末尾(参见第八章),处理这样的数据结构通常是用循环语句.遇到NULL指针时,循环停止.
  注意:全局指针变量被自动初始化为NULL,局部指针变量的初值是随机的.我们编程错误常常出现在没有给指针赋初值.未初始化的指针可能是一个非法的地址,导致程序运行时出现"segmentation fault", "bus error", "system error 2" 等错误,而使程序运行终止.


第二节 动态内存分配

在程序运行过程中,堆内存能够被动态地分配,new和delete两个运算符分别用于堆内存的分配和释放.
  注意:malloc,free和new ,delete的不同,前者是函数而后者是运算符.new和delete都是单目运算符,new的操作数是一个数据类型,返回为该类型的变量分配的内存块的指针.例如: 
  int *ptr = new int;
  char *str = new char[10]; 
  上面的两个语句分别分配了存放一个int型变量的内存块及能够存放10个字符的内存块,它实际上就是一个字符数组.
在堆中分配的内存的生存期是由程序员自己控制的,例如: 
void Foo (void)
{
 char *str = new char[10];
 //...
} 

  当Foo返回时,局部变量str被释放,但它所指的内存还在,直到被程序员显式释放为止.
  delete运算符用于释放由new运算符分配的内存.delete的操作数是指针,释放该指针所指向的内存.例如: 
  delete ptr; // 释放ptr指向的内存块
  delete [] str; // 释放str数组 
  注意:当被释放的内存块是数组时,需要添加[].如果指针指向的内存不是用new申请的堆内存(例如,该内存在栈中),而用delete释放时,则会产生一个严重的运行错误.如果指针为空(指针值为0或NULL)时,它不指向任何内存单元,释放没有意义,不过,这不会导致程序出错.
  下面我们看一个内存动态申请的一个例子7-4:
    #include <string.h>
    char *CopyOf (const char *str)
    {
     char *copy = new char[strlen(str) + 1];
     strcpy(copy, str);
     return copy;
    } 

  函数CopyOf的功能是复制字符串,需要注意的是:该函数的名字前有一个*,表示该函数返回值是一个指针.形参是一个指向复制的字符串的指针,函数的返回值是指向已复制的字符串的指针.
  (1) string.h是标准的字符串头文件,它包含了许多字符串操作函数的说明,除strcpy、strlen外,还有strcmp、strcat等.
  (2) strlen函数是统计字符串中字符的个数(不包括结尾符'\0'),我们为字符串申请内存时,除要有存放各字符的内存单元外,还要有一个存放结尾符的单元.
  (3) 本例中,strcpy函数是把字符串str复制到copy,包括结尾符.
  我们知道:局部变量的内存单元是被自动分配和释放的,而用new申请的堆内存需要用delete显式释放.当我们对申请的堆内存不再需要时,就应及时释放.因为内存资源是有限的,如果在程序运行中,申请了许多大的内存块而又没有释放,则有可能使内存资源耗尽.如果程序中存在未被释放的、无用的内存块,我们称之为有内存泄露.内存泄露会导致程序性能降低,甚至崩溃. 
在本例中,CopyOf返回指向申请的内存块的指针,在调用函数中,不再需要该内存时,就应及时释放.
  顺便提及:函数的返回值也可以是一个指针,如本例的CopyOf函数.带回指针值的函数的一般定义形式是:
  数据类型 *函数名(参数表列);
  但是,带回指针值的函数,不能将具有局部作用域的变量的地址返回.例如:
   int *GetInt(char *str)
   {
    int value = 20;
     …
    return &value
   }定义的函数是错误的:
  这是因为value是GetInt函数局部变量,当GetInt函数返回后,value被释放.存放value变量的原内存单元的值是随机的,在调用函数中,并不能根据返回的指针,取回所期望的值. 

第三节 指针运算

在C++中,指针也能与整数作加减运算,即让指针变量加一个整数或减一个整数.但指针运算与整数的运算并不相同,它与指针所指向的变量的大小有关.例如: 
  char *str = "HELLO";
  int nums[] = {10, 20, 30, 40};
  int *ptr = &nums[0]; // 指向nums数组第一个元素 
  假定一个int型变量占用的内存空间是4个字节.在上例中,str++使str移动一个字符(一个字节),指向"HELLO"的第二个字符;而ptr++使ptr移动一个int型数(即4个字节),指向数组的第二个元素,如图7-11. 
  所以,"HELLO"的元素可以通过*str、 *(str + 1)、 *(str + 2)等引用,nums的元素可以用*ptr、 *(ptr + 1)、*(ptr + 2)、*(ptr + 3)等引用.
  在C++中,两个相同类型的指针也允许作减运算.例如: 
  int *ptr1 = &nums[1];
  int *ptr2 = &nums[3];
  int n = ptr2 - ptr1; // n变为2 
  在处理数组元素时,指针运算是非常方便的.例如: 
  void CopyString (char *dest, char *src)
  {
  while (*dest++ = *src++);
  } 
  这个循环是将src指向的内容赋给dest指向的内容,然后,两个指针分别加1.当src的结尾符被赋给dest时,条件表达式变为0,即为假,循环结束.


第四节 指针与常量

我们在定义指针时,如果在*的右边加一个const修饰符,则定义了一个常量指针,即指针值是不能修改的:
  int d =1;
  int* const p =&d;
  p是一个常量指针,它指向一个整型变量.p本身不能修改,但它所指向的内容却可以修改:
  *p =2;

我们也可以用定义常量指针指向常量,以下两种形式都是合法的:
  int d =1;
  const int* const x = &d; // (1)
  int const* const x2 = &d; // (2)
  现在,指针和变量都不能改变.
  注意:定义指针常量时必须初始化.下面的常量指针定义是错误的:
  const int* const x;


第五节 指针做函数参数

 同其它变量一样,指针也可以作函数的参数.我们以下面的swap函数为例,该函数的功能是交换两个整型变量的值:
  void swap(int* pa, int* pb)
  {
  int temp = *pa;
  *pa = *pb;
  *pb = temp;
  }
  注意:被交换的值是pa、pb指向的内容,不是pa、pb本身.
  调用swap函数,会影响到实参的值.例如:
  float x=10, y = 20, z = 0; 
  swap (&x, &y);
  cout<< x <<", "<< y << endl;
  输出结果为:20, 10.这是因为当swap被调用时,会创建两个临时指针变量pa、pb,并分别被初始化为实参x、y的地址,即相当于: 
  int *pa = &x, *pb = &y;
  函数中参与运算的值不是pa、pb本身,而是它们指向的内容,也就是实参x、y的值(*pa与x、*pa与y占用相同的内存单元).所以在swap函数中改变了*pa,也就是改变了实参x,改变了*pb,也就是改变了实参y.交换了*pa与*pb,也就是交换了x、y的值.
  从上面我们可以看到:在调用函数中,是把实参的指针传送给形参,即传送&x、&y,这是函数参数的引用传递.但是,作为指针本身,仍然是函数参数的值传递的方式.因为在swap函数中创建的临时指针,在函数返回时被释放,它不能影响调用函数中的实参指针(即地址)值.


第六节 指针与数组

在C++中,指针与数组是密不可分的.数组名本身就是指针(地址),是数组元素在内存中的首地址,数组元素可用下标访问,也可以用指针访问.指针本身也可以定义成数组,称之为指针数组.下面,我们将分别介绍.

6.1 指针数组

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -