C++八股文(一)

C++八股文(一)

1.1 C++语言的特点

  1. C++在C的基础上引入了面向对象机制,同时也兼容C语言;
  2. C++三大特性:封装、继承、多态;
  3. C++程序结构清晰、易于扩充、程序可读性好;
  4. C++代码质量高,运行效率高、仅比汇编语言慢10%~20%;
  5. C++更安全,增加const常量、引用、四类cast转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)、智能指针、try-catch等等;
  6. C++可复用性高,C++引入了模板的概念,有专门的模板库(STL);
  7. C++是不断发展的语言,C++11中新引入了nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。

1.2 C++和C语言的区别

  1. C语言是C++的子集,C++可以很好兼容C语言。但是C++又有很多新特性,如引用、智能指针、auto变量等;
  2. C++是面对对象的编程语言;C语言是面对过程的编程语言;
  3. C语言有一些不安全的语言特性,如指针使用的潜在危险、强制转换的不确定性、内存泄露等。而C++对此增加了不少新特性来改善安全性,如const常量、引用、cast转换、智能指针、try—catch等等;
  4. C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL。C++的STL库相对于C语言的函数库更灵活、更通用

1.3 C++中 struct 和 class 的区别

  1. struct 一般用于描述一个数据结构集合,而 class 是对一个对象数据的封装;
  2. struct 中默认的访问控制权限是 public 的,而 class 中默认的访问控制权限是 private 的;
  3. 在继承关系中,struct 默认是公有继承,而 class 是私有继承;
  4. class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数。

1.4 include头文件的顺序以及双引号""和尖括号<>的区别

  1. 区别:
    1. 尖括号<>的头文件是系统文件,双引号""的头文件是自定义文件
    2. 编译器预处理阶段查找头文件的路径不一样;
  2. 查找路径;
    1. 使用尖括号<>的头文件的查找路径:编译器设置的头文件路径–>系统变量;
    2. 使用双引号""的头文件的查找路径:当前头文件目录–>编译器设置的头文件路径–>系统变量。

1.5 C++结构体和C结构体的区别

  1. C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数;
  2. C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种;
  3. C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而 C++ 中可以省略 struct 关键字直接使用;
  4. C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。

1.6 导入C函数的关键字是什么,C++编译时和C有什么不同?

关键字:在C++中,导入C函数的关键字是extern,表达形式为extern “C”, extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

编译区别:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名

1.7 简述C++从代码到可执行二进制文件的过程

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

  1. 预编译:这个过程主要的处理操作如下:

    (1) 将所有的#define删除,并且展开所有的宏定义

    (2) 处理所有的条件预编译指令,如#if、#ifdef

    (3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。

    (4) 过滤所有的注释

    (5) 添加行号和文件名标识

  2. 编译:这个过程主要的处理操作如下:

    (1) 词法分析:将源代码的字符序列分割成一系列的记号。

    (2) 语法分析:对记号进行语法分析,产生语法树。

    (3) 语义分析:判断表达式是否有意义。

    (4) 代码优化:

    (5) 目标代码生成:生成汇编代码

    (6) 目标代码优化

  3. 汇编:这个过程主要是将汇编代码转变成机器可以执行的指令。

  4. 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

    ​ 链接分为静态链接和动态链接。

    ​ (1) 静态链接,是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。

    ​ (2) 而动态链接,是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。

1.8 static关键字的作用

  1. 定义全局静态变量和局部静态变量:在变量前面加上static关键字。static的变量默认初始化为0。(static变量)初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样;
  2. 定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;
  3. 在变量类型前加上static关键字,变量即被定义为静态变量。静态变量只能在本源文件中使用
  4. 在c++中,static关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间,static修饰的变量要在类外初始化。
  5. 在c++中,static关键字可以用于定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static即可。如静态成员函数也是类的一部分,而不是对象的一部分。所有这些对象的静态数据成员都共享这一块静态存储空间,只能访问类的static成员变量,static修饰的变量要在类外初始化。

1.9 数组和指针的区别

概念

(1)数组:数组是用于储存多个相同类型数据的集合。数组名是首元素的地址。

(2)指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。指针名指向了内存的首地址。

区别

  1. 赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝;
  2. 存储方式
    1. 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,数组的存储空间,不是在静态区就是在栈上。
    2. 指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。
  3. …感觉这道题不是很好

1.10 什么是函数指针,如何定义函数指针,有什么使用场景

概念:函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。

定义形式:

int func(int a); 
int (*f)(int a); 
f = &func;

使用场景: 回调(callback)。我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。

//以库函数qsort排序函数为例,它的原型如下:
void qsort(void *base,//void*类型,代表原始数组
           size_t nmemb, //第二个是size_t类型,代表数据数量
           size_t size, //第三个是size_t类型,代表单个数据占用空间大小
           int(*compar)(const void *,const void *)//第四个参数是函数指针
          );
//第四个参数告诉qsort,应该使用哪个函数来比较元素,即只要我们告诉qsort比较大小的规则,它就可以帮我们对任意数据类型的数组进行排序。在库函数qsort调用我们自定义的比较函数,这就是回调的应用。

//示例
int num[100];
int cmp_int(const void* _a , const void* _b){//参数格式固定
    int* a = (int*)_a;    //强制类型转换
    int* b = (int*)_b;
    return *a - *b;  
}
qsort(num,100,sizeof(num[0]),cmp_int); //回调

1.11 静态变量什么时候初始化

对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。

而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。

1.12 nullptr调用成员函数可以吗?为什么?

可以。因为在编译时对象就绑定了函数地址,和指针空不空没关系。

//给出实例
class animal{
public:
    void sleep(){ cout << "animal sleep" << endl; }
    void breathe(){ cout << "animal breathe haha" << endl; }
};
class fish :public animal{
public:
    void breathe(){ cout << "fish bubble" << endl; }
};
int main(){
    animal *pAn=nullptr;
    pAn->breathe();   // 输出:animal breathe haha
    fish *pFish = nullptr;
    pFish->breathe(); // 输出:fish bubble
    return 0;
}  
// 原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。pAn->breathe();编译的时候,函数的地址就和指针pAn绑定了;调用breath(*this), this就等于pAn。由于函数中没有需要解引用this的地方,所以函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。

1.13 什么是野指针,怎么产生的,如何避免?

  1. **概念:**野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的);

  2. 产生原因:释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。

  3. 避免办法:

    (1)初始化置NULL

    (2)申请内存后判空

    (3)指针释放后置NULL

    (4)使用智能指针

1.14 静态局部变量,全局变量,局部变量的特点,以及使用场景

  1. 首先从作用域考虑:C++里作用域可分为6种:全局,局部,类,语句,命名空间和文件作用域。

    全局变量:全局作用域,可以通过extern作用于其他非定义的源文件。

    静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。

    局部变量:局部作用域,比如函数的参数,函数内的局部变量等等。

    静态局部变量 :局部作用域,只被初始化一次,直到程序结束。

  2. 从所在空间考虑:除了局部变量在栈上外,其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。

  3. 生命周期: 局部变量在栈上,出了作用域就回收内存;而全局变量、静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。

  4. 使用场景:从它们各自特点就可以看出各自的应用场景,不再赘述。

1.15 内联函数和宏函数的区别

1.16 new和malloc的区别,各自底层实现原理

  1. new是操作符,而malloc是函数。
  2. new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
  3. malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。
  4. new可以被重载;malloc不行
  5. new分配内存更直接和安全。
  6. new发生错误抛出异常,malloc返回null

1.17 const和define的区别

1.18 C++中函数指针和指针函数的区别

  1. 定义不同
    指针函数本质是一个函数,其返回值为指针。
    函数指针本质是一个指针,其指向一个函数。

  2. 写法不同

    指针函数:int *fun(int x,int y);
    函数指针:int (*fun)(int x,int y)
    
  3. 用法不同

    //指针函数示例
    typedef struct _Data{
        int a;
        int b;
    }Data;
    //指针函数
    Data* f(int a,int b){
        Data * data = new Data;
        //...
        return data;
    }
    int main(){
        //调用指针函数
        Data * myData = f(4,5);
        //Data * myData = static_cast(f(4,5));
       //...
    }
    
    //函数指针示例
    int add(int x,int y){
        return x+y;
    }
    //函数指针
    int (*fun)(int x,int y);
    //赋值
    fun = add;
    //调用
    cout << "(*fun)(1,2) = " << (*fun)(1,2) ;
    //输出结果
    //(*fun)(1,2) =  3
    

1.19 常量指针和指针常量

1. const int a;     //指的是a是一个常量,不允许修改。
2. const int *a;    //a指针所指向的内存里的值不变,即(*a)不变
3. int const *a;    //同const int *a;
4. int *const a;    //a指针所指向的内存地址不变,即a不变
5. const int *const a;   //都不变,即(*a)不变,a也不变

1.20 使用指针需要注意什么?

  1. 定义指针时,先初始化为NULL。
  2. 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
  3. 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
  4. 避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作
  5. 动态内存的申请与释放必须配对,防止内存泄漏
  6. 用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”
1.21 内联函数和函数的区别
  1. 内联函数比普通函数多了关键字inline
  2. 内联函数避免了函数调用的开销;普通函数有调用的开销;
  3. 普通函数在被调用的时候,需要寻址(函数入口地址);内联函数不需要寻址。
  4. 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句(内联函数内不允许用循环语句和开关语句。);普通函数没有这个要求。

1.22 简述C++有几种传值方式,之间的区别是什么?

值传递、引用传递、指针传递

  1. 值传递:形参即使在函数体内值发生变化,也不会影响实参的值;
  2. 引用传递:形参在函数体内值发生变化,会影响实参的值;
  3. 指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;

1.23 四种cast类型转换

​ 作用:克服c中强制类型转化带来的风险,C++引入四种更加安全的强制类型转换运算符(明确转换的目 的,偏于程序的维护和分析)

  1. const_cast:
// 1.去除const属性,将只读变为只读写
// 2.针对常量指针、常量引用和常量对象
const char *p;
char *p1 = const_cast<char*>(p);

​ 2. static_cast

// 1.内置数据类型之间的转换,int转double,char转int
// 2.基类指针与派生类之间的转换,只能转换有继承或派生关系的类。用于类层次结构之间基类和派生类指针和引用之间的转换,进行向上转型是安全的,但是进行向下转型是不安全的,但是是可以转换的;向上转型:我们知道基类的引用和指针都可以指向派生类的对象,那么将派生类的指针或者引用强转为基类的指针或者引用,那么这就是向上转型,也就是向父类转;向下转型:向下转型就和向上转型相反,它是将父类的指针或者引用,强制转换为子类的指针或者引用
// 3.把void类型指针转换为目标类型的指针
// 4.任何类型的表达式转化为void类型

// 整形转浮点型
int a = 10;
double b = static_cast<double>a;
 
//基类指针转派生类
class A{}; class B : public A{};
 
A *pA = new A;
 
B *pB = static_cast<B*>(pA);
  1. reinterpret_cast

    • 可以将一个类型的指针转换为其它任意类型的指针,也可以用在指针和整形数据之间的转换它是很危险的,如果我们没有使用它的充分理由,那么就不要使用它

    • 为运算对象的位模式提供较低层次上的重新解释

    • 用于底层的强制转换,依赖于机器,一般使用较少

  2. dynamic_cast

    • dynamic_cast是运行时处理的,运行时进行类型检查,其他三种是编译时处理的
    • 不能用于内置数据类型之间的转换
    • dynamic_cast在进行上行转换时和static_cast效果是一样的,但是进行下行转换时会进行类型检查,比static_cast更加安全,下行转换是否成功取决于转换对象的实际类型与目标类型是否相同
    • 要求基类必须具有虚函数,否则编译不通过
    • 若转换成功,返回的是指向目标的指针或引用,不成功返回NULL

1.24 C++继承

  1. 公有继承public:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
  2. 私有继承private:私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
  3. 保护继承protect:保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

你可能感兴趣的:(C++基础,c++,C++八股文)