您的位置:首页 > 职场人生

常见IT企业面试题(4)-每日5题

2017-03-11 21:00 459 查看

1.C++有哪些特性?什么是多态?多态是怎么实现的?

封装,继承,多态是面向对象思想的”三大特征“。

多态性的定义:同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

有两种类型的多态性:

*1.编译时的多态性。编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数,返回的类型等信息决定实现何种操作;

*2.运行时的多态性。运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C++中,运行时的多态性通过虚成员来实现。

2.C++继承,多态机制,讲讲对多态的理解,析构函数为什么是虚函数?

继承机制使得派生类能够获得基类的成员数据和方法,只需要在派生类中增加基类没有的成员。多态是建立在继承的基础上,它使用了C++编译器最核心的一个技术,即动态绑定技术。其核心思想是负累对象调用子类对象的方法。

C++中继承主要有三种关系:public,protected和private。

public继承是一种接口继承,子类可以代替父类完成父类接口所声明的行为。此时子类可以自动转换成为父类的接口,完成接口转换。从语法角度来说,public继承会保留父类中成员(包括函数和变量等)的可见性不变,也就是说,如果父类中的某个函数是public的,那么在被子类继承后仍然是public的。

protected继承是一种实现继承,子类不能代替父类完成父类接口所声明的行为,此时子类不能自动转换成父类的接口。从语法角度来说,protected继承会将父类中的public可见性的成员修改成为protected可见性,相当于在子类中引入了protected成员,这样在子类中同样还是可以调用父类的protected和public成员,子类的子类也就可以调用被protected继承的父类的protected和public成员。

private继承是一种实现继承,子类不能代替父类完成父类接口所声明的行为,此时子类不能自动转换成为父类的接口。从语法角度上来说,private继承会将父类中的public和protected可见性的成员修改成为private可见性。这样一来,虽然子类中同样还是可以调用父类的protected和public成员,但是子类的子类就不可以再调用被private继承的父类的成员了。

多态性的定义:同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

有两种类型的多态性:

*1.编译时的多态性。编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数,返回的类型等信息决定实现何种操作;

*2.运行时的多态性。运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C++中,运行时的多态性通过虚成员来实现。

析构函数可以为虚函数,也可以不为虚函数。(更多的时候不为虚函数)

设计析构函数为虚函数,主要是考虑到继承。

当A为基类,B为A的继承类,考虑如下情况:

A *p = new B();

…..

delete p;

如果此时A的析构函数不是虚函数,那么在delete p的时候就会调用A的析构函数,而不会调用B的析构函数,这样就会造成B的资源没有释放。

而如果A的析构函数为虚函数,那么就会调用B的析构函数,一切正常。

3.结构体和枚举有什么区别?

枚举类型:

enum Position
{
x,
y,
z
}
static void Main(String[ ] args)
{
Position pos = Position.x; // x为Position里的属性
Console.WriteLine(pos);
int num = (int) pos;//显示转换
Console.WriteLine(num); //结果为0
}


枚举类型默认为是int类型,并默认为从0,1,2,3…递增排序,也可为属性赋值,也可改变枚举类型的int类型(如将其换成byte来减少内存的消耗)如:

enum Position:byte
{
x = 100,
y = 200,
z = 134
}


结构体:

struct Student
{
public String name;
public String school;
}
static void Main(String[ ] args)
{
Student tom;
tom.name = "Tom";
tom.school = "ZK";
Console.WriteLine(tom.name + tom.school);
}


结构体可以将有限个不同类型的属性变量组合在一起,与枚举类型不同之处是枚举类型内的都是同类型的属性变量,并且结构体可以有结构函数。如:

stuct Student
{
public String name;
public String school;
public String info(int time)
{
return name +" "+ school + time;
}
}
static void Main(String[ ] args)
{
Student tom;
tom.name = "Tom";
tom.school = "ZK";
int mytime = 10;
Console.WriteLine(tom.info(mytime));
}


4.复制构造函数需要注意一些什么?

构造函数的作用是使类的成员变量有合适的初值,调用是时机是在创建该类的对象的时候,由编译器自动调用.

拷贝构造函数是指类的对象在创建的时候,能使用已有的对象之间初始化它.或者是在类的对象在作为函数形参的时候,或者类的对象在作为函数返回值的时候作为复制的机制需要自动拷贝类型值.

5.宏和const的区别?指针和引用的区别?

1.差别:const与#define最大的差别在于:前者在堆栈分配了空间,而后者只是把具体数值直接传递到目标变量罢了,#define不占用内存单元,每次调用都会分配内存。

或者说,const的常量是一个Run-Time的概念,他在程序中确确实实的存在可以被调用、传递。而#define常量则是一个Compile-Time概念,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。const常量存在于程序的数据段.#define常量存在于程序的代码段。

2优缺点:至于两者的优缺点,要看具体的情况了。

从执行效率上来说#define是一个更好的选择:

i.从run-time的角度来看,他在空间上和时间上都有很好优势。

ii.从compile-time的角度来看,类似m=t*10的代码不会被编译器优化,t*10的操作需要在run-time执行。而#define的常量会被合并。但是:如果你需要粗鲁的修改常数的值,那就的使用const了,因为后者在程序中没有实际的存在.另外在头文件中使用 #define 可以避免头文件重复包含的问题,这个功能,是const无法取代的

从执行稳健性来说用const会好一些:

在C语言中,还有用于定义常数的宏#define,它做是值代替(即文本代替),没有类型检查工具。而const提供了类型检查,可以避免值代替时容易出现的一些问题。安全检查。而且字符替换可能会带来料想不到的边界效应。 有些集成化工具可以对const常量进行调试, 但不能对宏量进行调试。所以C++中我们一般提倡用const,C语言就看情况使用了。

_————————————–

1.指针和引用的定义和性质区别:

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

int a=1;int *p=&a;

int a=1;int &b=a;

上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。

而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

(2)可以有const指针,但是没有const引用;

(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;

(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

(6)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

(7)指针和引用的自增(++)运算意义不一样;

2.指针和引用作为函数参数进行传递时的区别。

#include<iostream>
using namespace std;

void swap(int *a,int *b)
{
  int temp=*a;
  *a=*b;
  *b=temp;
}

int main(void)
{
  int a=1,b=2;
  swap(&a,&b);
  cout<<a<<" "<<b<<endl;
  system("pause");
  return 0;
}


结果为2 1;

用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址,因此使用*a实际上是取存储实参的内存单元里的数据,即是对实参进行改变,因此可以达到目的。

再看一个程序:

#include<iostream>
using namespace std;

void test(int *p)
{
  int a=1;
  p=&a;
  cout<<p<<" "<<*p<<endl;
}

int main(void)
{
int *p=NULL;
test(p);
if(p==NULL)
cout<<"指针p为NULL"<<endl;
system("pause");
return 0;
}


运行结果为:

0x22ff44 1

指针p为NULL

大家可能会感到奇怪,怎么回事,不是传递的是地址么,怎么p回事NULL?事实上,在main函数中声明了一个指针p,并赋值为NULL,当调用test函数时,事实上传递的也是地址,只不过传递的是指地址。也就是说将指针作为参数进行传递时,事实上也是值传递,只不过传递的是地址。当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,即上面程序main函数中的p何test函数中使用的p不是同一个变量,存储2个变量p的单元也不相同(只是2个p指向同一个存储单元),那么在test函数中对p进行修改,并不会影响到main函数中的p的值。

如果要想达到也同时修改的目的的话,就得使用引用了。

2.将引用作为函数的参数进行传递。

在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

看下面这个程序:

#include<iostream>
using namespace std;

void test(int &a)
{
  cout<<&a<<" "<<a<<endl;
}

int main(void)
{
int a=1;
cout<<&a<<" "<<a<<endl;
test(a);
system("pause");
return 0;
}


输出结果为: 0x22ff44 1

0x22ff44 1

再看下这个程序:

这足以说明用引用进行参数传递时,事实上传递的是实参本身,而不是拷贝。

所以在上述要达到同时修改指针的目的的话,就得使用引用了。

#include<iostream>
using namespace std;

void test(int *&p)
{
  int a=1;
  p=&a;
  cout<<p<<" "<<*p<<endl;
}

int main(void)
{
int *p=NULL;
test(p);
if(p!=NULL)
cout<<"指针p不为NULL"<<endl;
system("pause");
return 0;
}


输出结果为:0x22ff44 1

指针p不为NULL
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  面试题