第一题:虚函数表是在什么时候建立的?
分析过程:虚函数表的建立是取决于定义类的时候是否包含虚函数,如果有类函数/ 方法声明为虚,则改类会建立一张虚表.在网上找到一个牛人用汇编的方法论证虚 表是存放在常量区内(具体论证就省略,否则就扯远了). 继承类会同样建立一张虚表,并将覆盖虚表中父类虚函数的地址. 虚函数指针:虚函数指针是在实例化对象 的时候由构造函数生成的指向虚表的指针.放在对象地址的首位. 下面代码用于显 示虚表及虚表指针. (在Ubuntu下验证通过)
#include <iostream>
#include <string>
using namespace std;
class Base{
public:
virtual void f(){cout<<"Base::f"<<endl;};
virtual void g(){cout<<"Base::g"<<endl;};
};
//typedef void(*Fun)(void);
int main(int argc,char* argv[])
{
typedef void(*Fun)(void);
Base b;
Fun pFun=NULL;
cout<<"v-table address:"<<(int*)(&b)<<endl;
cout<<"first virtual fun address:"<<(int*)*(int*)(&b)<<endl;
pFun=(Fun)*((int*)*(int*)(&b));
pFun();
cin.get();
return 0;
} 所以这个问题的答案:我的理解是虚函数表是在代码编译生成,程序载入内存的时候,放在内存常量区.
虚函数指针是在对象建立时由构造函数生成并保存于对象地 址首部.
第二题:请说明一下静态函数和静态变量
C++中的静态成员变量和静态成员函数。
(1)类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在, 不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有 一个拷贝,为所有此类的对象所共享。需要注意的是:静态数据成员不能在类中初 始化(对于常量静态类变量有待考证,好像可以在类外或main()函数之前定义,初 始化可以放在类中),一般在类外和main()函数之前初始化,缺省时初始化为0。静 态数据成员用来定义类的各个对象所公有的数据,比全局变量更安全。(2)类静态 成员函数属于整个类,不属于某个对象,由该类所有对象共享。静态成员可定义为 inline函数。一般情况下静态成员函数用于访问同一类中的静态数据成员或全局变 量,而不访问非静态成员,如需访问非静态成员,需要将对象作为参数,通过对象 名访问该对象的非静态成员。静态成员函数也可以在类外定义,此时不可以用 static修饰。静态成员函数存在的原因是什么呢?主要是在建立任何对象之前可用 它来访问静态数据成员,普通函数不能实现此功能。
C++静态成员和静态成员函数的使用:静态成员的调用格式:类名::静态数据 成员名、对象名.静态数据成员名、对象指针->静态数据成员、对象引用.静态数据 成员(但类中很少会出现公有数据成员,这段仅仅讨论语法,未考虑实际运用中的 数据封装问题)。静态成员函数的调用格式:类名::静态成员函数名、对象名.静 态成员函数名、对象指针->静态成员函数名、对象引用.静态数据成员。静态成员 函数没有this指针,因它不与特定对象相联系,调用时推荐使用“类名::静态成 员函数名”格式。总结来说,在有对象的情况下,可以用调用普通类成员函数、普 通成员变量的方式调用静态成员函数和静态成员变量。从这里可以看出静态成员变 量和静态成员函数的使用应该是在不建立任何对象的情况下调用它们。
第三题:C++多态是怎么是实现的?
编译时的多态性
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
运行时的多态性
运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C++中,运行时的多态性通过虚成员实现。
编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。
第四题:构造函数可以是虚函数吗?
由前面第一问的时候给出,如果构造函数是虚函数的话,在调用构造函数的时候会使用指针在虚函数表里寻找构造函数. 但是由于虚函数指针是在构造函数执行后才能生成的. 所以会产生悖论.
实际的使用效果是当构造函数为虚函数的时候,编译时会报错,构造函数不能设为虚.
第五题:const 在什么时候使用?
1可以定义const常量
const int Max = 100;
2便于进行类型检查
const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误
void f(const int i) { .........}//对传入的参数进行类型检查,不匹配进行提示
3可以保护被修饰的东西
防止意外的修改,增强程序的健壮性。
void f(const int i) { i=10;//error! }//如果在函数体内修改了i,编译器就会报错
4可以很方便地进行参数的调整和修改
同宏定义一样,可以做到不变则已,一变都变
5为函数重载提供了一个参考
class A
{
......
void f(int i) {......} //一个函数
void f(int i) const {......} //上一个函数的重载
......
};
6
可以节省空间,避免不必要的内存分配
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中
......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
7提高了效率
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
第六题:虚函数与纯虚函数的区别?
1.C++虚函数与纯虚函数用法与区别,.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数 的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class) 。
2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子 类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是 提供一个统一的接口。
5.虚函数的定义形式:virtual fun{};
纯虚函数的定义形式:virtual fun{ } = 0;
虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期
bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一
样。
6. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象
基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
第七题:引用与指针的区别?
1.在任何情况下都不能使用指向空值的引用,一个引用必须总是指向某些对象,引用应被初始化。指针可以为空指针
2. 指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。(但是引用所指的对象可以被修改,可以理解为地址不被修改?)
3.重载某个操作符时,你应该使用引用(返回值使用引用)
以下引用答案:
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
★ 联系
1. 引用在语言内部用指针实现(如何实现?)。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。