面试题6

101 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.编写String类的构造函数、析构函数和赋值函数

String类的原型为:

class String {
public:
String(const char *str = NULL);//	普通构造函数
String(const String &other);//	拷贝构造函数
~ String(void);//	析构函数
String & operator = (const String &other);//	赋值函数

private:
class *m_data;//	用于保存字符串

};
//普通构造函数
String::String(const char* str) {
	if(str == NULL) {//加分点:对m_data加NULL判断
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空间
		*m_data = '\0';
	}else {
		int length = strlen(str);
		m_data = new char[length + 1];
		strcpy(m_data, str);
	}
}

// String的析构函数
String::~String(void) {
	delete [] m_data; // 或delete m_data;
}
//拷贝构造函数
String::String(const String &other) {// 得分点:输入参数为const型 
	 int length = strlen(other.m_data); 
	 m_data = new char[length+1];     
	 strcpy(m_data, other.m_data); 
}

//赋值函数
String & String::operator =(const String &other) {// 得分点:输入参数为const型
	if(this == &other)   //得分点:检查自赋值
	return *this; 
 	delete [] m_data;     //得分点:释放原有的内存资源
	int length = strlen( other.m_data ); 
	
	m_data = new char[length+1];  
	strcpy( m_data, other.m_data ); 
	return *this;         //得分点:返回本对象的引用
}

2.请回答数组与指针的区别

指针数组
保存数组的地址保存数据
间接的访问数据,首先获取指针的内容,然后将其作为地址,从该地址中提取数据直接访问数据
通常用于动态的数据结构通常用于固定数目且数据类型相同的元素
通过malloc来分配内存,free来释放内存隐式的分配和删除
通常指向匿名数据,操作匿名函数自身即为数据名

3.给定一个三角形ABC和一点P(x, y, z),判断点P是否在ABC内,给出思路并且手写代码

//给定三角形ABC和一点P(x,y,z),判断点P是否在ABC内,给出思路并手写代码

#include <iostream>
#include <math.h>

using namespace std;

#define ABS_FLOAT 0.0001

struct point_float {
	float x;
	float y;
};

//计算三角形面积
float getTriangleSqaur(const point_float pt0, const point_float pt1, const point_float pt2) {
	point_float AB, BC;
	AB.x = pt1.x - pt0.x;
	AB.y = pt1.y - pt0.y;
	BC.x = pt2.x - pt1.x;
	BC.y = pt2.y - pt1.y;

	return fabs((AB.x * BC.y - AB.y * BC.x)) / 2.0f;

}

//判断给定点是否在三角形内或者边上
bool isInTriangle(const point_float A, const point_float B, const point_float C, const point_float D) {
	float SABC, SADB, SBDC, SADC;
	float total_sum = 0;
	
	SABC = getTriangleSqaur(A, B, C);
	SADB = getTriangleSqaur(A, D, B);
	SADC = getTriangleSqaur(A, D, C);
	SBDC = getTriangleSqaur(B, D, C);
	total_sum = SADB + SADC + SBDC;
	
	if ((-ABS_FLOAT < (SABC - total_sum)) && ((SABC - total_sum) < ABS_FLOAT)) {
		return true;
	}
	else {
		return false;
	}
}

int main() {
	point_float pt0;
	pt0.x = 0;
	pt0.y = 0;

	point_float pt1;
	pt1.x = 0;
	pt1.y = 3;

	point_float pt2;
	pt2.x = 4;
	pt2.y = 0;

	point_float pt3;
	pt3.x = 2;
	pt3.y = 0;
		
	int a, b;
	a = getTriangleSqaur(pt0, pt1, pt2);
	b = isInTriangle(pt0, pt1, pt2, pt3);
	cout << "面积:" << a << "\t" << "\t" << "该点是否存在于三角形内部或边上:(1是true,0是false)" << b << endl;
	system("pause");
	return 0;
}

4.尽可能的多说一些static和const的作用

static

  1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
  2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为static。
  3. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
  4. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在static函数内不能访问非静态成员。

const:

  1. 修饰变量,说明该变量不可以被改变;
  2. 修饰指针,分为指向常量的指针和指针常量;
  3. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
  4. 修饰成员函数,说明该成员函数内不能修改成员变量。

5.说说C++中虚函数和纯虚函数的区别

(1)虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类;

(2)虚函数可以被直接使用,也可以被子类重载之后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类中有声明,但是没有定义。

(3)虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用

(4)虚函数和纯虚函数通常都存在于抽象基类中,被继承的子类重载,目的是提供一个统一的接口

(5)虚函数的定义形式:virtual{}; 纯虚函数的定义形式:virtual{ } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因是被static修饰的函数在编译时要求前期绑定,然而虚函数确实动态绑定,而且被两者修饰的函数声明周期也不一样

例子:

class A { 
public:virtual void foo() {
    cout<<"A::foo() is called"<<endl;
    }
};
 
class B:public A { 
public:void foo() {
    cout<<"B::foo() is called"<<endl;
    }
 }; 
int main(void) {
    A *a = new B();
    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!     return 0; 
}

6.说说C++中什么是菱形继承,如何解决?

在这里插入图片描述 假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。因为上述图表的形状类似于菱形,因此这个问题被形象地称为菱形继承问题。现在,我们将上面的图表翻译成具体的代码:

//菱形继承

#include <iostream>
using namespace std;

//动物类(虚基类)
class Animal
{
public:
	int a_weight;

};

//狮子类
class Lion :virtual public Animal {};

//老虎类(利用虚继承可以解决菱形继承的问题)
class Tiger :virtual public Animal {};

//狮虎兽类
class Liger :public Lion, public Tiger {};

void test01() {
	Liger lg;
	//lg.a_weight;//继承了两份数据,不知道用谁的
	
	//出现菱形继承的时候,两个父类拥有相同数据,需要给他们的分配作用域来区分
	lg.Lion::a_weight = 18;
	lg.Tiger::a_weight = 25;

	cout << "lg.Lion::a_weight:"<< lg.Lion::a_weight <<endl;
	cout << "lg.Tiger::a_weight:" << lg.Tiger::a_weight << endl;
	cout << lg.a_weight << endl;

	//这份数据我们只需要一份就够了,菱形继承导致数据有两份,浪费了资源
}

int main() {
	test01();
	return 0;
}

菱形继承的解决方法: 1.出现菱形继承的时候,两个父类拥有相同数据,需要给他们的分配作用域来区分; 2.使用virtual关键字,使父类虚继承基类,当子类再继承父类的时候就不会出现菱形继承

7.请问构造函数中能不能调用虚方法

从语法上来讲,构造函数中调用虚方法使可以的,但是调用之后往往达不到所需的目的。

派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。同样进入基类析构函数时,对象也是基类类型。 所以虚函数始终仅仅调用基类的虚函数(如果是基类调用虚函数),不能达到多态的效果,所以放在构造函数中是没有意义的,而且还达不到想要的效果。

8.简述一下拷贝赋值和移动赋值

拷贝赋值是通过拷贝构造函数来赋值,在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象

移动赋值是通过移动构造函数来赋值

两者的主要区别: (1)拷贝构造函数是一个左值引用,而移动构造函数的形参是一个右值引用; (2)拷贝构造函数完成的是整个对象或变量的拷贝,而移动构造函数是生成一个指针指向源对象或者变量的地址,接管源对象的内存,相对于大量数据的拷贝,节省了时间和内存空间。

9.虚函数表里存放的内容是什么时候写进去的?

1.虚函数表是一个存储虚函数地址的数组,以NULL接尾。虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的vfptr,然后调用构造函数。即:虚表在构造函数之前写入。

2.除了在构造函数之前写入之外,我们还需要考虑到虚表的二次写入机制,通过此机制让每个对象的虚表指针都能准确的指到自己类的虚表,为实现动态多态提供支持。

10.说说C++中智能指针和指针的区别是什么?

1.智能指针:如果在程序中使用new从堆(自由存储区)分配内存,等到不需要时,应该使用delete将其释放。C++中引用了智能指针auto_ptr,以帮助自动完成这个过程,后来C++摈弃了auto_ptr,并新增了三种智能指针,unique_ptr、shared_ptr和weak_ptr。所有新增的智能指针都能与STL容器和移动语义协同工作。

2.指针:C语言中规定所有变量在使用前必须先定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是专门用来存放地址的,所以必须将它定义为“指针类型”。

3.智能指针和指针的区别在于智能指针实际上是堆普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命周期。