这是本人面试之前整理的C++面试题,感觉还可以,知识点挺全的,如果大家需要我会即时发布。
1. New delete 与malloc free 的联系与区别?
malloc 与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于在堆区上申请动态内存和释放内存。
用malloc函数需要指定分配的字节数并且不能初始化对象,new会自动调用对象的构造函数,delete会调用对象的析构函数,而free不会调用对象的析构函数。
1) malloc 、free
函数原型:void* malloc(size_t size);
调用方法:
(数椐类型*)malloc(size)
如: char* p;
p = (char*)malloc(100*sizeof(char));// 分配 100 个字节的内存空间
函数原型:void free(void *ptr);
调用方法:
free(void* ptr);
如: int *ip;
ip = (int*)malloc(100*sizeof(int));
free(ip);
2) new delete
使用方法:
type *p = new type;
其中type是一个数椐类型,p是指向该类型的指针。
如: type *p = new type[size];
int *ip = new int[100];// 数组分配形式
int *iq = new int(100);// 单个变量分配并赋值。
使用方法:
delete p;// 单个变量
p = NULL;
delete []q;// 数组或结构的等多
q = NULL; 个变量。
2. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?
宏就是对字符串进行替换。
因此:i = 5*DOUBLE(5) 相当于 i = 5*x+x => i = 5*5+5
#include <iostream>
using namespace std;
#define DOUBLE(x) x+x
//#define DOUBLE(x) ( x+x )
int main()
{
double i = 0;
cout << “i = ” << 5*DOUBLE(5) << endl;// 结果是 30
return 0;
}
3. 有哪几种情况只能用intialization list 而不能用assignment? ( 注意:把引用和对象成员的代码也加上吧)
答:1.当类中含有const、reference 成员变量,对象成员;基类的构造函数都需要初始化。
2. 当基类有带参构造,子类就应当声明一个将参数传递给基类构造函数的途径。
3. 当基类派生子类对象时,就要对基类数据成员等初始化。
示例代码:
#include<iostream>
using namespace std;
class A
{
private:
const int a; // const 成员
const int b; // const 成员
public:
A(int i,int j):a(i),b(j) // 必须在这里初始化
{
}
void print()
{
cout << "a=" << a << ",b=" << b << endl;
}
};
int main()
{
A a(1,2);
a.print();
return 0;
}
#include<iostream>
using namespace std;
class A
{
private:
int x1;
public:
A(int i) // 只有一个带参的构造函数
{
x1 = i;
}
void printA()
{
cout << "x1=" << x1 << endl;
}
};
class B:public A
{
private:
int x2;
public:
B(int i):A(i + 10) // 必须在这里初始化
{
x2 = i;
}
void printB()
{
printA();
cout << "x2=" << x2 << endl;
}
};
int main()
{
B b(2);
b.printB();
return 0;
}
4. 简述数组与指针的区别?
答: 数组与指针的区别?
1 、修改内容上的差别
char a[] = "abcd";
a[0] = 'e';
char *p = "abcd";//p 指向的是常量字符串
p[0] = 'e';// 编绎时不报错,运行时崩溃
2.sizeof() 值不同
cout << sizeof(a) << endl;// 5 计算的是数组的所占字节数
cout << sizeof(p) << endl;// 4 计算指针变量所占字节数
特殊情况:如果数组做函数的参数时行传递时 sizeof() 的值为 4
void show(char str[100])
{
cout << sizeof(str) << endl;// 4 数组自动退化为同类型的指针
}
3 、指针的本质是一个与地址相关的复合类型,它的值是数据存放的位置(地址);数组的本质则是一系列的变量。
int a[10] = {0};
int *p = a;
cout << &a << endl;// 数组 a 的地址
cout << p << endl;//p 的值为数组 a 的地址
4 、数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。
int a[10] = {1,2,3,4};
int *p = a;
cout << p++ << endl;//p 指向发生变化
cout << a++ << endl;// 报错
问题:指针与数组
听说char a[]与char *a是一致的,是不是这样呢?
指针和数组存在着一些本质的区别。当然,在某种情况下,比如数组作为函数的参数进行 传递时,由于该数组自动退化为同类型的指针,所以在函数内部,作为函数参数传递进来的指针与数组确实具有一定的一致性,但这只是一种比较特殊的情况而已,在本质上,两者是有区别的。请看以下的例子:
char a[] = "Hello!";
char *p = "Hello!";
上述两个变量的内存布局分别如下:
数组a需要在内存中占用7个字节的空间,这段内存区通过名字a来标志。指针p则需要4个字节的空间来存放地址,这4个字节用名字p来标志,目前这个p指向某地连续的7个字节,即字符串“Hello!”。
另外,例如:对于a[2]和p[2],二者都返回字符‘i’,但是编译器产生的执行代码却不一样。对于a[2],执行代码是从a的位置开始,向后移 动2两个字节,然后取出其中的字符。对于p[2],执行代码是从p的位置取出一个地址,在其上加2,然后取出对应内存中的字符。
示例代码:
#include<iostream>
using namespace std;
int main()
{
int a[] = {1,2,3,4,5,6,7,8,9};
for(int i=0; i<9; i++)
{
cout << "a[" << i << "]:" << a[i] << " ";
cout << "&a[" << i << "]:" << &a[i] << " ";
cout << "a+" << i << ":" << (a + i) << " ";
cout << "*(a+" << i << "):" << *(a + i) << " ";
cout << endl;
}
cout << endl;
int *p = &a[0];
for(i=0; i<9; i++)
{
cout << "p[" << i << "]:" << p[i] << " ";
cout << "&p[" << i << "]:" << &p[i] << " ";
cout << "p+" << i << ":" << (p + i) << " ";
cout << "*(p+" << i << "):" << *(p + i) << " ";
cout << endl;
}
return 0;
}
5. 描述内存分配方式以及它们的区别?
1 栈区分配。 局部变量、const对象。函数执行结束时这些存储单元自动被释放。
(函数的形参:函数调用时才被分配内存单元,调用结束后内存单元被释放。)
2 数据区分配。全局变量,静态变量。常量。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。
全局变量,静态变量 生命周期:到程序结束。。
3 堆区上分配,动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活.
#include <stdlib.h>
void* malloc(size_t size);
int* p;
p = (int*)malloc( 10*sizeof(int) );
if(!p)
{
cout << “memeroy failed “ << endl;
}
free(p);// 申请失败
p = (int*)realloc(p, 20*sizeof(int))// 重新分配
//////////////////////////////////
int* p = new int(5);
if(!P)// 申请失败
{
cout << “memeroy failed “ << endl;
};
delete p;
p = NULL;
6. 分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。
bool : if ( !a ) or if(a)
int : if ( a == 0)
float : const float EXP = 0.000001
if ( a < EXP && a >-EXP)
//float 精度问题, float 类型的 “ 零值 ” 不一定是 “ 0” , 所以必须不能直接比较
指针 : if ( a != NULL) or if(a == NULL)
7. 请说出const与#define 相比,有何优点?
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而#define 只作简单的字符串替换,无类型安全检查。
(2)const 在编译时分配存储空间,而#define在预编译时编译,不分配存储空间。
(3)有些集成化的调试工具可以对const进行调试,但不能对宏进行调试。
8. 分析一下这段程序的输出
#include <iostream>
#include <string>
using namespace std;
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};
B Play(B b)
{
return b ;
}
//(1)
/// *
int main(int argc, char* argv[])
{
B t1 = Play(5);
B t2 = Play(t1);
return 0;
}
//* /
/*
//(2)
int main(int argc, char* argv[])
{
B t1 = Play(5);
B t2 = Play(10);
return 0;
}
*/
结果:
(1)
constructed by parameter 5
destructed
destructed
destructed
destructed
(2)
constructed by parameter 5
destructed
constructed by parameter 10
destructed
destructed
destructed
说明:
如果类的成员函数中有拷贝构造函数,则将调用的拷贝构造的原理和过程说明一下,并解释由于编译环境的不同,编译的结果也有所不同(如此题中在Linux的g++中报编译错误,而在VC6.0中不报编译错误,但显示调用的拷贝构造的过程(解释为编译器的升级导致));如果没有,则无需说明拷贝构造的过程,
9. 类成员函数的重载(overload)、覆盖(override)和隐藏(override)区别?
作用域:
重载(1)相同的范围(在同一个类中);
覆盖(重写) (1)不同的范围(分别位于派生类与基类);
隐藏 (1)不同的范围(分别位于派生类与基类);
表现形式:
重载
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖(重写)
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
隐藏
(2)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(3)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
注:如果要调用基类被隐藏的函数,则加成员名限定。
举例:
#include <stdio.h>
#include <iostream>
using namespace std;
class A
{
public:
A()
{}
virtual ~A()
{}
// 重写
virtual void Gun(int a, int b)
{
cout << "A~Gun(int a, int b)" << endl;
}
Void Kun()// 隐藏 1
{
cout << "A~Kun(int a)" << endl;
}
//virtual Kun();
Void Run(int a)// 隐藏 2
{
cout << "A~Run(int a)" << endl;
}
};
class B:public A
{
public:
B()
{}
~B()
{}
// 重载
void Fun()
{
cout << "B~Fun()" << endl;
}
void Fun(int a)
{
cout << "B~Fun(int a)" << endl;
}
// 重写
Void Gun(int a, int b)
{
cout << "B~Gun(int a, int b)" << endl;
}
// 隐藏 1 (参数不同即可隐藏)
Void Kun(int a)
{
cout << "B~Kun(int a)" << endl;
}
// 隐藏 2 (函数同名,参数相同,没有 virtual )
Void Run(int a)
{
cout << "B~Run(int a)" << endl;
}
}
int main(int argc, char* argv[])
{
A a;
B b;
// 重写
a.Gun(1, 2);
b.Gun(1, 2);
// 重载
b.Fun(4);
b.Fun();
// 隐藏 1
//b.Kun();// 隐藏了
b.Kun(3);
// 隐藏 2
a.Run(4);
b.Run(5);
return 0;
}
10. const 符号常量;
(1)const char *p
(2)char const *p
(3)char * const p
说明上面三种描述的区别;
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向的内容不可修改;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是不可修改。
(1) const char *p
一个指向char类型的const对象指针,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如:
(2) char const *p
这两个好象是一样的,此时*p可以修改,而p不能修改。
举例:
const char *p;
char c1='a';
char c2='b';
p=&c1;//ok
p=&c2;//ok
*p=c1;//error// 不能通过 p 的指向来修改 p 所指向的变量的值。
(3)char * const p
一个指向char类型的指针,此指针p的值不可修改,但p所指向的变量的值可修改。
(4)const char * const p
这种是地址及指向对象都不能修改。
添加下面解释:注意面试解释时尽量使用英文,显示更加专业。
1). char * const cp; ( * 读成 pointer to )
cp is a const pointer to char ,cp值不可改变,但*cp,也就是cp所指对象能够改变。
2). const char * p;
p is a pointer to const char ,亦即指向常量的指针,所以p所指的对象不可改变。
11. 下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么?
int n;
if (n == 10) // 第一种判断方式
if (10 == n) // 第二种判断方式
第二种代码风格更好。
第一种方式:如果==误写为 = ,if语句永远为真。
第二种方式:如果==误写为 = ,10=n,把一个变量赋给一个常量,系统直接报错。
同理: if(NULL == point) 与if(point ==NULL )
如果误写成:if(point =NULL )
可能会无法delete相应的内存区域导致内存泄漏!
12. 在不用第三方参数的情况下,交换两个参数的值
方法一:
Swap(x,y)
{
x=x+y;
y=x-y;
x=x-y;
}
方法二:
Swap(x,y)
{
x=x-y;
y=x+y;
x=y-x;
}
方法三:
Swap(x,y)
{
x=y-x;
y=y-x;
x=y+x;
}
方法四:
i^=j;
j^=i;
i^=j;
方法五:
a = a+b-(b=a)