封装是实现面向对象程序设计的第一步,封装就是将数据或者函数等集合一个个单元中,也叫做类,可以保护数据和防止代码被破坏。
继承是为了实现代码的重复利用,子类可以继承父类的一些属性和方法。
多态是指同样的操作或者函数,过程可作用于多种类型的对象上并获得不同的结果。不同的对象,接受到同一消息而产生不同的结果,这种现象叫做多态。
封装是使得代码模块化,继承是可以扩展已存在的代码,他们都是为了代码重用。
而多态的目的是为了接口重用,“一个接口,多种方法”,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
多态的定义是:C++的多态性主要是通过虚函数来实现的,虚函数允许子类重写override。
(而overload是重载是允许同名函数的出现,而这些函数参数列表/类型不同。)
虚函数:被virtual关键字修饰的成员函数,作用是为了实现多态性,多态性是将接口与实现进行分离 。
而不能声明为虚函数的则是 构造函数 因为要清楚的知道要构造什么,否则无法构造一个对象。
析构函数可以是纯虚数。
为什么要用纯虚数?
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为基类可以派生出老虎,孔雀等子类,但动物本身生成对象明显不合常理。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
在什么情况下使用纯虚函数(pure vitrual function)?
- 当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;
- 这个方法必须在派生类(derived class)中被实现
虚函数与纯虚函数的区别
虚函数为了重载和多态。 在基类中是有定义的,即便定义为空。 在子类中可以重写。
纯虚函数在基类中没有定义, 必须在子类中加以实现。
重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
常考的题目。
1. 从定义上来说:
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义父类虚函数的方法。
2. 从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译 器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的 调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
函数重载是什么意思?它与虚函数的概念有什么区别?
答:函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。而虚函数实现的是在基类中通过使用关键字virtual来申明一个函数为虚函数,含义就是该函数的功能可能在将来的派生类中定义或者在基类的基础之上进行扩展,系统只能在运行阶段才能动态决定该调用哪一个函数,所以实现的是动态的多态性。它体现的是一个纵向的概念,也即在基类和派生类间实现。
常见的STL容器有哪些,算法哪些
STL包括两部分,容器和算法。(重要的还有融合这二者的迭代器)
介绍一下STL,详细说明STL如何实现vector。
STL是标准模版库,由容器算法迭代器组成。
STL有以下的一些优点:
(1)可以很方便的对一堆数据进行排序(调用sort());
(2)调试程序时更加安全和方便;
(3)stl是跨平台的,在linux下也能使用。
vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间。
什么是容器。如何实现?
容器是一种类类型,用来存储数据
STL有7种主要容器:vector,list,deque,map,multimap,set,multiset.
1. 容器
即为存放数据的地方。分为两类。
- 序列式容器:vector,list,deque等
- 关联式容器: 内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有兼职和一个实值。
举例:vector是动态分配存储空间的容器。
2. 算法
排序,复制等等,与各个容器相关联。
3. 迭代器
STL的精髓。可以这样描述,它提供了一种方法,使之能够按照顺序访问某个容器所含的各个元素,但是无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。
开发中常用到的数据结构有哪些。
数组,链表,树。也会用到栈(先进后出)和队列(先进先出)。
1.数组和链表的区别。(很简单,但是很常考,记得要回答全面)
C++语言中可以用数组处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。而在实际应用中,用户使用数组之 前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。链表是一种常见的数据组织形式,它 采用动态分配内存的形式实现。需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。
从逻辑结构来看:
数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况,即数组的大小一旦定义就不能改变。当数据增加时,可能超出原先 定义的元素个数;当数据减少时,造成内存浪费;链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删 除数据项时,需要移动其它数据项)。
从内存存储来看:
(静态)数组从栈中分配空间(用NEW创建的在堆中), 对于程序员方便快速,但是自由度小;链表从堆中分配空间, 自由度大但是申请管理比较麻烦.
从访问方式来看:
数组在内存中是连续存储的,因此,可以利用下标索引进行随机访问;链表是链式存储结构,在访问元素的时候只能通过线性的方式由前到后顺序访问,所以访问效率比数组要低。
全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?
【参考答案】
生命周期不同:
全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;
使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。
操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。
const与static的用法
1. const:
- const修饰类的成员变量,表示该成员变量不能被修改。
- const修饰函数,表示本函数不会修改类内的数据成员。不会调用其他非const成员函数。
- const函数只能调用const函数,非const函数可以调用const函数
类外定义的const成员函数,在定义和声明出都需要const修饰符
总结const的应用和作用?
答:(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
2. static:
2.1 对变量:
a. 局部变量:
在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。位于内存中静态存储区; 未初始化的局部动初始化为0. 作用域仍是局部作用域.
注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改为静态存储区)及其生命周期(局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问),但未改变其作用域。
b. 全局变量.
在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。静态存储区,未经初始化的全局静态变量会被程序自动初始化为0,全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。注: static修饰全局变量并未改变其存储位置及生命周期, 而是改变了其作用域,使得当前文件外的源文件无法访问该变量.不能被其他文件访问和修改,其他文件中可以使用相同名字的变量,不会产生冲突.
2.2 对类:
a. 成员变量.
用static修饰类的数据成员实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static成员必须在类外进行初始化(初始化格式: int base::var=10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化 。
注意:
- 不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。
- 静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。
- 静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的指针或引用。
b. 成员函数
注意:
a. 用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针。
b. 静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。base::func(5,3);当static成员函数在类外定义时不-需要加static修饰符。
c. 在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。因为静态成员函数不含this指针。
d. 不可以同时用const和static修饰成员函数。
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
类的static变量在什么时候初始化,函数的static变量在什么时候初始化。
类的静态成员在类实例化之前就存在了,并分配了内存。函数的static变量在执行此函数时进行实例化。
指针和引用
- 指针是一个变量,存放地址的变量,指向内存的一个存储单元,引用仅是个别名。
- 引用必须被初始化, 指针不必
- 引用使用时无需加*。
- 引用没有const修饰,指针有const修饰
- sizeof引用对象得到的是所指对象,变量的大小;sizeof指针得到的是指针本身的大小
- 指针可有多级,引用只可一级
- 内存分配上,程序为指针变量分配内存,不为引用分配内存。
引用的必要了,两大好处:1、传参数时避免拷贝;2、重载操作符时返回值可以继续用来接收操作数
引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
申明一个引用的时候,切记要对其进行初始化。
引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
不能建立数组的引用。
int a=4; int &f(int x) { a=a+x; return a; } int main(void) { int t=5; cout<<f(t)<<endl; //a = 9 f(t)=20; //a = 20 cout<<f(t)<<endl; //t = 5,a = 20 a = 25 t=f(t); // a = 30 t = 30 cout<<f(t)<<endl; } // t = 60 }
什么是指针?谈谈你对指针的理解?
答:指针是一个变量,该变量专门存放内存地址;
指针变量的类型取决于其指向的数据类型,在所指数据类型前加*
指针变量的特点是它可以访问所指向的内存。
什么是常指针,什么是指向常变量的指针?
答:常指针的含义是该指针所指向的地址不能变,但该地址所指向的内容可以变化,使用常指针可以保证我们的指针不能指向其它的变量,
指向常变量的指针是指该指针的变量本身的地址可以变化,可以指向其它的变量,但是它所指的内容不可以被修改。指向长变量的指针定义,
指针的几种典型应用情况?
答:
int *p[n];—–指针数组,每个元素均为指向整型数据的指针。
int (*)p[n];—p为指向一维数组的指针,这个一维数组有n个整型数据。
int *p();——函数带回指针,指针指向返回的值。
int (*)p();—-p为指向函数的指针。
内存
1.内存类别
栈 --由编译器自动分配释放, 局部遍历存放位置
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
堆 --由程序员分配和释放.
从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
全局区(静态区) --全局变量和静态变量的存储是放在一起的, 初始化的全局变量和static静态变量在一块区域.
从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
程序代码区 --存放二进制代码.
在函数体中定义的变量通常是在栈上, 用malloc, 等分配内存的函数分配得到的就是在堆上. 在所有函数体外定义的是全局量, 加了static修饰符后不管在哪里都存放在全局区, 在所有函数体外定义的static变量表示在该文件有效, 不能extern 到别的文件用. 在函数体内定义的static表示只在该函数体内有效.
2. 堆栈溢出的原因:
数组越界, 没有回收内存, 深层次递归调用
3. 内存分配方式
内存分配的三种方式: a 静态存储区,程序编译时便分好, 整个运行期间都存在,比如全局变量,常量; b, 栈上分配; 堆上分配。
4. 避免内存泄漏
原因:动态分配的内存没有手动释放完全.
避免:使用的时候应记得指针的长度; 分配多少内存应记得释放多少, 保证一一对应的关系; 动态分配内存的指针最好不要再次赋值.
new和malloc的算法.
malloc和free是c语言的标准库函数, new/delete是c++的运算符.都可用来申请动态内存释放.
由于malloc/free是库函数,不是运算符,因此不能将执行构造函数和析构函数的任务强加于malloc/free. c++需要一个能够完成动态内存分配和初始化工作的运算符new. 主要, new/delete不是库函数.
c++可以调用c函数, 而c程序只能用malloc/free管理动态内存.
new可以认为是malloc加构造函数的执行. new出来的指针都是直接带类型信息的, 而malloc返回的都是void指针
delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数
delete与delete【】的区别
delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。在More Effective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。”delete与new配套,delete []与new []配套。
定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。
频繁出现的短小函数, 在c和c++中分别如何实现。
c中使用宏定义, c++中使用inline内联函数
数组与指针的区别。
数组被定义在静态存储区(全局变量)或栈上, 指针可指向任意类型数据块。
char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char *str5 = "abc"; const char *str6 = "abc"; char *str7 = "abc"; char *str8 = "abc"; cout << ( str1 == str2 ) << endl;//0 分别指向各自的栈内存 cout << ( str3 == str4 ) << endl;//0 分别指向各自的栈内存 cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同 cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同
void * ( * (*fp1)(int))[10];
float (*(* fp2)(int,int,int))(int);
int (* (* ( * fp3)())[10])()
分别表示什么意思?
【标准答案】
1.void * ( * (*fp1)(int))[10]; fp1是一个指针,指向一个函数,这个函数的参数为int型,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个void*型指针。
2.float (*(* fp2)(int,int,int))(int); fp2是一个指针,指向一个函数,这个函数的参数为3个int型,函数的返回值是一个指针,这个指针指向一个函数,这个函数的参数为int型,函数的返回值是float型。
3.int (* ( * fp3)())[10](); fp3是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是int型。
结构struct与联合union的区别。
(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
(2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
全局对象的构造函数会在main函数之前执行
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
extern“C”有什么作用?
答:Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。
Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。可以用一句话概括extern “C”这个声明的真实目的:实现C++与C及其它语言的混合编程。
SendMessage和PostMessage有什么区别
答案:SendMessage是阻塞的,等消息被处理后,代码才能走到SendMessage的下一行。PostMessage是非阻塞的,不管消息是否已被处理,代码马上走到PostMessage的下一行。
CMemoryState主要功能是什么
答案:查看内存使用情况,解决内存泄露问题。
#if!defined(AFX_…_HADE_H)
#define(AFX_…_HADE_H)
……
#endif作用?
答:防止该头文件被重复引用。
数组在做函数实参的时候会转变为什么类型?
答:数组在做实参时会变成指针类型。
系统会自动打开和关闭的3个标准的文件是?
(1) 标准输入—-键盘—stdin
(2) 标准输出—-显示器—stdout
(3) 标准出错输出—-显示器—stderr
在Win32下 char, int, float, double各占多少位?
(1) Char 占用8位
(2) Int 占用32位
(3) Float 占用32位
(4) Double 占用64位
strcpy()和memcpy()的区别?
答:strcpy()和memcpy()都可以用来拷贝字符串,strcpy()拷贝以’\0’结束,但memcpy()必须指定拷贝的长度。
windows消息系统由哪几部分构成?
答:由一下3部分组成:
1. 消息队列:操作系统负责为进程维护一个消息队列,程序运行时不断从该消息队列中获取消息、处理消息;
2. 消息循环:应用程序通过消息循环不断获取消息、处理消息。
3. 消息处理:消息循环负责将消息派发到相关的窗口上使用关联的窗口过程函数进行处理。
什么是UDP和TCP的区别是什么?
答:TCP的全称为传输控制协议。这种协议可以提供面向连接的、可靠的、点到点的通信。
UDP全称为用户报文协议,它可以提供非连接的不可靠的点到多点的通信。用TCP还是UDP,那要看你的程序注重哪一个方面?可靠还是快速?
成员函数通过什么来区分不同对象的成员数据?为什么它能够区分?
答:通过this指针指向对象的首地址来区分的。
C++编译器自动为类产生的四个缺省函数是什么?
答:默认构造函数,拷贝构造函数,析构函数,赋值函数。
构造函数与普通函数相比在形式上有什么不同?(构造函数的作用,它的声明形式来分析)
答:构造函数是类的一种特殊成员函数,一般情况下,它是专门用来初始化对象成员变量的。
构造函数的名字必须与类名相同,它不具有任何类型,不返回任何值。
构造函数的调用顺序是什么?
答:1.先调用基类构造函数
2.按声明顺序初始化数据成员
3.最后调用自己的构造函数。
const char *p和char * const p; 的区别
答:
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
子类析构时要调用父类的析构函数吗?
答:会调用。析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了
构造函数和析构函数的调用顺序? 析构函数为什么要虚拟?
答案:构造函数的调用顺序:基类构造函数—对象成员构造函数—派生类构造函数;析构函数的调用顺序与构造函数相反。析构函数虚拟是为了防止析构不彻底,造成内存的泄漏。
请说出类中private,protect,public三种访问限制类型的区别
答:private是私有类型,只有本类中的成员函数访问;protect是保护型的,本类和继承类可以访问;public是公有类型,任何类都可以访问.
TCP/IP 建立连接的过程
答:在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送连接请求到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到客户端连接请求,向客户端发送允许连接应答,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的允许连接应答,向服务器发送确认,客户端和服务器进入通信状态,完成三次握手
//为了实现链式操作,将目的地址返回,加10分! char * strcpy( char *strDest, const char *strSrc ) { assert( (strDest != NULL) && (strSrc != NULL) ); char *address = strDest; while( (*strDest++ = * strSrc++) != ‘\0‘ ); return address; }
//普通构造函数 String::String(const char *str) { if(str==NULL) { m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志‘\0‘的空 *m_data = ‘\0‘; //加分点:对m_data加NULL 判断 } else { int length = strlen(str); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, str); } } // String的析构函数 String::~String(void) { delete [] m_data; } //拷贝构造函数 String::String(const String &other) // 得分点:输入参数为const型 { int length = strlen(other.m_data); m_data = new char[length+1]; //加分点:对m_data加NULL 判断 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]; //加分点:对m_data加NULL 判断 strcpy( m_data, other.m_data ); return *this; //得分点:返回本对象的引用 }
变量的声明和定义有什么区别
变量的声明是告诉编译器我有某个类型的变量,但不会为其分配内存。但是定义会分配了内存。
什么是“野指针”?
野指针指向一个已删除的对象或无意义地址的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力避免。
造成的主要原因是:指针变量没有被初始化,或者指针p被free或者delete之后,没有置为NULL。
常量指针和指针常量的区别?
常量指针:是一个指向常量的指针。可以防止对指针误操作而修改该常量。
指针常量:是一个常量,且是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。但是指针常量的内容是可以改变。