C++指针详解

什么是指针

​ 指针也是一种数据类型,具有指针类型的变量叫指针变量。指针变量的作用是存放单元地址

//语法:数据类型 * 变量名 例:
int * ptr = 1;

指针为什么要指明其类型

  • 类型包括了指针所指向的元素所需要的内存空间大小

  • 指针参与运算是跟指针对象类型是密切相关的。

​ 指针值得是内存单元的地址。指针变量指的是存储地址的变量,也就是存储指针的变量。两者的区别需要注意。

* / &

​ C++提供两个与地址有关的运算符*&。在不同的运用场景中他们有着不同的作用。下面我们来讨论他们在各种不同场景中的应用。

*称为指针运算符,也称为解析(dereference)

  • * 用来定义指针变量

  • 加在指针变量之前表示解析当前指针变量,取其所指向的对象

    cout << * ptr << endl;
    // 1

&称为取地址运算符

  • & point可以取到变量point所指向的元素的地址

    扫描二维码关注公众号,回复: 853605 查看本文章
  • &出现在变量声明中表示声明引用

  • 出现在赋值语句中,表示取右边对象的地址。

    int * p = &i;

指针的赋值

​ 就像普通变量一样,指针在定义之后必须赋值才能引用。如定义的那样,指针变量存储的就是地址。所以指针变量赋值的语法如下数据类型 * 指针名 = 初始地址;,但赋值的地址必须是已经定义过的常量(eg:数组名称实际上就是一个指针常量),对象或变量,结构体等,否则会引起空指针异常

int a[10];  //定义int型数组,同时用a指针常量记录数组的首地址
int * ptr = a;  //定义并初始化int型指针
int * const p2 = &a;    //当然我们也可以自己定义指针常量

指针运算

​ 指针是一种数据结构,和其他数据结构一样,指针也是数据+操作。但是指针参加的运算又与其他的操作不太一样。

​ 指针可以参加整数的加减运算,但是又与普通的加减运算又一点区别,指针参加的加减运算与指针的类型密切相关。

ptr + nl;   //表示当前ptr指针后方第nl个数据的地址。
ptr++;ptr--;
*(ptr+nl);
ptr[nl];    //表示ptr之后第nl个元素的内容。也可以写作ptr[nl],所以现在对数组的访问是不是点想法了呢?

PS:一般来讲,指针的运算是和数组联合使用,因为只有数组会开辟一段连续的地址空间,同时如果使用这种方式一定要注意下标越界检查。

​ 前面介绍了指针的算术运算,接下来介绍指针的关系运算。

  • 指针可以和0进行比较,0用来表示空指针,也就是不指向任何一个有效地址。

    PS:空指针也常常用NULL(NULL是一个常用的宏定义,其定义其实也是0)来定义。在定义一个指针之后不知道指向什么地方的时候,一定要将转为空指针,避免错误调用造成一些不可遇见的错误。

  • 指针数组:如果一个数组的每一个元素都是指针变量,那么这个数组就是指针数组。指针数组的每个元素都是统一类型的指针。

    int * ptrArray[capacity];
    //读者可以自行猜想根据前面讲的指针加法运算考虑下指针的二维数组。

指针在函数中应用

​ 想象一下一个在没有指针的情况下如果你要传递一大堆数据到一个函数中。这是个噩梦,就算你真的把所有参数都写了一遍,这个函数也不具有普遍性可适用性。同时再想像一下当你需要返回的参数有一堆的时候,如果没有指针,根本没法实现对吧。

​ 使用指针作为函数参数有几点好处:

  • 实参和形参共享内存空间。也就是参数的双向传递

  • 减少数据传输时的开销。

  • 通过指针也可以指向函数代码的首地址。

    PS:如果参数对象并不需要做任何修改,我们应该在参数列表中将它定义为常量指针。

    虽然在传入参数的时候传入指针和对象的引用是一回事。但是应用会使程序的可读性更好些。

指针型函数

​ 当一个函数的返回值是指针类型的时候这个函数就是一个指针性函数。而指针型函数的主要目的就是在函数结束的时候把大量的数据返回。

语法格式如下:

数据类型 * 函数名(参数列表){}

​ 前面提到我们在需要返回一堆数据的问题,固然我们可以传入这堆数据的指针,实现前面所说的双向传递。但是这样可读性较差,同时会破坏原本传入数据的原本,如果你后面还要用原来的数据的话,那么这样干基本就GG了。

函数指针

​ 函数指针的正解是指向函数的指针

​ 在我们平常调用函数的时候每个函数都有一个函数名。是的你没猜错,指针又来了。调用函数的时候我们通常的形式是函数名(参数列表)其实质上就是函数代码的地址。那么函数指针就具有和函数名相同的作用。

函数范围值类型 (* 函数指针名)(参数列表)

​ 那么不少读者会疑问void返回类型的函数怎么办?在仔细思考一下,void类型的函数指针其实是存在的。但是void类型的指针不论是使用还是理解都有一定的难度。

  • void类型的指针没有类型,或则说不知道所指的对象长度。

    PS:感兴趣的读者可以想象如何构建一个可以存储任何类型的数组。之所以是数组只是为了方便查找快。

  • 任何类型的指针都可以赋值给它,并且不需要转换。但相应的问题是它只能获得对象的地址,而不能获得对象的大小。

  • 由于不能明确长度,所以你能猜到我们无法使用它参与指针运算

  • 无法复引用,也就是说无法解析。因为没有void类型的解析规则。

    假如 int * p,那么在你解析的时候,程序知道将该指针后的四位作为对象的内容。但是我们前面说过void类型不知道所指向类型的长度。也就没有对应的解析规则,应用程序就无法解析了。

->.的理解

​ 我们前面知道了指针可以指向一个对象,那么来想象一下如果我指向一个结构体(类一般成员私有,不好做例子)当我用指针指向他的时候我需要访问它的成员我需要这样写。

MyStruct * ptr = new MyStruct();
(* ptr).member;
//巨麻烦有木有

请饶恕我懒癌晚期,但是每次能少些就少些,所以定义对于指针变量,需要访问其指向的指针对象时。我们可以直接使用.来访问。于是上面一段就变成了ptr->member。嗯,顺眼多了。

this--一颗神奇的指针

​ 不论哪种语言,大家都没少写this这颗指针吧,别否认,呵呵,我不信。

this指针是一个隐含于每一个类的非静态成员函数中的特殊指针。它用来指向正在被成员函数操作的对象

PS:this实际上类成员函数的一个隐含参数,在调用类成员函数时,目的对象的地址会自动作为该参数的值。这样被调用的函数就能访问当前目的对象的数据成员了。

*

SO:结合前面讲的->.的区别你知道为什么this之后你只能使用->了吧。

引用和指针的区别

​ 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。想象一下,指针可以对你的对象或者变量进行增删改查,他的权限对于有些应用来说是不是太大了。

​ 指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。

​ 就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?

​ 如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。

​ 比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。

​ ----「高质量c++编程」

​ 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

  • 指针是一个实体,而引用仅是个别名;

  • 引用使用时无需解引用(*),指针需要解引用;

  • 引用只能在定义时被初始化一次(PS:必须初始化,且不为空,甚至指向空指针都不行。),一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。引用“从一而终” ❤️

  • 引用没有 const,指针有 const,const 的指针不可变;

  • 引用不能为空,指针可以为空;

  • “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

  • 指针和引用的自增(++)运算意义不一样;

引用与指针的详细区别

​ 在c++中参数传递返回值的传递有三种方式:

  • 值传递

    void function1(int x){
        x = x + 10;
    }
    ​
    int n = 0;
    //实际上传递的是n的副本,并不是n真实地址所指向的那个对象。
    function1(n);
    cout << "n = " << n << endl;// n = 0
  • 指针传递

    //指针传递的是变量的地址,我们直接修改地址,等同于修改了原变量
    void function2(int *x)
    {
        (* x) = (* x) + 10;
    }
    int n = 0;
    function2(&n);
    cout << "n = "  << n << endl; // n = 10
  • 引用传递

    //传递的引用,这里的X只是n的另外一个名字,其指向的地址还是x的地址。
    void funcion3(int &x)
    {
        x = x + 10;
    }
    int n = 0;
    function2(n);
    cout << "n = "  << n << endl; // n = 10

​ 因为引用肯定会指向一个对象,引用应被初始化。

PS: 不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。

​ 指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。

string s1("Nancy");
string s2("Clancy");
string & rs = s1;   // rs 引用 s1
string * ps = &s2;  // ps 指向 s2
rs = s2;    //引用不用解析可以直接赋值,现在rs仍旧引用s1,但是s1的 rs 指向的是值现在是 "Clancy"
ps = &s1; // ps 现在指向 s1;
cout <<" rs 指向的内容是" << rs << endl;
cout <<" ps 指向的内容是" << *ps << endl;

猜你喜欢

转载自my.oschina.net/u/3483440/blog/1812517