[关闭]
@Moritz 2019-03-29T08:01:35.000000Z 字数 4684 阅读 419

<谭浩强>第六章 指针和引用

课程学习 C++ 谭浩强 所有文稿


6.1 什么是指针

直接访问
按变量地址存取变量值的方式称为直接存取方式,或直接访问方式。
间接访问
将变量的地址存放在另一个变量中。
指针
一个变量的地址称为该变量的指针,可以通过取地址运算符&获得。
指针变量
用来存放地址(即指针)的变量,注意区别指针指针变量两个概念

6.2 变量与指针

定义指针变量

一个变量的指针包含两个方面的含义,一是以储存单元编号表示的地址(如编号为2000的字节),二是它指向的储存单元的数据类型,即基类型。指针变量是基本数据类型派生出来的类型,不能离开基本类型而独立存在。

引用指针变量

两个运算符
&:取地址运算符。例如,&a为变量a地址
*:指针运算符(间接访问运算符)。例如,*p为指针变量p所指向的存储单元
两个运算符优先级别相同,按自右向左方向结合。例如,&*p先进行*p的运算,在执行&运算,即p指向的变量的地址

指针变量的两种应用区别

①指针互换:

int *p1,*p2,*p,a,b;
cin>>a>>b;
p1=&a;
p2=&b;
if (a<b)                 //如果a<b,就使p1与p2的值交换
{p=p1;p1=p2;p2=p;}       //p是指针变量,将p1的指向与p2的指向交换

不交换整型变量的值,而是交换两个指针变量的值,ab保持原值,p1的值变为&b(指向b),p2的值变为&a(指向a)。

②指针所指的值互换:

void swap(int *p1,int *p2){ //将*p1的值与*p2的值互换
int temp;                 //temp是整型变量,不是指针变量
temp=*p1;
*p1=*p2;
*p2=temp;}

int main(){
int *p1,*p2,a,b;
cin>>a>>b;
p1=&a;
p2=&b;
if (a<b) swap(p1,p2);
return 0;}

交换ab的值,而p1p2的值不变。由于虚实结合是采取单向的“值传递”方式,只能从实参向形参传递数据,形参值的改变无法传回给实参。调用函数时不会改变实参指针变量的值,但可以改变实参指针变量所指变量的值。

错误函数调用:

if (a<b) swap(*p1,*p2);

p1p2的值是地址,*p1*p2的值为整型变量,与形参不匹配。

6.3 数组与指针

指向数组元素的指针

int a[10],*p;

/*以下两个语句等价*/
p=&a[0];
p=a;//把数组a的首元素的地址赋给指针变量p

如果指针变量p已经指向数组中的一个元素,则p+1指向同一数组中的下一个元素。一个数组元素,可以用以下方法:
1. 下标法:如a[i]形式;
2. 指针法:如*(a+i)*(p+i),其中a是数组名,p是指向数组元素的指针变量。使用指针法占内存少,运行速度快,能提高程序质量。

p可以指向数组以后的内存单元,虽然编译时不会出错,但应避免这种情况出现。

指针变量作形参接收数组地址

数组名代表数组首元素的地址,将其作为实参,传递的是首元素的地址。C++编译系统将形参数组名一律作为指针变量来处理
以下两种写法完全等价:

void select_sort(int a[],int n)
void select_sort(int *a,int n)

实参数组名a代表一个固定地址,或者说是指针型常量,因此改变a的值是不可能的。
以下为错误语句

a++;

形参数组名指针变量,值是可以改变的,在函数执行期间,它可以在被赋值,如:

void function(int a[],int n){
cout<<a;                       //输出a[0]
a=a+3;                         //指针变量a的值改变了,指向a[3]
cout<<a;}                      //输出a[3]

6.4 字符串与指针

用字符指针指向一个字符串

char *str="I am happy";
cout<<str;

程序中定义了一个字符指针变量str,用字符串常量对它初始化,把字符串中第一个地址赋给str。等价于下面两行语句:

char *str;
str="I am happy";

输出时,系统输出str所指向的第一个字符数据,然后使str自动加1,指向下一个字符……直至遇到字符串结束标志'\0'为止。注意,在内存中,字符串的最后被自动加了一个'\0'

6.5 函数与指针

指针变量可以指向一个函数。一个函数在编译时被分配给一个入口地址,这个函数入口地址就成为函数的指针

int max(int,int);
int (*p)(int,int);
int m,a,b;
cin>>a>>b;
p=max;
m=p(a,b);//即调用函数max

*p两侧的括号不可省略,表示p先与*结合,是指针变量,再与后面的(int,int)结合,表示指向函数。

6.6 返回指针值的函数

int *a(int,int);

()优先级高于*,所以a先与()结合。

6.7 指针数组和指向指针的指针

指针数组的元素均为指针类型数据。

void print(char *name[],int n){
int i=0;
char *p;
p=name[0];
while(i<n){
p=*(name+i++);    //先求*(name+i)的值,将它赋给p,然后i加1
cout<<p<<endl;}}

指向指针的指针,定义如下:

char *(*p);
char **p;

由于*运算符的结合性是从右到左,因此这两句语句等价。
例子:

char **p,*name[]={"ABC","DEF","GHI"};
p=name+2;
cout<<*p<<endl;    //输出name[2]指向的字符串,"GHI"
cout<<**p<<endl;   //输出name[2]指向的字符串中的第一个字符,'G'

6.8 const指针

指向常量的指针变量

const int *p=a;

不允许通过改变指针变量改变它指向对象的值,例如:

*p=15; //试图通过p改变它指向的对象a的值,非法

不能通过p来改变a,但p的值可以改变(即p的指向),例如:

p=&b;   //p改为指向b,合法

其指向对象的值也可以改变,例如

a=15;   //直接改变a的值,合法

指向常量的指针通常用于作函数形参,以防止指针形参所指向对象的值改变而影响实参。

常指针

常指针变量,简称常指针,其值是常量,即指针变量的指向不能改变。

int a,b;
int *const p1=&a;     //指定p1只能指向变量a
p1=&b;                //试图改变p1指向,不合法

注意:必须在定义时初始化,指定其指向。
指向变量的值可以改变,即a值可以改变

指向常量的常指针

const int *const p=&a;
p=&b;              //试图改变p的值,错误
*p=30;             //试图通过p改变a的值,错误
a=10;              //直接改变a的值,合法

6.9 void指针类型

指向空类型/不指向确定的类型,不能理解为能指向“任何类型”的数据。

如果指针变量不指定一个确定的数据类型,它是无法访问任何一个具体的数据的,它只提供一个地址。在C中用malloc函数开辟动态存储空间,函数的范围值是该空间的起始地址,由于该空间尚未使用,其中没有数据,谈不上指向什么类型的数据,故返回一个void *型的指针,表示它不指向确定的具有类型的数据。

显然这种指针式过渡型的,它必须转换为指定一个确定的数据类型的数据,才能访问实际存在的数据,否则它是没有任何用处的。在实际使用该指针变量时,要对它进行类型转换,使之适合于被赋值的变量的类型。

int *p1=&a;
void *p3=(void *)p1;//将p1的值转换为void*类型,然后赋值给p3
cout<<*p3;          //*p3非法,p3不能指向确定类型的变量
cout<<*(int *)p3;   //把*p3的值转换为(int *)型,可以指向变量a

可以把非void型的指针赋给void型指针变量,但反过来不行,要先进行类型转换。

6.10 小结

blahblah....

指针变量可以有空值,即不指向任何变量

p=NULL;

在VC和其他一些编译系统中,在iostream头文件中已经定义了符号常量#define NULL 0,因此,以上语句使p指向地址为0的单元,即不指向任何有效的单元。有的编译系统中,NULL并不代表0,而取其他值。

指针可以相减:

p1=&a[1];
p2=&a[4];

p4-p1=(a+4)-(a+1)=4-1=3,两个指针变量值之差是两个指针之间的元素个数。但p1+p2并无实际意义。

两个指针变量比较
指向同一个数组的元素则可以进行比较,指向前面的元素的指针变量小于指向后面元素的指针变来那个

6.11 引用

变量的引用

对一个数据可以建立一个引用,它的作用是为一个变量起一个别名。

int a;
int &b=a;

ba引用,即ba别名,通过b可以引用a,其中&引用声明符,并不代表地址。

  1. 引用不是一种独立的数据类型。对引用只有声明,没有定义。必须先定义一个变量,然后声明对该变量建立一个引用。
  2. 声明一个引用的时,必须同时使之初始化,声明它代表哪一个变量。
  3. 声明一个引用之后,不能再使之作为另一个变量的引用
  1. /*错误用法*/
  2. int a1,a2;
  3. int &b=a1;
  4. int &b=a2;
  5. /*不能建立引用数组*/
  6. int a[5];
  7. int &b[5]=a;//错误,不能建立引用数组
  8. int &b=a[0];//错误,不能作为数组元素的别名
  9. /*不能建立引用的引用*/
  10. int a=3;
  11. int &b=a;//声明b是a的别名,正确
  12. int &c=b;//错误,建立引用的引用
  13. /*也没有引用的指针*/
  14. int *p=b;//不能建立指向引用的指针
  15. /*可以取引用的地址*/
  16. int *p,a;
  17. int &b=a;//b是a的引用
  18. p=&b;//把变量a的地址&a(即&b)赋给指针变量
  19. /*区别引用声明符和地址运算符*/
  20. /*出现在声明中的是引用声明符,其他情况下是地址运算符*/
  21. int &b=a;//声明b是a的引用
  22. cout<<&b<<endl;//输出b的地址,此处&a不是引用

声明ba的引用时,内存中为b开辟了一个指针型储存空间,在其中存放a的地址,输出引用b的时候,就输出b所指向的变量a的值,相当于输出*b。引用其实就是一个指针常量,它的指向不能改变。

引用的本质还是指针,增加引用机制是为了方便用户,可以不必具体去处理地址,而把引用作为变量的别名来理解和使用,而把地址的细节隐藏起来。

引用作为函数参数

引用作为函数参数,可以扩充函数传递数据的功能。

函数参数传递有三种情况
(1)变量名作为实参和形参,值传递,形参的改变不传回给实参

void swap(int a,int b);
swap(i,j);//实际上,i和j的值并没有交换

(2)传递变量的地址,形参指针变量,实参是一个变量的地址

void swap(int *p1,int *p2);
swap(&i,&j);//实参是i和j的地址,可以交换两个变量的值

void swap(int *p1,int *p2){
int temp;
temp=*p1;*p1=*p2;*p2=temp;}

这种虚实结合的方法仍然是值传递方式,只是实参的是变量的地址而已(区别于Pascal语言中的地址传递:传递的是变量的地址,使形参变量与实参变量具有同一地址

(3)传址方式,引用作为形参,虚实结合时间时建立变量的引用

void swap(int &a,int &b);
swap(i,j);

void swap(int &a,int &b){    //此处&a不是a的地址,而是指a是一个引用
int temp;
temp=a;a=b;b=temp;}

完成施工 -2019.1.18


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注