现代的C++程序应当尽量使用vector和迭代器,而非内置数组和指针,应当尽量使用string,而不是C风格的基于数组的字符串。
1)简述C++虚函数作⽤及底层实现原理
要点是要答出虚函数表和虚函数表指针的作用。
C++中虚函数使用虚函数表和 虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地 址,假如子类的虚函数重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的 虚函数的地址;虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处), 它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。
2)⼀个对象访问普通成员函数和虚函数哪个更快?
不一定,如果虚函数构成多态的条件,那么普通成员函数快
否则的话是一样的。
3)在什么情况下,析构函数需要是虚函数?
用父类指向new出的子类对象,要定义为虚函数。
最好把父类的析构函数定义为虚函数
A*p1=new A;
A*p1=new B;//若A,B不为虚函数,则都清理A,会造成内存泄漏
delete A;
delete B;
析构函数之所以可以写成虚函数是因为在编译器中析构函数名相同,并不是自己写的。
4)内联函数、构造函数、静态成员函数可以是虚函数吗?
都不可以。
内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开;并且内联函数没有函数地址,不能是虚函数。
构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类 的,因此不存在动态绑定的概念。
虚函数是需要虚表的,在构造函数时对象是不完整的,虚表未完成,所以不能是虚函数。
静态成员函数是以类为单位的函数,与具体对象无关(静态成员函数可以不通过对象调用),虚函数是 与对象动态绑定的,所以不能是虚函数。
5)构造函数中可以调⽤虚函数吗?
不可以,当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。
6)简述C++中虚继承的作⽤及底层实现原理
虚继承用于解决多继承条件下的菱形继承的二义性和数据冗余问题。
底层实现原理与编译器相关,一般通过虚基类指针实现,即各对象中只保存一份父类的对象,多继承时通过虚基类指针引用该公共对象,从而避 免菱形继承中的二义性问题。
7)析构函数中可以调用虚函数吗?
不可以,在析构的时候会首先调用子类的析构函数,析构子类,然后在调用父类的析构函数析构父类,如果在父类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
C++ 多态分类及实现:
i. 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
ii. ⼦类型多态(Subtype Polymorphism,运⾏期):虚函数
iii. 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
iv. 强制多态(Coercion Polymorphism,编译期/运⾏期):基本类型转换、⾃定义类型转换
因此,对于test和&test你应该这样理解,test是函数的首地址,它的类型是void (),&test表示一个指向函数test这个对象的地址,
它的类型是void (*)(),因此test和&test所代表的地址值是一样的,但类型不一样。test是一个函数,&test表达式的值是一个指针!
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
这个功能主要用在下面的情况:
1、C++代码调用C语言代码
2、在C++的头文件中使用
3、在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
扩展:extern “C”的作用如下:
extern "C"包含双重含义,从字面上可以知道,首先,被它修饰的目标是"extern"的;其次,被它修饰的目标代码是"C"的。
核心作用:实现C和C++的混合编程。extern “C”提供一个链接交换指定符号,用于告诉C++这段函数是C函数,extern “C”后面的函数不使用C++的名字修饰,而是使用C。
C++支持函数重载,C不支持函数重载。函数被C++编译后在库中的名字与C语言不同。如void add(int a, int b),该函数在C编译器编译后,库中名字为_add,而C++编译器则会生成add_int_int的名字。故C++提供C链接交换指定符号extern “C”来解决名字匹配问题。
被extern “C”限定的函数或变量是extern类型,extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,此关键字告诉编译器,该声明的函数可以在本模块或其它模块使用。被extern “C”修饰的变量和函数按照C语言方式编译和链接。
与extern对应的关键字是static,被static修饰的全局变量和函数只能在本模块中使用。如果一个函数或变量只能在本模块中使用时,不能用extern “C”修饰
回调函数的优点
1 函数的调用 和 函数的实现 有效的分离
2 类似C++的多态,可扩展
- 当函数模板和普通函数都符合调用时,优先选择普通函数
- 若显示使用函数模板,则使用<> 类型列表
- 如果 函数模板产生更好的匹配 使用函数模板
可以使用一个实参进行调用,不是指构造函数只能有一个形参。
隐式类类型转换容易引起错误,除非你有明确理由使用隐式类类型转换,否则,将可以用一个实参进行调用的构造函数都声明为explicit。
explicit只能用于类内部构造函数的声明。它虽然能避免隐式类型转换带来的问题,但需要用户能够显式创建临时对象(对用户提出了要求)。
模板类 不要轻易写友元函数, 要写的 就写<< 和>> 。
子类转换为父类:向上转型,使用dynamic_cast<type_id>(expression),这种转换相对来说比较安全不会有数据的丢失;
父类转换为子类:向下转型,可以使用强制转换,这种转换时不安全的,会导致数据的丢失,原因是父类的指针或者引用的内存中可能不包含子类的成员的内存。
动态转换和静态转换,dynamic_cast<type_id>(expression),static_cast<type_id>(expression),表示的是将expression转换为type_id类型数据,动态转换一般是子类的指针或者引用转换为父类的指针或者引用,静态转换常用用于数值数据类型的转换。
异常处理
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的
所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈
的解旋(unwinding)。
调用成员函数open的一般形式为:
文件流对象.open(磁盘文件名, 输入输出方式);
指针的操作最容易犯的错误就是:越界、非法指针、指向的数据不是想要的起始位置而是中间位置、指针的值和其指向的值分不清这些。
要使用for循环多维数组,除去内层循环,其他循环控制变量必须声明成引用。
对于 const对象, C++ 编译器不允许进行成员函数的调用,除非成员函数本身也声明为 Const 这一点是非常严格的
将staic成员函数声明为 const 是一个编译错误 const 限定符指示函数不能修改它操作的对象的 内容、但是 static 成员函数独立于类的任何对象存在并且进行操作
C++ 提供 (reinterpret cast 运算符,把某种类型的指针强制转换为其他无关类型
比如 int number;
outfile.write(reinterret<const char > (&numbre));
将int型指针转换成writer写入文件所设定的const char了类型。在编译期间完成
使用__attribute__((visibility(“default”)))可以指定动态链接库中符号的可见性为默认,也就是仅在动态链接库内部可见。此外,还可以使用其他的可见性选项,例如hidden和protected等,来进一步限制符号的可见性