从零开始学Visual C++
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.7 指针

指针是C和C++灵活强大的重要因素,但也是难以控制的不稳定因素,在C#、Java语言中都取消指针类型,不能够直接操作物理内存。指针是变量的内存地址,不同类型的变量需要不同的指针类型,尽管指针的实际值是一个数字,但用于存储指针值的变量必须明确声明为指针类型。

1.7.1 指针概述

指针存储变量的内存地址,指针类型为变量类型加*,如int* p1;若p1设定为指向int类型的指针,则不能接受其他类型的指针。&用于获取变量的内存地址,如int* p1=&a;要获取指针指向的值,使用*如int b=*p1;在不同的场合*有不同的意义,应小心区分。

指针变量实际存放的就是一个地址值,但编译器将其抽象为一种数据类型,在实际应用中,经常需要进行指针类型的转换,如int*转为void*、void*转为double*、派生类指针转为基类指针等,这些转换都是编译器层次上的表面转换,实际上就是一个地址。

【实例1-25】输出指针变量存储的地址值,指针指向的值,指针本身所在的地址,指针变量占用的内存大小。

        #include <iostream>
        using namespace std;
        int main()
        {
            int a=120;                                 //整型变量
            int* p=&a;                                 //将a的地址赋给整型指针p
            cout<<"指针p的值为:"<<p<<endl;            //p存储地址值
            cout<<"p指向的值为:"<<*p<<endl;           //p指向的值
            int** q=&p;                                //指向指针p的指针
            cout<<"指针p的地址:"<<q<<endl;            //指针p所在的内存地址
            cout<<"指针p的大小:"<<sizeof(p)<<endl;    //指针占用的空间大小
            return 0;
        }

编译运行,结果如图1-20所示。通过&获取整型变量a的地址值,存放到int指针p中。通过*获取指针p所指向的变量的值。指针作为一个变量,也有其地址,通过&获取指针变量的地址值,存放到q中,int**表示一个指向int指针变量的指针。通过sizeof获取指针变量占用的内存大小,指针变量占用4个字节。

图1-20 指针

1.7.2 指针与数组

数组变量实际上就是一个指针,数组名为数组的第一个元素地址,即数组的首地址。根据下标索引确定元素地址,如a[1]的地址等于a[0]的地址加上一个元素的长度。

尽管指针实际上是个地址值,但不能把指针当做数值进行数学运算,只能按照编译器设定的方式进行运算,如a[2]等同于*(a+2),数组名a为数组首地址,2表示两个元素的长度,(a+2)即为第3个元素的地址,通过*获取第3个元素的值。

【实例1-26】将数组名赋给指针变量,用指针变量访问数组元素,输出每个元素的值和地址值。

        #include <iostream>
        using namespace std;
        int main()
        {
            int a[3]={12,45,60};
            int* p=a;                                      //数组名赋给指针变量p
            cout<<p[0]<<" "<<(long)&p[0]<<endl;            //输出第1个元素值及地址值
            cout<<p[1]<<" "<<(long)&p[1]<<endl;
            cout<<p[2]<<" "<<(long)&p[2]<<endl;
            return 0;
        }

编译运行,结果如图1-21所示。将数组名a赋给指针变量p后,指针p同样可以使用下标访问数组元素,由此可见,数组名a等同于数组的首地址&a[0]。&p[0]得到数组第1个元素的地址,(long)强制将地址值转换为长整型数,默认为十六进制数。三个元素的内存地址是连续的,相邻元素相差4个字节,为一个元素的大小。

图1-21 指针与数组

1.7.3 指针与函数

函数有多个参数,但只有一个返回值,若函数执行后有多个输出值,仅依赖函数的返回值是不够的。可以将部分参数作为输出值,实现输出多个值,如将变量a、b作为参数传给函数f,即f(a,b),调用函数f后,a和b存放的是输出值。但一般情况下,传递给函数f的是变量a、b的值拷贝,在函数f内部的任何修改对a、b本身都没有影响,在C语言中使用传入变量的指针的方法解决该问题。

将变量的指针值传递给函数,如f(&a,&b),传入函数f的是a、b的地址值的拷贝,在函数内部通过该地址值可以访问变量a、b,从而修改其值,在函数f的内部操作过程中已经将外部变量a、b的值修改了。C++提供了引用方式可达到同样的效果,两者形式不同,但实质上都是通过传入变量的内存地址达到改变外部变量值的效果。

【实例1-27】使用传递变量指针和引用两种方式,实现修改外部变量。

        #include <iostream>
        using namespace std;
        //指针方式改变参数值
        void CalcByPointer(int a,int b,int* pAddResult,int* pSubResult)
        {
            *pAddResult=a+b;                           //相加结果保存到指针参数所指向的变量中
            *pSubResult=a-b;
        }
        //引用方式改变参数值
        void CalcByReference(int a,int b,int& AddResult,int& SubResult)
        {
            AddResult=a+b;                             //相加结果保存到引用参数中
            SubResult=a-b;
        }
        int main()
        {
            int a=120,b=30;
            int c1,c2,d1,d2;
            CalcByPointer(a,b,&c1,&c2);                //指针方式
            CalcByReference(a,b,d1,d2);                //引用方式
            cout<<"指针方式:"<<c1<<","<<c2<<endl;     //输出结果
            cout<<"引用方式:"<<d1<<","<<d2<<endl;
            return 0;
        }

编译运行,结果如图1-22所示。通过指针方式传入变量c1、c2的指针值后,pAddResult指向要改变的变量c1,pSubResult指向要改变的变量c2,在CalcByPointer函数内部执行*pAddResult=a+b;后,直接在内存上进行变量的修改,pAddResult所指向变量c1的值已经改变。

图1-22 指针与函数

引用相当于变量的别名,引用和变量占用同一块内存,改变引用的值等同于改变变量本身。引用定义的同时必须初始化,如int& b=a;表示b是变量a的一个引用,对b的任何操作等同于对a的直接操作。CalcByReference函数中AddResult和SubResult两个参数是整型引用,将d1、d2传入函数,相当于int& AddResult=d1; int& SubResult=d2; 。对AddResult、SubResult的修改相当于直接修改d1、d2的值。

1.7.4 指针与字符串

字符串可看做常量字符数组,如字符串“language”是长度为9的字符数组,前8个元素存储单个字符,最后1个为结束标志'\0',表明字符串到此结束。字符'\0'的ASCII值为0,可根据元素值是否等于0来判断是否到达字符串末尾。

一个字符数组能存放的字符数目为数组长度减1,字符数组初始化时,C++自动在字符串末尾添加空字符'\0'。字符数组名相当于一个指向字符串首地址的指针,通过移动指针指向的位置可灵活操作一个字符串。

【实例1-28】通过字符指针遍历一个字符数组,输出所有字符。

        #include <iostream>
        using namespace std;
        int main()
        {
            char a[]="Study Program";             //字符数组
            char* p=a;                            //字符指针,指向数组首地址
            while(*p!=0)                          //若指针指向的字符不等于0(ASCII值)
            {
                cout<<*p<<" ";                    //输出字符
                p++;                              //字符指针向前移动一位
            }
            cout<<endl;
            return 0;
        }

编译运行,结果如图1-23所示。字符数组名等同于字符数组的首地址,将字符数组名赋给字符指针p后,p指向第1个元素,通过*获取p指向的元素的值。p每次递增移动一个元素大小的位置,指向下一个元素,当指向字符串结束标志'\0'时,停止循环。

图1-23 指针与字符串