C++求职基础汇总

整理一些C++知识, 主要参考这里,文中引用已给出连接。

文章目录

C/C++

封装、继承、多态、重载、覆盖、隐藏
  1. 面向对象的三个特征:

    • 封装:就是把客观事物封装为抽象的类,且类可以把自己的数据和方法只让可信的对象或者类进行操作,对不可信的类进行隐藏;
    • 继承:可以使用现有类的所有功能,并在无需重新编写的情况写对这些功能进行拓展。通过继承产生了基类和派生类。继承是一种从一般到复杂的过程。
      • 基类的数据成员和成员函数在派生类中都有一份拷贝,派生类能够直接访问从基类继承而来的public和protected成员,且只能够通过这两类成员访问从基类继承而来的private成员
    • 多态:同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
  2. 多态

    • 多态是以封装和继承为基础的,分为静态多态与动态多态两种
      • 静态多态:函数重载, 运算符重载属于静态多态, 复用函数名。函数地址早绑定:编译阶段确定函数地址。
      • 动态多态:派生类和虚函数实现运行时多态。
  3. 静态多态(编译器、早绑定)

    • 函数重载
      class A{
          public:
              void do(int a);
              void do(int a, int b);
      }
      
  4. 动态多态

    • 虚函数:用virtual修饰成员函数,使其成为虚函数。
重载和覆盖
  • 重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中
  • 重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写
构造函数和析构函数
  • 定义:
    • 构造函数:没有返回值,不用写void. 函数名与类名相同,构造函数可以有参数,可以发生重载。 创建对象的时候会自动调用(用户未定义时为空实现),且只调用一次。
    • 析构函数:没有返回值,不用写void. 函数名与类名相同,前面加~,构造函数没有参数,不可以发生重载。对象销毁前会自动调用(未定义时为空实现),且只调用一次。
      Class person{
          
          Persion(){
              cout << "person 构造函数的调用" << endl;
          }
      
          ~Person(){
              cout << "person 析构函数的调用" << endl;
          }
      
      };
      
虚函数与纯虚函数
  • 虚函数:virtual 修饰的成员函数。 类里如果声明了虚函数,这个类就是实现的,哪怕是空实现。作用是可以让这个函数在子类里面能被覆盖。
  • 纯虚函数:virtual 返回值类型 函数名(形参)=0;virtual void fun()=0;。纯虚函数只是一个接口,是个函数声明,需要在子类里去实现。
  • 虚函数在子类里可以不用重写,但是纯虚函数必须在子类实现才可以实例化子类。
  • 虚函数的类用于 实作继承 ,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类完成。
  • 带纯虚函数的类叫抽象类, 这种类不能直接生成对象,只能被继承,并且只有重写虚函数之后才可以使用。抽象类被继承后,子类可以继续是抽象类也可以是普通类。
  • 虚基类是虚继承中的基类。
虚函数指针,虚函数表
  • 虚函数指针:在含有虚函数的对象中,指向虚函数表,在运行时确定。
  • 虚函数表:在程序只读数据段,存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。
为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?
  • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
  • C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚函数指针,占用额外的内存。对于不会被继承的类来说,其析构函数若是虚函数,会浪费内存。因此C++默认的析构函数不是虚函数,而只有当需要当做父指针时,设置为虚函数。
静态函数和虚函数的区别
  • 静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数使用虚函数表机制,调用的时候会增加一次内存开销。
你理解的虚函数和多态
  • 多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。

  • 虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

虚函数表具体是怎么样实现运行时多态的

子类若重写父类虚函数,虚函数表中,该函数的地址会被替换。对于存在虚函数的类的对象,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态。

C++中类成员的访问权限

C++通过public,protected,private三个关键字来控制成员变量和成员函数的访问权限,他们分别表示共有的,受保护的,私有的,被称为成员访问限定符。在类的内部(定义类的代码内部),无论成员被声明为public,protected还是private,都是可以互相访问的,没有访问权限的限制。类的外部(定义代码除外),只能通过对象访问成员,并且通过对象只能访问public属性的成员。

struct和class的区别

在C++中,可以用struct和class定义类,都可以继承。区别在于:struct的默认继承权限和默认访问权限是public,而class的默认继承和访问权限是private;
另外,class可以定义模板类形参,如 template <class T, int i>

C++ 类内可以定义引用数据成员吗

可以,但是必须通过成员函数列表初始化。

指针和引用
  1. 定义:
    • 引用:C++是C语言的继承,它可进行过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。引用就是C++对C语言的重要扩充。引用就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名;引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。
    • 指针:指针利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
  2. 区别:
    1. 指针有自己的一块空间,引用只是一个别名;
    2. 指针可以被初始化为NULL(空),而引用必须初始化且必须是一个已有对象的引用;
    3. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用修改都会改变引用所指向的对象;
    4. 可以有const指针,但是没有const引用;
    5. 指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能被改变;
    6. 指针可以有多级指针(**p),而引用只有一级;
    7. 指针和引用使用++运算符的意义不同;int a = 0; int b = &a; int *p = &a; p++; b++;
    8. 如果返回动态内存分配的对象或者内存,必须使用指针,使用引用可能会引起内存泄漏;
    9. 使用sizeof看,指针是一个指针的大小,而引用是被引对象的大小;
const

作用:

  1. 修饰变量,说明该变量不能被改变;
  2. 修饰指针,分为指向常量得到指针与自身是常量的指针;
  3. 修饰引用,指向常量的引用,用于形参类型,即避免了拷贝,又避免了函数对值得修改;
  4. 修饰成员函数,说明该成员函数不能修改成员变量。

const 指针与作用:

  • 指针

    • 指向常量得指针 char* ptr = const int a;
    • 是常量的指针 const chat* ptr = &a
  • 引用

    • 指向常量的引用 fun(const string &str)
    • 引用本身就是常量 const string str; fun(str);
  • 详解:

    1. const char p 限定变量p为只读。这样如p=2这样的赋值操作就是错误的。
    2. const char *p p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如*p=4这样的操作就错了,因为企图改写这个已经被限定为只读属性的对象。
    3. char *const p 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而*p=3这样的操作合法,因为并没有限定其最终对象为只读。
    4. const char *const p 两者皆限定为只读,不能改写。
    5. const char **p p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像**p=3这样的赋值是错误的,而像*p=? p++这样的操作合法。
    6. const char * const *p 限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的。
    7. const char * const * const p 全部限定为只读,都不可以改写。
    char greeting[] = "Hello";
    char* p1 = greeting;                // 指针变量,指向字符数组变量
    const char* p2 = greeting;          // 指针变量,指向字符数组常量(const 后面是 char,说明指向的字符(char)不可改变)
    char* const p3 = greeting;          // 自身是常量的指针,指向字符数组变量(const 后面是 p3,说明 p3 指针自身不可改变)
    const char* const p4 = greeting;    // 自身是常量的指针,指向字符数组常量
static

作用:

  1. 修饰普通变量,放置在静态存储区,main函数之前就分配了空间,有储值按照初值进行初始化,没有使用系统默认值进行初始化。变量在程序运行期间一直存在。局部静态函数结束后不可访问,再次调用函数可用。
  2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。不可extern,不可写在头文件中。
  3. 修饰成员变量,使所有对象共享该变量,且不需要生成对象就可以访问该成员。
  4. 修饰成员函数,使得不需要生成对象就可以访问该函数,但是在static函数内不可访问非静态成员。
volatile

volatile int i = 10;

  • volatile 关键字是一种类型修饰符,用它声明的变量表示可以被某些编译器未知的因素(操作系统、硬件、其他线程等)更改。使用volatile告诉编译器不对这种对象进行优化。
  • volatile关键字声明的变量每次访问必须从内存取值(未被volatile修饰可能由于编译器的优化,直接从CPU寄存器取值)
  • const 可以是volatile(如只读的状态寄存器);
  • 指针可以是volatile。
explicit(显示)关键字

C++中的关键字explicit主要是用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。类构造函数默认情况下声明为隐式的即implicit

  • 隐式转换: 隐式转换即是可以由单个实参来调用的构造函数定义了一个从形参类型到该类类型的隐式转换。编译器在试图编译某一条语句时,如果某一函数的参数类型不匹配,编译器就会尝试进行隐式转换,如果隐式转换后能正确编译,编译器就会继续执行编译过程,否则报错。如:
    int a = 4;
    float b = 5.56;
    b = a; //隐式转换,将int型转为float型     
    显示转换可以理解为强制转换
    
  • explicit关键字只能用于类内部的构造函数声明上,而不能用在类外部的函数定义(函数实现)上,它的作用是不能进行隐式转换;
  • 当构造函数只有一个参数时,会进行自动隐式转换,当构造函数参数个数超过或等于两个时自动取消隐式转换,当只有一个必须输入的参数,其余的为有默认值的参数时使用explicit也起作用;
  • 一般只将有单个参数的构造函数声明为explicit;
  • explicit使用
struct A
{
	A(int) { }
	operator bool() const { return true; }
};

struct B
{
	explicit B(int) {}
	explicit operator bool() const { return true; }
};

void doA(A a) {}

void doB(B b) {}

int main()
{
	A a1(1);		// OK:直接初始化
	A a2 = 1;		// OK:复制初始化
	A a3{ 1 };		// OK:直接列表初始化
	A a4 = { 1 };		// OK:复制列表初始化
	A a5 = (A)1;		// OK:允许 static_cast 的显式转换 
	doA(1);			// OK:允许从 int 到 A 的隐式转换
	if (a1);		// OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
	bool a6(a1);		// OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
	bool a7 = a1;		// OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
	bool a8 = static_cast<bool>(a1);  // OK :static_cast 进行直接初始化

	B b1(1);		// OK:直接初始化
	B b2 = 1;		// 错误:被 explicit 修饰构造函数的对象不可以复制初始化
	B b3{ 1 };		// OK:直接列表初始化
	B b4 = { 1 };		// 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
	B b5 = (B)1;		// OK:允许 static_cast 的显式转换
	doB(1);			// 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
	if (b1);		// OK :被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
	bool b6(b1);		// OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
	bool b7 = b1;		// 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
	bool b8 = static_cast<bool>(b1);  // OK:static_cast 进行直接初始化

	return 0;
}
assert()
  • 断言,而非宏。其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG 来关闭 assert,但是需要在源代码的开头,include <assert.h> 之前。
#define NDEBUG          // 加上这行,则 assert 不可用
#include <assert.h>

assert( p != NULL );    // assert 不可用
attribute

在main函数之前执行:
__attribute() void before_main(){}

sizeof()
  • 对对象表示对象所占空间大小;
  • 对指针表示指针本身所占空间大小。
左值与右值引用
  1. 概念:
    • 左值:能对表达式取地址,或具名对象/变量。一般指表达式结束后依然存在的持久对象;
    • 右值: 不能对表达式取地址,或匿名对象。一般指表达式结束就不在存在的临时变量。
    int i = 1;
    int * p = i;
    int a = &p; //左值引用;
    int &&r = i*2; //右值引用,将i*2的结果绑定到r上;
    
  2. 区别:
    • 左值可以寻址,右值不可以;
    • 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值;
    • 左值可变,右值不可变;
this指针
  1. this指针是隐藏于每一个非静态成员函数中隐含的一个参数,它指向调用该成员函数的那个指针。
  2. 当一个对象调用成员函数时,编译程序会首先将对象的地址赋值给this,然后通过this存取数据成员。
  3. 当一个成员函数被调用时,会自动向他传递一个隐含的参数,即该成员函数所在对象的指针。
  4. this 被隐式的声明为 CLASSNAME *const this, 说明不可修改。
  5. this 不是常规变量,是一个右值,不能取地址(&this)。
  6. 以下场景经常使用this"
    • 为实现对象的链式引用;
    • 为避免对同一对象进行赋值操作;
    • 在实现一些数据结构时,如list。
智能指针

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上避免这个问题,因为智能指针是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源,所以智能指针的原理就是在函数结束时自动释放内存空间。

  • auto_ptr:

  • unique_ptr:采用独占式拥有,意味着一个对象和其相应的资源在同一时间只能被一个pointer拥有。一旦销毁或者编程empty,或开始拥有另外一个对象,先前拥有的那个对象就会被销毁,其资源也被释放。对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

    unique_ptr<string> p3 (new string ("auto"));   //#4
    unique_ptr<string> p4;                       //#5
    p4 = p3;//此时会报错!!
    

    编译器会认为 p4 = p3非法,避免了p3不在指向有效数据的问题。另外,如果unique_ptr是个临时右值,编译不会报错,如果源unique_ptr将存在一段时间,编译器将禁止这么做:

    unique_ptr<string> pu1(new string ("hello world"));
    unique_ptr<string> pu2;
    pu2 = pu1;                                      // #1 not allowed
    unique_ptr<string> pu3;
    pu3 = unique_ptr<string>(new string ("You"));   // #2 allowed
    

    说明:

    • #2处,调用unique_ptr构造函数,在临时对象将所有权p3之后就自动销毁,所以可以使用。
    • #1处,若想实现赋值,可以如下做法:
      unique_ptr<string> ps1, ps2;
      ps1 = demo("hello");
      ps2 = move(ps1);
      
  • shared_ptr:多个智能指针共享一个对象,对象的最末一个拥有销毁对象的责任,并清理与该对象有关的所有资源。其使用计数机制来表明资源被几个指针共享,可以通过成员函数use_count()来查看资源的所有者个数。处理可以通过new创建,还可以通过传入auto_ptr, unique_ptr, weak_ptr来构造。调用release()时当前指针会释放资源的所有权,计数减一。计数为0时,资源被释放。

  • weak_ptr:weak_ptr不控制对象的生命周期,它是对shares_ptr管理的对象,进行该对象内存管理的是那个强引用的shares_ptr。 weak_ptr设计的目的是为配合shared_ptr而引入的一中智能指针,它可以从一个shared_ptr/weak_ptr对象构造,它的构造不会引起计数的增加或者减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放,此时就会导致内存泄漏,为解决此问题,引入weak_ptr。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

    class B;
    class A
    {
    public:
    shared_ptr<B> pb_;
    ~A()
    {
    cout<<"A delete\n";
    }
    };
    
    class B
    {
    public:
    shared_ptr<A> pa_;
    ~B()
    {
    cout<<"B delete\n";
    }
    };
    
    void fun()
    {
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());
    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
    }
    
    int main()
    {
    fun();
    return 0;
    }
    

    可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。

智能指针有没有内存泄漏的情况

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。如上描述。

智能指针的内存泄漏如何解决

为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

野指针

野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针;

隐式类型转换
  • 对于内置类型,低精度给高精度的变量赋值会发生隐式类型转换;
  • 对于只存在单个参数的构造函数的对象构造来说,函数调用可以直接使用该参数输入,编译器会启动调用其构造函数生成临时变量;
隐式 类 类型 转换

调用单个实参的构造函数定义了从形参类型到该类类型的一个隐式转换。
eg:

class Student
{
public: 
    Student() { }
    Student(int age) { }
};

class Teacher
{
public:
    Teacher() { }
    Teacher(int age, string name="unkown") { }  
};

Student foo;
Teacher bar;

foo = 12;    //隐式转换
bar = 40;    //隐式转换

Student类的构造函数仅需要一个实参就能构造对象。Teacher类的构造函数有两个参数,但是由于第二个参数提供了默认值,也可以通过一个参数构造对象。因此,按照C++规则,这两个类的构造函数都实现了隐式转换功能。上述的隐式类型转换同样由编译器完成,编译器通过构造函数构造出一个临时对象,并将其复制为foo和bar。

  • 优点就是语法简洁,省事儿。
  • 缺点就比较多了:
    • 容易隐藏类型不匹配的错误;
    • 代码更难阅读;
    • 接收单个参数的构造方法可能会意外地被用做隐式类型转换;
    不能清晰地定义那种类型在何种那个情况下应该被隐式转换,从而使代码变得晦涩难懂。
类型转换
  • static_cast:
    • 用于非多态类型的转换
    • 不执行运行时类型检查(安全性不如dynamic_case)
    • 通常用于转换数值数据类型(如 float -> int)
    • 可以在类层次结构中移动指针,子类转化为父类安全,父类转为子类不安全。
  • dynamic_cast
    • 用于多态类型的转换;
    • 执行运行时类型检查;
    • 只适用于指针或引用;
    • 对不明确的指针的转换将失败,返回nullptr,但不会引发异常;
    • 可以再整个类层次中移动指针,包括向上转换、向下转换;
  • const_cast
    • 用于删除const、volatile和__unaligned(如将const int ->int
  • reinterpret_cast
    • 用于位的简单重新解释
    • 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)
    • reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。
::范围解析运算符
  • 分类:
    • 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间;
    • 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的;
    • 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的。
      使用:
    int count = 11;         // 全局(::)的 count
    
    class A {
    public:
        static int count;   // 类 A 的 count(A::count)
    };
    int A::count = 21;
    
    void fun()
    {
        int count = 31;     // 初始化局部的 count 为 31
        count = 32;         // 设置局部的 count 的值为 32
    }
    
    int main() {
        ::count = 12;       // 测试 1:设置全局的 count 的值为 12
    
        A::count = 22;      // 测试 2:设置类 A 的 count 为 22
    
        fun();		        // 测试 3
    
        return 0;
    }
    
C++内存管理

在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。

  • 代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本存储区存储程序的机器代码;
  • 数据段:存储程序中已初始化的全局变量和静态变量;
  • BSS段:存储为初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。
  • 堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
  • 映射区:存储动态链接库以及调用mmap函数进行的文件映射;
  • 栈:使用栈空间存储函数的返回地址,参数,局部变量,返回值;
内存分配和管理
  1. malloc、calloc、realloc、alloca
    • malloc: 申请指定字节数的内存。申请到的内存中的初始值不确定。
    • calloc: 为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。
    • realloc: 更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。
    • alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存。但是需要注意的是,alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA),可以用来替代 alloca。
  2. malloc、free
    • 用于分配释放内存;
    • 申请内存,并确认申请成功, 最后释放内存后指针置空(避免野指针):
    char *str = (char*) malloc(100);
    assert(str != nullptr);
    
    free(p);
    p = nullptr;
    
  3. new、delete
    • new / new[]:完成两件事,先底层调用 malloc 分配了内存,然后调用构造函数(创建对象);
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值