C++指针(面试高频考点2)

C++内存模型

在 C++ 中,程序运行时,内存主要分成四个区,分别是栈、堆、数据段代码段

栈:存储局部变量、函数参数和返回值。

堆:存储动态开辟内存的变量。

数据段:存储全局变量和静态变量。

代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。

栈和堆的主要区别:

1)管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收。

2)空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)。

3)分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。

4)分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的。

5)是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。

6)增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。

动态分配内存new和delete

使用堆区的内存有四个步骤:

1)声明一个指针;

2)用new运算符向系统申请一块内存,让指针指向这块内存;

3)通过对指针解引用的方法,像使用变量一样使用这块内存;

4)如果这块内存不用了,用delete运算符释放它。

申请内存的语法:new 数据类型(初始值); // C++11支持{}

如果申请成功,返回一个地址;如果申请失败,返回一个空地址(暂时不考虑失败的情况)。

释放内存的语法:delete 地址;

释放内存不会失败(还钱不会失败)。

注意:

  • 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。

  • 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。

  • 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。

  • 就算指针的作用域已失效,所指向的内存也不会释放。

  • 用指针跟踪已分配的内存时,不能跟丢。

示例:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int main()
{
    // 1)声明一个指针;
    // 2)用new运算符向系统申请一块内存,让指针指向这块内存;
    // 3)通过对指针解引用的方法,像使用变量一样使用这块内存;
    // 4)如果这块内存不用了,用delete运算符释放它。
    // 申请内存的语法:new 数据类型(初始值);   // C++11支持{}
    // 释放内存的语法:delete 地址;
    int* p = new int(5);
    cout << "*p=" << *p << endl;
    *p = 8;
    cout << "*p=" << *p << endl;
    delete p;

    /*    for (int ii = 1; ii > 0; ii++)
    {
        int* p = new int[100000];     // 一次申请100000个整数,这个语法以后再讲。
        cout << "ii="<<ii<<",p=" << p << endl;
    }/*
}

二级指针

指针指针变量的简称,也是变量,是变量就有地址

指针用于存放普通变量地址

二级指针用于存放指针变量地址

声明二级指针的语法:数据类型** 指针名;

使用指针有两个目的:1)传递地址;2)存放动态分配的内存的地址。

在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。

把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中指针的值。

示例:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void func(int **pp)
{
    *pp = new int(3);
    cout << "pp=" << pp << ",*pp=" << *pp << endl;
}

int main()
{
    /*int ii = 8;               cout << "ii=" << ii << ",ii的地址是:" << &ii << endl;
    int* pii = &ii;        cout << "pii=" << pii << ",pii的地址是:" << &pii << ",*pii=" << *pii << endl;
    int** ppii = &pii;  cout << "ppii=" << ppii << ",ppii的地址是:" << &ppii << ",*ppii=" << *ppii << endl;
    cout << "**ppii=" << **ppii << endl;*/

    int* p=0;
    func(&p);
    /*{
        int** pp = &p;
        *pp = new int(3);
        cout << "pp=" << pp << ",*pp=" << *pp << endl;
    }*/

    cout << "p=" << p << ",*p=" << *p << endl;
}

空指针

在C和C++中,用0或NULL都可以表示空指针。

声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。

1)使用空指针的后果

如果对空指针解引用,程序会崩溃。

如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。

在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。

为什么空指针访问会出现异常?

NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。

2)C++11的nullptr

用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void *)0。

NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议用nullptr替代NULL吧,而NULL就当做0使用。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void func(int* no, string* str)    // 向超女表白的函数。 
{
    if ((no == 0) || (str == 0)) return;  

    cout << "亲爱的" << *no << "号:" << *str << endl;
}

int main()
{
    // int bh = 3;      // 超女的编号。
    // string message = "我是一只傻傻鸟。";          // 向超女表白的内容。
    int* bh = 0;   //  new int(3);
    string* message = 0; //  new string("我是一只傻傻鸟。");
    
    func(bh,message);            // 调用向超女表白的函数。

    delete bh; delete message;
}

野指针

野指针就是指针指向的不是一个有效(合法)的地址。

在程序中,如果访问野指针,可能会造成程序的崩溃。

出现野指针的情况主要有三种:

1)指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。

2)如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。

3)指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。

规避方法:

1)指针在定义的时候,如果没地方指,就初始化为nullptr。

2)动态分配的内存被释放后,将其置为nullptr。

3)函数不要返回局部变量的地址。

注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面列举几道 C++ 指针面试题供参考。 1. 指针和引用有什么区别? 指针和引用都可以用来访问内存中的变量,但是它们有以下几点区别: - 指针可以被赋值为 `nullptr`,而引用不行。 - 指针可以在运行时指向不同的变量,而引用在声明时就必须绑定到一个变量上,并且不能更改绑定的变量。 - 指针可以进行算术运算和解引用操作,而引用不能。 - 指针可以被重新赋值为指向不同类型的变量,而引用必须与原始变量类型匹配。 2. 指针和数组有什么关系? 指针和数组有着紧密的关系,因为数组名其实就是数组首元素的地址。例如,下面的代码定义了一个数组并初始化: ```c++ int arr[] = {1, 2, 3, 4, 5}; ``` 可以通过下标访问数组元素,也可以通过指针访问数组元素。例如,下面的代码使用指针访问数组元素: ```c++ int* p = arr; for (int i = 0; i < 5; i++) { cout << *(p + i) << " "; } ``` 这里的 `p` 是一个指向 `int` 类型的指针,它指向数组 `arr` 的首元素。在循环中,我们通过指针访问数组元素,其中 `*(p + i)` 表示指针 `p` 加上 `i` 个偏移量后所指向的元素。 3. 如何避免空指针引用? 空指针引用是一种常见的程序错误,可以通过以下几种方式避免: - 在使用指针之前,先将其初始化为 `nullptr`。 - 在使用指针之前,先进行空指针判断。 - 使用智能指针,可以自动管理指针的生命周期,并且可以避免空指针引用。 例如,下面的代码演示了如何进行空指针判断: ```c++ int* p = nullptr; if (p != nullptr) { *p = 10; } ``` 在这个示例代码中,我们先将指针 `p` 初始化为 `nullptr`,然后在使用指针之前,先进行空指针判断。由于 `p` 是空指针,所以不会执行赋值操作,从而避免了空指针引用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值