[笔试]程序员面试宝典

4种强制类型转换

dynamic_cast:(1)子类向父类的转换,必定成功。如A是B的父类,A *pa=dynamic_cast<A*>=new B。(2)父类指针像子类指针转换,则遵循多态原则:如父类指针指向父类则返回NULL;如父类指针指向子类,则返回子类指针,转换成功。

class A
{
public:
    virtual void f(){
        cout<<"A"<<endl;
    }
    void fa(){
        cout<<"fa"<<endl;
    }
};

class B:public A
{
public:
     void f(){
        cout<<"B"<<endl;
    }
    void fb(){
        cout<<"fb"<<endl;
    }
};

void test(A* pa){//并不知道传来的指针时子类还是父类
    B *pb=dynamic_cast<B*>(pa);
    if(pb){//cast success
        pb->fb();//fb不是虚函数,不能使用动态绑定来选择
    }else{
        pa->fa();//fa也不是虚函数
    }
}

static_cast:转换类型,如int转double

reinterpret_cast:以复制二进制数据的方式转换数据类型。如把一个double转成int,由于浮点数的存储方式与int不同,因此会得到一个意料之外的int型。

const_cast:去掉数据的const属性。

整型提升:未满32为的类型如char在算术运算之前,如该数可以用int来表示则提升为int,否则提升为unsigned int,最后才强制转为成目标类型。

K&R C中关于整型提升(integral promotion)的定义为:

 "A character, a short integer, or an integer bit-field, all either signed or not, or an object of enumeration type, may be used in an expression wherever an integer maybe used. If an int can represent all the values of the original type, then the value is converted to int; otherwise the value is converted to unsigned int. This process is called integral promotion."

char a,b,c;
c=a+b;//a,b先提升为int,运算后再转换为char赋给c

 内存对齐原则

(1)各个成员对齐自己的大小:成员的偏移地址能整除自己的大小。(前面成员的总大小能整除下一个元素)

(2)整个结构体的大小要是最大成员大小的整数倍。

struct{
        double a;//偏移地址为0,自身大小为8, 0%8=0,对齐
        short b;//偏移地址为8,自身大小为2, 8%2=0,对齐
        short c;//偏移地址为10,自身大小为2, 10%2=0,对齐
    }A;//总大小为8+2+2=12不是8的倍数,因此最后加4,最终为16

struct{
        short a;//0
        char b[3];//当前偏移地址为2,2%1!=0;因此从偏移3开始
    }A;//3+3=6,  6是3的倍数,因此最终大小为6

 PS:数组成员的对齐是以数组类型对齐,比如int数组是4,而不是以整个数组大小对齐

struct
    {
        int a;
        char b;//偏移4  自身1,对齐
        int c[3];//偏移5,自身4,不对其;插入3字节,偏移8,自身4,对齐
    }A;//总大小20,是4的倍数,因此最终大小为20

 虚继承 virtual public

用于解决多重继承时,防止二义性问题。虚继承使用到虚表,因此大小会加4.

如A类有成员函数f()。B,C继承于A,而D多继承于B,C,则调用D::f()时,编译器会发现有两个A::f()因此产生错误。解决的办法,要不重载D::f(),重新定义D的f(),要不就是用虚继承。

函数指针

普通函数指针:int (*p)(int,int) //返回值,参数写法跟函数一样,函数名用(*p)代替

函数返回函数指针:int ( *(*f)(int,int) ) (int,double)// 函数有参数两个int,返回一个函数指针,该指针有int和double两个参数返回int

存放函数指针的数组:int (*p[10])(int ,int)//存放十个函数指针的数组,该函数有两个int参数返回int。其实跟普通数组一样,在名字后面加[SIZE]就好了。

STL智能指针用法

//方式1 初始化一个智能指针
std::auto_ptr<Object> pObject(new Object)
//方式2 使用专用函数创建空间,并返回智能指针
std::auto_ptr<Object> fun(){
    return new Object;        
}

注意:(1)<>放类名不加*(2)智能指针不能放容器里

静态成员变量与函数

(1)静态成员变量必须在类外初始化,未初始化会编译错误。(2)静态变量不受public等访问控制符限制(3)派生类与父类共享静态成员变量

class A
{
    static int a;
public:
    void print(){
        cout<<A::a<<endl;
    }
};
int A::a;//需要在类外声明一次才能使用。如不赋值,则全局区默认初始化为0.

静态成员函数没有this指针,不能访问非静态变量和非静态函数。

基类的析构函数需声明为virtual

这是由于当使用多态时,如基类析构函数是非virtual的,则撤销基类指针时只会调用基类析构函数,不会调用子类析构函数,可能会造成内存泄露。当基类析构声明为virtual时,使用父类指针类型调用子类类存中的析构函数时,多态发生,调用子类析构函数。调用子类析构函数后会自动调用父类析构函数。因此,不会有类存泄露的危险。

explicit关键字
对于有单个参数的构造函数的类,如新建类时传递一个构造函数参数的类型,可隐式转换成该类。

class A
{
public:
      A(int k){}
};

A a=4;
相当于:
A a=A(4);

explicit只对包含单个参数构造函数作用。消除隐式转换。

class A
{
public:
      explicit A(int k){}
};

void f(A a){}
f(3); //编译出错,不能进行隐式转换

 多态的就近调用原则

class A
{
protected:
    int m_data;
public:
    A(int data=0)
    {
        m_data = data;
    }
    int GetData()
    {
        return doGetData();
    }
    virtual int doGetData()
    {
        return m_data;
    }
};
 
class B: public A
{
protected:
    int m_data;
public:
    B(int data = 1)
    {
        m_data = data;
    }
    int doGetData()
    {
        return m_data;
    }
};
 
class C: public B
{
protected:
    int m_data;
public:
    C(int data=2)
    {
        m_data = data;
    }
};
int main()
{
    C c(10);
    cout << c.GetData() << endl;
    cout << c.A::GetData() << endl;
    cout << c.B::GetData() << endl;
    cout << c.C::GetData() << endl;
    cout << c.doGetData() << endl;
    cout << c.A::doGetData() << endl;
    cout << c.B::doGetData() << endl;
    cout << c.C::doGetData() << endl;
    return 0;
}

原则:(1)如不加限制作用符(A::)调用虚函数,则从该指针的实际类型开始搜索该虚函数,如有重写该需函数则调用,如没有则继续搜索父类,直到找到重写了该虚函数的父类为止。(就近调用原则)  例如:

cout << c.GetData() << endl;

使用C类的对象调用GetData(),但C、B类都没有此函数,故调用A::GetData()。此函数又调用了虚函数doGetData()。根据就近调用原则,搜索C类没有重写doGetData()。再向上搜索B类,重写了doGetData(),因此调用B::doGetData()。

(2)如使用限定作用符调用虚函数,如该作用于下重写了该虚函数,则直接调用。否则根据就近调用原则向上搜索

运算符符重载

如果运算符函数是成员函数,则二元运算符函数只有一个参数,参数代表符号右侧的实例。如一元符号,则成员函数无参数

关于printf和sacnf

printf("%f",5);printf不会把int强制转换成double,而是在栈中用4个字节存放5,但读取栈的时候却根据double读取8个字节(%f被认为是double),因此造成读取到一个极小的浮点数,输出结果为0.000000。scandf同理。

结构体位制

class A
{
public:
    int a:2;
    int b:3;
    int c:3;
};

表示每个变量占的二进制位数,位数不能超过本来int类型的32位。存储方式以4字节对齐。本例子未满4字节算4字节。

关于NULL指针

class A
{
public:
    virtual void f(){
        cout<<"A"<<endl;
    }
    void fa(){
        cout<<"fa"<<endl;
    }
};

((A*)NULL)->fa();//运行成功
((A*)NULL)->f();//运行失败

因为fa没有使用成员变量,不需this指针,也不是虚函数(需要虚表指针),因此尽管是NULL指针也可以运行成功。

大小端与位在内存的摆放与符号数符号位扩展

//0x12345678
//小端
低地址 -> 高地址
0x78 0x56 0x34 0x12

//大端
低地址 -> 高地址
0x12 0x34 0x56 0x78

//二进制数的摆放 0x56=0b01010110
低地址 -> 高地址
01101010 (与习惯写法相反顺序)

小端:低字节放低地址;大端:低字节放高地址。x86cpu使用的是小端系统,mac使用大端。

struct T
{
    int b1:5;
    int :2;
    int b2:2;
};
int main(void)
{
    T t;
    memcpy(&t,"EMC",sizeof(T));
    printf("%d %d
",t.b1,t.b2);
    return 0;
}

ASSIC里'A'是0x41,于是'E'=0x45(0b01000101),'M'=0x4D(0b01001101)。根据小端系统,低字节放低地址。二进制数据的摆放顺序也是按照低位从低字节开始向高字节方向摆放(注意与手写的顺序相反)。因此EM在内存的摆放是

低地址 -> 高地址
1010 0010(E) 1011 0010(M)

因此t的前9位为1010 0010 1,t.b1=0b00101,t.b2=0b10(注意:t.b1占数据前5位,但计算其数值时,应按照人的习惯一定要将其逆序为低位在右,高位在左的形式)。输出时会把它们转化成int

t.b1:0b00101 扩展符号位-> 0b000000....000101(按符号位补到32位长)
t.b2:0b10 扩展符号位-> 0b1111111....1110(符号位是什么就补什么)

补充了符号位后它们是补码,需要转化成原码才知道它们的值:原码->求反->加1=补码,补码进行相反操作得原码。

t.b1:0b000000....000101(正数补码等于原码)=5
t.b2: 
补码:0b1111111....1110
减一: 0b1111111....1101
求反:0b1000000....0010(符号位不反)
可得t.b2=-2
原文地址:https://www.cnblogs.com/iyjhabc/p/3174899.html