📄 c++7.dat
字号:
第七章 指针与引用
第一节 指针变量的定义与使用
在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 + -