指针与数组

指针是C/C++中十分重要的概念,其指的是指向某一个变量的地址的变量,该变量的值即为某一个变量的地址,其指向的值为某一个变量的值。由于其能够有效的减小传递变量所需的内存与所需时间,其在函数参数、算法等方面十分重要。

目录

一些基本的东西

动态内存分配与动态数组、动态结构

模板类vector与array

指针数组、指向数组的指针与二维数组

函数指针


一些基本的东西

  1. 指针的定义

    指针是指向某一个变量的地址的变量,指针的值即为某一个地址,其指向的值(区别于“指针的值”)为某一个变量的值。如其可以是变量a的地址,也可以是指针变量的地址(即指针的指针)

    需要注意的是,指针只是获得了变量的地址,并可以通过地址访问变量,而不是变量的副本,后者还需要进行内存的新分配与初始化。
     
  2. 指针的声明、初始化

    指针的声明很简单,其格式如下。声明语句可以理解为*PointerName这个变量为typename这个类型的变量,变量PointerName则是指向typename类变量的指针。*为解除引用运算符
    //类型名 *指针名
    typename *PointerName

    *pointerName是指针指向的值,其可以是变量值,也可以是数组名,当为数组名时(在后面还带有[]),(*pointerName)或者*pointerName就表明这个指针是指向数组的指针。

    其初始化则需要使用相应类型的变量的地址(变量地址通过地址运算符&获得),或者通过动态内存分配(见下方“动态内存分配”处),和其他变量相同,使用指针前也需要初始化。指针的初始化语句可以和声明语句合并,例如:

    int a=5;
    int *PtrInt=&a;

    通过调用*PtrInt即可取得PtrInt指向的值,即a。也可以通过给*PtrInt赋值修改a的值(前提是指向的量不是const定义的常量,见“指针与const”小节)。

    需要强调的是,在谈论指针是,应将指针指向的类型共同表明,如PtrInt是一个指向int类型的指针,C++中指针并不是一个基础类型,而是和数组一样的复合类型
     

  3. C++中基本类型的指针

    本节所说的基本类型包括整型、浮点数这两类。它们的指针只需要按照正常的指针声明、初始化即可。
     
  4. 指针与数组

    C/C++中,指针与数组基本等价,因为数组名一般会被解释为数组地址(其值等于数组第一个元素的地址,但二者并不相同。对于数组中的每个元素而言,可以用数组表示法或者指针表示法获得,例如:
     
    int arr[]={1,2,3,4,5};
    int *ptr=arr;
    
    数组arr的下标为i的元素可以用arr[i]或者ptr[i]表示。因此可以在for循环中使用ptr[i]进行遍历


    需要注意的是,指针与数组也不能完全等同,其有如下不同:
    #指针可以修改值(如果不是const指针的话,见“指针与const”小节);而数组名是常量。
    #对指针使用sizeof()运算,得到的是指针的值(即地址)的长度(即字节数);对数组使用sizeof()运算,得到的是整个数组的长度。

     
  5. 指针算术

    指针应具有+、-的运算。利用这两种运算可以移动指针所指向的地址,或者获得两个指针的地址差。

    对于上文中的指向int的指针ptr,若执行ptr+1的指令,那么ptr的值(即地址)将增加1个int类型所占的字节(取决于系统,可能为2或4字节)。

    对一个指向某类型的指针ptr进行ptr+i的运算,将使得ptr的地址增加 i*sizeof(该类型)个字节。对于ptr-i也是同理。

    实际上,对于任意数组arr元素与指向arr的指针ptr而言,存在如下关系:
    arr[i]=*(ptr+i)
    前者arr[i]称为数组表示法,后者*(ptr+i)称为指针表示法。
     
  6. 指针与字符串

    C/C++中字符串均可以用字符数组表示(字符结尾为空字符'\0')。实际上字符串可以用字符指针表示:
    char *str="Hello World";
    用字符指针表示的字符串,与C风格的字符串、字符串字面量(string literal)、字符数组、string类对象,基本等价。

    不过需要注意的是,若使用cout打印一个指针,一般会打印出指针的值即地址,但是若打印一个char *,那么会打印出字符串,此时需要用int *强制转换:
     
    char *str="Hello";
    cout<<str<<" at "<<(int *)str;
  7. 指针与const限定符


    对于指针和变量而言,均可以通过使用const限定符来保证值不被修改。对于变量而言,使用const后即为常量;对于指针而言,其也可以理解为指针常量,因为其值(即地址)不能被修改,即其指向不变。


    根据指针、变量是否使用const声明,可以分为以下各种变量(注意区分指针常量与指向常量的指针):
    int a=6; //常规变量(非const变量)
    const int b=5; //常量,b的值不能被修改
    int *ptr_1; //常规指针(非const指针),指针指向可以修改
    int * const ptr_2; //指针常量(const指针),指针值(即指向)不变,但可以通过*ptr_2修改指向的值
    const int *ptr_3; //指向常量的指针(const指针),指向的量为常量或者变量,不能通过*ptr_3修改指向的值,但可以改指向。
    对于 const int * ptr_3与int * const ptr_2可以这么理解:const int * ptr_3中的const指的是int值是常量,int * const ptr_2中的const指指针ptr_2是常量,常量的指都无法被修改,所以对于const int * ptr_3,无法通过*ptr_3修改int的指;对于int * const ptr_2,无法修改ptr_2的指向(即其值)。

    对于任意变量(const或者非const)与任意指针(const或者非const),除了不能将const变量的地址赋给非const指针以外,可以将const/非const变量地址(这个地址也可以通过指针表示,但只限于将指针指向基本类型的一级间接关系)赋给const指针,还可以将非const变量地址赋给非const指针。
    对于二级间接关系,上述规则就不适用了,应尽量避免。

    将指针声明为指向常量的指针(而不是const指针)有如下好处:
    #指向常量的指针能够接受const或者非const量的地址
    #指向常量的指针能够保证数据不被修改,从而保证数据的安全性。



    对于指针而言,请尽量将其声明为指向常量的指针。

动态内存分配与动态数组、动态结构

同C语言一样,C++也提供了动态储存(即在程序运行时的内存管理)的方案,不过不同的是,C++中采用new与delete运算符来新建与释放动态内存。

  1. new与delete的使用

    使用new为变量分配动态内存的代码如下,new运算符会返回一个指针:
    //类型名 *指针名=new 类型名
    //*指针名=该类型的某一个值
    
    typename *ptr=new typename;
    *ptr=value;
    new分配的内存是动态内存,其位于称为堆(heap)或者自由存储区(free store)的内存块中正常声明的常规变量的内存在编译时即分配好,属于静态内存,且储存于称为栈(stack)的内存区域中。堆、栈是不同的数据结构。

    当不需要使用new分配的内存时,需要使用delete将该内存释放,避免内存溢出(memory leak,即分配的内存无法使用)。对于上面new出来得指针ptr,其释放内存的代码如下:
    //delete newAllocPointer
    delete ptr; 
    当用delete释放内存时,只需不需要再声明指针的类型。

    需要注意的是:
    #delete只能用于释放new创建出的新内存
    #不能用delete释放同一内存两次
    #delete与new应配套使用,但它们可以位于不同代码块中(如函数内new、函数外delete)。
  2. 动态数组的创建与释放

    使用new创建动态数组仍是利用指针,但是用delete [ ](而不是delete)释放动态数组内存的代码如下:
    //typename arrayName=new typename [Size]
    double *arr=new double [10];//创建含有10个double类变量的动态数组
    delete [] arr;//释放动态数组内存
    创建了动态数组后,即可用指针名表示数组,例如上面代码段中,arr下标为i的元素即为arr[i]。
     
  3. 动态结构的创建、使用

    也可以使用new与指针来创建一个动态结构,其代码与new一个基本类型的内存没什么不同:
    struct student
    {
        int ID;
        char class[10];
        ...
    };
    
    student *ptr=new student;
    delete ptr;//释放动态结构内存
    但是,类似于函数中以结构指针为参数,动态结构需要用箭头成员运算符->来访问数据成员,如ptr->ID。也可以使用(*ptr).ID来访问数据成员。

     
  4. C++中的new/delete与C中的malloc()/free()的区别

    区别如下:
    #new 不止是分配内存,而且会调用类的构造函数,delete则会调用类的析构函数;malloc则只分配内存,不会进行初始化类成员的工作,free也不会调用析构函数。简单地说,new可以被看做执行malloc()后再执行类的构造函数,delete则是执行free()后再执行类的析构函数。

    #new是C++中的一个操作符,不需要包含头文件即可使用;malloc是C中的一个函数,需要包含头文件stdlib.h

    #new可以自动计算所需分配的内存大小,并返回相应类型的指针;malloc()则需要自己计算所需大小(或者使用sizeof()计算),并返回一个通用指针void *,后续需要强制转换。
     

模板类vector与array

C++中提供了动态数组的模板类vector与array,vector类是动态数组,可以使用变量作为动态数组大小。array和数组类型,只能使用常量或者整型字面量或者表达式创建。

  1. vector类

    vector类的创建代码如下:
    #include<vector>//vector类在头文件vector中
    using namespace std;
    
    //vector<typename> vectorName(vectorSize)
    vector<int> v(10);//创建一个含有10个int类型变量的动态数组
    int size=1000;
    vector<int> v_2(size);//利用变量size创建长度为1000的动态数组
    后只需要使用v[i]即可遍历每个动态数组元素,使用v.来调用vector类的成员函数。
     
  2. array类

    array类则只是一个固定数组,其使用的是静态内存,使用效率却比动态数组要高。

    array类的创建代码如下:
    #include<array>//array类在头文件array中
    using namespace std;
    
    //array<typename> arrayName(arraySize)
    array<int,10> v;//创建一个长度为10的int数组
  3.  数组、vector、array的区别
    #vector是动态数组,数组大小在程序运行时可以改变,使用的是堆内存;数组、array是静态数组,其大小固定,在编译时即分配好,使用的是栈内存。
    #可以将一个array对象通过赋值运算符=赋给另一个相同大小的array对象;而数组、vector则不行。
    #数组不是一个类,没有类成员函数;vector、array都是类,其实例化对象可以调用类成员函数,如vector类对象v.at(i),用于访问下标为i的元素。

指针数组、指向数组的指针与二维数组

这三个概念有点绕,但其本质是考虑*与[]的优先级。

需要牢记的东西是:
#若没有括号()将*于变量名括起来,则优先级:[]>*,[]表明与其结合的变量为一个数组。
#若有括号()将*于变量名括起来,则先运算括号内的,整个括号的值是相应类型的值或者数组名,则该变量为指针,后再与[]结合,表明是该指针将指向一个数组。
#*pointerName是指针指向的值,其可以是变量值,也可以是数组名,当为数组名时,(*pointerName)或者*pointerName就表明这个指针是指向数组的指针

  1. 指针数组

    此时不需要括号,[]优先级比*高,[]将先从右至左与变量名结合,表明这是一个数组,后再与*结合,表明这是一个元素为指针的数组,其代码如下:
    int a=1,b=2,c=3;
    int *arrPtr[3]={&a,&b,&c};
  2. 指向数组的指针与二维数组

    指向数组的指针声明如下:
    int arr[4]={1,2,3,4};
    int (*ptrArr)[4];//ptrArr是一个指向含有4个int的数组的指针。
    ptrArr=&arr;//

    括号使得*优先与变量名结合。这个代码的意思有点像指针的声明:(*ptrArr)是一个数组名,int (*ptrArr)[4]则是一个长度为4的int数组,ptrArr则是指向数组名的指针

    二维数组可以通过数组的数组或者指向数组的指针两种方式表示:

    int arrTwoDimen[3][2]={{1,2},{3,4},{5,6}};//arrTwoDimen是一个3行2列的表。
    int (*ptrArr)[2]=&arrTwoDimen;
    

    同理,二维数组作为函数参数,也可以使用这两种表示方式。

函数指针

和变量相似,函数也具有地址。函数指针可以用于函数调用

  1. 函数指针的声明、初始化、使用
    函数指针的声明与初始化的代码如下:
     
    int sum(int a,int b)//函数名sum本身即为函数指针
    {
        ...
    }
    
    int mod(int a,int b)
    {
        ...
    }
    //函数指针声明格式:
    //函数返回类型 (*指针名)(函数参数类型)
    int (*ptrF)(int,int)=sum//定义一个叫ptrF的指针,其值为函数sum的地址。
    ptrF=mod;//还可以修改函数指针,只需要指针与函数的特征标和返回类型相同即可
    
    从上述代码可以看出,函数指针的声明和函数原型/定义很相似,只是把函数名换成了(*函数指针名),并只写出函数参数类型即可(不需要写出参数名),注意括号与*不能省略。

    C++将一个函数的参数类型、个数、排列顺序称为特征标函数指针与函数的特征表与返回类型相同时才能将函数名作为地址赋给函数指针,否则无法通过编译。

    当函数指针ptrF初始化以后,即可通过函数指针名ptrF或者(*ptrF)调用函数:
    int result=ptrF(1,2);
    int result=(*ptrF)(1,2);//第二种调用形式
    在这里,C++认为两种方式都可以,尽管ptrF才是指针,*ptrF算是函数。

    函数指针也可以作为参数传入函数中,使得函数可以在另一个函数中调用,这在一些有相同步骤但只是函数调用不同的例程中很有用。例如统计一个统计函数运行时间的函数,即可以将一个函数指针作为参数:
    #include<time.h>//clock()函数在头文件time.h中
    
    int sum(int a,int b)//函数名sum本身即为函数指针
    {
        ...
    }
    
    int mod(int a,int b)
    {
        ...
    }
    
    clock_t timeOfFunc(int (*ptrF)(int,int),int a, int b)
    {
        clock_t begin=clock();
        ptrF(a,b);
        clock_t end=clock();
        return end-begin;
    }
  2. 函数指针数组与指向函数指针数组的指针

    函数指针也可以组成数组。函数指针数组声明的代码示例如下:
    int sum(int a,int b)//函数名sum本身即为函数指针
    {
        ...
    }
    
    int mod(int a,int b)
    {
        ...
    }
    //声明格式
    //返回类型 (*数组名[数组大小])(参数类型)={数组初始化列表}
    int (*arrPtr[2])(int,int)={sum,mod};//特征标为(int,int)的、返回类型为int的函数指针数组arrPtr
    
    
    
    []的优先级高于*,因此*arrPtr[2]表明这是一个指针数组。后即可通过arrPtr[i]来遍历每个元素、调用函数:
     
    int (*ptrF_0)(int,int)=arrPtr[0];//将arrPtr[0]赋给一个函数指针
    int result=arrPtr[0](1,2);//使用arrPtr[0]调用函数sum

    声明一个指向函数指针数组的指针(即指向“指向指针的指针”的指针)的代码如下所示:

    //f1()、f2()是两个特征标为(int)、返回类型为int的函数
    int (*ptrF[2])(int)={f1,f2};//声明一个含有2个函数指针的数组
    int (*(*ptrP)[2])=&ptrF//ptrP是一个指向ptrF函数指针数组的指针

    可以这么理解这个声明:*(*ptrP)[2]是一个含有两个特征标为(int)、返回类型为int的函数指针的数组,那么*ptrP是数组名,ptrP是一个值为数组地址。
     

  3. typedef与函数指针管理

    关键字typedef可以将某一类名称用一个别名替代,使变量类型更具有实际意义,便于管理与使用,尤其是对于函数指针。其声明格式如下:
     
    //声明格式:
    //typedef typename otherName
    typedef int ID;//不要忘记分号!!
    
    //f1()、f2()是两个特征标为(int)、返回类型为int的函数
    int (*ptrF)(int);//声明一个特征标为(int)、返回类型为int的函数指针
    
    typedef int (*ptrF)(int);
    ptrF p1=f1;//ptrF是特征标为(int)、返回类型为int的函数指针的别名,就不需要写上面的一长串的声明了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值