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++中拷贝构造函数和拷贝赋值函数不能进行值传递

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

 

  

原文地址:https://www.cnblogs.com/wshr007/p/11425089.html