C++笔试题(1),持续更新中……

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

31.头文件的作用是什么?
1、通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取出相应的代码。
2、头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
32. 解释 static 关键字的用途,越多越好

static关键字至少有下列作用:

1)函数体内static 变量的作用范围为该函数体,不同于auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

2)在模块内的static 全局变量可以被模块内所有函数访问,但不能被模块外其它函数访问;

3)在模块内的static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

4)在类中的static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

5)在类中的static 成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

32. 解释 const 关键字的用途,越多越好

const关键字至少有下列作用:

1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const

3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为左值

33.在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”?

c++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为:void fee(int x,inty); 该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字, C++提供了C连接交换指定符号extern “C”来解决名字匹配的问题.
33.宏和 inline 的区别

使用宏和内联函数都可以节省在函数调用方面的时间和空间开销。二者都是为了提高效率,但是却有着显著的区别:

(1)、在使用时,宏只做简单的预处理器符号表(字符串)中的简单替换。而内联函数可以进行参数类型检查,且具有返回值(也能被强制转换为可转换的合适类型)。
(2)、内联函数首先是函数,函数的许多性质都适用于内联函数(如内联函数可以重载)。
(3)、内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了(无法将this指针放在合适位置)。

可以用内联函数完全替代宏。但是在使用内联函数时也要注意:作为内联函数,函数体必须十分简单,不能包含循环,条件,选择等复杂结构,否则不能作为内联函数。实际上,编译器的优化系统会自动将

34.内存思考题

void GetMemory(char *p){

 p = (char *)malloc(100);

}
void Test(void)
{
   char *str = NULL;

   GetMemory(str);

   strcpy(str,”hello world”);

   printf(str);
}

请问Test函数会有什么样的结果?函数内的变量是独立于main的,对其改变不会影响main的变量。程序会崩溃,因为GetMemory并不能传递动态内存,Test函数中的str一直是NULL。Strycpy(str,”hello world”);将使程序崩溃。

char *GetMemory(void)
{
        char p[] = “hello world”;
        return p;
}
void Test(void)
{
        char *str = NULL;
        str = GetMemory();
        printf(str);
}
请问Test函数会有什么样的结果?乱码。因为GetMemory返回的是指向”栈内存”的指针,该指针的地址不是NULL,但其原现的内容已经被清除,新内容不知。
void GetMemory(char **p,int num)
{
        *p = (char *)malloc(num);
}
void Test(void)
{
        char *str = NULL;
        GetMemory(&str,100);
        strcpy(str,”hello”);
        printf(str);
}

请问运行Test函数会有什么样的结果?能够输出hello,内存泄露。

void Test(void)
{
 char *str = (char *) malloc(100);
 strcpy(str,”hello”);
 free(str);
 if(str != NULL)
 {
    strcpy((str,”world”);
    printf(str);
 }
}
请问运行Test函数会有什么样的结果?篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str成为野指针,if(str!=NULL)语句不起作用。

重拾C++经典笔试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. coutprintf在多线程中的区别和影响

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中较大者。

int maxFun(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.      constdefine对比?

 

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

classmut ClS  
{  
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个字节的空间,不论有多少个虚函数。

扩展一:如果在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个字节))。

重拾C++经典笔试提(11-20

11.     C++对象模型基本概念之程序使用内存区

计算机程序主要由代码+数据组成,两部分是影响一个程序所需内存的重要因素。

数据区存储分类

存储内容

全局/静态数据区

全局变量及静态变量(全局静态变量、局部静态变量)

常量数据区

存储程序中的常量字符串等

存储自动变量或局部变量,以及传递的函数参数等

用户控制的存储区,存储动态产生的数据

代码区

程序中代码

12.     不被重视的sizeof()大小问题。

(1)情况一

int a = 8;

         cout << sizeof(a=6) <<endl; //a=6是不被编译的,只是转换为a的类型。

        cout<< a << endl; //8

注意:sizeof(a=6)在编译过程中是不被翻译的,而是被替代类型。

(2)情况二,求函数大小等价于其对应返回值的大小。  

int fun1()  
{  
         return 0;  
}  
void fun2()  
{  
   
}  
char fun3()  
{  
         return 'c';  
}  
double fun4()  
{  
         return 0.0;  
}  
         cout << sizeof(fun1()) << endl; //4  
        cout << sizeof(fun2()) << endl; //error C2070:illegal sizeof operand**  
         cout << sizeof(fun3()) << endl; //1  
        cout<< sizeof(fun4()) << endl; //8 

(3)情况三,求数组大小。       

         char c[2][3] = {"a",""};

         cout << sizeof(c) << endl; //2*3*1

(4)情况四,括号、给数组大小赋值(主要原因,编译的时候已经计算过sizeof大小了)。

int na = 35;  
      //等价于sizeof(na), 如果是变量名,可以不加括号。  
      cout << sizeof na << endl;  
      cout << sizeof(int)<< endl;  
  
      int nArray[25] = {0};  
      //可以通过sizeof( )定义数组的大小,等价于new int[100].  
      int *pArray = new int[sizeof(nArray)];   

13.     深究结构体地址对齐的原则及应用实例

struct simpleA  
{  
         float f; //0  
         char p; //4  
         int adf[3]; //5—>8[按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了]  
};//8+12-->20 

这里有三点很重要:

(1)每个成员分别按自己的方式对齐,并能最小化长度

(2).复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度;

(3).对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

补充一下,对于数组,比如:char a[3];这种,它的对齐方式和分别写3个char是一样的。也就是说它还是按1个字节对齐。

如果写: typedef char Array3[3];Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度。不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....的一个。

 总结如下:

数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

联合 :按其包含的长度最大的数据类型对齐。

结构体: 结构体中每个数据类型都要对齐。

14.     注意此处错误的原因

void swap1(int* p , int* q)  
{  
/*      int a = 0; 
//       int* temp;  //单纯的temp会出现temp' used without having beeninitialized 
         int* temp = &a; 
  
         *temp = *p; 
         *p = *q; 
         *q = *temp; */  
   
         //等价于下面  
         int temp = *p;  
         *p = *q;  
         *q = temp;  
}

给随机地址赋值,函数结束的时候不回收,会造成内存泄露。

15.     地址相减计算

int main()  
{  
         int a[3];  
         a[0] = 0;  
         a[1] = 1;  
         a[2] = 2;  
   
         int *p, *q;  
         p = a;  
         q = &a[2];  
   
         cout << p << endl;  
         cout << q << endl;  
          
         //q-p等价于[(q的地址值-p的地址值)/sizeof(int)].  
         cout << q-p << endl; //2  
         cout << a[q-p] << endl;//a[2] = 2;  
   
         return 0;  
}

16.     为什么是1

classA  
{  
public:  
         A() { m_a = 1, m_b =2; }  
         ~A(){}  
         void fun() { printf("%d \t %d\n", m_a, m_b); }  
         int m_a;  
         int m_b;  
};  
   
classB  
{  
public:  
         B() { m_c = 3;}  
         ~B(){}  
         void fun() { printf("%d\n",m_c); } //为什么是1,思考!  
         int m_c;  
};  
intmain()  
{  
         A a;  
         B *pb = (B*)&a;  
         pb->fun();                //为什么是1,思考!  
          
         cout << &a <<endl;       //12FF6C  
         cout << &(a.m_a) <<endl; //12FF6C  
   
         printf("%08x\n",&A::m_a);  
         printf("%08x\n",&A::m_b);  
         printf("%08x\n",&B::m_c);  
   
         return 0;  
}

17.     一个含有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整形数。

int(*p[10])(int)

18.      有了malloc/free为什么还用new/delete?

1)malloc/free为C/C++标准库函数;new/delete为C++运算符。他们都可以申请和释放动态内存。

2)只用malloc/free无法满足非内部数据类型的要求;对象在创建的时候自动调用构造函数,在销毁的时候自动调用析构函数;而malloc/free是库函数而不是运算符,不再编译器控制权限之内,所以不能把调用构造函数和析构函数的任务强加给它们。

19.      注意下列的取值

int main()  
{  
         int a[] = {1,2,3,4,5};  
         int *ptr = (int*)(&a+1); //1代表1个sizeof(a)  
         printf("%d %d\n",*(a+1),*(ptr-1));//2 , 5  
         return 0;  
}  
   
int main()  
{  
         char* a[] ={"hello","the","word"};  
         char** pa = a; //pa为指向字符串数组的指针.  
         pa++;  
         cout << *pa << endl;  //the  
   
         cout << *pa[0] << endl; //t  
         cout << *(*pa+1)<< endl;//h  
         cout << *(*pa+2) <<endl;//e  
         return 0;  
} 

20.    题解:深拷贝+浅显拷贝,为什么?

通俗解释深、浅拷贝:

深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。

浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。

如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝

class CA  
{  
public:  
CA(int b,char* cstr)  
{  
           cout << "CAconstructor!" << endl;  
           a=b;  
           str=new char[b];  
           strcpy(str,cstr);  
}  
void Show()  
{  
           cout<<str<<endl;  
}  
~CA()  
{  
           cout << "~CAconstructor!" << endl;  
           delete str;  
}  
   
private:  
int a;  
char *str;  
};  
   
int main()  
{  
CA A(10,"Hello!");  
CA B=A; //此处会调用默认的拷贝构造函数,是为浅拷贝。  
B.Show();  
//浅拷贝后A,B对象的str字符串是同一个地址。当发生析构时会出现运行报错!  
return 0;  
}

自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

//对比浅拷贝,以下是深拷贝。

#include"stdafx.h"  
#include<iostream>  
usingnamespace std;  
   
class CA  
{  
public:  
         CA(int b,char* cstr)  
         {  
                   cout << "CAconstructor!" << endl;  
                   a=b;  
                   str=new char[b];  
                   strcpy(str,cstr);  
         }  
         //自定义拷贝构造函数  
        CA(constCA& C)  
        {  
                   a=C.a;  
                  str=newchar[a]; //深拷贝  
                  if(str!=0)  
                  strcpy(str,C.str);  
        }  
         void Show()  
         {  
                   cout<<str<<endl;  
         }  
         ~CA()  
         {  
                   cout << "~CAconstructor!" << endl;  
                   delete str;  
         }  
   
private:  
         int a;  
         char *str;  
};  
   
int main()  
{  
         CA A(10,"Hello!");  
         CA B=A;  
         B.Show();  
         return 0;  
}

深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。

浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错

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

21.   为什么Delete会出错?

class CBase  
{  
public:  
         CBase() { cout <<"CBase" << endl; }  
         virtual ~CBase() { cout <<"~CBase" << endl;}  
};  
   
classCDerived : public CBase  
{  
public:  
         CDerived() { cout <<"CDerived" << endl; }  
         ~CDerived() { cout <<"~CDerived" << endl; }  
};  
   
int main()  
{  
         CBase base;  
         CBase* pBase = new CBase;  
         pBase = &base;  
         delete pBase; //运行时报错!  
}


【分析如下】:

1.pBase指向了栈区内存,那是系统管理的空间,不能用delete释放的。

2.程序在堆区new的空间最后没有被释放,造成了内存泄露。

3.最好不要随便把申请到堆区空间的指针指向别处,至少也要有一个指针指向申请的空间。以便最后释放的是自己申请的那块内存。

【修正后做法】:

int main()  
{  
    CBase base;  
    CBase* pBase = new CBase;  
    CBase* pBase2 = pBase;   //至少也要有一个指针指向申请的空间  
    pBase = &base;  
    delete pBase2;  //以便最后释放的是自己申请的那块内存。  
} //运行时不再报错!

【再深入点】:程序有两个问题:

1.内存泄露,new出来的没delete;

2.两次析构;base不是new出来,在生命周期结束(也就是你函数结束的时候)会自动释放,你主动调用delete将其析构,系统在函数结束时又会对其析构,所以才会报错。而且报错的地方应该是程序退出时

22.   类中静态常成员变量的定义?

#include<iostream>  
usingnamespace std;  
//可以在类的声明中对常量的类变量进行赋值  
//VS2008可以,vc6.0不可以。和编译器有关。  
class myclass  
{  
public:  
         static const int i=20; //只有类的静态常量数据成员才可以在类中初始化。  
};  
const int myclass::i = 10;  
int main()  
{  
         cout<<myclass::i<<endl;  
         return 0;  
}

23.  重载和多态的关系?

不同点

重载overload

覆盖override

1.是否支持多态?

不支持

支持

2.存在形式?

可以在类中或在C++语言中都可以体现

存在于类中父类、子类之间。

3.参数列表、返回值

参数列表或返回值不同,或二者都不同。

参数列表、返回指标必须相同。

24.  输出格式:printf

%a(%A)    

浮点数、十六进制数字和p-(P-)记数法(C99)

%c           

字符

%d            

有符号十进制整数

%f             

浮点数(包括floatdoulbe)

%e(%E)    

浮点数指数输出[e-(E-)记数法]

%g(%G)    

浮点数不显无意义的零"0"

%i             

有符号十进制整数(%d相同)

%u            

无符号十进制整数

%o            

八进制整数    e.g.     0123

%x(%X)     

十六进制整数0f(0F)   e.g.   0x1234

%p            

指针

%s            

字符串  

25.  一个参数或指针可以既是const又是volatile)解读?

Volatile 以防止编译器将其优化成从寄存器中读取。一个定义为volatile的变量时说这个变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

一个参数可以既是const又是volatile,const是不让程序修改,volatile是意想不到的改变,不是程序修改。一个指针也可以是volatile,中断服务子程序修改一个指向buffer的指针。

26.  Little Endian低字节序(由低字节—>高字节存储);计算机默认的为低字节序。

High Endian高字节序(由高字节—>低字节存储)

typedef struct bitstruct  
{  
         int b1:5;  
         int b2:2;  
         int b3:2;  
}bitstruct;  
   
int main()  
{  
         bitstruct b;  
         memcpy(&b,"EMCEXAMINATION",sizeof(b));  
   
         cout << sizeof(b) << endl;  
   
         printf("%d,%d\n",b.b1,b.b2);//5,-2  
         return 0;  
} 

[解读]:1.sizeof(b)=4;即4个字节的大小。

2.memcpy将”EMC …”存入b中。

3.实质b中只有5+2+2,9位。即对应字符也只有”EM”.E的ASCII码为0X45,M的ASCII码为0X4D。

满足高字节—>低字节存储0X4D0X45,对应二进制位:01001101 0100 0101

4.对应的b1,满足(低字节存放于低位)取得后低地址的5位0 0101,首位为0代表正数,大小为5。

对应的b2,取10,首位为1代表负数,取补码后得到b2=-2。

27.输出结果?

int main()  
{  
         int a[5][2] = {0,1,2,3,4,5,6,7,8,9};  
          
         int *p = a[0];  
         int (*p2)[2] = &a[1];  
         ++p;  
         ++p2;  
          
         printf("%d\n",*p); //1 p 是整型指针,初始指向元素0,加1指向1  
         printf("%d\n",**p2); //4 p2是含2个元素的数组指针,初始指向元素2,该指针加1是向后移动2个数据,所以指向4  
         printf("%d \n",p2[1][2]); //如何解读? 见下解读。   
         return 0;  
}

 

0

1

0

0 (p)

1(++p后)

1

2  (P2指向)

3

2

4 (++p2后)

5

3

6  (p2+1后)

7

4

8  (p2[1][2])

9

 解读:p2是一个指针,是一个指向包含两个元素数组的指针变量。和普通的指针不同的地方时它指向的长度为2。(*p2)[2]和a是等价的。

对于p2[1][2],此时p2指向4,前一个下标1就是p2指针再加1指向6,后一个下标加2移动2个元素,指向了8。

28.拷贝构造输出结果?

class A  
{  
    static int objectCount;  
public:  
    A()  
    {  
        objectCount++;  
        cout << "A():" <<objectCount << endl;  
    }  
     
    A(const A& r)  
    {  
        objectCount++;  
        cout << "A(const A&r):" << objectCount << endl;  
    }  
          
    ~A()  
    {  
        objectCount--;  
        cout << "~A():"<< objectCount << endl;  
    }  
};  
   
intA::objectCount = 0;  
   
A f(A x)          
{  
    cout << endl <<  "Begin: f(A x)" << endl;  
    return x;   //【临时对象】调用默认拷贝构造函数A(const A& r):3  
}                //~A():2  
   
int main()  
{  
    A h;        //A():1  
    A h2 = f(h); //调用默认拷贝构造函数A(constA& r):2  
    cout << endl <<"End(main): f(A x)" << endl << endl;  
          
    return 0;     
}               //~A():1  析构h2     
//~A():0  构函h

29.四类强制类型转换

类型

示意

举例

static_cast

1.类型转换,编译器隐式执行的任何类型都可由static_cast显示完成;2.使用类型信息执行转换,在转换执行必要的检测(越界检测、类型检查),操作相对安全;

int—>double

int ival;

double result = static_cast<double> ival

const_cast

转换掉对象的const属性

下举例

dynamic_cast

运行时类型检查,用于继承体制下的由上到下的转换downcast。

下举例

reinterpret_cast

1.仅仅重新编译了给定对象的比特模型,而没有进行二进制转换;2.为操作数提供低层次的重新解释。

下举例

举例:

//const_cast 实例.  
class B  
{  
public:  
         int m_num;  
};  
   
int main()  
{  
         B b0;  
         b0.m_num = 100;  
         const B b1 = b0;  
         cout << b0.m_num<< " " << b1.m_num << endl;  
// 以下修改const对象的值是错误的。  
//       b1.m_num = 355;  
//       cout << b1.m_num <<endl;  // error C2166: l-value specifiesconst object  
   
//以下使用const_cast是正解.  
         const_cast<B&>(b1).m_num =355;  
         cout<< b1.m_num << endl;  
         return 0;  
}  
   
//reinterpret_cast实例  
int main()  
{  
         int n = 9;  
         double dval =reinterpret_cast<double& >(n);  
         double dval_new =static_cast<double>(n); //成功.  
         //[仅仅复制了n的比特位到d,没有进行必要的分析]  
         cout << dval << endl;//2.64214e-308  
         cout << dval_new << endl;//9  
         return 0;  
}  
//dynamic_cast实例  
class B  
{  
public:  
         B() { cout << "B()"<< endl; }  
         ~B() { cout << "~B()"<< endl; }  
};  
class C :public B  
{  
public:  
         C() { cout << "C()"<< endl; }  
         ~C() { cout << "~C()"<< endl; }  
};  
class D :public C  
{  
public:  
         D(){ cout << "D()"<< endl; }  
         ~D(){ cout << "~D()"<< endl; }  
};  
   
void f(D* pd)  
{  
         C* pc =dynamic_cast<C*>(pd);   // ok: C isa direct base class  
         // pc points to C subobject of pd  
   
         B* pb =dynamic_cast<B*>(pd);   // ok: B isan indirect base class  
         // pb points to B subobject of pd      
}  
   
int main()  
{  
         D objd;  
         f(&objd);  
         return 0;  
}  

30. 如何在C/C++中显示当前程序所在的文件名及行号。

——这个当时没答上来,见过没记住。今天查了下MSDN如下:

__FILE__, //用于显示文件名的宏 %s, 格式如【F:\NeuSoftDemo\NeuSoftDemo.cpp】;__LINE__, //用于显示行号的宏 %d,格式如【12】;

扩展》》__DATE__, //用于显示当前日期,格式如【Sep 18 2012】 %s; __TIME__, //用于显示当前时间,格式如【09:45:01】 %s;

__TIMESTAMP__,//用于显示当前日期和时间,格式如【Tue Sep 18 09:48:07 2012】 %s。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值