• C++ 基本知识整理


      本基本知识整理及代码源于牛客网C++面试宝典导读,

      网址https://www.nowcoder.com/tutorial/93/a34ed23d58b84da3a707c70371f59c21

      Static关键字 (查询类变量内存所在位置)

      1. 全局静态变量

      内存中位置:静态存储区,且程序运行期间一直存在。

      未经初始化的全局静态变量自动初始化为0。

      全局静态变量在声明文件之外是不可见的。

      2.局部静态变量

      内存中位置:静态存储区。

      未初始化自动初始化为0。

      作用域为局部作用域,但离开作用域后不会销毁,仍然驻留在内存中,再次访问时值不变。

      3.静态函数

      函数定义声明默认为extern,但静态函数只在声明的文件中可见,其他文件不可见。

      即使用static修饰则该函数只能在本文件中使用,且不会与其他文件中同名函数冲突。

      全局函数应在头文件中声明,局部函数在cpp中声明带static

      4.类静态成员

      同一个类中静态成员在多个对象之间数据共享。

      5.类静态函数

      .在静态成员函数中不能直接引用类中的非静态成员,但可以引用类中的静态成员。静态成员函数中药引用非静态成员时要通过对象来引用

      C++中内存分配

      栈区:编译器自动分配和释放,存放函数参数值、局部变量等,操作方式类似栈。栈区最大值为1M,可以调整

      堆区:手动申请的动态内存,分配方式类似于链表。

      全局/静态区(数据段):已初始化的全局变量和静态变量。

      BSS段:未初始化的全局变量和静态变量,以及被初始化为0的全局变量和静态变量

      程序代码区(代码段):存放函数体二进制代码,只读存储区。

      函数调用过程

      首先参数从右向左压栈,将返回地址压栈,再将栈帧压栈。

      C++如何处理返回值  

      生成一个临时变量,将其引用作为函数参数传入函数内。不能返回局部变量指针和引用。

      strcpy和strlen

      strcpy为字符串拷贝函数,原型:char *strcpy(char* dest, const char *src);

      从src拷贝到dest,遇到‘’结束。可能导致越界,安全应用strncpy(char* dest, const char *src,n)

      strlen计算字符串长度。到‘结束’

      ++i 和 i++实现

      1.++i

    int & int::operator++()
    {
        *this+=1;
        return *this;
    }

      2.i++ (返回应是常量!

    const int int::operator++(int)
    {
          int ret=*this;
         *this++;
         return ret;  
       }

      指针和引用的区别

      1. 指针有分配空间(大小是4个字节),引用没有(sizeof大小为引用对象的大小)

      2. 指针初始化为NULL(nullptr),引用必须初始化为一个已有对象的引用。

      3.参数传递时,指针需要解引用(*)才可以对对象操作,引用则可以直接修改。

      4.指针在使用中可以改变指向的对象,但引用仅是别名,不能改变。

      5.可以有多级指针,但引用只有一级(&&为右值引用)

      new/delete与malloc/free的区别

      new/delete是C++关键字,而malloc/fre是C语言库函数。

      new是先分配内存,再构造对象,然后将对象绑定到内存的地址上。

      delete是先析构对象,再释放内存。

      malloc和free仅对内存操作,不使用构造/析构函数。

      四个智能指针

      四个智能指针为:shared_ptr,weak_ptr,unique_ptr,auto_ptr,前三个C++11支持

      智能指针原理:智能指针为一个类,超出类作用域后,类会子懂调用析构函数,析构函数则会自动释放资源。

      所以智能指针即在函数结束时自动释放内存空间,不需要手动释放。

      1. shared_ptr

      多个该智能指针可以指向相同对象,该对象和其相关资源在最后一个引用被销毁时释放。

      通过use_count()查看资源所有者个数。可以通过new来构造,也可以传入其他智能指针构造。

      调用release()时,可以手动释放资源所有权,且引用计数减一,当计数为0时资源被释放。

      使用make_shared()或构造函数可以传入普通指针,或者通过get函数获取普通指针。

      成员函数:

      use_count()  返回引用计数

      unique 返回是否独占(即引用计数为1)

      reset()  放弃对象的所有权

      2. unique_ptr(替换auto_ptr)

      同一时间只有一个智能指针可以指向该对象。

      不能将一个unique_ptr赋值给另一个,但如果源unique_ptr为一个右值则可以赋值。

      3. weak_ptr

      weak_ptr不控制对象生命周期,指向share_ptr管理的对象,仅是一种访问手段,但不会引起引用计数的增加或减少,是一种弱引用,不能访问对象的成员函数,需要通过lock()转化为shared_ptr才可以。

      expired()  若use_count为0则返回true,否则返回false

      lock() 返回对象的shared_ptr,不存在则返回空shared_ptr。

      

      智能指针主要用于管理在堆上分配的内存,将普通指针封装成栈对象。当栈对象生存周期结束后,在析构函数中释放申请的内存,防止内存泄漏。

      在两个shared_ptr成员变量指向对方时会造成循环引用,使引用计数失效,从而导致内存泄漏。为了解决这个问题,使用weak_ptr可以解决这个问题,weak_ptr不会修改引用计数,从而避免无法访问。

       

      shared_ptr实现

      主要实现引用计数,什么时候销毁底层指针,赋值和拷贝构造时引用计数变化。

      参考:https://github.com/anbo225/shared_ptr/blob/master/sharedPtr.hpp

      储存:底层真实指针,使用指针保存引用计数。

      构造函数:

    template<typename T>
        shared_ptr<T>::shared_ptr(T *p):ptr(p),use_count(new_int(1))
    {
          
    }

      拷贝构造函数

    template<typename T>
    shared_ptr<T>::shared_ptr(const shared_ptr<T> &orig)
    {
          use_count=orig.use_count;//引用计数保存在一块内存
          this->ptr=orig.ptr;
          ++(*use_count);//引用计数加1
    }

      重载=运算符

      当赋值后,需要判断原对象的引用计数。template<typename T>shared_ptr<T>& shared_ptr<T>::operator=(const shared_ptr<T> &rhs)

    {
      if(&rhs != this){ //防止自赋值
    ++(*rhs.use_count);//增加引用计数 if((--(*use_count))==0)//原引用计数为0则释放原内存 { delete ptr; ptr=nullptr; delete use_count; use_count=nullptr; } ptr=rhs.ptr; *use_count=*(rhs.use_count); return *this;
      }
      return *this;
    }

      析构函数

    template<typename T> shared_ptr<T>::~shared_ptr()
    {
        if(ptr && --(*use_count)==0) //ptr存在
        {
            delete ptr;
            ptr=nullptr;
            delete use_count;
            use_count=nullptr;
        }
    }

      函数指针

      指向一个具体函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针指向的地址,可以使用该指针变量调用函数。

      

      fork()函数

      #include<sys/types.h>

      #include<unistd.h>

      pid_t fork(void);

      调用fork()会创建一个新进程。在子进程中fork调用返回的是0,父进程返回子进程的pid,如果错误则返回负值。

      在调用fork()后,可以使用exec()载入二进制映像来替换当前进程的映像。

      在linux中对进程内部结构采用写时复制的方法,而不是将父进程整体复制。(需要查阅)

      写个函数在main函数前执行的

      在gcc下使用attribute声明多个constructor、destructor

      

    __attribute((constructor))void before()
    {
        printf("before main
    ");
    }

      

      C++中析构函数作用

      当对象结束生命周期时,系统自动执行析构函数。一个类只能有一个析构函数,不能重载。

      如果不编写析构函数,则无法回收动态分配的内存。

      析构顺序  类本身的析构函数->对象成员的析构函数->基类析构函数

      静态函数和虚函数区别

      静态函数在编译时就已经确定运行时机,而虚函数则在运行时动态绑定。

      虚函数使用虚函数表机制,调用时会增加一次内存开销

      重载和覆盖(重写)和隐藏

      重载:两个函数名相同,但参数列表不同,在同一个作用域内。

      重写:子类继承父类,父类中函数时虚函数,在子类中重新定义了这个函数。

      C++调用C函数需要extern C,因为C语言没有重载。

      隐藏:1.子类函数与父类函数的函数名相同,参数不同,不管父类是不是虚函数,都隐藏。

            2.子类函数与父类函数名和参数都相同,但父类不是虚函数。

      虚继承

      用在多重继承中,防止继承一个类两次:例如,类A派生类B,类C,D同时继承B和C,此时就需要使用虚继承防止继承A两次。

      虚函数和多态

      多态分为静态多态和动态多态。

      静态多态指函数重载,在编译时确定。

      动态多态指虚表指针指向的虚表中的虚函数不同,运行时表现出来。

      虚函数的实现:有虚函数的类中,类最开始的部分是一个虚函数表的指针,指针指向一个虚函数表,表中存放虚函数的地址,当子类继承父类时也会继承续函数表。当子类重写父类中虚函数时,会将其继承到的虚函数表中的地址替换为重写的函数地址。使用虚函数会增加访问内存开销

      

      为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数

      析构函数必须是虚函数,为了保证创建一个子类,父类指针指向该对象时,释放基类指针可以释放掉子类的空间,防止内存泄漏。

      只有要当做父类时虚函数才需要,而虚函数则需要生成额外的虚函数表及虚指针占用额外内存,对于不会被继承的类来说则会浪费内存。

      C++中拷贝构造函数和拷贝赋值函数不能进行值传递

      在调用时首先将实参传递给形参,传递过程需要调用拷贝构造函数,如此会循环爆栈。

     

      

  • 相关阅读:
    Git push 出错 [The remote end hung up unexpectedly]
    [Git高级教程(二)] 远程仓库版本回退方法
    git分支与版本管理、版本回退、冲突解决记录
    上传本地代码到gitHub过程详解
    如何用git将项目代码上传到github
    Git pull 强制覆盖本地文件
    Git忽略规则.gitignore梳理
    composer本地安装文档
    服务器通过微信公众号Token验证测试的代码(Python版)
    转载自lanceyan: 一致性hash和solr千万级数据分布式搜索引擎中的应用
  • 原文地址:https://www.cnblogs.com/wshr007/p/11425089.html
Copyright © 2020-2023  润新知