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

139 阅读6分钟

重拾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对比?

 constdefine
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个字节))。