重拾C++经典笔试30题(1-10)

简介: 解读:baseClass类包含1个虚函数表指针(4个字节)、1个int型数据成员(4个字节)、1个char型数据(对齐后4个字节)成员,共12个字节。 midClass1同midClass2一致,需要在baseClass类的基础上,多了1个虚函数表指针(4个字节)、1个指向虚基类表的指针(4个字节)、一个整形数据成员(4个字节),合计共12+12 =24个字节。derivedClass 在上述的基础上,包含baseClass(12个字节)、midClass1(新增12个字节)、midClass2(新增12个字节)、derivedClass的1个整形数据成员(4个字节),合计共4

重拾C++经典笔试30题(1-10)

1.     /*----------------统计10进制转化为2进制1的个数-----------------


int total2Cnts(int x)

{

        int count = 0;

        while(x)

        {

             ++count;

             x = x&(x-1);

        }

        cout << endl;

        return count;

}

int main()

{

        int num = 9999;

        for( int i = 0; i < 100; i++)

        {

             cout << i <<"中1的个数为:" <<total2Cnts(i) << endl;

        }

        return 0;

}

------------------------------------------------------------*/



2.     /*---------------浮点转换---------------------------------


int main()

{

        float a = 1.0f;

        cout << (int)a << endl;

        cout << &a << endl;

        cout << (int&)a <<endl;

        cout << boolalpha << ((int)a == (int&)a ) << endl; //false

        float b = 0.0f;

        cout << (int)b << endl;

        cout << &b << endl;

        cout << (int&)b <<endl;

        cout << boolalpha << ((int)b == (int&)b ) << endl; //true

        return 0;

}

---------------------------------------------------------*/


问题1:(int&)a中(int&)的确切含义是什么?(int&)a 等价于*(int*)&a;


问题2:浮点数的内存和整数的存储方式不同;int(&a) 相当于该浮点地址开始的sizeof(int)个字节当成int型的数据输出,这取决于float型数据在内存中的存储方式(这是关键),而不是经过int(&a)显示转换的结果1。




3.     /*---------------位运算--------------------------------


#include<iostream>

usingnamespace std;

intmain()

{

   char a = 'a';

   char b = ~a;

   cout<<sizeof(a)<<endl;                //1

   cout << typeid(a).name() <<endl;        // char

   cout<<sizeof(b)<<endl;                //1

   cout << typeid(a).name() <<endl;        // char

   cout<<sizeof(~a)<<endl;                  //4

   cout << typeid(~a).name() <<endl;        // int

   cout<<sizeof(a&b)<<endl;                //4

   cout << typeid(a&b).name()<< endl;        // int

     

   return 0;

}



---------------------------------------------------------*/



4.     /*------------------输入输出流与printf在多线程中的区别和影响--------------


int g_cnt;

unsignedint _stdcall ThreadProc1(PVOID lpParameter)

{

        g_cnt++;

       printf("subThreadis running! g_cnt = %d\n",g_cnt);

//       cout << "subThread is running!g_cnt = " << g_cnt << endl; //此处和printf打印在多线程操作中的区别?

        return 0;

}

int main()

{

   g_cnt = 0;

        const int nThreadNum = 5;

        HANDLE hThread1[nThreadNum];

        //Caution...!

        for( int i=0; i < nThreadNum; i++)

        {

                  hThread1[i]=(HANDLE)_beginthreadex(NULL,0,ThreadProc1,NULL,0,NULL);

        }      

        WaitForMultipleObjects(nThreadNum,hThread1,true,INFINITE);

        _endthreadex(0);

        return 0;

}



-------------------------------------------------------------------*/


问题:写多线程上述操作最基本实例的时候,发现了用printf能正常逐行打印输出,而用cout<<输出流则出现并行输出的现象。

原因:一个是C库(printf),一个是C++库(cout)。两者的缓冲方式有所不同,而且使用相互独立的缓冲区。printf是传统的行缓冲,cout则是基于字符的缓冲。注意同一个程序中不要混合两种输出方式,有可能导致输出混乱。




5.     位运算


X&Y指取出X与Y的相同位;


X异或Y取出X与Y的不同位;


X右移1等价于X除以2。


X左移1等价于X乘以2.


//取出a,b中较大者。


intmaxFun(int a, int b)

{

        return (((a+b) + abs(a-b))/2);

}

上式简化为:若a>b,((a+b)+(a-b))/2= a; 若a<b,((a+b)-(a-b))/2=b.显然才有了绝对值一说。


//a,b交换操作

voidexchange(int& a, int& b)

{

        a = a^b;

        b = a^b;

        a = a^b;

}

6.     求解结构体偏移量的方法?


#define FIND(STRUC, e)(size_t)&(((STRUC*)0)->e)

structstudent

{

        int a;

        char b[20];

        double c;

};//32

intmain()

{

        cout << FIND(student,a) <<endl; //0

        cout << FIND(student,b) <<endl; //4

        cout << FIND(student,c) <<endl; //24

     

        cout << sizeof(student) <<endl; //32

        return 0;

}

#define FIND(STRUC, e)(size_t)&(((STRUC*)0)->e)


解读:


第一步,((TRUC*)0)将0转化为STRUC*指针所指向的地址;


第二步,&(((STRUC*)0)->e)结构体指针成员e的地址,由于结构体的首地址为0,&(((STRUC*)0)->e)即为e距离结构体成员的偏移量。


第三步,强制类型转换,最终求的是偏移值,最好定义为无符号数据。size_t 等价于 unsigned int


7.     注意以下的表示方法!


#define SECONDS_PER_YEAR 365UL*24UL*3600UL用法值得商榷!


#defineMIN(a,b) ((a)<=(b) ? (a):(b))




8.      const与define对比?



const


define


1. 是否具有类型?


有类型


没有类型


2. 是否进行安全检查?


编译器有安全检查


仅是字符替换


3.        是否可调试?


可调试


不可调试



内联函数与define宏定义对比?



优点


缺点及注意点


Define宏定义


1.提高了运行效率


1.不可调试,无法进行安全检查(类型)


2.可能出现边际效应导致出错。


3.不能操作类的私有数据成员。


内联函数


1提高效率及安全性;


2编译器可以用上下文优化技术继续对结果代码优化。


1每一次内联处都要拷贝代码,使程序总工作量大;


2.短小、 简单函数设为内联(重复调用,无switch、for、while)等语句;


3.不要将构造、析构函数设置为内联函数。




9.     不常用的mutalbe关键字


[MSDN]mutable


C++Specific —>mutable member-variable-declaration;


Thiskeyword can only be applied to non-static and non-const data members of aclass. If a data member is declared mutable, then it is legal to assign a valueto this data member from a const member function.


解读:mutable成员变量的声明,这个关键字只能应用于类的非静态与非const数据成员。如果一个数据成员声明为mutable,那么通过const成员函数给数据成员分派一个值是合法的。


[作用]:加mutable关键字的成员变量,修饰为const的成员函数就可以修改它了。


不加mutable,会报错:l-value specifies const object。


classmutClS

{

public:

        mutClS(int nx):m_x(nx) {}

        ~mutClS(){}

        void increase( int incx) const

        {

                  m_x += incx;

        }

        void decrease(int decx) const

        {

                  m_x -= decx;

        }

        void display()

        {

                  cout << m_x <<endl;

        }

private:

        mutable int m_x; //注意此处!

};

intmain()

{

        mutClS objA(35);

        objA.increase(5);

        objA.display();

        objA.decrease(5);

        objA.display();

        return 0;

}

10.  C++对象的内存布局


先看一下以下程序占用内存的大小,即:sizeof(simpleClass)=?


class simpleClass

{

public:

        simpleClass(){}

        virtual ~simpleClass() {}

        int getValue(){}

        virtual void fool(){}

        static void addCount(){}

        static int nCount;

        int nValue;

        char c;

};



该simpleClass类中含有构造、析构、静态成员函数、虚函数、普通成员函数;静态成员变量、普通成员变量。


分析:


类别


类型


存储类别


占内存情况


数据成员


static int nCount;


全局/静态存储区


不作为对象占据内存的一部分


int nValue;


char c;


非静态数据成员


栈存储区


根据地址对齐,二者占用8字节空间。


成员函数


static void addCount(){}


C++编译器采用普通与C函数类似的方式进行编译,只不过对函数进行了名字修饰(name mangling),用来支持重载;并且在参数列增加了一个this指针,用来表明哪一个对象调用的该函数。


静态和非静态成员函数的多少对对象的大小没有影响。


int getValue(){}


构造、析构函数、拷贝构造


virtual void fool(){}


C++编译器在碰到虚函数的类时,会分配一个指针指向一个函数地址表,叫做“虚函数表”。


占4个字节,虚函数表指针占据的4个字节。



看下面注释的结果值,再分析:


int main()

{

        simpleClass aSimple;

        cout << "Object startaddress:\t" << &aSimple << endl; //0012FF68

        cout << "nValueaddress:\t" << &aSimple.nValue << endl; //0012FF6C

        printf("c address: %x\n",&aSimple.c); //0012FF70

        cout << "size: "<< sizeof(simpleClass) << endl; //12

        return 0;

}



&aSimple= 0012FF68;即虚函数表指针占据的一个对象开始的4个字节。


结论如下:


(1)非静态数据成员是影响对象占据内存的主要因素,随着对象数目的增多,非静态数据成员占据的内存也会相应增加。


(2)所有的对象共享一份静态数据成员,所以静态数据成员占据的内存的数量不会随着对象数目的增加而增加。


(3)静态成员函数和非静态成员函数不会影响对象内存的大小,虽然其实现会占据相应的内存空间,同样也不会随着对象数目的增加而增加。


(4)如果对象中包含虚函数,会增加4个字节的空间,不论有多少个虚函数。


——摘自《C++应用程序性能优化》(第2版)P25


扩展一:如果在simpleClass的基础上增加继承类,如下:继承类所占内存大小是多少?


class derivedClass :public simpleClass

{

public:

        derivedClass(){}

        ~derivedClass(){}

        int nSubValue;

};


答案:16个字节,派生类derivedClass与其基类simpleClass使用的是同一个虚函数表。或者说派生类在构造时,不再创建一个新的虚函数表,而应该是在基类的虚函数表中增加或修改。


扩展二:空类的大小,以及单继承、多继承,虚拟继承后的空类大小。


class A

{

};

class B

{

};

class C : public  A

{

};

class D : virtualpublic  B //4

{

};

class E : public  A, public B

{

};

int main()

{

        cout << sizeof(A) << endl; //1

        cout << sizeof(B) << endl; //1

        cout << sizeof(C) << endl; //1

        cout << sizeof(D) << endl; //4[涉及虚拟继承(虚指针)]

        cout << sizeof(E) << endl; //1

        return 0;

}



扩展三:为了避免出现菱形问题,用使用虚拟继承后的子类大小。示例如下:


class baseClass

{

public:

        virtual void fool(void) {}

        int nValue;

        char c;

};

class midClass1 : virtualpublic baseClass

{

public:

        virtual void setVal(){}

        int nMidValue1;

};

class midClass2 : virtualpublic baseClass

{

public:

        virtual void setVal(){}

        int nMidValue2;

};

class derivedClass :  public midClass1,  public midClass2

{

public:

        virtual void foo2(){}

        int subVal;

};

int main()

{

        cout << sizeof(baseClass) << endl; //12

        cout << sizeof(midClass1) << endl; //24

        cout << sizeof(midClass2) << endl; //24

        cout << sizeof(derivedClass) << endl; //48

        return 0;

}



已经知道的,对于baseClass类的大小,考虑地址对齐为4(c)+4(nvalue)+4(虚拟函数指针)共12个字节;


如果去掉虚拟继承,为如下形式:


class midClass1 : publicbaseClass //仅是增加了nMidValue1,扩展为16字节


class midClass2 : publicbaseClass //仅是增加了nMidValue2,扩展为16字节


classderivedClass :  public midClass1,  public midClass2 //在继承midclass1,midclass2基础上仅是增加了subVal,为16+16+4=36字节。


不理解点:为什么加了虚拟继承,sizeof(midClass1)= 24;sizeof(midClass2)=24;sizeof(derivedClass)48;


主要原因,VisualC++添加了了虚拟基类表指针来实现虚拟继承,类中只要有visual函数就会产生这个vtb 虚函数表和一个vptr虚函数指针,它们都会占内存的。


具体为什么增加了8个字节,希望与大家探讨!


【已解决】主要原因,VisualC++添加了了虚拟基类表指针来实现虚拟继承,因此,空间变大?实际怎么多了8个字节。。

     解读:baseClass类包含1个虚函数表指针(4个字节)、1个int型数据成员(4个字节)、1个char型数据(对齐后4个字节)成员,共12个字节。

     midClass1同midClass2一致,需要在baseClass类的基础上,多了1个虚函数表指针(4个字节)、1个指向虚基类表的指针(4个字节)、一个整形数据成员(4个字节),合计共12+12 =24个字节。

derivedClass 在上述的基础上,包含baseClass(12个字节)、midClass1(新增12个字节)、midClass2(新增12个字节)、derivedClass的1个整形数据成员(4个字节),合计共40个字节。注意derivedClass是继承而非虚拟继承自两个父类,所以没有指向虚基类表的指针。

     扩展,如果将上述继承该为:class derivedClass : virtual public midClass1, virtual public midClass2.上述大小会变为48个字节(多了两个指向虚基类表的指针(每个4个字节))。


相关文章
|
7月前
|
存储 算法 C语言
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
67 5
|
7月前
|
存储 编译器 C语言
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题(下)
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题
54 1
|
7月前
|
Java 编译器 定位技术
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题(中)
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题
50 0
|
7月前
|
安全 程序员 C语言
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题(上)
从C语言到C++_22(继承)多继承与菱形继承+笔试选择题
58 0
|
7月前
|
C语言 C++
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
40 0
|
21天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
31 2
|
27天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
70 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
72 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
82 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
31 4