1.什么是面向对象
- 面向对象是一种编程思想,把一切东西看成是一个个对象,比如人、耳机、鼠标、水杯等,他们各自都有属性,比如:耳机是白色的,鼠标是黑色的,水杯是圆柱形的等等,把这些对象拥有的属性变量和操作这些属性变量的函数打包成一个类来表示;
- 面向过程和面向对象的区别
- 面向过程:根据业务逻辑从上到下写代码
- 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程
2.面向对象的三大特征
- 封装、继承、多态
-
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理,开放一些公有的成员函数对成员合理的访问。所以封装本质是一种管理。
-
继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
三种继承方式
-
多态:用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,实现多态,有二种方式,重写、重载。
3.简述一下C++的重载和重写,以及他们的区别
-
重写
是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类对象调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。
-
重载
我们在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同。例如:交换两个数的值其中包括(int, float,char,double)这些个类型。在C语言中我们是利用不同的函数名来加以区分。这样的代码不美观而且给程序猿也带来了很多的不便。于是在C++中人们提出了用一个函数名定义多个函数,也就是所谓的函数重载。函数重载是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
4.C++的重载和重写是如何实现的
- C++利用命名倾轧(name mangling)技术,来改名函数名,区分参数不同的同名函数。命名倾轧是在编译阶段完成的。
- 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
- 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
- 存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
- 多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。
- 重写用虚函数来实现,结合动态绑定。
- 纯虚函数是虚函数再加上= 0。
- 抽象类是指包括至少一个纯虚函数的类。
纯虚函数:virtual void fun()=0。即抽象类必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。
5.C语言如何实现C++语言中的重载
- c语言中不允许有同名函数,因为编译时函数命名是一样的,不像c++会添加参数类型和返回类型作为函数编译后的名称,进而实现重载。如果要用c语言显现函数重载,可通过以下方式来实现:
- 使用函数指针来实现,重载的函数不能使用同名称,只是类似的实现了函数重载功能
- 重载函数使用可变参数,方式如打开文件open函数
- gcc有内置函数,程序使用编译函数可以实现函数重载
#include<stdio.h>
void func_int(void * a)
{
printf("%d\n",*(int*)a); //输出int类型,注意 void * 转化为int
}
void func_double(void * b)
{
printf("%.2f\n",*(double*)b);
}
typedef void (*ptr)(void *); //typedef申明一个函数指针:typedef 返回类型(*新类型)(参数表)
void c_func(ptr p,void *param)
{
p(param); //调用对应函数,实现“函数重载”
}
int main()
{
int a = 23;
double b = 23.23;
c_func(func_int,&a);
c_func(func_double,&b);
return 0;
}
6.构造函数有几种,分别什么作用(不全…继续整理)
4类:默认构造函数、初始化构造函数、拷贝构造函数。
- 默认构造函数和初始化构造函数。 在定义类的对象的时候,完成对象的初始化工作。
class Student
{
public:
//默认构造函数
Student()
{
num=1001;
age=18;
}
//初始化构造函数
Student(int n,int a):num(n),age(a){}
private:
int num;
int age;
};
int main()
{
//用默认构造函数初始化对象S1
Student s1;
//用初始化构造函数初始化对象S2
Student s2(1002,18);
return 0;
}
有了有参的构造了,编译器就不提供默认的构造函数。
- 拷贝构造函数
#include "stdafx.h"
#include "iostream.h"
class Test
{
int i;
int *p;
public:
Test(int ai,int value)
{
i = ai;
p = new int(value);
}
~Test()
{
delete p;
}
Test(const Test& t) // 拷贝构造函数
{
this->i = t.i;
this->p = new int(*t.p);
}
};
//拷贝构造函数用于复制本类的对象
int main(int argc, char* argv[])
{
Test t1(1,2);
Test t2(t1);//将对象t1复制给t2。注意复制和赋值的概念不同
return 0;
}
赋值构造函数默认实现的是值拷贝(浅拷贝)。
7.只定义析构函数,会自动生成哪些构造函数
- 只定义了析构函数,编译器将自动为我们生成拷贝构造函数和默认构造函数。
8.一个类,默认会生成哪些函数
// 定义一个空类
class Empty
{
};
// 默认会生成以下几个函数
// 1.无参的构造函数,在定义类的对象的时候,完成对象的初始化工作。
Empty()
{
}
// 2.拷贝构造函数,拷贝构造函数用于复制本类的对象
Empty(const Empty& copy)
{
}
// 3.赋值运算符
Empty& operator = (const Empty& copy)
{
}
// 4.析构函数(非虚)
~Empty()
{
}
// 构造函数的调用规则:
// 如果用户定义了普通的构造函数(非拷贝构造),C++不会提供默认无参构造,但会提供默认拷贝构造函数
// 如果用户定义了拷贝构造函数,C++不会提供任何默认构造函数
9.C++类对象的初始化顺序,有多重载继承情况下的顺序
-
创建派生类的对象,基类的构造函数优先被调用(也优先于派生类里的成员类);
-
如果类里面有成员类,成员类的构造函数优先被调用;(也优先于该类本身的构造函数)
-
基类构造函数如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;
-
成员类对象构造函数如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;
-
派生类构造函数,作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)
-
综上可以得出,初始化顺序:
父类构造函数–>成员类对象构造函数–>自身构造函数
其中成员变量的初始化与声明顺序有关,构造函数的调用顺序是类派生列表中的顺序。
析构顺序和构造顺序相反。
10.向上转型和向下转型
- 向上转型:子类转换为父类,使用dynamic_cast(expression),这种转换相对来说比较安全不会有数据的丢失;
- 向下转型:父类转换为子类,可以使用强制转换,这种转换时不安全的,会导致数据的丢失,原因是父类的指针或者引用的内存中可能不包含子类的成员的内存。
11.深拷贝和浅拷贝,如何实现深拷贝
- 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其