C++函数值的调用 & C++语法

无返回值函数(void函数)

void函数的定义

不带返回值的return语句只能用于返回类型是void的函数。在返回类型为void的函数中,return返回语句不是必需的,隐式的return发生在函数的最后一个语句完成时。

一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,这种return的用法类似于循环结构中的break语句的作用。

void函数的示例

示例一:交换两个整形变量数值的函数

void swap(int & a, int & b)
{
    if(a == b)
    {
        return;    //若两个值相等,无需比较,即让函数停止运行。
    }
    int temp;
    temp = a;
    a = b;
    b = temp;    //如果两个值不相等,则交换这两个值,隐式的return发生在最后一个赋值语句后。
}

示例二:void函数,可以返回另一个返回类型同样是void的函数的调用结果

#include
using namespace std;

void do_swap(int & a, int & b)
{
    int temp = a;
    a = b;
    b = temp;
}

void swap_(int & a, int & b)
{
    if(a == b)
    {
        return; //不带返回值的return语句只能用于返回类型为void的函数
    }
    return do_swap(a, b);   //void函数,可以返回另一个返回类型同样是void的函数的调用结果
}

int main()
{
    int a = 3, b = 4;
    swap_(a, b);
    cout << a << endl;
    cout << b << endl;
    return 0;
}

C++数组作为函数参数

参考:C++中数组作为函数参数

#include
using namespace std;

int GetSize(int data[])
{
    return sizeof(data);
}

int main()
{
   int data1[] = {1, 2, 3, 4, 5};
   int size1 = sizeof(data1);
   cout << size1 << endl;   //20

   int data2[100];
   int size2 = sizeof(data2);
   cout << size2 << endl;   //400

   int *data3 = data1;
   int size3 = sizeof(data3);
   cout << size3 << endl;   //8

   int size4 = GetSize(data1);
   cout << size4 << endl;   //8

   return 0;
}

data1是一个数组,sizeof(data1)是求数组的大小。这个数组包含5个整数,每个整数占4个字节,因此总共是20个字节。

data3声明为指针,对于指针求sizeof,64位机器上指针占8个字节。

C/C++中,当数组作为函数的参数进行传递时,数组自动变为同类型的指针。因此函数GetSize的参数data被声明为数组,但是它会变为指针,故size4的结果仍然为8。

数组的特殊性质

不允许拷贝和赋值,不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。

int a[] = {0, 1, 2};    //含有三个整数的数组
int s2 = a;    //错误,不允许使用一个数组初始化另一个数组
a2 = a;        //错误,不能将一个数组直接赋值给另一个数组

使用数组是通常将将其转化成指针 ,在C++中,指针和数组有非常紧密的联系,使用数组的时候编译器会将其转化成指针。

通常情况下,使用取地址符获取指向某个对象的指针,取地址符也可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能获取指向该元素的指针。

string nums[] = {"one", "two", "three"};    //数组元素是string对象
string *p = &nums[0];    //p指向nums的第一个元素
string *p2 = nums;    //等价于p2 = &nums[0]
数组还有一个特性,在很多使用数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。

数组形参

因为不能拷贝数组,所以无法以值传递的方式使用数组参数。因为数组会被转换成指针,当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。

尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式。

//三个函数等价,每个函数都有一个const int*类型的形参
void print(const int *);
void print(const int []);
void print(const int [10]);    //这里的维度表示我们期望数组含有多少元素,实际不一定

和其他使用数组的代码一样,以数组作为形参的函数必须确保使用数组时不会越界。

对于高维数组而言

可以使用数组名作为实参或者形参,在被调用函数中对形参数组定义时可以指定所有维数的大小,也可以省略第一维度的大小说明

void fun(int a[3][10]);
void fun(int a[][10]);
//但是不能把第二维度或者更高维度的大小省略
void fun(int a[][]);    //不合法
void fun(int a[3][]);    //不合法

因为从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列。如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能只指定第一维度而不指定第二维度。对于数组int p[m][n],编译器寻址,它的地址为:p + i * n + j。倘若省略第二维或者更高维度,编译器将不知道如何正确寻址。

实参数组维度可以大于形参数组:

int a[5][10];
void fun(int a[3][10]);
//此时形参数组只取实参数组的一部分,其余部分不起作用

二维数组形参传值的三种方式

//传数组,第二维度必须声明
void display1(int a[][4], const int row)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
}
//一重指针,传数组指针,第二维度必须声明
void display2(int (*a)[4], const int row)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
}
//使用二维指针传值
void display3(int **a, int row, int col)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            cout << *((int*)a + col * i + j) << endl;
        }
        cout << endl;
    }
}

一个指针占几个字节

一个指针占几个字节?

指针就是地址,地址就是指针。而地址是内存单元的编号。因此一个指针占几个字节,等于是一个地址的内存单元编号有多长。

在计算机中,CPU不能直接与硬盘进行数据交换,CPU只能直接跟内存进行数据交换。而CPU是通过地址总线、数据总线、控制总线三条线与内存进行数据传输与操作。

假如,我们想通过CPU在内存中读取一个数字3,如何操作?

  1. 首先,CPU通过地址总线,在内存中找到数字3的地址
  2. 然后,通过控制总线知道该操作是读or写
  3. 最后,通过数据总线,将数字3传输到CPU中

地址总线的宽度决定了CPU的寻址能力

控制总线决定了CPU对其他控件的控制能力以及控制方式

数据总线的宽度决定了CPU单次数据传输的传送量,也就是数据传输速度

我们平时所说计算机是64位、32位、16位,指的是计算机CPU中通用寄存器一次性处理、传输、暂时存储的信息最大长度。即CPU在单位时间内能一次处理的二进制数的位数。

参考:32位CPU最多支持4G内存是怎么算出来的?

实际上内存是把8个bit排成一组,每1组成为1个单位,大小是1byte(字节),cpu每次只能访问1个byte,而不能单独去访问具体的1bit,1个byte就是内存最小的IO单位。因此“32位操作系统”中的“位”并不是内存中的“bit”的概念,对应到内存中其实是“字节byte”,所以有2^32 = 4GB的说法

地址总线为32,指针占4字节,内存最大为2^32Byte = 2^2GB = 4GB。

变量的存储位置

变量的声明和定义

声明:声明是用来告诉编译器变量的名称和类型,而不分配内存。

定义:定义是为了给变量分配内存,可以为变量赋初值。

即使是extern,如果给变量赋值了,就是定义了。

全局变量或静态变量初始值为0,局部变量初始化为随机值。

在C/C++中,变量的声明和定义区别并不大,定义和声明往往是同时发生的,变量定义时,会根据变量类型分配空间。(定义也是声明;如果声明有初始化式,就被当作定义;除非有extern关键字,否则都是变量的定义)

extern int var; //声明
extern int ble = 10;    //定义
typedef int INT;    //声明
struct Node;    //声明

int value;  //定义
struct Node{
    int left;
    int right;
};
  • 不要把变量定义放入.h文件,这样容易导致重复定义错误。
  • 尽量使用static关键字把变量定义限制于该源文件作用域,除非变量被设计成全局的。
  • 可以在头文件中声明一个变量,在用的时候包含这个头文件即声明了这个变量。 

以下三个实体的定义也可以放到头文件中

  • 值在编译时就已知的const变量的定义可以放到头文件中
  • 类的定义可以放到头文件中
  • inline函数

变量的存储位置

一个由C/C++编译过的程序占用的内存分为以下几个部分

  1. 栈区stack:由编译器自动分配释放,存放函数的参数值,局部变量的值等。我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都是由OS管理。这个栈的操作方式类似于数据结构中的栈
  2. 堆区heap:(在内存开辟另一块存储区域, 通常是指用来存放程序中进程运行时被动态分配的内存段,动态分配:malloc/new, 动态释放:free/delete)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,它与数据结构中的堆不同,分配方式类似于链表
  3. 全局区(静态区)static:(编译器编译时即分配内存)全局变量和静态变量的存储是放在一起的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量又放在相邻的另一块区域中。程序结束后由系统释放。
  4. 文字常量区:常量字符串存储在这里,程序结束后由系统释放。
  5. 程序代码区:存放函数体的二进制代码

堆(heap)和栈(stack)的区别

参考:堆(heap)和栈(stack)的区别

  1. 申请方式
  2. 申请后系统的响应
  3. 申请大小的限制
  4. 申请效率的比较
  5. 堆和栈中的存储内容

根据变量的位置可以分为全局变量和局部变量

根据变量的静态属性可以分为静态变量和非静态变量

根据变量的const属性可以分为const变量和非const变量

针对上述的变量分类,变量的初始化分为以下几种:

全局变量和局部变量的初始化:

全局变量和局部变量的不同主要体现在变量的作用范围上,全局变量的初始化分为两种,非类对象的变量初始化发生在函数的编译阶段,如果我们没有显示的初始化,编译器会默认初始化,类对象的全局变量初始化发生在程序运行阶段的main函数之前。

对于局部变量,不会执行默认初始化,因此在使用局部变量之前必须先进行变量的初始化。

静态变量和非静态变量的初始化:

静态变量根据其位置可以分为三种:全局静态变量定义在函数中的静态变量以及定义在类中的静态变量

静态变量的初始化:编译器要求不同,有的要求必须主动完成初始化,有的编译器会完成默认初始化,但是值不确定。所以,在使用静态变量的时候,我们一般要求必须在定义的同时完成初始化。对于g++编译器,如果静态变量是全局或者函数的局部变量,都会完成默认初始化;但是如果类包含静态数据成员,C++要求这个静态数据成员仅可以在类内部完成声明,然后在类外,且任何函数之外,完成静态成员的初始化,初始化可以赋初始值,也可以不赋值,不赋值会默认初始化。

由于类的静态数据成员所有对象共有,所以类在分配内存的时候并不会主动分配这个静态数据成员的内存,因此要求我们必须主动要求分配内存。必须在类外完成初始化的原因是,这个静态数据成员保存在数据段(全局区),如果在函数内初始化,意味着要求编译器在栈区为这个变量分配内存,因而错误。但是如果类的静态数据成员是const,那么这个变量必须在类内完成初始化。

const对象和非const对象的初始化:

const对象必须在定义的时候进行初始化,引用的本质是一个const指针,因此引用也必须在定义的时候进行初始化。

程序示例:

int a = 0;  //全局初始化区
char *p1;   //全局未初始化区

int main()
{
    int b;  //栈
    char s[] = "abc";   //栈
    char *p2;   //栈
    char *p3 = "123456";    //p3在栈上,"123456\0"在常量区
    static int c = 0;   //全局(静态)初始化区
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20);
    //分配得来的10和20字节的区域在堆区
    strcpy(p1, "123456");   //"123456\0"在常量区
    
    return 0;
}

C++的异常处理机制

异常处理就是处理程序中的错误。(throw、try、catch、finally)

程序运行时常会碰到一些异常情况(对于异常情况,不能发现加以处理,会导致程序崩溃),例如

  • 做除法的时候除数为0
  • 用new运算符动态分配空间时,空间不够导致无法分配
  • 访问数组元素时,下标越界
  • 打开文件读取时,文件不存在

当发生异常,程序无法沿着正常的顺序执行下去的时候,立即结束程序可能并不妥当,我们需要给程序提供另外一条可以安全退出的路径。在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。

  • throw:当问题出现时,程序会抛出一个异常,是通过使用thorw关键字完成。
  • try:标识被激活的特定异常,后面跟着一个或多个catch块
  • catch:通过异常处理程序捕获异常,catch关键字用于捕获异常
  • finally:finally放在catch之后,如果异常没有被catch捕获,使用关键字清理释放资源

C/C++的编译器

主流C/C++编译器简介

MSVC(MSVC是微软Windows平台Visual Studio自带的C/C++编译器)

GCC原名GNU C Compiler、G++,后来逐渐支持更多的语言编译(C++、Fortran、Pascal、Objective-C、Java、Ada、Go等),是一套由GNC工程开发的支持多种编程语言的编译器。GCC是大多数类Unix(如Linux、BSD、Mac OS X等)的标准编译器,而且适用于Windows(借助其他移植项目实现的,比如MingW、Cygwin等)。GCC支持多种计算机体系芯片,如x86、ARM,并已移植到其他多种硬件平台。

  • Cygwin,是一个Windows下Unix-like模拟环境,具体说就是Unix-like接口(OS API,命令行)重定向层,GCC是安装在Cygwin上的编译器
  • MingGW(Minimalist GNU on Windows)是一个Linux/Windows下的可以把软件源码中Unix-like OS API调用通过头文件翻译替换成相应的Windows API调用的编译环境,其目的和Cygwin相同

Clang,是一个由Apple主导编写,基于LLVM(Low Level Virtual Machine的简称)的C/C++/Objective-C编译器,主要用于Mac OS X平台的开发。

编程开发工具选择

集成开发环境

  • Visual Studio 社区版
  • 安装Clion社区版 + MinGW
  • CodeBlocks、Dev C++等

代码编辑器 + MinGW

  • VSCode + MinGW
  • Sublime Text + MinGW

直接使用GCC

适合于有linux使用经验且对vim比较熟悉的人,对于gcc编译,需要了解makefile的编写。

安装编译器在命令行:# apt-get install gcc g++

编写代码:        # vim helloworld.cpp

编译在命令行执行        # g++ code.cpp -o exefile

C++中auto的使用

参考:c++ auto基本用法

总述:

auto的原理就是根据后面的值,来自己推测前面的类型是什么。

auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

注意点:

1.用auto声明的变量必须初始化(auto是根据后面的值来推测这个变量的类型,如果后面没有值,自然会报错)

2.函数和模板参数不能被声明为auto(原因同上)

3.因为auto是一个占位符,并不是一个他自己的类型,因此不能用于类型转换或其他一些操作,如sizeof和typeid

4.定义在一个auto序列的变量必须始终推导成同一类型

auto x1 = 5, x2 = 5.0;    //错误, 必须推导为同一类型
auto x1 = 5, x2 = 6;    //正确
int main()
{
    std::vector ve;
    ve.push_back("wqsd");

    std::vector::iterator it = ve.begin();
    auto it_a = ve.begin();    //auto 可以用来代替长类型

    cout << *it << endl;
    cout << *it_a << endl;
    return 0;
}

 对于数组或者容器(可用迭代器的对象)情况下

参考:C++ for(auto &a:b)、for(auto a:b)、for(const auto &a:b)

b为数组或容器,是被遍历的对象

for(auto &a:b),循环体中修改a,b中对应内容也会修改

for(auto a:b),循环体中修改a,b中内容不受影响

for(const auto &a:b),a不可修改,用于只读取b中内容

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
                        //for(const auto &a:b)  a不可修改,用于只读取b中内容
    for(auto a: arr)    //for(auto a:b) 循环体中修改a, b中内容不受影响
    {
        cout << a << ' ';
        a++;
    }
    cout << endl;

    for(int i = 0; i < 5; i++)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    
    for(auto &a: arr)   //for(auto &a: b) 循环体中修改a, b中对应内容也会修改
    {
        cout << a << ' ';
        a++;
    }
    cout << endl;

    for(int i = 0; i < 5; i++)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;

    return 0;
}

C++的专项联系

1.

a++ 和 ++a 都属于自增运算符,区别在于对变量a的值进行自增的时机不同。a++是先进行取值,再进行自增;++a是先进性自增,再进行取值。++运算符优先级较高

2.

逗号运算符和逗号分隔符, 赋值运算符的优先级别高于逗号运算符,逗号运算符(a, b)会先执行a再执行b。

3.

struct st{
    int *p;
    int i;
    chat a;
};
int sz = sizeof(struct st);

结构体的总大小,即为sizeof的结果,必须是其内部最大成员的“最宽基本类型成员”的整数倍,不足的要补齐。

4.C++中class和struct的区别

【C++】struct和class的区别_忽晴忽雨江湖的博客-CSDN博客_struct和class的区别

C++语法

答案: 超全面的后端开发C/C++面经整理

C++中,内存分成5个区域, 堆, 栈, 自由存储区, 全局/静态存储区、常量存储区

(2)堆和栈的区别

(3)堆块还是栈快

(4)new和delete如何实现的, new和malloc的异同处

new和malloc都会分配空间,但是new还会调用对象的构造函数进行初始化, malloc需要给定空间大小, 而new只需要对象名

(5)既然有malloc和free, C++中为什么还需要new/delete呢

(6)C和C++的区别

  • 封装隐藏了实现细节, 使得代码模块化
  • 继承通过子类继承父类的方法和属性, 实现了代码重用
  • 多态则是“一个接口、多个实现”,通过子类重写父类的虚函数, 实现了接口重用

C++中还有函数重载和引用等概念, C没有

(7)delete和delete[]的区别

(8)C++、Java的联系与区别, 包括语言特性、垃圾回收、应用场景

(9)C++和python的区别

python的基本数据类型只有 数字、布尔值、字符串、列表、元组等等

(10)struct和class区别

(11)define和const的联系区别

(12)在C++中const的用法

(13)C++中的static的用法和意义

  • 空类的大小都是1, C++中空类占用一个字节。当该空白类作为基类时, 该类的大小就优化为0。子类的大小就是子类本身的大小,即所谓的空白基类最优化。
  • 有虚函数的类对象都有一个虚函数表指针__vptr, 其大小4字节。
  • 静态成员存放在静态存储区, 不占用类的大小,普通函数也不占用类大小。

(15)C++的STL实现

内存管理allocator、函数、实现机理、多线程实现等、

算法、容器、迭代器

(16)STL源码中的hash表的实现

unordered_map的底层实现时hashtable, 采用开链法解决哈希冲突。一定情况下, 自动转为红黑树进行组织

(17)解决哈希冲突的方式

  • 线性探查
  • 二次探查
  • 双散列函数法
  • 开链法
  • 建立公共溢出区

(18)STL中的unordered_map 和 map的区别

unordered_map使用哈希实现, 占用内存多,查询速度快,内部无序

map底层采用红黑树实现, 插入删除时间复杂度O(log(n)), 内部有序

(19)STL的vector实现

(20)频繁对vector调用push_back()对性能影响和原因

(21)C++中的vector和list的区别

(22)C++中的重载和重写的区别

重载(overload)是指函数名相同,参数列表不同的函数实现方法,其返回值可以不同。

重写(overwrite)是指函数名相同, 参数列表相同,只有方法体不相同的实现方法, 用于子类继承父类时对父类方法的重写。子类的同名方法屏蔽了父类方法的现象称为隐藏。

(23)C++内存管理

在C++中,内存分成5个区,他们分别是堆、栈、全局/静态存储区和常量存储区和代码区。

(24)面向对象的三大特性

(25)多态的实现

C++ 多态包括编译时多态和运行时多态,编译时多态体现在函数重载和模板上,运行时多态体现在虚函数上。

【26 - 31】虚函数相关

(26)C++虚函数相关(虚函数表, 虚函数指针),虚函数的实现原理

(27)实现编译器处理虚函数表应该如何操作

(28)基类的析构函数一般写成虚函数的原因

如果析构函数不被声明成虚函数, 则编译器实现静态绑定,在删除指向子类的父类指针时, 只会调用父类的析构函数而不调用子类析构函数(造成内存泄漏)

(29)构造函数为什么一般不定义为虚函数

(31)纯虚函数

(32)静态绑定和动态绑定

(33)深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)

深拷贝可以避免重复释放和写冲突。例如使用浅拷贝的对象进行释放后,对原对象的释放会导致内存泄漏或程序崩溃。

(34) 对象复用的了解,零拷贝的了解

(35)C++所有的构造函数

C++中的构造函数主要有三种类型:默认构造函数、重载构造函数和拷贝构造函数

(36)什么情况下会调用拷贝构造函数(三种情况)

(37)结构体内存对齐方式和为什么要进行内存对齐?

因为结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来。

(38)内存泄露的定义,如何检测与避免?

动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。

造成内存泄漏的几种原因:

1)类的构造函数和析构函数中new和delete没有配套

2)在释放对象数组时没有使用delete[],使用了delete

3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露

4)没有正确的清楚嵌套的对象指针

避免方法:

malloc/free要配套
使用智能指针;
将基类的析构函数设为虚函数;
(39)C++的智能指针有哪些

C++中的智能指针有auto_ptr,shared_ptr,weak_ptr和unique_ptr。智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存地址造成内存泄漏。

auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并且将旧指针指向空,但是这种方式在需要访问旧指针的时候,就会出现问题。
unique_ptr是auto_ptr的一个改良版,不能赋值也不能拷贝,保证一个对象同一时间只有一个智能指针。
shared_ptr可以使得一个对象可以有多个智能指针,当这个对象所有的智能指针被销毁时就会自动进行回收。(内部使用计数机制进行维护)
weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。

(40)调试程序的方法

  • 通过设置断点进行调试
  • 打印log进行调试
  • 打印中间结果进行调试

(45)C11特性

自动类型推导auto:auto的自动类型推导用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作

nullptr
:nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0,而nullptr是void*类型的

lambda表达式:它类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法如下:
[函数对象参数](操作符重载函数参数)mutable或exception声明->返回值类型{函数体}

thread类和mutex类

新的智能指针 unique_ptr和shared_ptr

(48)string的底层实现
string继承自basic_string,其实是对char*进行了封装,封装的string包含了char*数组,容量,长度等等属性。

string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2*n),然后将原字符串拷贝过去,并加上新增的内容。

(51)定义和声明的区别
声明是告诉编译器变量的类型和名字,不会为变量分配空间

定义就是对这个变量和函数进行内存分配和初始化。需要分配空间,同一个变量可以被声明多次,但是只能被定义一次

(52)typdef和define区别
#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查

typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名

(53)被free回收的内存是立即返还给操作系统吗?为什么
https://blog.csdn.net/YMY_mine/article/details/81180168

不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。

(58)为什么C++没有实现垃圾回收?

  • 首先,实现一个垃圾回收器会带来额外的空间和时间开销。你需要开辟一定的空间保存指针的引用计数和对他们进行标记mark。然后需要单独开辟一个线程在空闲的时候进行free操作。
  • 垃圾回收会使得C++不适合进行很多底层的操作。

C/C++ 数组作为参数传递 指针 引用

你可能感兴趣的:(C++学习,c++,开发语言,c语言)