c面试题

Posted chzh999

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c面试题相关的知识,希望对你有一定的参考价值。

一、简答题。(共4题,共20分,每题5分)
1void 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)语句不起作用。
2void 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,内存泄漏
3char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void main(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行main函数会有什么样的结果?
最佳答案是:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
4void 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++)!=); //[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!= && *q!=)
{
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!=)
{
p++;
}
while (*q != )
{
*p++ = *q++;
}
*p = ;

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.析构函数可以声明为纯虚函数,但是必须要给出定义。

 

以上是关于c面试题的主要内容,如果未能解决你的问题,请参考以下文章

Java进阶之光!2021必看-Java高级面试题总结

经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!

面试必备 | 常见C++笔试面试题整理

可能碰到的iOS笔试面试题--C语言

一道经典面试题:字符串在Java中如何通过“引用”传递

Mybatis最全的高质量面试题和答案—3