一、简答题。(共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 <iostream> #include <cstring> 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<<"名字为:"<<name<<"id:"<<id<<endl; } private: char* name; int id; static int next_id; }; int Person::next_id = 0; class Student:public Person { public: Student() { label = new char[1]; strcpy(label, ""); grade = 0; } Student(char* _name,char* _label,int _grade):Person(_name) { label = new char[strlen(_label)+1]; strcpy(label, _label); grade = _grade; } Student(const Student& s):Person(s) { label = new char[strlen(s.label)+1]; strcpy(label, s.label); grade = s.grade; } const Student& operator=(const Student& s) { if (this != &s) { Person::operator=(s); delete [] label; label = new char[strlen(s.label)+1]; strcpy(label, s.label); grade = s.grade; } return *this; } ~Student() { delete [] label; label = NULL; } void print() { Person::print(); cout<<"label:"<<label<<"grade:"<<grade<<endl; } private: char* label; int grade; }; class GraduateStudent:public Student { public: GraduateStudent() { message = new char[1]; strcpy(message, ""); salary = 1000; } GraduateStudent(char* _name,char* _label,int _grade,char* _message,int _salary):Student(_name,_label,_grade) { message = new char[strlen(_message)+1]; strcpy(message, _message); salary = _salary; } GraduateStudent(const GraduateStudent& g):Student(g) { message = new char[strlen(g.message)+1]; strcpy(message, g.message); salary = g.salary; } const GraduateStudent& operator=(const GraduateStudent& g) { if (this != &g) { Student::operator=(g); delete [] message; message = new char[strlen(g.message)+1]; strcpy(message, g.message); salary = g.salary; } return *this; } ~GraduateStudent() { delete [] message; message = NULL; } void print() { Student::print(); cout<<"message:"<<message<<"salary:"<<salary<<endl; } private: char* message;//盛放毕业信息 int salary; }; int main() { Person p; Person p1("zhangsan"); Person p2(p1); p2.print(); p2 = p; p1.print(); cout<<"test student"<<endl; Student s; Student s1("lisi","student1",100); Student s2(s1); s2.print(); s2=s; s1.print(); cout<<"test graduatestudent"<<endl; GraduateStudent gs; GraduateStudent gs1("wangwu","student2",99,"biyesheng",10000); GraduateStudent gs2(gs1); gs2.print(); gs2 = gs; gs1.print(); return 0; } 四、 实现以下的String类 class String { friend bool operator==(const String&, const String&); friend String operator+(const String&, const String&); friend bool operator<(const String&, const String&); public: String(); String(char*); String(const String&); const String& operator=(const String&); const String& operator+=(const String&); const char& at(int) const; // 如果越界要抛出异常 char& at(int); // 如果越界要抛出异常 const char& operator[](int)const; char&operator[](int); int length(); private: char* rep; // 存放字符串 int len; // 字符串的长度 }; 最佳答案是: #include <iostream> #include <cstring> 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<<s.size<<endl; out<<s.rep<<endl; return out; } //cin>>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<<"请输入您所输入的字符串的长度"<<endl; in>>s.size; delete [] s.rep; s.rep = new char[s.size+1]; cout<<"请输入您所输入的字符串"<<endl; in>>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 rep String 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.析构函数可以声明为纯虚函数,但是必须要给出定义。