《c++入门经典》笔记10

《c++入门经典》笔记10

第十章 创建指针

10.1理解指针及其用途

变量是可存储一个值的对象:整型变量存储一个数字,字符变量存储一个字母,而指针是存储内存地址的变量

计算机内存是存储变量值的地方。根据约定,计算机内存被划分成按顺序编号的内存单元,每个内存单元都有对应的地址。内存中,不管其类型是什么,每个变量都位于特定的地址处。

内存编址方案随计算机而异。通常,程序员无须知道变量地址,如果想要获取地址信息,可使用地址运算符&

程序清单10.1 Address.cpp

#include<iostream>

int main()
{
    unsigned short shortVar = 5;
    unsigned long longVar = 65535;
    long sVar = -65535;

    std::cout<<"shortVar:\t"<<shortVar;
    std::cout<<"\tAddress of shortVar:\t"<<&shortVar<<"\n";
    std::cout<<"longVar:\t"<<longVar;
    std::cout<<"\tAddress of longVar:\t"<<&longVar<<"\n";
    std::cout<<"sVar:\t"<<sVar;
    std::cout<<"\tAddress of sVar:\t"<<&sVar<<"\n";

}

(地址默认以16进制表示法输出的)

您运行该程序时,变量的地址将不同,因为这取决于内存中存储的其他内容以及可用的内存有多少。

在指针中存储地址

每个变量都有地址,即使不知道变量的具体地址,也可将该地址存储在指针变量中。

int howOld = 50;
int* pAge = nullptr;//初始化一个int型空指针变量,这样能更明显看出来pAge类型是int*,但c/c++的标准写法是int *pAge
pAge = &howOld;//将howOld的地址取出来放入指针变量pAge中

间接运算符

间接运算符(*)又被称为解引用运算符。对指针解除引用时,将获取指针存储的地址处的值。

int howOld = 50;
int* pAge = &howOld;
int yourAge;
yourAge = *pAge;//yourAge的值变成了50
*pAge = 10;//howOld的值变成了10,而yourAge的值还是50

指针pAge前面的间接运算符(*)表示“存储在......处的值”。这条赋值语句的意思是,从pAge指向的地址处获取值,并将其赋给yourAge。看待这条语句的另一种方式是,不影响指针,而是影响指针指向的内容(比如上面最后一条语句)。

使用指针操作数据(其实上面那个例子就是)

程序清单10.2 Pointer.cpp

#include <iostream>

int main()
{
    int myAge;
    int *pAge = nullptr;

    myAge = 5;
    pAge = &myAge;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n\n";

    std::cout << "*pAge = 7\n";
    *pAge = 7;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n\n";

    std::cout << "myAge = 9\n";
    myAge = 9;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";
}

查看存储在指针中的地址:

程序清单10.3 PointerCheck.cpp

#include <iostream>

int main()
{
    unsigned short int myAge = 5, yourAge = 10;
    unsigned short int *pAge = &myAge;

    std::cout << "pAge: " << pAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";

    pAge = &yourAge;
    std::cout << "after reassign the pAge point to yourAge : "
              << "\n";
    std::cout << "pAge: " << pAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";

    return 0;
}

为何使用指针

熟悉指针的语法后,便可将其用于其他用途了,指针最长用于完成如下三项任务:

  • 管理堆中的数据;
  • 访问类的成员数据和成员函数;
  • 按引用将变量传递给函数

10.2堆和栈

(这部分其实如果有一点数据结构或者操作系统基础更好)

程序员通常需要处理下述五个内存区域

  • 全局名称空间
  • 寄存器
  • 代码空间

局部变量和函数参数存储在栈中,代码当然在代码空间中,而全局变量在全局名称空间中。寄存器用于内存管理,如跟踪栈顶和指令指针,几乎余下的所有内存都分配给了堆,堆有时也被称为自由存储区。

局部变量的局限性是不会持久化,函数返回时,局部变量将被丢弃。全局变量解决了这种问题,但代价是在整个程序中都能访问它,这导致代码容易出现bug,难以理解与维护。将数据放在堆中可解决这两个问题。

每当函数返回时,都会清理栈(实际上,开始调用函数时,栈空间进行压栈操作;函数调用完时,栈空间进行出栈操作)。此时,所有的局部变量都不在作用域内,从而从栈中删除。只有到程序结束后才会清理堆,因此使用完预留的内存后,您需要负责将其释放(手动GC)。让不再需要的信息留在堆中称为内存泄露(垃圾滞留)

堆的优点在于,在显示释放前,您预留的内存始终可用。如果在函数中预留堆中的内存,在函数返回后,该内存仍可用。

以这种方式(而不是全局变量)访问内存的优点是,只有有权访问指针的函数才能访问它指向的数据。这提供了控制严密的数据接口,消除了函数意外修改数据的问题。

关键字new

在c++中,使用new关键字分配堆中的内存,并在其后指定要为之分配内存的对象的类型,让编译器知道需要多少内存。比如new int分配4字节内存。

关键字new返回一个内存地址,必须将其赋给指针。

int *pPointer = new int;//指针pPointer将指向堆中的一个int变量
*pPointer = 72;//将72赋值给pPointer指向的堆内存变量

关键字delete

使用好了分配的内存区域后,必须对指针调用delete,将内存归还给堆空间。

delete pPointer;

对指针调用delete时,将释放它指向的内存。如果再次对该指针调用delete,就会导致程序崩溃(delete野指针)。删除指针时,应将其设置为nullptr,对空指针调用delete是安全的。

Animal *pDog = new Animal;
delete pDog;//释放内存
pDog = nullptr;//设置空指针
delete pDog;//安全行为

程序清单10.4 Heap.cpp

#include <iostream>

int main()
{
    int localVariable = 5;
    int *pLocal = &localVariable;
    int *pHeap = new int;
    if (pHeap == nullptr)
    {
        std::cout << "Error! No memory for pHeap!!";
        return 1;
    }
    *pHeap = 7;
    std::cout << "localVariable: " << localVariable << std::endl;
    std::cout << "*pLocal: " << *pLocal << std::endl;
    std::cout << "*pHeap: " << *pHeap << std::endl;
    delete pHeap; //此处只是释放了堆中new分配的内存,并没有删除指针,所以下面可以接着用。
    pHeap = new int;
    if (pHeap == nullptr)
    {
        std::cout << "Error! No memory for pHeap!!";
        return 1;
    }
    *pHeap = 9;
    std::cout << "*pHeap: " << *pHeap << std::endl;
    delete pHeap; //再次释放new出来的内存
    return 0;
}

另一种可能无意间导致内存泄露的情形是,没有释放指针指向的内存就给它重新赋值。

int *pPointer = new int;
*pPointer = 72;
pPointer = new int;
*pPointer = 50;//指针变量指向了一个新new出来的存有50的int型变量,但是之前那个存有72的堆内存变量还没被释放,也就造成了内存泄露

上述代码应该修改成这样:

int *pPointer = new int;
*pPointer = 72;
delete pPointer;
pPointer = new int;
*pPointer = 50;

也就是说一个不存在内存泄露的c++程序至少其new与delete是成对的,或者说是数量相等的。(这可苦了c艹程序员咯!)

空指针常量:

在较早的c++版本中,使用0或者NULL来设置指针为空值。

int *pBuffer = 0;
int *pBuffer = NULL;

但是其实因为定义NULL的语句是一个预处理宏:

#define NULL 0

所以其实NULL和0一个意思,上面两句也是一个意思。

但是当某个函数进行了重载,参数有指针类型也有int型的时候,传了一个值为0的空值指针进去,这个时候会出现二义性:

void displayBuffer(char*);//这个函数的参数是char型指针变量
void displayBuffer(int);

如果将空指针作为参数去调用,那么会调用displayBuffer(int),这个时候就会造成运行与预期不同。

所以才应该使用关键字nullptr

int *pBuffer = nullptr;

程序清单10.5 Swapper.cpp

#include <iostream>

int main()
{
    int value1 = 12500;
    int value2 = 1700;
    int *pointer2 = nullptr;
    pointer2 = &value2;
    value1 = *pointer2;
    pointer2 = 0;
    std::cout << "value = " << value1 << "\n";

    return 0;
}

posted @ 2020-07-15 18:57  小立哥  阅读(251)  评论(0编辑  收藏  举报