一、简答题。(共4题,共20分,每题5分)1、void main(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, “world”);printf(str);}}请问运行main函数会有什么样的结果?最佳答案是:篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str成为野指针, if(str != NULL)语句不起作用。2、void GetMemory(char **p, int num){ char *p[];*p = (char *)malloc(num);}void main(void){char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);}请问运行main函数会有什么样的结果?最佳答案:能够输出hello,内存泄漏3、char *GetMemory(void){char p[] = "hello world";return p;}void main(void){char *str = NULL;str = GetMemory();printf(str);}请问运行main函数会有什么样的结果?最佳答案是:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。4、void GetMemory(char *p){p = (char *)malloc(100);}void main(void){char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);}请问运行Test函数会有什么样的结果?最佳答案是:程序崩溃。因为GetMemory并不能传递动态内存, Test函数中的 str一直都是 NULL。 strcpy(str, "hello world");将使程序崩溃。而且GetMemory函数体内申请的堆内存为释放,造成内存泄漏。二、问答题。(共10题,每题4分,共40分)1、解释C++三大核心概念。参考答案是:封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。继承:子类自动继承父类所有的成员(除了构造函数,析构函数,赋值运算符重载),子类可以重写父类的方法,子类也可以扩展成员,使用继承可以达到代码重用或复用的目的。多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。(多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。)2、拷贝构造函数在哪几种情况下调用?参考答案是:解答要点:用一个对象初始化另一个对象时 当用对象作为函数参数传递时 当函数以值的形式返回对象时3、虚函数,纯虚函数的概念。参考答案是:函数声明时被virtual关键字修饰,可用于实现多态;没有函数体的虚函数为纯虚函数,纯虚函数被派生类继承后一般需要实现,否则派生类也是抽象类。4、什么叫抽象类?抽象类有何作用?抽象类的派生类是否一定要实现纯虚函数?参考答案是:有纯虚函数的类为抽象类,抽象类不能被实例化,但是可以声明抽象类的指针或者引用;抽象类的主要作用是通过它为一个类族(其派生类)建立一个公共的接口,使它们能够更有效地发挥多态特性;抽象类声明了一组派生类共同操作接口的通用语义,而接口的完整实现,即纯虚函数的函数体,要由派生类自己给出。但抽象类的派生类并非一定要给出纯虚函数的实现,如果派生类没有给出纯虚函数的实现,这个派生类仍然是一个抽象类5、什么是动态绑定,动态绑定有什么好处?参考答案是: 虚函数在被调用时,具体调用哪个版本的函数(父类还是子类),在编译时无法确定,只有在运行时才能确定称为动态绑定。动态绑定(C++中,通过基类的引用或指针调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。)6、简单描述多重继承中怎样产生的菱形继承问题,又是如何解决的呢?参考答案是:在多重继承中,多个基类又拥有共同的基类,导致派生类中有多份“爷爷”类中的成员,从而导致成员变量冗余以及成员函数调用的歧义;使用虚继承解决菱形继承问题,直接继承祖先的两个类,在继承时使用虚继承,通过多重继承而来的那个子类(孙子类)在构造对象时,直接调用祖先类的构造函数,孙子辈的派生类,直接继承祖先类的成员,再继承两个父类各自扩展的成员。7、什么是Big Three?为什么程序中要实现Big Three?参考答案是:Big Three包括拷贝构造函数、析构函数以及赋值运算符重载函数;当类中有指向堆空间的指针成员时,不实现拷贝以及赋值重载,有可能会造成二次删除或者内存泄漏问题,不实现析构函数会出现内存泄漏,其中二次删除有可能导致程序崩溃。8、static_cast和dynamic_cast的区别是什么?参考答案是:都可用于做向下类型转换,static_cast相当于强转,没有是否能转型成功的检查;而使用dynamic_cast进行转型时,编译器会判断待转型的数据所指向对象的真实身份,以此判断是否可以转换成功,如果转型不成功,dynamic_cast表达式则会返回NULL.9、函数传参的三种方式,每种方式都有什么特点?参考答案是:三种方式:值、指针、引用;以“值”方式进行参数传递时,为单向传递,只能由实参传递给形参,传指针时,可以通过指针修改指针所指向的变量,传引用时可以节省空间,相当于给实参取别名,形参与实参公用一块内存空间。10、标准模板库分为哪两大类,各包含哪些类,它们有什么区别?参考答案是:标准模板库中包含顺序容器与关联容器两大类,顺序容器中常用的容器有vector、deque以及list,关联容器中常用的容器包含set、multiset、map以及multimap;顺序容器中的元素是按照存入时的先后顺序进行排列,而关联容器则没有。三、编程题(共10分). 已知strcpy函数的原型是char *strcpy(char *strDest, const char *strSrc);其中strDest是目的字符串,strSrc是源字符串。(1)不调用C++/C的字符串库函数,请编写函数 strcpy(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?最佳答案是:char * strcpy(char * strDest,const char * strSrc){char * strDestCopy=strDest; //[3]if((strDest==NULL)||(strSrc==NULL)) //[1]throw "Invalid argument(s)"; //[2]while((*strDest++=*strSrc++)!='\0'); //[4]return strDestCopy;}返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想三、 使用继承实现以下几个类Person(5分)Student(10分)GarduateStudent(15分)#include#include using namespace std;class Person{public:Person(){next_id++;name = new char[1];strcpy(name, "");id = next_id;}Person(char* _name){next_id++;name = new char[strlen(_name)+1];strcpy(name, _name);id = next_id;}Person(const Person& _p){next_id++;name = new char[strlen(_p.name)+1];strcpy(name, _p.name);id = next_id;}const Person& operator=(const Person& p){if (this != &p){delete []name;name = new char[strlen(p.name)+1];strcpy(name, p.name);id = p.id;}return *this;}~Person(){delete [] name;name = NULL;}void print(){cout<<"名字为:"< <<"id:"< < #include using namespace std;int strcmp1(const char * p, const char * q){while (*p==*q && *p!='\0' && *q!='\0'){p++;q++;}if (*p==*q){return 0;}return *p-*q;}char* strcat1(char * p, const char * q){if (p==NULL || q==NULL){throw "invalid arguments";}char* pt = p;while (*p!='\0'){p++;}while (*q != '\0'){*p++ = *q++;}*p = '\0';return pt;}class String{public:String(){rep = new char[1];strcpy(rep, "");size = 0;}String(char* _rep){size = (int)strlen(_rep);rep = new char[size+1];strcpy(rep, _rep);}String(const String& s){size = s.size;rep = new char[size+1];strcpy(rep, s.rep);}const String& operator=(const String& s){if (this!=&s){delete [] rep;rep = new char[s.size+1];strcpy(rep, s.rep);size = s.size;}return *this;}const char& operator[](int index)const{return rep[index];}char& operator[](int index){return rep[index];}const char& at(int index) const{if(index >= size||index<0)throw "越界";return rep[index];}char& at(int index){if(index >= size||index<0)throw "越界";return rep[index];}const String& operator+=(const String& s){*this = *this + s;return *this;}friend ostream& operator<<(ostream& out,const String& s){out< < < < >p//friend bool operator<(const String& s1, const String& s2){if(strcmp1(s1.rep,s2.rep) == -1)return true;return false;}friend istream& operator>>(istream& in,String& s){ //gets(a);cout<<"请输入您所输入的字符串的长度"< >s.size;delete [] s.rep;s.rep = new char[s.size+1];cout<<"请输入您所输入的字符串"< >s.rep;return in;}friend bool operator==(const String& s1,const String& s2){return !strcmp1(s1.rep, s2.rep);}//"abc" "edf"friend const String operator+(const String& s1,const String& s2){ //size repString tmp(s1);//rep//空间tmp.size+=s2.size;char* temp = tmp.rep;//delete []tmp.rep;tmp.rep = new char[tmp.size+1];strcat1(tmp.rep, temp);strcat1(tmp.rep, s2.rep);delete [] temp;return tmp;}int length(){return size;}~String(){delete [] rep;rep = NULL;}private:char* rep;int size;//字符串的长度};int main(){return 0;}3、链表逆序//将一个链表逆置typedef struct linknode{int data;struct linknode *next;}node;//类型定义node *reverse(node *head){node *p,*q,*r; //q代表当前节点,p代表当前节点的前一个节点,r代表当前节点的下一个节点p=head;q=p->next;while(q!=NULL){r=q->next;q->next=p;p=q;q=r;}head->next=NULL;head=p;return head;}附加题目:在C++语言中,能否声明虚构造函数?为什么?能否声明虚析构函数?有何用途? 答:构造函数不能声明为虚函数,析构函数可以声明为虚函数,但是析构函数不能声明为纯虚函数。1. 每一个拥有虚成员函数的类都有一个指向虚函数表的指针。对象通过虚函数表里存储的虚函数地址来调用虚函数。那虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过虚指针来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有虚指针的。因此,构造函数不能是虚函数。2.析构函数可以声明为虚函数。当基类指针指向派生类对象的时候,通过基类指针删除派生类对象,声明基类析构函数为虚函数,则会调用派生类的析构函数,这样能保证内存不发生泄露。3.析构函数可以声明为纯虚函数,但是必须要给出定义。