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

2019-01-04  本文已影响17人  yuanCruise

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

上一篇下一篇

猜你喜欢

热点阅读