admin 管理员组

文章数量: 1087649

C++/MFC 面试题(二、C++理论)

一、面向对象

1.1 什么是面向对象程序设计

面向对象程序设计是一种程序设计泛型,这种泛型的主要特征是 程序 = 对象 + 消息

1.2 什么是对象?什么是类?二者关系?

对象:将描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。
类:具有相同的数据和相同的操作的一组对象的集合,
关系:

  1. 类是对具有相同数据结构和相同操作的一类对象的描述
  2. 类是对象的抽象,对象是类的实例

1.3 什么是消息?消息具有什么性质?

面向对象程序设计中,对象之间的联系称之为对象的交互。
面向对象程序设计中提供的一种对象与对象之间交互的机制叫做消息传递。
性质:

  1. 同一个对象可以接收不同形式的多个消息,做出不同的响应
  2. 相同形式的消息可以传递给不同的对象,所做出的响应可以是不同的
  3. 对消息的响应并不是必须的,对象可以响应消息,也可以不响应。

1.4 什么是方法?实现方式?

方法:对象所能实现的行为/操作。
实现:通过调用相应的函数来实现,C++中通过成员函数来实现

1.5 什么是抽象?什么是封装?

抽象:将有关事物的共性归纳、集中的过程
封装:

  1. 将有关的数据和操作封装在一个对象中,相互独立、互不干扰
  2. 对象中的某些数据与操作对外隐蔽,隐藏细节、留下接口

1.6 什么是继承

继承:对象的一种相互关系,这种关系使得一个类继承另一个类的特征与能力
若类之间具有继承关系,则具有以下特点:

  1. 类间具有共享特征
  2. 类间具有差别或新增部分
  3. 类间具有层次结构

1.7 多态?

多态:不同的对象收到相同的消息时产生多种不同的行为方式
分类:

  1. 编译时的多态性:通过重载来实现
  2. 运行时的多态性:通过虚函数来实现

1.8 C(面向过程)的局限,C++(面向对象)的优势

面向过程的局限:

  1. 效率低下
  2. 难以应付大信息量
  3. 难以适应新环境

面向对象的优势:

  1. 提高可重用性
  2. 程序复杂性可控
  3. 可维护性号
  4. 大型程序设计方便
  5. 扩展了计算机处理信息的范围
  6. 很好的适应新的硬件环境

二、C++基础

2.1 编译、链接、运行

预处理:替换宏、删除注释、处理预处理指令
编译优化:语法分析、语义分析、代码优化、代码生成 得到汇编代码
汇编 / 生成目标文件:把汇编语言翻译成目标机器指令的过程 得到机器指令
链接:它会把所有编译好的单元全部链接为一个整体文件

2.2 const修饰符

在C语言中,使用#define来定义常量 #define PI 3.14
在C++中,用const来定义常量 const float PI = 3.14;
const比#define更具有安全性,所以建议使用const来取代#define
const 与 指针

  1. const char* p = “abcd”;
    指向常量的指针:即,值不可以修改,地址可以修改
p[3]='x';    // error
p="asdhoas"; // OK
  1. char * const p = "abcd";
    常指针:即,地址不可修改,值可以修改
p[3]='x';    // OK
p="asdhoas"; // error
  1. const char * const p = "abcd";
    指向常量的常指针:值和地址均不可以被修改
p[3]='x';    // error
p="asdhoas"; // error

说明:

  1. const int buff = 200;与 const buff =200;有且只有int可以省略,所以二者含义一样
  2. 常量一旦被建立,则不能再被修改
  3. const可以有自己的数据类型
  4. 函数参数传参也可以增加const,保证函数只可传参不可修改

2.3 void型指针

void不能用来声明变量,但是可以用来声明指针,他表示不确定的指针类型,即通用型指针。
可以接受任何类型的指针赋值,但是在使用(输出/传递)的时候需要首先进行显示类型转换。

2.4 内联函数

在函数名前加入inline关键字,函数就变为内联函数。
特点:内联函数是空间换时间的一种方式,一般不超过10行(1~5),且使用频繁,从而可以提高运行速度。

2.5 作用域标识符::

在局部作用域内使用::可以调用同名的全局变量进行访问。

2.6 new和delete

用来替代C语言中的malloc和free来进行动态内存管理,单具备以下的优势

  1. new可以自动计算分配内存的大小
  2. new自动返回正确的指针类型

使用说明:

  1. new申请,delete释放,否则变为死空间
  2. new有一定的失败几率,所有需要检查
  3. new[]用来为数组分配动态空间
  4. delete[] 用来释放动态分配的数组内存空间
  5. new可以申请时直接赋初值;

2.7 引用

变量的引用就是变量的别名。

  1. 引用并不是一种独立的数据类型,它必须和某一种类型的变量相联系
  2. 为引用提供的初始值,可以是一个变量也可以是一个引用
  3. 指针是通过地址间接访问某个变量,而引用是通过别名直接访问某个变量
  4. 引用在初始化后不能再被重新声明为另一个变量的引用;

引用使用注意事项:

  1. 不允许建立void类型的引用
  2. 不能建立引用的数组
  3. 不能建立引用的引用
  4. 可以将引用的地址赋给一个指针
  5. 可以用const对引用加以限定,不允许改变该引用的值
  6. 引用和地址都使用&,但是二者不一样。

2.8 什么是命名空间

命名空间:由程序设计者命名的内存区域
程序设计者可以根据需要指定一些有名字的命名空间,保证不同的命名空间的同名标识符不发生冲突

三、类和对象

3.1 结构体到类

C语言结构体与C++结构体:

  1. C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。所以C的结构体是没有构造函数、析构函数、和this指针的。
  2. C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
  3. C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。

本质为:面向过程和面向对象的区别。

C++中结构体与C++类的区别:

  1. C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。
  2. C++结构体的继承默认是public,而c++类的继承默认是private。

3.2 成员函数

成员函数是函数的一种,它也拥有函数名、返回值、和参数表,用法与普通函数基本一致。

  1. 普通成员函数:类声明中给出成员函数的原型,成员函数的定义写在类的外部。
  2. 内联成员函数:将函数以内联函数的形式进行定义(隐式声明和显示声明)

3.3 构造函数与析构函数

构造函数:用来初始化类的成员。

  1. 名字必须与类名相同
  2. 没有返回值
  3. 可以写在类内,也可以写在类外
  4. 构造函数一般声明为公有成员
  5. 构造函数可以不带参数

成员初始化列表:成员初始化的第二种方式

  1. 可以初始化使用const修饰符的数据
  2. 初始化列表的初始化顺序是按照类中被声明的顺序

析构函数:一种特殊的成员函数

  1. 析构函数与构造函数名字相同,前加~
  2. 无参数、无返回值、不可重载
  3. 对象撤销,自动调用

析构函数调用:

  1. 全局对象,离开此全局对象作用域,则析构函数被调用
  2. 函数体内,函数结束调用时释放
  3. new创建,需要使用delete释放

默认构造函数:

  1. 没有定义构造函数,编译系统自动生成一个默认的构造函数
  2. 默认构造函数,其公有数据成员可以使用初始值列表来进行初始化
  3. 只要自己定义了构造函数,系统不再提供默认构造函数

默认析构函数:
若无自定义析构函数,系统自动生成一个析构函数

3.4 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用

  1. 拷贝构造函数也是一种构造函数
  2. 只有一个参数,并且是同类对象的引用
  3. 每个类都必须有一个拷贝构造函数

拷贝构造函数的调用:

  1. 当用类的一个对象去初始化该类的另一个对象时(代入法和赋值法)
  2. 当函数的形参是类的对象,调用函数进行形参和实参结合时
  3. 当函数返回值是对象,函数执行完成返回调用者时

深拷贝与浅拷贝
浅拷贝:默认的拷贝构造函数所实现的数据成员的逐一赋值(含有指针类型数据吗,会产生错误)
深拷贝:显示自定义一个拷贝构造函数,复制指针类型的数据成员,并且还要为其分配内存空间

3.5 自引用指针 this

C++为成员函数提供了一个名字为this的指针,这个指针称之为自引用指针
每创建一个对象时,系统就把this指针初始化为指向该对象
每调用一个成员函数时,系统就自动把this指针作为一个隐含的参数传给该函数

3.6 静态成员

为了实现一个类的多个对象之间的数据共享(静态数据成员与静态成员函数)

静态数据成员(static 数据类型 数据成员名)使用注意

  1. 与普通数据成员类似,但是需加上static关键字
  2. 需要在main函数之前,类声明之后,类外单独进行成员的初始化
  3. 可以类名:: 静态数据成员名 来进行访问调用
  4. 与静态变量一样,在编译时创建并初始化,在该类的任何对象被创建之前就已经存在。
  5. 在类外,私有的静态数据成员不能被直接访问,必须通过公有的成员函数访问
  6. 有了静态数据成员,则不必使用全局变量来违法面向对象的设计

静态成员函数(static 返回类型 静态成员函数名(参数表);)使用注意

  1. 一般情况,静态成员函数主要用来访问静态数据成员
  2. 私有静态成员函数不能被类外部的函数和对象访问
  3. 可以在创建任何对象之前调用静态成员函数来处理静态数据成员
  4. 编译系统将静态成员函数限定为内部连接,即不会和其他文件中的同名函数冲突
  5. 静态成员函数是类的一部分,不是对象的一部分

3.7 友元

友元是一扇通向私有成员的后门

友元函数:

  1. 既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数
  2. 不是当前类的成员函数,但是可以访问该类的所有成员
  3. 声明需在其函数名前加上friend关键字,可以放在public、private、protected任意部分
  4. 友元函数可以定义在类内部也可以定义在类外部

友元类:

  1. 在另一个类A声明中加入friend 类名B,可以放在public、private、protected任意部分
  2. 则类B所有的成员函数都可以访问类A的所有成员(包括私有成员)
  3. 友元关系是单向的,不具有交换性
  4. 友元关系不具有传递性

3.8 共享数据的保护

常类型的引入就是为了既保证数据共享又防止数据被改动

常引用:通常作为函数的形参

常对象: 在定义对象时必须进行初始化,而且不能被更新,且不能调用普通的成员函数

常对象成员:使用const声明的数据成员,构造函数只能通过成员初始化列表来进行初始化

常成员函数:类型 函数名(参数表)const;

四点说明:

  1. 常成员函数可以访问常数据成员,也可以访问普通数据成员。
  2. 常数据成员可以被常成员函数访问,也可以被普通成员函数访问
  3. 如果将一个对象说明为一个常对象,则通过该对象只能调用它,而不能调用普通的成 员函数的成员函数。
  4. 常成员函数不能更新对象的数据成员,也不能调用该类中的普通成员函数。
函数类型普通数据成员常数据成员常对象的数据成员
普通成员函数可以访问,也可以改变值可以访问,但不可以改变值不允许访问和改变值
常成员函数可以访问,但不可以改变值可以访问,但不可以改变值可以访问,但不可以改变值

四、继承和派生

4.1 概念

继承:类的继承就是新的类从已有类的那里的得到已有的特性
派生:从已有类产生新类的过程就是派生。

4.2 派生类的构造函数和析构函数

调用顺序

  1. 创建时,先调用基类的构造函数,再调用派生类的构造函数
  2. 释放时,先调用派生类的析构函数,再调用基类的析构函数

派生类的构造函数:
基类无参:和普通的构造函数一致
基类有参:子类(参数类型 参数名):父类(参数名)
派生类含有子对象且基类有参:子类(参数类型 参数名):父类(参数名),子对象(参数名)

4.3 多继承

当一个派生类具有多个基类时,称之为多基派生/多继承

默认继承方式是private

class z:public x,private y{
// 派生类新增的成员函数与数据成员
};

构造函数调用顺序:

  1. 先调用基类,再调用子类
  2. 多个基类的顺序取决于继承时声明的顺序

析构函数调用顺序与上述相反

4.4 虚基类

虚基类的引入是为了解决多继承可能产生二义性的问题。

父类Base;含有数据成员a;
中间类:Base1:public base,Base2:public Base
最终类:Base3 :public Base1,public Base2
以上的继承方式,Base3中如果调用a,请问是Base1的还是Base2的?

虚基类的实现
将中间类的继承加上virtual关键字:
父类Base;含有数据成员a;
中间类:Base1:virtual public base,Base2:virtual public Base
最终类:Base3 :public Base1,public Base2

虚基类的初始化

  1. 若虚基类中有函数形参的构造函数且未定义默认形式的构造函数,则所有派生类需要在构造函数的出初始化列表中列出对虚基类构造函数的调用
  2. 若对象中含有从虚基类继承来的成员,则虚基类的成员是由最远的派生类的构造函数通过调用虚基类的构造函数来进行初始化的,并且忽略其他基类
  3. 若同一层次中同时包涵虚基类和其他基类,先调用虚基类的构造函数,在调用其他基类构造函数,最后调用派生类构造函数
  4. 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下
  5. 对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下
  6. 若虚基类由非虚基类派生而来,则仍然先调用基类的构造函数,再调用派生类的构造函数

4.5 赋值兼容规则

  1. 派生类对象可以赋值给基类对象
  2. 派生类对象可以初始化基类对象的引用
  3. 派生类对象的地址可以赋给指向基类对象的指针
  4. 如果函数的形参是基类对象或者基类对象的引用,在调用函数时可以用派生类对象作为实参

五、多态性与虚函数

5.1 多态简述

多态性:不同对象收到相同的消息时,产生不同的动作
从实现上来说,多态分为两种:

  1. 编译时多态,通过函数重载和运算符重载来实现(静态连编:速度快,效率高,缺少灵活性)
  2. 运行时多态,虚函数来实现(动态连编:灵活,但是运行效率低)

5.2 虚函数

virtual 返回类型 函数名(形参表){函数体
}

基类的某个函数被声明为虚函数后,此函数即可在派生类中被重新定义

几点说明:

  1. 多态时,必须公有派生(赋值兼容规则的前提)
  2. 必须首先在基类中定义虚函数
  3. 派生类中virtual关键字可以不写,但是为了区分,建议写
  4. 使用'.'和'::'也可以调用虚函数,但是这不属于运行时多态,只有通过基类指针访问虚函数才是
  5. 一个虚函数无论被公有继承多少次,依旧是虚函数
  6. 虚函数不能是友元,也不能是静态成员,必须是所在类的成员函数
  7. 内联函数不能是虚函数
  8. 构造函数不能是虚函数,但是析构函数通常为虚函数

5.3 虚析构函数

若析构函数不为虚函数
那么在下列写法中,析构函数将只调用Base的,而不会调用Base1 的(其中Base1继承Base)。

Base *p;
p = new Base1();
delete p;

5.4 纯虚函数和抽象类

纯虚函数:在声明虚函数时“初始化”为0的函数。

virtual 返回类型 函数名(形参表)=0;

抽象类:至少具有一个纯虚函数的类。

  1. 抽象类只能作为基类使用,不能实例化对象
  2. 不允许从具体类派生抽象类
  3. 抽象类不能用作函数的参数类型、函数的返回类型或显示转换的类型
  4. 可以声明指向抽象类的指针或引用,
  5. 若派生类只是继承并未定义纯虚函数,则这个类仍是一个抽象类

六、运算符重载

运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同的行为

重载运算符主要是为了实现对类的运算操作

6.1 注意问题

  1. C++不允许定义新的运算符,只能对已有的运算符重载
  2. 除了 . .* :: sizeof ?:这几个运算符,其他都可以重载
  3. 运算符保持原含义人员接受,且符合人们的习惯
  4. 重载不能改变运算符的操作数
  5. 重载不能改变运算符的优先级
  6. 重载不能改变运算符的结合特性
  7. 运算符重载函数的参数至少应有一个是类对象
  8. 双目运算符可以被重载为友元/成员运算符重载函数,但是当类的一个对象和一个整数相加时,需使用友元

6.2 友元运算符重载函数

友元运算符重载函数:把运算符重载定义为某个类的友元函数
格式如下:

class X{friend 返回类型 operater运算符(形参表);
}
返回类型 operater运算符(形参表){函数体
}
  1. 双目友元运算符重载(以复数加法运算为例)
class X {
private:double real; // 复数实部double imag; // 复数虚部
public:X(double x = 0, double y = 0) :real(x), imag(y) { ; }friend X operator+(X x1,X x2);
};
X operator+(X x1, X x2) {return X(x1.real + x2.real, x1.imag + x2.imag);
}

  1. 单目友元运算符重载(重载++)
    前缀方式++i(将++变为每次加0.1)
class X {
private:double x;
public:X(double m) :x(m) { ; }friend X operator++(X &x1);
};
X operator++(X &x1) {x1.x += 0.1;return x1;
}

后缀方式,且每次自加0.2

class X {
private:double x;
public:X(double m) :x(m) { ; }friend X operator++(X &x1,int);
};
X operator++(X &x1,int ) {x1.x += 0.2;return x1;
}

6.3 成员运算符重载函数

格式如下:

class X{返回类型 operater运算符(形参表);
}
返回类型 X::operater运算符(形参表){函数体
}

或者
格式如下:

class X{返回类型 operater运算符(形参表){函数体}
}
  1. 双目运算符
class X {
private:double real; // 复数实部double imag; // 复数虚部
public:X(double x = 0, double y = 0) :real(x), imag(y) { ; }X operator+(X x1){return X(real + x1.real, imag + x1.imag);}
};

  1. 单目运算符

6.4 重载“=”

若未重载“=”,C++自带的拷贝是一种浅拷贝的方式。而浅拷贝可能会导致指针悬挂的问题
解决方式就是,重载“=”运算符函数,开辟一块新的内存来存放数据,而不是只复制指针

#include<iostream>
#include<string.h>
using namespace std;
class String
{private :char *ptr;int size;public :String(int size = 1,char * str = "\0") {ptr = new char[size];strcpy(ptr,str);this->size = size;}char & operator[](int idx) {return ptr[idx];}~String(){delete [] ptr;}
};
int main()
{String str1(10,"gaozhefeng"),str2(10,"Neo");str2 = str1;//容易造成指针悬挂return 0;
}
String operator=(const String & obj) { //避免造成指针悬挂问题size = obj.size;//大小delete [] ptr;//先释放原来的内存ptr = new char[size];//生成新的内存strcpy(ptr,obj.ptr);//拷贝字符串return *this;
}

6.5 重载“[ ]”

class cls
{
private:int ar[6];public:cls(){for (int i = 0; i < 6; i++) { ar[i] = i + 1;}}int& operator[] (int i){return ar[i];}  //重载"[]"操作符
};

6.6 重载“( )”

class cls{public:void operator() () { //重载"()"操作符,"()"内无操作数cout<<"HelloWorld!"<<endl;}void operator() (const char* str) { //重载"()","()"内的操作数是字符串cout << str << endl;}};

6.7 重载“>>”“<<”

ostream& operator <<(ostream&, const 自定义类 &)

第一个输入参数 os 是将要向它写数据的那个流,它是以“引用传递”方式传递的。
第二个输入参数是打算写到那个流里的数据值,不同的 operator <<()重载函数就是因为这个输入参数才相互区别的  
返回类型是 ostream 流的引用。一般来说,在调用 operator <<()重载函数时传递给它的是哪一个流,它返回的就应该是那个流的一个引用。

istream& operator >>(istream& , const 自定义类 &);

6.8 类型转换

6.8.1 标准类型转换
  1. 隐式类型转换
int x=5,y;
y = 3.5+x;

x先从5变为5.0(即从int转换为double)
3.5+5.0 = 8.5
将8.5赋值给y时被转换为8
即y = 8;

  1. 显示类型转换
    类型名(表达式)
double a = 3,b = 2.5;
int(a+b);
6.8.2 类类型与标准类型
  1. 通过转换构造函数
    转换构造函数可以将一个指定类型的数据转换为类的对象
Complex(double r) 
{real=r;imag=0;
}
  1. 通过类型转换函数
    将一个类的对象转换为一个其他类型的数据
operator double( )
{return real;
}

七、模板

模板是实现代码重用机制的一种工具
它可以把实现类型参数化,即把类型定义为参数,从而实现代码重用

7.1 函数模板

格式:

template <typename/class type> 返回类型 函数名(形参表)
{// 函数的主体
}

举例:

template <typename T> T Max(T x, T y) {return x > y ? x : y;
}

7.2 类模板

格式

template <class/typename type> class class-name {类成员声明
}

举例

template <typename T> 
class W {
private:T x, y;
public:W(T m, T n):x(m), y(n) { ; }
};

八、IO/输入输出

C++的输入系统比C更加安全可靠

8.1 流介绍

C++的输入输出是以字节流的形式实现的。
字节流:ASCII字符、二进制形式的数据、图形/图像、音频/视频等
文件和字符串也可以看成有序的字节流,分别称为文件流、字符串流

常用头文件:iostream(标准IO操作)、fstream(文件读写)、strstream(字符串流操作)、iomanip(输入输出的格式化控制)

常用函数

  1. put() 函数
    put(单字符/字符型变量)用于输出一个字符
  2. get() 函数
    该函数有三种形式,分别是char ch = cin.get(), cin.get(char ch), cin.get(array,length)
  3. getline() 函数
    cin.getline(string str,int length) 接收一个长度为length-1的字符串,包括空格,遇到换行结束。
  4. ignore() 函数
    跳过输入流的n个字符。

8.2 输入输出格式控制

8.2.1 用流成员函数
  1. 设置状态标志 setf()
  2. 清除状态标志 unself()
  3. 设置域宽 width()
  4. 设置实数的精度 precision()
  5. 填充字符 fill()
8.2.2 预定义的操作符

8.2.3 用户自定义操作符
输出流自定义操纵符
ostream &操纵符名(ostream &s) {自定义代码return s;
}输入流自定义操纵符
istream &操纵符名(istream &s) {自定义代码return s;
}
#include "stdafx.h"
#include <iostream>
#include <iomanip>
std::ostream& outputNo(std::ostream& s)//编号格式如:0000001
{s<<std::setw(7)<<std::setfill('0')<<std::setiosflags(std::ios::right);return s;
}std::istream& To16(std::istream& s)//要求输入的数为十六进制数
{s>>std::hex;return s;
}int main()
{std::cout<<outputNo<<8<<std::endl;int a;std::cout<<"请输入十六进制的数:";std::cin>>To16>>a;std::cout<<"转化为十进制数:"<<a<<std::endl;return0;
}

8.3 文件的输入输出

  1. 打开文件
ofstream outfile ;  
outfile.open(“file.txt”,ios::out);

或者

ofstream outfile ("file.txt",ios::out);
  1. 读写文件
    <<、put、write、>>、get、getline均可
#include <iostream>
#include <fstream>
using namespace std;
class CStudent
{
public:char szName[20];int age;
};
int main()
{CStudent s;ofstream outFile("students.dat", ios::out | ios::binary);while (cin >> s.szName >> s.age)outFile.write((char*)&s, sizeof(s));outFile.close();return 0;
}
#include <iostream>
#include <fstream>
using namespace std;
class CStudent
{public:char szName[20];int age;
};
int main()
{CStudent s;       ifstream inFile("students.dat",ios::in|ios::binary); //二进制读方式打开if(!inFile) {cout << "error" <<endl;return 0;}while(inFile.read((char *)&s, sizeof(s))) { //一直读到文件结束int readedBytes = inFile.gcount(); //看刚才读了多少字节cout << s.szName << " " << s.age << endl;   }inFile.close();return 0;
}
  1. 关闭文件
out.close

九、异常处理

try、throw、catch

9.1 格式说明

throw 表达式 抛出异常

9.2 检查捕获

检查:

try{语句
}

捕获

catch(异常类型 1){处理
}
catch(异常类型 2){处理
}
catch(异常类型 3){处理
}

9.3 异常类型


9.4 异常的优缺点

C++异常的优点:

1)异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
2)返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误。
3)很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库。
4)很多测试框架都使用异常,这样能更好的使用单元测试等进行白盒的测试。
5)部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误

C++异常的缺点:

1)异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
2)异常会有一些性能的开销。
3)C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。
4)C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
5)异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func()throw();的方式规范化。

9.5 异常安全

构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)。
C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII(智能指针)来解决以上问题。

9.6 异常规范

异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
函数的后面接throw(),表示函数不抛异常。
若无异常接口声明,则此函数可以抛掷任何类型的异常。

本文标签: CMFC 面试题(二C理论)