C与C++整理

目录:

  1. 引用与指针的区别

1. 引用与指针的区别

  1. 是什么
    1. 指针是一个实体,需要分配内存空间,使用指针之前需要进行类型检查,避免出现野指针;引用只是变量的别名,不需要分配空间,引用底层是通过指针实现的;
    2. sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小;
  2. 初始化:
    1. 引用定义的时候必须进行初始化,并且不能够改变,指针在定义的时候不一定要初始化,且指向的空间可变;
    2. 有多级指针,但是没有多级引用,只有一级引用;
  3. 传参与访问:
    1. 引用访问一个变量是直接访问的,而指针访问一个变量是间接访问的;
    2. 作为参数时,传指针的实质是传值,传递的值是指针的地址,形参会在栈中开辟内存空间以存放实参值。
    3. 传引用的实质是传地址,被调函数对形参的处理都是间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。

2. 静态成员变量

  1. 静态成员变量的初始化

    静态成员变量与全局变量一样,存放在全局区域,编译阶段分配内存;C与C++在初始化节点有不同:C语言中,分配好内存之后就会进行初始化,程序结束,变量所处的全局内存被回收。C++中,全局或静态对象首次用到时才会进行构造,程序结束是,按照构造顺序反方向析构。

3. static 关键字的作用与用法

  1. 作用

  2. 隐藏,在编译多个文件时,未加static 的前缀的全局变量和函数都是全局可见的,也就是说,加了之后就只能在自己文件中使用。

    1. 默认初始化的值为0;
    2. 保持变量内容的持久性。
  3. C++中的类成员变量声明static

    1. 函数体内的static 变量的作用域范畴在函数中,该变量的内存只分配一次,因此其值在下次调用时仍然维持上次的值。
    2. 在类中的static成员变量属于整个类拥有,对类的所有对象职业后一份拷贝;
    3. static 成员函数属于整个类所拥有,不属于对象,这个函数不接受this 指针,不能访问非static类成员只能访问类的static成员变量。
    4. static 类对象必须在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化。
    5. static 成员函数不能被virtual 修饰,static 成员函数不属于任何对象或实例。静态成员函数没有this指针,虚函数的实现是每一个对象分配一个vptr 指针,而vptr 是通过this指针调用的,所以不能为virtual;

4. 关键字const

  1. 阻止一个变量改变
  2. 以下是const 指针等生命含义:
1
2
3
4
5
6
const int a;			//a为常整形数,是不可以改变的
int const a; //和上面的一样
const int *a; // a是一个指向长整形的指针,
int *const a; //const
const int * const a;
int const * const a;
  1. 对于类的成员函数,如果指定为const类型,则表明是一个常函数,不能修改类的成员变量,类的常对象智能访问类的常成员函数;
  2. const 类型成员可以通过类型转换符const_cast 将const类型转换为非const类型。

5. mutable用法

  1. 如果需要在const 成员方法中修改一个半成员变量的值,那么需要将这个成员函数修饰为mutable,及用mutable修饰的成员变量不受const 成员方法的限制。

6. 深拷贝与浅拷贝

  1. 浅拷贝:仅仅指向被复制的内存地址,如果地址中对象被改变了,那么浅复制出来的结果也会被相应改变。
  2. 深拷贝:在计算机中开辟一块新的内存地址用于存放复制对象。

7. C++ 模板底层实现方法

编译过程中,编译器会对函数模板进行两次编译: 在声明的地方对模板本身就行编译,在调用的地方对参数替换后的代码进行编译,在调用的地方对参数替换后的代码进行编译。

8. C语言struct与C++ 中的struct的区别

  1. C语言中:struct是用户自定义数据类型(UDT);C++支持成员函数的定义;
  2. C中struct是没有权限设置的,且struct只是一些变量的集合体,可以封装函数却不可以隐藏函数。C++中,struct的成员函数默认访问说明符为public。
  3. 在C语言中必须在结构标志前加上struct,才能做结构类型名。

9. 虚函数可以声明为inline吗?

A: 不可以

  1. 虚函数用于实现运行时候的多态,或者成为晚绑定或是动态绑定。而内联函数用于提高代码效率,在编译期间对调用内联函数的地方代码替换成函数代码。
  2. 虚函数要求在运行时候进行类型确定,而内联函数要求在编译期间完成相关函数的替换。

10. 类成员初始化的两种方式

两种方式:

  1. 通过在函数体内进行赋值初始化,即在构造函数中进行赋值操作;2.初始化函数列表,是纯粹的初始化操作。

    C++的赋值操作会产生临时对象,临时对象的出现会降低程序的效率。

11.成员列表初始化

  1. 编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前。

  2. 必须使用成员初始化的四种情况:
    1. 当初始化一个引用成员时;
    2. 当初始化一个常量成员时;
    3. 当调用一组基类的构造函数,而它应有一组参数时;
    4. 当调用一个成员函数的构造函数,而它有一组参数时;

12. 构造函数为什么不能为虚函数,析构函数为什么要虚函数

  1. 构造函数本身是为了明确初始化对象成员才产生的,virtual function主要是为了在不完全了解细节的情况也能处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,对象没产生的情况下,无法使用virtual函数完成动作。
  2. C++中基类采用virtual虚析构函数是为了放置内存泄漏。具体的,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放,假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时,*就不会触发动态绑定*,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。因此C++基类中的析构函数应该采用virtual析构函数。

13 智能指针

引用地址: https://zsmj2017.tech/post/8043ddb1.html

原始指针的缺点

  1. 不能从声明中推断出是单一对象还是数组,
  2. 不能从声明中推断出使用完毕后是否应当销毁指针对象,也不知道用啥方式销毁(delete 还是专有方式销毁)
  3. 明确要销毁时候,就不知道是要delete 还是delete[];

四个智能指针:auto_ptr, unique_ptr,shared_ptr weak_ptr 。这些指针目的在于帮助管理动态分配对象的生命周期,通过保证在适当的时候以适当的方法销毁这些对象来避免资源泄漏。

在进行独占式资源管理时使用std::unique_ptr : unique的指针大小与原始指针相同,可在以内存吃紧,实时性较高的场合中使用他们。任何一个非空的unique_ptr总是唯一拥有其指向资源。可以与shared_ptr进行相互转化。

在进行共享式资源管理时使用 shared_ptr

类shared_ptr但可空悬的智能指针 weak_ptr: 类似于shared_ptr, 但不参与资源共享?不是很明白。https://zsmj2017.tech/post/bf8718e1.html

14 new与maolloc 的区别

  1. new与malloc都实现了在堆区的内存地址的申请,new/delete是靠malloc与free底层实现的。
  2. new/delete是C++的关键字,而malloc与free是库函数,需要头文件支持。
  3. 使用new操作符申请内存空间分配时无需要指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显示的指出所需内存的尺寸。
  4. new 操作符在内存分配成功之后会返回对象类型的指针,类型严格与对象匹配,无需进行类型转换,因此new是符合安全类型安全的操作符。而malloc内存分配成功之后需要强制类型转换转换成我们想要的类型。
  5. new在内存分配失败时候,会抛出异常,malloc 失败会返回NULL
  6. 释放空间时:new-delete ,malloc-free。

C++多态的实现

1. 静态多态(重载与模板):在编译阶段确定调用函数的类型。

2. 动态多态::(覆盖,虚函数实现)在运行的时候才能够调用是哪个函数,动态绑定,运行基类指针指向派生类的对象,并调用派生类的函数。

虚函数的原理: 虚函数表与虚函数指针。

编译与执行的四个阶段

代码的编译过程分为编译与连接两个过程

img

编译过程又分为编译与汇编。

编译:读取源程序(字符流),对之进行词法和语法分析,将高级语言指令转换为功能等效的汇编代码。源文件的编译的过程分为两部分。

预处理阶段: 进行以下几个方面的处理

  1. 宏定义指令:#define a b
  2. 条件预编译指令: #ifdef, #ifndef, #else, #elif ,# endif 等。
  3. 头文件包含:#include

第二阶段编译:优化阶段

编译程序所要做的工作就是通过词法分析与语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码或是汇编代码。

优化阶段

  1. 对中间代码的优化,删除公共表达式,循环优化(代码外提,强度削弱,变量循环控制),腹泻传播;
  2. 针对代码的生成进行,同机器的硬件结构密切相关,主要考虑的是如何充分利用机器的各个硬件寄存器存放相关变量,以减少对内存的访问次数。

*汇编*: 汇编实际上指把汇编代码发已成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,最终经过这一处理得到相应的目标代码文件。一个目标文件中至少有两个段:

  1. 代码段: 包含程序的指令,可读,可执行,不可写。
  2. 数据段: 主要存放在程序中要用到的全局变量与静态变量,可读可写可执行;

连接: 将有关的文件彼此相连接,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

连接处理可分为两种:

静态链接:函数的代码将其所在的静态连接库中被拷贝到最终的可执行程序中。这样在被执行是代码将被装入进程的虚拟空间中。

动态链接: 函数的代码将被放在成为动态链接库或共享对象的某个目标文件中。

在linux使用的GCC将以上几个过程进行捆绑。

img

预编译、编译、汇编、链接

参考链接:2019年题目:https://blog.csdn.net/qq_38410730/article/details/80951443

程序占用的内存:

每个线程都会有自己的栈,但是堆空间是共用的。

  1. 栈区(stack):由编译器自动分配释放在编译期间就能确定存储大小,存放函数的参数值,局部变量值以及函数的返回值。操作方式和数据结构中的栈类似,按照内存地址由高到低生长,速度快,但自由行差,最大空间不大(VC编译器的默认栈的大小为1M)。
  2. 堆区(heap):存放new或者malloc申请的对象,需要手动释放,如果没有释放,程序结束时候由OS回收;堆由高地址向低地址生长。堆的大小一般没有限制,理论上每个程序最大可达到4G。
  3. 静态(全局)区(static):全局变量和静态变量存储的区域,程序结束后由系统释放。
  4. 常量存储区:存放的是常量,不可以修改,全局可见;
  5. 代码存储区:存储二进制代码
  6. 关于堆、栈对比
存储内容 局部变量、形参、函数返回值 new、malloc申请的地址
作用域 函数作用域、语句块作用域 函数作用域
编译期间是否确定大小
大小 1M 4GB
内存分配方式 地址由高到低的减少 地址由低到高
内存是否可以修改

img