求职应聘C++复习小结
扫描二维码
随时随地手机看文章
2014年我写的博客比较少,总结的也比较少。2015年我绝对不能松懈,毕竟知识不总结容易遗忘。2014年有很多工作没完成,比如找个好的大公司实习结果来了个小公司、学习Java、Python、在github上搭建自己的个人博客都没有完成。2015年,有很多事情要做,秋季找正式工作、毕业论文等,所以更加需要总结所学知识,为秋季招聘做准备。今年年假13天里,学习方面只把《C++ Primer》这本书过了一遍。下面是对相关知识点做的小结。
构造函数的调用顺序为:调用基类的构造函数->调用成员对象的构造函数->调用自身的构造函数,析构函数调用反之。
9,解释堆和栈的区别
答:1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
由系统自动分配。声明在函数中一个局部变量 int b;系统自动在栈中为b开辟空间。
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域,栈的大小是2M。
如果申请的空间超过栈的剩余空间时,将提示overflow。
栈由系统自动分配,速度较快。但程序员是无法控制的。
函数调用时,第一个进栈的是主函数中后的下一条指令,的地址,然后是函数的各个参数。
在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,需要程序员自己申请,并指明大小,在c中malloc函数
在C++中用new运算符。首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
堆是向高地址扩展的数据结构,是不连续的内存区域。而链表的遍历方向是由低地址向高地址。
堆的大小受限于计算机系统中有效的虚拟内存。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便
一般是在堆的头部用一个字节存放堆的大小。
11,C++的空类,默认产生哪些类成员函数?
class Empty
{
public:
Empty(); //缺省构造函数
Empty( const Empty& ); //拷贝构造函数
~Empty(); //析构函数
Empty& operator=( constEmpty& ); //赋值运算符
Empty* operator&(); //取址运算符
const Empty* operator&()const; //取址运算符 const
};
默认构造函数
析构函数
拷贝构造函数
赋值运算符(operator=)
取址运算符(operator&)(一对,一个非const的,一个const的)
12,谈谈类和结构体的区别
答:结构体在默认情况下的成员都是public的,而类在默认情况下的成员是private的。结构体和类都必须使用new创建,
struct保证成员按照声明顺序在内存在存储,而类不保证。
13,const_cast 、static_cast、dymnaic_cast及reinterpreter_cast的区别?
答:(1)const_cast
字面上理解就是去const属性,去掉类型的const或volatile属性。
struct SA{ int k}; const SA ra;
ra.k = 10; //直接修改const类型,编译错误 SA& rb = const_cast
(2)static_cast//居中
主要用于基本类型之间和具有继承关系的类型之间的转换。用于指针类型的转换没有太大的意义
static_cast是无条件和静态类型转换,可用于基类和子类的转换,基本类型转换,把空指针转换为目标类型的空指针,
把任何类型的表达式转换成void类型,static_cast不能进行无关类型(如非基类和子类)指针之间的转换。
int a; double d = static_cast
int &pn = &a; void *p =static_cast
(3)dynamic_cast//比较安全的转化
你可以用它把一个指向基类的指针或引用对象转换成继承类的对象
动态类型转换,运行时类型安全检查(转换失败返回NULL)
基类必须有虚函数,保持多态特性才能用dynamic_cast
只能在继承类对象的指针之间或引用之间进行类型转换
class BaseClass{public: int m_iNum; virtual void foo(){};};
class DerivedClass:BaseClass{public: char*szName[100]; void bar(){};};
BaseClass* pb = new DerivedClass();
DerivedClass *p2 =dynamic_cast
BaseClass* pParent =dynamic_cast
//子类->父类,动态类型转换,正确
当基类指针指向基类时不能通过这种转化为子类指针
(4)reinterpreter_cast//最暴力的转化
转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。
主要是将一个类型的指针,转换为另一个类型的指针
不同类型的指针类型转换用reinterpreter_cast
最普通的用途就是在函数指针类型之间进行转换
int DoSomething(){return 0;};
typedef void(*FuncPtr)(){};
FuncPtr funcPtrArray[10];
funcPtrArray[0] =reinterpreter_cast
16,简单叙述面向对象的三个基本特征
答:封装性
把客观事物封装成抽象的类,对自身的数据和方法进行(public,private, protected)
继承性
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
实现继承是指使用基类的属性和方法而无需额外编码的能力;
接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface而不是 Class
多态性
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,
父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖(子类重新定义父类的虚函数),重载(允许存在多个同名函数,参数个数,类型不同)。
18,什么是预编译,何时需要预编译
答:就是指程序执行前的一些预处理工作,主要指#表示的.
需要预编译的情况:总是使用不经常改动的大型代码体。所有模块都使用一组标准的包含文件和相同的编译选项。
http://feihe027.blog.163.com/blog/static/5932583320130256177961/
19,memset ,memcpy和strcpy的根本区别?
答:memset用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为' '或'';
它对较大的结构体或数组进行清零操作的一种最快方法。
char temp[30]; memset(temp,' ',sizeof(temp));
char temp[30]只是分配了一定的内存空间给该字符数组,但并未初始化该内存空间,即数组。所以,需要使用memset()来进行初始化。
memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;
strcpy就只能拷贝字符串了,它遇到' '就结束拷贝;例:chara[100],b[50];strcpy(a,b); char *s="Golden Global View"; char d[20];memcpy(d,s,strlen(s));
20,多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?
答:虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.
而对象的隐藏成员--虚拟函数表指针是在运行期也就是构造函数被调用时进行初始化的,这是实现多态的关键.
22,进程和线程的差别?
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:创建撤消进程,系统都要为之分配和回收资源,系统的开销明显大于创建撤消线程
多进程与多线程,两者都可以提高程序的并发度,提高程序运行效率和响应时间。
23,请说出static关键字尽可能多的作用
答:(1)函数体内作用范围为该函数体,该变量内存只被分配一次,具有记忆能力
(2)在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
24,头文件的作用是什么?
答:一,通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
二,头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
25,在C++程序中调用C编译后的函数,为什么要加externC的声明?
答:因为C++支持函数重载,而C不支持函数重载,函数被C++编译后在库中的名字与C语言的不同。
假设某个函数的原型为:void foo(int x, int y);该函数被C编译器编译后在库中的名字为_foo,
而C++编译器则产生像_foo_int_int之类的名字。 C++提供extern C来解决名字匹配问题
26,C++中哪些函数不能被声明为虚函数?
答:普通函数(非成员函数),构造函数,内联成员函数、静态成员函数、友元函数。
(1)虚函数用于基类和派生类,普通函数所以不能
(2)构造函数不能是因为虚函数采用的是虚调用的方法,允许在只知道部分信息的情况的工作机制,特别允许调用只知道接口而不知道对象的准确类型的方法,但是调用构造函数即使要创建一个对象,那势必要知道对象的准确类型。
(3)内联成员函数的实质是在调用的地方直接将代码扩展开
(4)继承时,静态成员函数是不能被继承的,它只属于一个类,因为也不存在动态联编等
(5)友元函数不是类的成员函数,因此也不能被继承
27,数组int c[3][3];为什么c,*c的值相等,(c+1),(*c+1)的值不等, c,*c,**c,代表什么意思?
答:c是第一个元素的地址,*c是第一行元素的首地址,其实第一行元素的地址就是第一个元素的地址,
**c是提领第一个元素。为什么c,*c的值相等?
c:数组名;是一个二维指针,它的值就是数组的首地址,也即第一行元素的首地址(等于 *c),
也等于第一行第一个元素的地址( & c[0][0]);可以说成是二维数组的行指针。
*c:第一行元素的首地址;是一个一维指针,可以说成是二维数组的列指针。
**c:二维数组中的第一个元素的值;即:c[0][0]
所以:c和 *c的值是相等的,但他们两者不能相互赋值,(类型不同)
(c + 1):c是行指针,(c + 1)是在c的基础上加上二维数组一行的地址长度,
即从&c[0][0]变到了&c[1][0];
(*c + 1):*c是列指针,(*c + 1)是在*c的基础上加上二数组一个元素的所占的长度,
&c[0][0]变到了&c[0][1],从而(c + 1)和(*c + 1)的值就不相等了。
32,用一个宏定义FIND求一个结构体struc中某个变量相对struc的偏移量
答: #define FIND(struc, e) (size_t)&( ((struc*)0)->e )
解析:其中(struc*)0表示将常量0转化为struc*类型指针所指向的地址。
&( ((struc*)0)->e )表示取结构体指针(struc*)0的成员e的地址,因为该结构体的首地址为0,
所以其实就是得到了成员e距离结构体首地址的偏移量,(size_t)是一种数据类型,为了便于不同系统之间的移植,
最好定义为一种无符号型数据,一般为unsigned int
当sizeof的参数为结构或类时候
结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置。
与结构或者类的实例地址无关。没有成员变量的结构或类的大小为1,
因为必须保证结构或类的每一实例在内存中都有唯一的地址
34,在main函数执行之前,还会执行什么代码和工作
答:运行全局构造器,全局对象的构造函数会在main函数之前执行
设置栈指针,初始化static静态和global全局变量,即数据段的内容
将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等
将main函数的参数,argc,argv等传递给main函数
编译C++程序时编译器自动定义了一个预处理器名字__cplusplus注意前面有两个下
划线因此我们可以根据它来判断该程序是否是C++程序以便有条件地包含一些代码
例如:
#ifdef __cplusplus
//不错我们要编译C++
// extern "C"
extern "C"
#endif
int min( int, int );
在编译标准C时编译器将自动定义名字__STDC__当然__cplusplus与__STDC__
不会同时被定义。
另外两个比较有用
39,运算符重载的三种方式和不允许重载的5个运算符
答:运算符重载意义是为了对用户自定义数据的操作和内定义的数据类型的操作形式一致
(1)普通函数,友元函数,类成员函数
(2).*(成员指针访问运算符)
::(域运算符)
sizeof长度运算符
?:条件运算符
.(成员访问运算符)
40,友元关系有什么特性?
答:单向的,非传递的,不能继承的.
42,关键字volatile有什么含意?并给出三个不同的例子
答:一个定义为volatile的变量是说这变量可能会被意想不到地改变,编译器就不会去假设这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值
而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automaticvariables)
3)多线程应用中被几个任务共享的变量
深究:一个参数既可以是const还可以是volatile,一个例子是只读的状态寄存器,
它是volatile因为它可能被意想不到地改变,是const因为程序不应该试图去修改它。
一个指针可以是volatile,一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
43,动态连接库的两种方式?
答:调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-timedynamic linking),模块非常明确调用某个导出函数
,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向
系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-timedynamic linking),运行时可以通过LoadLibrary或Loa
dLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的
出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。
48,main主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答:可以,可以用_onexit注册一个函数,它会在main之后执行int fn1(void), fn2(void), fn3(void), fn4 (void)
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first.n" );
}
int fn1()
{
printf( "next.n" );
return0;
}
int fn2()
{
printf( "executed " );
return0;
}
int fn3()
{
printf( "is " );
return0;
}
int fn4()
{
printf( "This " );
return0;
}
5.将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:
类型标识符 &函数名(形参列表及类型说明)
{
//函数体
}
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意:
(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用(这个要注意啦,很多人没意识到,哈哈。。。)。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用:
流操作符<
(5)在另外的一些操作符中,却千万不能返回引用:+-*/四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。
6.“引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例(见:C++中类的多态与虚函数的使用)。
8.什么时候需要“引用”?
流操作符<
i++和++i的
最重要的区别大家都知道就是 +1和返回值的顺序
但,两这还有一个区别(在C++中)就是i++在实现的时候,产
生了一个local
object
class INT;
//++i 的版本
INT INT::operator++()
{
*this=*this+1;
return *this;
}
//i++ 的版本
const INT INT::operator ++(int)
{
INT oldvalue=*this;
*this=*this+1;
return oldvalue
}
所以从效率上来说++i比i++来的更有效率
36. 如何打印出当前源文件的文件名以及源文件的当前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的
34.类成员函数的重载、覆盖和隐藏区别?
答案:
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
33.简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] =“hello”;
a[0] = ‘X’;
char *p =“world”; // 注意p指向常量字符串
p[0] =‘X’; // 编译器不能发现该错误,运行时错误
(2)用运算符sizeof可以计算出数组的容量(字节数)。sizeof(p),p为指针得到的是一个指针变量的字节数,而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[]="hello world";
char*p = a;
cout<<sizeof(a)<< endl; // 12字节
cout<<sizeof(p)<< endl; // 4字节
计算数组和指针的内存容量
void Func(chara[100])
{
cout<<sizeof(a)<< endl; //4字节而不是100字节
}
14.#include
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
2015.02.25 晚