美文网首页
C++ | 让人又爱又恨的指针

C++ | 让人又爱又恨的指针

作者: yuanCruise | 来源:发表于2019-01-04 22:03 被阅读17次

    1.指针数据类型

    众所周知,指针是用来保存数据地址的,虽然各种类型数据的地址的格式是一致的,但由于如char类型,double类型数据的字节数不一致,因此指针的指向字节数也不一致,所以我们声明指针时需定义好指针所指向的类型。基于此,我们认为指针和数组是一样的均是基于其他类型的新类型。其中有一个特别类型的指针:void* 指针。该种类型的指针可以保存任何类型对象的地址 。

    void* 指针只支持几种有限的操作: 1. 与另一个指针比较。 2. 向函数传递void* 指针或从函数返回void* 指针。 3. 不允许使用void* 指针操纵它所指向的对象

    2.声明指针

    有两种声明方式:

    1. int* ptr:这种写法可以理解为定义了一个指向int型变量的指针(int*)
    2. int *ptr:这种写法可以理解为定义了一个int型变量(int),而这个变量的名称就是*ptr,因为*ptr的用法和int是一样的。

    正常情况下,在C++中我们用前者,也就是认为(int*)是一种新的数据类型。当执行操作:int* ptr=&abc时,因为此时赋值的是给ptr赋值。所以把ptr看成一个指针变量更合理。但需要注意的是,int* p1,p2表示的是声明了一个指针p1,一个int型变量p2.如果要声明两个指针,那么需要写成int* p1, * p2 。

    3.指针初始化

    初始化指针时请给定指针指向,这将会是一个良好的编程习惯。

    int* ptr;   
    *ptr = 233;
    

    如上述若在声明指针ptr时,未给出其指向,那么意味着ptr的指向是随机的,此时会造成许多隐私错误的发生,如将233数据存到了代码段。指针初始化要在定义的时候直接初始化掉,不然会产生内存危险,因为一开始不赋值就会随机分配地址,有可能分配什么代码的地址。
    给指针变量赋值数字的时候,不能将整型数字赋值给指针变量,要把数字强制类型转换成指向int的指针类型:

    int* ptr;
    ptr = 0x12121212;
    
    int* ptr;
    ptr = (int*)0x12121212;
    

    在C++中,对数据类型十分严格。因此,上述代码段的全两句将整型数据赋值给int型指针是错误的,这本质上是两种不同的类型,一种是int一种是int*。代码段的后两句才是正确的赋值方式。如下为三种常见的指针初始化方式。

    //初始化空指针(一般用Null)
    int *P = Null;
    
    //初始化同类型变量的地址
    int ival = 2;
    int *P = &ival;
    
    //初始化同类型的另一个有效指针
    int ival = 2;
    int *P = &ival;
    int *p2 = P;
    

    4.指向指针的指针

    某一指针的存储地址,可存放在另一个指针中。

    #include<iostream>
    using namespace std;
    int main()
    {
        int ival = 100;
        int *Pi = &ival;
        int **PPi = &Pi;//指向指针的指针
    
        cout<< "ival=" << ival <<endl;
        cout<< "Pi指向的值=" << *Pi<<endl;
        cout<< "PPi指向的指针所指向的值=" << **PPi<<endl;//最终都是输出100
    
        return 0;
    }
    

    5.函数返回指针(决不能返回局部变量的指针)

    主要原因在于函数中的局部变量在函数返回之后就会被清空,所以是返回不出函数的。

    int* fp()
    {
        int local = 3;
        return &local;
    }
    
    //当然也不能返回局部对象的引用
    const string& f(const string& s)
    {
        string ret = s;
        return ret;
    }
    

    6.指向函数的指针

    对于函数SORT而言,其函数名为(SORT)所以一般调用的时候要用(SORT)(1)这样的形式,但在C++中可以用SORT(1)这个形式,C中必须用(SORT)(1)。这是对于全局函数而言的,对于类的成员函数来说就不一样了。 原因是对于全局函数而言,它的名称就是它的地址,当然直接取地址也仍然表征的是地址,所以上述代码中会有2*2=4种调用方式。然而类的成员函数用的时候必须用取地址符,因为成员函数本质上是变量并不是函数。

    #include <iostream>
    using namespace std;
    
    void (*SORT)(int n);
    //void *SORT(int n); 这样声明是不行的,这样声明仅仅只是声明一个函数其返回值是void* 的指针。
    void Sort1(int n){printf("冒泡排序\n");}
    void Sort2(int n){printf("希尔排序\n");}
    void Sort3(int n){printf("快速排序\n");}
    
    void TestFunPointer()
    {
        SORT = Sort1; SORT(1);//SORT = Sort1; (*SORT)(1);  所以也可以这样写
                              //SORT = &Sort1; (*SORT)(1); 所以也可以这样写,
                             //这两个组合一些有四种方式都可以(C++中)
        SORT = Sort2; SORT(1);
        SORT = Sort3; SORT(1);
    }
    
    int main()
    {
        TestFunPointer();
        return 0;
    }
    

    当然这种指向函数的指针真正应用的时候会用到typedef限定符,因为这样会比较方便易懂。调用方式如下:

    typedef (*PSORT)(int n);
    void TestFunPointer()
    {
        PSORT sort = Sort1;sort(1);
        (*sort) = &Sort2; (*sort)(1);//这里用到了上面讲的不同种的调用方式。
    }
    

    7.指向类的成员函数的指针

    #include <iostream>
    using namespace std;
    class CA
    {
    typedef int((CA::*PClassFun)(int ,int));
    public:
        int max(int a, int b)
        {
            return a>b? a:b;
        }
    
            int min(int a, int b)
        {
            return a<b? a:b;
        }
    
        int Result(PClassFun fun, int a, int b)
        {
            //像上面说的调用类的成员函数的时候必须加上“*”,因为这个函数相当于变量
            return (this->*fun)(a,b);
        }
    
    };
    
    void TestMemberFun()
    {
        CA ca;
        int a = 3; int b = 4;
        printf("test member fun\n");
        //所以成员函数必须用&来调用,因为其本质是变量
        printf("max result = %d\n", ca.Result(&CA::max, a,b));
        printf("min result = %d\n", ca.Result(&CA::min, a,b));
    }
    
    int main()
    {
        TestMemberFun();
        return 0;
    }
    //输出结果:
    //test member fun
    //max result = 4
    //min result = 3
    

    8.指针操纵二维数组

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    
    
    int main()
    {
        int i, j;
        int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };//定义一个二维数组a  
        int b[2][3] = { 1, 2, 3, 4, 5, 6 };//定义一个二维数组b  
        int s[2][3];//声明一个二维数组 s用来存放两个数组的和  
        int *p, *q, *su;//声明三个int类型的指针 用来指向三个二维数组  
        //*****下边是分别指向*****   
        p = a[0];
        q = b[0];
        su = s[0];
        int sum;
        //******用指针来操作二维数组******  
        for (i = 0; i<6; i++)
        {
            sum = p[i] * q[i];
            su[i] = sum;
        }
        //******用数组本身来操作二维数组******  
        printf("the sum of the arrays is \n{");
        for (i = 0; i<2; i++)
        {
            for (j = 0; j<3; j++)
            {
                printf("%d ", s[i][j]);
            }
            printf("\n");
        }
        printf("}");
    
        getchar();
    }
    

    具体来说指针和二维数组的关系如下:
    int b[2][3] = { 1,2,3,4,5,6};
    int* p2 = b[0];
    此时我们有
    // p2[0] = b[0][0] ,p2[1] = b[0][1] ,p2[2] = b[0][2]
    // p2[3] = b[1][0] ,p2[4] = b[1][1] ,p2[5] = b[1][2]

    9.指针动态分配内存

    1.主要实现方式为:
    在程序执行的过程中,利用new动态的分配所需要的资源,并用指针跟踪这些资源。最终实现动态资源分配。
    2.使用方式:

    typename* ptr = new typename;
    

    new后面的typename指定了当前变量类型以及该类型所需要的字节数。前面的typename*指定了当前指针变量的类型。需要注意的是:利用new分配的内存称为堆(heap)内存区域。而普通定义的变量所分配的内存称为栈(stack)内存区域。利用关键字delete可以释放new分配内存,delete只能释放new分配的内存。利用delete释放内存块时,需指定指向该内存块的指针(之前用new分配内存块时指向的指针)。如:

    int* ptr = new int;
    ...
    delete ptr;
    

    3.new关键字真正的用武之地1
    利用new动态分配数组,即能够使得数组元素个数无需预先确定,可在程序运行过程中根据需要制定,具体操作如下:

    int* ptr = new int[10];  //其中ptr表示的是指向了十个元素的指针变量,也就是数组的首地址。
    ...
    delete [] ptr; //需要知道删除的是数组,所以需要添加[]
    

    4.new关键字真正的用武之地2
    利用new动态分配结构or类。(C++中结构和类用法基本一致)。具体操作如下:

    //首先定义一个未命名的结构
    struct str1
    {
        char name[20];
        float volume;
        doduble price;
    };
    
    //利用new分配内存块
    str1* ptr = new str1;
    
    //利用“.”或者“->”访问结构内部的变量
    //注解:什么时候用“.”,什么时候用“->”:
    //若结构的标识符是结构名,用“.”;若结构的标识符是指向结构的指针用“->”
    cin.get(ptr->name,20);
    cin.get(ptr->volume);
    cin,get((*ptr).price);
    
    

    10.指针和引用

    1.引用

    定义引用时没有初始化是错误的 :

    /正确
    int ival = 1024;
    int &refcal = ival;
    
    //错误
    int &refval;  //错误原因:引用在定义的时候必须初始化
    int &refval = 10;//错误原因:引用必须是一个变量(对象)
    

    引用是变量的别名,所以对引用进行操作时,实质是对引用的那个变量进行操作:

    int ival = 1024;
    int &refval = ival;
    refval += 2;
    cout<< "refval=" << refval << endl;
    cout<< "ival=" << ival <<endl;
    
    //输出时ival也被加了2.
    

    引用就是对象的另一个名字,实际应用中引用主要用作函数的形式参数,或函数返回。

    #include <iostream>
    #include <vector>
    using namespace std;
    class VectorRef
    {
        //定义类变量
        std::vector<int> vecInts;
    public:
        //构造函数
        //初始化压入向量容器0~4
        VectorRef(int size = 5)
        {
            for(int i =0; i< size; i++)
                vecInts.push_back(i);
        }
    
        //将类变量vecInts以引用的形式返回
        std::vector<int> &GetVecInts()
        {
            return vecInts;
        }
    };
    
    //将引用作为函数参数
    void PrintVecInts (const std::vector<int> & vecInts)
    {
        printf("\n");
        for(int i = 0; i< vecInts.size(); i++)
            printf("%d current value = %d\n",i,vecInts[i]);
    }
    
    void TestVecInts()
    {
        //定义一个类,此时自动调用了构造函数,已经填充了数字
        VectorRef vRef;
        //返回引用,不会发生拷贝构造,v和vRef是同一个东西,如图1
        vector<int>& v = vRef.GetVecInts();
        //返回非引用,会发生拷贝构造,就是v变量vRef是不会变的,如图2
        vector<int> v = vRef.GetVecInts();
        v[0] = 100;
        PrintVecInts(v);
        PrintVecInts(vRef.GetVecInts());
    }
    
    int main()
    {
        TestVecInts();
        //getchar();
        return 0;
    }
    

    2.指针和引用的比较
    引用存储的是值,而指针存储的是地址。
    引用只能对已经存在的对象进行,而指针确可以定义为空。
    引用直接访问不需要定义内存空间,而指针需要有自己的内存空间。

    相关文章

      网友评论

          本文标题:C++ | 让人又爱又恨的指针

          本文链接:https://www.haomeiwen.com/subject/lwtlrqtx.html