C++ (指针,智能指针,异常处理)

指针

  指针是一个变量,他的值是另一个变量的地址,即内存位置的直接地址。在C++中指针和引用会有很大的区别:1、指针存储的是一个变量的地址,引用根原来的变量实质上是同一个东西,是原变量的别名;2、指针可以有多级,引用只能有一级;3、指针可以为空并且声明和定义是可以分开的,引用不能为NULL并且在定义时必须初始化;4、指针在初始化后可以改变指向,而引用在初始化后不可以再改变;5、使用sizeof指针得到的是本指针的大小,而sizeof引用得到的是引用所指向变量的大小;6、当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以;7、引用本质是一个指针,同样会占4字节内存(在64位编译环境下指针大小占8字节,32位环境下指针大小占4字节,而指针占内存的大小根编译环境有关,与机器位数无关);指针是具体变量,需要占用存储空间(,具体情况还要具体分析)。针对引用和指针的不同特性在传递参数时基于不同的情况会选择不同的方式:如果要返回函数内局部变量的内存就用指针,并且使用指针要开辟内存用完需要释放不然容易内存泄漏指针传参本质上是值传递(形参指针变了,实参指针不会变),而如果我们的栈空间比较小往往用引用,因为这不用创建临时变量,开销比较小,并且类对象作为参数传递的时候都是使用引用,因为这是C++类对象传递的标准方式。指针的加减功能本质是对所指地址的移动,移动的步长和指针类型是有关系的,所以在涉及指针加减时要注意不能加多或是加少导致指向了未知的内存地址。
  区分如下几种指针类型:

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
int const*p/const int* p
int *const p

  int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
  int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
  int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  int (p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
  int const
p是常量指针,这是一个指针,指向的是一个常量,所以指针保存的地址可以改变,指针指向的值不能改变。
  int *const p是指针常量,指针本身的值(存放地址)不能改变,指针指向的值可以改变(在第五版C++ Primer中的常量指针与这里描述的不符)
  野指针和悬空指针:都是是指向无效内存区域(这里的无效指的是"不安全不可控")的指针,访问行为将会导致未定义行为。野指针指的是没有被初始化的指针,为了防止出错一般指针在初始化时要赋值为nullptr或者直接定义时初始化,这样就不会有非法内存访问。悬空指针:指针最初指向的内存已经被释放的指针(free或delete之后没有及时置空,需要在释放操作后立即置空)在C++中一般使用智能指针来避免悬空指针的产生。
  在继承中指针和引用总共有向上类型转换和向下类型准换,向上类型转换是将派生类的指针或引用转换为基类的指针或引用,这是自动进行的并且是安全的,向下类型转换是将基类指针或引用转换为派生类指针,这不会自动进行因为一个基类对于几个派生类,并且如果要转换得加动态类型识别技术用dynamic_cast进行转换。
  数组和指针的区别:1、数组在内存中是连续存放的,开辟连续的内存空间;2、使用sizeof(数组)可以计算出数组的字节数,使用sizeof§可以得到指针变量的字节数而不是p所指的内存容量;3、在使用下标的时候数组和指针的用法是相同的,都是原地址加上下标值,不过数组的原地址是数组首地址是固定的,而指针原地址不固定;4、在向函数传递参数时,如果实参是一个数组那么接收的形参就是对应的指针,就是传递过去的数组是首地址而不是整个数组,这会提升效率。
  回调函数也是运用指针来实现的,是一个通过函数指针调用的函数,把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,就说这是回调函数。当发生某个事件时,系统或者其他函数会自动调用定义的一段函数,回调函数就像是一个中断处理函数,由系统在符合设定的条件时自动调用,因此需要做函数的声明(与定义有区别,声明只是将变量位置告诉编译器,不分配内存空间,定义要在定义的地方分配内存空间,变量可以在多处声明,只能在一处定义,声明函数指针一定得加括号,函数声明一般在头文件,函数定义一般在源文件),定义,触发条件设置(在函数中把回调函数名称转化为地址作为一个参数,以便用于系统调用)。

智能指针

  智能指针是什么:智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象生命周期结束时,会自动调用析构函数释放资源(使用普通指针会有内存泄漏,就是忘记释放内存或是二次释放的问题),使用智能指针在初始化时要注意,智能指针是一个模板类,可以指定类型,传入指针通过构造函数初始化,也可以使用make_shared初始化,但是不能直接将指针直接赋值给一个智能指针
  常用的智能指针有四种:shared_ptr,unique_ptr,weak_ptr,auto_ptrshared_ptr的实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数+1,每当减少一个智能指针指向对象时,引用计数-1,当计数为0的时候会释放动态分配的资源。1、智能指针将一个计数器与类指向的对象关联,引用计数器记录共有多少个类对象共享同一指针;2、每次创建类的新对象时,初始化指针并将引用计数置位1;3、当对象作为另一对象的副本而创建时,拷贝构造函数会拷贝指针并增加与之相应的引用计数;4、对一个对象进行赋值操作时,赋值操作符会减少左操作数所指对象的引用计数(如果减到0就删除对象),并增加右操作数所指对象的引用计数;5、调用析构函数时,构造函数减少引用计数(如果引用计数减至0,就删除基础对象)。shared_ptr实际上维护了两部分信息分别是指向共享资源的指针和引用计数等共享资源的控制信息——维护一个指向控制信息的指针。

{
    std::shared_ptr<int> sptr = std::make_shared<int>(200);
    assert(sptr.use_count() == 1);  // 此时引用计数为 1
    {   
        std::shared_ptr<int> sptr1 = sptr;
        assert(sptr.get() == sptr1.get());
        assert(sptr.use_count() == 2);   // sptr 和 sptr1 共享资源,引用计数为 2
    }   
    assert(sptr.use_count() == 1);   // sptr1 已经释放
}
// use_count 为 0 时自动释放内存

  unique_ptr:采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源,转移一个unique_ptr会将所有权全部从源指针转移给目标指针,源指针被置位空;所以unique_ptr不支持普通的拷贝和赋值,不能用在STL容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁,所以还是能保证独占所有权);如果要拷贝一个unique_ptr那么拷贝结束后,这两个unique_ptr会指向相同的资源,造成在结束时对同一内存指针多次释放而程序崩溃。unique_ptr的生命周期为从unique_ptr指针创建开始,到离开作用域,离开作用域时将其指向的对象销毁,这是基本的RAII思想,在生命周期内可以改变智能指针所指向的对象(创建智能指针时通过构造函数指定,通过reset方法重新指定,通过release方法释放所有权,通过移动语义转移所有权)。使用案例如下:

{
    std::unique_ptr<int> uptr = std::make_unique<int>(200);
    std::unique_ptr<int> uptr1 = uptr;  // 编译错误,std::unique_ptr<T> 是 move-only 的
    std::unique_ptr<int> uptr2 = std::move(uptr);
    assert(uptr == nullptr);
    //也可以指向一个数组
    std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10);
    for (int i = 0; i < 10; i++) {
        uptr[i] = i * i;
    }   
    for (int i = 0; i < 10; i++) {
        std::cout << uptr[i] << std::endl;
    }   
}

  weak_ptr:弱引用,引用计数有一个问题就是互相引用形成环(环形引用)这一般是使用多个shared_ptr时出现了指针之间相互指向,从而形成环,类似于死锁现象,这种情况下没法正常调用对象的析构函数,两个指针指向的内存都无法释放(两个指针引用计数都减为1后通过因为互相引用都等待对方先释放计数器变为0,自己再释放),需要使用weak_ptr破坏环形引用,weak_ptr是一个弱引用,是为了配合shared_ptr而引入的一个智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说它只会引用,不会计数;如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构之后,不管还有没有weak_ptr引用该内存,内存也会被释放,所以weak_ptr不保证指向的内存一定是有效的,在使用前需要用lock()检查weak_ptr是否为空指针。而weak_ptr之所以能够解决环形引用的问题是因为只要出了作用域就会自动析构。
  auto_ptr:主要是为了解决“有异常抛出时发生内存泄漏”的问题,在构造时取得某个对象的控制权,在析构时释放该对象,我们实际上创建一个auto_ptr<Type>类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放,所以不会有内存泄漏。构造函数用了explicit关键字阻止了一般指针转换为auto_ptr,所以不能直接将一般指针赋值给auto_ptr对象,必须使用auto_ptr的构造函数创建对象但是支持所拥有的的指针类型之间的隐式类型转换。因为auto_ptr在析构时会删除它所拥有的指针(使用的是delete而不是delete[],所以不能管理数组),所以使用时避免多个auto_ptr对象管理同一个指针。一般使用T* get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回。auto_ptr有拷贝语义,拷贝后源对象变得无效,这会引发很严重的问题,而unique_ptr没有拷贝语义。auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中,STL容器中的元素经常要支持拷贝、赋值操作,在这个过程中auto_ptr会传递所有权,所以不能在ST中使用,这也是auto_ptr被弃用的主要原因,被unique_ptr取代。

异常处理

  C++中的异常有语法错误(编译器错误),比如变量未定义,括号不匹配等编译器在编译时能发现的错误,而且还会知道错误的位置和原因,还有的是运行时的错误,比如数组下标越界、系统内存不足等,这类问题需要引入异常处理机制来解决,如果不对异常信息进行处理,程序可能会崩溃,C++的异常处理机制主要通过try,throw和catch关键字实现,使用流程是先执行try包裹的语句块,如果执行过程中没有异常发生,则不会进入任何catch包裹的语句块,如果发生异常,则使用throw进行异常抛出,再由catch进行捕获,throw可以抛出各种数据类型的信息,可以使用数字,也可以自定义异常class。使用catch(…)的方式可以捕获任何异常(不推荐)。
  函数的异常声明列表:如果能提前知道函数可能发生什么异常,就可以在声明和定义时指出所能抛出的异常的列表int fun() throw(int,double,A,B,C){...};,这种写法表明函数可能会抛出int,double或者A,B,C类型的异常,如果throw中为空表明不会抛出异常,如果没有throw表明可能抛出任何异常。除此之外C++还有标准异常类exception,C++标准库中的一些类代表异常都是从exception中派生 而来的,比如bad_typeid:运用typeid运算符,如果操作数是一个多态类指针,而指针值为NULL,就会抛出异常。bad_cast:在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。bad_alloc:在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。out_of_range:用 vector 或 string的at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。
  对于常见错误coredump的调试:coredump是程序由于异常或者bug在运行时异常退出或者终止,在一定的条件下生成的一个叫做core的文件,这个core文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等。对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息。使用gdb调试coredump:gdb [可执行文件名] [core文件名]
  如果在构造函数中发生异常,那么控制权会转出构造函数之外,对象就不算在构造函数中执行完毕,这时析构函数就不会被调用,会造成内存泄漏。如果使用unique_ptr来取代指针类成员,就免除了异常时资源泄漏的问题,因为不再需要析构函数中手动释放资源。如果析构函数中抛出异常,并且没有对其进行捕捉,那么析构函数会执行不完全。

参考资料:阿秀的学习笔记智能指针

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值