C++八股总结——基础语法

一. 函数执行

1. 在main执行之前和之后执行的代码可能是什么?

main函数执行之前,主要就是初始化系统相关资源:

  1. 设置栈指针
  2. 初始化静态static变量和global全局变量,即.data段的内容
  3. 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等等,即.bss段的内容
  4. 全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码
  5. 将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数
    attribute((constructor))

main函数执行之后:

  1. 全局对象的析构函数会在main函数之后执行;
  2. 可以用 atexit 注册一个函数,它会在main 之后执行;
  3. attribute((destructor))

二. 指针、变量、引用和this指针

1. 指针和引用的区别

  1. 指针是一个变量,存储的是一个地址;引用是原变量的别名。
  2. 指针可以为空,引用不能为空且在定义时必须初始化。
  3. 指针在初始化后可以改变指向,而引用在初始化之后不可再改变。
  4. 指针可以有多级,引用只有一级

2. 传递函数参数时,何时使用指针?何时使用引用

使用指针的情况:

  1. 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。

使用引用的情况:

  1. 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小。
  2. 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

3. this指针

1. this指针的用处

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。

2. this指针的使用范围

this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。

三. 栈、堆、队列

1. 栈和队列的区别

  1. 出入方式不同:栈是后进先出、队列是先进先出。
  2. 栈和队列在具体实现的时候操作的位置不同:因为栈是后进先出,它在一端进行操 作;而队列是先进先出,实现的时候在两端进行。
  3. 遍历数据速度不同栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性。队列则不同,它基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多

2. 栈和堆的区别

  1. 申请方式:栈由系统自动分配;堆是自己申请和释放的(容易产生memory leak)。
  2. 空间大小:栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M;堆是不连续的内存区域,堆大小受限于计算机系统中有效的虚拟内存,32bit 系统理论上是4G。
  3. 生长方向:栈向下,向低地址方向增长;堆向上,向高地址方向增长。
  4. 分配效率:栈是其系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存器存放栈地址,栈操作有专门指令,所以栈的效率比较高也比较快。堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。
  5. 碎片问题:栈数据进出一一对应,不会产生碎片。堆频繁的new/delete会造成大量碎片,使程序效率降低。

3. new/delete和malloc/free的区别

  1. malloc和free是标准库函数,支持覆盖; new和delete是运算符,支持重载。
  2. malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
  3. new自动计算要分配的空间大小,malloc需要手工计算。
  4. new是类型安全的,malloc不是。

四. 关键字的区别

1. struct和class的区别

  1. 相同点:
    (1)两者都拥有成员函数、公有和私有部分
    (2)任何可以使用class完成的工作,同样可以使用struct完成
  2. 不同点:
    (1)struct里面的成员默认是公有的,且为公有继承;class默认是私有的,且为私有继承;

2. const和static的区别

static:

  1. 不考虑类的情况:
    (1)隐藏作用:所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用;
    (2)默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区;
    (3)静态变量在函数内定义,始终存在,且只进行一次初始化,具有记忆性,其作用范围与局部变量相同,函数退出后仍然存在,但不能使用。
  2. 考虑类的情况:
    (1)static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。
    (2)static成员函数:不具有this指针,无法访问类对象的非static成员变量和非static成员函数;不能被声明为const、虚函数和volatile;可以被非static成员函数任意访问
    const:
    1.不考虑类的情况:
    (1)const常量在定义时必须初始化,之后无法更改。
    (2)const形参可以接收const和非const类型的实参。
    2.考虑类的情况:
    (1)const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化。
    (2)const成员函数:const对象不可以调用非const成员函数;非const对象都可以调用;不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值;
    (3)const修饰变量是也与static有一样的隐藏作用。只能在该文件中使用,其他文件不可以引用声明使用。 因此在头文件中声明const变量是没问题的,因为即使被多个文件包含,链接性都是内部的,不会出现符号冲突。

五. C和C++的类型安全

1. 什么是类型安全?

类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域。

2. C++的类型安全

C++提供了一些机制保障类型安全:
(1)操作符new返回的指针类型严格与对象匹配。
(2)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。

六. C++中的重载、重写(覆盖)和隐藏的区别

1. 重载

重载是指在同一范围定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目有所不同,不能出现参数个数和类型均相同,仅仅依靠返回值不同来区分的函数。重载和函数成员是否是虚函数无关。

2. 重写(覆盖)

重写指的是在派生类中覆盖基类中的同名函数,重写就是重写函数体,要求基类函数必须是虚函数且:
(1)与基类的虚函数有相同的参数个数
(2)与基类的虚函数有相同的参数类型
(3)与基类的虚函数有相同的返回值类型

3. 隐藏

隐藏指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数,包括以下情况:
(1)两个函数参数相同,但是基类函数不是虚函数。隐藏和重写的区别在于基类函数是否是虚函数。
(2)两个函数参数不同,无论基类函数是不是虚函数,都会被隐藏。和重载的区别在于两个函数不在同一个类中。

七. C++有哪几种构造函数

1. 默认构造函数

以Student类为例,默认构造函数的原型为

Student();   //没有参数

默认构造函数和初始化构造函数在定义类的对象的时候,完成对象的初始化工作。

2. 初始化构造函数

以Student类为例,初始化构造函数的原型为

Student(int num,int age);//有参数

3. 拷贝(复制)构造函数

拷贝构造函数用于拷贝本类的对象,形参是本类对象的引用:

Student  s2(1002,1008);
Student  s3(s2);//将对象s2复制给s3。注意复制和赋值的概念不同。
//下面这种情况叫做赋值,不调用复制构造函数。
Student s4;
s4=s2;//这种情况叫做赋值

调用场景:

  1. 用类的一个实例化对象去初始化另一个对象的时候
  2. 函数的参数是类的对象时(非引用传递)
  3. 函数的返回值是函数体内局部对象的类的对象时 ,此时虽然发生(Named return Value优化)NRV优化,但是由于返回方式是值传递,所以会在返回值的地方调用拷贝构造函数

4. 转换构造函数

转换构造函数用于将其他类型的变量,隐式转换为本类对象:

Student(int r);//形参时其他类型变量,且只有一个形参
//转换构造函数
 Student(int r)
 {
     int num=1004;
     int age= r;
 }

5. 移动构造函数

移动构造函数主要用对象a自身的空间初始化对象b,避免了新的空间的分配,降低了构造的成本。为了防止浅拷贝导致两个指针共同指向一片内存空间,将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间;

八. 浅拷贝和深拷贝的区别

浅拷贝

浅拷贝只是拷贝一个指针,并没有新开辟一个地址,拷贝的指针和原来的指针指向同一块地址,如果原来的指针所指向的资源释放了,那么再释放浅拷贝的指针的资源就会出现错误。

深拷贝

深拷贝不仅拷贝值,还开辟出一块新的空间用来存放新的值,即使原先的对象被析构掉,释放内存了也不会影响到深拷贝得到的值。在自己实现拷贝赋值的时候,如果有指针变量的话是需要自己实现深拷贝的。

区别:

浅拷贝在对象的拷贝创建时存在风险,即被拷贝的对象析构释放资源之后,拷贝对象析构时会再次释放一个已经释放的资源,深拷贝的结果是两个对象之间没有任何关系,各自成员地址不同。

九. 什么是内存泄露,如何检测和避免

内存泄露

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定)内存块,使用完后必须显式释放的内存。应用程序般使用malloc,、realloc、 new等函数从堆中分配到块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

避免内存泄露的几种方式

  1. 计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
  2. 一定要将基类的析构函数声明为虚函数
  3. 对象数组的释放一定要用delete []
  4. 有new就有delete,有malloc就有free,保证它们一定成对出现

十. 面向对象的三大特性

(1)继承

让某种类型对象获得另一个类型对象的属性和方法。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

(2)封装

数据和代码捆绑在一起,避免外界干扰和不确定性访问。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏,例如:将公共的数据或方法使用public修饰,而不希望被访问的数据或方法采用private修饰。

(3)多态

同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)
实现多态有二种方式:覆盖(override),重载(overload)。
覆盖:是指子类重新定义父类的虚函数的做法。
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。例如:基类是一个抽象对象——人,那教师、运动员也是人,而使用这个抽象对象既可以表示教师、也可以表示运动员。

十. C++的四种强制转换

1. static_cast

静态类型转换,对应于C语言中的隐式类型转换场景,可以转换基础数据类型,但是不能转换指针类型。该类型转换会在编译时进行类型检查。
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换时:
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的

2. reinterpret_cast

重新解释类型,可用于转换指针、引用、算术类型、函数指针或者成员指针。但是不能转换基础数据类型。

3. dynamic_cast

动态类型转换,会进行动态类型检查,应用场景是在多态场景中(子类对象传给父类指针或引用),可以动态检查子类的类型。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

4. const_cast

应用场景是去除只读属性,但有一个前提是内存本身必须是可以修改的(内存分配在堆栈上)。

十一. 指针函数和函数指针的区别

1. 指针函数

它的本质是一个函数,不过它的返回值是一个指针。

# include <stdio.h>
# include <stdlib.h>

int * func_sum(int n)
{
    if (n < 0)
    {
        printf("error:n must be > 0\n");
        exit(-1);
    }
    static int sum = 0;    //静态局部变量在整个程序运行期间存在,不加static会错
    int *p = &sum;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return p;
}

int main(void)
{
    int num = 0;
    printf("please input one number:");
    scanf("%d", &num);
    int *p = func_sum(num); 
    printf("sum:%d\n", *p);
    return 0;
}

2. 函数指针

函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。函数的定义是存在于代码段,因此,每个函数在代码段中,也有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针

#include <stdio.h>

int max(int a, int b)
{
    return a > b ? a : b;
}

int main(void)
{
    int (*p)(int, int); //函数指针的定义
    //int (*p)();       //函数指针的另一种定义方式,不过不建议使用
    //int (*p)(int a, int b);   //也可以使用这种方式定义函数指针
    
    p = max;    //函数指针初始化

    int ret = p(10, 15);    //函数指针的调用
    //int ret = (*max)(10,15);
    //int ret = (*p)(10,15);
    //以上两种写法与第一种写法是等价的,不过建议使用第一种方式
    printf("max = %d \n", ret);
    return 0;
}

3. 函数指针在回调函数中的运用

回调函数就是一个通过指针函数调用的函数。其将函数指针作为一个参数,传递给另一个函数。回调函数并不是由实现方直接调用,而是在特定的事件或条件发生时由另外一方来调用的。

#include<stdio.h>
#include<stdlib.h>

//函数功能:实现累加求和
int func_sum(int n)
{
        int sum = 0;
        if (n < 0)
        {
                printf("n must be > 0\n");
                exit(-1);
        }
        for (int i = 0; i < n; i++)
        {
                sum += i;
        }
        return sum;
}

//这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int n, int (*p)(int))
{
        return p(n);
}

int main(void)
{
        int n = 0;
        printf("please input number:");
        scanf("%d", &n);
        printf("the sum from 0 to %d is %d\n", n, callback(n, func_sum));       //此处直接调用回调函数,而不是直接调用func_sum函数
        return 0;
}

十二. C++的内存分区

C++中的内存分区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区和代码区。
在这里插入图片描述
:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
全局/静态存储区全局变量、静态变量、常量、字符串常量被分配到同一块内存中,在以前的C语言中,全局变量和静态变量又分为初始化的和未初始化的,在C++里面没有这个区分了,它们共同占用同一块内存区,在该区定义的变量若没有初始化,则会被自动初始化,例如int型变量自动初始为0。
代码区:存放函数体的二进制代码。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值